@martintrojer/mu 0.4.1 → 0.4.2
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/dist/cli.js +286 -47
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +72 -48
- package/dist/index.js +58 -14
- package/dist/index.js.map +1 -1
- package/docs/USAGE_GUIDE.md +38 -0
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/db.ts","../src/logs.ts","../src/detect.ts","../src/tmux.ts","../src/reconcile.ts","../src/snapshots/capture.ts","../src/snapshots/core.ts","../src/snapshots/prune.ts","../src/snapshots/restore.ts","../src/tasks/core.ts","../src/staleness.ts","../src/workspace/core.ts","../src/workspace/crud.ts","../src/agents/spawn.ts","../src/output.ts","../src/agents/errors.ts","../src/vcs/git.ts","../src/vcs/helpers.ts","../src/vcs/types.ts","../src/vcs/jj.ts","../src/vcs/none.ts","../src/vcs/sl.ts","../src/vcs/index.ts","../src/workspace/decorate.ts","../src/workspace/orphans.ts","../src/workspace/recreate.ts","../src/tasks/queries.ts","../src/tasks/id.ts","../src/tasks/errors.ts","../src/tasks/edges.ts","../src/workstream.ts","../src/exporting.ts","../src/archives/core.ts","../src/archives/addremove.ts","../src/archives/delete.ts","../src/archives/query.ts","../src/archives/restore.ts","../src/tasks/status.ts","../src/tasks/edit.ts","../src/tasks/lifecycle.ts","../src/tasks/claim.ts","../src/tasks/wait.ts","../src/agents/adopt.ts","../src/agents/kick.ts","../src/agents.ts","../src/dag.ts","../src/db-sync.ts","../src/db-sync-replay.ts","../src/tracks.ts","../src/doctor-summary.ts","../src/state.ts"],"sourcesContent":["// mu — DB module.\n//\n// Opens ~/.mu/mu.db (or MU_DB_PATH override), enables WAL + foreign keys,\n// applies the schema idempotently, and exposes the live Database handle.\n//\n// Schema (see CHANGELOG.md §\"Schema\"):\n// - core tables: workstreams, agents, tasks, task_edges, task_notes,\n// agent_logs, vcs_workspaces, snapshots\n// (+5 v6 archive_* tables; -1 approvals dropped in v7;\n// +2 v8 sync-substrate tables)\n// - 2 singleton-ish meta tables: schema_version, machine_identity\n// - 3 views: ready, blocked, goals\n//\n// v5 (this version) is the surrogate-INTEGER-PK shape per\n// docs/ARCHITECTURE.md § Surrogate-PK + SDK-boundary discipline.\n// Every entity table has an INTEGER PK; FKs reference INTEGER ids;\n// the operator-facing TEXT name is per-scope unique via\n// UNIQUE (<scope_id>, <name>).\n//\n// IMPORTANT: src/db.ts knows ONLY the v5 shape. Pre-v5 DBs are\n// rejected at openDb time with SchemaTooOldError; the operator\n// recovers the one-shot v4→v5 migration script from git history\n// (`git log --all --diff-filter=D -- scripts/migrate-v4-to-v5.ts`).\n// The old in-process forward-only migration ladder (v1→v2, v2→v3,\n// v3→v4) was removed in schema_v5_drop_migrations_ts: with the\n// loud-fail hook below catching every pre-v5 DB before openDb\n// returns, none of those migration paths could ever run.\n\nimport { randomUUID } from \"node:crypto\";\nimport { mkdirSync } from \"node:fs\";\nimport { homedir, hostname } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport Database, { type Database as DatabaseType } from \"better-sqlite3\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\n\nexport type Db = DatabaseType;\n\nexport interface OpenDbOptions {\n /**\n * Absolute path to the SQLite file. Defaults to MU_DB_PATH env var or\n * the XDG state path (see `defaultDbPath`). Use a per-test temp path\n * in tests.\n */\n path?: string;\n\n /**\n * If true, opens the DB read-only. Used by `mu sql` and similar read-only\n * surfaces to enforce no-mutation guarantees at the connection level.\n */\n readonly?: boolean;\n}\n\n/**\n * Resolve the canonical mu state directory:\n * MU_STATE_DIR > $XDG_STATE_HOME/mu > ~/.local/state/mu\n */\nexport function defaultStateDir(): string {\n if (process.env.MU_STATE_DIR) return process.env.MU_STATE_DIR;\n const stateHome = process.env.XDG_STATE_HOME ?? join(homedir(), \".local\", \"state\");\n return join(stateHome, \"mu\");\n}\n\n/**\n * Resolve the canonical DB path:\n * MU_DB_PATH > <state-dir>/mu.db\n */\nexport function defaultDbPath(): string {\n if (process.env.MU_DB_PATH) return process.env.MU_DB_PATH;\n return join(defaultStateDir(), \"mu.db\");\n}\n\n/**\n * Open the mu database. Creates the parent directory and applies the schema\n * idempotently on every open. Safe to call from many short-lived processes\n * concurrently — WAL mode handles cross-process writes.\n */\nexport function openDb(options: OpenDbOptions = {}): Db {\n const path = options.path ?? defaultDbPath();\n refuseUserDbDuringTests(path);\n mkdirSync(dirname(path), { recursive: true });\n\n const db = new Database(path, { readonly: options.readonly ?? false });\n\n if (!options.readonly) {\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"foreign_keys = ON\");\n // Detect schema version BEFORE applySchema so a real v<5 DB is not\n // silently stamped as v5 by the CREATE-IF-NOT-EXISTS in applySchema.\n const detectedVersion = detectExistingSchemaVersion(db);\n if (detectedVersion !== null && detectedVersion < MIN_ACCEPTED_SCHEMA_VERSION) {\n // Loud-fail: refuse to touch a pre-v5 DB. The operator\n // restores the one-shot migrator from git history and retries.\n // (See docs/ARCHITECTURE.md § Surrogate-PK + SDK-boundary\n // discipline for the v5 substrate; the migrator was deleted\n // in the post-landing cleanup per the temp-impl-artifact rule.)\n // v5 DBs are forward-bumped to v6 in `applySchema` (purely\n // additive change).\n try {\n db.close();\n } catch {\n // best effort\n }\n throw new SchemaTooOldError(detectedVersion, MIN_ACCEPTED_SCHEMA_VERSION);\n }\n applySchema(db);\n seedMachineIdentity(db);\n } else {\n db.pragma(\"foreign_keys = ON\");\n }\n\n return db;\n}\n\n/**\n * Hard guard (Layer \"db\" of bug_test_flake_round_2): refuse to open\n * the user's REAL default mu.db when running under vitest. Tests\n * MUST point at a per-test temp DB (via MU_DB_PATH or the explicit\n * `{ path }` option). A test that forgets to override either one\n * silently mutated the dev box's live state — we observed a stray\n * 'demo' workstream row replicated from test/tui-acceptance.integration.test.ts\n * into ~/.local/state/mu/mu.db. The guard throws a useful diagnostic\n * the moment the offending openDb() call is made; the failing test's\n * stack trace then names the leak source directly.\n *\n * Test mode = `process.env.VITEST` is defined OR `NODE_ENV === \"test\"`.\n * vitest sets VITEST=\"true\" in every fork; the NODE_ENV branch is for\n * other runners that may invoke openDb during tests.\n *\n * The user's REAL DB path is computed from HOME / XDG_STATE_HOME\n * directly (NOT from defaultDbPath() — which would honour MU_DB_PATH\n * and produce the temp path the test set, defeating the check).\n * Production code paths (the `mu` CLI binary) never set VITEST, so\n * the guard is a complete no-op outside the test runner.\n */\nfunction refuseUserDbDuringTests(path: string): void {\n const inTest = process.env.VITEST !== undefined || process.env.NODE_ENV === \"test\";\n if (!inTest) return;\n const home = process.env.HOME ?? homedir();\n const xdg = process.env.XDG_STATE_HOME ?? join(home, \".local\", \"state\");\n const realDb = resolve(join(xdg, \"mu\", \"mu.db\"));\n if (resolve(path) === realDb) {\n throw new Error(\n `openDb refused: tests must NEVER write to the user DB (${realDb}). Set MU_DB_PATH to a per-test temp path (test/_runCli.ts does this automatically) or pass an explicit { path } argument. The leak source is the call site of openDb in this stack frame.`,\n );\n }\n}\n\n/**\n * Thrown by openDb when the on-disk DB is at a schema version older\n * than v5. v5 dropped the in-process forward migrator; the one-shot\n * v4→v5 migration script lives in git history (recover via\n * `git log --all --diff-filter=D -- scripts/migrate-v4-to-v5.ts`).\n *\n * Maps to exit code 4 (conflict) in cli.ts handle().\n */\n// ─── Resolve helpers (operator-facing name -> surrogate id) ───────────\n//\n// docs/ARCHITECTURE.md § Surrogate-PK + SDK-boundary discipline:\n//\n// PUBLIC SDK functions take operator-facing names (workstream + local\n// id + agent name). Internal helpers take surrogate ids. Resolution\n// happens at the public-function entry, exactly once.\n//\n// `resolveWorkstreamId` is the only resolve helper that throws a typed\n// error from this leaf module — `WorkstreamNotFoundError` is defined\n// here, so there is no cycle. The task / agent resolvers RETURN\n// `number | null` (`tryResolveTaskId` / `tryResolveAgentId`) and let\n// SDK callers in `src/tasks/*.ts` / `src/agents.ts` throw the typed\n// `TaskNotFoundError` / `AgentNotFoundError` they own. That keeps\n// `cli/handle.ts`'s `instanceof`-based exit-code map (3 = not-found)\n// honest: a leaf throwing a plain `Error` whose `.name` was monkey-\n// patched to `\"TaskNotFoundError\"` flunks `instanceof TaskNotFoundError`\n// and falls through to the generic exit 1\n// (review_substrate_resolve_id_anonymous_errors).\n\nexport class WorkstreamNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"WorkstreamNotFoundError\";\n constructor(public readonly workstream: string) {\n super(`no such workstream: ${workstream}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List workstreams\", command: \"mu workstream list\" },\n {\n intent: \"Initialise this workstream\",\n command: `mu workstream init ${this.workstream}`,\n },\n ];\n }\n}\n\n/** Resolve a workstream name to its INTEGER surrogate id. Throws\n * WorkstreamNotFoundError on miss. Pure: no auto-create — callers\n * that want the auto-create-or-resolve semantics use\n * `ensureWorkstream` from src/workstream.ts (which returns void;\n * follow up with `resolveWorkstreamId` if the id is needed).\n */\nexport function resolveWorkstreamId(db: Db, workstream: string): number {\n const row = db.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as\n | { id: number }\n | undefined;\n if (!row) throw new WorkstreamNotFoundError(workstream);\n return row.id;\n}\n\n/** Resolve a workstream name to its id, returning null on miss instead\n * of throwing. Useful for read paths that want to early-return [] on\n * a non-existent workstream (e.g. listTasks). */\nexport function tryResolveWorkstreamId(db: Db, workstream: string): number | null {\n const row = db.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as\n | { id: number }\n | undefined;\n return row ? row.id : null;\n}\n\n/** Resolve a (workstream_id, local_id) pair to the task's surrogate\n * id, returning `null` on miss. SDK callers in `src/tasks/*.ts`\n * wrap the null return in `TaskNotFoundError` so the CLI's typed-\n * error → exit-code map (3 = not-found) fires. The leaf intentionally\n * does NOT throw a typed error (it would either pull in a cyclic\n * import or — as before — fake the `.name` and silently flunk\n * `instanceof TaskNotFoundError`, falling through to exit 1).\n * Renamed from `resolveTaskId` in\n * review_substrate_resolve_id_anonymous_errors. */\nexport function tryResolveTaskId(db: Db, workstreamId: number, localId: string): number | null {\n const row = db\n .prepare(\"SELECT id FROM tasks WHERE workstream_id = ? AND local_id = ?\")\n .get(workstreamId, localId) as { id: number } | undefined;\n return row ? row.id : null;\n}\n\n/** Resolve a (workstream_id, agent_name) pair to the agent's surrogate\n * id, returning `null` on miss. SDK callers in `src/agents.ts` wrap\n * the null return in `AgentNotFoundError` so the CLI's typed-error →\n * exit-code map (3 = not-found) fires. See `tryResolveTaskId` for the\n * full rationale (review_substrate_resolve_id_anonymous_errors). */\nexport function tryResolveAgentId(db: Db, workstreamId: number, name: string): number | null {\n const row = db\n .prepare(\"SELECT id FROM agents WHERE workstream_id = ? AND name = ?\")\n .get(workstreamId, name) as { id: number } | undefined;\n return row ? row.id : null;\n}\n\nexport class SchemaTooOldError extends Error implements HasNextSteps {\n override readonly name = \"SchemaTooOldError\";\n constructor(\n public readonly detectedVersion: number,\n public readonly requiredVersion: number,\n ) {\n super(\n `Detected v${detectedVersion} schema; v${requiredVersion} is required. The one-shot v4→v5 migration script (scripts/migrate-v4-to-v5.ts) was deleted post-landing; recover it from git history and run it once, then retry your command.`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Recover the one-shot v4→v5 migration script from git history\",\n command: \"git log --all --diff-filter=D -- scripts/migrate-v4-to-v5.ts | head\",\n },\n {\n intent: \"Then run it once against the DB\",\n command:\n \"git show <commit>:scripts/migrate-v4-to-v5.ts > /tmp/migrate.ts && npx tsx /tmp/migrate.ts\",\n },\n {\n intent: \"Then retry the original command\",\n command: \"# (your original mu invocation)\",\n },\n {\n intent: \"Inspect the on-disk DB version\",\n command: `sqlite3 \"$MU_DB_PATH\" 'SELECT version FROM schema_version'`,\n },\n ];\n }\n}\n\n/**\n * Sniff an existing DB's schema version BEFORE applySchema runs, so we\n * can distinguish:\n * - Brand-new DB: no tables at all -> returns null (fresh, will be\n * stamped to CURRENT_SCHEMA_VERSION by applySchema).\n * - Pre-versioning DB (had v1 tables before schema_version existed):\n * workstreams exists, schema_version doesn't -> returns 1.\n * - Already-versioned DB: schema_version row present -> returns its\n * value.\n */\nfunction detectExistingSchemaVersion(db: Db): number | null {\n const hasVersionTable = db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'\")\n .get() as { name: string } | undefined;\n if (hasVersionTable) {\n const row = db.prepare(\"SELECT version FROM schema_version WHERE id = 1\").get() as\n | { version: number }\n | undefined;\n return row?.version ?? null;\n }\n // No schema_version table. Check whether any of the original v1\n // tables exist; if so this is a pre-versioning v1 DB.\n const hasWorkstreams = db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='workstreams'\")\n .get() as { name: string } | undefined;\n if (hasWorkstreams) return 1;\n return null;\n}\n\n/**\n * Apply the schema. Idempotent: tables use CREATE TABLE IF NOT EXISTS;\n * views are dropped and recreated so the latest definition always wins.\n *\n * For fresh DBs this writes the current schema shape and stamps\n * schema_version = CURRENT_SCHEMA_VERSION. For existing DBs this is a\n * no-op for the table CREATEs (IF NOT EXISTS) but DOES recreate the\n * views. Pre-v5 DBs never reach this function — openDb's loud-fail\n * hook rejects them with SchemaTooOldError first.\n *\n * v5 → v6 in-place bump: v6 was purely additive (5 new archive_*\n * tables; no existing column / FK / view touched). The CREATE TABLE\n * IF NOT EXISTS blocks above create the new tables on a v5 DB.\n *\n * v6 → v7 in-place bump: v7 is destructive — drops the `approvals`\n * table (zero usage in 200+ task dogfood; anti-anticipatory pruning\n * per VISION.md \"no traits with zero implementors\"). The DROP runs\n * BEFORE the version stamp so a partial migration doesn't leave a\n * v7-stamped DB with the v6 table still present. Gated on the\n * detected pre-bump version so it's a one-shot for v6 DBs and a\n * harmless `IF EXISTS` no-op for fresh v7 DBs.\n *\n * v7 → v8 in-place bump: v8 is additive (machine_identity and\n * workstream_sync tables). openDb seeds machine_identity after this\n * schema block so v7 DBs upgraded in place get an identity too.\n */\nfunction seedMachineIdentity(db: Db): void {\n const row = db.prepare(\"SELECT COUNT(*) AS count FROM machine_identity\").get() as {\n count: number;\n };\n if (row.count !== 0) return;\n db.prepare(\n `INSERT OR IGNORE INTO machine_identity (id, machine_id, hostname, created_at)\n VALUES (1, ?, ?, ?)`,\n ).run(randomUUID(), hostname(), new Date().toISOString());\n}\n\nfunction applySchema(db: Db): void {\n // Sniff the recorded version BEFORE the schema CREATEs land — needed\n // to decide whether the v6 → v7 destructive migration runs (only on\n // a DB that's at v6 or older but ≥ v5, the openDb floor).\n const preBumpVersion = detectExistingSchemaVersion(db);\n db.exec(CURRENT_SCHEMA);\n // v6 → v7 destructive migration: drop the approvals table on any\n // pre-v7 DB. IF EXISTS so a fresh v7 DB (no approvals table ever\n // created) is a no-op too. The DROP must precede the version\n // stamp below: a partial migration that crashed mid-DROP would\n // re-run on next open instead of silently leaving the table.\n if (preBumpVersion !== null && preBumpVersion < 7) {\n db.exec(\"DROP INDEX IF EXISTS idx_approvals_status\");\n db.exec(\"DROP INDEX IF EXISTS idx_approvals_workstream\");\n db.exec(\"DROP TABLE IF EXISTS approvals\");\n }\n // Stamp the version on a fresh DB. INSERT OR IGNORE so we don't\n // overwrite the version on an existing v5+ DB.\n db.prepare(\"INSERT OR IGNORE INTO schema_version (id, version) VALUES (1, ?)\").run(\n CURRENT_SCHEMA_VERSION,\n );\n // Forward-additive bump for in-place transitions (v5 → v6 archive\n // tables, v6 → v7 approvals removal). Guarded by `version < ?` so a\n // future open against a same-or-newer DB doesn't accidentally\n // downgrade.\n db.prepare(\"UPDATE schema_version SET version = ? WHERE id = 1 AND version < ?\").run(\n CURRENT_SCHEMA_VERSION,\n CURRENT_SCHEMA_VERSION,\n );\n}\n\n/** The schema version a fresh DB starts at. v8 adds the\n * machine_identity and workstream_sync sync substrate on top of v7\n * (which dropped the approvals table), v6 (which added 5 archive_*\n * tables), and v5's surrogate-PK substrate. The refusal floor is\n * v5 — pre-v5 DBs throw `SchemaTooOldError`; v5+ DBs are\n * forward-bumped in place by `applySchema`. */\nexport const CURRENT_SCHEMA_VERSION = 8;\n\n/** The lowest schema version `openDb` will accept. v5+ DBs are\n * forward-bumped to the current version in place (v5 → v6 added\n * archive tables; v6 → v7 dropped the approvals table; v7 → v8\n * adds the sync substrate). Pre-v5 DBs throw `SchemaTooOldError`. */\nconst MIN_ACCEPTED_SCHEMA_VERSION = 5;\n\n/** Tables a healthy DB must contain. Single source of truth so\n * `mu doctor` and any other consumer don't drift. Adding a new table\n * = one new entry here AND a CREATE TABLE in CURRENT_SCHEMA. (Schema\n * changes that aren't compatible with prior schemas bump\n * CURRENT_SCHEMA_VERSION and ship with a one-shot script under\n * scripts/ (the v4→v5 transition was the canonical example\n * before the script was deleted post-landing). */\nexport const EXPECTED_TABLES: readonly string[] = [\n \"agent_logs\",\n \"agents\",\n \"archived_edges\",\n \"archived_events\",\n \"archived_notes\",\n \"archived_tasks\",\n \"archives\",\n \"machine_identity\",\n \"schema_version\",\n \"snapshots\",\n \"task_edges\",\n \"task_notes\",\n \"tasks\",\n \"vcs_workspaces\",\n \"workstream_sync\",\n \"workstreams\",\n];\n\n// ─── View DDL — single source of truth ────────────────────────────────\n//\n// The three views (ready, blocked, goals) get DROPped + CREATEd by\n// applySchema on every openDb. Each constant is self-contained:\n// DROP IF EXISTS + CREATE. Running DROP twice in a row is harmless,\n// so callers that already DROP up-front can still re-execute these\n// without churn.\n//\n// Exported as named constants so consumers can reference the canonical\n// shape (e.g. one-shot migration scripts under scripts/) without\n// duplicating SQL.\n\nexport const READY_VIEW_SQL = `\nDROP VIEW IF EXISTS ready;\nCREATE VIEW ready AS\n SELECT t.*\n FROM tasks t\n WHERE t.status = 'OPEN'\n AND NOT EXISTS (\n SELECT 1\n FROM task_edges e\n JOIN tasks b ON e.from_task_id = b.id\n WHERE e.to_task_id = t.id\n AND b.status <> 'CLOSED'\n );\n`;\n\nexport const BLOCKED_VIEW_SQL = `\nDROP VIEW IF EXISTS blocked;\nCREATE VIEW blocked AS\n SELECT t.*\n FROM tasks t\n WHERE t.status = 'OPEN'\n AND EXISTS (\n SELECT 1\n FROM task_edges e\n JOIN tasks b ON e.from_task_id = b.id\n WHERE e.to_task_id = t.id\n AND b.status <> 'CLOSED'\n );\n`;\n\n// A goal is an active endpoint of the DAG — a task with no dependents\n// that we're still working toward. CLOSED, REJECTED, and DEFERRED are\n// all excluded: a finished/abandoned/parked leaf is not an active goal.\n// (REJECTED and DEFERRED still BLOCK dependents per the views above\n// — they're terminal/parked from the perspective of 'what's a goal',\n// but they don't satisfy a blocked-by edge: only CLOSED does that.)\nexport const GOALS_VIEW_SQL = `\nDROP VIEW IF EXISTS goals;\nCREATE VIEW goals AS\n SELECT t.*\n FROM tasks t\n WHERE t.status NOT IN ('CLOSED', 'REJECTED', 'DEFERRED')\n AND NOT EXISTS (\n SELECT 1 FROM task_edges WHERE from_task_id = t.id\n );\n`;\n\n// ─── v5 SCHEMA ────────────────────────────────────────────────────────\n//\n// Per docs/ARCHITECTURE.md § Surrogate-PK + SDK-boundary discipline.\n// Every entity table has:\n// - INTEGER PRIMARY KEY AUTOINCREMENT (surrogate identity)\n// - <scope>_id INTEGER NOT NULL REFERENCES <parent>(id) ON DELETE CASCADE\n// - <name> TEXT NOT NULL (operator-facing, mutable)\n// - UNIQUE (<scope>_id, <name>)\n//\n// Foreign keys are INTEGER. Renames become single-row UPDATEs (no\n// cascade chain). The TEXT name is just an attribute. snapshots is\n// the documented exception (intentionally NO FK on workstream so a\n// destroy snapshot outlives its workstream).\n\nconst CURRENT_SCHEMA = `\n-- ─── Schema versioning ────────────────────────────────────────────────\n--\n-- Single-row table tracking which schema version this DB is at. Migrations\n-- read and update this; the row is INSERT-OR-IGNOREd by applySchema with\n-- the current version on a fresh DB.\nCREATE TABLE IF NOT EXISTS schema_version (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n version INTEGER NOT NULL\n);\n\n-- machine_identity: one durable identity per DB/machine, seeded by\n-- openDb after schema creation. hostname is advisory only.\nCREATE TABLE IF NOT EXISTS machine_identity (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n machine_id TEXT NOT NULL,\n hostname TEXT,\n created_at TEXT NOT NULL\n);\n\n-- ─── Tables ───────────────────────────────────────────────────────────\n\n-- workstreams: top of the hierarchy. name stays globally unique\n-- because it IS a tmux session name; no <scope_id> column because\n-- there's no parent.\nCREATE TABLE IF NOT EXISTS workstreams (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT UNIQUE NOT NULL,\n created_at TEXT NOT NULL -- ISO 8601\n);\n\n-- workstream_sync: per-workstream cross-machine drift state. Rows are\n-- created on demand by db import/export code, not pre-seeded.\nCREATE TABLE IF NOT EXISTS workstream_sync (\n workstream_id INTEGER PRIMARY KEY REFERENCES workstreams (id) ON DELETE CASCADE,\n last_known_peer_seqs TEXT NOT NULL DEFAULT '{}'\n);\n\n-- agents: one row per managed pane. Per-workstream unique on name.\nCREATE TABLE IF NOT EXISTS agents (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n workstream_id INTEGER NOT NULL REFERENCES workstreams (id) ON DELETE CASCADE,\n name TEXT NOT NULL, -- per-workstream unique\n cli TEXT NOT NULL DEFAULT 'pi',\n pane_id TEXT NOT NULL,\n status TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'full-access',\n tab TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE (workstream_id, name),\n CHECK (status IN (\n 'spawning', 'busy', 'needs_input', 'needs_permission',\n 'free', 'unreachable', 'terminated'\n )),\n CHECK (role IN ('full-access', 'read-only'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_agents_workstream ON agents (workstream_id);\nCREATE INDEX IF NOT EXISTS idx_agents_status ON agents (status);\n\n-- tasks: per-workstream unique on local_id (TRULY local now —\n-- different workstreams may reuse the same local_id).\nCREATE TABLE IF NOT EXISTS tasks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n workstream_id INTEGER NOT NULL REFERENCES workstreams (id) ON DELETE CASCADE,\n local_id TEXT NOT NULL, -- per-workstream unique\n title TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'OPEN',\n -- OPEN | IN_PROGRESS | CLOSED | REJECTED | DEFERRED — see VOCABULARY.md.\n impact INTEGER NOT NULL,\n effort_days REAL NOT NULL,\n owner_id INTEGER REFERENCES agents (id) ON DELETE SET NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE (workstream_id, local_id),\n CHECK (impact BETWEEN 1 AND 100),\n CHECK (effort_days > 0),\n CHECK (status IN ('OPEN', 'IN_PROGRESS', 'CLOSED', 'REJECTED', 'DEFERRED'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_tasks_workstream ON tasks (workstream_id);\nCREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks (status);\nCREATE INDEX IF NOT EXISTS idx_tasks_owner ON tasks (owner_id);\n\n-- task_edges: composite PK by pair. INTEGER FKs into tasks.id.\nCREATE TABLE IF NOT EXISTS task_edges (\n from_task_id INTEGER NOT NULL REFERENCES tasks (id) ON DELETE CASCADE,\n to_task_id INTEGER NOT NULL REFERENCES tasks (id) ON DELETE CASCADE,\n created_at TEXT NOT NULL,\n PRIMARY KEY (from_task_id, to_task_id),\n CHECK (from_task_id <> to_task_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_task_edges_to ON task_edges (to_task_id);\n\n-- task_notes: append-only context. author stays free-text\n-- (\"orchestrator\", \"user\", \"π - mu\", \"system\") — not always a\n-- registered agent.\nCREATE TABLE IF NOT EXISTS task_notes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_id INTEGER NOT NULL REFERENCES tasks (id) ON DELETE CASCADE,\n author TEXT,\n content TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_task_notes_task ON task_notes (task_id);\n\n-- agent_logs: append-only timeline. source stays free-text (\"system\",\n-- \"user\", \"orchestrator\", or any agent name) — not an FK relation.\n-- workstream_id is nullable (a future machine-wide event might exist)\n-- but every current emitter sets it; CASCADE on workstream destroy.\nCREATE TABLE IF NOT EXISTS agent_logs (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n workstream_id INTEGER REFERENCES workstreams (id) ON DELETE CASCADE,\n source TEXT NOT NULL,\n kind TEXT NOT NULL DEFAULT 'message',\n payload TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_agent_logs_seq ON agent_logs (seq);\nCREATE INDEX IF NOT EXISTS idx_agent_logs_ws_seq ON agent_logs (workstream_id, seq);\nCREATE INDEX IF NOT EXISTS idx_agent_logs_source ON agent_logs (source);\n\n-- vcs_workspaces: one isolated working copy per agent.\n-- UNIQUE (agent_id) enforces the 1:1 invariant; workstream_id is\n-- denormalised for query convenience. path is UNIQUE because two\n-- agents pointing at the same on-disk workspace would defeat the\n-- purpose.\nCREATE TABLE IF NOT EXISTS vcs_workspaces (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n agent_id INTEGER NOT NULL UNIQUE REFERENCES agents (id) ON DELETE CASCADE,\n workstream_id INTEGER NOT NULL REFERENCES workstreams (id) ON DELETE CASCADE,\n backend TEXT NOT NULL CHECK (backend IN ('jj', 'sl', 'git', 'none')),\n path TEXT NOT NULL UNIQUE,\n parent_ref TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_vcs_workspaces_workstream ON vcs_workspaces (workstream_id);\n\n-- snapshots: documented exception. NO FK on workstream — a destroy\n-- snapshot must outlive its workstream. workstream column stays TEXT\n-- so the snapshot remains readable even after every reference is gone.\nCREATE TABLE IF NOT EXISTS snapshots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n workstream TEXT,\n label TEXT NOT NULL,\n db_path TEXT NOT NULL,\n schema_version INTEGER NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_snapshots_created_at ON snapshots (created_at);\nCREATE INDEX IF NOT EXISTS idx_snapshots_workstream ON snapshots (workstream);\n\n-- ─── v6 archive tables (additive on top of v5) ────────────────────\n--\n-- 5 new tables landed in v6 to back the mu archive verb (cross-workstream\n-- preservation of CLOSED/REJECTED/DEFERRED tasks before destroy).\n-- Additive only: no existing column / FK / view touched. The v5 → v6\n-- transition is in-place via applySchema (no separate migration\n-- script). See docs/VOCABULARY.md § archive for terminology.\n--\n-- Design constraint: archives outlive workstreams. archives.label is\n-- globally unique (NOT per-workstream), and archived_tasks columns\n-- that refer to the source workstream are TEXT (not FKs) so the\n-- destroyed workstream's name stays readable post-destroy.\n\n-- archives: one row per operator-named archive bucket. label is\n-- globally unique because archives outlive workstreams (an archive\n-- whose label was scoped to a workstream would lose its name when\n-- the workstream is destroyed).\nCREATE TABLE IF NOT EXISTS archives (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n label TEXT UNIQUE NOT NULL,\n description TEXT,\n created_at TEXT NOT NULL,\n last_added_at TEXT NOT NULL -- bumped on every successful add (additive accumulation invariant)\n);\n\n-- archived_tasks: snapshot of a task at archive time. source_workstream\n-- is intentionally TEXT (the source workstream may be destroyed after\n-- archive); owner_name is snapshotted for the same reason. The\n-- (archive_id, source_workstream, original_local_id) UNIQUE is the\n-- idempotency lever for mu archive add re-runs.\nCREATE TABLE IF NOT EXISTS archived_tasks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n archive_id INTEGER NOT NULL REFERENCES archives (id) ON DELETE CASCADE,\n source_workstream TEXT NOT NULL,\n original_local_id TEXT NOT NULL,\n title TEXT NOT NULL,\n status TEXT NOT NULL,\n impact INTEGER NOT NULL,\n effort_days REAL NOT NULL,\n owner_name TEXT,\n archived_at_status TEXT NOT NULL,\n archived_at TEXT NOT NULL,\n original_created_at TEXT NOT NULL,\n original_updated_at TEXT NOT NULL,\n UNIQUE (archive_id, source_workstream, original_local_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_archived_tasks_archive ON archived_tasks (archive_id);\nCREATE INDEX IF NOT EXISTS idx_archived_tasks_source ON archived_tasks (archive_id, source_workstream);\n\n-- archived_edges: composite PK by pair of archived_tasks ids.\n-- archive_id is denormalised so a CASCADE on the archive cleans every\n-- edge in one shot.\nCREATE TABLE IF NOT EXISTS archived_edges (\n archive_id INTEGER NOT NULL REFERENCES archives (id) ON DELETE CASCADE,\n from_archived_id INTEGER NOT NULL REFERENCES archived_tasks (id) ON DELETE CASCADE,\n to_archived_id INTEGER NOT NULL REFERENCES archived_tasks (id) ON DELETE CASCADE,\n PRIMARY KEY (archive_id, from_archived_id, to_archived_id)\n);\n\n-- archived_notes: snapshot of task_notes for archived tasks. author\n-- stays free-text, mirroring task_notes.\nCREATE TABLE IF NOT EXISTS archived_notes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n archive_id INTEGER NOT NULL REFERENCES archives (id) ON DELETE CASCADE,\n archived_task_id INTEGER NOT NULL REFERENCES archived_tasks (id) ON DELETE CASCADE,\n author TEXT,\n content TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_archived_notes_task ON archived_notes (archived_task_id);\n\n-- archived_events: snapshot of kind='event' rows from agent_logs for\n-- the source workstream at archive time. Only events (not the full\n-- message log; that's recoverable via snapshot+undo if ever needed).\nCREATE TABLE IF NOT EXISTS archived_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n archive_id INTEGER NOT NULL REFERENCES archives (id) ON DELETE CASCADE,\n source_workstream TEXT NOT NULL,\n seq INTEGER NOT NULL,\n source TEXT NOT NULL,\n payload TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_archived_events_archive ON archived_events (archive_id, source_workstream);\n\n-- ─── Views (always replaced so the latest definition wins) ────────────\n-- See READY_VIEW_SQL / BLOCKED_VIEW_SQL / GOALS_VIEW_SQL above for the\n-- canonical DDL — interpolated here so applySchema is one db.exec().\n${READY_VIEW_SQL}\n${BLOCKED_VIEW_SQL}\n${GOALS_VIEW_SQL}\n`;\n","// mu — agent_logs: append-only timeline of activity in a workstream.\n//\n// Three roles in one table:\n// 1. Manual broadcasts (`mu log \"...\"` from a shell or agent pane)\n// 2. System events (auto-emitted by every state-changing verb;\n// wired in a follow-up commit so this surface is reviewable\n// first)\n// 3. External script entries via `mu log --as ...`\n//\n// The seq column (AUTOINCREMENT INTEGER PK) is the cursor. A tail\n// subscriber stores the last seq it saw and re-queries with\n// `seq > <last>`; AUTOINCREMENT guarantees seq never recycles even\n// after deletes, so the cursor is durable.\n\nimport { type Db, tryResolveWorkstreamId } from \"./db.js\";\n\nexport type LogKind = \"message\" | \"event\" | \"broadcast\" | string;\n\nexport interface LogRow {\n /** Monotonic AUTOINCREMENT id. Use as the cursor for `--since`. */\n seq: number;\n /** Workstream this entry belongs to, or `null` for machine-wide. */\n workstreamName: string | null;\n /** Free TEXT: agent name, \"system\", \"user\", or anything a caller picks. */\n source: string;\n /** Free TEXT: \"message\" (default), \"event\" (auto state changes),\n * \"broadcast\" (explicit cross-agent), or any caller-defined value. */\n kind: LogKind;\n /** Free utf-8 string. May be JSON if the kind suggests structure. */\n payload: string;\n /** ISO 8601 timestamp set at insert time. */\n createdAt: string;\n}\n\ninterface RawLogRow {\n seq: number;\n /** Joined from workstreams.name. Null when workstream_id is NULL. */\n workstream: string | null;\n source: string;\n kind: string;\n payload: string;\n created_at: string;\n}\n\n/** SELECT clause for joining workstream_id back to the operator-facing\n * workstream name. Used by every read path so the JS-side row shape\n * is operator-facing TEXT names (not surrogate ids). */\nconst SELECT_LOG_COLS = `\n l.seq AS seq,\n ws.name AS workstream,\n l.source AS source,\n l.kind AS kind,\n l.payload AS payload,\n l.created_at AS created_at\n`;\n\nconst LOG_FROM_JOIN = \"FROM agent_logs l LEFT JOIN workstreams ws ON ws.id = l.workstream_id\";\n\nfunction rowFromDb(row: RawLogRow): LogRow {\n return {\n seq: row.seq,\n workstreamName: row.workstream,\n source: row.source,\n kind: row.kind,\n payload: row.payload,\n createdAt: row.created_at,\n };\n}\n\nexport interface AppendLogOptions {\n /** Workstream this entry belongs to. `null` for machine-wide. */\n workstream: string | null;\n /** Who emitted this. Agent name, \"system\", \"user\", or arbitrary. */\n source: string;\n /** Defaults to \"message\". */\n kind?: LogKind;\n /** Free utf-8. Multi-line allowed. */\n payload: string;\n}\n\n/**\n * Append a log entry. Returns the inserted row (with assigned `seq`).\n * Constant-time. Single INSERT; safe to call from any state-changing\n * verb without a transaction wrapper.\n */\nexport function appendLog(db: Db, opts: AppendLogOptions): LogRow {\n const kind = opts.kind ?? \"message\";\n const createdAt = new Date().toISOString();\n // Resolve workstream name -> surrogate id. Null stays null. We do NOT\n // throw on a missing workstream here — an event payload may legitimately\n // reference a workstream the row for which is being concurrently dropped\n // (e.g. workstream destroy emits its own log row with workstream=null\n // for exactly this reason). Best-effort resolution.\n const workstreamId =\n opts.workstream === null ? null : tryResolveWorkstreamId(db, opts.workstream);\n const result = db\n .prepare(\n `INSERT INTO agent_logs (workstream_id, source, kind, payload, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n )\n .run(workstreamId, opts.source, kind, opts.payload, createdAt);\n return {\n seq: Number(result.lastInsertRowid),\n workstreamName: opts.workstream,\n source: opts.source,\n kind,\n payload: opts.payload,\n createdAt,\n };\n}\n\nexport interface ListLogsOptions {\n /** Filter by workstream. `undefined` = every workstream + machine-wide.\n * `null` = ONLY machine-wide entries. */\n workstream?: string | null;\n /** Strictly > this seq. Use to resume a tail. */\n since?: number;\n /** Cap the result. With `since`, returns the FIRST N matching (oldest\n * first). Without `since`, returns the LAST N (most recent),\n * re-sorted oldest-first. */\n limit?: number;\n source?: string;\n kind?: string;\n}\n\n/**\n * List log entries. Always returns oldest-first. Use `since` for\n * cursor-based reads (the canonical tail pattern); use `limit` alone\n * for \"show me the most recent N\" reads.\n */\nexport function listLogs(db: Db, opts: ListLogsOptions = {}): LogRow[] {\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (opts.workstream === null) {\n conditions.push(\"l.workstream_id IS NULL\");\n } else if (opts.workstream !== undefined) {\n // Resolve once; if the workstream doesn't exist the result set is empty.\n const wsId = tryResolveWorkstreamId(db, opts.workstream);\n if (wsId === null) return [];\n conditions.push(\"l.workstream_id = ?\");\n params.push(wsId);\n }\n if (opts.since !== undefined) {\n conditions.push(\"l.seq > ?\");\n params.push(opts.since);\n }\n if (opts.source !== undefined) {\n conditions.push(\"l.source = ?\");\n params.push(opts.source);\n }\n if (opts.kind !== undefined) {\n conditions.push(\"l.kind = ?\");\n params.push(opts.kind);\n }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n\n // Two query shapes:\n // - When `since` is set, ascending order is what we want directly.\n // - When `limit` is set without `since`, fetch the most-recent N\n // (descending) then reverse so the caller still sees oldest-first.\n if (opts.limit !== undefined && opts.since === undefined) {\n const rowsDesc = db\n .prepare(`SELECT ${SELECT_LOG_COLS} ${LOG_FROM_JOIN} ${where} ORDER BY l.seq DESC LIMIT ?`)\n .all(...params, opts.limit) as RawLogRow[];\n return rowsDesc.reverse().map(rowFromDb);\n }\n\n let sql = `SELECT ${SELECT_LOG_COLS} ${LOG_FROM_JOIN} ${where} ORDER BY l.seq ASC`;\n if (opts.limit !== undefined) {\n sql += \" LIMIT ?\";\n params.push(opts.limit);\n }\n const rows = db.prepare(sql).all(...params) as RawLogRow[];\n return rows.map(rowFromDb);\n}\n\n/**\n * Return the latest seq currently in the table (or 0 if empty). Used\n * by `mu log --tail` to start the cursor at \"now\" so the subscriber\n * only sees NEW entries unless they explicitly pass `--since 0`.\n */\nexport function latestSeq(db: Db, workstreamId?: number): number {\n const row =\n workstreamId === undefined\n ? (db.prepare(\"SELECT MAX(seq) AS s FROM agent_logs\").get() as { s: number | null })\n : (db\n .prepare(\"SELECT MAX(seq) AS s FROM agent_logs WHERE workstream_id = ?\")\n .get(workstreamId) as { s: number | null });\n return row.s ?? 0;\n}\n\n/**\n * One-line helper for state-changing SDK functions to auto-emit a\n * `kind='event'` log entry. Called AFTER the mutation succeeds, only\n * when the mutation actually produced a change (no-ops stay quiet).\n *\n * `source` defaults to 'system' since this is the auto-emission path;\n * a different source means \"a specific agent caused this\" and is set\n * by callers like `claimTask` (source = the claiming agent).\n */\nexport function emitEvent(\n db: Db,\n workstream: string | null,\n payload: string,\n source = \"system\",\n): void {\n appendLog(db, { workstream, source, kind: \"event\", payload });\n}\n\n// ─── claim-event structured prefix ─────────────────────────────────\n//\n// `task claim` events are the one place where a state-changing verb\n// emits TWO actors per row: the agent recorded as `source`, and the\n// `actor=` field that may differ on the --self anonymous-claim path\n// (where source == actor but tasks.owner stays NULL). The original\n// payload was free prose (`task claim foo by bar (was owner=...)`)\n// and the consumer (lastClaimActor below) prefix-matched the prose\n// — brittle: any rename silently nulled out the attribution.\n//\n// The fix keeps the prose suffix for human readability but prepends\n// a tab-delimited structured prefix that lastClaimActor parses\n// robustly. Format:\n//\n// task.claim<TAB><localId><TAB>actor=<actor><TAB>self=<0|1><TAB><prose>\n//\n// The trailing prose still starts with `task claim <localId> ...` so\n// event renderers (which strip the structured prefix via\n// displayEventPayload before colouring) keep working unchanged.\n//\n// See: review_code_last_claim_actor_brittle.\n\n/** Structured-prefix sentinel used by claim event payloads. The dot\n * distinguishes it from the prose `task claim ...` tail. */\nexport const CLAIM_EVENT_PREFIX = \"task.claim\";\n\n/** Build the structured payload for a `task claim` event. */\nexport function formatClaimEvent(opts: {\n localId: string;\n actor: string;\n anonymous: boolean;\n prose: string;\n}): string {\n const self = opts.anonymous ? \"1\" : \"0\";\n return `${CLAIM_EVENT_PREFIX}\\t${opts.localId}\\tactor=${opts.actor}\\tself=${self}\\t${opts.prose}`;\n}\n\n/** Strip the structured `task.claim` prefix and return the human-prose\n * tail. For non-claim payloads, returns the input unchanged. Used by\n * `mu log`, static state, and the TUI so the user sees the prose, not\n * the delimiter-noise. */\nexport function displayEventPayload(payload: string): string {\n if (!payload.startsWith(`${CLAIM_EVENT_PREFIX}\\t`)) return payload;\n // task.claim<TAB><id><TAB>actor=...<TAB>self=...<TAB><prose>\n // Split into 5 fields; the prose may itself contain tabs (it doesn't\n // today, but be defensive: rejoin with TAB so we never lose data).\n const parts = payload.split(\"\\t\");\n if (parts.length < 5) return payload;\n return parts.slice(4).join(\"\\t\");\n}\n\n/** Parse the actor= field out of a structured claim payload. Returns\n * null when the payload isn't a claim event or is malformed. */\nexport function parseClaimEventActor(payload: string): string | null {\n if (!payload.startsWith(`${CLAIM_EVENT_PREFIX}\\t`)) return null;\n for (const field of payload.split(\"\\t\")) {\n if (field.startsWith(\"actor=\")) return field.slice(\"actor=\".length);\n }\n return null;\n}\n\n/**\n * Find the actor of the most recent `task claim <id>` event for a\n * given task. Used to surface 'who's working on this' when\n * `tasks.owner IS NULL` (the --self anonymous-claim path). Returns\n * null when no claim event exists for this task.\n *\n * Implementation: indexed lookup on (workstream, seq) with a LIKE\n * against the structured prefix. Unbounded — the previous limit=100\n * ceiling silently dropped attribution on long-lived workstreams.\n * The structured prefix (CLAIM_EVENT_PREFIX) makes the match\n * robust against payload-prose churn.\n */\nfunction lastClaimEvent(\n db: Db,\n workstream: string,\n localId: string,\n): { payload: string; created_at: string } | null {\n // localId is validated by isValidTaskId — alnum + `_` + `-`. The\n // `_` is a LIKE wildcard, so escape it (and `%` and `\\` for\n // completeness, even though they can't appear in a valid id).\n const escaped = localId.replace(/[\\\\%_]/g, (c) => `\\\\${c}`);\n const pattern = `${CLAIM_EVENT_PREFIX}\\t${escaped}\\t%`;\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return null;\n const row = db\n .prepare(\n `SELECT payload, created_at FROM agent_logs\n WHERE workstream_id = ? AND kind = 'event' AND payload LIKE ? ESCAPE '\\\\'\n ORDER BY seq DESC LIMIT 1`,\n )\n .get(wsId, pattern) as { payload: string; created_at: string } | undefined;\n return row ?? null;\n}\n\nexport function lastClaimActor(db: Db, workstream: string, localId: string): string | null {\n const row = lastClaimEvent(db, workstream, localId);\n return row ? parseClaimEventActor(row.payload) : null;\n}\n\n/**\n * Find the `created_at` timestamp of the most recent `task claim`\n * event for a given task (the structured `task.claim<TAB>...` payload\n * emitted by claim.ts, both worker-claim and `--self` paths).\n *\n * Used by `mu task notes --since-claim` to slice the note timeline at\n * the most recent claim, so an operator dispatching a worker can see\n * only the post-claim notes (the spec was added before the claim;\n * the worker's progress lives after).\n *\n * Returns null when no claim event exists for this task — the CLI's\n * `--since-claim` then degrades gracefully to no filter (equivalent\n * to `--since-beginning`). Mirrors `lastClaimActor`'s LIKE-with-\n * escape pattern so a same-prefix id (`foo` vs `foo_2`) can't\n * cross-match.\n */\nexport function lastClaimEventAt(db: Db, workstream: string, localId: string): string | null {\n return lastClaimEvent(db, workstream, localId)?.created_at ?? null;\n}\n\n/**\n * Canonical list of two-token verb prefixes that `emitEvent` callers\n * use as the leading words of a payload. Single source of truth for\n * event renderers so they can never drift away from the actual emitter\n * sites.\n *\n * Maintenance contract: when you add an `emitEvent(...)` call whose\n * payload starts with a new two-word verb, add the verb here. A\n * regression test walks every entry and asserts the classifier\n * recognises it; the test fails if you add an emitter without adding\n * its verb here.\n *\n * Audit (2026-05): every `emitEvent` callsite under src/ produces a\n * payload that starts with one of these. Verified by\n * `grep -rn emitEvent src/ | grep -v import`.\n */\nexport const EVENT_VERB_PREFIXES: readonly string[] = [\n // src/tasks.ts + src/tasks/*.ts\n \"task add\",\n \"task note\",\n \"task status\",\n // `task claim` is the prose-tail of a `task.claim\\t...` structured\n // payload (see CLAIM_EVENT_PREFIX above); displayEventPayload\n // strips the structured prefix before renderers classify it, so the\n // prose tail starting with `task claim` still matches.\n \"task claim\",\n \"task release\",\n \"task update\",\n \"task delete\",\n \"task reap\",\n \"task block\",\n \"task unblock\",\n \"task reparent\",\n // src/agents.ts + src/agents/*.ts\n \"agent spawn\",\n \"agent close\",\n \"agent free\",\n \"agent adopt\",\n \"agent kick\",\n // src/tasks/wait.ts — emitted when --stuck-after fires (alive +\n // assigned + no recent progress; idle_assigned_agent_detection).\n \"agent stalled\",\n // src/workspace.ts\n \"workspace create\",\n \"workspace free\",\n \"workspace refresh\",\n \"workspace recreate\",\n // src/workstream.ts\n \"workstream init\",\n \"workstream destroy\",\n \"workstream export\",\n // src/archives.ts — v6 archive verbs. Machine-wide events\n // (workstream=null) because archives outlive workstreams.\n \"archive create\",\n \"archive delete\",\n \"archive add\",\n \"archive remove\",\n \"archive restore\",\n // src/exporting.ts — archive export emits the bucket-render summary\n // as a machine-wide event (workstream=null; the export spans every\n // source-ws in the archive).\n \"archive export\",\n];\n\n// ─── Verb classification (for renderers that colour by verb) ──────\n\nexport interface ClassifiedEvent {\n /** One of EVENT_VERB_PREFIXES. */\n verb: string;\n /** Payload past the verb token; preserves leading separator (\" \" or \"\\t\"). */\n rest: string;\n}\n\n/**\n * Match `payload` against EVENT_VERB_PREFIXES. Returns {verb, rest} on\n * match; null otherwise. The verb-boundary check is `next is space, tab,\n * or end-of-string` so we don't false-match e.g. `task addnote`.\n *\n * Pure parser. Consumers (the static state card, the ink Activity-log\n * card) apply their own colour to `verb` after matching.\n */\nexport function classifyEventVerb(payload: string): ClassifiedEvent | null {\n for (const verb of EVENT_VERB_PREFIXES) {\n if (!payload.startsWith(verb)) continue;\n const next = payload.charCodeAt(verb.length);\n if (!Number.isNaN(next) && next !== 0x20 && next !== 0x09) continue;\n return { verb, rest: payload.slice(verb.length) };\n }\n return null;\n}\n","// mu — Pi status detector.\n//\n// Derives an agent's runtime status (busy / needs_input / needs_permission)\n// from its tmux pane scrollback. 0.1.0 is pi-only — we know pi's exact\n// markers from its source. Heterogeneous CLI support (claude, codex) is\n// on the roadmap and will land alongside ground-truth scrollback fixtures.\n//\n// Algorithm (cribbed from a prior internal multi-agent runtime's per-CLI detector):\n//\n// 1. Take the last TAIL_WINDOW_LINES (100) lines of scrollback.\n// 2. Strip trailing blank lines (TUIs pad blanks below content).\n// 3. From what remains, take the last TAIL_LINES (20).\n// 4. If a permission pattern is present in the tail → needs_permission.\n// 5. Else if a busy pattern is present → busy.\n// 6. Else → needs_input.\n//\n// The tail-window is what makes detection robust: pi's startup banner\n// contains \"to interrupt\" in its keybindings hint list. By only looking\n// at the bottom of the pane, we ignore stale matches that have scrolled\n// out of view.\n\n/**\n * The full agent lifecycle status. Most values are scrollback-derived\n * (`DetectedStatus`); the rest are set by the lifecycle layer.\n */\nexport type AgentStatus =\n | \"spawning\"\n | \"busy\"\n | \"needs_input\"\n | \"needs_permission\"\n | \"free\"\n | \"unreachable\"\n | \"terminated\";\n\n/** Status that can be inferred from pane scrollback. */\nexport type DetectedStatus = \"busy\" | \"needs_input\" | \"needs_permission\";\n\n/**\n * Number of lines from the bottom of the scrollback to consider.\n * Larger than TAIL_LINES so we can strip trailing blanks first without\n * running out of content.\n */\nconst TAIL_WINDOW_LINES = 100;\n\n/**\n * After stripping trailing blanks from the window, look at this many lines.\n * Narrow enough that a stale prompt one screen up doesn't match.\n */\nconst TAIL_LINES = 20;\n\n/**\n * Pi prints `Working... (Esc to interrupt)` via its loading animation while\n * streaming an LLM response or running a tool. We require the closing paren\n * (`to interrupt)`) to distinguish from pi's startup banner, which renders\n * the same hint as `<key> to interrupt` (no parens) in a list of\n * keybindings. Without the paren, a fresh pane with the banner still\n * visible would false-positive busy.\n */\nconst PI_BUSY_PATTERNS: readonly string[] = [\"to interrupt)\"];\n\n/**\n * Fallback busy signal: any character in the Unicode Braille block\n * (U+2800–U+28FF). Every TUI spinner library worth using cycles a\n * subset of these glyphs (⠇⠏⠙⠧⠷⠿⠟⠋⠈ …) every ~80ms while\n * blocking work runs. They essentially never appear in agent prose\n * output, so the false-positive risk is tiny.\n *\n * This catches every wrapper around pi (pi-meta + solo, claude-code,\n * codex …) whose chrome differs from vanilla pi enough that the\n * `to interrupt)` literal isn't in the tail — surfaced by the\n * multi-agent dogfood when pi-meta workers all classified as\n * `needs_input` mid-work. Tracked in roadmap-v0-2\n * `bug_status_detector_pi_solo_misclassifies`.\n */\nconst BRAILLE_SPINNER_RE = /[\\u2800-\\u28FF]/;\n\n/**\n * Pi's confirm / select / input dialogs render footer hints like\n * `(Esc to cancel, Enter to submit)`. Both `to submit)` (with closing\n * paren) and `to cancel,` (with the comma artifact of being the first of\n * the parenthesised pair) only appear inside dialog footers — not in\n * prose, not in the banner.\n */\nconst PI_PERMISSION_PATTERNS: readonly string[] = [\"to submit)\", \"to cancel,\"];\n\n/**\n * Run the detector against pane scrollback and return the inferred\n * status. Permission overrides busy.\n */\nexport function detectPiStatus(scrollback: string): DetectedStatus {\n const tail = extractTail(scrollback);\n if (matchesAny(tail, PI_PERMISSION_PATTERNS)) return \"needs_permission\";\n if (matchesAny(tail, PI_BUSY_PATTERNS)) return \"busy\";\n if (BRAILLE_SPINNER_RE.test(tail)) return \"busy\";\n return \"needs_input\";\n}\n\n/**\n * Public for tests — extract the tail window the detector actually\n * inspects. Take last TAIL_WINDOW_LINES, strip trailing blanks, take\n * last TAIL_LINES.\n */\nexport function extractTail(scrollback: string): string {\n const lines = scrollback.split(\"\\n\");\n const window = lines.slice(-TAIL_WINDOW_LINES);\n // Strip trailing blank lines (Codex/pi pad with blanks below content).\n let end = window.length;\n while (end > 0 && (window[end - 1] ?? \"\").trim() === \"\") {\n end--;\n }\n const start = Math.max(0, end - TAIL_LINES);\n return window.slice(start, end).join(\"\\n\");\n}\n\nfunction matchesAny(text: string, patterns: readonly string[]): boolean {\n for (const pattern of patterns) {\n if (text.includes(pattern)) return true;\n }\n return false;\n}\n","// mu — tmux substrate.\n//\n// Single source of truth for all tmux interactions. Every tmux invocation\n// goes through `tmux(args)`, which wraps execa and produces structured\n// `TmuxError`s carrying args + stderr.\n//\n// The send protocol is the bracketed-paste sequence (canonical\n// implementation lives in `sendToPane` below):\n// 1. copy-mode -q (silent if not in copy mode)\n// 2. set-buffer (load text into a uniquely named buffer)\n// 3. paste-buffer -p -d -r (bracketed paste, delete buffer, preserve LF)\n// 4. delay (MU_SEND_DELAY_MS, default 500)\n// 5. send-keys Enter\n//\n// Naive `tmux send-keys \"<text>\"` is broken: characters like /, ?, f get\n// interpreted by the agent's TUI (Claude, Codex, less, vim) or by tmux's\n// copy mode if the user has scrolled up. Use `sendToPane()`.\n\nimport { execa } from \"execa\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\n\n// ─── Error type ────────────────────────────────────────────────────────\n\nexport class TmuxError extends Error implements HasNextSteps {\n constructor(\n public readonly args: readonly string[],\n public readonly stderr: string,\n public readonly stdout: string,\n public readonly exitCode: number | null,\n ) {\n const detail = stderr.trim() || stdout.trim() || \"no output\";\n super(`tmux ${args.join(\" \")} failed (exit ${exitCode}): ${detail}`);\n this.name = \"TmuxError\";\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Run health check\", command: \"mu doctor\" },\n {\n intent: \"Verify tmux is running and reachable\",\n command: \"tmux info | head\",\n },\n {\n intent: \"Check the failing tmux command in isolation\",\n command: `tmux ${this.args.join(\" \")}`,\n },\n ];\n }\n}\n\n/**\n * Thrown when a verb references a tmux pane id that doesn't exist on\n * the running tmux server. Distinct from TmuxError (which wraps any\n * tmux command failure) so callers can map it to a specific exit code\n * (`mu` maps it to 5 — substrate failure — alongside other tmux\n * issues, but the message is more actionable than a raw tmux stderr).\n */\nexport class PaneNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"PaneNotFoundError\";\n constructor(public readonly paneId: string) {\n super(`tmux pane not found: ${paneId}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: `Verify the pane id ${this.paneId} actually exists`,\n command: `tmux display-message -t ${this.paneId} -p '#{pane_id} #{pane_title}'`,\n },\n {\n intent: \"List all live panes across all sessions\",\n command:\n \"tmux list-panes -a -F '#{session_name}:#{window_id}.#{pane_id}\\\\t#{pane_title}\\\\t#{pane_current_command}'\",\n },\n {\n intent: \"List workstreams to choose the right scope\",\n command: \"mu workstream list\",\n },\n {\n intent: \"List registered agents and orphan panes in that scope\",\n command: \"mu agent list -w <workstream>\",\n },\n ];\n }\n}\n\n// ─── Pane ID validation ────────────────────────────────────────────────\n\n/**\n * Stable tmux pane IDs are of the form `%N` (e.g. \"%15\"). They never change\n * for the lifetime of the pane. **Pane indexes** (0, 1, 2…) are volatile and\n * shift when other panes close — never store or pass them.\n */\nexport const PANE_ID_RE = /^%\\d+$/;\n\nexport function isValidPaneId(s: string): boolean {\n return PANE_ID_RE.test(s);\n}\n\nexport function assertValidPaneId(s: string): void {\n if (!isValidPaneId(s)) {\n throw new TypeError(`invalid tmux pane id: ${JSON.stringify(s)} (expected /^%\\\\d+$/)`);\n }\n}\n\n// ─── Configurable delay ────────────────────────────────────────────────\n\n/**\n * Delay between bracketed-paste and Enter, in milliseconds. Claude/Codex/pi\n * process pasted text asynchronously; without this delay, Enter can arrive\n * before the agent has ingested the text. Defaults to 500; lower for tests,\n * raise for slow remotes via `MU_SEND_DELAY_MS`.\n */\nexport function defaultSendDelayMs(): number {\n const raw = process.env.MU_SEND_DELAY_MS;\n if (raw === undefined) return 500;\n const parsed = Number.parseInt(raw, 10);\n if (Number.isNaN(parsed) || parsed < 0) return 500;\n return parsed;\n}\n\n// ─── Executor (swappable for tests) ────────────────────────────────────\n\nexport interface TmuxExecResult {\n stdout: string;\n stderr: string;\n exitCode: number | null;\n}\n\nexport type TmuxExecutor = (args: readonly string[]) => Promise<TmuxExecResult>;\n\n/**\n * Optional global-flag prefix to splice in front of every tmux args\n * vector. When `MU_TMUX_SOCKET=<name>` is set, this returns\n * `[\"-L\", name, \"-f\", \"/dev/null\"]`:\n *\n * `-L <name>` routes every call through a private tmux server with\n * the given socket name (Linux: `/tmp/tmux-<uid>/<name>`,\n * macOS: `$TMPDIR/tmux-<uid>/<name>`) instead of the user's default\n * `/tmp/tmux-<uid>/default`. Set by the test harness in Layer 3 of\n * bug_test_suite_flake_leaks_isolation so the integration suite\n * can never observe — or contaminate — the user's interactive\n * tmux server.\n *\n * `-f /dev/null` skips the user's `~/.tmux.conf`. This matters\n * because tmux auto-starts the server on the first client call if\n * none is running (e.g. after a test’s last `kill-session`\n * shuts the server down). A typical user config uses `run-shell`\n * for status-bar plugins (TPM, hostname/network probes), and each\n * such hook adds ~1–4s to that auto-start. Without `-f /dev/null`\n * a single integration test grows from 3s to 48s on a configured\n * dev box. The suite drives tmux through the documented protocol,\n * not through bound keys, so the user's config is irrelevant.\n *\n * Read fresh on every call so a setupFiles hook that mutates\n * `process.env.MU_TMUX_SOCKET` mid-run takes effect immediately.\n *\n * Production code never sets this; it's a test-isolation seam.\n */\nfunction tmuxGlobalFlags(): readonly string[] {\n const socket = process.env.MU_TMUX_SOCKET;\n if (socket === undefined || socket.length === 0) return [];\n return [\"-L\", socket, \"-f\", \"/dev/null\"];\n}\n\nconst realExecutor: TmuxExecutor = async (args) => {\n const result = await execa(\"tmux\", [...tmuxGlobalFlags(), ...args], { reject: false });\n return {\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n exitCode: result.exitCode ?? null,\n };\n};\n\nlet currentExecutor: TmuxExecutor = realExecutor;\n\n/**\n * Install a custom executor (for tests). Returns the previous executor so\n * tests can restore it cleanly. Production code should never call this.\n */\nexport function setTmuxExecutor(executor: TmuxExecutor): TmuxExecutor {\n const previous = currentExecutor;\n currentExecutor = executor;\n return previous;\n}\n\n/** Restore the real (execa-backed) executor. */\nexport function resetTmuxExecutor(): void {\n currentExecutor = realExecutor;\n}\n\n/**\n * Run an arbitrary tmux command. The single point of contact with the\n * tmux binary; every higher-level operation in this module goes through it.\n *\n * Throws `TmuxError` on non-zero exit. Returns stdout on success.\n */\nexport async function tmux(args: readonly string[]): Promise<string> {\n const result = await currentExecutor(args);\n if (result.exitCode !== 0) {\n throw new TmuxError([...args], result.stderr, result.stdout, result.exitCode);\n }\n return result.stdout;\n}\n\n// ─── Sleep helper (testable) ──────────────────────────────────────────\n\nlet currentSleep: (ms: number) => Promise<void> = (ms) =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\nexport function setSleepForTests(\n impl: (ms: number) => Promise<void>,\n): (ms: number) => Promise<void> {\n const previous = currentSleep;\n currentSleep = impl;\n return previous;\n}\n\nexport function resetSleep(): void {\n currentSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Test-aware sleep — honours `setSleepForTests`. Public so other modules\n * (notably `agents.ts` for spawn liveness polling) get free no-op-ing in\n * tests without re-implementing the swap. */\nexport function sleep(ms: number): Promise<void> {\n return currentSleep(ms);\n}\n\n// ─── Domain types ──────────────────────────────────────────────────────\n\nexport interface TmuxSession {\n name: string;\n}\n\nexport interface TmuxWindow {\n /** tmux window id, e.g. \"@1\". */\n id: string;\n name: string;\n /** Session this window belongs to (only set by cross-session listings). */\n sessionName?: string;\n}\n\nexport interface TmuxPane {\n /** Stable tmux pane id, e.g. \"%15\". */\n paneId: string;\n /** Pane title set via `select-pane -T`. The agent's name in mu's convention. */\n title: string;\n /** Current foreground command (e.g. \"claude\", \"node\", \"bash\"). */\n command: string;\n /** Window this pane lives in. Only set by cross-window listings. */\n windowId?: string;\n /** Session this pane lives in. Only set by cross-session listings. */\n sessionName?: string;\n}\n\n// ─── Sessions ──────────────────────────────────────────────────────────\n\nexport async function listSessions(): Promise<TmuxSession[]> {\n // `list-sessions` exits 1 when no sessions exist; treat as empty.\n try {\n const out = await tmux([\"list-sessions\", \"-F\", \"#{session_name}\"]);\n return out\n .split(\"\\n\")\n .filter((line) => line.length > 0)\n .map((name) => ({ name }));\n } catch (err) {\n if (err instanceof TmuxError && /no server running|no sessions/i.test(err.stderr)) {\n return [];\n }\n throw err;\n }\n}\n\nexport async function sessionExists(name: string): Promise<boolean> {\n const result = await currentExecutor([\"has-session\", \"-t\", name]);\n return result.exitCode === 0;\n}\n\nexport interface NewSessionOptions {\n detached?: boolean;\n windowName?: string;\n command?: string;\n /** Initial working directory for the first pane (`-c <path>`). */\n cwd?: string;\n /** Extra env vars to set in the new pane via tmux `-e KEY=VALUE`.\n * Available since tmux 3.0; sets the variable in the new pane's\n * environment without polluting the tmux server's global env. */\n env?: Record<string, string>;\n}\n\nexport async function newSession(name: string, opts: NewSessionOptions = {}): Promise<void> {\n const args = [\"new-session\"];\n if (opts.detached !== false) args.push(\"-d\");\n args.push(\"-s\", name);\n if (opts.windowName) args.push(\"-n\", opts.windowName);\n if (opts.cwd) args.push(\"-c\", opts.cwd);\n appendEnvFlags(args, opts.env);\n if (opts.command) args.push(opts.command);\n await tmux(args);\n}\n\nexport interface NewSessionWithPaneOptions {\n windowName: string;\n command: string;\n cwd?: string;\n detached?: boolean;\n /** Extra env vars to set in the new pane via tmux `-e KEY=VALUE`. */\n env?: Record<string, string>;\n}\n\n/**\n * Create a tmux session AND its first window+pane in one atomic call.\n * Returns the new pane's stable id. Used by mu when spawning the first\n * agent in a workstream so we never end up with an empty `mu-<workstream>`\n * session left behind by a failed spawn.\n */\nexport async function newSessionWithPane(\n name: string,\n opts: NewSessionWithPaneOptions,\n): Promise<string> {\n const args = [\"new-session\"];\n if (opts.detached !== false) args.push(\"-d\");\n args.push(\"-s\", name, \"-n\", opts.windowName);\n if (opts.cwd) args.push(\"-c\", opts.cwd);\n appendEnvFlags(args, opts.env);\n args.push(\"-P\", \"-F\", \"#{pane_id}\", opts.command);\n const out = (await tmux(args)).trim();\n assertValidPaneId(out);\n return out;\n}\n\n/**\n * Idempotent: succeeds even if the session is already gone.\n *\n * Three swallowed shapes:\n * - \"can't find session: <name>\" — session never existed.\n * - \"session not found\" — alternate phrasing on some tmux builds.\n * - \"no server running on <path>\" — the tmux server itself has exited\n * (typical when the test suite runs against a private `tmux -L\n * <socket>` server and the just-killed session was its last; tmux\n * quietly shuts the server down). Without this, killSession would\n * throw on the very next idempotent call — only visible under\n * Layer 3 of bug_test_suite_flake_leaks_isolation.\n */\nexport async function killSession(name: string): Promise<void> {\n const result = await currentExecutor([\"kill-session\", \"-t\", name]);\n if (\n result.exitCode !== 0 &&\n !/can't find session|session not found|no server running/i.test(result.stderr)\n ) {\n throw new TmuxError(\n [\"kill-session\", \"-t\", name],\n result.stderr,\n result.stdout,\n result.exitCode,\n );\n }\n}\n\n// ─── Windows ───────────────────────────────────────────────────────────\n\nexport async function listWindows(session?: string): Promise<TmuxWindow[]> {\n if (session) {\n const out = await tmux([\"list-windows\", \"-t\", session, \"-F\", \"#{window_id}\\t#{window_name}\"]);\n return parseWindows(out);\n }\n // Cross-session: include the session name.\n const out = await tmux([\n \"list-windows\",\n \"-a\",\n \"-F\",\n \"#{session_name}\\t#{window_id}\\t#{window_name}\",\n ]);\n const windows: TmuxWindow[] = [];\n for (const line of out.split(\"\\n\")) {\n if (line.length === 0) continue;\n const [sessionName, id, name] = line.split(\"\\t\");\n if (!sessionName || !id || name === undefined) continue;\n windows.push({ id, name, sessionName });\n }\n return windows;\n}\n\nfunction parseWindows(output: string): TmuxWindow[] {\n const windows: TmuxWindow[] = [];\n for (const line of output.split(\"\\n\")) {\n if (line.length === 0) continue;\n const [id, name] = line.split(\"\\t\");\n if (!id || name === undefined) continue;\n windows.push({ id, name });\n }\n return windows;\n}\n\nexport interface NewWindowOptions {\n /** Target session. Required if invoking outside an existing tmux client. */\n session?: string;\n /** Window name. Maps to the agent's `tab:` value (or its name if no tab). */\n name: string;\n /** Command to run in the first pane. */\n command: string;\n /** If true, do not switch focus. Defaults to true. */\n detached?: boolean;\n /** Initial working directory (`-c <path>`). */\n cwd?: string;\n /** Extra env vars to set in the new pane via tmux `-e KEY=VALUE`. */\n env?: Record<string, string>;\n}\n\n/**\n * Create a new tmux window with one pane. Returns the new pane's stable\n * pane id (e.g. `%15`).\n */\nexport async function newWindow(opts: NewWindowOptions): Promise<string> {\n const args = [\"new-window\"];\n if (opts.detached !== false) args.push(\"-d\");\n if (opts.session) args.push(\"-t\", opts.session);\n args.push(\"-n\", opts.name);\n if (opts.cwd) args.push(\"-c\", opts.cwd);\n appendEnvFlags(args, opts.env);\n args.push(\"-P\", \"-F\", \"#{pane_id}\", opts.command);\n const out = (await tmux(args)).trim();\n assertValidPaneId(out);\n return out;\n}\n\n// ─── Panes ─────────────────────────────────────────────────────────────\n\n/**\n * List ALL panes in a tmux session (across every window). Used by\n * reconciliation to find every pane in the workstream's session.\n *\n * Note `list-panes -t <session>` (no -s) lists panes in the current\n * *window* of that session, not the whole session — a common gotcha.\n * `-s` is the flag that says \"all panes in this session.\"\n *\n * Returns `[]` (not throws) when the session doesn't exist or has no\n * panes. tmux destroys a session as soon as its last pane closes, so the\n * \"session was just here a moment ago\" case is normal during reconcile.\n * tmux's error wording in this case varies (\"can't find session\" or\n * \"can't find window\"), so we match either.\n */\nexport async function listPanesInSession(session: string): Promise<TmuxPane[]> {\n const args = [\n \"list-panes\",\n \"-s\",\n \"-t\",\n session,\n \"-F\",\n \"#{window_id}\\t#{pane_id}\\t#{pane_title}\\t#{pane_current_command}\",\n ];\n const result = await currentExecutor(args);\n if (result.exitCode !== 0) {\n if (/can't find (session|window)|no server running|no sessions/i.test(result.stderr)) {\n return [];\n }\n throw new TmuxError(args, result.stderr, result.stdout, result.exitCode);\n }\n const panes: TmuxPane[] = [];\n for (const line of result.stdout.split(\"\\n\")) {\n if (line.length === 0) continue;\n const [windowId, paneId, title, command] = line.split(\"\\t\");\n if (!windowId || !paneId || command === undefined) continue;\n panes.push({ paneId, title: title ?? \"\", command, windowId });\n }\n return panes;\n}\n\n/**\n * List panes in the current session, a specific window/session target, or\n * all panes across all sessions when `target` is the literal \"*\".\n */\nexport async function listPanes(target?: string): Promise<TmuxPane[]> {\n if (target === \"*\") {\n const out = await tmux([\n \"list-panes\",\n \"-a\",\n \"-F\",\n \"#{session_name}\\t#{window_id}\\t#{pane_id}\\t#{pane_title}\\t#{pane_current_command}\",\n ]);\n const panes: TmuxPane[] = [];\n for (const line of out.split(\"\\n\")) {\n if (line.length === 0) continue;\n const [sessionName, windowId, paneId, title, command] = line.split(\"\\t\");\n if (!sessionName || !windowId || !paneId || command === undefined) continue;\n panes.push({ paneId, title: title ?? \"\", command, windowId, sessionName });\n }\n return panes;\n }\n\n const args = [\"list-panes\"];\n if (target !== undefined) args.push(\"-t\", target);\n args.push(\"-F\", \"#{pane_id}\\t#{pane_title}\\t#{pane_current_command}\");\n const out = await tmux(args);\n const panes: TmuxPane[] = [];\n for (const line of out.split(\"\\n\")) {\n if (line.length === 0) continue;\n const [paneId, title, command] = line.split(\"\\t\");\n if (!paneId || command === undefined) continue;\n panes.push({ paneId, title: title ?? \"\", command });\n }\n return panes;\n}\n\nexport interface SplitWindowOptions {\n /** Target window or pane (e.g. \":Backend\" or \"%15\"). */\n target: string;\n command: string;\n /** Horizontal split (side-by-side). Default true. */\n horizontal?: boolean;\n detached?: boolean;\n /** Initial working directory for the new pane (`-c <path>`). */\n cwd?: string;\n /** Extra env vars to set in the new pane via tmux `-e KEY=VALUE`. */\n env?: Record<string, string>;\n}\n\n/**\n * Split a window and run a command in the new pane. Returns the new pane's\n * stable pane id.\n */\nexport async function splitWindow(opts: SplitWindowOptions): Promise<string> {\n const args = [\"split-window\"];\n if (opts.horizontal !== false) args.push(\"-h\");\n if (opts.detached !== false) args.push(\"-d\");\n args.push(\"-t\", opts.target);\n if (opts.cwd) args.push(\"-c\", opts.cwd);\n appendEnvFlags(args, opts.env);\n args.push(\"-P\", \"-F\", \"#{pane_id}\", opts.command);\n const out = (await tmux(args)).trim();\n assertValidPaneId(out);\n return out;\n}\n\n/**\n * Push one `-e KEY=VALUE` flag per entry into `args`, validating that\n * keys are non-empty and contain no `=` (tmux would error obscurely\n * otherwise; throwing TypeError keeps the failure at the call site).\n * No-op when `env` is undefined or empty.\n *\n * Iteration order follows Object.entries (insertion order); tests\n * shouldn't depend on a specific ordering, only on the presence of\n * each `-e KEY=VALUE` pair in the captured args.\n */\nfunction appendEnvFlags(args: string[], env: Record<string, string> | undefined): void {\n if (!env) return;\n for (const [k, v] of Object.entries(env)) {\n if (k.length === 0) {\n throw new TypeError(\"tmux env key must be non-empty\");\n }\n if (k.includes(\"=\")) {\n throw new TypeError(`tmux env key must not contain '=': ${JSON.stringify(k)}`);\n }\n args.push(\"-e\", `${k}=${v}`);\n }\n}\n\n/** Idempotent: succeeds even if the pane is already gone. */\nexport async function killPane(paneId: string): Promise<void> {\n assertValidPaneId(paneId);\n const result = await currentExecutor([\"kill-pane\", \"-t\", paneId]);\n if (result.exitCode !== 0 && !/can't find pane/i.test(result.stderr)) {\n throw new TmuxError([\"kill-pane\", \"-t\", paneId], result.stderr, result.stdout, result.exitCode);\n }\n}\n\nexport async function paneExists(paneId: string): Promise<boolean> {\n if (!isValidPaneId(paneId)) return false;\n // tmux's `display-message -t <bogus>` exits 0 but emits empty output; we\n // must check that the echoed pane id matches what we asked for.\n const result = await currentExecutor([\"display-message\", \"-t\", paneId, \"-p\", \"#{pane_id}\"]);\n if (result.exitCode !== 0) return false;\n return result.stdout.trim() === paneId;\n}\n\nexport async function setPaneTitle(paneId: string, title: string): Promise<void> {\n assertValidPaneId(paneId);\n await tmux([\"select-pane\", \"-t\", paneId, \"-T\", title]);\n}\n\n/**\n * Look up the window id (e.g. `@42`) that contains a given pane id\n * (e.g. `%15`). Used by spawn so we can apply window-scoped options\n * (`pane-border-status`) to the freshly created window.\n *\n * Returns undefined if the pane no longer exists.\n */\nexport async function getWindowIdForPane(paneId: string): Promise<string | undefined> {\n if (!isValidPaneId(paneId)) return undefined;\n const result = await currentExecutor([\"display-message\", \"-t\", paneId, \"-p\", \"#{window_id}\"]);\n if (result.exitCode !== 0) return undefined;\n const id = result.stdout.trim();\n return id.length > 0 ? id : undefined;\n}\n\n/**\n * Single source of truth for the operator opt-out from the mu pane\n * banner / border decorations. Set `MU_BANNER_QUIET=1` to disable.\n * All `enableMuPaneBorders*` helpers self-check this so callers\n * don't have to wrap them in env guards (a footgun: forget the\n * guard and you set the border even when the operator wanted\n * quiet).\n */\nfunction muBannersDisabled(): boolean {\n return process.env.MU_BANNER_QUIET === \"1\";\n}\n\n/**\n * Apply the mu pane border (status=top, format='[mu] #{pane_title}')\n * to EVERY window currently in `session`. Idempotent. Best-effort:\n * windows that have vanished mid-iteration are silently skipped. Used\n * by `mu workstream init` (covers the placeholder `_mu` window plus\n * any windows that already exist, e.g. on re-init of an upgraded\n * mu-pre-border session) and by `mu agent spawn` (covers the\n * just-created window so the border shows immediately on attach).\n *\n * No-op (returns 0) when `MU_BANNER_QUIET=1`.\n *\n * Returns the number of windows that received the option.\n */\nexport async function enableMuPaneBordersForSession(session: string): Promise<number> {\n if (muBannersDisabled()) return 0;\n const windows = await listWindows(session).catch(() => []);\n let n = 0;\n for (const w of windows) {\n try {\n await enableMuPaneBorders(w.id);\n n += 1;\n } catch {\n // Window vanished; skip silently. Border is decorative.\n }\n }\n return n;\n}\n\n/**\n * Apply the mu pane border to the window containing `paneId`. This is\n * the spawn/adopt shape: callers have a pane id (from `new-window` or\n * from an adopt target), and need to resolve the enclosing window\n * before calling `enableMuPaneBorders` (a window-scoped option).\n *\n * Self-checks `MU_BANNER_QUIET` and swallows tmux errors — the border\n * is decorative; failing to set it is never load-bearing.\n */\nexport async function enableMuPaneBordersForPane(paneId: string): Promise<void> {\n if (muBannersDisabled()) return;\n const wid = await getWindowIdForPane(paneId).catch(() => undefined);\n if (wid) await enableMuPaneBorders(wid).catch(() => {});\n}\n\n/**\n * Enable a one-line top pane border on a specific window/session target,\n * showing `[mu] <pane-title>`. Idempotent (set-option is a write, not\n * a toggle).\n *\n * IMPORTANT: tmux's `pane-border-status` and `pane-border-format` are\n * **window** options, not session options. `set-option -t <session>`\n * only updates the active window at call time — windows created later\n * inherit from the GLOBAL value (which is `off` by default and which\n * we deliberately do NOT touch, since changing the global would\n * affect every other tmux session on the user's machine, including\n * dotfile-curated ones).\n *\n * Therefore mu must call this twice:\n * 1. At `mu workstream init` time on the placeholder `_mu` window\n * (so an attached operator sees a border immediately).\n * 2. On every `mu agent spawn` (which calls `tmux new-window`),\n * against the new window's id.\n *\n * The border is tmux chrome, not pane content: it doesn't scroll, it\n * survives copy-mode, and the inner CLI never sees it.\n *\n * Designed as the pane-border visual cue for mu-managed panes.\n */\nexport async function enableMuPaneBorders(target: string): Promise<void> {\n if (muBannersDisabled()) return;\n await tmux([\"set-option\", \"-w\", \"-t\", target, \"pane-border-status\", \"top\"]);\n await tmux([\"set-option\", \"-w\", \"-t\", target, \"pane-border-format\", \" [mu] #{pane_title} \"]);\n // Bottom + sides: heavy box-drawing lines so a mu-managed pane is\n // visually distinct even when not the active pane (top carries the\n // labeled status text; the rest of the frame carries the visual\n // \"this is mu\" cue). Cyan-bold for the active pane, dim brightblack\n // for inactive ones, so the operator's eye lands on the pane that\n // currently has focus.\n await tmux([\"set-option\", \"-w\", \"-t\", target, \"pane-border-lines\", \"heavy\"]);\n await tmux([\"set-option\", \"-w\", \"-t\", target, \"pane-active-border-style\", \"fg=cyan,bold\"]);\n await tmux([\"set-option\", \"-w\", \"-t\", target, \"pane-border-style\", \"fg=brightblack\"]);\n}\n\n/**\n * Look up the TTY device path for a pane (e.g. `/dev/ttys012` on macOS,\n * `/dev/pts/3` on Linux). Used by `mu agent kick` to find the\n * foreground process group on the pane's TTY so it can be signalled\n * directly — `tmux send-keys C-c` does NOT propagate to wrapped\n * subprocesses inside a CLI like pi/claude/codex (the CLI catches it\n * itself and treats it as a UI input). The escape hatch is signalling\n * the foreground pgid of the underlying TTY from outside the pane.\n *\n * Throws `PaneNotFoundError` when the pane id is invalid or the pane\n * has vanished. Throws `TmuxError` on any other tmux failure.\n */\nexport async function paneTTY(paneId: string): Promise<string> {\n assertValidPaneId(paneId);\n const result = await currentExecutor([\"display-message\", \"-t\", paneId, \"-p\", \"#{pane_tty}\"]);\n if (result.exitCode !== 0) {\n if (/can't find pane|pane not found/i.test(result.stderr)) {\n throw new PaneNotFoundError(paneId);\n }\n throw new TmuxError(\n [\"display-message\", \"-t\", paneId, \"-p\", \"#{pane_tty}\"],\n result.stderr,\n result.stdout,\n result.exitCode,\n );\n }\n const tty = result.stdout.trim();\n if (tty === \"\") throw new PaneNotFoundError(paneId);\n return tty;\n}\n\nexport async function getPaneTitle(paneId: string): Promise<string | undefined> {\n if (!isValidPaneId(paneId)) return undefined;\n const result = await currentExecutor([\"display-message\", \"-t\", paneId, \"-p\", \"#{pane_title}\"]);\n if (result.exitCode !== 0) return undefined;\n return result.stdout.trimEnd();\n}\n\n/**\n * Read the title of the *current* pane (the one whose shell is running this\n * process), via $TMUX_PANE. Returns undefined when not inside tmux. Used by\n * `mu claim` to derive the agent identity from the pane title — the claim\n * protocol's zero-config identity step.\n */\nexport async function currentPaneTitle(): Promise<string | undefined> {\n const paneId = process.env.TMUX_PANE;\n if (!paneId || !isValidPaneId(paneId)) return undefined;\n return getPaneTitle(paneId);\n}\n\n/**\n * Extract the agent-name token from a (possibly composed) pane title.\n * mu's composeAgentTitle renders titles as `name · <glyph> · task_id`,\n * where <glyph> is a Nerd Font codepoint from STATUS_EMOJI (see\n * src/agents.ts). The agent name is always the first ' · '-separated\n * token. Adopted panes that haven't been re-titled by mu have just the\n * name (one token) — still parses.\n *\n * Returns trimmed name, or the input unchanged if no separator.\n */\nexport function parseAgentNameFromTitle(title: string): string {\n const idx = title.indexOf(\" · \");\n return idx === -1 ? title.trim() : title.slice(0, idx).trim();\n}\n\n/**\n * Convenience: read the current pane's title and extract the agent name.\n */\nexport async function currentAgentName(): Promise<string | undefined> {\n const title = await currentPaneTitle();\n if (title === undefined) return undefined;\n return parseAgentNameFromTitle(title);\n}\n\nexport async function selectLayout(window: string, layout: string): Promise<void> {\n await tmux([\"select-layout\", \"-t\", window, layout]);\n}\n\n// ─── Send protocol (the canonical bracketed-paste sequence) ────────────\n\nexport interface SendOptions {\n /** Override the default delay between paste and Enter, in ms. */\n delayMs?: number;\n}\n\n/**\n * Send a single line of text to a pane and submit it.\n *\n * Sequence:\n * 1. exit copy mode (silent if not in copy mode)\n * 2. load text into a uniquely-named tmux buffer\n * 3. paste with bracketed-paste mode (-p) so apps treat as literal text;\n * delete buffer after paste (-d); preserve LF (-r)\n * 4. wait MU_SEND_DELAY_MS (default 500) so the agent ingests the text\n * 5. send Enter as a real key event\n *\n * Naive `send-keys \"<text>\"` would let characters like /, ?, f, : be\n * interpreted by the agent's TUI or by tmux's copy mode. Always use this.\n */\nexport async function sendToPane(\n paneId: string,\n text: string,\n opts: SendOptions = {},\n): Promise<void> {\n assertValidPaneId(paneId);\n\n // 1. Exit copy mode silently. -q suppresses errors when not in copy mode.\n const copyResult = await currentExecutor([\"copy-mode\", \"-q\", \"-t\", paneId]);\n // Even with -q, some tmux versions report errors. Swallow non-fatal.\n if (copyResult.exitCode !== 0 && /can't find pane|no current target/i.test(copyResult.stderr)) {\n throw new TmuxError(\n [\"copy-mode\", \"-q\", \"-t\", paneId],\n copyResult.stderr,\n copyResult.stdout,\n copyResult.exitCode,\n );\n }\n\n // 2. Load text into a uniquely-named buffer.\n const bufferName = `mu-send-${process.pid}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;\n await tmux([\"set-buffer\", \"-b\", bufferName, text]);\n\n // 3. Bracketed paste: -p wraps in \\e[200~...\\e[201~ so apps see literal\n // text; -d deletes buffer after paste; -r preserves LF (no CR conversion).\n try {\n await tmux([\"paste-buffer\", \"-p\", \"-d\", \"-r\", \"-b\", bufferName, \"-t\", paneId]);\n } catch (err) {\n // Best-effort buffer cleanup if paste failed before -d took effect.\n await currentExecutor([\"delete-buffer\", \"-b\", bufferName]).catch(() => {});\n throw err;\n }\n\n // 4. Wait for the agent CLI to ingest the pasted text.\n const delay = opts.delayMs ?? defaultSendDelayMs();\n if (delay > 0) await currentSleep(delay);\n\n // 5. Submit. Enter must be a real key event, not part of the paste.\n await tmux([\"send-keys\", \"-t\", paneId, \"Enter\"]);\n}\n\n// ─── Capture ───────────────────────────────────────────────────────────\n\nexport interface CaptureOptions {\n /**\n * Number of trailing lines to capture. Omitted = full scrollback.\n * 0 = visible pane only.\n */\n lines?: number;\n}\n\n/**\n * Read pane scrollback as plain text (no ANSI escapes).\n *\n * - No options: full scrollback (`-S - -E -`)\n * - `lines: 0`: visible pane only\n * - `lines: N`: last N lines (`-S -N`)\n */\nexport async function capturePane(paneId: string, opts: CaptureOptions = {}): Promise<string> {\n assertValidPaneId(paneId);\n const args = [\"capture-pane\", \"-t\", paneId, \"-p\"];\n if (opts.lines === undefined) {\n args.push(\"-S\", \"-\", \"-E\", \"-\");\n } else if (opts.lines > 0) {\n args.push(\"-S\", `-${opts.lines}`);\n }\n return tmux(args);\n}\n","// mu — the canonical \"reality wins\" reconciliation routine.\n//\n// Three steps, in order:\n//\n// 1. Prune ghost rows whose pane no longer exists in tmux.\n// 2. Detect status from pane scrollback for surviving agents.\n// 3. Surface orphan panes that look like agents but have no DB row.\n// Do NOT auto-adopt — `mu list` shows orphans under a separate\n// section and the user runs `mu agent adopt` (roadmap) to formally claim.\n//\n// `mu list` and `mu doctor` both call this. It's the only place where\n// the registry's view of the world is reconciled against tmux's view.\n\nimport {\n type AgentRow,\n deleteAgent,\n isPendingPaneId,\n listAgents,\n refreshAgentTitle,\n resolveCliCommand,\n shouldOverwriteAgentStatus,\n updateAgentStatus,\n} from \"./agents.js\";\nimport type { Db } from \"./db.js\";\nimport { detectPiStatus } from \"./detect.js\";\nimport { type TmuxPane, capturePane, listPanesInSession } from \"./tmux.js\";\n\n/**\n * What kind of reconciliation pass to run.\n *\n * \"full\" Default for `mu agent list`. Prunes ghosts (deleting\n * the registry row, which fires the deleteAgent reaper\n * that flips IN_PROGRESS tasks back to OPEN with\n * [reaper] notes), runs status detection against\n * surviving panes, surfaces orphans.\n *\n * \"status-only\" The \"freshen the operator's view\" mode. Runs status\n * detection (DB writes that update agent status +\n * pane title — desired side-effects of a refresh) and\n * orphan surface. Does NOT prune (so a dead pane's\n * row stays visible until a real `mu agent list`) and\n * does NOT reap. Used by `mu state` and\n * `mu agent attach` — the verbs an operator polls to\n * answer \"is worker-X busy or idle right\n * now?\". Status detection skips placeholder agents\n * whose pane id starts with `%pending-` (mid-spawn,\n * no usable scrollback yet).\n *\n * \"report-only\" Pure observation. Counts would-be-pruned ghosts\n * without deleting; skips status detection entirely\n * (no DB writes, no tmux title writes); surfaces\n * orphans (pure read). Used by `mu undo` (the\n * post-restore pass MUST NOT delete rows the snapshot\n * just restored — see\n * snap_undo_reconcile_destroys_recovered_agents) and\n * `mu doctor` (read-only diagnostic).\n *\n * Surfaced live by bug_pane_title_glyph_stuck_at_needs_input: the\n * old `dryRun: boolean` flag conflated \"don't prune\" with \"don't\n * detect status\", so state-card pollers showed stale status\n * indefinitely. Splitting prune-suppression from status-suppression\n * is the fix.\n */\nexport type ReconcileMode = \"full\" | \"status-only\" | \"report-only\";\n\nexport interface ReconcileOptions {\n /** The workstream whose registry rows we're reconciling. */\n workstream: string;\n /**\n * Override the tmux session name. Defaults to `mu-<workstream>`. Useful\n * for tests and for the rare case where a workstream's tmux session was\n * created with a non-default name.\n */\n tmuxSession?: string;\n /**\n * Which kind of pass to run. Default is `\"full\"` (the documented\n * mutating behaviour `mu agent list` has always had). See\n * `ReconcileMode` for the full per-mode contract.\n *\n * BREAKING: this replaces the previous `dryRun?: boolean` flag.\n * Migration: `dryRun: true` → `mode: \"report-only\"`; default\n * (`dryRun: false` / unset) → `mode: \"full\"`.\n */\n mode?: ReconcileMode;\n}\n\nexport interface ReconcileReport {\n /** Number of registry rows whose pane was gone. In status-only and\n * report-only modes this is the count of rows that WOULD have\n * been pruned; in `full` mode it's the count actually deleted. */\n prunedGhosts: number;\n /** Number of agents whose status was changed by scrollback detection.\n * Always 0 in `report-only` mode (status detection is skipped). */\n statusChanges: number;\n /** Panes in the workstream's tmux session that look like agents but\n * aren't in the registry. NOT auto-adopted. */\n orphans: TmuxPane[];\n /** Which mode this report was generated in. Lets callers switch their\n * output text (\"agents pruned\" vs \"would-be-pruned (suppressed)\")\n * without re-deriving from options. */\n mode: ReconcileMode;\n}\n\n/**\n * Pane commands that suggest \"this is an agent, surface it as an orphan.\"\n * 0.1.0 scope: pi only is detected, but the orphan list will surface\n * claude/codex panes too so the user can adopt them later.\n *\n * Also includes any env-overridden binary names (e.g. `MU_PI_COMMAND=pi-alt`\n * makes \"pi-alt\" agent-worthy) so externally-spawned panes running the\n * user's actual pi binary are still surfaced as orphans.\n */\nconst BASE_AGENT_CLIS: readonly string[] = [\"pi\", \"claude\", \"codex\"];\n\nfunction knownAgentCommands(): ReadonlySet<string> {\n const names = new Set<string>(BASE_AGENT_CLIS);\n for (const cli of BASE_AGENT_CLIS) {\n names.add(resolveCliCommand(cli));\n }\n return names;\n}\n\n/**\n * Build a recogniser closure that captures one snapshot of\n * knownAgentCommands() (which itself reads MU_<CLI>_COMMAND env vars).\n * Hoisted out of the orphan-surface loop so reconcile() reads each env\n * var at most once per pass instead of once per pane: dozens of panes\n * × three env vars per call = needless syscalls in a `mu state` poll\n * tick. Also pins the env-var snapshot for the loop, so test suites\n * that twiddle MU_PI_COMMAND in afterEach can't race the inner check.\n */\nfunction buildAgentPaneRecogniser(): (pane: TmuxPane) => boolean {\n const known = knownAgentCommands();\n return (pane) => known.has(pane.command);\n}\n\nexport async function reconcile(db: Db, opts: ReconcileOptions): Promise<ReconcileReport> {\n const sessionName = opts.tmuxSession ?? `mu-${opts.workstream}`;\n const mode: ReconcileMode = opts.mode ?? \"full\";\n const dbAgents = listAgents(db, { workstream: opts.workstream });\n const tmuxPanes = await listPanesInSession(sessionName);\n const tmuxByPaneId = new Map(tmuxPanes.map((p) => [p.paneId, p]));\n\n let prunedGhosts = 0;\n let statusChanges = 0;\n const orphans: TmuxPane[] = [];\n\n // 1. Prune ghosts (DB row references a pane that no longer exists).\n // Only `full` mode actually deletes; `status-only` and\n // `report-only` count the would-be-prunes so callers can surface\n // drift, but leave the row in place. The orphan-surface step\n // always treats them as \"not-in-tmux\" so the orphan list\n // semantics don't change.\n const survivors: AgentRow[] = [];\n for (const agent of dbAgents) {\n if (tmuxByPaneId.has(agent.paneId)) {\n survivors.push(agent);\n } else {\n if (mode === \"full\") deleteAgent(db, agent.name, agent.workstreamName);\n prunedGhosts++;\n }\n }\n\n // 2. Detect status from scrollback for survivors. capturePane uses the\n // last 100 lines, which is the same window the detector operates on.\n //\n // `report-only` skips this entirely — status detection writes to\n // the DB (updateAgentStatus + refreshAgentTitle), and the\n // report-only contract is \"no mutation\".\n //\n // `status-only` runs detection but skips placeholder agents\n // whose pane id starts with `%pending-` — those have no usable\n // scrollback yet (mid-spawn) and the placeholder pane id won't\n // resolve to a real tmux pane anyway. The pending sentinel is\n // documented in src/agents.ts (PENDING_PANE_PREFIX). Without\n // this skip, status-only would still try to capturePane on a\n // fake id and tmux would error.\n if (mode !== \"report-only\") {\n for (const agent of survivors) {\n if (isPendingPaneId(agent.paneId)) continue;\n const scrollback = await capturePane(agent.paneId, { lines: 100 });\n const detected = detectPiStatus(scrollback);\n if (shouldOverwriteAgentStatus(agent.status, detected) && detected !== agent.status) {\n updateAgentStatus(db, agent.name, detected, agent.workstreamName);\n statusChanges++;\n }\n // ALWAYS refresh the pane title (even when status didn't change),\n // so that:\n // 1. Inner CLIs that self-set their pane title (pi, pi-meta, vim,\n // tmux's default 'host - dir') get overwritten with mu's\n // composed title.\n // 2. Task-ownership changes that happen between reconciles\n // (claim / release / close) re-propagate even if the status\n // detector didn't flip.\n // Best-effort: a tmux failure here never blocks the reconcile report.\n await refreshAgentTitle(db, agent.name, agent.workstreamName);\n }\n }\n\n // 3. Surface orphan panes. `looksLikeAgentPane` is conservative:\n // pane.command must be one we recognise as an agent CLI. A bash\n // pane the user spawned for their own use is never an orphan.\n // Pure read; runs in every mode.\n const dbPaneIds = new Set(survivors.map((a) => a.paneId));\n const looksLikeAgentPane = buildAgentPaneRecogniser();\n for (const pane of tmuxPanes) {\n if (dbPaneIds.has(pane.paneId)) continue;\n if (looksLikeAgentPane(pane)) orphans.push(pane);\n }\n\n return { prunedGhosts, statusChanges, orphans, mode };\n}\n","// mu — capture and list snapshots.\n\nimport { existsSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { CURRENT_SCHEMA_VERSION, type Db } from \"../db.js\";\nimport {\n type CaptureSnapshotResult,\n type ListSnapshotsOptions,\n type RawSnapshotRow,\n type SnapshotRow,\n gcMaxAgeDays,\n gcMaxCount,\n rowFromDb,\n snapshotsDir,\n} from \"./core.js\";\n\nexport function captureSnapshot(\n db: Db,\n label: string,\n workstream: string | null = null,\n): CaptureSnapshotResult {\n const dir = snapshotsDir(db);\n mkdirSync(dir, { recursive: true });\n\n const insert = db\n .prepare(\n \"INSERT INTO snapshots (workstream, label, db_path, schema_version, created_at) VALUES (?, ?, ?, ?, ?)\",\n )\n .run(workstream, label, \"\", CURRENT_SCHEMA_VERSION, new Date().toISOString());\n const id = Number(insert.lastInsertRowid);\n const dbPath = join(dir, `${id}.db`);\n\n try {\n db.prepare(\"UPDATE snapshots SET db_path = ? WHERE id = ?\").run(dbPath, id);\n if (existsSync(dbPath)) unlinkSync(dbPath);\n db.exec(`VACUUM INTO ${quoteSqlString(dbPath)}`);\n } catch (err) {\n db.prepare(\"DELETE FROM snapshots WHERE id = ?\").run(id);\n try {\n if (existsSync(dbPath)) unlinkSync(dbPath);\n } catch {\n // Ignore — we're already on the failure path.\n }\n throw err;\n }\n\n try {\n gcSnapshots(db);\n } catch {\n // Insurance, not version history: GC failure must not break the destructive verb.\n }\n\n return { id, dbPath };\n}\n\nfunction quoteSqlString(s: string): string {\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nexport function listSnapshots(db: Db, opts: ListSnapshotsOptions = {}): SnapshotRow[] {\n const conditions: string[] = [];\n const params: unknown[] = [];\n if (opts.workstream !== undefined) {\n conditions.push(\"(workstream = ? OR workstream IS NULL)\");\n params.push(opts.workstream);\n }\n const where = conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n const limit = opts.limit !== undefined ? `LIMIT ${Math.max(0, Math.floor(opts.limit))}` : \"\";\n const rows = db\n .prepare(`SELECT * FROM snapshots ${where} ORDER BY id DESC ${limit}`)\n .all(...params) as RawSnapshotRow[];\n return rows.map(rowFromDb);\n}\n\nexport function gcSnapshots(db: Db): { deletedRows: number; deletedFiles: number } {\n const keepLast = gcMaxCount();\n const cutoffDate = new Date(Date.now() - gcMaxAgeDays() * 24 * 60 * 60 * 1000).toISOString();\n const protectedIds = (\n db.prepare(`SELECT id FROM snapshots ORDER BY id DESC LIMIT ${keepLast}`).all() as Array<{\n id: number;\n }>\n ).map((r) => r.id);\n const placeholders = protectedIds.length > 0 ? protectedIds.map(() => \"?\").join(\",\") : \"NULL\";\n const victims = db\n .prepare(\n `SELECT id, db_path FROM snapshots WHERE id NOT IN (${placeholders}) OR created_at < ?`,\n )\n .all(...protectedIds, cutoffDate) as Array<{ id: number; db_path: string }>;\n\n if (victims.length === 0) return { deletedRows: 0, deletedFiles: 0 };\n\n let deletedFiles = 0;\n for (const v of victims) {\n try {\n if (existsSync(v.db_path)) {\n unlinkSync(v.db_path);\n deletedFiles += 1;\n }\n } catch {\n // ignore — orphan file is preferable to a half-completed GC\n }\n }\n\n const ids = victims.map((v) => v.id);\n const inList = ids.map(() => \"?\").join(\",\");\n const result = db.prepare(`DELETE FROM snapshots WHERE id IN (${inList})`).run(...ids);\n return { deletedRows: result.changes, deletedFiles };\n}\n","// mu — snapshot shared types, errors, paths, and row helpers.\n\nimport { statSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { CURRENT_SCHEMA_VERSION, type Db, defaultStateDir } from \"../db.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\n\nexport interface SnapshotRow {\n /** Operator-facing snapshot id. EXCEPTION to the no-surrogate-ids rule:\n * snapshots have no human-meaningful name; the id is what the\n * operator types in `mu undo --to <id>` / `mu snapshot show <id>`. */\n id: number;\n /** NULL for whole-DB snapshots (e.g. workstream destroy). */\n workstreamName: string | null;\n /** Human-readable operation label, e.g. \"task close design\". */\n label: string;\n /** Absolute path to the .db file on disk. */\n dbPath: string;\n /** schema_version at the moment of capture. */\n schemaVersion: number;\n /** ISO-8601 capture timestamp. */\n createdAt: string;\n}\n\nexport interface RawSnapshotRow {\n id: number;\n workstream: string | null;\n label: string;\n db_path: string;\n schema_version: number;\n created_at: string;\n}\n\nexport function rowFromDb(r: RawSnapshotRow): SnapshotRow {\n return {\n id: r.id,\n workstreamName: r.workstream,\n label: r.label,\n dbPath: r.db_path,\n schemaVersion: r.schema_version,\n createdAt: r.created_at,\n };\n}\n\nexport interface ListSnapshotsOptions {\n /** Filter to one workstream. NULL-workstream rows are also returned\n * when this is set, since they (workstream-destroy snapshots) span\n * every workstream including this one. */\n workstream?: string;\n /** Cap the number of rows returned. Default: no cap. */\n limit?: number;\n}\n\nexport interface CaptureSnapshotResult {\n id: number;\n dbPath: string;\n}\n\nexport interface RestoreSnapshotResult {\n id: number;\n /** The path the snapshot was copied to (the live DB path). */\n restoredTo: string;\n /** schema_version of the restored snapshot (== CURRENT_SCHEMA_VERSION\n * by virtue of having passed the version check). */\n schemaVersion: number;\n}\n\nexport class SnapshotNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"SnapshotNotFoundError\";\n constructor(public readonly snapshotId: number) {\n super(`no such snapshot: ${snapshotId}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List available snapshots\", command: \"mu snapshot list\" },\n {\n intent: \"Look one up directly\",\n command: `mu sql \"SELECT id, label, created_at FROM snapshots ORDER BY id DESC\"`,\n },\n ];\n }\n}\n\nexport class SnapshotVersionMismatchError extends Error implements HasNextSteps {\n override readonly name = \"SnapshotVersionMismatchError\";\n constructor(\n public readonly snapshotId: number,\n public readonly snapshotVersion: number,\n public readonly currentVersion: number,\n ) {\n const direction =\n snapshotVersion < currentVersion\n ? \"older — your DB has migrated past it\"\n : \"newer — written by a newer mu binary\";\n super(\n `snapshot ${snapshotId} is at schema v${snapshotVersion}; current DB is at v${currentVersion} (${direction}). mu does not auto-migrate snapshots; refusing restore.`,\n );\n }\n errorNextSteps(): NextStep[] {\n const olderSnapshot = this.snapshotVersion < this.currentVersion;\n return olderSnapshot\n ? [\n {\n intent: \"Pick a newer snapshot at the current schema\",\n command: `mu sql \"SELECT id, label, created_at FROM snapshots WHERE schema_version = ${this.currentVersion} ORDER BY id DESC\"`,\n },\n {\n intent: \"Inspect the stale snapshot read-only (snapshot is forensic; bypass mu)\",\n command: `sqlite3 <snapshot-path> \"SELECT * FROM tasks\"`,\n },\n ]\n : [\n {\n intent: \"Run mu with a newer binary that knows this schema\",\n command: \"npm install -g @martintrojer/mu@latest\",\n },\n ];\n }\n}\n\nexport class SnapshotFileMissingError extends Error implements HasNextSteps {\n override readonly name = \"SnapshotFileMissingError\";\n constructor(\n public readonly snapshotId: number,\n public readonly dbPath: string,\n ) {\n super(`snapshot ${snapshotId} row exists but file is missing: ${dbPath}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Drop the orphan row\",\n command: `mu sql \"DELETE FROM snapshots WHERE id = ${this.snapshotId}\"`,\n },\n { intent: \"List remaining snapshots\", command: \"mu snapshot list\" },\n ];\n }\n}\n\nconst DEFAULT_GC_MAX_COUNT = 100;\nconst DEFAULT_GC_MAX_AGE_DAYS = 14;\n\nexport function gcMaxCount(): number {\n const env = process.env.MU_SNAPSHOT_KEEP_LAST;\n if (env === undefined || env === \"\") return DEFAULT_GC_MAX_COUNT;\n const n = Number.parseInt(env, 10);\n if (!Number.isFinite(n) || n < 0) return DEFAULT_GC_MAX_COUNT;\n return n;\n}\n\nexport function gcMaxAgeDays(): number {\n const env = process.env.MU_SNAPSHOT_MAX_AGE_DAYS;\n if (env === undefined || env === \"\") return DEFAULT_GC_MAX_AGE_DAYS;\n const n = Number.parseInt(env, 10);\n if (!Number.isFinite(n) || n < 0) return DEFAULT_GC_MAX_AGE_DAYS;\n return n;\n}\n\nexport function snapshotsDir(db?: Db): string {\n if (db) {\n const livePath = (db as Db & { name: string }).name;\n if (livePath && livePath !== \":memory:\") {\n return join(dirname(livePath), \"snapshots\");\n }\n }\n return join(defaultStateDir(), \"snapshots\");\n}\n\nexport function isStaleVersion(row: { schemaVersion: number }): boolean {\n return row.schemaVersion !== CURRENT_SCHEMA_VERSION;\n}\n\nexport function snapshotFileSize(snapshot: SnapshotRow): number | null {\n try {\n return statSync(snapshot.dbPath).size;\n } catch {\n return null;\n }\n}\n","// mu — manual snapshot cleanup verbs.\n\nimport { existsSync, statSync, unlinkSync } from \"node:fs\";\nimport type { Db } from \"../db.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport { captureSnapshot } from \"./capture.js\";\nimport { listSnapshots } from \"./capture.js\";\nimport {\n type RawSnapshotRow,\n SnapshotNotFoundError,\n type SnapshotRow,\n gcMaxAgeDays,\n gcMaxCount,\n isStaleVersion,\n rowFromDb,\n snapshotFileSize,\n} from \"./core.js\";\n\nexport type PruneMode = \"gc\" | \"keep-last\" | \"older-than\" | \"stale-version\" | \"all\";\n\nexport interface PruneOptions {\n mode: PruneMode;\n keepLast?: number;\n olderThanDays?: number;\n dryRun?: boolean;\n}\n\nexport interface PruneResult {\n victims: SnapshotRow[];\n freedBytes: number;\n deletedRows: number;\n deletedFiles: number;\n safetyNetSnapshotId?: number;\n}\n\nexport class PruneOptionsInvalidError extends Error implements HasNextSteps {\n override readonly name = \"PruneOptionsInvalidError\";\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Show prune options\", command: \"mu snapshot prune --help\" },\n { intent: \"List snapshots\", command: \"mu snapshot list\" },\n ];\n }\n}\n\nexport function pruneSnapshots(db: Db, opts: PruneOptions): PruneResult {\n const mode = opts.mode;\n let victims: SnapshotRow[];\n let safetyNetSnapshotId: number | undefined;\n switch (mode) {\n case \"gc\": {\n victims = computeGcVictims(db);\n break;\n }\n case \"keep-last\": {\n if (opts.keepLast === undefined || !Number.isInteger(opts.keepLast) || opts.keepLast < 0) {\n throw new PruneOptionsInvalidError(\n `--keep-last requires a non-negative integer; got ${JSON.stringify(opts.keepLast)}`,\n );\n }\n victims = computeKeepLastVictims(db, opts.keepLast);\n break;\n }\n case \"older-than\": {\n if (\n opts.olderThanDays === undefined ||\n !Number.isFinite(opts.olderThanDays) ||\n opts.olderThanDays < 0\n ) {\n throw new PruneOptionsInvalidError(\n `--older-than requires a non-negative number of days; got ${JSON.stringify(opts.olderThanDays)}`,\n );\n }\n victims = computeOlderThanVictims(db, opts.olderThanDays);\n break;\n }\n case \"stale-version\": {\n victims = listSnapshots(db).filter(isStaleVersion);\n break;\n }\n case \"all\": {\n victims = listSnapshots(db);\n break;\n }\n default: {\n throw new PruneOptionsInvalidError(`unknown prune mode: ${JSON.stringify(mode)}`);\n }\n }\n\n let freedBytes = 0;\n for (const v of victims) {\n const sz = snapshotFileSize(v);\n if (sz !== null) freedBytes += sz;\n }\n\n if (opts.dryRun === true) {\n return { victims, freedBytes, deletedRows: 0, deletedFiles: 0 };\n }\n\n if (mode === \"all\") {\n const cap = captureSnapshot(db, \"snapshot prune --all (safety-net)\", null);\n safetyNetSnapshotId = cap.id;\n }\n\n if (victims.length === 0) {\n return {\n victims,\n freedBytes,\n deletedRows: 0,\n deletedFiles: 0,\n ...(safetyNetSnapshotId !== undefined ? { safetyNetSnapshotId } : {}),\n };\n }\n\n let deletedFiles = 0;\n for (const v of victims) {\n try {\n if (existsSync(v.dbPath)) {\n unlinkSync(v.dbPath);\n deletedFiles += 1;\n }\n } catch {\n // ignore — orphan file is preferable to a half-completed prune\n }\n }\n\n const ids = victims.map((v) => v.id);\n const inList = ids.map(() => \"?\").join(\",\");\n const result = db.prepare(`DELETE FROM snapshots WHERE id IN (${inList})`).run(...ids);\n return {\n victims,\n freedBytes,\n deletedRows: result.changes,\n deletedFiles,\n ...(safetyNetSnapshotId !== undefined ? { safetyNetSnapshotId } : {}),\n };\n}\n\nfunction computeGcVictims(db: Db): SnapshotRow[] {\n const keepLast = gcMaxCount();\n const cutoffDate = new Date(Date.now() - gcMaxAgeDays() * 24 * 60 * 60 * 1000).toISOString();\n const protectedIds = (\n db.prepare(`SELECT id FROM snapshots ORDER BY id DESC LIMIT ${keepLast}`).all() as Array<{\n id: number;\n }>\n ).map((r) => r.id);\n const placeholders = protectedIds.length > 0 ? protectedIds.map(() => \"?\").join(\",\") : \"NULL\";\n const rows = db\n .prepare(\n `SELECT * FROM snapshots WHERE id NOT IN (${placeholders}) OR created_at < ? ORDER BY id DESC`,\n )\n .all(...protectedIds, cutoffDate) as RawSnapshotRow[];\n return rows.map(rowFromDb);\n}\n\nfunction computeKeepLastVictims(db: Db, n: number): SnapshotRow[] {\n const rows = db\n .prepare(\n `SELECT * FROM snapshots\n WHERE id NOT IN (SELECT id FROM snapshots ORDER BY id DESC LIMIT ?)\n ORDER BY id DESC`,\n )\n .all(n) as RawSnapshotRow[];\n return rows.map(rowFromDb);\n}\n\nfunction computeOlderThanVictims(db: Db, days: number): SnapshotRow[] {\n const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();\n const rows = db\n .prepare(\"SELECT * FROM snapshots WHERE created_at < ? ORDER BY id DESC\")\n .all(cutoff) as RawSnapshotRow[];\n return rows.map(rowFromDb);\n}\n\nexport interface DeleteSnapshotResult {\n deleted: true;\n deletedFiles: 0 | 1;\n freedBytes: number;\n}\n\nexport function deleteSnapshot(db: Db, snapshotId: number): DeleteSnapshotResult {\n const row = db.prepare(\"SELECT * FROM snapshots WHERE id = ?\").get(snapshotId) as\n | RawSnapshotRow\n | undefined;\n if (!row) throw new SnapshotNotFoundError(snapshotId);\n let freedBytes = 0;\n let deletedFiles: 0 | 1 = 0;\n try {\n if (existsSync(row.db_path)) {\n freedBytes = statSync(row.db_path).size;\n unlinkSync(row.db_path);\n deletedFiles = 1;\n }\n } catch {\n // best-effort; the row goes either way\n }\n db.prepare(\"DELETE FROM snapshots WHERE id = ?\").run(snapshotId);\n return { deleted: true, deletedFiles, freedBytes };\n}\n","// mu — restore snapshots.\n\nimport {\n closeSync,\n copyFileSync,\n existsSync,\n openSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from \"node:fs\";\nimport Database from \"better-sqlite3\";\nimport { CURRENT_SCHEMA_VERSION, type Db } from \"../db.js\";\nimport { captureSnapshot } from \"./capture.js\";\nimport {\n type RawSnapshotRow,\n type RestoreSnapshotResult,\n SnapshotFileMissingError,\n SnapshotNotFoundError,\n SnapshotVersionMismatchError,\n} from \"./core.js\";\n\nexport function restoreSnapshot(db: Db, snapshotId: number): RestoreSnapshotResult {\n const row = db.prepare(\"SELECT * FROM snapshots WHERE id = ?\").get(snapshotId) as\n | RawSnapshotRow\n | undefined;\n if (!row) throw new SnapshotNotFoundError(snapshotId);\n if (!existsSync(row.db_path)) {\n throw new SnapshotFileMissingError(snapshotId, row.db_path);\n }\n if (row.schema_version !== CURRENT_SCHEMA_VERSION) {\n throw new SnapshotVersionMismatchError(snapshotId, row.schema_version, CURRENT_SCHEMA_VERSION);\n }\n\n const pre = captureSnapshot(db, `pre-restore of snapshot ${snapshotId}`, row.workstream);\n const preCreatedAt =\n (\n db.prepare(\"SELECT created_at FROM snapshots WHERE id = ?\").get(pre.id) as\n | { created_at: string }\n | undefined\n )?.created_at ?? new Date().toISOString();\n\n const livePath = (db as Db & { name: string }).name;\n if (!livePath || livePath === \":memory:\") {\n throw new Error(\n `restoreSnapshot: refusing to restore over a non-file DB handle (path=${JSON.stringify(livePath)})`,\n );\n }\n\n db.close();\n\n const tmpPath = `${livePath}.restore-${snapshotId}.tmp`;\n copyFileSync(row.db_path, tmpPath);\n try {\n const fd = openSync(tmpPath, \"r+\");\n try {\n writeSync(fd, Buffer.alloc(0), 0, 0, 0);\n } finally {\n closeSync(fd);\n }\n } catch {\n // ignore — fsync is best-effort here\n }\n renameSync(tmpPath, livePath);\n\n for (const sidecar of [`${livePath}-wal`, `${livePath}-shm`]) {\n if (existsSync(sidecar)) {\n try {\n unlinkSync(sidecar);\n } catch {\n // ignore — sqlite will recreate on next open\n }\n }\n }\n\n const tmp = new Database(livePath);\n try {\n tmp.pragma(\"foreign_keys = ON\");\n tmp\n .prepare(\n \"INSERT OR IGNORE INTO snapshots (id, workstream, label, db_path, schema_version, created_at) VALUES (?, ?, ?, ?, ?, ?)\",\n )\n .run(\n pre.id,\n row.workstream,\n `pre-restore of snapshot ${snapshotId}`,\n pre.dbPath,\n CURRENT_SCHEMA_VERSION,\n preCreatedAt,\n );\n } finally {\n tmp.close();\n }\n\n return {\n id: snapshotId,\n restoredTo: livePath,\n schemaVersion: row.schema_version,\n };\n}\n","// mu — task graph shared row-shape helpers.\n//\n// This module owns the snake_case → camelCase task/note mappings and\n// surrogate-id resolution helpers consumed by the task SDK cluster.\n// It stays below the public hub (`src/tasks.ts`): cluster files import\n// from this file or sibling files, never from the hub they're re-exported\n// through.\n\nimport { type Db, tryResolveWorkstreamId } from \"../db.js\";\nimport type { TaskStatus } from \"./status.js\";\n\nexport interface TaskRow {\n /** Per-workstream-unique TEXT name. The operator-facing identifier. */\n name: string;\n /** Foreign-name reference to the owning workstream. */\n workstreamName: string;\n title: string;\n status: TaskStatus;\n impact: number;\n effortDays: number;\n /** Foreign-name reference to the owning agent (NULL when unowned). */\n ownerName: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface TaskNoteRow {\n author: string | null;\n content: string;\n createdAt: string;\n}\n\nexport interface RawTaskRow {\n /** Surrogate id (v5). */\n id: number;\n local_id: string;\n /** Joined from workstreams.name. */\n workstream: string;\n title: string;\n status: string;\n impact: number;\n effort_days: number;\n /** Joined from agents.name via owner_id. NULL when unowned. */\n owner: string | null;\n created_at: string;\n updated_at: string;\n}\n\nexport interface RawTaskNoteRow {\n author: string | null;\n content: string;\n created_at: string;\n}\n\n// SELECT clause for v5 task reads. Joins workstreams + agents to expose\n// the operator-facing names as `workstream` and `owner`. Used by every\n// read path so callers don't see surrogate ids.\nexport const SELECT_TASK_COLS = `\n t.id AS id,\n t.local_id AS local_id,\n ws.name AS workstream,\n t.title AS title,\n t.status AS status,\n t.impact AS impact,\n t.effort_days AS effort_days,\n ag.name AS owner,\n t.created_at AS created_at,\n t.updated_at AS updated_at\n`;\n\nexport const TASK_FROM_JOIN = `\n FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id\n LEFT JOIN agents ag ON ag.id = t.owner_id\n`;\n\nexport const SELECT_NOTE_COLS = `\n n.author AS author,\n n.content AS content,\n n.created_at AS created_at\n`;\n\nexport function rowFromDb(row: RawTaskRow): TaskRow {\n return {\n name: row.local_id,\n workstreamName: row.workstream,\n title: row.title,\n status: row.status as TaskStatus,\n impact: row.impact,\n effortDays: row.effort_days,\n ownerName: row.owner,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport function noteFromDb(row: RawTaskNoteRow): TaskNoteRow {\n return {\n author: row.author,\n content: row.content,\n createdAt: row.created_at,\n };\n}\n\n/** Look up a task by local_id across every workstream. Returns the\n * first match (sorted by workstream name for determinism). Used by\n * edge verbs (addBlockEdge / reparentTask) to resolve a blocker so a\n * cross-workstream blocker can surface `CrossWorkstreamEdgeError`\n * rather than the less-actionable `TaskNotFoundError`. NOT for\n * operator-facing reads — use `getTask(db, localId, workstream)`\n * for those. */\nexport function lookupTaskAnyWorkstream(db: Db, localId: string): TaskRow | undefined {\n const row = db\n .prepare(\n `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} WHERE t.local_id = ? ORDER BY ws.name LIMIT 1`,\n )\n .get(localId) as RawTaskRow | undefined;\n return row ? rowFromDb(row) : undefined;\n}\n\n/** Resolve a (workstream, localId) pair to the surrogate task id.\n * Returns null on miss. */\nexport function taskIdFor(db: Db, localId: string, workstream: string): number | null {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return null;\n const row = db\n .prepare(\"SELECT id FROM tasks WHERE workstream_id = ? AND local_id = ?\")\n .get(wsId, localId) as { id: number } | undefined;\n return row ? row.id : null;\n}\n\n/**\n * Bump `tasks.updated_at` on a surrogate task id. Used by every write\n * that mutates a child row (notes, edges) without touching the task\n * row itself, so `mu task list --sort recency` reflects 'last write of\n * any kind' rather than only 'last status/field change'\n * (task_updatedat_not_bumped_by_reparent). Status changes, field\n * updates, and claim/release already update `updated_at` directly in\n * their own UPDATE statements; don't double-bump from here.\n *\n * Always called inside the same transaction as the child-row mutation\n * so the bump rolls back together with the mutation on error.\n */\nexport function touchTask(db: Db, taskId: number, now?: string): void {\n db.prepare(\"UPDATE tasks SET updated_at = ? WHERE id = ?\").run(\n now ?? new Date().toISOString(),\n taskId,\n );\n}\n","// Shared workspace staleness threshold + predicate.\n//\n// The TUI Workspaces card originally owned the magic number (red bucket\n// at ≥10 commits behind main). Dispatch-time warnings need the same\n// definition without importing ink/react code, so the threshold lives here.\n\nexport const WORKSPACE_STALE_THRESHOLD = 10;\n\nexport function isWorkspaceStale(behind: number | null | undefined): boolean {\n return behind !== null && behind !== undefined && behind >= WORKSPACE_STALE_THRESHOLD;\n}\n","// mu — shared workspace row shapes, path helpers, and typed errors.\n\nimport { join } from \"node:path\";\nimport { defaultStateDir } from \"../db.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport type { VcsBackendName } from \"../vcs.js\";\n\nexport interface WorkspaceRow {\n agentName: string;\n workstreamName: string;\n backend: VcsBackendName;\n path: string;\n parentRef: string | null;\n createdAt: string;\n /** How many commits the workspace's parent_ref is behind the project's\n * default branch HEAD, as of the last time the workspace's local refs\n * cache was updated. Undefined when not yet computed (the listWorkspaces\n * fast path leaves it unset; call decorateWithStaleness to populate).\n * Null when staleness was queried but cannot be computed (no main found,\n * none-backend, missing parent_ref, command failure). */\n commitsBehindMain?: number | null;\n /** True when the workspace has uncommitted / unstaged / untracked-not-\n * ignored files, as observed by the backend's `listDirtyFiles`.\n * Undefined when not yet computed (the listWorkspaces fast path leaves\n * it unset; call decorateWithDirty to populate). Null when the dirty\n * check could not be performed (backend command failure). For jj /\n * none backends — which have no operator-visible \"dirty\" concept —\n * this is always false (their listDirtyFiles returns []). */\n dirty?: boolean | null;\n}\n\nexport interface RawWorkspaceRow {\n /** Joined from agents.name. */\n agent: string;\n /** Joined from workstreams.name. */\n workstream: string;\n backend: string;\n path: string;\n parent_ref: string | null;\n created_at: string;\n}\n\n// SELECT clause that joins vcs_workspaces back to operator-facing\n// agent + workstream names (v5 stores surrogate ids; the JS row shape\n// is operator-facing TEXT names).\nexport const SELECT_WS_COLS = `\n ag.name AS agent,\n ws.name AS workstream,\n v.backend AS backend,\n v.path AS path,\n v.parent_ref AS parent_ref,\n v.created_at AS created_at\n`;\n\nexport const WS_FROM_JOIN = `FROM vcs_workspaces v\n JOIN agents ag ON ag.id = v.agent_id\n JOIN workstreams ws ON ws.id = v.workstream_id`;\n\nexport function rowFromDb(row: RawWorkspaceRow): WorkspaceRow {\n return {\n agentName: row.agent,\n workstreamName: row.workstream,\n backend: row.backend as VcsBackendName,\n path: row.path,\n parentRef: row.parent_ref,\n createdAt: row.created_at,\n };\n}\n\nexport class WorkspaceExistsError extends Error implements HasNextSteps {\n override readonly name = \"WorkspaceExistsError\";\n constructor(public readonly agent: string) {\n super(`workspace already exists for agent: ${agent}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Show its on-disk path\", command: `mu workspace path ${this.agent}` },\n {\n intent: \"Free it (optionally --commit pending changes first)\",\n command: `mu workspace free ${this.agent} (--commit to commit pending changes first)`,\n },\n {\n intent: \"Then re-create with a different backend or base ref\",\n command: `mu workspace create ${this.agent} --backend <jj|sl|git|none> --from <ref>`,\n },\n ];\n }\n}\n\nexport class WorkspaceNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"WorkspaceNotFoundError\";\n constructor(public readonly agent: string) {\n super(`no workspace for agent: ${agent}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List workspaces in current workstream\", command: \"mu workspace list\" },\n { intent: \"List workspaces across all workstreams\", command: \"mu workspace list --all\" },\n {\n intent: \"Create one for this agent\",\n command: `mu workspace create ${this.agent}`,\n },\n ];\n }\n}\n\n/**\n * Thrown by createWorkspace when the on-disk path it would create is\n * already occupied. Distinct from WorkspaceExistsError (which is about\n * the DB row) so the recovery is clear: the dir is orphaned (no DB\n * row points at it) and needs cleanup.\n *\n * Maps to exit code 4 (conflict).\n */\nexport class WorkspacePathNotEmptyError extends Error implements HasNextSteps {\n override readonly name = \"WorkspacePathNotEmptyError\";\n constructor(\n public readonly agent: string,\n public readonly workstream: string,\n public readonly workspacePath: string,\n ) {\n super(\n `workspace dir already on disk for agent ${agent} (${workspacePath}); refusing to overwrite`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"List every orphan workspace dir in this workstream\",\n command: `mu workspace orphans -w ${this.workstream}`,\n },\n {\n intent: \"If the dir is intentional (orphan from older mu), free it via mu first\",\n command: `mu workspace free ${this.agent} -w ${this.workstream} # also runs backend cleanup if a row remains`,\n },\n {\n intent: \"Or delete it manually if the registry has no row\",\n command: `rm -rf ${this.workspacePath}`,\n },\n {\n intent: \"For git workspaces specifically: also prune the worktree registration\",\n command: \"cd <project-root> && git worktree prune\",\n },\n ];\n }\n}\n\n/**\n * Thrown by createWorkspace when the resolved projectRoot is the\n * user's $HOME.\n *\n * Maps to exit code 4 (conflict).\n */\nexport class HomeDirAsProjectRootError extends Error implements HasNextSteps {\n override readonly name = \"HomeDirAsProjectRootError\";\n constructor(\n public readonly agent: string,\n public readonly workstream: string,\n public readonly homeDir: string,\n ) {\n super(\n `refusing to create workspace with projectRoot=$HOME (${homeDir}); a recursive copy/clone of your home directory is almost never what you want`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Re-run from inside a real project directory\",\n command: `cd <your-project> && mu workspace create ${this.agent} -w ${this.workstream}`,\n },\n {\n intent: \"Or pass --project-root explicitly\",\n command: `mu workspace create ${this.agent} -w ${this.workstream} --project-root <your-project>`,\n },\n ];\n }\n}\n\n/**\n * Compose the canonical on-disk path for an agent's workspace. Used by\n * createWorkspace and reachable from `mu workspace path` so the user\n * can `cd $(mu workspace path foo)` even before the directory exists.\n */\nexport function workspacePath(workstream: string, agent: string): string {\n return join(defaultStateDir(), \"workspaces\", workstream, agent);\n}\n\n/** Root dir for a workstream's workspaces — the parent of all\n * per-agent workspace dirs. Used by listWorkspaceOrphans to scan\n * the filesystem. */\nexport function workspacesRoot(workstream: string): string {\n return join(defaultStateDir(), \"workspaces\", workstream);\n}\n\nexport interface WorkspaceStaleness {\n agentName: string;\n workstreamName: string;\n commitsBehindMain: number | null;\n isStale: boolean;\n}\n","// mu — workspace registry CRUD and free/list/commits helpers.\n\nimport { existsSync, rmSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { resolve } from \"node:path\";\nimport { AgentNotFoundError } from \"../agents/errors.js\";\nimport { type Db, tryResolveWorkstreamId } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport {\n type CommitSummary,\n type RebaseResult,\n type VcsBackend,\n type VcsBackendName,\n backendByName,\n detectBackend,\n} from \"../vcs.js\";\nimport {\n HomeDirAsProjectRootError,\n type RawWorkspaceRow,\n SELECT_WS_COLS,\n WS_FROM_JOIN,\n WorkspaceExistsError,\n WorkspaceNotFoundError,\n WorkspacePathNotEmptyError,\n type WorkspaceRow,\n rowFromDb,\n workspacePath,\n} from \"./core.js\";\n\nexport interface CreateWorkspaceOptions {\n agent: string;\n workstream: string;\n /** Project root to branch from. Defaults to the current working\n * directory (the `mu` invocation site, which is normally what the\n * user wants). */\n projectRoot?: string;\n /** Override backend detection. Default: walk `detectBackend`.\n * Accepts either a name (\"jj\" / \"sl\" / \"git\" / \"none\") OR a\n * pre-built `VcsBackend` object — the object form lets tests inject\n * a fresh fake backend without mutating the exported singletons. */\n backend?: VcsBackendName | VcsBackend;\n /** Optional ref to base the workspace on. Backend-specific. */\n parentRef?: string;\n /** INTERNAL. When false, suppress the `workspace create` system\n * event. Used by `recreateWorkspace` so the audit trail records\n * ONE atomic `workspace recreate` line instead of separate\n * free + create entries. Defaults to true. */\n _suppressEvent?: boolean;\n}\n\n/**\n * Create a fresh workspace for an agent. Allocates the on-disk\n * directory, records the row, emits a system event. Idempotent ONLY\n * to the extent that the row check is up-front; if the row exists\n * we throw `WorkspaceExistsError` rather than silently re-using a\n * possibly-stale on-disk state. Callers should `freeWorkspace` first.\n */\nexport async function createWorkspace(db: Db, opts: CreateWorkspaceOptions): Promise<WorkspaceRow> {\n if (getWorkspaceForAgent(db, opts.agent, opts.workstream) !== undefined) {\n throw new WorkspaceExistsError(opts.agent);\n }\n\n const projectRoot = opts.projectRoot ?? process.cwd();\n\n // Footgun guard: refuse projectRoot=$HOME. resolve() normalises a\n // trailing slash, `.`, symlinks-in-name, etc., so `cd && mu workspace\n // create ...` and `--project-root ~/` are all blocked the same way.\n // Direct children of $HOME (e.g. ~/Documents) are NOT blocked —\n // that would be overreach. See snap_dogfood Finding 4.\n if (resolve(projectRoot) === resolve(homedir())) {\n throw new HomeDirAsProjectRootError(opts.agent, opts.workstream, homedir());\n }\n\n const backend =\n opts.backend === undefined\n ? await detectBackend(projectRoot)\n : typeof opts.backend === \"string\"\n ? backendByName(opts.backend)\n : opts.backend;\n const path = workspacePath(opts.workstream, opts.agent);\n\n // Surface the dir-already-exists case as a typed error WITH actionable\n // nextSteps before we delegate to the backend (which throws a bare\n // Error). This is the orphan-from-older-mu case: a workspace dir from\n // before the cccba88 close-refuses fix landed; it has no DB row.\n if (existsSync(path)) {\n throw new WorkspacePathNotEmptyError(opts.agent, opts.workstream, path);\n }\n\n const createOpts: { projectRoot: string; workspacePath: string; parentRef?: string } = {\n projectRoot,\n workspacePath: path,\n };\n if (opts.parentRef !== undefined) createOpts.parentRef = opts.parentRef;\n\n // Wrap the backend's on-disk side effect in a cleanup guard. If the\n // backend throws mid-way (cp -a hits a DRM-protected file, git\n // worktree add fails after creating the dir, an interrupt during a\n // long copy), the partial dir would otherwise be left behind with\n // no DB row — exactly the failure mode from snap_dogfood Finding 4,\n // which then blocked subsequent `mu workspace create` calls with\n // WorkspacePathNotEmptyError. Best-effort: if the rm itself fails,\n // surface the original error and let the user clean up via\n // `mu workspace orphans`.\n let created: Awaited<ReturnType<typeof backend.createWorkspace>>;\n try {\n created = await backend.createWorkspace(createOpts);\n } catch (err) {\n try {\n rmSync(path, { recursive: true, force: true });\n } catch {\n // best-effort; original error wins\n }\n throw err;\n }\n\n // Roll back the on-disk + VCS-registry side effect if the DB row\n // insert fails (FK violation, schema constraint, sqlite_busy timeout).\n const now = new Date().toISOString();\n try {\n const wsId = tryResolveWorkstreamId(db, opts.workstream);\n if (wsId === null) {\n throw new Error(\n `createWorkspace: workstream not found: ${opts.workstream} (insertAgent should have ensured this)`,\n );\n }\n const agentRow = db\n .prepare(\"SELECT id FROM agents WHERE name = ? AND workstream_id = ? LIMIT 1\")\n .get(opts.agent, wsId) as { id: number } | undefined;\n if (!agentRow) {\n throw new AgentNotFoundError(opts.agent, opts.workstream);\n }\n db.prepare(\n `INSERT INTO vcs_workspaces (agent_id, workstream_id, backend, path, parent_ref, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n ).run(agentRow.id, wsId, backend.name, path, created.parentRef, now);\n } catch (err) {\n await backend.freeWorkspace({ workspacePath: path, commit: false }).catch(() => {});\n throw err;\n }\n\n if (opts._suppressEvent !== true) {\n emitEvent(\n db,\n opts.workstream,\n `workspace create ${opts.agent} (backend=${backend.name}, path=${path}${created.parentRef ? `, parent=${created.parentRef.slice(0, 12)}` : \"\"})`,\n );\n }\n\n return {\n agentName: opts.agent,\n workstreamName: opts.workstream,\n backend: backend.name,\n path,\n parentRef: created.parentRef,\n createdAt: now,\n };\n}\n\nexport function getWorkspaceForAgent(\n db: Db,\n agent: string,\n workstream: string,\n): WorkspaceRow | undefined {\n // v5: agents.name is per-workstream unique — the lookup must scope\n // by (workstream, agent) so a same-named worker elsewhere can't be\n // resolved instead.\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return undefined;\n const row = db\n .prepare(`SELECT ${SELECT_WS_COLS} ${WS_FROM_JOIN} WHERE ag.name = ? AND v.workstream_id = ?`)\n .get(agent, wsId) as RawWorkspaceRow | undefined;\n return row ? rowFromDb(row) : undefined;\n}\n\nexport function listWorkspaces(db: Db, workstream?: string): WorkspaceRow[] {\n if (workstream === undefined) {\n const rows = db\n .prepare(`SELECT ${SELECT_WS_COLS} ${WS_FROM_JOIN} ORDER BY ws.name, ag.name`)\n .all() as RawWorkspaceRow[];\n return rows.map(rowFromDb);\n }\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(`SELECT ${SELECT_WS_COLS} ${WS_FROM_JOIN} WHERE v.workstream_id = ? ORDER BY ag.name`)\n .all(wsId) as RawWorkspaceRow[];\n return rows.map(rowFromDb);\n}\n\nexport interface FreeWorkspaceOptions {\n /** If true, attempt to commit pending changes before tearing down.\n * Backend-specific; see VcsBackend.freeWorkspace. */\n commit?: boolean;\n /** INTERNAL. When false, suppress the `workspace free` system\n * event AND skip the pre-mutation snapshot capture. Used by\n * `recreateWorkspace` so the audit trail records ONE atomic\n * `workspace recreate` line and one snapshot for the whole\n * free+create cycle. Defaults to true. */\n _suppressEvent?: boolean;\n}\n\nexport interface FreeWorkspaceResult {\n /** The committed ref, when `commit` was true and there was something\n * to commit. */\n committedRef?: string;\n /** True iff the on-disk path was actually removed. */\n removed: boolean;\n /** True iff the DB row was actually deleted. */\n rowDeleted: boolean;\n}\n\n/**\n * Tear down an agent's workspace. Calls the backend to remove the\n * on-disk directory (with optional auto-commit), then DELETEs the row.\n * Idempotent on a missing workspace (returns all-false).\n */\nexport async function freeWorkspace(\n db: Db,\n agent: string,\n opts: FreeWorkspaceOptions & { workstream: string },\n): Promise<FreeWorkspaceResult> {\n const row = getWorkspaceForAgent(db, agent, opts.workstream);\n if (!row) return { removed: false, rowDeleted: false };\n\n // Pre-mutation snapshot — the row deletion + on-disk teardown is\n // not recoverable from history. Snapshot is DB-only (the worktree\n // is not rolled back; that's the design's tmux/disk honesty point).\n // recreateWorkspace owns its own snapshot label so the undo trail\n // shows one `workspace recreate` step, not free + create.\n if (opts._suppressEvent !== true) {\n captureSnapshot(db, `workspace free ${agent}`, row.workstreamName);\n }\n\n const backend = backendByName(row.backend);\n const result = await backend.freeWorkspace({\n workspacePath: row.path,\n commit: opts.commit ?? false,\n });\n\n // Resolve to surrogate ids scoped by the row's workstream.\n const wsIdForDel = tryResolveWorkstreamId(db, row.workstreamName);\n const del =\n wsIdForDel === null\n ? { changes: 0 }\n : db\n .prepare(\n `DELETE FROM vcs_workspaces\n WHERE agent_id = (SELECT id FROM agents WHERE name = ? AND workstream_id = ?)\n AND workstream_id = ?`,\n )\n .run(agent, wsIdForDel, wsIdForDel);\n if (opts._suppressEvent !== true) {\n emitEvent(\n db,\n row.workstreamName,\n `workspace free ${agent} (backend=${row.backend}, path=${row.path}${result.committedRef ? `, committed=${result.committedRef.slice(0, 12)}` : \"\"})`,\n );\n }\n\n return {\n removed: result.removed,\n rowDeleted: del.changes > 0,\n ...(result.committedRef !== undefined ? { committedRef: result.committedRef } : {}),\n };\n}\n\nexport interface RefreshWorkspaceOptions {\n agent: string;\n workstream: string;\n /** Optional override of the rebase target. When undefined, the\n * backend resolves its own default (origin/HEAD for git,\n * `trunk()` for jj/sl). */\n fromRef?: string;\n}\n\nexport interface RefreshWorkspaceResult extends RebaseResult {\n /** Backend name (mirrors the row) so a JSON consumer doesn't have\n * to look up the workspace separately to know what kind of rebase\n * it just got. */\n vcs: VcsBackendName;\n /** The workspace's on-disk path (so the JSON shape is self-contained\n * for piping to a downstream `jq` script). */\n workspacePath: string;\n}\n\n/**\n * Refresh an agent's workspace by rebasing it onto `fromRef` (or the\n * backend's default base). The agent / pane are NOT touched — only\n * the on-disk working copy moves. Bumps the row's `created_at` proxy\n * via the emit event; the row itself is otherwise unchanged.\n */\nexport async function refreshWorkspace(\n db: Db,\n opts: RefreshWorkspaceOptions,\n): Promise<RefreshWorkspaceResult> {\n const row = getWorkspaceForAgent(db, opts.agent, opts.workstream);\n if (!row) throw new WorkspaceNotFoundError(opts.agent);\n const backend = backendByName(row.backend);\n const result = await backend.rebaseTo(row.path, opts.fromRef);\n emitEvent(\n db,\n row.workstreamName,\n `workspace refresh ${opts.agent} (backend=${row.backend}, fromRef=${result.fromRef}, replayed=${result.replayed.length})`,\n );\n return { ...result, vcs: row.backend, workspacePath: row.path };\n}\n\nexport interface ListCommitsOptions {\n workstream: string;\n /** Optional override of the base ref (default: the workspace row's\n * parent_ref). Useful when the operator wants to ask \"what's on\n * top of an arbitrary ref\" without re-creating the workspace. */\n since?: string;\n}\n\nexport interface ListCommitsResult {\n /** Backend name (mirrors the row). */\n vcs: VcsBackendName;\n /** The base ref actually used. */\n baseRef: string;\n /** The commits, oldest-first. Empty when the workspace is exactly\n * at baseRef. */\n commits: CommitSummary[];\n /** The workspace's on-disk path (so JSON consumers don't have to\n * call `mu workspace path` separately). */\n workspacePath: string;\n}\n\n/**\n * List commits the workspace has on top of its `parent_ref` (or the\n * `--since` override), oldest-first. Promotes the dogfood-painful\n * cd $(mu workspace path X) && git log <base>..HEAD\n * incantation into a typed verb that knows the workspace's\n * recorded fork point.\n */\nexport async function listCommitsForWorkspace(\n db: Db,\n agent: string,\n opts: ListCommitsOptions,\n): Promise<ListCommitsResult> {\n const row = getWorkspaceForAgent(db, agent, opts.workstream);\n if (!row) throw new WorkspaceNotFoundError(agent);\n const backend = backendByName(row.backend);\n const baseRef = opts.since ?? row.parentRef;\n if (baseRef === null || baseRef.length === 0) {\n if (row.backend === \"none\") {\n await backend.commitsSinceBase(row.path, \"\");\n }\n throw new Error(`workspace ${agent} has no recorded parent_ref; pass --since <ref> explicitly`);\n }\n const commits = await backend.commitsSinceBase(row.path, baseRef);\n return { vcs: row.backend, baseRef, commits, workspacePath: row.path };\n}\n\n/**\n * \"Is this workspace safe to silently free on agent close?\" — i.e.\n * does it have ZERO uncommitted changes AND ZERO commits since its\n * fork point. Used by closeAgent to auto-free clean workspaces.\n */\nexport async function isWorkspaceClean(row: WorkspaceRow): Promise<boolean> {\n const backend = backendByName(row.backend);\n let clean: boolean;\n try {\n clean = await backend.isClean(row.path);\n } catch {\n return false;\n }\n if (!clean) return false;\n if (row.backend === \"none\") return true;\n if (row.parentRef === null || row.parentRef.length === 0) return false;\n try {\n const commits = await backend.commitsSinceBase(row.path, row.parentRef);\n return commits.length === 0;\n } catch {\n return false;\n }\n}\n","// mu — spawnAgent + supporting helpers (resolveCliCommand,\n// awaitSpawnLiveness, createOrReusePane, defaultSpawnLivenessMs,\n// prestageWorkspace, finalizeAgentRow, rollbackSpawn).\n//\n// spawnAgent's flow is documented inline. The interesting bit is the\n// `--workspace` cycle (workspace row FKs agent.name; agent row needs\n// pane_id which needs workspace path as cwd) — see prestageWorkspace.\n//\n// Extracted from src/agents.ts as part of refactor_split_large_src_files.\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport {\n type AgentRow,\n deleteAgent,\n getAgent,\n insertAgent,\n isValidAgentName,\n pendingPaneIdFor,\n refreshAgentTitle,\n} from \"../agents.js\";\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { type NextStep, isJsonMode } from \"../output.js\";\nimport {\n capturePane,\n enableMuPaneBordersForPane,\n killPane,\n listWindows,\n newSessionWithPane,\n newWindow,\n paneExists,\n sessionExists,\n setPaneTitle,\n sleep,\n splitWindow,\n} from \"../tmux.js\";\nimport type { VcsBackendName } from \"../vcs.js\";\nimport { createWorkspace, freeWorkspace } from \"../workspace.js\";\nimport {\n AgentDiedOnSpawnError,\n AgentExistsError,\n AgentSpawnCliNotFoundError,\n AgentSpawnStartupError,\n} from \"./errors.js\";\n\n/**\n * Smallest-unused-suffix naming convention for agent names: a role\n * (lowercase alpha + digits) followed by `-<n>` where `<n>` is one or\n * more digits. Examples that pass: `worker-1`, `reviewer-2`, `scout-12`.\n * Examples that warn: `worker-tests`, `alice`, `db-leader`, `x-y-1`.\n *\n * Source: feedback ws task fb_agent_naming_convention. The dogfood report\n * was operators (LLMs included) drifting to descriptive names like\n * `worker-tests` after a few spawns, breaking the convention silently.\n * The hint surfaced by `maybeWarnNonConventionalAgentName` is a lint, not\n * a rule — `isValidAgentName` still accepts the broader\n * `^[a-z][a-z0-9_-]{0,31}$` shape.\n */\nconst AGENT_NAME_CONVENTION_RE = /^[a-z][a-z0-9]*(?:-[0-9]+)$/;\n\n/**\n * Stderr lint when an agent name doesn't match the smallest-unused-suffix\n * convention. Mirrors the slugify-truncation stderr hint in cmdTaskAdd\n * (commit 28a13af): stderr-only, exit 0, suppressed under --json so\n * machine consumers stay clean. The spawn already succeeded by the time\n * this runs — we're just nudging the operator toward `<role>-<n>`.\n */\nfunction maybeWarnNonConventionalAgentName(name: string): void {\n if (AGENT_NAME_CONVENTION_RE.test(name)) return;\n if (isJsonMode()) return;\n process.stderr.write(\n `hint: agent name \"${name}\" does not match the smallest-unused-suffix convention (<role>-<n>; e.g. worker-1, reviewer-2). Accepted; consider renaming if you spawn additional workers.\\n`,\n );\n}\n\n/**\n * Resolve the actual executable to launch in an agent's pane for a given\n * `cli`. Honours the env var `MU_<UPPER_CLI>_COMMAND` (e.g. `MU_PI_COMMAND=\n * pi-alt` makes `--cli pi` actually exec `pi-alt`). Falls back to the cli\n * name itself, which is what users expect when their `pi` binary is on\n * `$PATH` under that exact name.\n *\n * Used by `spawnAgent` to pick the spawned command, and by reconcile's\n * orphan detector so externally-spawned panes running the resolved binary\n * are still recognised as agents.\n */\nexport function resolveCliCommand(cli: string): string {\n const envName = envVarNameForCli(cli);\n const override = process.env[envName];\n return override && override.trim() !== \"\" ? override : cli;\n}\n\n/**\n * Compute the `MU_<UPPER_CLI>_COMMAND` env var name mu consults when\n * resolving `--cli <key>`. Hyphens in the cli key become underscores\n * (env var names can't contain `-`); this matches the operator-aliases\n * convention documented in the mu skill (e.g. `--cli pi-meta` →\n * `MU_PI_META_COMMAND`).\n */\nexport function envVarNameForCli(cli: string): string {\n return `MU_${cli.toUpperCase().replace(/-/g, \"_\")}_COMMAND`;\n}\n\n/**\n * Resolve `--cli <key>` to its actual command string AND tell the\n * caller whether the resolution came from a `MU_<UPPER_CLI>_COMMAND`\n * env var or fell through to the bare cli name. The CLI uses this to\n * surface env-var attribution in the spawn-success line so config\n * issues are visible without `mu agent show`\n * (fb_agent_spawn_no_validation, part C).\n */\nexport function resolveCliCommandWithSource(cli: string): {\n command: string;\n envVar: string;\n resolvedFromEnv: boolean;\n} {\n const envVar = envVarNameForCli(cli);\n const override = process.env[envVar];\n if (override !== undefined && override.trim() !== \"\") {\n return { command: override, envVar, resolvedFromEnv: true };\n }\n return { command: cli, envVar, resolvedFromEnv: false };\n}\n\n// ─── Pre-flight PATH check (fb_agent_spawn_no_validation, part A) ──\n//\n// Operator typo'd `mu agent spawn worker-1 --cli pi-meta` on a host\n// where `pi-meta` wasn't on PATH; the spawn appeared to succeed but\n// the pane died with `command not found` and the existing 1.5s\n// liveness check sometimes missed it (shells stay alive past a failed\n// exec). Pre-flight the PATH resolution so a typo never:\n// 1. creates an orphan workspace dir\n// 2. creates an orphan tmux pane\n// 3. inserts a half-spawned DB row\n// The check fires AFTER `resolveCliCommand` so `MU_<UPPER>_COMMAND`\n// overrides are honoured AND the diagnostic mentions the env var by\n// name. Hookable via `setCommandResolverForTests` so the test suite\n// can simulate \"binary present\" / \"binary absent\" without polluting\n// the real PATH.\n\nexport interface CommandResolutionResult {\n ok: boolean;\n /** First whitespace-separated token of the command — the binary\n * whose presence on PATH we checked. */\n binary: string;\n /** Absolute path of the resolved binary on PATH, when ok=true. */\n resolvedPath?: string;\n}\n\nexport type CommandResolver = (command: string) => Promise<CommandResolutionResult>;\n\nconst execFileP = promisify(execFile);\n\n/** Default resolver: shell-out to `command -v <binary>` (POSIX-portable\n * equivalent of `which`). Returns ok=false when the lookup exits\n * non-zero. Always returns the parsed `binary` field so the typed\n * error can quote what we actually searched for. */\nasync function defaultCommandResolver(command: string): Promise<CommandResolutionResult> {\n const binary = parseFirstToken(command);\n if (binary === \"\") return { ok: false, binary };\n try {\n // `command -v` is the POSIX builtin lookup. Spawned via /bin/sh\n // -c so the builtin is available (it's not an external program).\n const { stdout } = await execFileP(\"/bin/sh\", [\"-c\", `command -v -- ${shellQuote(binary)}`], {\n env: process.env,\n });\n const resolvedPath = stdout.trim();\n if (resolvedPath === \"\") return { ok: false, binary };\n return { ok: true, binary, resolvedPath };\n } catch {\n return { ok: false, binary };\n }\n}\n\n/** Single-quote a token for /bin/sh -c. Only used on the binary name,\n * which `parseFirstToken` already restricted to a non-whitespace run\n * — no embedded single-quotes expected. Escape defensively anyway. */\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** First whitespace-separated token of a command string (\"pi-meta\n * --no-solo\" → \"pi-meta\"). Returns \"\" on an empty / whitespace-only\n * input. */\nfunction parseFirstToken(command: string): string {\n const trimmed = command.trim();\n if (trimmed === \"\") return \"\";\n const match = trimmed.match(/^\\S+/);\n return match ? match[0] : \"\";\n}\n\nlet activeCommandResolver: CommandResolver = defaultCommandResolver;\n\n/** Override the PATH resolver. Tests use this to simulate \"binary\n * absent\" / \"binary present\" without depending on what's actually\n * installed. Production callers should never touch this. */\nexport function setCommandResolverForTests(resolver: CommandResolver): void {\n activeCommandResolver = resolver;\n}\n\n/** Restore the default PATH resolver. */\nexport function resetCommandResolverForTests(): void {\n activeCommandResolver = defaultCommandResolver;\n}\n\n/**\n * Verify the first token of `command` resolves to a binary on PATH.\n * Public so tests can call it directly; spawnAgent calls it before\n * prestageWorkspace so a bad --cli never creates an orphan workspace.\n */\nexport async function checkCommandResolvable(command: string): Promise<CommandResolutionResult> {\n return activeCommandResolver(command);\n}\n\nexport interface SpawnAgentOptions {\n name: string;\n workstream: string;\n /** Defaults to \"pi\". 0.1.0 only really supports \"pi\" but the column\n * accepts any string for forward-compat with future multi-CLI support\n * (claude/codex). */\n cli?: string;\n /** The actual command to run in the pane. Defaults to the cli value. */\n command?: string;\n /** Window name to group this agent under. Defaults to the agent's name\n * (so each agent gets its own window). Multiple agents sharing a `tab`\n * share a window with multiple panes. */\n tab?: string;\n /** \"full-access\" (default) or \"read-only\". The schema stores it; today\n * the role isn't enforced (deferred to a future capabilities pass). */\n role?: string;\n /** Initial working directory for the spawned pane (`tmux -c <path>`).\n * When `workspace: true` is passed, this is ignored — the workspace\n * path is used instead. */\n cwd?: string;\n /** Override the tmux session name. Defaults to `mu-<workstream>`. */\n tmuxSession?: string;\n /** Auto-create a VCS workspace for this agent before spawning the\n * pane and use the workspace path as cwd. Backend defaults to\n * detection (jj > sl > git > none). */\n workspace?: boolean;\n /** Force a specific VCS backend (only meaningful with `workspace: true`). */\n workspaceBackend?: VcsBackendName;\n /** Optional ref to base the workspace on (only meaningful with\n * `workspace: true`). Backend-specific. */\n workspaceFrom?: string;\n /** Project root the workspace branches from (only meaningful with\n * `workspace: true`). Defaults to `process.cwd()`. */\n workspaceProjectRoot?: string;\n}\n\n/**\n * Spawn a new agent in its tmux pane and register it in the DB.\n *\n * Phases:\n * 1. Validate name + uniqueness.\n * 2. If --workspace: prestageWorkspace() (placeholder agent row +\n * workspace dir + workspace row).\n * 3. createOrReusePane() in the workspace path (or opts.cwd).\n * 4. setPaneTitle + enableMuPaneBordersForPane.\n * 5. finalizeAgentRow() — patch placeholder pane_id to real (workspace\n * path), or insert a fresh agent row (no-workspace path).\n * 6. awaitSpawnLiveness().\n *\n * Failure between any of (3)–(6) calls rollbackSpawn() to undo the\n * pane + row + workspace. The caller-visible error is preserved.\n */\nexport async function spawnAgent(db: Db, opts: SpawnAgentOptions): Promise<AgentRow> {\n if (!isValidAgentName(opts.name)) {\n throw new TypeError(\n `invalid agent name: ${JSON.stringify(opts.name)} (expected /^[a-z][a-z0-9_-]{0,31}$/)`,\n );\n }\n // Per-workstream uniqueness check: v5 allows the same agent name in\n // different workstreams. Scope the existence check to the spawn's\n // workstream so two operators spawning 'worker-1' in wsA and wsB\n // both succeed (bug_v5_name_clash_silent_misroute).\n if (getAgent(db, opts.name, opts.workstream) !== undefined) {\n throw new AgentExistsError(opts.name);\n }\n\n const session = opts.tmuxSession ?? `mu-${opts.workstream}`;\n const windowName = opts.tab ?? opts.name;\n const cli = opts.cli ?? \"pi\";\n const command = opts.command ?? resolveCliCommand(cli);\n\n // Pre-flight PATH resolution. Catches `--cli does-not-exist` (and\n // typos in `MU_<UPPER>_COMMAND` overrides) before any side effect.\n // See `checkCommandResolvable` / `AgentSpawnCliNotFoundError` for\n // the rationale; in short: prevents orphan workspace dirs + orphan\n // panes + half-spawned DB rows on a typo.\n //\n // We do NOT pre-flight an explicit `--command \"...\"` value: the\n // operator who passed --command gave us the literal string they\n // want spawned (often a wrapper like `pi-meta --no-solo` whose\n // first token IS on PATH but with arg-quoting that the resolver\n // would mis-parse; or a shell snippet like `bash -lc '...'`). For\n // those, the existing post-spawn liveness check is the safety net.\n if (opts.command === undefined) {\n const check = await checkCommandResolvable(command);\n if (!check.ok) {\n throw new AgentSpawnCliNotFoundError(cli, check.binary, envVarNameForCli(cli));\n }\n }\n\n // Workspace pre-stage. See `prestageWorkspace` for the FK-ordering\n // rationale. Returns the workspace path (used as the pane's cwd) or\n // undefined when --workspace wasn't requested.\n const workspacePathStr = opts.workspace ? await prestageWorkspace(db, opts, cli) : undefined;\n\n // Inject identity env vars into the pane so anything running inside\n // (pi extensions, claim-protocol scripts, status segments) can branch\n // on 'I am a mu-managed worker' without scraping pane titles or DB\n // lookups. Set via tmux `-e KEY=VALUE` (per-pane; doesn't pollute the\n // tmux server's global env).\n //\n // These are NOT exposed via SpawnAgentOptions — mu identity is not\n // user-tunable. Adding more keys here means every spawned pane sees\n // them automatically.\n const paneEnv: Record<string, string> = {\n MU_MANAGED_AGENT: \"1\",\n MU_AGENT_NAME: opts.name,\n MU_WORKSTREAM: opts.workstream,\n };\n\n const hasWorkspace = workspacePathStr !== undefined;\n\n // Single outer try wrapping every step that can throw AFTER the\n // workspace has been prestaged: pane create/reuse, pane title +\n // borders, agent-row finalize, liveness check. Earlier shape had\n // createOrReusePane outside the try, so a tmux failure (e.g. no\n // workstream session and tmux refusing to create one) left the\n // prestaged workspace dir + placeholder agent row behind as an\n // orphan — task agent_spawn_abort_leaves_orphan_workspace.\n //\n // paneId is undefined until createOrReusePane returns; rollbackSpawn\n // skips killPane in that case. The inner try/catches that previously\n // wrapped finalize and liveness were folded into this single catch\n // (clarity > belt-and-suspenders; rollbackSpawn is idempotent and\n // best-effort regardless).\n let paneId: string | undefined;\n let agent: AgentRow;\n try {\n paneId = await createOrReusePane({\n session,\n windowName,\n command,\n cwd: workspacePathStr ?? opts.cwd,\n env: paneEnv,\n });\n await setPaneTitle(paneId, opts.name);\n // Apply the mu pane border to the new window. Window-scoped option;\n // see enableMuPaneBorders docstring for why this is required per\n // window (and not just per session). Self-checks MU_BANNER_QUIET\n // and is best-effort — the border is decorative.\n await enableMuPaneBordersForPane(paneId);\n agent = finalizeAgentRow(db, { opts, cli, paneId, hasWorkspace });\n // Liveness check: wait briefly, then verify the pane is still alive.\n // Catches the silent-spawn-failure class of bugs where the CLI dies\n // immediately (lock conflict, bad credentials, etc.). On failure,\n // surface a typed error with whatever scrollback tmux still has.\n await awaitSpawnLiveness(paneId, opts.name);\n } catch (err) {\n await rollbackSpawn(db, opts.name, paneId, hasWorkspace, opts.workstream);\n if (hasWorkspace) attachOrphanCleanupHint(err, opts.name, opts.workstream);\n throw err;\n }\n emitEvent(\n db,\n opts.workstream,\n `agent spawn ${opts.name} (cli=${cli}, role=${opts.role ?? \"full-access\"}, pane=${paneId})`,\n );\n // Initial title push: the agent row is in 'spawning' state at this\n // point, which composeAgentTitle renders as bare name (no decoration\n // until the first detect cycle). Reconcile will re-push as soon as\n // the operator runs any status-reading verb.\n await refreshAgentTitle(db, opts.name, opts.workstream);\n // Lint-grade stderr nudge when the operator picked a name that\n // doesn't fit the <role>-<n> smallest-unused-suffix convention. The\n // spawn already succeeded; this is advice, not an error. Done LAST\n // so the hint trails the spawn's own stdout output and so a partial\n // failure (rolled back above) never emits it.\n maybeWarnNonConventionalAgentName(opts.name);\n return agent;\n}\n\n/**\n * Stage 1 of `--workspace` spawn: insert the agent row with a placeholder\n * pane id, then create the VCS workspace (whose row FKs the agent name).\n *\n * Why placeholder-first? `vcs_workspaces.agent` FK + ON DELETE CASCADE\n * means the workspace row needs an agents row to exist before we insert\n * it; but we can't insert agents without a pane_id (NOT NULL), and we\n * can't create the pane until we know the workspace's on-disk path\n * (used as cwd). The placeholder unblocks the cycle.\n *\n * The placeholder is a publicly-visible quirk — see `PENDING_PANE_PREFIX`\n * and the consumers it documents (refreshAgentTitle, the\n * mode: \"status-only\"/\"report-only\" rationale in `listLiveAgents`).\n * The deeper fix (eliminate the placeholder\n * by reordering ws-dir → pane → atomic dual-insert) is filed as a\n * separate refactor; this shape is the established equilibrium.\n *\n * Throws after best-effort rollback (deleteAgent) if workspace creation\n * fails. Returns the workspace's on-disk path.\n */\nasync function prestageWorkspace(db: Db, opts: SpawnAgentOptions, cli: string): Promise<string> {\n insertAgent(db, {\n name: opts.name,\n workstream: opts.workstream,\n cli,\n paneId: pendingPaneIdFor(opts.name),\n status: \"spawning\",\n role: opts.role,\n tab: opts.tab ?? null,\n });\n try {\n const wsOpts: Parameters<typeof createWorkspace>[1] = {\n agent: opts.name,\n workstream: opts.workstream,\n };\n if (opts.workspaceBackend !== undefined) wsOpts.backend = opts.workspaceBackend;\n if (opts.workspaceFrom !== undefined) wsOpts.parentRef = opts.workspaceFrom;\n if (opts.workspaceProjectRoot !== undefined) wsOpts.projectRoot = opts.workspaceProjectRoot;\n const ws = await createWorkspace(db, wsOpts);\n return ws.path;\n } catch (err) {\n deleteAgent(db, opts.name, opts.workstream);\n throw err;\n }\n}\n\n/**\n * Stage 2 of spawn (after the real pane exists): either patch the\n * placeholder row to the real pane id (workspace path), or insert a\n * fresh agent row (no-workspace path). Throws if the patch UPDATE\n * silently affects no row (the placeholder row vanished mid-spawn —\n * historically the bug_agent_spawn_workspace_fk_failure class, now\n * patched via mode: \"status-only\"/\"report-only\" on read verbs).\n */\nfunction finalizeAgentRow(\n db: Db,\n args: { opts: SpawnAgentOptions; cli: string; paneId: string; hasWorkspace: boolean },\n): AgentRow {\n const { opts, cli, paneId, hasWorkspace } = args;\n if (!hasWorkspace) {\n return insertAgent(db, {\n name: opts.name,\n workstream: opts.workstream,\n cli,\n paneId,\n status: \"spawning\",\n role: opts.role,\n tab: opts.tab ?? null,\n });\n }\n // Scope the patch to the spawn's workstream so a same-named worker\n // in another workstream isn't repointed at this pane\n // (bug_v5_name_clash_silent_misroute).\n db.prepare(\n `UPDATE agents SET pane_id = ?, updated_at = ?\n WHERE name = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)`,\n ).run(paneId, new Date().toISOString(), opts.name, opts.workstream);\n const row = getAgent(db, opts.name, opts.workstream);\n if (!row) throw new Error(`spawnAgent: agent vanished after workspace stage: ${opts.name}`);\n return row;\n}\n\n/**\n * Roll back a failed spawn. Idempotent and best-effort: every step\n * swallows its own errors so a partial-cleanup substrate (already-killed\n * pane, no agent row) never masks the original failure.\n *\n * `paneId` is `undefined` when createOrReusePane itself threw before\n * returning a pane id — there's nothing to kill. `hasWorkspace` is\n * `true` when prestageWorkspace succeeded; freeWorkspace is idempotent\n * on a missing workspace so calling it after a same-cycle failure is\n * safe. deleteAgent is also idempotent (no-op on a missing row).\n */\nasync function rollbackSpawn(\n db: Db,\n name: string,\n paneId: string | undefined,\n hasWorkspace: boolean,\n workstream: string,\n): Promise<void> {\n if (paneId !== undefined) await killPane(paneId).catch(() => {});\n if (hasWorkspace) {\n // Scope cleanup to the spawn's workstream so a same-named worker\n // elsewhere isn't torn down by accident\n // (bug_v5_name_clash_silent_misroute).\n await freeWorkspace(db, name, { workstream }).catch(() => {});\n }\n deleteAgent(db, name, workstream);\n}\n\n/**\n * After a failed spawn that prestaged a workspace, augment the thrown\n * error with orphan-cleanup hints (`mu workspace orphans` +\n * `mu workspace free`). rollbackSpawn is best-effort — if the\n * freeWorkspace call inside it failed (rmdir blocked, vcs backend\n * refusing to remove a workspace with uncommitted state, etc.), the\n * dir survives and the operator needs to know how to clean it up.\n *\n * Uses the duck-typed `errorNextSteps()` convention (see\n * `hasNextSteps()` in src/output.ts) so this works whether the inner\n * error is a typed mu error already exposing nextSteps\n * (AgentDiedOnSpawnError) or a bare TmuxError / Error from\n * createOrReusePane that has none. Existing hints (if any) are\n * preserved and listed first; the orphan-cleanup hints are appended\n * so the diagnostic-first ordering of typed errors is kept.\n */\nfunction attachOrphanCleanupHint(err: unknown, agent: string, workstream: string): void {\n if (typeof err !== \"object\" || err === null) return;\n const target = err as { errorNextSteps?: () => NextStep[] };\n const existing =\n typeof target.errorNextSteps === \"function\" ? target.errorNextSteps.bind(target) : null;\n const orphanHints: NextStep[] = [\n {\n intent: \"Check for an orphan workspace dir (rollback is best-effort; may have failed)\",\n command: `mu workspace orphans -w ${workstream}`,\n },\n {\n intent: \"Free the workspace if it survived the rollback (idempotent on missing)\",\n command: `mu workspace free ${agent} -w ${workstream}`,\n },\n ];\n target.errorNextSteps = () => [...(existing ? existing() : []), ...orphanHints];\n}\n\n/**\n * Default liveness window in milliseconds. 0 disables the check (useful\n * for fast tests that don't want to wait). Override via env var\n * `MU_SPAWN_LIVENESS_MS`.\n */\nexport function defaultSpawnLivenessMs(): number {\n const raw = process.env.MU_SPAWN_LIVENESS_MS;\n if (raw === undefined) return 1500;\n const parsed = Number.parseInt(raw, 10);\n if (Number.isNaN(parsed) || parsed < 0) return 1500;\n return parsed;\n}\n\n/**\n * Curated list of patterns that indicate the spawned CLI hit a fatal\n * provider/auth error during startup and is now parked at an error\n * prompt instead of becoming a working agent. Source:\n * `agent_spawn_model_auth_failure_counts_as_live` in the feedback ws.\n *\n * Kept short and case-insensitive on purpose: every entry must match a\n * real, observed dogfood failure mode. Don't add patterns speculatively\n * — the false-positive cost is to roll back a healthy spawn, which the\n * operator then has to re-attempt without the scan (`MU_SPAWN_LIVENESS_MS=0`).\n *\n * Mitigation against scrollback noise from harmless prior-session text:\n * the caller (`awaitSpawnLiveness`) only scans the LAST `STARTUP_ERROR_TAIL_LINES`\n * lines of the capture taken right after the liveness sleep, so matches\n * naturally come from the CLI's first ~1.5s of output (the spawned\n * pane has had no time to scroll older content into view).\n */\nconst STARTUP_ERROR_PATTERNS: readonly RegExp[] = [\n /No API key found for [\\w-]+/i,\n /Error: invalid API key/i,\n /Authentication failed/i,\n /401 Unauthorized/i,\n /Could not authenticate/i,\n // fb_agent_spawn_no_validation part B: post-spawn detection of the\n // \"binary not found at exec time\" failure mode. The pre-flight check\n // above catches the common typo BEFORE any side effect, but a few\n // edge cases still slip through and only surface in the pane:\n // - `--command \"...\"` skips the pre-flight (operator opt-out).\n // - PATH inside the spawned shell differs from PATH in mu's\n // process (login shell rc files, /etc/paths.d, etc.).\n // - Race: binary on PATH at spawn time, gone 1.5s later.\n //\n // Anchored to lines that LOOK like a shell error rather than prose\n // mentioning the marker (review_substrate_startup_err_patterns_too_broad):\n // require the marker at end-of-line, optionally followed by a single\n // `: <name>` token (the zsh form: `zsh: command not found: pi-meta`).\n // This still trips the canonical cases —\n // `bash: pi-meta: command not found`\n // `zsh: command not found: pi-meta`\n // `sh: pi-meta: No such file or directory`\n // — but rejects banner prose like\n // `I noticed earlier you saw 'command not found'`\n // `type \\`mu\\` — command not found? install with …`\n // that previously triggered a full spawn rollback. The pre-flight\n // PATH check remains the deterministic safety net for typo'd `--cli`.\n /^(?:.*: )?(?:command not found|No such file or directory)(?::\\s*\\S+)?$/i,\n];\n\n/**\n * Number of trailing lines of the post-liveness scrollback to scan for\n * `STARTUP_ERROR_PATTERNS`. The capture is `lines: 50`; tailing the last\n * 30 keeps the scan focused on the spawned CLI's own startup output\n * (the pane is brand-new, so anything earlier than that is the shell\n * banner / our own command-line, both safe).\n */\nconst STARTUP_ERROR_TAIL_LINES = 30;\n\n/**\n * Scan the tail of a freshly-captured pane buffer for known startup-\n * failure patterns. Returns the matched line on first hit, or undefined\n * if the buffer is clean.\n *\n * Exported for the test suite; not part of the SDK surface.\n */\nexport function detectSpawnStartupError(scrollback: string): string | undefined {\n const lines = scrollback.split(/\\r?\\n/);\n const tail = lines.slice(Math.max(0, lines.length - STARTUP_ERROR_TAIL_LINES));\n for (const line of tail) {\n for (const pattern of STARTUP_ERROR_PATTERNS) {\n if (pattern.test(line)) return line;\n }\n }\n return undefined;\n}\n\nasync function awaitSpawnLiveness(paneId: string, agentName: string): Promise<void> {\n const ms = defaultSpawnLivenessMs();\n if (ms === 0) return;\n await sleep(ms);\n // Capture-pane first so we have something to attach to the error if the\n // pane is in the process of being torn down (the buffer survives a beat\n // longer than the pane's existence in some tmux builds).\n const scrollback = await capturePane(paneId, { lines: 50 }).catch(() => undefined);\n if (!(await paneExists(paneId))) {\n throw new AgentDiedOnSpawnError(agentName, paneId, scrollback);\n }\n // Pane is alive — but \"alive\" doesn't mean \"working\". Scan the tail of\n // the capture for known provider/auth startup errors\n // (agent_spawn_model_auth_failure_counts_as_live). The pane stays at\n // an error prompt forever otherwise, and the orchestrator only\n // discovers the dud minutes later when `mu task wait` stalls.\n if (scrollback !== undefined) {\n const matchedLine = detectSpawnStartupError(scrollback);\n if (matchedLine !== undefined) {\n throw new AgentSpawnStartupError(agentName, paneId, matchedLine, scrollback);\n }\n }\n}\n\n/**\n * Three cases, all returning a stable pane id:\n * - session doesn't exist → create session+window+pane in one shot\n * - session exists, no such window → create a new window for this agent\n * - session and window both exist → split the window to add a pane\n */\nasync function createOrReusePane(opts: {\n session: string;\n windowName: string;\n command: string;\n cwd?: string;\n env?: Record<string, string>;\n}): Promise<string> {\n if (!(await sessionExists(opts.session))) {\n return newSessionWithPane(opts.session, {\n windowName: opts.windowName,\n command: opts.command,\n cwd: opts.cwd,\n env: opts.env,\n });\n }\n\n const windows = await listWindows(opts.session);\n const matching = windows.find((w) => w.name === opts.windowName);\n\n if (matching) {\n return splitWindow({\n target: `${opts.session}:${opts.windowName}`,\n command: opts.command,\n cwd: opts.cwd,\n env: opts.env,\n });\n }\n\n return newWindow({\n session: opts.session,\n name: opts.windowName,\n command: opts.command,\n cwd: opts.cwd,\n env: opts.env,\n });\n}\n","// mu — self-documenting output helpers.\n//\n// Every successful verb output should answer \"what changed AND what's\n// the natural next step?\". Every error should answer \"why AND what are\n// the actionable resolutions?\". The same data shape feeds both human\n// (dim text) and JSON (`nextSteps` array) consumers.\n//\n// Why a separate module: this is shared by cli.ts (success-path\n// rendering), the typed errors in src/agents.ts / src/tasks.ts /\n// src/workstream.ts (error nextSteps), and tests. Keeping the type +\n// helpers in one place avoids circular imports.\n\nimport Table from \"cli-table3\";\nimport type { Command } from \"commander\";\nimport picocolors from \"picocolors\";\n\n/**\n * Should we emit ANSI color escapes from this process?\n *\n * picocolors ships an `isColorSupported` flag, but it bakes its env\n * inspection (NO_COLOR / FORCE_COLOR / isTTY) ONCE at module-load\n * time. That makes the function untestable without dynamic re-imports\n * — and worse, it loses colors whenever stdout is a pipe, most\n * painfully under `watch --color mu state` and `tmux display-popup -E\n * 'mu state' | cat`, where the surrounding pane is a real terminal but\n * our own stdout is a pipe.\n *\n * We therefore re-implement the decision from scratch at call time,\n * reading every signal directly from `process.env` / `process.stdout`\n * so tests can flip env vars and observe the result without the\n * vi.resetModules + vi.doMock dance (per task\n * review_test_color_enabled_no_color_module_load_caveat).\n *\n * Order of precedence (first match wins):\n * - `NO_COLOR` set (cross-tool opt-out, https://no-color.org/) →\n * OFF, even when TMUX/MU_FORCE_COLOR/FORCE_COLOR are set. We\n * treat any defined value (including \"\") as set, matching the\n * no-color.org convention and picocolors' own behavior.\n * - `MU_FORCE_COLOR` truthy → ON (mu-specific override). Values\n * \"\", \"0\", and \"false\" are explicit opt-outs, matching chalk's\n * FORCE_COLOR convention.\n * - `FORCE_COLOR` truthy → ON (the standard env var picocolors /\n * chalk consult). Values \"\", \"0\", and \"false\" opt out.\n * - `TMUX` set → ON (the load-bearing fix for `watch` inside tmux:\n * the surrounding pane is a real terminal even though our stdout\n * is a pipe).\n * - Fall back to the standard TTY heuristic: stdout is a TTY AND\n * TERM !== \"dumb\". This mirrors what picocolors itself does in\n * `isColorSupported` for the happy-path case.\n *\n * See task hud_colors_stripped_under_watch_and for the original repro.\n */\nfunction envForceTrue(key: string): boolean {\n const v = process.env[key];\n if (v === undefined) return false;\n if (v === \"\" || v === \"0\" || v.toLowerCase() === \"false\") return false;\n return true;\n}\n\nexport function colorEnabled(): boolean {\n if (process.env.NO_COLOR !== undefined) return false;\n if (envForceTrue(\"MU_FORCE_COLOR\")) return true;\n if (envForceTrue(\"FORCE_COLOR\")) return true;\n if (process.env.TMUX !== undefined) return true;\n return Boolean(process.stdout.isTTY) && process.env.TERM !== \"dumb\";\n}\n\n/**\n * The single picocolors instance the rest of the codebase imports.\n * Built once at module load with `colorEnabled()` baked in, so every\n * caller (cli.ts, src/cli/*.ts) renders consistently regardless of\n * isTTY heuristics. Any other module that needs `pc` should import\n * this one rather than reaching for `picocolors` directly.\n */\n// picocolors is still used as the renderer (createColors honors the\n// flag we pass), but the *decision* of whether to render is ours.\nexport const pc = picocolors.createColors(colorEnabled());\n\n/**\n * One actionable next step. The `intent` is human-prose (\"Drop notes\n * as you work\"); the `command` is a literal shell command the user (or\n * an LLM) can copy-paste or `eval` directly.\n *\n * Used both for success-path hints (post-verb) and for typed-error\n * resolutions (in the error message + JSON output).\n */\nexport interface NextStep {\n /** Short human-prose label, e.g. \"Drop notes as you work\". */\n intent: string;\n /** Literal shell command, e.g. `mu task note foo \"...\"`. */\n command: string;\n}\n\n/**\n * Print a block of next-step hints to stdout, dimmed so humans can\n * skim past them but agents reading the captured output still get\n * them. No-op when the array is empty.\n *\n * Format:\n *\n * Next:\n * <intent padded>: <command>\n * <intent padded>: <command>\n *\n * The padding aligns the colons so visual scanning is easy.\n */\nexport function printNextSteps(steps: readonly NextStep[]): void {\n printNextStepsTo(steps, \"stdout\");\n}\n\n/** Same as `printNextSteps` but routes to either stdout or stderr.\n * Errors emit nextSteps to stderr (so success vs failure paths\n * capture cleanly when scripts redirect them separately); success\n * paths emit to stdout. Single source of truth for the formatting\n * (review_code_print_next_steps_duplicated). */\nexport function printNextStepsTo(steps: readonly NextStep[], sink: \"stdout\" | \"stderr\"): void {\n if (steps.length === 0) return;\n const labelWidth = Math.max(...steps.map((s) => s.intent.length));\n const out = sink === \"stderr\" ? console.error : console.log;\n out(pc.dim(\"Next:\"));\n for (const step of steps) {\n const label = step.intent.padEnd(labelWidth);\n out(pc.dim(` ${label} : ${step.command}`));\n }\n}\n\n/**\n * Build a cli-table3 Table with the mu-standard safety belt:\n * `wordWrap: false` (cells wider than their column truncate with `…`\n * instead of wrapping to a second visual row), per-column max widths\n * applied only where the caller asks (`null` = auto), and a default\n * borderless style mirroring the state/workspace/workstream tables.\n *\n * Callers should pre-truncate values they care about via the\n * `truncate()` / `truncateFront()` helpers in cli.ts (the proactive\n * path); `wordWrap: false` is the safety belt for the cells they\n * miss. This is load-bearing for renderers with fixed row budgets:\n * a single wrapped cell silently blows out the promised section height.\n *\n * Surfaced live by `mu workspace list` blowing the terminal width on\n * the `path` column (tables_truncate_long_cols_audit). Don't try to\n * cap every column — apply `colWidths` only on the column(s) the\n * operator is least likely to read in full and most likely to be\n * long. Don't add a `--full` / `--no-truncate` flag per verb either;\n * `--json` already emits the full value.\n */\nexport function muTable(opts: {\n head: string[];\n /** Per-column max widths in cells (`null` = auto width). When\n * supplied, the array length must match `head`. cli-table3\n * truncates with `…` because we set `wordWrap: false`. */\n colWidths?: (number | null)[];\n /** Style override; defaults to `{ head: [], border: [] }` (mu's\n * borderless look). Pass `{ head: [] }` to keep cli-table3's\n * default border styling. */\n style?: { head?: string[]; border?: string[] };\n}): InstanceType<typeof Table> {\n return new Table({\n head: opts.head,\n ...(opts.colWidths !== undefined ? { colWidths: opts.colWidths } : {}),\n style: opts.style ?? { head: [], border: [] },\n wordWrap: false,\n });\n}\n\n/**\n * The typed-error wire format for `--json` output. Errors that carry\n * actionable resolutions (most of them) implement `errorNextSteps()`;\n * the handler in cli.ts wraps them in this shape and emits to stderr.\n */\nexport interface ErrorJson {\n /** Class name (e.g. \"ClaimerNotRegisteredError\"). */\n error: string;\n /** Human-readable message (the same one printed to stderr in non-JSON mode). */\n message: string;\n /** Actionable resolutions. May be empty. */\n nextSteps: NextStep[];\n /** Process exit code that will follow. */\n exitCode: number;\n}\n\n/**\n * Marker interface for typed errors that carry actionable resolutions.\n * The handler checks this with a duck-typed `typeof err.errorNextSteps\n * === \"function\"` rather than instanceof so cross-realm errors (e.g.\n * thrown from a different module instance after a hot-reload) still\n * surface their nextSteps.\n */\nexport interface HasNextSteps {\n errorNextSteps(): NextStep[];\n}\n\n/**\n * Detect whether the current invocation requested `--json`. Used by the\n * error handler to decide between human-prose stderr and structured\n * JSON stderr. Reads `process.argv` directly because commander has\n * already consumed it by the time the handler runs, and threading it\n * through every verb wrapper would be invasive.\n *\n * Tolerates `--json=...` form (commander supports both) but mu's verbs\n * only use the bare `--json` flag.\n */\nexport function isJsonMode(): boolean {\n return process.argv.some((a) => a === \"--json\" || a.startsWith(\"--json=\"));\n}\n\n/**\n * Has the current err object produced its own actionable nextSteps?\n * Encapsulates the duck-type check so the handler stays readable.\n */\nexport function hasNextSteps(err: unknown): err is HasNextSteps {\n return (\n typeof err === \"object\" &&\n err !== null &&\n typeof (err as { errorNextSteps?: unknown }).errorNextSteps === \"function\"\n );\n}\n\n// ─── Usage rendering for the validation-error contract ──────────────\n//\n// Every operator-error path (commander-thrown CommanderError, handler-\n// thrown UsageError, typed *Invalid* errors) gets the same surface:\n// (1) the error line, (2) the failing subcommand's --help. The human\n// path prints commander's own helpInformation() (so future commander\n// version bumps automatically pick up any rendering improvements);\n// the --json path renders a structured shape so a script orchestrator\n// can introspect the verb without re-shelling for --help.\n//\n// Why structured-not-string for JSON: the entire point of --json is\n// that consumers never have to free-text-parse mu output. Embedding a\n// multi-kilobyte rendered help blob in the JSON envelope defeats that\n// — every option is already structured on the Command object.\n\n/** Structured rendition of a verb's --help, for JSON error envelopes. */\nexport interface UsageJson {\n /** Full canonical name including parent commands (e.g. \"mu task add\"). */\n command: string;\n /** The single-line synopsis (e.g. \"mu task add [options] [id]\"). */\n synopsis: string;\n /** Verb description (the one-paragraph prose under the synopsis). */\n description: string;\n /** Positional arguments in declared order. */\n args: Array<{ name: string; required: boolean; variadic: boolean; description: string }>;\n /** Options in declared order. `mandatory: true` iff the option was\n * declared via `.requiredOption()` (i.e. the operator MUST pass it).\n * `valueRequired: true` for `<value>`-style options whose value is\n * required when the flag IS passed. The two are independent. */\n options: Array<{\n flags: string;\n description: string;\n mandatory: boolean;\n valueRequired: boolean;\n }>;\n}\n\n/** Walk parent chain so subcommand renderings include the full path\n * (\"mu task add\" not just \"add\"). */\nfunction commandFullName(cmd: Command): string {\n const parts: string[] = [];\n let cur: Command | null = cmd;\n while (cur) {\n parts.unshift(cur.name());\n cur = cur.parent;\n }\n return parts.join(\" \");\n}\n\n/** Extract the structured usage shape for `--json` error envelopes. */\nexport function renderUsageJson(cmd: Command): UsageJson {\n return {\n command: commandFullName(cmd),\n synopsis: `${commandFullName(cmd)} ${cmd.usage()}`,\n description: cmd.description(),\n args: cmd.registeredArguments.map((a) => ({\n name: a.name(),\n required: a.required,\n variadic: a.variadic,\n description: a.description ?? \"\",\n })),\n options: cmd.options.map((o) => ({\n flags: o.flags,\n description: o.description ?? \"\",\n mandatory: o.mandatory ?? false,\n valueRequired: o.required ?? false,\n })),\n };\n}\n\n/** Render the human --help block (commander's own `helpInformation()`)\n * to stderr. Single source of truth for the post-error help dump. */\nexport function printUsageHuman(cmd: Command): void {\n // helpInformation() returns the full \"Usage: ...\\n\\n<desc>\\n\\nOptions:\\n ...\" block.\n // Print to stderr (errors only) so success-path stdout is never polluted.\n process.stderr.write(`\\n${cmd.helpInformation()}`);\n}\n","// mu — agent error classes.\n//\n// Every agent verb that can fail in a typed way has its own error class\n// here. The CLI's classifyError() (src/cli.ts) maps them to exit codes:\n// not found → 3 (AgentNotFoundError)\n// conflict → 4 (AgentExistsError, AgentNotInWorkstreamError,\n// AgentDiedOnSpawnError, AgentSpawnStartupError,\n// WorkspacePreservedError)\n//\n// AgentDiedOnSpawnError + AgentSpawnStartupError reach into spawn.ts for\n// defaultSpawnLivenessMs — a single, narrow cross-cluster import that\n// documents itself in the error message (\"agent died within Nms of\n// spawn\" / \"agent reported a startup error within Nms of spawn\").\n//\n// AgentSpawnCliNotFoundError is the pre-flight cousin of the two\n// post-spawn-detect errors above: thrown BEFORE prestageWorkspace when\n// the resolved `--cli` command's first token doesn't exist on PATH.\n// Distinct from AgentSpawnStartupError so the operator can tell\n// 'I never had a working CLI' from 'CLI started but parked at an error'.\n//\n// Extracted from src/agents.ts as part of refactor_split_large_src_files.\n\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport { defaultSpawnLivenessMs } from \"./spawn.js\";\n\n/**\n * Pre-flight failure: the command mu would have spawned in the new\n * pane doesn't resolve to a binary on PATH (and isn't an absolute /\n * relative path that exists + is executable). Thrown by `spawnAgent`\n * BEFORE `prestageWorkspace` so a typo in `--cli` never leaves an\n * orphan workspace dir behind.\n *\n * Source: feedback ws task `fb_agent_spawn_no_validation`. Live\n * dogfood report: `mu agent spawn worker-1 --cli pi-meta` on a host\n * where the `pi-meta` binary wasn't on PATH printed `Spawned worker-1\n * (pi-meta)` and the pane immediately died with `command not found`;\n * the existing 1.5s liveness check sometimes missed it (the shell\n * stays alive after the failed exec). Pre-flighting the PATH lookup\n * surfaces the typo before any side effects (workspace, pane, DB row).\n *\n * Distinct from `AgentSpawnStartupError` (pane alive but parked at an\n * error prompt) and `AgentDiedOnSpawnError` (pane vanished within the\n * liveness window). All three carry different remediation hints, so\n * they're separate types.\n */\nexport class AgentSpawnCliNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"AgentSpawnCliNotFoundError\";\n constructor(\n public readonly cli: string,\n /** First whitespace-separated token of the resolved command — the\n * thing actually missing on PATH. Surfaced verbatim in the\n * message so the operator sees what mu searched for (which may\n * differ from `cli` when `$MU_<UPPER_CLI>_COMMAND` rewrites it). */\n public readonly binary: string,\n /** Name of the env var that mu consulted before falling back to\n * the bare `cli` value (e.g. `MU_PI_META_COMMAND`). Always set\n * to the conventional name so the nextSteps hint can recommend\n * exporting it. */\n public readonly envVarChecked: string,\n ) {\n super(\n `--cli ${cli} resolved to binary \"${binary}\" which is not on PATH (and not an executable absolute/relative path). Refusing to spawn — would create a pane that dies immediately on \"command not found\".`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Try the default CLI (the one mu's substrate ships against)\",\n command: \"mu agent spawn <name> --cli pi\",\n },\n {\n intent: \"If you meant a custom alias, set the env var to its real path\",\n command: `export ${this.envVarChecked}=\"<absolute-path-to-binary> [args...]\"`,\n },\n {\n intent: \"List installed CLIs typically supported by mu\",\n command: \"which pi pi-meta claude codex\",\n },\n ];\n }\n}\n\nexport class AgentExistsError extends Error implements HasNextSteps {\n override readonly name = \"AgentExistsError\";\n constructor(public readonly agentName: string) {\n // v5: agent names are UNIQUE per (workstream, name) — the same\n // name can legitimately exist in two different workstreams. The\n // pre-v5 message claimed global uniqueness, which (a) lied about\n // the schema and (b) misled operators into closing the existing\n // agent when the actual fix is `-w <other-ws>`.\n super(`agent already exists in this workstream: ${agentName}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n // v5: agents.workstream_id → workstreams.id; there is no\n // `agents.workstream` column. Use the join so this hint\n // actually runs against a v5 DB.\n intent: \"List which workstream(s) already have an agent by this name\",\n command: `mu sql \"SELECT a.name, ws.name AS workstream FROM agents a JOIN workstreams ws ON ws.id = a.workstream_id WHERE a.name = '${this.agentName}'\"`,\n },\n {\n intent: \"Spawn it in a different workstream (per-workstream unique → no clash)\",\n command: `mu agent spawn ${this.agentName} -w <other-workstream>`,\n },\n {\n intent: \"Or close the existing agent in this workstream and re-spawn\",\n command: `mu agent close ${this.agentName} && mu agent spawn ${this.agentName}`,\n },\n { intent: \"Or pick a different name\", command: \"mu agent spawn <new-name>\" },\n ];\n }\n}\n\nexport class AgentNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"AgentNotFoundError\";\n constructor(\n public readonly agentName: string,\n /** Optional workstream context. When set, the message is enriched\n * with `(in workstream <ws>)` so the verb that hit the miss\n * (e.g. `mu workspace create <agent> -w <ws>`) doesn't leave the\n * operator guessing which scope was searched. Optional so existing\n * call sites that only know the agent name keep their original\n * one-line message. */\n public readonly workstream?: string,\n ) {\n super(\n workstream === undefined\n ? `no such agent: ${agentName}`\n : `no such agent: ${agentName} (in workstream ${workstream})`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List agents in current workstream\", command: \"mu agent list\" },\n { intent: \"List workstreams to choose the right scope\", command: \"mu workstream list\" },\n {\n intent: \"Spawn it now\",\n command: `mu agent spawn ${this.agentName} -w <workstream>`,\n },\n ];\n }\n}\n\n/**\n * Thrown when an entity-targeted verb is invoked with `-w/--workstream\n * <name>` but the named agent lives in a different workstream.\n * Mirrors `TaskNotInWorkstreamError`. Maps to exit code 4 (conflict /\n * wrong scope). Distinguishes \"the user typo'd the workstream\" from\n * \"the agent doesn't exist anywhere\" (which surfaces as\n * `AgentNotFoundError`).\n */\nexport class AgentNotInWorkstreamError extends Error implements HasNextSteps {\n override readonly name = \"AgentNotInWorkstreamError\";\n constructor(\n public readonly agentName: string,\n public readonly expectedWorkstream: string,\n public readonly actualWorkstream: string,\n ) {\n super(`agent ${agentName} is in workstream ${actualWorkstream}, not ${expectedWorkstream}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Use the agent's actual workstream\",\n command: `mu agent show ${this.agentName} -w ${this.actualWorkstream}`,\n },\n {\n intent: \"List agents in the requested workstream\",\n command: `mu agent list -w ${this.expectedWorkstream}`,\n },\n ];\n }\n}\n\n/**\n * Thrown when an agent's pane is created and titled successfully but the\n * spawned process exits within the liveness window (default 1500ms;\n * configurable via `MU_SPAWN_LIVENESS_MS`). The most common cause is the\n * underlying CLI failing fast: a wrapper CLI blocking on a single-instance\n * lock, `claude` rejecting an invalid API key, etc. The agent's last\n * scrollback (when capturable) is attached to help diagnose.\n */\nexport class AgentDiedOnSpawnError extends Error implements HasNextSteps {\n override readonly name = \"AgentDiedOnSpawnError\";\n constructor(\n public readonly agentName: string,\n public readonly paneId: string,\n public readonly scrollback: string | undefined,\n ) {\n const tail = scrollback?.trim();\n const detail = tail ? `\\n\\n--- pane scrollback ---\\n${tail}\\n--- end scrollback ---` : \"\";\n super(\n `agent ${agentName} died within ${defaultSpawnLivenessMs()}ms of spawn (pane ${paneId}). Most common cause: the spawned CLI exited immediately (e.g. a wrapper CLI blocking on its instance lock; set MU_<UPPER_CLI>_COMMAND to a non-blocking variant to bypass).${detail}`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Inspect the dead pane's scrollback for the underlying error\",\n command: `mu agent read ${this.agentName} -n 100`,\n },\n {\n // agent_spawn_liveness_check_trips_on: per-spawn override is\n // the right scope for one-offs (e.g. wrapper CLIs blocking\n // on a per-project solo lock). Listed first so operators\n // reach for it before exporting an env var that leaks into\n // every subsequent spawn in the shell.\n intent: \"Override per spawn (one-off; no env-var leak)\",\n command: `mu agent spawn ${this.agentName} --command \"<cli> <bypass-flag>\" (e.g. pi-meta --no-solo)`,\n },\n {\n intent:\n \"Make the override the default for this CLI (applies to every subsequent spawn in this shell)\",\n command: 'export MU_PI_COMMAND=\"pi-alt --some-flag\"',\n },\n {\n intent: \"Disable the liveness check (CI / known long-lived sh subprocess)\",\n command: \"export MU_SPAWN_LIVENESS_MS=0\",\n },\n { intent: \"Run health check\", command: \"mu doctor\" },\n ];\n }\n}\n\n/**\n * Thrown when an agent's pane is alive AND staying alive after the\n * liveness window, but its first burst of output matches a known\n * provider-startup-failure pattern (missing API key, auth rejected, …).\n * Source: feedback ws task `agent_spawn_model_auth_failure_counts_as_live`.\n * Live dogfood report: `pi-meta --no-solo --model sonnet:high` printed\n * `Error: No API key found for amazon-bedrock` and parked at a prompt.\n * The pane stayed alive (1.5s liveness check passed) but the worker\n * could never do work — the orchestrator only discovered this when\n * `mu task wait` stalled minutes later.\n *\n * Distinct from `AgentDiedOnSpawnError`:\n * - `AgentDiedOnSpawnError` → pane vanished within the liveness window\n * (CLI exited fast).\n * - `AgentSpawnStartupError` → pane alive, but the captured scrollback\n * tail contains a curated provider-auth-failure pattern.\n * The two carry different remediation hints (CLI override vs. fix the\n * env var), so they're separate types instead of one with a flag.\n *\n * The pattern list is curated and short to keep false-positive risk low\n * — the scan only looks at the last ~30 lines of the 50-line capture\n * taken right after the liveness sleep, so matches naturally come from\n * the CLI's first ~1.5s of output (not arbitrary later prompts the\n * agent might type into).\n */\nexport class AgentSpawnStartupError extends Error implements HasNextSteps {\n override readonly name = \"AgentSpawnStartupError\";\n constructor(\n public readonly agentName: string,\n public readonly paneId: string,\n /** The single scrollback line that matched a known startup-error\n * pattern. Surfaced verbatim in the message so the operator sees\n * what mu saw. */\n public readonly matchedLine: string,\n /** Full captured scrollback (tail-trimmed already by\n * awaitSpawnLiveness). Attached to the message for context. */\n public readonly scrollback: string,\n ) {\n super(\n `agent ${agentName} reported a startup error within ${defaultSpawnLivenessMs()}ms of spawn (pane ${paneId}). The pane is alive but the spawned CLI parked at an error prompt instead of becoming a working agent.\\n\\nMatched line: ${matchedLine.trim()}\\n\\n--- pane scrollback ---\\n${scrollback.trim()}\\n--- end scrollback ---`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Inspect the parked pane's scrollback for the full error\",\n command: `mu agent read ${this.agentName} -n 100`,\n },\n {\n // Most common today: the operator picked a model whose\n // provider has no credentials in this env. Default Anthropic\n // is the safe fallback for pi-meta.\n intent: \"Re-spawn with a CLI command whose provider credentials are present\",\n command: `mu agent spawn ${this.agentName} --command \"pi-meta --no-solo\" # default Anthropic`,\n },\n {\n intent: \"Or set the missing API key env var for the provider you wanted, then re-spawn\",\n command:\n \"export ANTHROPIC_API_KEY=... # or AWS_BEARER_TOKEN_BEDROCK, OPENAI_API_KEY, ...\",\n },\n {\n intent:\n \"Disable the startup-error scan if you actually wanted that prompt (CI / scripted recovery)\",\n command: \"export MU_SPAWN_LIVENESS_MS=0\",\n },\n ];\n }\n}\n\n/**\n * Thrown when `closeAgent` is called on an agent that has an associated\n * workspace AND the caller didn't explicitly opt into discarding it.\n *\n * Background: the FK on `vcs_workspaces.agent` cascades on agent\n * delete, so a naive `closeAgent` drops the workspace registry row\n * but leaves the on-disk dir orphaned (mu can't see it via\n * `mu workspace list / free / path` afterwards). Surfaced during\n * the multi-agent dogfood teardown when three workspaces went\n * orphaned silently.\n *\n * The fix: refuse close if a workspace exists; force the caller to\n * decide. Two actionable resolutions:\n * - `mu workspace free <agent>` first, then close cleanly.\n * - `mu agent close <agent> --discard-workspace` to free the\n * workspace AND close the agent in one shot (lossy: pending\n * changes in the workspace are gone).\n *\n * Maps to exit code 4 (conflict) via the cli.ts handler.\n */\nexport class WorkspacePreservedError extends Error implements HasNextSteps {\n override readonly name = \"WorkspacePreservedError\";\n constructor(\n public readonly agentName: string,\n public readonly workspacePath: string,\n ) {\n super(\n `agent ${agentName} has a workspace at ${workspacePath}; refusing to close (would orphan the on-disk dir)`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Free the workspace first (preserves agent for next step)\",\n command: `mu workspace free ${this.agentName} (--commit to commit pending changes first)`,\n },\n {\n intent: \"Or close + discard the workspace in one shot (lossy)\",\n command: `mu agent close ${this.agentName} --discard-workspace`,\n },\n {\n intent: \"If the workstream was archived, restore task memory under a fresh name\",\n command: \"mu archive restore <label> --as <new-workstream> --source <workstream>\",\n },\n {\n intent: \"Or just inspect what's in the workspace\",\n command: `cd ${this.workspacePath}`,\n },\n ];\n }\n}\n","// mu — git VCS backend.\n\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport {\n commitSummary,\n ensureParent,\n exec,\n probeVcsRoot,\n rmDirSync,\n run,\n runShow,\n saneLimit,\n} from \"./helpers.js\";\nimport {\n type CommitSummary,\n type FreeWorkspaceResult,\n type VcsBackend,\n WorkspaceConflictError,\n WorkspaceDirtyError,\n} from \"./types.js\";\n\n// Uses `git worktree` so the new workspace shares the .git store with\n// the project root. Cheap (no copy), fast, and integrates with the rest\n// of the agent's git workflow (push, log, etc) trivially.\n\nexport const gitBackend: VcsBackend = {\n name: \"git\",\n\n async detect(projectRoot) {\n return probeVcsRoot(\"git\", [\"rev-parse\", \"--show-toplevel\"], projectRoot);\n },\n\n async createWorkspace(opts) {\n if (existsSync(opts.workspacePath)) {\n throw new Error(`vcs git: workspacePath already exists: ${opts.workspacePath}`);\n }\n await ensureParent(opts.workspacePath);\n // Defensive prune: if a previous workspace at the same path was\n // freed by `rm -rf` (or otherwise lost its dir without proper\n // teardown), git's worktree registry still points at it. Then\n // `git worktree add` fails with 'missing but already registered\n // worktree'. `git worktree prune` is idempotent and cheap; running\n // it BEFORE every add costs ~10ms and immunises us against the\n // mufeedback workspace_free_cleanup_leaves_git case.\n await run(\"git\", [\"worktree\", \"prune\"], opts.projectRoot).catch(() => {\n /* prune is best-effort; if it fails we'll get a clear error from `add` next */\n });\n // `git worktree add <path> [<ref>]`. Without a ref, the new worktree\n // checks out a detached HEAD at the project's current HEAD, which\n // matches the \"fresh per-agent workspace\" semantics we want. Use a\n // detached HEAD by default so two agents from the same parent ref\n // don't collide on a branch name.\n const args = [\"worktree\", \"add\", \"--detach\", opts.workspacePath];\n if (opts.parentRef) args.push(opts.parentRef);\n await run(\"git\", args, opts.projectRoot);\n // Resolve the actual SHA we ended up on for the parentRef record.\n const sha = await run(\"git\", [\"rev-parse\", \"HEAD\"], opts.workspacePath);\n return { parentRef: sha };\n },\n\n // Working-copy clean check: empty `git status --porcelain` output\n // means no working-tree, staged, or untracked-not-ignored changes.\n // Returns false on any failure (workspace path missing, git\n // explodes) — be conservative; auto-free should never \"silently\n // succeed\" because we couldn't check.\n async isClean(workspacePath) {\n if (!existsSync(workspacePath)) return false;\n try {\n const files = await listGitDirtyFiles(workspacePath);\n return files.length === 0;\n } catch {\n return false;\n }\n },\n\n // Compute commits-behind as: count of commits reachable from main\n // but not from `ref`. Resolves \"main\" via origin/HEAD (the symbolic\n // ref the remote advertises), falling back to origin/main and then\n // origin/master. Returns null when none of those resolve, or when\n // the rev-list call fails (e.g. ref unknown locally).\n //\n // Pure observation: NO `git fetch`. The number is as fresh as the\n // last time the user (or some other process) updated the local\n // remote-tracking refs.\n async commitsBehind(workspacePath, ref) {\n if (!existsSync(workspacePath)) return null;\n const main = await resolveGitMainRef(workspacePath);\n if (main === undefined) return null;\n try {\n const out = await run(\"git\", [\"rev-list\", \"--count\", `${ref}..${main}`], workspacePath);\n const n = Number.parseInt(out.trim(), 10);\n return Number.isFinite(n) ? n : null;\n } catch {\n return null;\n }\n },\n\n // Rebase the worktree onto `fromRef` (default = origin/HEAD via\n // resolveGitMainRef). Refuses on a dirty WC; returns the replayed\n // commit subjects oldest-first; on conflict aborts the rebase and\n // throws WorkspaceConflictError so the operator never inherits a\n // half-rebased worktree from us.\n //\n // We DO `git fetch` first — otherwise the rebase target would only\n // be as fresh as the local refs cache, and the operator running\n // `mu workspace refresh` is explicitly asking for the latest. This\n // is the one-and-only place mu fetches; commitsBehind() stays pure.\n async rebaseTo(workspacePath, fromRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs git: workspace path missing: ${workspacePath}`);\n }\n // Dirty-check first: refuse before any side effect.\n const dirtyFiles = await listGitDirtyFiles(workspacePath);\n if (dirtyFiles.length > 0) {\n throw new WorkspaceDirtyError(workspacePath, dirtyFiles);\n }\n // Best-effort fetch so the resolved main ref is fresh. Failure is\n // ignored (offline / no remote) — we still attempt the rebase\n // against whatever the local refs cache holds.\n await run(\"git\", [\"fetch\", \"--quiet\"], workspacePath).catch(() => {});\n let resolvedRef: string;\n if (fromRef !== undefined) {\n resolvedRef = fromRef;\n } else {\n const main = await resolveGitMainRef(workspacePath);\n if (main === undefined) {\n throw new Error(\n `vcs git: cannot resolve default branch (no origin/HEAD, origin/main, or origin/master) in ${workspacePath}; pass --from <ref>`,\n );\n }\n resolvedRef = main;\n }\n // Capture the pre-rebase HEAD so we can compute `replayed` as the\n // commits that ended up on top of resolvedRef after the rebase.\n const preHead = await run(\"git\", [\"rev-parse\", \"HEAD\"], workspacePath);\n try {\n await run(\n \"git\",\n [\"-c\", \"user.email=mu@local\", \"-c\", \"user.name=mu\", \"rebase\", resolvedRef],\n workspacePath,\n );\n } catch (err) {\n // Capture the conflicting paths BEFORE aborting (the abort\n // resets the index and the unmerged paths disappear).\n const conflicts = await listGitUnmergedPaths(workspacePath);\n await run(\"git\", [\"rebase\", \"--abort\"], workspacePath).catch(() => {});\n if (conflicts.length > 0) {\n throw new WorkspaceConflictError(workspacePath, resolvedRef, conflicts);\n }\n // Non-conflict rebase failure (e.g. unknown ref). Surface it raw.\n throw err;\n }\n // Replayed commits = the new HEAD..resolvedRef gap, but oldest-first\n // and limited to what was actually replayed from preHead. We use\n // `git log --reverse <merge-base>..HEAD` where the merge base is\n // computed against resolvedRef, since after a successful rebase\n // HEAD's history above the base IS the replayed set.\n const mergeBase = await run(\"git\", [\"merge-base\", \"HEAD\", resolvedRef], workspacePath).catch(\n () => preHead,\n );\n const logOut = await run(\n \"git\",\n [\"log\", \"--reverse\", \"--format=%s\", `${mergeBase}..HEAD`],\n workspacePath,\n );\n const replayed = logOut.length === 0 ? [] : logOut.split(\"\\n\");\n return { fromRef: resolvedRef, replayed, conflicts: [] };\n },\n\n // List commits in (baseRef..HEAD), oldest-first. The format string\n // packs four NUL-delimited fields per record, then a record\n // separator '\\x1e' (RECORD-SEPARATOR control char) so subjects /\n // bodies with embedded newlines or NULs survive parsing. We don't\n // use a JSON template because git's `--format` is field-oriented;\n // %x00 (NUL) and %x1e (RS) are git's portable escape sequences.\n async commitsSinceBase(workspacePath, baseRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs git: workspace path missing: ${workspacePath}`);\n }\n const out = await run(\n \"git\",\n [\"log\", \"--reverse\", \"-z\", \"--format=%H%x00%s%x00%b%x00%aI\", `${baseRef}..HEAD`],\n workspacePath,\n );\n if (out.length === 0) return [];\n // -z makes git use NUL as the record separator; combined with our\n // %x00 field separators each commit looks like:\n // <sha>\\0<subject>\\0<body>\\0<authorDate>\\0\n // i.e. four fields followed by the record-terminating NUL git\n // injects with -z. Splitting on NUL leaves us with 4N fields plus\n // a trailing empty string we drop.\n const fields = out.split(\"\\x00\");\n const records: CommitSummary[] = [];\n for (let i = 0; i + 3 < fields.length; i += 4) {\n const sha = fields[i] ?? \"\";\n const subject = fields[i + 1] ?? \"\";\n const body = fields[i + 2] ?? \"\";\n const authorDate = fields[i + 3] ?? \"\";\n if (sha.length === 0) continue;\n records.push(commitSummary(sha, subject, body, authorDate));\n }\n return records;\n },\n\n async recentCommits(projectRoot, limit) {\n if (!existsSync(projectRoot)) {\n throw new Error(`vcs git: project root missing: ${projectRoot}`);\n }\n const n = saneLimit(limit);\n if (n === 0) return [];\n const out = await run(\n \"git\",\n [\"log\", `--max-count=${n}`, \"-z\", \"--format=%H%x00%s%x00%b%x00%aI%x00%an\"],\n projectRoot,\n );\n return parseGitZRecords(out);\n },\n\n async showCommit(projectRoot, sha) {\n return runShow(\"git\", [\"show\", sha, \"--stat\", \"-p\", \"--color=always\"], projectRoot);\n },\n\n async freeWorkspace(opts) {\n // Disk-missing case: a previous caller (or the user) ran `rm -rf`\n // out from under us, but the git worktree registry STILL has an\n // entry pointing here. Without a prune, the next `git worktree\n // add` at this path errors out (the mufeedback case). We can't\n // reach the project root via the workspace itself (the .git\n // pointer file is gone with the dir), but `worktree prune` runs\n // from inside any git repo and reaps every dead worktree. We\n // can't reliably guess WHICH project root, so log it as a hint\n // in the result rather than running prune ourselves; the spawn\n // path's defensive prune (above) will clean it on next use.\n if (!existsSync(opts.workspacePath)) {\n return { removed: false };\n }\n let committedRef: string | undefined;\n if (opts.commit) {\n // Commit only if there's anything to commit. Reuse the same\n // dirty-file semantics as isClean() and rebaseTo(): `git status\n // --porcelain` includes working-tree, staged, and untracked-not-\n // ignored changes.\n const dirty = (await listGitDirtyFiles(opts.workspacePath)).length > 0;\n if (dirty) {\n await run(\"git\", [\"add\", \"-A\"], opts.workspacePath);\n await run(\n \"git\",\n [\n \"-c\",\n \"user.email=mu@local\",\n \"-c\",\n \"user.name=mu\",\n \"commit\",\n \"-m\",\n \"mu workspace free auto-commit\",\n ],\n opts.workspacePath,\n );\n committedRef = await run(\"git\", [\"rev-parse\", \"HEAD\"], opts.workspacePath);\n }\n }\n // Tear down: git worktree remove --force <path> cleans both the\n // on-disk directory AND the git/worktrees/<name>/ admin entry. We\n // can't easily run it from the project root (we don't store it on\n // the workspace row), but git accepts the worktree's own path from\n // anywhere with --force, so we resolve a usable cwd via git's own\n // pointer back to the project's .git dir.\n const projectRoot = await resolveGitProjectRoot(opts.workspacePath);\n if (projectRoot) {\n await run(\"git\", [\"worktree\", \"remove\", \"--force\", opts.workspacePath], projectRoot);\n } else {\n // Lost the link — just rm the directory. git's admin entry will\n // be cleaned by the next `git worktree prune` invocation.\n rmDirSync(opts.workspacePath);\n }\n const result: FreeWorkspaceResult = { removed: true };\n if (committedRef !== undefined) result.committedRef = committedRef;\n return result;\n },\n\n async listDirtyFiles(workspacePath) {\n if (!existsSync(workspacePath)) return [];\n return listGitDirtyFiles(workspacePath);\n },\n};\n\n/**\n * Resolve the workspace's notion of \"main\". Tries, in order:\n * 1. `origin/HEAD` — the symbolic ref the remote published\n * (e.g. \"refs/remotes/origin/main\"). The most accurate signal.\n * 2. `refs/remotes/origin/main` — the convention.\n * 3. `refs/remotes/origin/master` — pre-rename convention.\n * Returns the resolved ref string (suitable for `git rev-list`) or\n * undefined if none of the three resolve.\n */\nasync function resolveGitMainRef(workspacePath: string): Promise<string | undefined> {\n for (const candidate of [\n \"refs/remotes/origin/HEAD\",\n \"refs/remotes/origin/main\",\n \"refs/remotes/origin/master\",\n ]) {\n try {\n await exec(\"git\", [\"rev-parse\", \"--verify\", \"--quiet\", candidate], { cwd: workspacePath });\n return candidate;\n } catch {\n /* try next */\n }\n }\n return undefined;\n}\n\n/**\n * Return the list of dirty paths (working-tree modifications + staged\n * + untracked-not-ignored), one entry per file. Empty when clean.\n * Used by `rebaseTo` to refuse with WorkspaceDirtyError carrying the\n * file list — one error message tells the operator both that the\n * workspace is dirty and which files to deal with.\n */\nasync function listGitDirtyFiles(workspacePath: string): Promise<string[]> {\n // `git status --porcelain` prints one line per changed file with\n // a 2-char status prefix; trim the prefix to get the path. Untracked\n // files appear as `?? path` and are included by default.\n const { stdout } = await exec(\"git\", [\"status\", \"--porcelain\"], { cwd: workspacePath });\n const lines = stdout.split(\"\\n\").filter((l) => l.length > 0);\n return lines.map((l) => l.slice(3));\n}\n\n/**\n * Return the unmerged paths after a failed `git rebase`. Used to\n * enrich WorkspaceConflictError before we abort. Empty list means the\n * rebase failed for a non-conflict reason (unknown ref, etc).\n */\nasync function listGitUnmergedPaths(workspacePath: string): Promise<string[]> {\n try {\n const out = await run(\"git\", [\"diff\", \"--name-only\", \"--diff-filter=U\"], workspacePath);\n return out.length === 0 ? [] : out.split(\"\\n\").filter((l) => l.length > 0);\n } catch {\n return [];\n }\n}\n\n/**\n * Resolve the project root that owns this worktree. Returns undefined\n * if the link is broken (e.g. project root deleted). git rev-parse\n * --git-common-dir gives us the parent project's .git dir; the worktree's\n * project root is its parent.\n */\nasync function resolveGitProjectRoot(workspacePath: string): Promise<string | undefined> {\n try {\n const { stdout } = await exec(\n \"git\",\n [\"rev-parse\", \"--path-format=absolute\", \"--git-common-dir\"],\n { cwd: workspacePath },\n );\n return resolve(stdout.trim(), \"..\");\n } catch {\n return undefined;\n }\n}\n\nfunction parseGitZRecords(raw: string): CommitSummary[] {\n if (raw.length === 0) return [];\n const fields = raw.split(\"\\x00\");\n const records: CommitSummary[] = [];\n for (let i = 0; i + 4 < fields.length; i += 5) {\n const sha = fields[i] ?? \"\";\n if (sha.length === 0) continue;\n records.push(\n commitSummary(\n sha,\n fields[i + 1] ?? \"\",\n fields[i + 2] ?? \"\",\n fields[i + 3] ?? \"\",\n fields[i + 4] ?? \"\",\n ),\n );\n }\n return records;\n}\n","// mu — shared helpers for concrete VCS backends.\n\nimport { execFile } from \"node:child_process\";\nimport { existsSync, rmSync } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { type CommitSummary, SHOW_COMMIT_MAX_CHARS, type ShowCommitResult } from \"./types.js\";\n\nexport const exec = promisify(execFile);\n\nexport async function probeVcsRootPath(\n cmd: string,\n args: string[],\n cwd: string,\n): Promise<string | null> {\n try {\n const stdout = await run(cmd, args, cwd);\n const root = stdout.trim();\n return root.length > 0 ? root : null;\n } catch {\n // Tool not installed, or tool exited non-zero because cwd is not\n // in that backend's repo type. Both mean \"not this backend\".\n return null;\n }\n}\n\nexport async function probeVcsRoot(cmd: string, args: string[], cwd: string): Promise<boolean> {\n return (await probeVcsRootPath(cmd, args, cwd)) !== null;\n}\n\nexport function isSaplingDotdir(dotdir: string): boolean {\n return /(?:^|[\\\\/])\\.(?:sl|hg)$/.test(dotdir);\n}\n\nexport async function ensureParent(path: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n}\n\nexport function rmDirSync(path: string): boolean {\n if (!existsSync(path)) return false;\n rmSync(path, { recursive: true, force: true });\n return true;\n}\n\n/**\n * Run a binary with args. Throws a typed Error on non-zero exit. Stdout\n * is returned trimmed; stderr is appended to the Error message.\n */\nexport async function run(bin: string, args: readonly string[], cwd?: string): Promise<string> {\n try {\n const { stdout } = await exec(bin, [...args], { cwd, maxBuffer: 16 * 1024 * 1024 });\n return stdout.trim();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`vcs ${bin} ${args.join(\" \")} failed: ${msg}`);\n }\n}\n\nexport async function runShow(\n bin: string,\n args: readonly string[],\n cwd?: string,\n): Promise<ShowCommitResult> {\n try {\n const { stdout } = await exec(bin, [...args], {\n cwd,\n maxBuffer: SHOW_COMMIT_MAX_CHARS * 2,\n });\n if (stdout.length > SHOW_COMMIT_MAX_CHARS) {\n return {\n text: `${stdout.slice(0, SHOW_COMMIT_MAX_CHARS)}\\n…(truncated at ${SHOW_COMMIT_MAX_CHARS} chars)`,\n truncated: true,\n };\n }\n return { text: stdout, truncated: false };\n } catch (err) {\n return {\n text: \"\",\n truncated: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nfunction relTimeFromIso(iso: string): string {\n const t = Date.parse(iso);\n if (!Number.isFinite(t)) return \"—\";\n const sec = Math.max(0, Math.floor((Date.now() - t) / 1000));\n if (sec < 60) return `${sec}s`;\n const min = Math.floor(sec / 60);\n if (min < 60) return `${min}m`;\n const hr = Math.floor(min / 60);\n if (hr < 24) return `${hr}h`;\n const day = Math.floor(hr / 24);\n if (day < 7) return `${day}d`;\n return `${Math.floor(day / 7)}w`;\n}\n\nexport function commitSummary(\n sha: string,\n subject: string,\n body: string,\n authorDate: string,\n author = \"\",\n): CommitSummary {\n return { sha, subject, body, author, authorDate, relTime: relTimeFromIso(authorDate) };\n}\n\nexport function saneLimit(limit: number): number {\n if (!Number.isFinite(limit)) return 25;\n return Math.max(0, Math.min(200, Math.floor(limit)));\n}\n\n/**\n * Parse the NUL-field / \\x1e-record format used by the jj/sl\n * commitsSinceBase impls. Each record is `sha\\0subject\\0body\\0date`\n * terminated by \\x1e. Empty input → [].\n */\nexport function parseNulRecords(raw: string): CommitSummary[] {\n if (raw.length === 0) return [];\n const records: CommitSummary[] = [];\n for (const rec of raw.split(\"\\x1e\")) {\n if (rec.length === 0) continue;\n const fields = rec.split(\"\\x00\");\n const sha = fields[0] ?? \"\";\n if (sha.length === 0) continue;\n records.push(\n commitSummary(sha, fields[1] ?? \"\", fields[2] ?? \"\", fields[3] ?? \"\", fields[4] ?? \"\"),\n );\n }\n return records;\n}\n","// mu — shared VCS backend contracts and typed errors.\n\nimport type { HasNextSteps, NextStep } from \"../output.js\";\n\nexport type VcsBackendName = \"jj\" | \"sl\" | \"git\" | \"none\";\n\n// ─── Refresh / rebase result + typed errors ──────────────────────────\n//\n// rebaseTo is the backend-side of `mu workspace refresh`. The errors\n// live in this module (not src/workspace.ts) because they're thrown\n// from inside the backend impls; workspace.ts imports vcs.ts, and the\n// root vcs hub re-exports these concrete definitions.\n\nexport interface RebaseResult {\n /** The ref the workspace was actually rebased onto (resolved\n * symbolic-or-revset → concrete name). For git that is the\n * resolveGitMainRef() symbolic ref; for jj/sl it's the literal\n * `trunk()` revset (or whatever the operator passed via fromRef). */\n fromRef: string;\n /** Commit subjects (or descriptions) that got replayed, oldest-first.\n * Empty when the workspace was already at fromRef (no-op). */\n replayed: string[];\n /** Files / commits that conflicted during the rebase. Always\n * empty for a successful rebase — a non-empty conflicts list\n * means we threw WorkspaceConflictError before returning. The\n * field exists so the error's serialised payload can carry it. */\n conflicts: string[];\n}\n\nexport interface CommitSummary {\n /** Full commit / change id. */\n sha: string;\n /** First-line description / subject. */\n subject: string;\n /** Remainder of the commit message (may be empty). */\n body: string;\n /** Author display name, when the backend exposes one. */\n author: string;\n /** ISO-8601 author / commit timestamp. */\n authorDate: string;\n /** Compact relative author time (e.g. \"3m\", \"2d\"). */\n relTime: string;\n}\n\nexport interface ShowCommitResult {\n /** Captured VCS show output (possibly truncated). Empty string on error. */\n text: string;\n /** True when stdout exceeded SHOW_COMMIT_MAX_CHARS and was clipped. */\n truncated: boolean;\n /** Human-readable error message; omitted on success. */\n error?: string;\n}\n\n/** Cap captured `show` output so giant merge commits can't eat the TUI. */\nexport const SHOW_COMMIT_MAX_CHARS = 100_000;\n\n/**\n * Thrown by `rebaseTo` / `commitsSinceBase` on the `none` backend\n * (cp -a snapshots have no notion of a rebase target / fork point).\n * Maps to exit code 4.\n */\nexport class WorkspaceVcsRequiredError extends Error implements HasNextSteps {\n override readonly name = \"WorkspaceVcsRequiredError\";\n constructor(\n public readonly verb: string,\n public readonly workspacePath: string,\n ) {\n super(\n `vcs none: \\`mu workspace ${verb}\\` requires a real VCS (jj/sl/git); ${workspacePath} is a cp -a snapshot`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Free the snapshot and re-create with a real VCS backend\",\n command: \"mu workspace free <agent> && mu workspace create <agent> --backend <jj|sl|git>\",\n },\n ];\n }\n}\n\n/**\n * Thrown by `rebaseTo` when the workspace has uncommitted changes\n * the rebase would clobber. Carries the dirty file list so the operator\n * can decide between commit/stash/--force. Maps to exit code 4.\n */\nexport class WorkspaceDirtyError extends Error implements HasNextSteps {\n override readonly name = \"WorkspaceDirtyError\";\n /** The verb that refused (\"rebase\", \"recreate\", ...). Used to make\n * the error message + nextSteps point the operator at the right\n * escape hatch (e.g. recreate's `--force`). Default \"rebase\" for\n * backward compatibility with the original rebaseTo call sites. */\n public readonly verb: string;\n constructor(\n public readonly workspacePath: string,\n public readonly files: readonly string[],\n verb = \"rebase\",\n ) {\n super(\n `workspace dirty (${files.length} uncommitted file(s)): ${workspacePath}; refusing to ${verb}`,\n );\n this.verb = verb;\n }\n errorNextSteps(): NextStep[] {\n const steps: NextStep[] = [\n {\n intent: \"Inspect the dirty files\",\n command: `(cd ${this.workspacePath} && git status -s) # or jj st / sl st`,\n },\n {\n intent: `Commit them first, then retry ${this.verb}`,\n command: `(cd ${this.workspacePath} && git add -A && git commit -m WIP)`,\n },\n {\n intent: \"Or stash them first (git only)\",\n command: `(cd ${this.workspacePath} && git stash)`,\n },\n ];\n if (this.verb === \"recreate\") {\n steps.push({\n intent: \"Or DISCARD all uncommitted changes (the lossy escape)\",\n command: \"mu workspace recreate <agent> --force\",\n });\n }\n return steps;\n }\n}\n\n/**\n * Thrown by `rebaseTo` when the rebase produced conflicts the\n * operator must resolve manually. Carries the conflicting paths.\n * Maps to exit code 5.\n */\nexport class WorkspaceConflictError extends Error implements HasNextSteps {\n override readonly name = \"WorkspaceConflictError\";\n constructor(\n public readonly workspacePath: string,\n public readonly fromRef: string,\n public readonly conflicts: readonly string[],\n ) {\n super(`rebase onto ${fromRef} produced ${conflicts.length} conflict(s): ${workspacePath}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"cd into the workspace and resolve\",\n command: `cd ${this.workspacePath} # then resolve & commit; or: git rebase --abort / jj abandon / sl rebase --abort`,\n },\n ];\n }\n}\n\nexport interface CreateWorkspaceOptions {\n /** The repository being branched from. Absolute path. */\n projectRoot: string;\n /** Where to place the new workspace. Absolute path; must NOT exist. */\n workspacePath: string;\n /** Optional commit / branch / changeset id to base off. Backend-specific:\n * git uses it as a `git worktree add`'s ref, jj as a revset, sl as a\n * rev. Undefined = current head. */\n parentRef?: string;\n}\n\nexport interface CreateWorkspaceResult {\n /** The actual ref the workspace points at (resolved to a stable id\n * when possible). Stored on the row; useful for `mu workspace list`\n * and for `--commit` flows. May be null for backends that don't\n * expose a meaningful parent (e.g. `none`). */\n parentRef: string | null;\n}\n\nexport interface FreeWorkspaceOptions {\n workspacePath: string;\n /** If true, attempt to commit any pending changes BEFORE removal.\n * Backend-specific: jj auto-commits via `jj describe + jj new`, git\n * needs an explicit commit on the worktree, sl needs `sl commit`,\n * none has nothing to commit. If pending changes exist and `commit`\n * is false, the on-disk directory still gets removed and changes are\n * lost — the verb prints a clear warning. */\n commit: boolean;\n}\n\nexport interface FreeWorkspaceResult {\n /** The commit id that captured the pending changes, when `commit` was\n * true and there was something to commit. Otherwise undefined. */\n committedRef?: string;\n /** True iff the on-disk path was actually removed (vs. already gone). */\n removed: boolean;\n}\n\nexport interface VcsBackend {\n readonly name: VcsBackendName;\n\n /** True iff this backend should handle `projectRoot`. Implementations\n * check for the relevant marker dir (`.jj`, `.sl`, `.git`); `none`\n * always returns true and is consulted last. */\n detect(projectRoot: string): Promise<boolean>;\n\n createWorkspace(opts: CreateWorkspaceOptions): Promise<CreateWorkspaceResult>;\n\n freeWorkspace(opts: FreeWorkspaceOptions): Promise<FreeWorkspaceResult>;\n\n /**\n * Count commits that the project's default branch (\"main\") has but\n * `ref` does not — i.e. how many commits `ref` is BEHIND main.\n *\n * Used by `mu workspace list` and `mu state` to surface staleness\n * (bug_workspace_stale_parent_silent_drift). Cheap, pure-observation:\n * NO automatic fetch. We compare against whatever main resolves to in\n * the workspace's LOCAL refs cache. The user can `git fetch` (or\n * equivalent) themselves if they want a fresher number.\n *\n * Returns null when:\n * - main / trunk cannot be resolved (no origin/HEAD, no origin/main,\n * no origin/master, no trunk() bookmark, etc.)\n * - the underlying VCS command fails for any reason (detached worktree,\n * missing refs, the `none` backend which has no notion of \"main\")\n *\n * Callers treat null as \"unknown — render — — and don't warn\".\n */\n commitsBehind(workspacePath: string, ref: string): Promise<number | null>;\n\n /**\n * Rebase the workspace onto `fromRef` (or the backend's tracked\n * base when undefined: `origin/HEAD` for git, `trunk()` for jj/sl).\n * Returns the resolved ref + replayed commits + conflicts list.\n *\n * Backend-specific behaviour:\n * - git: refuses on dirty WC (WorkspaceDirtyError); fetches first;\n * `git rebase <ref>`. On conflict, aborts the rebase and throws\n * WorkspaceConflictError so the operator resolves manually.\n * - jj: always-snapshotted, so dirty is never an issue. After\n * `jj rebase -d <ref>` the conflict-set is queried via\n * `jj log -r 'conflict()'`. Conflicts surface as\n * WorkspaceConflictError without an abort (jj's conflict markers\n * persist as commits; the operator resolves in-place).\n * - sl: similar to jj. `sl rebase -d <ref>`; conflicts via\n * `sl resolve -l`. On dirty WC sl errors itself; we wrap that\n * into WorkspaceDirtyError.\n * - none: throws WorkspaceVcsRequiredError unconditionally.\n *\n * Surfaced by fb_workspace_recycle_verb: dogfood between waves\n * needed `close → free → spawn` to refresh a worker against new\n * main; that killed the worker's LLM context. `refresh` updates\n * the on-disk dir without touching the agent or pane.\n */\n rebaseTo(workspacePath: string, fromRef?: string): Promise<RebaseResult>;\n\n /**\n * Cheap \"is the working copy clean?\" probe used by close-auto-free\n * (allow_mu_agent_close_without_discard). Definition: ZERO uncommitted\n * changes (no working-tree modifications, no staged changes, no\n * untracked-not-ignored files). Pure observation; no fetch, no commit.\n *\n * Backend-specific:\n * - git: empty `git status --porcelain` output.\n * - jj: jj is auto-snapshotted, so the @ commit IS the WC; clean\n * here means @ has no diff from its parent (empty `jj diff\n * -r @ --summary`). A description-only difference still\n * counts as clean.\n * - sl: empty `sl status` output.\n * - none: meaningless (cp -a snapshot has no notion of\n * \"committed\" vs \"uncommitted\"); always returns true so the\n * close-auto-free path treats every none-workspace as\n * eligible for silent free (no commits can be lost; the only\n * loss is local file edits, which the operator implicitly\n * accepts by closing the agent).\n *\n * Returns false on any backend command failure — be conservative\n * (we'd rather refuse a close than auto-free a workspace whose\n * cleanliness we couldn't verify).\n */\n isClean(workspacePath: string): Promise<boolean>;\n\n /**\n * List commits the workspace has on top of `baseRef`, oldest-first.\n * Used by `mu workspace commits` (fb_workspace_commits_verb) to\n * promote the dogfood-painful\n * cd $(mu workspace path X) && git log <base>..HEAD\n * incantation into a typed verb that knows the workspace's\n * parent_ref. The CommitSummary fields survive subjects/bodies with\n * embedded newlines (NUL-delimited record format on the wire).\n *\n * `none` throws WorkspaceVcsRequiredError. Returns `[]` when the\n * workspace is exactly at baseRef (no commits since fork). Throws\n * on backend command failure (unknown ref, missing repo).\n */\n commitsSinceBase(workspacePath: string, baseRef: string): Promise<CommitSummary[]>;\n\n /** Last N commits on the project root, newest-first. Used by the\n * TUI Commits card / popup. Unlike commitsSinceBase, this is NOT\n * a per-workspace since-fork query. */\n recentCommits(projectRoot: string, limit: number): Promise<CommitSummary[]>;\n\n /** Show one commit / change from the project root, capped for TUI\n * rendering. Backend-specific equivalent of `git show <sha>`. */\n showCommit(projectRoot: string, sha: string): Promise<ShowCommitResult>;\n\n /**\n * Return the list of dirty (uncommitted / unstaged / untracked-not-\n * ignored) paths in the workspace. Empty array = clean.\n *\n * Used by `mu workspace recreate` to refuse a free+create cycle on\n * a dirty workspace unless the operator passes `--force` (the lossy\n * escape hatch). Mirrors the dirty-check `rebaseTo` does internally.\n *\n * Backend semantics:\n * - git: `git status --porcelain` (working-tree + staged +\n * untracked-not-ignored, mirroring the rebaseTo path).\n * - sl: `sl status` parsed for non-empty output.\n * - jj: always-snapshotted, so no concept of \"dirty\" — returns [].\n * - none: cp -a snapshots have no VCS, so we can't decide \"dirty\";\n * returns [] so the caller doesn't refuse for an unanswerable\n * question.\n *\n * Throws on backend command failure (the operator should see a\n * real error, not a silent \"clean\").\n */\n listDirtyFiles(workspacePath: string): Promise<string[]>;\n}\n","// mu — jj VCS backend.\n\nimport { existsSync } from \"node:fs\";\nimport {\n ensureParent,\n parseNulRecords,\n probeVcsRoot,\n rmDirSync,\n run,\n runShow,\n saneLimit,\n} from \"./helpers.js\";\nimport { type FreeWorkspaceResult, type VcsBackend, WorkspaceConflictError } from \"./types.js\";\n\n// `jj workspace add --name <name> <path>` shares the .jj/repo store\n// while giving each agent its own working copy. Workspaces are named;\n// we use basename(workspacePath) which (per our on-disk layout) is the\n// agent name.\n//\n// Free is two-step: `jj workspace forget <name>` from the workspace\n// itself unregisters; then we rm the dir since jj leaves the files\n// behind.\n//\n// --commit semantics for jj: jj's working copy is always automatically\n// snapshotted, so \"commit\" is really \"capture the current change_id\n// as the result.\" Nothing is ever lost; jj keeps all operations in\n// its op log indefinitely. We additionally call `jj describe` to set\n// a description IF the current commit's description is empty so the\n// captured ref is human-discoverable.\n\nexport const jjBackend: VcsBackend = {\n name: \"jj\",\n\n async detect(projectRoot) {\n return probeVcsRoot(\"jj\", [\"root\"], projectRoot);\n },\n\n async createWorkspace(opts) {\n if (existsSync(opts.workspacePath)) {\n throw new Error(`vcs jj: workspacePath already exists: ${opts.workspacePath}`);\n }\n await ensureParent(opts.workspacePath);\n const name = jjWorkspaceName(opts.workspacePath);\n const args = [\"workspace\", \"add\", \"--name\", name];\n if (opts.parentRef) args.push(\"--revision\", opts.parentRef);\n args.push(opts.workspacePath);\n await run(\"jj\", args, opts.projectRoot);\n const commitId = await jjCommitId(opts.workspacePath);\n return { parentRef: commitId };\n },\n\n async freeWorkspace(opts) {\n if (!existsSync(opts.workspacePath)) {\n return { removed: false };\n }\n const name = jjWorkspaceName(opts.workspacePath);\n let committedRef: string | undefined;\n if (opts.commit) {\n const desc = await run(\n \"jj\",\n [\n \"log\",\n \"-r\",\n \"@\",\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"--template\",\n \"description\",\n ],\n opts.workspacePath,\n );\n if (desc.trim().length === 0) {\n await run(\"jj\", [\"describe\", \"-m\", \"mu workspace free auto-commit\"], opts.workspacePath);\n }\n committedRef = await jjCommitId(opts.workspacePath);\n }\n // `jj workspace forget` works from inside the workspace itself.\n // jj prints a hint about the working copy becoming orphaned;\n // we resolve that immediately by rm-ing the dir.\n await run(\"jj\", [\"workspace\", \"forget\", name], opts.workspacePath);\n rmDirSync(opts.workspacePath);\n const result: FreeWorkspaceResult = { removed: true };\n if (committedRef !== undefined) result.committedRef = committedRef;\n return result;\n },\n\n // jj working-copy clean: @ has no diff from its parent.\n // `jj diff -r @ --summary` prints one line per changed file; empty\n // stdout = clean. jj's auto-snapshotting means there's no separate\n // \"untracked\" bucket — every working-tree change is already in @.\n async isClean(workspacePath) {\n if (!existsSync(workspacePath)) return false;\n try {\n const out = await run(\n \"jj\",\n [\"diff\", \"-r\", \"@\", \"--summary\", \"--no-pager\", \"--color\", \"never\"],\n workspacePath,\n );\n return out.length === 0;\n } catch {\n return false;\n }\n },\n\n // Compute commits-behind via jj's `trunk()` revset, which resolves\n // to the project's configured trunk (default-branch heuristic).\n // Returns null when trunk() is unresolvable (e.g. fresh repo with\n // no configured trunk) or when the log call fails.\n //\n // Pure observation: NO `jj git fetch`.\n async commitsBehind(workspacePath, ref) {\n if (!existsSync(workspacePath)) return null;\n try {\n // `<ref>..trunk()` is the set of commits reachable from trunk\n // but not from ref — exactly the staleness number. Template `\"x\\n\"`\n // gives one line per commit, which we count.\n const out = await run(\n \"jj\",\n [\n \"log\",\n \"-r\",\n `${ref}..trunk()`,\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"--template\",\n '\"x\\\\n\"',\n ],\n workspacePath,\n );\n if (out.length === 0) return 0;\n return out.split(\"\\n\").filter((l) => l.length > 0).length;\n } catch {\n return null;\n }\n },\n\n // Rebase the workspace's @ onto `fromRef` (default = `trunk()`).\n // jj is always-snapshotted so dirty WC is never an issue — the auto-\n // snapshot becomes part of the rebase. After the rebase we query\n // `conflict()` to surface any commits that ended up conflicted; jj\n // doesn't auto-abort on conflicts (they materialise as commits with\n // conflict markers), so the workspace is left in a state the\n // operator can resolve in-place.\n async rebaseTo(workspacePath, fromRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs jj: workspace path missing: ${workspacePath}`);\n }\n const target = fromRef ?? \"trunk()\";\n // Snapshot the pre-rebase change_id so we can compute replayed\n // descriptions afterwards. `@` is the working-copy commit.\n const preRev = await run(\n \"jj\",\n [\"log\", \"-r\", \"@\", \"--no-graph\", \"--no-pager\", \"--color\", \"never\", \"--template\", \"change_id\"],\n workspacePath,\n );\n await run(\"jj\", [\"rebase\", \"-d\", target], workspacePath);\n // Replayed = descriptions of commits in (target..@), oldest-first.\n // Template prints `description ++ \"\\n\\x00\"` so multi-line descs\n // survive splitting; we keep the first non-empty line as subject.\n const replayedRaw = await run(\n \"jj\",\n [\n \"log\",\n \"-r\",\n `${target}..@`,\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"--reversed\",\n \"--template\",\n 'description.first_line() ++ \"\\\\n\"',\n ],\n workspacePath,\n ).catch(() => \"\");\n const replayed = replayedRaw\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n // Conflict surface: list change_ids of commits in the rebased\n // range that are conflicted. Empty = clean rebase.\n const conflictRaw = await run(\n \"jj\",\n [\n \"log\",\n \"-r\",\n `(${target}..@) & conflict()`,\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"--template\",\n 'change_id.short() ++ \"\\\\n\"',\n ],\n workspacePath,\n ).catch(() => \"\");\n const conflicts = conflictRaw\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n if (conflicts.length > 0) {\n throw new WorkspaceConflictError(workspacePath, target, conflicts);\n }\n // Use preRev so future-resolution of @ at call time is irrelevant.\n void preRev;\n return { fromRef: target, replayed, conflicts: [] };\n },\n\n // List jj commits in (baseRef..@), oldest-first. jj's templating\n // gives us per-field strings; we glue them with NUL field-separators\n // and \\x1e record-separators so multi-line descriptions/bodies\n // round-trip cleanly. The author timestamp template is\n // `author.timestamp().format(\"%Y-%m-%dT%H:%M:%S%:z\")` which is\n // ISO-8601 (matches git's --aiso strict / --aI).\n async commitsSinceBase(workspacePath, baseRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs jj: workspace path missing: ${workspacePath}`);\n }\n const out = await run(\n \"jj\",\n [\n \"log\",\n \"-r\",\n `${baseRef}..@`,\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"--reversed\",\n \"--template\",\n jjCommitSummaryTemplate,\n ],\n workspacePath,\n );\n return parseNulRecords(out);\n },\n\n async recentCommits(projectRoot, limit) {\n if (!existsSync(projectRoot)) {\n throw new Error(`vcs jj: project root missing: ${projectRoot}`);\n }\n const n = saneLimit(limit);\n if (n === 0) return [];\n const out = await run(\n \"jj\",\n [\n \"log\",\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"-r\",\n \"::@\",\n \"--limit\",\n String(n),\n \"--template\",\n jjCommitSummaryTemplate,\n ],\n projectRoot,\n );\n return parseNulRecords(out);\n },\n\n async showCommit(projectRoot, sha) {\n return runShow(\"jj\", [\"show\", sha, \"--color\", \"always\"], projectRoot);\n },\n\n // jj is always-snapshotted: there is no \"uncommitted\" state. The\n // working copy is itself a commit; the next snapshot folds any\n // edits in. Surface that by returning [] so `recreateWorkspace`\n // never refuses a jj workspace as \"dirty\".\n async listDirtyFiles(_workspacePath) {\n return [];\n },\n};\n\nconst jjCommitSummaryTemplate =\n 'commit_id ++ \"\\\\x00\" ++ description.first_line() ++ \"\\\\x00\" ++ description ++ \"\\\\x00\" ++ author.timestamp().format(\"%Y-%m-%dT%H:%M:%S%:z\") ++ \"\\\\x00\" ++ author.name() ++ \"\\\\x1e\"';\n\nfunction jjWorkspaceName(workspacePath: string): string {\n // basename of /foo/bar/worker-1 → worker-1\n return workspacePath.replace(/\\/+$/, \"\").split(\"/\").pop() ?? workspacePath;\n}\n\nasync function jjCommitId(workspacePath: string): Promise<string> {\n return run(\n \"jj\",\n [\"log\", \"-r\", \"@\", \"--no-graph\", \"--no-pager\", \"--color\", \"never\", \"--template\", \"commit_id\"],\n workspacePath,\n );\n}\n","// mu — non-VCS fallback backend.\n\nimport { existsSync } from \"node:fs\";\nimport { ensureParent, rmDirSync, run } from \"./helpers.js\";\nimport { type VcsBackend, WorkspaceVcsRequiredError } from \"./types.js\";\n\n// The fallback for projects that aren't under any VCS we recognise.\n// `cp -a` is heavy but correct; the workspace is a full snapshot.\n// Free deletes the snapshot. No commit semantics (no VCS to commit\n// against), so `--commit` is silently ignored.\n\nexport const noneBackend: VcsBackend = {\n name: \"none\",\n\n async detect(_projectRoot) {\n return true;\n },\n\n async createWorkspace(opts) {\n if (existsSync(opts.workspacePath)) {\n throw new Error(`vcs none: workspacePath already exists: ${opts.workspacePath}`);\n }\n await ensureParent(opts.workspacePath);\n // `cp -a` is GNU/BSD-portable for \"preserve everything, recursive\".\n await run(\"cp\", [\"-a\", `${opts.projectRoot}/.`, opts.workspacePath]);\n return { parentRef: null };\n },\n\n async freeWorkspace(opts) {\n const removed = rmDirSync(opts.workspacePath);\n return { removed };\n },\n\n // The `none` backend has no VCS to compare against — there's no\n // notion of \"main\" for a `cp -a` snapshot. Always null.\n async commitsBehind(_workspacePath, _ref) {\n return null;\n },\n\n // none has no notion of clean — a cp -a snapshot doesn't track\n // committed vs uncommitted state. Returning true makes the\n // close-auto-free path silently free a none-workspace (consistent\n // with the fact that there are no commits to lose).\n async isClean(_workspacePath) {\n return true;\n },\n\n // none has no upstream to rebase onto. Throw a typed error so the\n // CLI's handle() maps it to exit 4 with a clean Next: hint.\n async rebaseTo(workspacePath, _fromRef) {\n throw new WorkspaceVcsRequiredError(\"refresh\", workspacePath);\n },\n\n // none has no notion of a fork point either — a cp -a snapshot\n // doesn't track history. Same typed error as rebaseTo.\n async commitsSinceBase(workspacePath, _baseRef) {\n throw new WorkspaceVcsRequiredError(\"commits\", workspacePath);\n },\n\n async recentCommits(_projectRoot, _limit) {\n return [];\n },\n\n async showCommit(_projectRoot, _sha) {\n return { text: \"\", truncated: false, error: \"vcs none: no commits to show\" };\n },\n\n // No VCS → nothing to compare against; \"dirty\" is unanswerable.\n // Caller (`recreateWorkspace`) treats [] as \"clean\" and proceeds.\n async listDirtyFiles(_workspacePath) {\n return [];\n },\n};\n","// mu — Sapling VCS backend.\n\nimport { existsSync } from \"node:fs\";\nimport {\n ensureParent,\n isSaplingDotdir,\n parseNulRecords,\n probeVcsRootPath,\n rmDirSync,\n run,\n runShow,\n saneLimit,\n} from \"./helpers.js\";\nimport {\n type FreeWorkspaceResult,\n type VcsBackend,\n WorkspaceConflictError,\n WorkspaceDirtyError,\n} from \"./types.js\";\n\n// `sl worktree` exists in Sapling but only for EdenFS-backed repos.\n// For portability we use `sl clone` instead, which works on any\n// sapling install. The trade-off is heavier (history copy) vs lighter\n// (shared store), but the workspace is fully isolated either way —\n// which is what we care about. EdenFS-specific worktree optimization\n// can layer on later if anyone hits the friction.\n//\n// Free is just rm -rf: sapling has no formal \"unclone\" because each\n// clone is a self-contained repo.\n\nexport const slBackend: VcsBackend = {\n name: \"sl\",\n\n async detect(projectRoot) {\n const root = await probeVcsRootPath(\"sl\", [\"root\"], projectRoot);\n if (root === null) return false;\n // Meta's Sapling build can transparently operate in plain git repos\n // by creating `.git/sl`. That should not beat git in BACKENDS order.\n // `--dotdir` keeps true sl / hg-compat repos (`.sl` / `.hg`) distinct.\n const dotdir = await probeVcsRootPath(\"sl\", [\"root\", \"--dotdir\"], projectRoot);\n return dotdir !== null && isSaplingDotdir(dotdir);\n },\n\n async createWorkspace(opts) {\n if (existsSync(opts.workspacePath)) {\n throw new Error(`vcs sl: workspacePath already exists: ${opts.workspacePath}`);\n }\n await ensureParent(opts.workspacePath);\n const args = [\"clone\"];\n if (opts.parentRef) args.push(\"-r\", opts.parentRef);\n args.push(opts.projectRoot, opts.workspacePath);\n await run(\"sl\", args);\n const commitId = await slCommitId(opts.workspacePath);\n return { parentRef: commitId };\n },\n\n async freeWorkspace(opts) {\n if (!existsSync(opts.workspacePath)) {\n return { removed: false };\n }\n let committedRef: string | undefined;\n if (opts.commit && (await slIsDirty(opts.workspacePath))) {\n // sl commit -A: stage all (including untracked), commit. Exits\n // non-zero if nothing changed, but we just guarded above.\n await run(\n \"sl\",\n [\n \"--config\",\n \"ui.username=mu <mu@local>\",\n \"commit\",\n \"-A\",\n \"-m\",\n \"mu workspace free auto-commit\",\n ],\n opts.workspacePath,\n );\n committedRef = await slCommitId(opts.workspacePath);\n }\n rmDirSync(opts.workspacePath);\n const result: FreeWorkspaceResult = { removed: true };\n if (committedRef !== undefined) result.committedRef = committedRef;\n return result;\n },\n\n // sl working-copy clean: empty `sl status` output. Same shape as\n // listSlDirtyFiles below but inlined to keep the failure-mode\n // boundary tight (any throw → not clean).\n async isClean(workspacePath) {\n if (!existsSync(workspacePath)) return false;\n try {\n const out = await run(\"sl\", [\"status\"], workspacePath);\n return out.length === 0;\n } catch {\n return false;\n }\n },\n\n // Same shape as the jj impl: count commits in trunk() not reachable\n // from ref. Sapling's revset language is close enough to jj's that\n // the same idiom works. Returns null when trunk() is unresolvable\n // (fresh repo, missing remote bookmark, etc.) or the log fails.\n //\n // Pure observation: NO `sl pull`.\n async commitsBehind(workspacePath, ref) {\n if (!existsSync(workspacePath)) return null;\n try {\n const out = await run(\n \"sl\",\n [\"log\", \"-r\", `${ref}::trunk() - ${ref}`, \"--template\", \"x\\\\n\"],\n workspacePath,\n );\n if (out.length === 0) return 0;\n return out.split(\"\\n\").filter((l) => l.length > 0).length;\n } catch {\n return null;\n }\n },\n\n // Rebase the active draft chain onto `fromRef` (default = `trunk()`).\n // Sapling refuses on dirty WC by default — we pre-check and convert\n // its error into the typed WorkspaceDirtyError. Conflict surface\n // post-rebase via `sl resolve --list --tool=internal:dumpjson` is\n // brittle across versions, so we use the textual `sl resolve --list`\n // output and look for the U-prefixed lines (unresolved).\n async rebaseTo(workspacePath, fromRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs sl: workspace path missing: ${workspacePath}`);\n }\n const target = fromRef ?? \"trunk()\";\n const dirtyFiles = await listSlDirtyFiles(workspacePath);\n if (dirtyFiles.length > 0) {\n throw new WorkspaceDirtyError(workspacePath, dirtyFiles);\n }\n await run(\n \"sl\",\n [\"--config\", \"ui.username=mu <mu@local>\", \"rebase\", \"-d\", target],\n workspacePath,\n ).catch(() => {\n // Rebase failure is acceptable here — the conflict-listing call\n // below will tell us what happened. Bare exception loss is OK\n // since `sl resolve` is the source of truth on conflicts.\n });\n const conflicts = await listSlUnresolved(workspacePath);\n if (conflicts.length > 0) {\n // Best-effort abort so the workspace returns to a clean state\n // — mirrors the git impl's never-leave-half-rebased policy.\n await run(\"sl\", [\"rebase\", \"--abort\"], workspacePath).catch(() => {});\n throw new WorkspaceConflictError(workspacePath, target, conflicts);\n }\n // Replayed = log of `target..` post-rebase, oldest-first. Single-\n // line subjects via `{desc|firstline}`. Empty when nothing replayed.\n const replayedRaw = await run(\n \"sl\",\n [\"log\", \"-r\", `${target}::. - ${target}`, \"--template\", \"{desc|firstline}\\\\n\"],\n workspacePath,\n ).catch(() => \"\");\n const replayed = replayedRaw\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n return { fromRef: target, replayed, conflicts: [] };\n },\n\n // List sl commits in (baseRef..., minus baseRef itself), oldest-\n // first. Same NUL-field / \\x1e-record format as the jj impl;\n // sl's templating uses {field} braces (not jj's `++` operator) but\n // the field set is equivalent ({node}, {desc|firstline}, {desc},\n // {date|isodate}).\n async commitsSinceBase(workspacePath, baseRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs sl: workspace path missing: ${workspacePath}`);\n }\n const out = await run(\n \"sl\",\n [\"log\", \"-r\", `${baseRef}::. - ${baseRef}`, \"--template\", slCommitSummaryTemplate],\n workspacePath,\n );\n // sl emits oldest-last by default; reverse to oldest-first to match\n // the git/jj contract.\n return parseNulRecords(out).reverse();\n },\n\n async recentCommits(projectRoot, limit) {\n if (!existsSync(projectRoot)) {\n throw new Error(`vcs sl: project root missing: ${projectRoot}`);\n }\n const n = saneLimit(limit);\n if (n === 0) return [];\n const out = await run(\n \"sl\",\n [\"log\", \"-l\", String(n), \"--template\", slCommitSummaryTemplate],\n projectRoot,\n );\n return parseNulRecords(out);\n },\n\n async showCommit(projectRoot, sha) {\n return runShow(\"sl\", [\"show\", sha, \"--color=always\"], projectRoot);\n },\n\n async listDirtyFiles(workspacePath) {\n if (!existsSync(workspacePath)) return [];\n return listSlDirtyFiles(workspacePath);\n },\n};\n\n/**\n * List dirty paths for a sapling workspace. `sl status` prints one\n * line per changed file: `<status> <path>`. Empty = clean.\n */\nasync function listSlDirtyFiles(workspacePath: string): Promise<string[]> {\n const out = await run(\"sl\", [\"status\"], workspacePath);\n if (out.length === 0) return [];\n return out\n .split(\"\\n\")\n .filter((l) => l.length > 0)\n .map((l) => l.slice(2));\n}\n\n/**\n * List unresolved (conflicting) paths after an `sl rebase`. `sl resolve\n * --list` prints `<U|R> <path>` per file; U = unresolved.\n */\nasync function listSlUnresolved(workspacePath: string): Promise<string[]> {\n try {\n const out = await run(\"sl\", [\"resolve\", \"--list\"], workspacePath);\n if (out.length === 0) return [];\n return out\n .split(\"\\n\")\n .filter((l) => l.startsWith(\"U \"))\n .map((l) => l.slice(2));\n } catch {\n return [];\n }\n}\n\nconst slCommitSummaryTemplate =\n \"{node}\\\\0{desc|firstline}\\\\0{desc}\\\\0{date|isodatesec}\\\\0{author|user}\\\\x1e\";\n\nasync function slCommitId(workspacePath: string): Promise<string> {\n return run(\"sl\", [\"log\", \"-r\", \".\", \"--template\", \"{node}\"], workspacePath);\n}\n\nasync function slIsDirty(workspacePath: string): Promise<boolean> {\n // sl status prints one line per changed file; empty stdout = clean.\n const out = await run(\"sl\", [\"status\"], workspacePath);\n return out.length > 0;\n}\n","// mu — VCS backend dispatcher.\n\nimport { gitBackend } from \"./git.js\";\nimport { jjBackend } from \"./jj.js\";\nimport { noneBackend } from \"./none.js\";\nimport { slBackend } from \"./sl.js\";\nimport type { VcsBackend, VcsBackendName } from \"./types.js\";\n\nexport { gitBackend } from \"./git.js\";\nexport { jjBackend } from \"./jj.js\";\nexport { noneBackend } from \"./none.js\";\nexport { slBackend } from \"./sl.js\";\nexport {\n SHOW_COMMIT_MAX_CHARS,\n type CommitSummary,\n type CreateWorkspaceOptions,\n type CreateWorkspaceResult,\n type FreeWorkspaceOptions,\n type FreeWorkspaceResult,\n type RebaseResult,\n type ShowCommitResult,\n type VcsBackend,\n type VcsBackendName,\n WorkspaceConflictError,\n WorkspaceDirtyError,\n WorkspaceVcsRequiredError,\n} from \"./types.js\";\n\n/**\n * Detection precedence: jj > sl > git > none. The first backend whose\n * detect() returns true wins. `none` is always last. Detection shells\n * out to each VCS's canonical root probe (`jj root`, `sl root`, `git\n * rev-parse --show-toplevel`) so worktrees and gitdir-pointer files are\n * handled by the owning tool instead of a brittle marker-dir heuristic.\n */\nconst BACKENDS: readonly VcsBackend[] = [jjBackend, slBackend, gitBackend, noneBackend];\n\n/** Return the backend that should handle projectRoot. Walks BACKENDS\n * in precedence order; never returns undefined because noneBackend\n * always claims. */\nexport async function detectBackend(projectRoot: string): Promise<VcsBackend> {\n for (const backend of BACKENDS) {\n if (await backend.detect(projectRoot)) return backend;\n }\n return noneBackend;\n}\n\n/** Look up a backend by name. Throws on unknown name. Used by\n * `mu workspace create --backend ...` to honour an explicit override. */\nexport function backendByName(name: VcsBackendName): VcsBackend {\n for (const backend of BACKENDS) {\n if (backend.name === name) return backend;\n }\n throw new Error(`unknown vcs backend: ${name}`);\n}\n","// mu — workspace staleness and dirty decorators.\n\nimport type { Db } from \"../db.js\";\nimport { isWorkspaceStale } from \"../staleness.js\";\nimport { backendByName } from \"../vcs.js\";\nimport type { WorkspaceRow, WorkspaceStaleness } from \"./core.js\";\nimport { getWorkspaceForAgent } from \"./crud.js\";\n\nconst DECORATE_CONCURRENCY = 4;\n\nexport async function getWorkspaceStaleness(\n db: Db,\n agentName: string,\n workstreamName: string,\n): Promise<WorkspaceStaleness | null> {\n const row = getWorkspaceForAgent(db, agentName, workstreamName);\n if (row === undefined) return null;\n const [decorated] = await decorateWithStaleness([row]);\n const commitsBehindMain = decorated?.commitsBehindMain ?? null;\n return {\n agentName,\n workstreamName,\n commitsBehindMain,\n isStale: isWorkspaceStale(commitsBehindMain),\n };\n}\n\n/**\n * Decorate each row with `commitsBehindMain` by asking the row's backend\n * how far the parent_ref is behind the project's default branch HEAD.\n * Cheap, pure observation: NO automatic `git fetch` / `jj git fetch` /\n * `sl pull`. The number is as fresh as the workspace's local refs cache.\n *\n * Returns a NEW array; does not mutate the input. Rows whose parent_ref\n * is missing, or whose backend's commitsBehind throws / returns null,\n * get `commitsBehindMain: null`.\n */\nexport async function decorateWithStaleness(\n rows: readonly WorkspaceRow[],\n): Promise<WorkspaceRow[]> {\n const cache = new Map<string, Promise<number | null>>();\n const fetchBehind = (r: WorkspaceRow): Promise<number | null> => {\n const parentRef = r.parentRef;\n if (parentRef === null) return Promise.resolve(null);\n const key = `${r.backend}\\x00${parentRef}`;\n const cached = cache.get(key);\n if (cached !== undefined) return cached;\n const p = (async (): Promise<number | null> => {\n try {\n const backend = backendByName(r.backend);\n return await backend.commitsBehind(r.path, parentRef);\n } catch {\n return null;\n }\n })();\n cache.set(key, p);\n return p;\n };\n return mapWithConcurrency(rows, DECORATE_CONCURRENCY, async (r) => ({\n ...r,\n commitsBehindMain: await fetchBehind(r),\n }));\n}\n\n/**\n * Decorate every row with a `dirty` marker — true when the backend's\n * `listDirtyFiles` reports any uncommitted / unstaged / untracked-not-\n * ignored files; false when clean; null on backend-command failure.\n *\n * Returns a NEW array; does not mutate the input.\n */\nexport async function decorateWithDirty(rows: readonly WorkspaceRow[]): Promise<WorkspaceRow[]> {\n return mapWithConcurrency(rows, DECORATE_CONCURRENCY, async (r) => {\n let dirty: boolean | null;\n try {\n const backend = backendByName(r.backend);\n const files = await backend.listDirtyFiles(r.path);\n dirty = files.length > 0;\n } catch {\n dirty = null;\n }\n return { ...r, dirty };\n });\n}\n\n/**\n * Tiny p-limit-style helper. Keeps at most `limit` callbacks in flight\n * at once and preserves input order in the result. Stays in this file\n * because it has exactly two local decorator callers; promote out only\n * when a second cluster needs it.\n */\nasync function mapWithConcurrency<T, R>(\n items: readonly T[],\n limit: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let next = 0;\n const worker = async (): Promise<void> => {\n while (true) {\n const i = next++;\n if (i >= items.length) return;\n const item = items[i] as T;\n results[i] = await fn(item, i);\n }\n };\n const workerCount = Math.min(Math.max(1, limit), items.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n return results;\n}\n","// mu — workspace orphan directory detection.\n\nimport { readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type Db, defaultStateDir, tryResolveWorkstreamId } from \"../db.js\";\nimport { workspacesRoot } from \"./core.js\";\nimport { listWorkspaces } from \"./crud.js\";\n\nexport interface WorkspaceOrphan {\n /** The on-disk dir name (the agent name it WOULD be for, if mu had\n * registered it). */\n agentName: string;\n /** Workstream the dir is filed under. */\n workstreamName: string;\n /** Absolute path to the orphan dir. */\n path: string;\n}\n\n/**\n * Like WorkspaceOrphan but additionally flags whether the parent\n * workstream itself is gone (no row in `workstreams`). Returned by\n * listAllOrphanWorkspaces; the per-workstream listWorkspaceOrphans\n * doesn't carry this since by construction it only runs against an\n * existing workstream.\n */\nexport interface StrandedWorkspaceOrphan extends WorkspaceOrphan {\n /** True iff the parent workstream has no DB row (the dir was left\n * behind by a `mu workstream destroy` or a manual DELETE). */\n stranded: boolean;\n}\n\n/**\n * Scan `<state-dir>/workspaces/<workstream>/` for directories that\n * have no row in `vcs_workspaces`.\n *\n * Returns `[]` when the workstream's workspaces dir doesn't exist,\n * or when every dir on disk has a corresponding DB row. Filesystem\n * read is best-effort: a missing/inaccessible dir returns `[]`.\n */\nexport function listWorkspaceOrphans(db: Db, workstream: string): WorkspaceOrphan[] {\n const root = workspacesRoot(workstream);\n let dirs: string[];\n try {\n dirs = readdirSync(root, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n } catch {\n return [];\n }\n const registered = new Set(listWorkspaces(db, workstream).map((w) => w.path));\n const orphans: WorkspaceOrphan[] = [];\n for (const agentDir of dirs) {\n const fullPath = join(root, agentDir);\n if (!registered.has(fullPath)) {\n orphans.push({ agentName: agentDir, workstreamName: workstream, path: fullPath });\n }\n }\n return orphans;\n}\n\n/**\n * Cross-workstream variant of listWorkspaceOrphans. Reads\n * `<state-dir>/workspaces/`, recurses one level (per-ws subdir →\n * per-agent subdir), and surfaces every dir with no row in\n * `vcs_workspaces`.\n */\nexport function listAllOrphanWorkspaces(db: Db): StrandedWorkspaceOrphan[] {\n const root = join(defaultStateDir(), \"workspaces\");\n let wsDirs: string[];\n try {\n wsDirs = readdirSync(root, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n } catch {\n return [];\n }\n const registered = new Set(listWorkspaces(db).map((w) => w.path));\n const orphans: StrandedWorkspaceOrphan[] = [];\n for (const wsName of wsDirs) {\n const wsRoot = join(root, wsName);\n let agentDirs: string[];\n try {\n agentDirs = readdirSync(wsRoot, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n } catch {\n continue;\n }\n const stranded = tryResolveWorkstreamId(db, wsName) === null;\n for (const agentDir of agentDirs) {\n const fullPath = join(wsRoot, agentDir);\n if (!registered.has(fullPath)) {\n orphans.push({\n agentName: agentDir,\n workstreamName: wsName,\n path: fullPath,\n stranded,\n });\n }\n }\n }\n return orphans;\n}\n","// mu — workspace recreate verb.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport {\n type VcsBackend,\n type VcsBackendName,\n WorkspaceDirtyError,\n backendByName,\n} from \"../vcs.js\";\nimport { WorkspaceNotFoundError, type WorkspaceRow } from \"./core.js\";\nimport {\n type CreateWorkspaceOptions,\n createWorkspace,\n freeWorkspace,\n getWorkspaceForAgent,\n} from \"./crud.js\";\n\nexport interface RecreateWorkspaceOptions {\n /** Same as createWorkspace; defaults to cwd. */\n projectRoot?: string;\n /** Same as createWorkspace; if undefined the previous backend is\n * reused (auto-detection re-runs only when --backend was passed). */\n backend?: VcsBackendName | VcsBackend;\n /** Same as createWorkspace; if undefined the new workspace bases on\n * the backend's current head (for git/jj/sl: the project's main),\n * which is the whole point of the verb. */\n parentRef?: string;\n /** When true, skip the dirty-check refusal and discard any\n * uncommitted changes in the existing workspace. The lossy escape\n * hatch — mirrors the implicit semantics of `mu workspace free`\n * without --commit. */\n force?: boolean;\n}\n\nexport interface RecreateWorkspaceResult {\n /** The freshly-created workspace row (the previous row is already\n * gone by the time we return). */\n workspace: WorkspaceRow;\n /** parent_ref of the WORKSPACE BEFORE recreate, so callers (and the\n * CLI's success message) can show \"bumped from <old> -> <new>\". */\n previousParentRef: string | null;\n}\n\n/**\n * Free + create in one atomic-ish verb. Between waves the operator\n * wants the SAME agent name with a fresh workspace pinned to current\n * main; doing `free` then `create` manually was the dogfood-painful\n * pattern.\n */\nexport async function recreateWorkspace(\n db: Db,\n agent: string,\n opts: RecreateWorkspaceOptions & { workstream: string },\n): Promise<RecreateWorkspaceResult> {\n const row = getWorkspaceForAgent(db, agent, opts.workstream);\n if (!row) throw new WorkspaceNotFoundError(agent);\n\n // Dirty-check the OLD workspace before we destroy it. Same\n // safety semantics as `free` (without --commit): refuse rather\n // than silently lose uncommitted edits. `--force` is the lossy\n // escape hatch.\n if (opts.force !== true) {\n const oldBackend = backendByName(row.backend);\n const dirty = await oldBackend.listDirtyFiles(row.path);\n if (dirty.length > 0) {\n throw new WorkspaceDirtyError(row.path, dirty, \"recreate\");\n }\n }\n\n // One snapshot for the whole free+create cycle; one event line at\n // the end. The internal `_suppressEvent` flag on free/create is\n // private to this module — not part of the SDK contract.\n captureSnapshot(db, `workspace recreate ${agent}`, row.workstreamName);\n\n await freeWorkspace(db, agent, {\n workstream: opts.workstream,\n commit: false,\n _suppressEvent: true,\n });\n\n const createOpts: CreateWorkspaceOptions = {\n agent,\n workstream: opts.workstream,\n _suppressEvent: true,\n };\n if (opts.projectRoot !== undefined) createOpts.projectRoot = opts.projectRoot;\n // Default to the prior backend so a between-wave refresh stays on\n // the same VCS regardless of cwd. Explicit override wins.\n if (opts.backend !== undefined) {\n createOpts.backend = opts.backend;\n } else {\n createOpts.backend = row.backend;\n }\n if (opts.parentRef !== undefined) createOpts.parentRef = opts.parentRef;\n\n const fresh = await createWorkspace(db, createOpts);\n\n emitEvent(\n db,\n opts.workstream,\n `workspace recreate ${agent} (backend=${fresh.backend}, path=${fresh.path}, old_parent=${row.parentRef ? row.parentRef.slice(0, 12) : \"—\"}, new_parent=${fresh.parentRef ? fresh.parentRef.slice(0, 12) : \"—\"})`,\n );\n\n return { workspace: fresh, previousParentRef: row.parentRef };\n}\n","// mu — task read/query primitives.\n\nimport type { Db } from \"../db.js\";\nimport { tryResolveWorkstreamId } from \"../db.js\";\nimport { lastClaimEventAt } from \"../logs.js\";\nimport {\n type RawTaskNoteRow,\n type RawTaskRow,\n SELECT_NOTE_COLS,\n SELECT_TASK_COLS,\n TASK_FROM_JOIN,\n type TaskNoteRow,\n type TaskRow,\n noteFromDb,\n rowFromDb,\n taskIdFor,\n} from \"./core.js\";\nimport type { TaskStatus } from \"./status.js\";\n\nexport function getTask(db: Db, localId: string, workstream: string): TaskRow | undefined {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return undefined;\n const row = db\n .prepare(\n `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} WHERE t.workstream_id = ? AND t.local_id = ?`,\n )\n .get(wsId, localId) as RawTaskRow | undefined;\n return row ? rowFromDb(row) : undefined;\n}\n\n/**\n * List tasks. With no `workstream` arg returns every row — used by `mu sql`\n * and by tests; CLI surfaces always pass a workstream so users only see\n * their own.\n */\nexport interface ListTasksOptions {\n /** Filter to one or more lifecycle statuses. Omitted = all statuses. */\n status?: TaskStatus | readonly TaskStatus[];\n}\n\nexport function listTasks(db: Db, workstream?: string, opts: ListTasksOptions = {}): TaskRow[] {\n const statuses =\n opts.status === undefined\n ? undefined\n : Array.isArray(opts.status)\n ? (opts.status as TaskStatus[])\n : [opts.status as TaskStatus];\n\n const where: string[] = [];\n const params: unknown[] = [];\n if (workstream !== undefined) {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n where.push(\"t.workstream_id = ?\");\n params.push(wsId);\n }\n if (statuses !== undefined) {\n where.push(`t.status IN (${statuses.map(() => \"?\").join(\", \")})`);\n params.push(...statuses);\n }\n const sql =\n where.length === 0\n ? `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} ORDER BY t.local_id`\n : `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} WHERE ${where.join(\" AND \")} ORDER BY t.local_id`;\n const rows = db.prepare(sql).all(...params) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\n// The three views (ready, blocked, goals) project tasks.* directly,\n// so they expose v5 columns (id, workstream_id, owner_id). We wrap\n// them with the same JOINs as TASK_FROM_JOIN to translate back to the\n// operator-facing TaskRow shape (workstream + owner as TEXT names).\nconst VIEW_FROM_JOIN = (view: string) => `\n FROM ${view} v\n JOIN workstreams ws ON ws.id = v.workstream_id\n LEFT JOIN agents ag ON ag.id = v.owner_id\n`;\nconst SELECT_VIEW_COLS = `\n v.id AS id,\n v.local_id AS local_id,\n ws.name AS workstream,\n v.title AS title,\n v.status AS status,\n v.impact AS impact,\n v.effort_days AS effort_days,\n ag.name AS owner,\n v.created_at AS created_at,\n v.updated_at AS updated_at\n`;\n\n/** Options for listReady. The optional `statuses` filter composes\n * on top of the `ready` view (which itself constrains to\n * `status='OPEN'`); passing only OPEN is identical to today's no-\n * filter shape, passing only non-OPEN values returns []. Exists so\n * `mu task next --status` can mirror the multi-status flag shape\n * shipped on `mu task list` (task_list_multi_status_union). */\nexport interface ListReadyOptions {\n status?: TaskStatus | readonly TaskStatus[];\n}\n\nexport function listReady(db: Db, workstream: string, opts: ListReadyOptions = {}): TaskRow[] {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const statuses =\n opts.status === undefined\n ? undefined\n : Array.isArray(opts.status)\n ? (opts.status as TaskStatus[])\n : [opts.status as TaskStatus];\n const where: string[] = [\"v.workstream_id = ?\"];\n const params: unknown[] = [wsId];\n if (statuses !== undefined && statuses.length > 0) {\n where.push(`v.status IN (${statuses.map(() => \"?\").join(\", \")})`);\n params.push(...statuses);\n }\n const rows = db\n .prepare(\n `SELECT ${SELECT_VIEW_COLS} ${VIEW_FROM_JOIN(\"ready\")} WHERE ${where.join(\" AND \")} ORDER BY v.local_id`,\n )\n .all(...params) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\nexport function listBlocked(db: Db, workstream: string): TaskRow[] {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(\n `SELECT ${SELECT_VIEW_COLS} ${VIEW_FROM_JOIN(\"blocked\")} WHERE v.workstream_id = ? ORDER BY v.local_id`,\n )\n .all(wsId) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\nexport function listGoals(db: Db, workstream: string): TaskRow[] {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(\n `SELECT ${SELECT_VIEW_COLS} ${VIEW_FROM_JOIN(\"goals\")} WHERE v.workstream_id = ? ORDER BY v.local_id`,\n )\n .all(wsId) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\n/** All IN_PROGRESS tasks in a workstream, most-recently-touched first.\n * Used by `mu state` to populate its in-progress slice; exposed as a\n * named SDK helper so CLI renderers don't re-derive the row-shape\n * conversion (review_code_raw_task_state_duplicate). */\nexport function listInProgress(db: Db, workstream: string): TaskRow[] {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(\n `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} WHERE t.workstream_id = ? AND t.status = 'IN_PROGRESS' ORDER BY t.updated_at DESC`,\n )\n .all(wsId) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\n/** Most-recently-closed tasks in a workstream, newest first, capped at\n * `limit` (default 5). Used by `mu state` for its 'recent closed'\n * slice; exposed as a named SDK helper so the CLI no longer needs the\n * raw-row type that was duplicating RawTaskRow\n * (review_code_raw_task_state_duplicate). */\nexport function listRecentClosed(db: Db, workstream: string, limit = 5): TaskRow[] {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(\n `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} WHERE t.workstream_id = ? AND t.status = 'CLOSED' ORDER BY t.updated_at DESC LIMIT ?`,\n )\n .all(wsId, limit) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\n/** Optional filter knobs for `listNotes`. Default-everything-undefined\n * preserves the historical \"return every note, oldest-first\" shape so\n * every existing caller (cmdTaskShow's notes block, exporting.ts's\n * bucket renderer, agents.test.ts) keeps working unchanged.\n *\n * Filters compose multiplicatively when both apply (`since` AND\n * `tail`): the timestamp filter is applied first, then `tail` slices\n * the last N of what survived. The CLI surface (`mu task notes\n * --tail / --since / --since-claim`) lives in src/cli/tasks/edit.ts;\n * the mutex between `--since` and `--since-claim` is a CLI concern,\n * not enforced here — if both arrive at the SDK, `since` wins (it's\n * the explicit one) and `sinceClaim` is ignored. The auto-resolve\n * for `sinceClaim` (look up the most recent `task claim` event in\n * agent_logs) happens here so the SDK is self-contained for scripted\n * callers. */\nexport interface ListNotesOptions {\n /** Print only the last N notes (after any timestamp filter). Must\n * be a positive integer; a value of 0 returns no rows but is not\n * an error here — CLI-side validation rejects `--tail 0`. */\n tail?: number;\n /** ISO-8601 cutoff: only notes with `created_at > since` survive.\n * Comparison is lexicographic on the ISO string (matches the way\n * the rest of the codebase compares ISO timestamps). */\n since?: string;\n /** When true and `since` is unset, look up the `created_at` of the\n * most recent `task claim` event for this task and use it as the\n * cutoff. Falls back to no filter when no claim event exists\n * (equivalent to `--since-beginning`). */\n sinceClaim?: boolean;\n}\n\n/** List notes for a task. Operator-facing local_id; resolves to the\n * surrogate task id via taskIdFor (with optional workstream scope).\n *\n * Optional filters: see {@link ListNotesOptions}. Default behaviour\n * (no opts) is unchanged — every note, oldest-first. */\nexport function listNotes(\n db: Db,\n taskLocalId: string,\n workstream: string,\n opts: ListNotesOptions = {},\n): TaskNoteRow[] {\n const taskId = taskIdFor(db, taskLocalId, workstream);\n if (taskId === null) return [];\n // Resolve the cutoff once: explicit `since` wins; otherwise\n // `sinceClaim` resolves via lastClaimEventAt (null → no filter).\n let cutoff: string | undefined = opts.since;\n if (cutoff === undefined && opts.sinceClaim === true) {\n const at = lastClaimEventAt(db, workstream, taskLocalId);\n if (at !== null) cutoff = at;\n }\n const rows =\n cutoff !== undefined\n ? (db\n .prepare(\n `SELECT ${SELECT_NOTE_COLS} FROM task_notes n JOIN tasks t ON t.id = n.task_id\n WHERE n.task_id = ? AND n.created_at > ? ORDER BY n.id`,\n )\n .all(taskId, cutoff) as RawTaskNoteRow[])\n : (db\n .prepare(\n `SELECT ${SELECT_NOTE_COLS} FROM task_notes n JOIN tasks t ON t.id = n.task_id\n WHERE n.task_id = ? ORDER BY n.id`,\n )\n .all(taskId) as RawTaskNoteRow[]);\n const mapped = rows.map(noteFromDb);\n if (opts.tail !== undefined && opts.tail >= 0) {\n return opts.tail === 0 ? [] : mapped.slice(-opts.tail);\n }\n return mapped;\n}\n\n/**\n * All tasks currently owned by `agent` in a given workstream\n * (v5: agents.name is per-workstream unique). Sorted by local_id.\n *\n * Defaults to **excluding CLOSED** since the verb's purpose is \"what\n * is X currently working on?\" and a closed task is no longer being\n * worked on. closeTask intentionally preserves `owner` as a\n * historical record (so audit/notes can attribute decisions); pass\n * `{ includeClosed: true }` to surface that history.\n */\nexport function listTasksByOwner(\n db: Db,\n workstream: string,\n owner: string,\n opts: { includeClosed?: boolean } = {},\n): TaskRow[] {\n // 'Live work' = not in any terminal-or-parked state. CLOSED is the\n // obvious one; REJECTED and DEFERRED are also off the agent's plate\n // (the user has decided 'won't do' or 'not now'). includeClosed\n // re-includes ALL of those so historical attribution is recoverable.\n // Filter on the joined ag.name so the operator-facing owner string\n // still drives the lookup; FK is now via owner_id.\n const filter = opts.includeClosed ? \"\" : \"AND t.status NOT IN ('CLOSED', 'REJECTED', 'DEFERRED')\";\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const sql = `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN}\n WHERE ag.name = ? AND t.workstream_id = ? ${filter}\n ORDER BY t.local_id`;\n return (db.prepare(sql).all(owner, wsId) as RawTaskRow[]).map(rowFromDb);\n}\n\n/**\n * Cross-workstream variant of `listTasksByOwner`. Returns tasks owned\n * by ANY agent of the given name across every workstream. Used by\n * `mu task owned-by --all` for the genuine cross-workstream view\n * (audit / dashboards). The bare name is the join key, so two\n * distinct same-named agents in different workstreams contribute\n * their tasks to the same result list.\n */\nexport function listTasksByOwnerCrossWorkstream(\n db: Db,\n owner: string,\n opts: { includeClosed?: boolean } = {},\n): TaskRow[] {\n const filter = opts.includeClosed ? \"\" : \"AND t.status NOT IN ('CLOSED', 'REJECTED', 'DEFERRED')\";\n const sql = `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN}\n WHERE ag.name = ? ${filter}\n ORDER BY ws.name, t.local_id`;\n return (db.prepare(sql).all(owner) as RawTaskRow[]).map(rowFromDb);\n}\n\nexport interface SearchTasksOptions {\n /** Restrict to one workstream; undefined = search across all. */\n workstream?: string;\n /** Also search `task_notes.content` (default false: titles + ids only). */\n includeNotes?: boolean;\n}\n\n/**\n * Substring search on task `title` and `local_id`, case-insensitive.\n * With `includeNotes: true` also searches `task_notes.content`. The\n * pattern is wrapped in `%...%` automatically so callers don't need\n * SQL LIKE knowledge — for explicit globs (or regex), use `mu sql`.\n */\nexport function searchTasks(db: Db, pattern: string, opts: SearchTasksOptions = {}): TaskRow[] {\n const like = `%${pattern.toLowerCase()}%`;\n const wsClause = opts.workstream === undefined ? \"\" : \"ws.name = ? AND\";\n const wsParams = opts.workstream === undefined ? [] : [opts.workstream];\n const orderBy =\n opts.workstream === undefined ? \"ORDER BY ws.name, t.local_id\" : \"ORDER BY t.local_id\";\n\n if (opts.includeNotes) {\n const sql = `SELECT DISTINCT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN}\n LEFT JOIN task_notes n ON n.task_id = t.id\n WHERE ${wsClause} (\n LOWER(t.title) LIKE ?\n OR LOWER(t.local_id) LIKE ?\n OR LOWER(n.content) LIKE ?\n )\n ${orderBy}`;\n return (db.prepare(sql).all(...wsParams, like, like, like) as RawTaskRow[]).map(rowFromDb);\n }\n\n const sql = `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN}\n WHERE ${wsClause} (LOWER(t.title) LIKE ? OR LOWER(t.local_id) LIKE ?)\n ${orderBy}`;\n return (db.prepare(sql).all(...wsParams, like, like) as RawTaskRow[]).map(rowFromDb);\n}\n","// mu — task id validation and title slug helpers.\n\nimport type { Db } from \"../db.js\";\nimport { getTask } from \"./queries.js\";\n\n/** Lowercase alpha first, then alnum / underscore / hyphen, ≤64 chars. */\nconst TASK_ID_RE = /^[a-z][a-z0-9_-]{0,63}$/;\n\nexport function isValidTaskId(id: string): boolean {\n return TASK_ID_RE.test(id);\n}\n\n/**\n * Derive a task id from a free-form title.\n *\n * \"Build the auth module\" → \"build_the_auth_module\"\n * \"FILES: foo.ts (refactor)\" → \"files_foo_ts_refactor\"\n */\n\n/**\n * Soft cap for auto-generated slugs. The collision-suffix loop in\n * idFromTitle can push past this (`_2`, `_3`, ...) without going past\n * the hard ceiling. 40 chars hits the sweet spot of 'short enough to\n * type and to look reasonable in mu task tree' without losing too\n * much of the title's meaning.\n */\nconst SLUG_SOFT_CAP = 40;\n\n/**\n * Hard ceiling for any generated id. Schema has no length limit, but\n * 64 keeps ids comfortable in tables, JSON, and tmux pane titles.\n */\nconst SLUG_HARD_CAP = 64;\n\n/**\n * Lowercase title; collapse non-alnum runs into single `_`; trim\n * leading/trailing `_`; prefix `t_` if the result starts with a digit\n * (schema requires first char letter); apply the soft cap with\n * word-boundary trim (cut at the last `_` at-or-before SLUG_SOFT_CAP\n * when one exists, else hard-truncate). Mirrors `tg`'s `id_from_title`\n * but adds the soft cap.\n *\n * Throws if `title` yields an empty slug after stripping.\n */\nexport function slugifyTitle(title: string): string {\n return slugifyTitleVerbose(title).slug;\n}\n\n/**\n * Result of `slugifyTitleVerbose`: the slug plus enough metadata for\n * the CLI to decide whether to warn the user that meaning was lost.\n *\n * slug — the same string `slugifyTitle` returns.\n * strippedLength — length of the post-strip pre-cap slug. When this\n * exceeds the SLUG_SOFT_CAP the verbose form had to\n * cut at a word boundary (or hard-truncate); the\n * cut clauses are gone with no in-band signal.\n * originalSlug — what the slug WOULD have been without the\n * SLUG_SOFT_CAP cut: full stripped slug with the\n * same `t_` digit-prefix correction and the same\n * SLUG_HARD_CAP ceiling, but no word-boundary\n * truncation. Equal to `slug` when nothing was\n * cut. The CLI surfaces this in `mu task add\n * --json` so scripted callers can detect the\n * truncation without grepping stderr.\n * truncated — true iff `slug.length < strippedLength` AFTER the\n * `t_` digit-prefix correction, i.e. real bytes were\n * dropped. False for any title that fits under the\n * soft cap or whose only diff vs the stripped slug\n * is the `t_` prefix.\n *\n * The CLI's `mu task add` uses `truncated` to print a one-line stderr\n * hint pointing at the `<id>` positional override and (under --json)\n * to surface `originalSlug` alongside `truncated:true`\n * (slugifytitle_silently_drops_clauses; task_add_slugify_silently_truncates_ids).\n */\nexport interface SlugifyResult {\n slug: string;\n strippedLength: number;\n originalSlug: string;\n truncated: boolean;\n}\n\n/**\n * Verbose sibling of `slugifyTitle`: returns the slug AND a\n * `truncated` flag so the CLI can hint to the user when the soft cap\n * dropped clauses (the meaning-shift hazard documented in\n * slugifytitle_silently_drops_clauses).\n *\n * Algorithm is byte-for-byte identical to `slugifyTitle`; this just\n * surfaces the metadata that the plain form throws away.\n */\nexport function slugifyTitleVerbose(title: string): SlugifyResult {\n const stripped = title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n if (stripped.length === 0) {\n throw new Error(`title yields empty slug: ${JSON.stringify(title)}`);\n }\n // Soft cap with word-boundary preference: if the slug exceeds the\n // soft cap, look for the last `_` at-or-before the cap and cut there\n // (so we never break a word). If no underscore is in the cap window\n // (ie the title is one giant word), fall back to a hard truncate at\n // the soft cap. The result is always <= SLUG_SOFT_CAP after this.\n let trimmed: string;\n if (stripped.length <= SLUG_SOFT_CAP) {\n trimmed = stripped;\n } else {\n const window = stripped.slice(0, SLUG_SOFT_CAP);\n const lastSep = window.lastIndexOf(\"_\");\n trimmed = lastSep > 0 ? window.slice(0, lastSep) : window;\n }\n // First char must be a letter → prefix `t_` if it isn't. v5 has no\n // global namespace and no reserved prefix; `mu_foo` is a perfectly\n // valid local_id (per-workstream unique).\n const applyPrefix = (s: string): string =>\n /^[a-z]/.test(s) ? s.slice(0, SLUG_HARD_CAP) : `t_${s}`.slice(0, SLUG_HARD_CAP);\n const slug = applyPrefix(trimmed);\n // `originalSlug` is what the slug would have been without the\n // soft-cap word-boundary cut: same prefix correction, same hard\n // cap, no word-boundary trim. Used by `mu task add --json` so\n // scripted callers can detect truncation programmatically.\n const originalSlug = applyPrefix(stripped);\n // `truncated` reflects whether real characters were dropped — the\n // `t_` prefix doesn't count as truncation. We compare the trimmed\n // length against the stripped length (both pre-prefix) so a digit-\n // led title that fit under the cap still reports truncated=false.\n return {\n slug,\n strippedLength: stripped.length,\n originalSlug,\n truncated: trimmed.length < stripped.length,\n };\n}\n\n/**\n * Generate a unique task id from a title. v5: tasks.local_id is\n * per-workstream unique, so the collision check scopes to one\n * workstream. On collision, appends `_2`, `_3`, … until unique.\n */\nexport function idFromTitle(db: Db, workstream: string, title: string): string {\n return idFromTitleVerbose(db, workstream, title).id;\n}\n\n/**\n * Result of `idFromTitleVerbose`: the unique-in-workstream id plus the\n * truncated flag from the underlying slugify pass. Used by `mu task\n * add` to decide whether to surface the stderr hint about lost clauses\n * (slugifytitle_silently_drops_clauses) and to surface the un-truncated\n * slug in `--json` (task_add_slugify_silently_truncates_ids).\n *\n * id — the unique-in-workstream task id.\n * truncated — true iff the underlying slugify pass cut real\n * characters (collision-suffixing does NOT flip\n * this).\n * originalSlug — what the slug would have been without the\n * SLUG_SOFT_CAP cut. Equal to `id` when nothing was\n * cut AND no collision suffix was appended; for\n * the truncation-detection use case the only thing\n * the CLI cares about is the lossy-vs-not\n * comparison surfaced via `truncated`.\n */\nexport interface IdFromTitleResult {\n id: string;\n truncated: boolean;\n originalSlug: string;\n}\n\n/**\n * Verbose sibling of `idFromTitle`: returns the unique id, the\n * `truncated` flag from the slugify pass, and the un-truncated\n * `originalSlug` for `--json` consumers. Collision-suffixing (`_2`,\n * `_3`, …) does not flip `truncated` — the underlying slug's lossiness\n * is what the CLI hint cares about.\n */\nexport function idFromTitleVerbose(db: Db, workstream: string, title: string): IdFromTitleResult {\n const { slug: base, truncated, originalSlug } = slugifyTitleVerbose(title);\n if (getTask(db, base, workstream) === undefined) return { id: base, truncated, originalSlug };\n for (let i = 2; i < 1000; i++) {\n const candidate = `${base}_${i}`.slice(0, SLUG_HARD_CAP);\n if (getTask(db, candidate, workstream) === undefined)\n return { id: candidate, truncated, originalSlug };\n }\n throw new Error(`could not derive a unique id from title in workstream ${workstream}: ${title}`);\n}\n\n/**\n * Sanitise a free-form string into a candidate task id.\n *\n * Lowercases, replaces every non-`[a-z0-9_-]` char with `_`, trims any\n * leading non-letter (the schema requires the first char to be a\n * letter), truncates to 64 chars. Returns `\"task\"` when the input has\n * no usable letters at all so the suggestion in\n * `TaskIdInvalidError.errorNextSteps()` is always a runnable command.\n *\n * Mirrors `slugifyTitle`'s prefix corrections so suggested ids will\n * pass `isValidTaskId` if the user runs them verbatim. Lives next to\n * `slugifyTitle` rather than in `tasks/errors.ts` because it's a slug\n * helper, not an error helper — the only caller happens to be\n * `TaskIdInvalidError.errorNextSteps()`.\n */\nexport function sanitiseTaskId(input: string): string {\n const s = input\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, \"_\")\n .replace(/^[^a-z]+/, \"\")\n .slice(0, SLUG_HARD_CAP);\n return s.length === 0 ? \"task\" : s;\n}\n","// mu — task error classes.\n//\n// Every task verb that can fail in a typed way has its own error class\n// here. The CLI's `classifyError()` (src/cli.ts) maps them to exit codes:\n// not found → 3 (TaskNotFoundError)\n// conflict → 4 (TaskExistsError, TaskNotInWorkstreamError,\n// TaskAlreadyOwnedError, TaskHasOpenDependentsError,\n// ClaimerNotRegisteredError, CrossWorkstreamEdgeError,\n// TaskIdInvalidError)\n// cycle → 4 (CycleError — also a conflict)\n//\n// Each error implements HasNextSteps so the CLI can render a per-error\n// `Next:` block with the most useful follow-up commands.\n//\n// Extracted from src/tasks.ts as part of refactor_split_large_src_files.\n\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport { WORKSPACE_STALE_THRESHOLD, type WorkspaceStaleness } from \"../workspace.js\";\nimport { sanitiseTaskId } from \"./id.js\";\n\nexport class TaskNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"TaskNotFoundError\";\n constructor(public readonly taskId: string) {\n super(`no such task: ${taskId}`);\n }\n errorNextSteps(): NextStep[] {\n // v5: tasks.workstream (TEXT) was replaced by tasks.workstream_id\n // (FK → workstreams.id). The pre-v5 recipe SELECTed a column that\n // no longer exists and crashed at runtime — and this is THE first\n // hint a user sees on a missed-task lookup, so the breakage was\n // high-traffic. Mirror the join pattern used by AgentExistsError.\n const idLit = this.taskId.replace(/'/g, \"''\").toLowerCase();\n const recipe = `mu sql \"SELECT ws.name AS workstream, t.local_id, t.status, t.title FROM tasks t JOIN workstreams ws ON ws.id = t.workstream_id WHERE LOWER(t.local_id) LIKE '%${idLit}%' OR LOWER(t.title) LIKE '%${idLit}%'\"`;\n return [\n { intent: \"List tasks in workstream\", command: \"mu task list -w <workstream>\" },\n { intent: \"Search by substring (id + title)\", command: recipe },\n { intent: \"Find which workstream owns it\", command: recipe },\n ];\n }\n}\n\n/**\n * Thrown by `addTask` when `localId` violates the schema regex\n * `/^[a-z][a-z0-9_-]{0,63}$/`. Replaces a bare `TypeError` so the\n * CLI's `handle()` wrapper can map it to exit code 4 (validation /\n * conflict) and surface a `--json` `nextSteps` block pointing at\n * the auto-derived-id workflow and a sanitised candidate.\n */\nexport class TaskIdInvalidError extends Error implements HasNextSteps {\n override readonly name = \"TaskIdInvalidError\";\n constructor(public readonly attempted: string) {\n super(`invalid task id: ${JSON.stringify(attempted)} (expected /^[a-z][a-z0-9_-]{0,63}$/)`);\n }\n errorNextSteps(): NextStep[] {\n const sanitised = sanitiseTaskId(this.attempted);\n return [\n {\n intent: \"Use the auto-derived id (drop --id and pass --title)\",\n command: 'mu task add --title \"...\" --impact <n> --effort-days <n>',\n },\n {\n intent: \"Sanitise to a valid id\",\n command: `mu task add ${sanitised} --title \"...\" --impact <n> --effort-days <n>`,\n },\n ];\n }\n}\n\nexport class TaskExistsError extends Error implements HasNextSteps {\n override readonly name = \"TaskExistsError\";\n constructor(public readonly taskId: string) {\n super(`task already exists: ${taskId}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Show the existing task\", command: `mu task show ${this.taskId}` },\n {\n intent: \"Update fields on the existing task\",\n command: `mu task update ${this.taskId} --title \"...\" --impact <n> --effort-days <n>`,\n },\n {\n intent: \"Pick a different id\",\n command: 'mu task add <new-id> --title \"...\" --impact <n> --effort-days <n>',\n },\n ];\n }\n}\n\n/**\n * Thrown when a verb is invoked with `-w/--workstream <name>` but the\n * named task lives in a different workstream. Distinguishes \"the user\n * typo'd the workstream\" from \"the task doesn't exist anywhere\"\n * (which surfaces as `TaskNotFoundError`). Maps to exit code 4\n * (conflict / wrong scope).\n */\nexport class TaskNotInWorkstreamError extends Error implements HasNextSteps {\n override readonly name = \"TaskNotInWorkstreamError\";\n constructor(\n public readonly taskId: string,\n public readonly expectedWorkstream: string,\n public readonly actualWorkstream: string,\n ) {\n super(`task ${taskId} is in workstream ${actualWorkstream}, not ${expectedWorkstream}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Use the correct workstream\",\n command: `mu task show ${this.taskId} -w ${this.actualWorkstream}`,\n },\n {\n intent: \"List tasks in the requested workstream\",\n command: `mu task list -w ${this.expectedWorkstream}`,\n },\n ];\n }\n}\n\nexport class TaskAlreadyOwnedError extends Error implements HasNextSteps {\n override readonly name = \"TaskAlreadyOwnedError\";\n constructor(\n public readonly taskId: string,\n public readonly currentOwner: string,\n ) {\n super(`task ${taskId} is already owned by ${currentOwner}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"See the current owner's task list\",\n command: `mu task owned-by ${this.currentOwner}`,\n },\n {\n intent: \"Release the current claim (if you ARE the owner)\",\n command: `mu task release ${this.taskId}`,\n },\n { intent: \"Show full task state\", command: `mu task show ${this.taskId}` },\n ];\n }\n}\n\n/**\n * Thrown by `rejectTask` / `deferTask` when the target task has\n * dependents that are still OPEN or IN_PROGRESS. Rejecting or\n * deferring such a task would silently strand the dependents (they'd\n * remain blocked by a prereq that's never going to satisfy the edge),\n * so we refuse and force an explicit decision: pass `--cascade` to\n * apply the same status to every transitive dependent, drop the\n * blocking edge first with `mu task unblock`, or address the\n * dependents individually. Maps to exit code 4.\n */\nexport class TaskHasOpenDependentsError extends Error implements HasNextSteps {\n override readonly name = \"TaskHasOpenDependentsError\";\n constructor(\n public readonly taskId: string,\n public readonly verb: \"reject\" | \"defer\",\n public readonly dependents: readonly string[],\n ) {\n super(\n `cannot ${verb} ${taskId}: ${dependents.length} open dependent(s) would be stranded (${dependents.slice(0, 5).join(\", \")}${dependents.length > 5 ? \", …\" : \"\"}). Pick one resolution and re-run.`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: `Preview the cascade (lists dependents that would be ${this.verb}ed; --cascade alone is dry-run)`,\n command: `mu task ${this.verb} ${this.taskId} --cascade`,\n },\n {\n intent: `${this.verb.charAt(0).toUpperCase() + this.verb.slice(1)} the whole sub-tree (commit; rerun with --yes after previewing)`,\n command: `mu task ${this.verb} ${this.taskId} --cascade --yes`,\n },\n {\n intent: \"Drop the blocking edge from a dependent first\",\n command: `mu task unblock <dep> --by ${this.taskId}`,\n },\n {\n intent: \"Address dependents individually first\",\n command: `mu task ${this.verb} <dep>`,\n },\n ];\n }\n}\n\n/**\n * Thrown when `mu task claim` resolves a claimer agent name (from the\n * pane title or --for) that has no matching row in the agents table.\n *\n * The FK on `tasks.owner` references `agents.name`; without this guard\n * the claim attempt would fail with the unhelpful 'FOREIGN KEY constraint\n * failed' from SQLite. This typed error gives the user actionable next\n * steps (run `mu agent adopt <pane-id>` to register, or use --for to pick a\n * different agent).\n *\n * Maps to exit code 4 (conflict) via the cli.ts handler.\n */\nexport class ClaimerNotRegisteredError extends Error implements HasNextSteps {\n override readonly name = \"ClaimerNotRegisteredError\";\n constructor(\n public readonly agentName: string,\n public readonly paneId: string | null,\n ) {\n const paneHint = paneId !== null ? ` (pane ${paneId})` : \"\";\n super(\n `claimer '${agentName}'${paneHint} is not a registered mu agent (no row in agents table)`,\n );\n }\n\n /**\n * Three actionable resolutions in expected-frequency order:\n * 1. --self : orchestrator pattern (working directly)\n * 2. --for : dispatcher pattern (assigning to a worker)\n * 3. mu agent adopt: registration pattern (promote pane to worker)\n */\n errorNextSteps(): NextStep[] {\n const steps: NextStep[] = [\n { intent: \"Work directly (anonymous)\", command: \"mu task claim <id> --self\" },\n { intent: \"Dispatch to a worker\", command: \"mu task claim <id> --for <worker>\" },\n ];\n steps.push(\n this.paneId !== null\n ? { intent: \"Register this pane\", command: `mu agent adopt ${this.paneId}` }\n : {\n intent: \"Register a pane\",\n command: \"mu agent adopt <pane-id> (must be in mu-<workstream> tmux session)\",\n },\n );\n return steps;\n }\n}\n\nexport class TaskClaimStaleWorkspaceError extends Error implements HasNextSteps {\n override readonly name = \"TaskClaimStaleWorkspaceError\";\n constructor(public readonly staleness: WorkspaceStaleness) {\n super(\n `${staleness.agentName} workspace is ${staleness.commitsBehindMain} commits behind main (≥${WORKSPACE_STALE_THRESHOLD} = stale); refresh before dispatch or rerun without --strict-staleness`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Refresh first\",\n command: `mu workspace refresh ${this.staleness.agentName} -w ${this.staleness.workstreamName}`,\n },\n ];\n }\n}\n\nexport class CycleError extends Error implements HasNextSteps {\n override readonly name = \"CycleError\";\n constructor(\n public readonly from: string,\n public readonly to: string,\n ) {\n super(`adding edge ${from} -> ${to} would create a cycle`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Show the dependency tree\",\n command: `mu task tree ${this.to} --down`,\n },\n {\n intent: \"Show the prereq tree (what blocks the from-task)\",\n command: `mu task tree ${this.from}`,\n },\n {\n intent: \"Remove an edge in the path to break the cycle\",\n command: \"mu task unblock <blocked> --by <blocker>\",\n },\n ];\n }\n}\n\n/**\n * Thrown by the `mu task wait` CLI wrapper when the per-poll\n * reconciler detects that a watched task transitioned\n * `IN_PROGRESS → OPEN` between polls (the reaper saw the owner's\n * pane was gone and flipped the task back). With `--status CLOSED`\n * (the default) the wait can never satisfy by progress — the worker\n * is dead — so we abort fast instead of running out the operator's\n * `--timeout`.\n *\n * Maps to exit code 6 (REAPER_DETECTED) via the cli.ts handler. The\n * suppression rule (only fire when target=CLOSED) lives in the\n * caller; this error type is a pure data carrier.\n *\n * Surfaced live by task_wait_reconcile_dead_panes (twice in one\n * v0.3 dispatch wave: tmux restart killed worker panes; `mu task\n * wait --timeout 1800` blocked silently for 25 min instead of\n * failing in seconds).\n */\nexport class ReaperDetectedDuringWaitError extends Error implements HasNextSteps {\n override readonly name = \"ReaperDetectedDuringWaitError\";\n constructor(\n public readonly taskId: string,\n public readonly previousOwner: string | null,\n public readonly workstream: string,\n ) {\n const ownerBit = previousOwner !== null ? `owner=${previousOwner}` : \"owner=<unknown>\";\n super(\n `task ${taskId} was IN_PROGRESS ${ownerBit} until just now; reaper detected dead pane and flipped to OPEN. wait abandoned. Re-dispatch a worker and retry.`,\n );\n }\n errorNextSteps(): NextStep[] {\n const ws = this.workstream;\n return [\n {\n intent: \"Inspect the task's current state\",\n command: `mu task show ${this.taskId} -w ${ws}`,\n },\n {\n intent: \"List live agents in the workstream (post-reap)\",\n command: `mu agent list -w ${ws}`,\n },\n {\n intent: \"Re-dispatch a fresh worker, then re-run the wait\",\n command: `mu agent spawn <name> -w ${ws} && mu task claim ${this.taskId} --for <name> -w ${ws}`,\n },\n ];\n }\n}\n\n/**\n * Thrown by the `mu task wait` CLI wrapper when `--on-stall exit` is\n * in effect and the existing `--stuck-after` predicate fires on a\n * watched task — the task is IN_PROGRESS, owned by a registered\n * agent whose detected status is `needs_input` for `>= stuckAfterMs`.\n *\n * Pairs with `ReaperDetectedDuringWaitError` (exit 6, dead pane).\n * Stall is the AMBIGUOUS sibling: the worker is alive but not\n * progressing — the operator decides whether it's transient (poke +\n * retry) or terminal (release + reopen). Exit code 7 = STALL_DETECTED\n * via classifyError, distinct from 6 so consumer scripts can branch.\n *\n * Carve-out (lives at the call site, not here): only fires when the\n * wait target is CLOSED — same logic as exit-6's reaper-flip\n * suppression. With `--status OPEN`/etc the worker reaching\n * needs_input might BE the success path.\n *\n * Surfaced by task_wait_stall_action_flag (the warn-only behaviour\n * pre-dates this; the typed-throw path is the new escape hatch for\n * unattended orchestrators).\n */\nexport class StallDetectedDuringWaitError extends Error implements HasNextSteps {\n override readonly name = \"StallDetectedDuringWaitError\";\n constructor(\n public readonly taskName: string,\n public readonly owner: string | null,\n public readonly workstream: string,\n public readonly ageSecs: number,\n ) {\n const ownerBit = owner !== null ? owner : \"<unknown>\";\n super(\n `task ${taskName} owned by ${ownerBit} has been needs_input for ${ageSecs}s; exiting per --on-stall exit. Re-dispatch a worker or send a poke (mu agent send ${ownerBit} \"...\") and re-run wait.`,\n );\n }\n errorNextSteps(): NextStep[] {\n const ws = this.workstream;\n const ownerBit = this.owner !== null ? this.owner : \"<owner>\";\n return [\n {\n intent: \"Poke the worker (often unblocks a transient stall)\",\n command: `mu agent send ${ownerBit} '<retry-instruction>' -w ${ws}`,\n },\n {\n intent: \"Inspect the worker's recent scrollback\",\n command: `mu agent show ${ownerBit} -w ${ws} -n 60`,\n },\n {\n intent: \"Release the task back to OPEN (declare the stall terminal)\",\n command: `mu task release ${this.taskName} --reopen -w ${ws}`,\n },\n {\n intent: \"Inspect the task's current state\",\n command: `mu task show ${this.taskName} -w ${ws}`,\n },\n ];\n }\n}\n\nexport class CrossWorkstreamEdgeError extends Error implements HasNextSteps {\n override readonly name = \"CrossWorkstreamEdgeError\";\n constructor(\n public readonly blocker: string,\n public readonly blockerWorkstream: string,\n public readonly dependent: string,\n public readonly dependentWorkstream: string,\n ) {\n super(\n `cross-workstream edge: blocker '${blocker}' is in workstream '${blockerWorkstream}', dependent '${dependent}' is in workstream '${dependentWorkstream}'`,\n );\n }\n errorNextSteps(): NextStep[] {\n // schema v5+: tasks.workstream_id is an INTEGER FK to\n // workstreams.id (no tasks.workstream column), and (workstream_id,\n // local_id) is the per-workstream unique key — so the move-blocker\n // recipe must scope by BOTH the source workstream's id AND set the\n // destination workstream's id via subselects. The v4-shaped\n // `UPDATE tasks SET workstream='…' WHERE local_id='…'` recipe we\n // used to print here errored at runtime (\"no such column:\n // workstream\") and was also ambiguous across workstreams.\n //\n // We also dropped the \"rename one workstream to the other\" hint:\n // it silently moves *every* task in the source workstream and\n // fails outright when the destination name already exists\n // (UNIQUE violation). Operators almost always want to move just\n // the blocker — or duplicate it — not merge whole workstreams.\n return [\n {\n intent: \"Move the blocker into the dependent's workstream\",\n command: `mu sql \"UPDATE tasks SET workstream_id=(SELECT id FROM workstreams WHERE name='${this.dependentWorkstream}') WHERE local_id='${this.blocker}' AND workstream_id=(SELECT id FROM workstreams WHERE name='${this.blockerWorkstream}')\"`,\n },\n {\n intent: \"Or duplicate the blocker (typed verb deferred)\",\n command: `mu task add <new-id> -w ${this.dependentWorkstream} --title \"<copy of ${this.blocker}>\" --impact <n> --effort-days <n>`,\n },\n ];\n }\n}\n","// mu — task edge reads and mutations.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { lookupTaskAnyWorkstream, taskIdFor, touchTask } from \"./core.js\";\nimport { CrossWorkstreamEdgeError, CycleError, TaskNotFoundError } from \"./errors.js\";\nimport { getTask } from \"./queries.js\";\nimport type { TaskStatus } from \"./status.js\";\n\nexport interface TaskEdges {\n /** Tasks that must close before this one can start (blockers). */\n blockers: string[];\n /** Tasks that this one blocks (dependents). */\n dependents: string[];\n}\n\nexport interface CanonicalBlocker {\n localId: string;\n id: number;\n}\n\nexport function dedupeBlockersById(blockers: readonly CanonicalBlocker[]): CanonicalBlocker[] {\n const seen = new Set<number>();\n const canonical: CanonicalBlocker[] = [];\n for (const blocker of blockers) {\n if (seen.has(blocker.id)) continue;\n seen.add(blocker.id);\n canonical.push(blocker);\n }\n return canonical;\n}\n\nfunction sameNumberSet(left: readonly number[], right: readonly number[]): boolean {\n if (left.length !== right.length) return false;\n const rightSet = new Set(right);\n if (rightSet.size !== right.length) return false;\n return left.every((value) => rightSet.has(value));\n}\n\n/** One end of an edge with the neighbour's current status attached.\n * Used by `mu task show` to group blockers/dependents into\n * \"still gating\" vs \"satisfied\" buckets without making the renderer\n * do a second round-trip to the DB per neighbour. */\nexport interface TaskEdgeWithStatus {\n name: string;\n status: TaskStatus;\n}\n\nexport interface TaskEdgesWithStatus {\n /** Tasks that must close before this one can start (blockers),\n * carrying each blocker's current status. */\n blockers: TaskEdgeWithStatus[];\n /** Tasks that this one blocks (dependents), carrying each\n * dependent's current status. */\n dependents: TaskEdgeWithStatus[];\n}\n\n/**\n * Direct (one-hop) edges for a task. For transitive prerequisites, use\n * `getPrerequisites()`; this helper is the immediate-neighbour view used\n * by `mu task show`.\n */\nexport function getTaskEdges(db: Db, taskLocalId: string, workstream: string): TaskEdges {\n const taskId = taskIdFor(db, taskLocalId, workstream);\n if (taskId === null) return { blockers: [], dependents: [] };\n const blockers = (\n db\n .prepare(\n `SELECT t.local_id AS id FROM task_edges e\n JOIN tasks t ON t.id = e.from_task_id\n WHERE e.to_task_id = ? ORDER BY t.local_id`,\n )\n .all(taskId) as { id: string }[]\n ).map((r) => r.id);\n const dependents = (\n db\n .prepare(\n `SELECT t.local_id AS id FROM task_edges e\n JOIN tasks t ON t.id = e.to_task_id\n WHERE e.from_task_id = ? ORDER BY t.local_id`,\n )\n .all(taskId) as { id: string }[]\n ).map((r) => r.id);\n return { blockers, dependents };\n}\n\n/**\n * Same one-hop edge view as `getTaskEdges`, but each neighbour is\n * returned as `{ name, status }` so callers can group / colour by\n * status without an N+1 round-trip. Used by `mu task show` to split\n * \"blocked by\" (still-gating) from \"satisfied\" (already-CLOSED)\n * blockers, and the symmetric split on the dependents side\n * (task_show_blocked_by_renders_closed). The status is the neighbour's\n * full TaskStatus, not just OPEN/CLOSED — REJECTED/DEFERRED still\n * gate downstream work, so the renderer keeps them in the\n * still-gating bucket.\n */\nexport function getTaskEdgesWithStatus(\n db: Db,\n taskLocalId: string,\n workstream: string,\n): TaskEdgesWithStatus {\n const taskId = taskIdFor(db, taskLocalId, workstream);\n if (taskId === null) return { blockers: [], dependents: [] };\n const blockers = db\n .prepare(\n `SELECT t.local_id AS name, t.status AS status FROM task_edges e\n JOIN tasks t ON t.id = e.from_task_id\n WHERE e.to_task_id = ? ORDER BY t.local_id`,\n )\n .all(taskId) as TaskEdgeWithStatus[];\n const dependents = db\n .prepare(\n `SELECT t.local_id AS name, t.status AS status FROM task_edges e\n JOIN tasks t ON t.id = e.to_task_id\n WHERE e.from_task_id = ? ORDER BY t.local_id`,\n )\n .all(taskId) as TaskEdgeWithStatus[];\n return { blockers, dependents };\n}\n\n/**\n * All tasks transitively reachable from `taskId` via reverse-edge\n * traversal (i.e. the set of tasks that block this one), including the\n * task itself.\n */\nexport function getPrerequisites(db: Db, taskLocalId: string, workstream: string): Set<string> {\n const taskId = taskIdFor(db, taskLocalId, workstream);\n if (taskId === null) return new Set([taskLocalId]);\n // Walk reverse edges in surrogate-id space, then translate back to\n // local_id strings. The seed must be the surrogate id; the result\n // includes the seed task itself (callers like the tracks union-find\n // rely on the inclusive set).\n const rows = db\n .prepare(\n `WITH RECURSIVE reach(node) AS (\n SELECT ?\n UNION\n SELECT from_task_id FROM task_edges, reach WHERE to_task_id = reach.node\n )\n SELECT t.local_id AS local_id FROM reach r JOIN tasks t ON t.id = r.node`,\n )\n .all(taskId) as { local_id: string }[];\n return new Set(rows.map((r) => r.local_id));\n}\n\n/**\n * Adding edge `from -> to` creates a cycle iff there's already a path\n * `to -> ... -> from`. SQL recursive CTE expresses this exactly.\n */\nexport function wouldCreateCycle(db: Db, fromId: number, toId: number): boolean {\n if (fromId === toId) return true;\n const result = db\n .prepare(\n `WITH RECURSIVE forward(node) AS (\n SELECT ?\n UNION\n SELECT to_task_id FROM task_edges, forward WHERE from_task_id = forward.node\n )\n SELECT 1 AS hit FROM forward WHERE node = ? LIMIT 1`,\n )\n .get(toId, fromId) as { hit: number } | undefined;\n return result !== undefined;\n}\n\nexport interface BlockEdgeResult {\n /** True iff a row was actually inserted (vs. already present). */\n added: boolean;\n}\n\n/**\n * Add the edge `blocker → blocked` ('blocker blocks blocked').\n * Idempotent (existing edge → `added: false`). Validates:\n *\n * - both tasks exist\n * - same workstream (cross-workstream edges forbidden)\n * - no cycle (the new edge wouldn't form a path blocked → ... → blocker)\n * - blocker ≠ blocked (no self-reference)\n */\nexport function addBlockEdge(\n db: Db,\n workstream: string,\n blocked: string,\n blocker: string,\n): BlockEdgeResult {\n if (blocked === blocker) {\n // Surface as a typed CycleError so the CLI maps it to exit 4 (conflict)\n // rather than letting the schema CHECK fire as a generic SQL error.\n throw new CycleError(blocker, blocked);\n }\n const blockedRow = getTask(db, blocked, workstream);\n if (!blockedRow) throw new TaskNotFoundError(blocked);\n // Resolve the blocker globally so a cross-workstream blocker surfaces\n // CrossWorkstreamEdgeError (clearer than TaskNotFoundError). Cycle\n // check + same-workstream guard run after.\n const blockerRow = lookupTaskAnyWorkstream(db, blocker);\n if (!blockerRow) throw new TaskNotFoundError(blocker);\n if (blockedRow.workstreamName !== blockerRow.workstreamName) {\n throw new CrossWorkstreamEdgeError(\n blocker,\n blockerRow.workstreamName,\n blocked,\n blockedRow.workstreamName,\n );\n }\n const blockedId = taskIdFor(db, blocked, blockedRow.workstreamName);\n const blockerId = taskIdFor(db, blocker, blockerRow.workstreamName);\n if (blockedId === null || blockerId === null) throw new TaskNotFoundError(blocked);\n if (wouldCreateCycle(db, blockerId, blockedId)) {\n throw new CycleError(blocker, blocked);\n }\n const now = new Date().toISOString();\n const added = db.transaction(() => {\n const result = db\n .prepare(\n \"INSERT OR IGNORE INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)\",\n )\n .run(blockerId, blockedId, now);\n if (result.changes > 0) {\n // Bump the BLOCKED task — its blocker set changed. The blocker\n // itself is unaffected. Aligned with reparentTask, which also\n // bumps the FROM_TASK side (the task whose blockers shifted).\n touchTask(db, blockedId, now);\n return true;\n }\n return false;\n })();\n if (added) emitEvent(db, blockedRow.workstreamName, `task block ${blocked} by ${blocker}`);\n return { added };\n}\n\nexport interface RemoveBlockEdgeResult {\n /** True iff a row was actually deleted (vs. no such edge). */\n removed: boolean;\n}\n\n/**\n * Remove the edge `blocker → blocked`. Idempotent (no edge →\n * `removed: false`). Does NOT validate task existence — if the\n * edge is gone there's nothing to do, regardless of whether the\n * tasks are gone too.\n */\nexport function removeBlockEdge(\n db: Db,\n workstream: string,\n blocked: string,\n blocker: string,\n): RemoveBlockEdgeResult {\n const blockedRow = getTask(db, blocked, workstream);\n if (!blockedRow) return { removed: false };\n const blockerRow = getTask(db, blocker, workstream);\n if (!blockerRow) return { removed: false };\n const blockedId = taskIdFor(db, blocked, blockedRow.workstreamName);\n const blockerId = taskIdFor(db, blocker, blockerRow.workstreamName);\n if (blockedId === null || blockerId === null) return { removed: false };\n const removed = db.transaction(() => {\n const result = db\n .prepare(\"DELETE FROM task_edges WHERE from_task_id = ? AND to_task_id = ?\")\n .run(blockerId, blockedId);\n if (result.changes > 0) {\n // Bump the BLOCKED task — its blocker set just shrank.\n touchTask(db, blockedId);\n return true;\n }\n return false;\n })();\n if (removed) {\n emitEvent(db, blockedRow.workstreamName, `task unblock ${blocked} by ${blocker}`);\n }\n return { removed };\n}\n\nexport interface ReparentTaskResult {\n /** Edges removed (i.e. all incoming `to_task = taskId` edges). */\n removedEdges: number;\n /** Edges added (after duplicate blockers are canonicalised). */\n addedEdges: number;\n}\n\n/**\n * Atomically replace every incoming edge of `taskId` with new ones\n * `blocker[i] → taskId`. Pass an empty `blockers` array to clear all\n * incoming edges (the task becomes ready iff its status allows).\n *\n * Validates ALL new blockers up-front (existence + same workstream +\n * cycle check); if any fails, no DELETE happens — the call is fully\n * atomic via a single transaction.\n *\n * Cycle reasoning: removing the existing incoming edges to `taskId`\n * doesn't change `taskId`'s OUTGOING reachability, so\n * `wouldCreateCycle(db, blocker, taskId)` evaluated against the\n * pre-state gives the right answer for each new edge.\n */\nexport function reparentTask(\n db: Db,\n taskLocalId: string,\n blockers: readonly string[],\n scope: { workstream: string },\n): ReparentTaskResult {\n const task = getTask(db, taskLocalId, scope.workstream);\n if (!task) throw new TaskNotFoundError(taskLocalId);\n const taskSurrogateId = taskIdFor(db, task.name, task.workstreamName);\n if (taskSurrogateId === null) throw new TaskNotFoundError(taskLocalId);\n\n // Resolve every blocker up-front to its surrogate id; do all\n // existence + same-workstream + cycle checks before any DELETE.\n // Look up blockers across all workstreams so a blocker that exists in\n // a DIFFERENT workstream surfaces CrossWorkstreamEdgeError (clearer\n // than TaskNotFoundError). Duplicate blockers are an ergonomic input\n // accident from repeat/CSV forms, so silently canonicalise by\n // surrogate id while preserving first-seen order.\n const requestedBlockers: CanonicalBlocker[] = [];\n for (const blockerLocalId of blockers) {\n if (blockerLocalId === taskLocalId) {\n throw new CycleError(blockerLocalId, taskLocalId);\n }\n const blocker = lookupTaskAnyWorkstream(db, blockerLocalId);\n if (!blocker) throw new TaskNotFoundError(blockerLocalId);\n if (blocker.workstreamName !== task.workstreamName) {\n throw new CrossWorkstreamEdgeError(\n blockerLocalId,\n blocker.workstreamName,\n taskLocalId,\n task.workstreamName,\n );\n }\n const blockerId = taskIdFor(db, blocker.name, blocker.workstreamName);\n if (blockerId === null) throw new TaskNotFoundError(blockerLocalId);\n if (wouldCreateCycle(db, blockerId, taskSurrogateId)) {\n throw new CycleError(blockerLocalId, taskLocalId);\n }\n requestedBlockers.push({ localId: blockerLocalId, id: blockerId });\n }\n const canonicalBlockers = dedupeBlockersById(requestedBlockers);\n const blockerIds = canonicalBlockers.map((blocker) => blocker.id);\n const currentBlockerIds = (\n db\n .prepare(\"SELECT from_task_id AS id FROM task_edges WHERE to_task_id = ?\")\n .all(taskSurrogateId) as { id: number }[]\n ).map((row) => row.id);\n\n if (sameNumberSet(currentBlockerIds, blockerIds)) {\n return { removedEdges: 0, addedEdges: 0 };\n }\n\n return db.transaction(() => {\n const removed = db.prepare(\"DELETE FROM task_edges WHERE to_task_id = ?\").run(taskSurrogateId);\n const insertEdge = db.prepare(\n \"INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)\",\n );\n const now = new Date().toISOString();\n for (const blockerId of blockerIds) {\n insertEdge.run(blockerId, taskSurrogateId, now);\n }\n // Bump the reparented task itself — its blocker set just changed.\n touchTask(db, taskSurrogateId, now);\n const blockerNames = canonicalBlockers.map((blocker) => blocker.localId);\n const blockersBit = blockerNames.length > 0 ? `, new=${blockerNames.join(\",\")}` : \"\";\n emitEvent(\n db,\n task.workstreamName,\n `task reparent ${taskLocalId} (removed ${removed.changes} edges, added ${blockerIds.length}${blockersBit})`,\n );\n return { removedEdges: removed.changes, addedEdges: blockerIds.length };\n })();\n}\n","// mu — workstream-level operations.\n//\n// One workstream = one tmux session + N agents + M tasks (and their\n// edges/notes) all sharing the workstream column. 0.1.0 ships `mu init`\n// (create the tmux session) and `mu destroy` (this module: nuke the\n// tmux session and every DB row tagged with the workstream name).\n//\n// `destroyWorkstream` is idempotent on every leg:\n// - tmux session already gone → killSession swallows the error\n// - no agents/tasks for this name → DELETE returns zero changes\n// - workstream never existed at all → returns all-zero counts\n//\n// Both summarize and destroy take an optional `tmuxSession` override so\n// tests (and the rare workstream whose tmux session was created with a\n// non-default name) work without env-var gymnastics.\n\nimport { existsSync, readdirSync, rmdirSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { type Db, defaultStateDir } from \"./db.js\";\nimport {\n type ExportManifest,\n type ExportSourceManifest,\n exportSourceForWorkstream,\n renderToBucket,\n} from \"./exporting.js\";\nimport { emitEvent } from \"./logs.js\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\nimport { captureSnapshot } from \"./snapshots.js\";\nimport { killSession, listSessions, sessionExists, tmux } from \"./tmux.js\";\nimport { type VcsBackend, type VcsBackendName, backendByName } from \"./vcs.js\";\nimport { listWorkspaces } from \"./workspace.js\";\n\n/**\n * Allowed workstream-name shape: lowercase alpha first, then alnum,\n * underscore, or hyphen, up to 32 chars total. Mirrors the agent-name\n * rule in VOCABULARY.md §\"Naming conventions\".\n *\n * Critically, this rule excludes `.` and `:` — tmux silently rewrites\n * `.` to `_` in session names (because `.` is the window/pane separator\n * in tmux's `session:window.pane` target syntax) and `:` is reserved\n * outright. A workstream name with `.` would create a session that mu\n * couldn't subsequently look up, breaking every downstream verb. We\n * fail loud at init time instead.\n */\nconst WORKSTREAM_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;\n\n/** Reserved prefix — mu auto-prepends `mu-` to derive the tmux session\n * name (so workstream `auth` lives in tmux session `mu-auth`). A\n * workstream named `mu-auth` would produce session `mu-mu-auth`,\n * which the user almost certainly didn't intend. Fail loud rather\n * than silently double-prefix. */\nexport const RESERVED_WORKSTREAM_PREFIX = \"mu-\";\n\nexport async function resolveTmuxSessionWorkstreamName(): Promise<string | null> {\n if (!process.env.TMUX) return null;\n try {\n const name = (await tmux([\"display-message\", \"-p\", \"#S\"])).trim();\n if (name.startsWith(RESERVED_WORKSTREAM_PREFIX)) {\n return name.slice(RESERVED_WORKSTREAM_PREFIX.length);\n }\n } catch {\n // fall through: tmux context is best-effort for workstream resolution\n }\n return null;\n}\n\nexport function isValidWorkstreamName(name: string): boolean {\n if (!WORKSTREAM_NAME_RE.test(name)) return false;\n if (name.startsWith(RESERVED_WORKSTREAM_PREFIX)) return false;\n return true;\n}\n\n/** Thrown by `ensureWorkstream` and `mu workstream init` when the name\n * doesn't match the rules. */\nexport class WorkstreamExistsError extends Error implements HasNextSteps {\n override readonly name: string = \"WorkstreamExistsError\";\n constructor(public readonly workstream: string) {\n super(`workstream already exists: ${workstream}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Pick a different workstream name\",\n command: \"mu archive restore <label> --as <new-name>\",\n },\n { intent: \"List existing workstreams\", command: \"mu workstream list\" },\n {\n intent: \"Destroy the existing workstream first\",\n command: `mu workstream destroy -w ${this.workstream} --yes`,\n },\n ];\n }\n}\n\nexport class WorkstreamNameInvalidError extends Error implements HasNextSteps {\n override readonly name = \"WorkstreamNameInvalidError\";\n constructor(public readonly attempted: string) {\n const reason = attempted.startsWith(RESERVED_WORKSTREAM_PREFIX)\n ? `the 'mu-' prefix is reserved (mu auto-prepends 'mu-' to derive the tmux session name; '${attempted}' would produce session 'mu-${attempted}', which is double-prefixed and almost never what you want). Drop the 'mu-' from the workstream name.`\n : `must match /^[a-z][a-z0-9_-]{0,31}$/. tmux silently rewrites '.' to '_' and reserves ':' as a target separator, so workstream names containing those characters would create tmux sessions mu couldn't look up afterwards. Use letters, digits, '_', and '-' only.`;\n super(`invalid workstream name ${JSON.stringify(attempted)}: ${reason}`);\n }\n errorNextSteps(): NextStep[] {\n // Suggest a sanitized form: strip the mu- prefix; replace dots and\n // colons with underscores; lowercase.\n const sanitized = this.attempted\n .toLowerCase()\n .replace(/^mu-/, \"\")\n .replace(/[.:]/g, \"_\")\n .slice(0, 32);\n // Branch the intent label on the failure class. For the mu-prefix\n // case the correction is unambiguous (drop the prefix), so phrase\n // the next-step as a direct action — \"Try a … (best guess)\" reads\n // as a hedge and dogfooding showed agents skip past the rationale\n // line entirely (workstream_init_name_rejected_mu in feedback ws).\n // For the regex/mangle branch the sanitiser really is guessing\n // (`.`/`:`/case all collapse), so the hedge stays honest there.\n const isPrefixCase = this.attempted.toLowerCase().startsWith(RESERVED_WORKSTREAM_PREFIX);\n const intent = isPrefixCase\n ? \"Retry without the 'mu-' prefix\"\n : \"Try a sanitized name (best guess)\";\n return [\n { intent, command: `mu workstream init ${sanitized || \"<name>\"}` },\n { intent: \"List existing workstreams\", command: \"mu workstream list\" },\n ];\n }\n}\n\nfunction assertValidWorkstreamName(name: string): void {\n if (!isValidWorkstreamName(name)) throw new WorkstreamNameInvalidError(name);\n}\n\n/**\n * Ensure a row exists in the `workstreams` table for `name`. Idempotent;\n * INSERT OR IGNORE so concurrent callers race safely. Called by\n * `insertAgent` and `addTask` so callers don't need to remember to call\n * `mu init` before adding a task / spawning an agent (preserves the\n * spawn-without-init ergonomics now that agents.workstream and\n * tasks.workstream are real FKs into this table).\n *\n * Validates the name before inserting; throws `WorkstreamNameInvalidError`\n * for names tmux would silently mangle (containing '.' or ':') or that\n * exceed 32 chars / start with a non-letter.\n *\n * Returns true iff a row was actually inserted (vs. already present).\n */\nexport function ensureWorkstream(db: Db, name: string): boolean {\n assertValidWorkstreamName(name);\n const result = db\n .prepare(\"INSERT OR IGNORE INTO workstreams (name, created_at) VALUES (?, ?)\")\n .run(name, new Date().toISOString());\n const created = result.changes > 0;\n if (created) emitEvent(db, name, `workstream init ${name}`);\n return created;\n}\n\nexport interface WorkstreamSummary {\n /** The workstream's own name. */\n name: string;\n /** Tmux session name, defaults to `mu-<name>`. */\n tmuxSession: string;\n /** True iff `tmux has-session -t <tmuxSession>` succeeds right now. */\n tmuxAlive: boolean;\n /** Rows in `agents` for this workstream. */\n agentCount: number;\n /** Rows in `tasks` for this workstream. */\n taskCount: number;\n /** Rows in `task_notes` whose task is in this workstream. */\n noteCount: number;\n /** Rows in `task_edges` whose `from_task` is in this workstream. */\n edgeCount: number;\n /** Rows in `vcs_workspaces` for this workstream. Surfaced so the\n * destroy dry-run can warn about per-agent worktrees that need\n * cleanup before the FK cascade silently nukes their rows. */\n workspaceCount: number;\n /** True iff a row exists in the `workstreams` table itself. False\n * for tmux-only `mu-*` sessions that mu never observed via\n * `mu workstream init`. Surfaced so destroy can clean up bare\n * registry rows (workstream row exists, no agents/tasks/etc.) —\n * otherwise such rows are orphaned forever (the previous\n * `nothingToDo` heuristic short-circuited on them). */\n registered: boolean;\n}\n\nexport interface DestroyResult {\n /** True iff `tmux kill-session` actually killed something. */\n killedTmux: boolean;\n /** Number of `agents` rows deleted. */\n deletedAgents: number;\n /** Number of `tasks` rows deleted (edges/notes cascade via FK). */\n deletedTasks: number;\n /** Number of `task_notes` deleted by the cascade — informational. */\n deletedNotes: number;\n /** Number of `task_edges` deleted by the cascade — informational. */\n deletedEdges: number;\n /** Number of vcs_workspaces whose on-disk path was actually\n * removed by the backend on this destroy. Excludes\n * `alreadyGoneWorkspaces` (those were no-ops on disk). */\n freedWorkspaces: number;\n /** Number of vcs_workspaces whose registry row existed but\n * whose on-disk path was already gone (manual rm -rf or a prior\n * interrupted destroy). The DB row was cascade-deleted; the\n * backend did no filesystem work. Tracked separately so the\n * destroy report doesn't lie about how much cleanup it actually\n * performed. */\n alreadyGoneWorkspaces: number;\n /** Workspaces whose backend cleanup failed (e.g. `git worktree\n * remove` refused because of uncommitted changes). The DB row\n * was still cascade-deleted; the on-disk path remains and needs\n * manual cleanup. */\n failedWorkspaces: WorkspaceFailure[];\n}\n\nexport interface WorkspaceFailure {\n agent: string;\n backend: string;\n path: string;\n error: string;\n}\n\nexport interface WorkstreamOptions {\n workstream: string;\n /** Override the tmux session name. Defaults to `mu-<workstream>`. */\n tmuxSession?: string;\n /** Override the per-name VcsBackend resolver. Defaults to\n * `backendByName`. Lets tests inject a fake backend (e.g. one whose\n * `freeWorkspace` throws) without mutating the exported singletons —\n * same pattern as `createWorkspace`'s `opts.backend` accepting a\n * pre-built `VcsBackend` object. Production callers leave this\n * unset. */\n resolveBackend?: (name: VcsBackendName) => VcsBackend;\n}\n\nexport interface DestroyWorkstreamOptions extends WorkstreamOptions {\n /** Skip the per-workstream pre-mutation snapshot because the caller\n * already captured a broader snapshot for the whole destructive\n * operation. Used by `mu workstream destroy --empty` after its\n * sweep-level safety snapshot; direct destroy callers leave this\n * false so the default safety net is unchanged. */\n suppressSnapshot?: boolean;\n}\n\n/**\n * Discover every workstream visible on this machine. The union of:\n * - rows in the `workstreams` table (canonical DB source; populated by\n * `mu init` and auto-created by insertAgent / addTask)\n * - tmux sessions named `mu-*` (with the prefix stripped) — catches\n * externally-created `tmux new-session -s mu-foo` that mu hasn't\n * observed yet\n *\n * Returns one `WorkstreamSummary` per workstream, sorted by name.\n * Useful as a pre-flight before `mu init` (\"is this name taken?\") and\n * for `mu doctor`-style diagnostics.\n */\nexport async function listWorkstreams(db: Db): Promise<WorkstreamSummary[]> {\n const dbNames = new Set<string>(\n (db.prepare(\"SELECT name FROM workstreams\").all() as { name: string }[]).map((r) => r.name),\n );\n\n const tmuxNames = new Set<string>();\n for (const session of await listSessions()) {\n if (session.name.startsWith(RESERVED_WORKSTREAM_PREFIX))\n tmuxNames.add(session.name.slice(RESERVED_WORKSTREAM_PREFIX.length));\n }\n\n const allNames = Array.from(new Set([...dbNames, ...tmuxNames])).sort();\n return Promise.all(allNames.map((name) => summarizeWorkstream(db, { workstream: name })));\n}\n\n/**\n * Discover every workstream that has no user-meaningful state\n * attached. Two flavours unioned:\n *\n * 1. REGISTERED-empty: a row in `workstreams` with zero tasks,\n * zero agents, zero vcs_workspaces. Tmux\n * session presence and agent_logs entries do NOT disqualify\n * — the session itself was created at init time and contains\n * no agent panes; the events are audit, not state.\n *\n * 2. TMUX-only: a tmux session named `mu-*` with no row in the\n * `workstreams` table. Catches test litter and remnants of a\n * partial destroy where the DB row was wiped but the tmux\n * session survived (or sessions created out-of-band via\n * `tmux new-session -s mu-foo`). The synthetic summary has\n * `registered=false`, all counts 0, and `tmuxAlive=true` (it\n * wouldn't have been surfaced otherwise).\n *\n * The predicate is intentionally narrow on the prefix: only\n * `mu-*` sessions are eligible. Arbitrary tmux sessions the\n * operator created for unrelated work are NEVER matched — mu only\n * owns its own namespace.\n *\n * Used by `mu workstream destroy --empty` to sweep test-litter\n * workstreams in one command (instead of the per-name jq incantation\n * over `mu workstream list --json`).\n *\n * Returns one `WorkstreamSummary` per match, sorted by name (with\n * defensive dedup — a registered-empty and a tmux-only of the same\n * name can't both arise from the same call by construction, but\n * belt-and-braces).\n */\nexport async function listEmptyWorkstreams(db: Db): Promise<WorkstreamSummary[]> {\n const registeredRows = db\n .prepare(\n `SELECT ws.name AS name\n FROM workstreams ws\n LEFT JOIN tasks t ON t.workstream_id = ws.id\n LEFT JOIN agents a ON a.workstream_id = ws.id\n LEFT JOIN vcs_workspaces v ON v.workstream_id = ws.id\n GROUP BY ws.id, ws.name\n HAVING COUNT(DISTINCT t.id) = 0\n AND COUNT(DISTINCT a.id) = 0\n AND COUNT(DISTINCT v.id) = 0\n ORDER BY ws.name`,\n )\n .all() as { name: string }[];\n const registeredEmpty = await Promise.all(\n registeredRows.map((r) => summarizeWorkstream(db, { workstream: r.name })),\n );\n\n // Tmux-only mu-* sessions: enumerate every running tmux session,\n // keep the ones with the `mu-` prefix (strip it to get the\n // would-be workstream name), then subtract names already in the\n // `workstreams` table. The mirror of listWorkstreams above; see\n // its comment for the prefix rationale.\n const dbNames = new Set<string>(\n (db.prepare(\"SELECT name FROM workstreams\").all() as { name: string }[]).map((r) => r.name),\n );\n const tmuxOnlyNames: string[] = [];\n for (const session of await listSessions()) {\n if (!session.name.startsWith(\"mu-\")) continue;\n const name = session.name.slice(RESERVED_WORKSTREAM_PREFIX.length);\n if (dbNames.has(name)) continue;\n tmuxOnlyNames.push(name);\n }\n const tmuxOnly = await Promise.all(\n tmuxOnlyNames.map((name) => summarizeWorkstream(db, { workstream: name })),\n );\n\n // Compose + sort + dedup-by-name (defensive; no overlap is possible\n // by construction since tmuxOnlyNames excludes every dbName).\n const seen = new Set<string>();\n const all: WorkstreamSummary[] = [];\n for (const ws of [...registeredEmpty, ...tmuxOnly]) {\n if (seen.has(ws.name)) continue;\n seen.add(ws.name);\n all.push(ws);\n }\n all.sort((a, b) => a.name.localeCompare(b.name));\n return all;\n}\n\nexport async function summarizeWorkstream(\n db: Db,\n opts: WorkstreamOptions,\n): Promise<WorkstreamSummary> {\n const tmuxSession = opts.tmuxSession ?? `mu-${opts.workstream}`;\n return {\n name: opts.workstream,\n tmuxSession,\n tmuxAlive: await sessionExists(tmuxSession),\n agentCount: countAgents(db, opts.workstream),\n taskCount: countTasks(db, opts.workstream),\n noteCount: countNotes(db, opts.workstream),\n edgeCount: countEdges(db, opts.workstream),\n workspaceCount: listWorkspaces(db, opts.workstream).length,\n registered: isRegistered(db, opts.workstream),\n };\n}\n\nfunction isRegistered(db: Db, workstream: string): boolean {\n const row = db.prepare(\"SELECT 1 AS x FROM workstreams WHERE name = ?\").get(workstream) as\n | { x: number }\n | undefined;\n return row !== undefined;\n}\n\n/**\n * Tear down a workstream: kill its tmux session and delete every DB row\n * tagged with its name. Cascades on `tasks` clean up `task_edges` and\n * `task_notes` automatically (FK ON DELETE CASCADE in the schema).\n *\n * Idempotent: safe to call against a workstream that never existed; safe\n * to call repeatedly. Returns counts so the caller can print a useful\n * summary.\n */\nexport async function destroyWorkstream(\n db: Db,\n opts: DestroyWorkstreamOptions,\n): Promise<DestroyResult> {\n const tmuxSession = opts.tmuxSession ?? `mu-${opts.workstream}`;\n\n // Pre-mutation snapshot (snap_design §EDGE CASES > WORKSTREAM\n // DESTROY). workstream=null because workstream-destroy snapshots\n // logically span every workstream in the DB (whole-DB backup;\n // anchoring to one name would lie about scope). If the snapshot\n // throws (disk full, perms), abort the destroy — better to refuse\n // than to delete irrecoverably.\n if (opts.suppressSnapshot !== true) {\n captureSnapshot(db, `workstream destroy ${opts.workstream}`, null);\n }\n\n // Pre-count the cascade victims so we can report them — SQLite's\n // changes() only reports rows directly affected by the last statement,\n // not cascade victims.\n const agentsBefore = countAgents(db, opts.workstream);\n const tasksBefore = countTasks(db, opts.workstream);\n const notesBefore = countNotes(db, opts.workstream);\n const edgesBefore = countEdges(db, opts.workstream);\n const workspacesBefore = listWorkspaces(db, opts.workstream);\n\n // Tmux first: if killSession throws we don't want the DB rows already\n // gone with no way to recover. (killSession is itself idempotent on\n // missing sessions — a real throw here is an unexpected tmux error.)\n const tmuxAliveBefore = await sessionExists(tmuxSession);\n if (tmuxAliveBefore) {\n await killSession(tmuxSession);\n }\n\n // Workspaces SECOND, before the FK cascade. The cascade silently\n // deletes vcs_workspaces rows but leaves the on-disk worktrees\n // (and the git worktree registry entries) behind — the bug from\n // mufeedback note #195. Per backend, the right cleanup is\n // 'git worktree remove --force' / 'jj workspace forget' / etc.,\n // not 'rm -rf'. We surface failures so the user can recover; we\n // do NOT abort the destroy on workspace failure (the workstream\n // semantics are 'tear it all down', not 'partial cleanup').\n let freedWorkspaces = 0;\n let alreadyGoneWorkspaces = 0;\n const failedWorkspaces: WorkspaceFailure[] = [];\n const resolveBackend = opts.resolveBackend ?? backendByName;\n for (const ws of workspacesBefore) {\n try {\n const backend = resolveBackend(ws.backend);\n const result = await backend.freeWorkspace({\n workspacePath: ws.path,\n commit: false,\n });\n if (result.removed) {\n // Backend actually removed the on-disk path. This is the\n // only case that counts as 'work done by destroy'.\n freedWorkspaces += 1;\n } else {\n // Path was already gone (manual rm -rf or interrupted prior\n // destroy). The DB row is cascade-deleted below either way,\n // but we don't claim to have freed anything on disk — it was\n // already in the desired state. Tracked separately so the\n // user can spot stale registry rows from past mishaps.\n alreadyGoneWorkspaces += 1;\n }\n } catch (err) {\n failedWorkspaces.push({\n agent: ws.agentName,\n backend: ws.backend,\n path: ws.path,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // After every per-agent worktree is freed, the parent\n // <state>/workspaces/<workstream>/ directory is empty — reap it\n // too. Best-effort: rmdir refuses if non-empty (e.g. backend\n // removal failed and left files behind), which is the right\n // outcome (don't silently rm -rf user data). Skipped if the\n // parent doesn't exist (workstream never had any workspaces).\n const parentDir = join(defaultStateDir(), \"workspaces\", opts.workstream);\n if (existsSync(parentDir)) {\n try {\n if (readdirSync(parentDir).length === 0) rmdirSync(parentDir);\n } catch {\n // Non-empty or otherwise unreapable. The failed-workspaces\n // list above already tells the user what to clean.\n }\n }\n\n // One DELETE: the FK CASCADE chain (workstreams → agents,\n // workstreams → tasks → task_edges + task_notes, workstreams →\n // agent_logs, workstreams → vcs_workspaces) cleans every row in\n // one shot, atomically. If the workstream was never registered\n // (e.g. an orphan tmux session that mu never observed),\n // changes() = 0 and we still report the killed tmux session\n // honestly.\n const result = db.prepare(\"DELETE FROM workstreams WHERE name = ?\").run(opts.workstream);\n // The destroy event itself goes to workstream=null (machine-wide)\n // because the FK CASCADE we just triggered would otherwise wipe\n // it on the same statement. Visible via `mu log --all`.\n if (result.changes > 0 || tmuxAliveBefore) {\n emitEvent(\n db,\n null,\n `workstream destroy ${opts.workstream} (agents=${agentsBefore}, tasks=${tasksBefore}, edges=${edgesBefore}, notes=${notesBefore}, workspaces=${freedWorkspaces}/${workspacesBefore.length}, already_gone=${alreadyGoneWorkspaces}, tmux=${tmuxAliveBefore})`,\n );\n }\n\n return {\n killedTmux: tmuxAliveBefore,\n deletedAgents: agentsBefore,\n deletedTasks: tasksBefore,\n deletedNotes: notesBefore,\n deletedEdges: edgesBefore,\n freedWorkspaces,\n alreadyGoneWorkspaces,\n failedWorkspaces,\n };\n}\n\n// ─── Counts ────────────────────────────────────────────────────────────\n\nfunction countAgents(db: Db, workstream: string): number {\n const row = db\n .prepare(\n `SELECT COUNT(*) AS n FROM agents a\n JOIN workstreams ws ON ws.id = a.workstream_id\n WHERE ws.name = ?`,\n )\n .get(workstream) as { n: number };\n return row.n;\n}\n\nfunction countTasks(db: Db, workstream: string): number {\n const row = db\n .prepare(\n `SELECT COUNT(*) AS n FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE ws.name = ?`,\n )\n .get(workstream) as { n: number };\n return row.n;\n}\n\nfunction countNotes(db: Db, workstream: string): number {\n const row = db\n .prepare(\n `SELECT COUNT(*) AS n\n FROM task_notes n\n JOIN tasks t ON t.id = n.task_id\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE ws.name = ?`,\n )\n .get(workstream) as { n: number };\n return row.n;\n}\n\nfunction countEdges(db: Db, workstream: string): number {\n // Count edges whose blocker (from_task) is in the workstream. Since\n // cross-workstream edges are forbidden by addTask, this equals the\n // edge count for the workstream subgraph.\n const row = db\n .prepare(\n `SELECT COUNT(*) AS n\n FROM task_edges e\n JOIN tasks t ON t.id = e.from_task_id\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE ws.name = ?`,\n )\n .get(workstream) as { n: number };\n return row.n;\n}\n\n// ─── exportWorkstream ──────────────────────────────────────────────────\n//\n// Thin sugar over `renderToBucket` (src/exporting.ts): one live\n// workstream → one ExportSource → bucket render. The renderer holds\n// every byte of layout knowledge; this wrapper just adapts the SDK\n// reads (listTasks / getTaskEdges / listNotes / latestSeq) and\n// emits the workstream-flavoured event.\n//\n// The on-disk shape is the v0.3 BUCKET layout (see src/exporting.ts):\n//\n// <outDir>/\n// README.md / INDEX.md / manifest.json # bucket-level\n// <workstream>/\n// README.md / INDEX.md / tasks/<id>.md # per-source-ws\n//\n// Re-export against the same outDir is additive: a different `-w`\n// adds a sibling subdir without touching the existing one. A re-run\n// with the same `-w` refreshes that subdir (sha256 short-circuit).\n//\n// Anti-features (preserved from the originating design note):\n// - re-import: out of scope\n// - HTML/PDF: markdown-only\n// - embedded VCS: caller can `git init && git add . && git commit`\n// - cross-workstream merge: source-ws subdirs stay separate\n\nexport interface ExportWorkstreamOptions {\n workstream: string;\n /** Output directory (the bucket). Defaults to `./<workstream>/`\n * in the cwd — i.e. the bucket and its single source-ws subdir\n * share a name. */\n outDir?: string;\n}\n\nexport interface ExportResult {\n outDir: string;\n /** Per-task files rewritten this call. */\n written: number;\n /** Per-task files sha256-skipped this call. */\n unchanged: number;\n /** Tasks present in a prior manifest that are no longer in the DB.\n * Their .md stays on disk; a banner is added once. */\n preserved: number;\n manifestPath: string;\n manifest: ExportManifest;\n /** Per-source-ws manifest entry for this workstream — convenience\n * for callers who only want one source's view. */\n source: ExportSourceManifest;\n}\n\n/**\n * Export one live workstream to a bucket directory. Idempotent +\n * additive: re-exporting the same workstream is sha256-skipped,\n * exporting a different workstream into the same bucket appends a\n * sibling subdir.\n *\n */\nexport function exportWorkstream(db: Db, opts: ExportWorkstreamOptions): ExportResult {\n const outDir = resolve(opts.outDir ?? join(process.cwd(), opts.workstream));\n const source = exportSourceForWorkstream(db, opts.workstream);\n const result = renderToBucket({\n sources: [source],\n bucketLabel: null,\n outDir,\n });\n const sourceManifest = result.manifest.sources[opts.workstream];\n if (!sourceManifest) {\n // Defensive: renderToBucket always inserts a manifest entry per\n // source it received. If this ever fires the renderer regressed.\n throw new Error(\n `exportWorkstream: renderer did not write a manifest entry for ${opts.workstream}`,\n );\n }\n emitEvent(\n db,\n opts.workstream,\n `workstream export ${opts.workstream} (out=${result.outDir}, tasks=${source.tasks.length}, written=${result.written}, unchanged=${result.unchanged}, preserved=${result.preserved})`,\n );\n return {\n outDir: result.outDir,\n written: result.written,\n unchanged: result.unchanged,\n preserved: result.preserved,\n manifestPath: result.manifestPath,\n manifest: result.manifest,\n source: sourceManifest,\n };\n}\n","// mu — unified bucket renderer for workstream / archive exports.\n//\n// One renderer, two entry points (`mu workstream export` and\n// `mu archive export`). Both produce the same on-disk shape: a\n// \"bucket\" directory whose top-level contains a bucket-wide README +\n// INDEX + manifest, and one subdirectory per source workstream that\n// holds the per-workstream README + INDEX + tasks/<id>.md files.\n//\n// The bucket layout is ADDITIVE: re-running `mu workstream export\n// -w X --out <bucket>` over an existing bucket either appends a new\n// source-ws subdirectory (if X wasn't there before) or refreshes the\n// existing subdirectory's contents in place (sha256 short-circuit).\n// Source-ws subdirectories from earlier exports are NEVER touched\n// by an unrelated source-ws's re-export.\n//\n// Disk shape (`bucketVersion: 2`):\n//\n// <bucket>/\n// README.md # bucket-level summary (every source-ws + dates + totals)\n// INDEX.md # union of all task tables; first column = source-ws\n// manifest.json # bucketVersion: 2 + per-source-ws sha256 + per-task sha256\n// <source-ws>/\n// README.md # per-source-ws (counts)\n// INDEX.md # per-source-ws (table of every task)\n// tasks/<id>.md # one .md per task; YAML frontmatter + notes\n//\n// Origin: this code was lifted out of `src/workstream.ts`'s\n// `exportWorkstream` (single-source rendering) and generalised to N\n// sources. The single-source case is preserved as a thin wrapper\n// (see exportWorkstream in src/workstream.ts) that builds a one-\n// element `sources` array and delegates here.\n\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { listArchivedTasks } from \"./archives.js\";\nimport type { Db } from \"./db.js\";\nimport { emitEvent, latestSeq } from \"./logs.js\";\nimport { getTaskEdges, listNotes, listTasks } from \"./tasks.js\";\nimport type { TaskNoteRow, TaskRow } from \"./tasks.js\";\nimport { isTaskStatus } from \"./tasks/status.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport const EXPORT_MANIFEST_VERSION = 2;\n\n/** One per-task summary inside a per-source-ws section of the manifest. */\nexport interface ExportTaskEntry {\n /** Task local_id == filename stem (`<id>.md`). Kept for v1 manifest compatibility. */\n id: string;\n /** Task local_id, duplicated under the operator-facing SDK name so bucket INDEX can render from manifest alone. */\n name: string;\n /** Compact summary fields needed for bucket-level INDEX.md without re-reading the DB. */\n title: string;\n status: TaskRow[\"status\"];\n impact: number;\n effortDays: number;\n /** Path relative to the bucket root (e.g. `auth/tasks/design.md`). */\n path: string;\n /** sha256 of the markdown body bytes; idempotency key. */\n sha256: string;\n /** ISO timestamp of the first observed export at which the task\n * was missing from the source. Absent for tasks still present. */\n deletedAt?: string;\n}\n\n/** Per-source-ws entry under `manifest.sources`. */\nexport interface ExportSourceManifest {\n /** ISO timestamp the source was first added to the bucket. */\n addedAt: string;\n /** ISO timestamp of the most recent re-export of this source. */\n lastReExportedAt: string;\n /** `latestSeq(db)` at the most recent re-export; for live workstreams\n * this is the live `agent_logs.seq` cursor. For archive sources\n * there is no equivalent live counter — we record the seq at\n * archive-add time when available, else 0. */\n eventsSeqAtExport: number;\n /** Per-task entries; sorted by id for stable diffs. */\n tasks: ExportTaskEntry[];\n}\n\n/** Top-level bucket manifest. `bucketVersion: 2` — the v0.3 disk layout.\n * `manifest_version` is the schema of the manifest JSON payload itself:\n * v1 lacked task summaries, v2 stores enough per-task data to render\n * bucket INDEX.md from `manifest.sources` alone. Manifests without\n * `bucketVersion: 2` fall through to the `corrupt` lane in `readManifest`. */\nexport interface ExportManifest {\n /** Disk-layout discriminator. Always 2 in this codebase. */\n bucketVersion: 2;\n /** Manifest-payload discriminator. Always 2 when written by this codebase. */\n manifest_version: typeof EXPORT_MANIFEST_VERSION;\n /** Operator-chosen bucket label (an archive label, or null for a\n * one-shot `mu workstream export`). Surfaced in README only. */\n bucketLabel: string | null;\n bucketCreatedAt: string;\n bucketLastUpdatedAt: string;\n muVersion: string;\n /** Per-source-ws map; key is the source workstream's TEXT name. */\n sources: Record<string, ExportSourceManifest>;\n}\n\n/** One source's worth of input: the per-task data the renderer needs.\n * Both entry points (workstream / archive) collapse to this shape. */\nexport interface ExportSource {\n /** Source workstream name. Becomes the subdirectory name. */\n name: string;\n tasks: TaskRow[];\n /** Per-task edges keyed on task name. Missing keys → no edges. */\n edges: Map<string, { blockers: string[]; dependents: string[] }>;\n /** Per-task notes keyed on task name. Missing keys → no notes. */\n notes: Map<string, TaskNoteRow[]>;\n /** `agent_logs.seq` cursor at this source's snapshot moment. 0 for\n * archive sources (no live cursor). */\n eventsSeqAtExport: number;\n}\n\nexport interface RenderBucketInput {\n sources: ExportSource[];\n /** Operator-chosen archive label, or null for a workstream export. */\n bucketLabel: string | null;\n outDir: string;\n}\n\nexport interface RenderBucketResult {\n outDir: string;\n /** Per-source-ws stat: how many task files were rewritten across\n * every source in this call. */\n written: number;\n /** Per-source-ws stat: how many task files were sha256-skipped. */\n unchanged: number;\n /** Per-source-ws stat: how many task files exist for a task that\n * has since vanished from the source. Banner is added once. */\n preserved: number;\n manifestPath: string;\n manifest: ExportManifest;\n}\n\n// ─── Markdown render helpers (per-task) ──────────────────────────────\n\n/** Wrap arbitrary text in a fenced code block, choosing a fence\n * longer than any backtick run inside `body` so the body's literal\n * ``` (or ````, etc.) survives intact. Used for note content,\n * which routinely contains markdown / code / triple-fences. */\nexport function fenceForBody(body: string): string {\n const longestRun = (body.match(/`+/g) ?? []).reduce((m, s) => Math.max(m, s.length), 0);\n return \"`\".repeat(Math.max(3, longestRun + 1));\n}\n\n/** YAML-ish scalar quote: always double-quoted, with `\"` and `\\\\`\n * escaped. Multi-line values are coerced to single-line by\n * replacing newlines with ` ` so the frontmatter block stays\n * valid YAML. */\nexport function yamlScalar(value: string): string {\n return `\"${value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \" \")}\"`;\n}\n\nexport function renderTaskMarkdown(\n task: TaskRow,\n edges: { blockers: string[]; dependents: string[] },\n notes: TaskNoteRow[],\n): string {\n const lines: string[] = [];\n lines.push(\"---\");\n lines.push(`id: ${yamlScalar(task.name)}`);\n lines.push(`workstream: ${yamlScalar(task.workstreamName)}`);\n lines.push(`status: ${task.status}`);\n lines.push(`impact: ${task.impact}`);\n lines.push(`effort_days: ${task.effortDays}`);\n // ROI is derived but a load-bearing field for operators ranking\n // closed tasks in retrospect; emit it precomputed so consumers\n // don't have to re-derive.\n lines.push(`roi: ${(task.impact / task.effortDays).toFixed(2)}`);\n lines.push(`owner: ${task.ownerName === null ? \"null\" : yamlScalar(task.ownerName)}`);\n lines.push(`created_at: ${yamlScalar(task.createdAt)}`);\n lines.push(`updated_at: ${yamlScalar(task.updatedAt)}`);\n lines.push(`blocked_by: [${edges.blockers.map(yamlScalar).join(\", \")}]`);\n lines.push(`blocks: [${edges.dependents.map(yamlScalar).join(\", \")}]`);\n lines.push(\"---\");\n lines.push(\"\");\n lines.push(`# ${task.title}`);\n lines.push(\"\");\n if (notes.length === 0) {\n lines.push(\"_No notes._\");\n lines.push(\"\");\n } else {\n lines.push(`## Notes (${notes.length})`);\n lines.push(\"\");\n for (const [i, note] of notes.entries()) {\n const author = note.author === null ? \"null\" : yamlScalar(note.author);\n lines.push(`### #${i + 1} by ${author}, ${note.createdAt}`);\n lines.push(\"\");\n const fence = fenceForBody(note.content);\n lines.push(fence);\n lines.push(note.content);\n lines.push(fence);\n lines.push(\"\");\n }\n }\n // Trailing newline so POSIX tools (and git diff) don't complain.\n return `${lines.join(\"\\n\")}`.replace(/\\n*$/, \"\\n\");\n}\n\n/** Per-source-ws INDEX.md — one row per task in this source. */\nexport function renderSourceIndexMarkdown(workstream: string, tasks: TaskRow[]): string {\n const lines: string[] = [];\n lines.push(`# ${workstream} — task index`);\n lines.push(\"\");\n if (tasks.length === 0) {\n lines.push(\"_No tasks._\");\n lines.push(\"\");\n return lines.join(\"\\n\");\n }\n lines.push(\"| id | status | impact | effort | ROI | title |\");\n lines.push(\"| --- | --- | --- | --- | --- | --- |\");\n for (const t of tasks) {\n const roi = (t.impact / t.effortDays).toFixed(2);\n const title = t.title.replace(/\\|/g, \"\\\\|\");\n lines.push(\n `| [\\`${t.name}\\`](tasks/${t.name}.md) | ${t.status} | ${t.impact} | ${t.effortDays} | ${roi} | ${title} |`,\n );\n }\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** Per-source-ws README.md — counts and pointer to INDEX.md. */\nexport function renderSourceReadmeMarkdown(\n workstream: string,\n tasks: TaskRow[],\n exportedAt: string,\n): string {\n const counts: Record<string, number> = {\n OPEN: 0,\n IN_PROGRESS: 0,\n CLOSED: 0,\n REJECTED: 0,\n DEFERRED: 0,\n };\n for (const t of tasks) counts[t.status] = (counts[t.status] ?? 0) + 1;\n const lines: string[] = [];\n lines.push(`# Source workstream: ${workstream}`);\n lines.push(\"\");\n lines.push(`Exported at: ${exportedAt}`);\n lines.push(\"\");\n lines.push(`- Tasks: ${tasks.length}`);\n for (const status of [\"OPEN\", \"IN_PROGRESS\", \"CLOSED\", \"REJECTED\", \"DEFERRED\"] as const) {\n lines.push(` - ${status}: ${counts[status] ?? 0}`);\n }\n lines.push(\"\");\n lines.push(\"See `INDEX.md` for the task table; one `.md` per task in `tasks/`.\");\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** Bucket-level README.md — multi-source summary. */\nexport function renderBucketReadmeMarkdown(manifest: ExportManifest): string {\n const lines: string[] = [];\n const label = manifest.bucketLabel ?? \"(no label)\";\n lines.push(`# Export bucket: ${label}`);\n lines.push(\"\");\n lines.push(`- Bucket created at: ${manifest.bucketCreatedAt}`);\n lines.push(`- Bucket last updated at: ${manifest.bucketLastUpdatedAt}`);\n lines.push(`- mu version: ${manifest.muVersion}`);\n lines.push(`- Bucket layout version: ${manifest.bucketVersion}`);\n lines.push(`- Manifest version: ${manifest.manifest_version}`);\n lines.push(\"\");\n const sources = Object.entries(manifest.sources).sort(([a], [b]) => a.localeCompare(b));\n lines.push(`## Sources (${sources.length})`);\n lines.push(\"\");\n if (sources.length === 0) {\n lines.push(\"_No sources yet._\");\n lines.push(\"\");\n } else {\n lines.push(\"| source workstream | tasks | added | last re-exported |\");\n lines.push(\"| --- | --- | --- | --- |\");\n for (const [name, src] of sources) {\n lines.push(\n `| [\\`${name}\\`](${name}/README.md) | ${src.tasks.length} | ${src.addedAt} | ${src.lastReExportedAt} |`,\n );\n }\n lines.push(\"\");\n }\n lines.push(\n \"_Bucket exports are additive: re-running `mu workstream export -w <ws> --out <this-dir>` appends or refreshes one source-ws subdirectory; `mu archive export <label> --out <this-dir>` (re)builds every source-ws from the named archive. See `INDEX.md` for the cross-source task table and `manifest.json` for per-task sha256s._\",\n );\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** Bucket-level INDEX.md — union of every source-ws's task table,\n * with a leading source-ws column to disambiguate cross-source. */\nfunction taskEntryName(entry: Pick<ExportTaskEntry, \"id\"> & { name?: string }): string {\n return entry.name ?? entry.id;\n}\n\nfunction taskEntryFromTask(task: TaskRow, path: string, sha256: string): ExportTaskEntry {\n return {\n id: task.name,\n name: task.name,\n title: task.title,\n status: task.status,\n impact: task.impact,\n effortDays: task.effortDays,\n path,\n sha256,\n };\n}\n\nexport function renderBucketIndexMarkdown(manifest: ExportManifest): string {\n const lines: string[] = [];\n const label = manifest.bucketLabel ?? \"(no label)\";\n lines.push(`# ${label} — task index (all sources)`);\n lines.push(\"\");\n const sourcesWithTasks = Object.entries(manifest.sources)\n .map(([name, source]) => ({\n name,\n tasks: source.tasks.filter((task) => task.deletedAt === undefined),\n }))\n .filter((source) => source.tasks.length > 0);\n if (sourcesWithTasks.length === 0) {\n lines.push(\"_No tasks._\");\n lines.push(\"\");\n return lines.join(\"\\n\");\n }\n lines.push(\"| source-ws | id | status | impact | effort | ROI | title |\");\n lines.push(\"| --- | --- | --- | --- | --- | --- | --- |\");\n // Stable sort across sources: source name then task name.\n const sortedSources = sourcesWithTasks.sort((a, b) => a.name.localeCompare(b.name));\n for (const src of sortedSources) {\n for (const t of src.tasks) {\n const name = taskEntryName(t);\n const roi = (t.impact / t.effortDays).toFixed(2);\n const title = t.title.replace(/\\|/g, \"\\\\|\");\n lines.push(\n `| ${src.name} | [\\`${name}\\`](${t.path}) | ${t.status} | ${t.impact} | ${t.effortDays} | ${roi} | ${title} |`,\n );\n }\n }\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n// ─── Deletion banner ─────────────────────────────────────────────────\n\nexport const DELETED_BANNER_PREFIX = \"> **Deleted from DB on \";\n\nexport function bannerFor(timestamp: string): string {\n return `${DELETED_BANNER_PREFIX}${timestamp}** — this task no longer exists in mu's database. The export below is the last-known state. Re-export will not regenerate it.\\n\\n`;\n}\n\n// ─── manifest.json read/parse ────────────────────────────────────────\n\n/** Read an existing bucket manifest. Returns `{ kind: \"v2\", manifest }`\n * for a v0.3+ bucket; `{ kind: \"absent\" }` if the file doesn't\n * exist; `{ kind: \"corrupt\" }` for anything else. The pre-0.3\n * (single-source, top-level `workstream` + `tasks`) shape is no\n * longer recognized — v0.3 shipped 2026-05-10 and there are no\n * pre-v0.3 buckets in the wild to keep a detection branch for. */\nexport type ManifestProbe =\n | { kind: \"v2\"; manifest: ExportManifest }\n | { kind: \"absent\" }\n | { kind: \"corrupt\" };\n\nexport function readManifest(path: string): ManifestProbe {\n if (!existsSync(path)) return { kind: \"absent\" };\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return { kind: \"corrupt\" };\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return { kind: \"corrupt\" };\n }\n if (typeof parsed !== \"object\" || parsed === null) return { kind: \"corrupt\" };\n const obj = parsed as Record<string, unknown>;\n if (obj.bucketVersion === 2 && typeof obj.sources === \"object\" && obj.sources !== null) {\n const manifest = migrateManifest(obj, dirname(path));\n return manifest ? { kind: \"v2\", manifest } : { kind: \"corrupt\" };\n }\n return { kind: \"corrupt\" };\n}\n\nfunction migrateManifest(obj: Record<string, unknown>, bucketDir: string): ExportManifest | null {\n const rawVersion = obj.manifest_version;\n const version = rawVersion === undefined ? 1 : rawVersion;\n if (version !== 1 && version !== 2) return null;\n const sources = migrateSources(obj.sources, bucketDir);\n if (sources === null) return null;\n return {\n bucketVersion: 2,\n manifest_version: EXPORT_MANIFEST_VERSION,\n bucketLabel:\n typeof obj.bucketLabel === \"string\" || obj.bucketLabel === null ? obj.bucketLabel : null,\n bucketCreatedAt: typeof obj.bucketCreatedAt === \"string\" ? obj.bucketCreatedAt : \"\",\n bucketLastUpdatedAt: typeof obj.bucketLastUpdatedAt === \"string\" ? obj.bucketLastUpdatedAt : \"\",\n muVersion: typeof obj.muVersion === \"string\" ? obj.muVersion : \"unknown\",\n sources,\n };\n}\n\nfunction migrateSources(\n raw: unknown,\n bucketDir: string,\n): Record<string, ExportSourceManifest> | null {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) return null;\n const out: Record<string, ExportSourceManifest> = {};\n for (const [sourceName, value] of Object.entries(raw)) {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return null;\n const source = value as Record<string, unknown>;\n const tasks = Array.isArray(source.tasks)\n ? source.tasks.map((task) => migrateTaskEntry(sourceName, task, bucketDir))\n : [];\n if (tasks.some((task) => task === null)) return null;\n out[sourceName] = {\n addedAt: typeof source.addedAt === \"string\" ? source.addedAt : \"\",\n lastReExportedAt: typeof source.lastReExportedAt === \"string\" ? source.lastReExportedAt : \"\",\n eventsSeqAtExport:\n typeof source.eventsSeqAtExport === \"number\" ? source.eventsSeqAtExport : 0,\n tasks: tasks.filter((task): task is ExportTaskEntry => task !== null),\n };\n }\n return out;\n}\n\nfunction migrateTaskEntry(\n sourceName: string,\n raw: unknown,\n bucketDir: string,\n): ExportTaskEntry | null {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) return null;\n const entry = raw as Record<string, unknown>;\n const id = typeof entry.id === \"string\" ? entry.id : undefined;\n const name = typeof entry.name === \"string\" ? entry.name : id;\n const path = typeof entry.path === \"string\" ? entry.path : `${sourceName}/tasks/${name ?? \"\"}.md`;\n const inferred = inferTaskSummaryFromMarkdown(join(bucketDir, path), name ?? id ?? \"\");\n const title = typeof entry.title === \"string\" ? entry.title : inferred.title;\n const status =\n typeof entry.status === \"string\" && isTaskStatus(entry.status) ? entry.status : inferred.status;\n const impact =\n typeof entry.impact === \"number\" && Number.isFinite(entry.impact)\n ? entry.impact\n : inferred.impact;\n const effortDays =\n typeof entry.effortDays === \"number\" &&\n Number.isFinite(entry.effortDays) &&\n entry.effortDays > 0\n ? entry.effortDays\n : inferred.effortDays;\n const sha256 = typeof entry.sha256 === \"string\" ? entry.sha256 : \"\";\n if (!id || !name) return null;\n const migrated: ExportTaskEntry = {\n id,\n name,\n title,\n status,\n impact,\n effortDays,\n path,\n sha256,\n };\n if (typeof entry.deletedAt === \"string\") migrated.deletedAt = entry.deletedAt;\n return migrated;\n}\n\nfunction inferTitleFromPath(path: string, fallback: string): string {\n const stem = basename(path).replace(/\\.md$/, \"\");\n return stem || fallback || \"(unknown title; manifest v1 fallback)\";\n}\n\nfunction inferTaskSummaryFromMarkdown(\n path: string,\n fallbackName: string,\n): Pick<ExportTaskEntry, \"title\" | \"status\" | \"impact\" | \"effortDays\"> {\n const fallback = {\n title: inferTitleFromPath(path, fallbackName),\n status: \"OPEN\" as TaskRow[\"status\"],\n impact: 0,\n effortDays: 1,\n };\n if (!existsSync(path)) return fallback;\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return fallback;\n }\n const lines = raw.split(\"\\n\");\n const firstFence = lines.findIndex((line) => line === \"---\");\n if (firstFence < 0) return fallback;\n let secondFence = -1;\n for (let i = firstFence + 1; i < lines.length; i += 1) {\n if (lines[i] === \"---\") {\n secondFence = i;\n break;\n }\n }\n if (secondFence < 0) return fallback;\n const fields: Record<string, string> = {};\n for (let i = firstFence + 1; i < secondFence; i += 1) {\n const line = lines[i] ?? \"\";\n const colon = line.indexOf(\":\");\n if (colon < 0) continue;\n fields[line.slice(0, colon).trim()] = line.slice(colon + 1).trim();\n }\n const status = fields.status && isTaskStatus(fields.status) ? fields.status : fallback.status;\n const impact = Number(fields.impact);\n const effortDays = Number(fields.effort_days);\n const titleLine = lines.slice(secondFence + 1).find((line) => line.startsWith(\"# \"));\n return {\n title: titleLine ? titleLine.slice(2).trim() : fallback.title,\n status,\n impact: Number.isFinite(impact) ? impact : fallback.impact,\n effortDays: Number.isFinite(effortDays) && effortDays > 0 ? effortDays : fallback.effortDays,\n };\n}\n\n// ─── sha256 + mu version ─────────────────────────────────────────────\n\nexport function sha256Hex(content: string): string {\n return createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\");\n}\n\n/** Read the package.json shipped next to the bundled CLI (or src/) so\n * the manifest records the mu version that produced it. Falls back\n * to \"unknown\" if the file isn't reachable. */\nexport function readMuVersion(): string {\n try {\n const here = dirname(fileURLToPath(import.meta.url));\n const raw = readFileSync(join(here, \"..\", \"package.json\"), \"utf8\");\n const parsed = JSON.parse(raw) as { version?: unknown };\n return typeof parsed.version === \"string\" ? parsed.version : \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\n// ─── Renderer ────────────────────────────────────────────────────────\n\n/**\n * Render `input.sources` to disk under `input.outDir` in the v0.3\n * bucket layout. Idempotent + additive:\n * - If the bucket doesn't exist, scaffold it.\n * - If it does exist with bucketVersion 2, MERGE: each source in\n * `input.sources` either appends (new) or refreshes (existing)\n * its subdirectory; sources NOT in `input.sources` are left\n * untouched.\n *\n * Per-task idempotency is sha256-keyed: a re-export of the same\n * source against an unchanged DB rewrites zero task files. Tasks\n * that disappear from a source between re-exports are preserved on\n * disk with a one-time `> **Deleted from DB on <ts>**` banner.\n */\nexport function renderToBucket(input: RenderBucketInput): RenderBucketResult {\n const outDir = input.outDir;\n if (existsSync(outDir)) {\n const stat = statSync(outDir);\n if (!stat.isDirectory()) {\n throw new Error(`renderToBucket: outDir exists and is not a directory: ${outDir}`);\n }\n } else {\n mkdirSync(outDir, { recursive: true });\n }\n\n const manifestPath = join(outDir, \"manifest.json\");\n const probe = readManifest(manifestPath);\n\n const now = new Date().toISOString();\n const muVersion = readMuVersion();\n const previous: ExportManifest | undefined = probe.kind === \"v2\" ? probe.manifest : undefined;\n // Start the new manifest from the previous one (so untouched\n // sources keep their entries) or a fresh scaffold.\n const manifest: ExportManifest = previous\n ? {\n bucketVersion: 2,\n manifest_version: EXPORT_MANIFEST_VERSION,\n bucketLabel: input.bucketLabel ?? previous.bucketLabel,\n bucketCreatedAt: previous.bucketCreatedAt,\n bucketLastUpdatedAt: now,\n muVersion,\n sources: { ...previous.sources },\n }\n : {\n bucketVersion: 2,\n manifest_version: EXPORT_MANIFEST_VERSION,\n bucketLabel: input.bucketLabel,\n bucketCreatedAt: now,\n bucketLastUpdatedAt: now,\n muVersion,\n sources: {},\n };\n\n let writtenTotal = 0;\n let unchangedTotal = 0;\n let preservedTotal = 0;\n\n for (const source of input.sources) {\n const sourceDir = join(outDir, source.name);\n const tasksDir = join(sourceDir, \"tasks\");\n mkdirSync(tasksDir, { recursive: true });\n\n const previousSource = previous?.sources[source.name];\n const previousById = new Map<string, ExportTaskEntry>();\n if (previousSource) {\n for (const t of previousSource.tasks) previousById.set(taskEntryName(t), t);\n }\n\n const liveIds = new Set(source.tasks.map((t) => t.name));\n const manifestEntries: ExportTaskEntry[] = [];\n let written = 0;\n let unchanged = 0;\n let preserved = 0;\n\n for (const task of source.tasks) {\n const edges = source.edges.get(task.name) ?? { blockers: [], dependents: [] };\n const notes = source.notes.get(task.name) ?? [];\n const md = renderTaskMarkdown(task, edges, notes);\n const sha = sha256Hex(md);\n const relPath = `${source.name}/tasks/${task.name}.md`;\n const absPath = join(outDir, relPath);\n\n const prev = previousById.get(task.name);\n const onDisk = existsSync(absPath);\n if (onDisk && prev?.sha256 === sha && prev.deletedAt === undefined) {\n unchanged += 1;\n } else {\n writeFileSync(absPath, md, \"utf8\");\n written += 1;\n }\n manifestEntries.push(taskEntryFromTask(task, relPath, sha));\n }\n\n // Preserve files for tasks that disappeared from the source.\n // Banner is one-time (idempotent across re-exports).\n for (const prev of previousById.values()) {\n if (liveIds.has(taskEntryName(prev))) continue;\n const absPath = join(outDir, prev.path);\n const deletedAt = prev.deletedAt ?? now;\n if (existsSync(absPath)) {\n const existing = readFileSync(absPath, \"utf8\");\n if (!existing.startsWith(DELETED_BANNER_PREFIX)) {\n writeFileSync(absPath, bannerFor(deletedAt) + existing, \"utf8\");\n }\n }\n manifestEntries.push({ ...prev, deletedAt });\n preserved += 1;\n }\n\n // Stable order — diffs across re-exports stay clean.\n manifestEntries.sort((a, b) => taskEntryName(a).localeCompare(taskEntryName(b)));\n\n // Per-source-ws scaffolding (cheap; always rewritten — but the\n // sha256 short-circuit on `tasks/<id>.md` is what matters for\n // mtime stability of the operator-visible files).\n writeFileSync(\n join(sourceDir, \"README.md\"),\n renderSourceReadmeMarkdown(source.name, source.tasks, now),\n \"utf8\",\n );\n writeFileSync(\n join(sourceDir, \"INDEX.md\"),\n renderSourceIndexMarkdown(source.name, source.tasks),\n \"utf8\",\n );\n\n manifest.sources[source.name] = {\n addedAt: previousSource?.addedAt ?? now,\n lastReExportedAt: now,\n eventsSeqAtExport: source.eventsSeqAtExport,\n tasks: manifestEntries,\n };\n\n writtenTotal += written;\n unchangedTotal += unchanged;\n preservedTotal += preserved;\n }\n\n // Bucket-level scaffolding covers EVERY source-ws in the merged\n // manifest, not just the ones refreshed by this call. Manifest v2\n // carries compact task summaries so INDEX.md can remain a true\n // cross-source union after additive one-workstream re-exports.\n const bucketReadme = renderBucketReadmeMarkdown(manifest);\n const bucketIndex = renderBucketIndexMarkdown(manifest);\n writeFileSync(join(outDir, \"README.md\"), bucketReadme, \"utf8\");\n writeFileSync(join(outDir, \"INDEX.md\"), bucketIndex, \"utf8\");\n\n writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`, \"utf8\");\n\n return {\n outDir,\n written: writtenTotal,\n unchanged: unchangedTotal,\n preserved: preservedTotal,\n manifestPath,\n manifest,\n };\n}\n\n// ─── Source builders ──────────────────────────────────────────────────\n\n/** Construct an ExportSource for one live workstream by reading the\n * current DB. Pure data assembly; renderer does the I/O. */\nexport function exportSourceForWorkstream(db: Db, workstream: string): ExportSource {\n const tasks = listTasks(db, workstream);\n const edges = new Map<string, { blockers: string[]; dependents: string[] }>();\n const notes = new Map<string, TaskNoteRow[]>();\n for (const t of tasks) {\n edges.set(t.name, getTaskEdges(db, t.name, t.workstreamName));\n notes.set(t.name, listNotes(db, t.name, t.workstreamName));\n }\n return {\n name: workstream,\n tasks,\n edges,\n notes,\n eventsSeqAtExport: latestSeq(db),\n };\n}\n\n/** Construct ExportSources for every source workstream that\n * contributed to an archive label. One ExportSource per\n * (archive_id, source_workstream) partition. The TaskRow shapes are\n * reconstructed from archived_* rows; `workstreamName` is set to\n * the source workstream so the rendered frontmatter reflects the\n * task's original home. */\nexport function exportSourcesForArchive(db: Db, label: string): ExportSource[] {\n // Pull every archived task in deterministic (source_workstream,\n // original_local_id) order.\n const allTasks = listArchivedTasks(db, label);\n if (allTasks.length === 0) return [];\n\n // archive_id (resolved internally) — we need it for the edge /\n // note / event queries below. Look it up via the first row's\n // archiveLabel (every row in `allTasks` shares the same archive).\n const archiveIdRow = db.prepare(\"SELECT id FROM archives WHERE label = ?\").get(label) as\n | { id: number }\n | undefined;\n if (!archiveIdRow) return []; // Should be unreachable: listArchivedTasks would have thrown.\n const archiveId = archiveIdRow.id;\n\n // Group tasks by source workstream.\n const bySource = new Map<string, typeof allTasks>();\n for (const t of allTasks) {\n const list = bySource.get(t.sourceWorkstream) ?? [];\n list.push(t);\n bySource.set(t.sourceWorkstream, list);\n }\n\n // Pre-load notes + edges per archived task. Two batched queries\n // is enough — the per-task loops below just dereference.\n const notesByArchivedId = new Map<number, TaskNoteRow[]>();\n const noteRows = db\n .prepare(\n `SELECT archived_task_id AS aid, author, content, created_at\n FROM archived_notes\n WHERE archive_id = ?\n ORDER BY id`,\n )\n .all(archiveId) as {\n aid: number;\n author: string | null;\n content: string;\n created_at: string;\n }[];\n for (const n of noteRows) {\n const list = notesByArchivedId.get(n.aid) ?? [];\n list.push({ author: n.author, content: n.content, createdAt: n.created_at });\n notesByArchivedId.set(n.aid, list);\n }\n\n // Edges: archived endpoint ids → original_local_id strings.\n // Build {from_archived_id → original_local_id} once, then map.\n const localIdByArchivedId = new Map<number, string>();\n for (const t of allTasks) localIdByArchivedId.set(t.id, t.originalLocalId);\n const blockersByArchivedId = new Map<number, string[]>();\n const dependentsByArchivedId = new Map<number, string[]>();\n const edgeRows = db\n .prepare(\n `SELECT from_archived_id AS f, to_archived_id AS t\n FROM archived_edges\n WHERE archive_id = ?`,\n )\n .all(archiveId) as { f: number; t: number }[];\n for (const e of edgeRows) {\n const fromId = localIdByArchivedId.get(e.f);\n const toId = localIdByArchivedId.get(e.t);\n if (fromId === undefined || toId === undefined) continue;\n // edge `from blocks to`: `from` is a blocker of `to`; `to` is\n // a dependent of `from`. (See getTaskEdges in src/tasks.ts.)\n const blockers = blockersByArchivedId.get(e.t) ?? [];\n blockers.push(fromId);\n blockersByArchivedId.set(e.t, blockers);\n const deps = dependentsByArchivedId.get(e.f) ?? [];\n deps.push(toId);\n dependentsByArchivedId.set(e.f, deps);\n }\n\n // archived_events: max(seq) per source workstream gives us the\n // best available \"events seq at archive time\" — the highest seq\n // of any event that contributed to this source's archive.\n const eventSeqRows = db\n .prepare(\n `SELECT source_workstream AS sw, MAX(seq) AS max_seq\n FROM archived_events\n WHERE archive_id = ?\n GROUP BY source_workstream`,\n )\n .all(archiveId) as { sw: string; max_seq: number }[];\n const eventsSeqBySource = new Map<string, number>();\n for (const r of eventSeqRows) eventsSeqBySource.set(r.sw, r.max_seq);\n\n const sources: ExportSource[] = [];\n for (const [sourceName, taskList] of bySource) {\n const tasks: TaskRow[] = taskList.map((t) => ({\n name: t.originalLocalId,\n workstreamName: t.sourceWorkstream,\n title: t.title,\n // Status as snapshotted; cast through the TaskStatus union by\n // way of any narrowed value (the renderer doesn't validate).\n status: t.status as TaskRow[\"status\"],\n impact: t.impact,\n effortDays: t.effortDays,\n ownerName: t.ownerName,\n createdAt: t.originalCreatedAt,\n updatedAt: t.originalUpdatedAt,\n }));\n const edges = new Map<string, { blockers: string[]; dependents: string[] }>();\n const notes = new Map<string, TaskNoteRow[]>();\n for (const t of taskList) {\n const blockers = (blockersByArchivedId.get(t.id) ?? []).sort((a, b) => a.localeCompare(b));\n const dependents = (dependentsByArchivedId.get(t.id) ?? []).sort((a, b) =>\n a.localeCompare(b),\n );\n edges.set(t.originalLocalId, { blockers, dependents });\n const ns = notesByArchivedId.get(t.id);\n if (ns) notes.set(t.originalLocalId, ns);\n }\n sources.push({\n name: sourceName,\n tasks,\n edges,\n notes,\n eventsSeqAtExport: eventsSeqBySource.get(sourceName) ?? 0,\n });\n }\n\n // Stable order across the bucket: source name ascending.\n sources.sort((a, b) => a.name.localeCompare(b.name));\n return sources;\n}\n\n// ─── Public verbs ────────────────────────────────────────────────────\n\nexport interface ExportArchiveOptions {\n label: string;\n /** Output directory (the bucket). Created if missing. */\n outDir: string;\n}\n\nexport interface ExportArchiveResult extends RenderBucketResult {\n archiveLabel: string;\n /** Number of source workstreams the renderer wrote / refreshed. */\n sourceCount: number;\n}\n\n/** Render every source-ws in an archive to a bucket directory.\n * Throws `ArchiveNotFoundError` (via listArchivedTasks) when the\n * label doesn't exist. */\nexport function exportArchive(db: Db, opts: ExportArchiveOptions): ExportArchiveResult {\n // Resolve up-front so a missing label fails before any disk I/O.\n // listArchivedTasks throws ArchiveNotFoundError on miss.\n listArchivedTasks(db, opts.label);\n const sources = exportSourcesForArchive(db, opts.label);\n const result = renderToBucket({\n sources,\n bucketLabel: opts.label,\n outDir: opts.outDir,\n });\n emitEvent(\n db,\n null,\n `archive export ${opts.label} (out=${result.outDir}, sources=${sources.length}, tasks=${sources.reduce((acc, s) => acc + s.tasks.length, 0)}, written=${result.written}, unchanged=${result.unchanged}, preserved=${result.preserved})`,\n );\n return { ...result, archiveLabel: opts.label, sourceCount: sources.length };\n}\n","// mu — archive shared types, label validation, errors, and row helpers.\n\nimport type { Db } from \"../db.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\n\n// ─── Label validation ────────────────────────────────────────────────\n\n/**\n * Allowed archive-label shape: lowercase alpha first, then alnum,\n * underscore, or hyphen, up to 64 chars total. Wider than the\n * workstream-name window (32 chars) because archive labels often\n * encode the workstream name PLUS a date / purpose (\"auth-2026-q1\",\n * \"rewrite-postmortem\").\n */\nconst ARCHIVE_LABEL_RE = /^[a-z][a-z0-9_-]{0,63}$/;\n\n/** True iff `label` matches the archive-label rule. Pure predicate. */\nexport function isValidArchiveLabel(label: string): boolean {\n return ARCHIVE_LABEL_RE.test(label);\n}\n\nexport function assertValidArchiveLabel(label: string): void {\n if (!isValidArchiveLabel(label)) throw new ArchiveLabelInvalidError(label);\n}\n\n// ─── Typed errors ────────────────────────────────────────────────────\n\nexport class ArchiveNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"ArchiveNotFoundError\";\n constructor(public readonly label: string) {\n super(`no such archive: ${label}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List existing archives\", command: \"mu archive list\" },\n {\n intent: \"Create this archive\",\n command: `mu archive add ${this.label} -w <workstream>`,\n },\n ];\n }\n}\n\nexport class ArchiveAlreadyExistsError extends Error implements HasNextSteps {\n override readonly name = \"ArchiveAlreadyExistsError\";\n constructor(public readonly label: string) {\n super(`archive already exists: ${label}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Add a workstream to the existing archive (additive)\",\n command: `mu archive add ${this.label} -w <workstream>`,\n },\n {\n intent: \"Inspect the existing archive\",\n command: `mu archive show ${this.label}`,\n },\n ];\n }\n}\n\nexport class ArchiveLabelInvalidError extends Error implements HasNextSteps {\n override readonly name = \"ArchiveLabelInvalidError\";\n constructor(public readonly attempted: string) {\n super(\n `invalid archive label ${JSON.stringify(attempted)}: must match /^[a-z][a-z0-9_-]{0,63}$/. Use letters, digits, '_', and '-' only; start with a letter; up to 64 chars.`,\n );\n }\n errorNextSteps(): NextStep[] {\n const sanitized =\n this.attempted\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, \"_\")\n .replace(/^[^a-z]+/, \"\")\n .slice(0, 64) || \"archive\";\n return [\n {\n intent: \"Try a sanitized label (best guess)\",\n command: `mu archive add ${sanitized} -w <workstream>`,\n },\n { intent: \"List existing archives\", command: \"mu archive list\" },\n ];\n }\n}\n\n// ─── Domain types ─────────────────────────────────────────────────────\n\nexport interface Archive {\n /** Surrogate INTEGER id. Internal — operators identify by label. */\n id: number;\n /** Globally-unique operator-facing TEXT label. */\n label: string;\n /** Optional one-liner description set at create time. */\n description: string | null;\n /** ISO 8601, set when the archive was first created. */\n createdAt: string;\n /** ISO 8601, bumped on every successful add. */\n lastAddedAt: string;\n}\n\nexport interface ArchiveSourceSummary {\n /** TEXT name of the source workstream this snapshot came from. */\n name: string;\n /** Number of archived_tasks rows from this workstream in this archive. */\n taskCount: number;\n /** Earliest archived_at among this workstream's rows in this archive. */\n addedAt: string;\n}\n\nexport interface ArchiveSummary extends Archive {\n /** One row per source workstream that contributed to this archive,\n * sorted by source workstream name. */\n sourceWorkstreams: ArchiveSourceSummary[];\n /** Total archived_tasks rows across every source workstream. */\n totalTasks: number;\n}\n\nexport interface ArchivedTaskRow {\n /** Surrogate id of the archived_tasks row. */\n id: number;\n /** Operator-facing label of the parent archive. */\n archiveLabel: string;\n /** TEXT name of the source workstream (intentionally not an FK). */\n sourceWorkstream: string;\n /** The local_id the task had in its source workstream. */\n originalLocalId: string;\n title: string;\n /** Status as stored at archive time. */\n status: string;\n impact: number;\n effortDays: number;\n /** Owner agent name as snapshotted at archive time. */\n ownerName: string | null;\n /** Status at the moment of archive (pinned for re-add semantics). */\n archivedAtStatus: string;\n /** ISO 8601, when this row was added to the archive. */\n archivedAt: string;\n /** Original tasks.created_at preserved for retrospect ordering. */\n originalCreatedAt: string;\n /** Original tasks.updated_at preserved for retrospect ordering. */\n originalUpdatedAt: string;\n}\n\nexport interface AddToArchiveResult {\n /** Number of new archived_tasks rows actually inserted. Zero on a\n * re-run against the same workstream (idempotency). */\n addedTasks: number;\n /** Tasks present in the source workstream that were already in the\n * archive (skipped by the OR IGNORE). */\n skippedTasks: number;\n /** Number of new archived_edges rows actually inserted. */\n addedEdges: number;\n /** Number of new archived_notes rows inserted. */\n addedNotes: number;\n /** Number of new archived_events rows inserted. */\n addedEvents: number;\n}\n\nexport interface RemoveFromArchiveResult {\n /** Number of archived_tasks rows deleted (cascade cleans the rest). */\n removedTasks: number;\n /** Number of archived_edges rows removed by the cascade. */\n removedEdges: number;\n /** Number of archived_notes rows removed by the cascade. */\n removedNotes: number;\n /** Number of archived_events rows directly deleted. */\n removedEvents: number;\n}\n\n// ─── Internal row shapes ──────────────────────────────────────────────\n\nexport interface RawArchiveRow {\n id: number;\n label: string;\n description: string | null;\n created_at: string;\n last_added_at: string;\n}\n\nexport function rowFromArchive(r: RawArchiveRow): Archive {\n return {\n id: r.id,\n label: r.label,\n description: r.description,\n createdAt: r.created_at,\n lastAddedAt: r.last_added_at,\n };\n}\n\nexport interface RawArchivedTaskRow {\n id: number;\n archive_label: string;\n source_workstream: string;\n original_local_id: string;\n title: string;\n status: string;\n impact: number;\n effort_days: number;\n owner_name: string | null;\n archived_at_status: string;\n archived_at: string;\n original_created_at: string;\n original_updated_at: string;\n}\n\nexport function rowFromArchivedTask(r: RawArchivedTaskRow): ArchivedTaskRow {\n return {\n id: r.id,\n archiveLabel: r.archive_label,\n sourceWorkstream: r.source_workstream,\n originalLocalId: r.original_local_id,\n title: r.title,\n status: r.status,\n impact: r.impact,\n effortDays: r.effort_days,\n ownerName: r.owner_name,\n archivedAtStatus: r.archived_at_status,\n archivedAt: r.archived_at,\n originalCreatedAt: r.original_created_at,\n originalUpdatedAt: r.original_updated_at,\n };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\n/** Resolve an archive label to its surrogate id. Returns null on miss\n * so callers can pick between throwing (createArchive) and returning\n * empty (listArchivedTasks). archives.label is globally unique\n * (NOT per-workstream) by design — see schema doc in src/db.ts. */\nexport function tryResolveArchiveId(db: Db, label: string): number | null {\n const row = db.prepare(\"SELECT id FROM archives WHERE label = ?\").get(label) as\n | { id: number }\n | undefined;\n return row ? row.id : null;\n}\n\n/** Resolve an archive label to its surrogate id, throwing\n * ArchiveNotFoundError on miss. */\nexport function resolveArchiveId(db: Db, label: string): number {\n const id = tryResolveArchiveId(db, label);\n if (id === null) throw new ArchiveNotFoundError(label);\n return id;\n}\n\nexport function archiveByLabel(db: Db, label: string): Archive | null {\n const row = db\n .prepare(\n \"SELECT id, label, description, created_at, last_added_at FROM archives WHERE label = ?\",\n )\n .get(label) as RawArchiveRow | undefined;\n return row ? rowFromArchive(row) : null;\n}\n\nexport function summarizeArchive(db: Db, archive: Archive): ArchiveSummary {\n const sources = db\n .prepare(\n `SELECT source_workstream AS name,\n COUNT(*) AS task_count,\n MIN(archived_at) AS added_at\n FROM archived_tasks\n WHERE archive_id = ?\n GROUP BY source_workstream\n ORDER BY source_workstream`,\n )\n .all(archive.id) as { name: string; task_count: number; added_at: string }[];\n const sourceWorkstreams: ArchiveSourceSummary[] = sources.map((s) => ({\n name: s.name,\n taskCount: s.task_count,\n addedAt: s.added_at,\n }));\n const totalTasks = sourceWorkstreams.reduce((acc, s) => acc + s.taskCount, 0);\n return { ...archive, sourceWorkstreams, totalTasks };\n}\n","// mu — add/remove workstream snapshots to/from archives.\n\nimport { type Db, resolveWorkstreamId } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport type { AddToArchiveResult, RemoveFromArchiveResult } from \"./core.js\";\nimport { resolveArchiveId } from \"./core.js\";\n\n/**\n * Add every task in `workstream` to the archive identified by `label`.\n *\n * Idempotency invariant: re-running with the same (label, workstream)\n * pair is a no-op for tasks already present. The\n * (archive_id, source_workstream, original_local_id) UNIQUE on\n * archived_tasks is the lever; we INSERT OR IGNORE and skip notes /\n * events for the (archive, source_workstream) pair entirely when the\n * task copy added zero new rows.\n */\nexport function addToArchive(db: Db, label: string, workstream: string): AddToArchiveResult {\n const archiveId = resolveArchiveId(db, label);\n const wsId = resolveWorkstreamId(db, workstream);\n\n return db.transaction(() => {\n const now = new Date().toISOString();\n\n const sourceTasks = db\n .prepare(\n `SELECT t.id AS source_task_id,\n t.local_id AS original_local_id,\n t.title AS title,\n t.status AS status,\n t.impact AS impact,\n t.effort_days AS effort_days,\n ag.name AS owner_name,\n t.created_at AS original_created_at,\n t.updated_at AS original_updated_at\n FROM tasks t\n LEFT JOIN agents ag ON ag.id = t.owner_id\n WHERE t.workstream_id = ?\n ORDER BY t.id`,\n )\n .all(wsId) as {\n source_task_id: number;\n original_local_id: string;\n title: string;\n status: string;\n impact: number;\n effort_days: number;\n owner_name: string | null;\n original_created_at: string;\n original_updated_at: string;\n }[];\n\n const insertTask = db.prepare(\n `INSERT OR IGNORE INTO archived_tasks (\n archive_id, source_workstream, original_local_id, title, status,\n impact, effort_days, owner_name, archived_at_status, archived_at,\n original_created_at, original_updated_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n const lookupArchivedId = db.prepare(\n `SELECT id FROM archived_tasks\n WHERE archive_id = ? AND source_workstream = ? AND original_local_id = ?`,\n );\n\n let addedTasks = 0;\n let skippedTasks = 0;\n const newArchivedIds: number[] = [];\n const archivedIdBySourceId = new Map<number, number>();\n\n for (const t of sourceTasks) {\n const r = insertTask.run(\n archiveId,\n workstream,\n t.original_local_id,\n t.title,\n t.status,\n t.impact,\n t.effort_days,\n t.owner_name,\n t.status,\n now,\n t.original_created_at,\n t.original_updated_at,\n );\n const isNew = r.changes > 0;\n const lookup = lookupArchivedId.get(archiveId, workstream, t.original_local_id) as\n | { id: number }\n | undefined;\n if (!lookup) {\n throw new Error(\n `addToArchive: archived_tasks lookup missed after upsert: ${workstream}/${t.original_local_id}`,\n );\n }\n archivedIdBySourceId.set(t.source_task_id, lookup.id);\n if (isNew) {\n addedTasks += 1;\n newArchivedIds.push(lookup.id);\n } else {\n skippedTasks += 1;\n }\n }\n\n const sourceEdges = db\n .prepare(\n `SELECT e.from_task_id AS from_id, e.to_task_id AS to_id\n FROM task_edges e\n JOIN tasks tf ON tf.id = e.from_task_id\n JOIN tasks tt ON tt.id = e.to_task_id\n WHERE tf.workstream_id = ? AND tt.workstream_id = ?`,\n )\n .all(wsId, wsId) as { from_id: number; to_id: number }[];\n const insertEdge = db.prepare(\n `INSERT OR IGNORE INTO archived_edges (archive_id, from_archived_id, to_archived_id)\n VALUES (?, ?, ?)`,\n );\n let addedEdges = 0;\n for (const e of sourceEdges) {\n const fromArchivedId = archivedIdBySourceId.get(e.from_id);\n const toArchivedId = archivedIdBySourceId.get(e.to_id);\n if (fromArchivedId === undefined || toArchivedId === undefined) continue;\n const r = insertEdge.run(archiveId, fromArchivedId, toArchivedId);\n if (r.changes > 0) addedEdges += 1;\n }\n\n let addedNotes = 0;\n let addedEvents = 0;\n if (newArchivedIds.length > 0) {\n const insertNote = db.prepare(\n `INSERT INTO archived_notes (archive_id, archived_task_id, author, content, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n );\n const noteCopySql = db.prepare(\n `SELECT n.author AS author, n.content AS content, n.created_at AS created_at\n FROM task_notes n\n WHERE n.task_id = ?\n ORDER BY n.id`,\n );\n const sourceIdByArchivedId = new Map<number, number>();\n for (const [sId, aId] of archivedIdBySourceId) sourceIdByArchivedId.set(aId, sId);\n for (const archivedId of newArchivedIds) {\n const sourceId = sourceIdByArchivedId.get(archivedId);\n if (sourceId === undefined) continue;\n const notes = noteCopySql.all(sourceId) as {\n author: string | null;\n content: string;\n created_at: string;\n }[];\n for (const note of notes) {\n insertNote.run(archiveId, archivedId, note.author, note.content, note.created_at);\n addedNotes += 1;\n }\n }\n\n const events = db\n .prepare(\n `SELECT seq, source, payload, created_at\n FROM agent_logs\n WHERE workstream_id = ? AND kind = 'event'\n ORDER BY seq`,\n )\n .all(wsId) as {\n seq: number;\n source: string;\n payload: string;\n created_at: string;\n }[];\n const insertEvent = db.prepare(\n `INSERT INTO archived_events (\n archive_id, source_workstream, seq, source, payload, created_at\n ) VALUES (?, ?, ?, ?, ?, ?)`,\n );\n for (const ev of events) {\n insertEvent.run(archiveId, workstream, ev.seq, ev.source, ev.payload, ev.created_at);\n addedEvents += 1;\n }\n }\n\n db.prepare(\"UPDATE archives SET last_added_at = ? WHERE id = ?\").run(now, archiveId);\n const eventSummary =\n newArchivedIds.length === 0\n ? `tasks=${addedTasks}, edges=${addedEdges}, notes=${addedNotes}, events=0 (snapshot-only; re-add is task-incremental, not event-incremental), skipped_existing=${skippedTasks}`\n : `tasks=${addedTasks}, edges=${addedEdges}, notes=${addedNotes}, events=${addedEvents}, skipped_existing=${skippedTasks}`;\n emitEvent(db, null, `archive add ${label} -w ${workstream} (${eventSummary})`);\n\n return { addedTasks, skippedTasks, addedEdges, addedNotes, addedEvents };\n })();\n}\n\n/**\n * Remove every row contributed by `sourceWorkstream` from the named\n * archive. Other source workstreams' contributions are untouched\n * (additive accumulation invariant).\n */\nexport function removeFromArchive(\n db: Db,\n label: string,\n sourceWorkstream: string,\n): RemoveFromArchiveResult {\n const archiveId = resolveArchiveId(db, label);\n return db.transaction(() => {\n const countBefore = (sql: string, params: unknown[]) =>\n (db.prepare(sql).get(...params) as { n: number }).n;\n const removedTasks = countBefore(\n \"SELECT COUNT(*) AS n FROM archived_tasks WHERE archive_id = ? AND source_workstream = ?\",\n [archiveId, sourceWorkstream],\n );\n const removedNotes = countBefore(\n `SELECT COUNT(*) AS n\n FROM archived_notes an\n JOIN archived_tasks t ON t.id = an.archived_task_id\n WHERE t.archive_id = ? AND t.source_workstream = ?`,\n [archiveId, sourceWorkstream],\n );\n const removedEdges = countBefore(\n `SELECT COUNT(*) AS n\n FROM archived_edges e\n JOIN archived_tasks t ON t.id = e.from_archived_id\n WHERE e.archive_id = ? AND t.source_workstream = ?`,\n [archiveId, sourceWorkstream],\n );\n const removedEvents = countBefore(\n \"SELECT COUNT(*) AS n FROM archived_events WHERE archive_id = ? AND source_workstream = ?\",\n [archiveId, sourceWorkstream],\n );\n\n db.prepare(\"DELETE FROM archived_tasks WHERE archive_id = ? AND source_workstream = ?\").run(\n archiveId,\n sourceWorkstream,\n );\n db.prepare(\"DELETE FROM archived_events WHERE archive_id = ? AND source_workstream = ?\").run(\n archiveId,\n sourceWorkstream,\n );\n\n if (removedTasks > 0 || removedEvents > 0) {\n emitEvent(\n db,\n null,\n `archive remove ${label} -w ${sourceWorkstream} (tasks=${removedTasks}, edges=${removedEdges}, notes=${removedNotes}, events=${removedEvents})`,\n );\n }\n return { removedTasks, removedEdges, removedNotes, removedEvents };\n })();\n}\n","// mu — archive delete verb.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { resolveArchiveId } from \"./core.js\";\n\n/**\n * Delete an archive and every row that references it. The FK\n * CASCADE chain (archives → archived_tasks → archived_edges /\n * archived_notes; archives → archived_events) cleans every row in\n * one statement.\n *\n * Idempotent: throws `ArchiveNotFoundError` rather than silently\n * succeeding on a missing label (operator confusion safeguard).\n */\nexport function deleteArchive(db: Db, label: string): void {\n const id = resolveArchiveId(db, label);\n db.prepare(\"DELETE FROM archives WHERE id = ?\").run(id);\n emitEvent(db, null, `archive delete ${label}`);\n}\n","// mu — archive create/list/show/search/read helpers.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport {\n type Archive,\n ArchiveAlreadyExistsError,\n ArchiveNotFoundError,\n type ArchiveSummary,\n type ArchivedTaskRow,\n type RawArchiveRow,\n type RawArchivedTaskRow,\n archiveByLabel,\n assertValidArchiveLabel,\n resolveArchiveId,\n rowFromArchive,\n rowFromArchivedTask,\n summarizeArchive,\n tryResolveArchiveId,\n} from \"./core.js\";\n\n/**\n * Create a new archive bucket. Throws `ArchiveAlreadyExistsError` if\n * the label is already in use; throws `ArchiveLabelInvalidError` for\n * malformed labels.\n *\n * The archive starts EMPTY: created_at and last_added_at both equal\n * now(). Use `addToArchive(label, workstream)` to populate it.\n */\nexport function createArchive(db: Db, label: string, description?: string): Archive {\n assertValidArchiveLabel(label);\n if (tryResolveArchiveId(db, label) !== null) {\n throw new ArchiveAlreadyExistsError(label);\n }\n const now = new Date().toISOString();\n const result = db\n .prepare(\n `INSERT INTO archives (label, description, created_at, last_added_at)\n VALUES (?, ?, ?, ?)`,\n )\n .run(label, description ?? null, now, now);\n const id = Number(result.lastInsertRowid);\n emitEvent(db, null, `archive create ${label}`);\n return {\n id,\n label,\n description: description ?? null,\n createdAt: now,\n lastAddedAt: now,\n };\n}\n\n/**\n * List every archive on this machine, summarised with per-source-\n * workstream counts. Sorted by label ascending. Pure read; safe to\n * call against an empty DB (returns []).\n */\nexport function listArchives(db: Db): ArchiveSummary[] {\n const rows = db\n .prepare(\n \"SELECT id, label, description, created_at, last_added_at FROM archives ORDER BY label\",\n )\n .all() as RawArchiveRow[];\n return rows.map((r) => summarizeArchive(db, rowFromArchive(r)));\n}\n\n/**\n * Look up a single archive by label. Throws `ArchiveNotFoundError`\n * on miss.\n */\nexport function getArchive(db: Db, label: string): ArchiveSummary {\n const archive = archiveByLabel(db, label);\n if (archive === null) throw new ArchiveNotFoundError(label);\n return summarizeArchive(db, archive);\n}\n\nexport interface ListArchivedTasksOptions {\n /** Filter by source workstream. Omit to return every source's\n * contribution, sorted by (source_workstream, original_local_id). */\n sourceWorkstream?: string;\n}\n\nexport function listArchivedTasks(\n db: Db,\n label: string,\n opts: ListArchivedTasksOptions = {},\n): ArchivedTaskRow[] {\n const archiveId = resolveArchiveId(db, label);\n const conditions: string[] = [\"t.archive_id = ?\"];\n const params: unknown[] = [archiveId];\n if (opts.sourceWorkstream !== undefined) {\n conditions.push(\"t.source_workstream = ?\");\n params.push(opts.sourceWorkstream);\n }\n const where = conditions.join(\" AND \");\n const rows = db\n .prepare(\n `SELECT t.id AS id,\n a.label AS archive_label,\n t.source_workstream AS source_workstream,\n t.original_local_id AS original_local_id,\n t.title AS title,\n t.status AS status,\n t.impact AS impact,\n t.effort_days AS effort_days,\n t.owner_name AS owner_name,\n t.archived_at_status AS archived_at_status,\n t.archived_at AS archived_at,\n t.original_created_at AS original_created_at,\n t.original_updated_at AS original_updated_at\n FROM archived_tasks t\n JOIN archives a ON a.id = t.archive_id\n WHERE ${where}\n ORDER BY t.source_workstream ASC, t.original_local_id ASC`,\n )\n .all(...params) as RawArchivedTaskRow[];\n return rows.map(rowFromArchivedTask);\n}\n\nexport interface ArchiveSearchHit {\n /** Operator-facing label of the parent archive. */\n archiveLabel: string;\n /** TEXT name of the source workstream this row came from. */\n sourceWorkstream: string;\n /** local_id the task had in its source workstream. */\n originalLocalId: string;\n /** Snapshotted title (always present, even on a note match). */\n title: string;\n /** Where the match was found: the title column, or one of this\n * task's archived_notes.content rows. Title matches win when\n * both apply (the dedup pass below picks one row per task). */\n matchKind: \"title\" | \"note\";\n /** Up to ~120 chars of context centered on the FIRST occurrence\n * of the pattern in the matching field. Case-insensitive index;\n * the snippet itself preserves original casing. */\n matchSnippet: string;\n}\n\nexport interface SearchArchivesOptions {\n /** LIKE-style needle. Wrapped in `%…%` automatically. */\n pattern: string;\n /** Restrict to one archive label; undefined = search every\n * archive. Throws ArchiveNotFoundError on miss. */\n label?: string;\n /** Cap on hits returned. Default 50; values below 1 fall back to\n * the default. */\n limit?: number;\n}\n\nconst SEARCH_DEFAULT_LIMIT = 50;\nconst SNIPPET_WIDTH = 120;\n\nfunction snippetAround(haystack: string, needle: string): string {\n const literal = needle.replace(/[%_]/g, \"\");\n if (literal.length === 0) {\n return haystack.length <= SNIPPET_WIDTH ? haystack : `${haystack.slice(0, SNIPPET_WIDTH - 1)}…`;\n }\n const idx = haystack.toLowerCase().indexOf(literal.toLowerCase());\n if (idx < 0) {\n return haystack.length <= SNIPPET_WIDTH ? haystack : `${haystack.slice(0, SNIPPET_WIDTH - 1)}…`;\n }\n const half = Math.floor((SNIPPET_WIDTH - literal.length) / 2);\n const start = Math.max(0, idx - half);\n const end = Math.min(haystack.length, start + SNIPPET_WIDTH);\n const head = start > 0 ? \"…\" : \"\";\n const tail = end < haystack.length ? \"…\" : \"\";\n return `${head}${haystack.slice(start, end)}${tail}`;\n}\n\n/** LIKE-search archived task titles AND archived note content. */\nexport function searchArchives(db: Db, opts: SearchArchivesOptions): ArchiveSearchHit[] {\n const trimmed = opts.pattern.trim();\n if (trimmed.length === 0) {\n throw new Error(\"searchArchives: pattern must be non-empty\");\n }\n const like = `%${trimmed}%`;\n const limit =\n opts.limit !== undefined && opts.limit > 0 ? Math.floor(opts.limit) : SEARCH_DEFAULT_LIMIT;\n\n let archiveFilterSql = \"\";\n const archiveFilterParams: unknown[] = [];\n if (opts.label !== undefined) {\n const archive = getArchive(db, opts.label);\n archiveFilterSql = \" AND a.id = ?\";\n archiveFilterParams.push(archive.id);\n }\n\n const titleRows = db\n .prepare(\n `SELECT a.label AS archive_label,\n t.source_workstream AS source_workstream,\n t.original_local_id AS original_local_id,\n t.title AS title\n FROM archived_tasks t\n JOIN archives a ON a.id = t.archive_id\n WHERE LOWER(t.title) LIKE LOWER(?)${archiveFilterSql}`,\n )\n .all(like, ...archiveFilterParams) as {\n archive_label: string;\n source_workstream: string;\n original_local_id: string;\n title: string;\n }[];\n\n const noteRows = db\n .prepare(\n `SELECT a.label AS archive_label,\n t.source_workstream AS source_workstream,\n t.original_local_id AS original_local_id,\n t.title AS title,\n n.content AS content\n FROM archived_notes n\n JOIN archived_tasks t ON t.id = n.archived_task_id\n JOIN archives a ON a.id = t.archive_id\n WHERE LOWER(n.content) LIKE LOWER(?)${archiveFilterSql}\n ORDER BY n.id`,\n )\n .all(like, ...archiveFilterParams) as {\n archive_label: string;\n source_workstream: string;\n original_local_id: string;\n title: string;\n content: string;\n }[];\n\n const seen = new Set<string>();\n const hits: ArchiveSearchHit[] = [];\n for (const r of titleRows) {\n const key = `${r.archive_label}\\u0000${r.source_workstream}\\u0000${r.original_local_id}`;\n seen.add(key);\n hits.push({\n archiveLabel: r.archive_label,\n sourceWorkstream: r.source_workstream,\n originalLocalId: r.original_local_id,\n title: r.title,\n matchKind: \"title\",\n matchSnippet: snippetAround(r.title, trimmed),\n });\n }\n for (const r of noteRows) {\n const key = `${r.archive_label}\\u0000${r.source_workstream}\\u0000${r.original_local_id}`;\n if (seen.has(key)) continue;\n seen.add(key);\n hits.push({\n archiveLabel: r.archive_label,\n sourceWorkstream: r.source_workstream,\n originalLocalId: r.original_local_id,\n title: r.title,\n matchKind: \"note\",\n matchSnippet: snippetAround(r.content, trimmed),\n });\n }\n\n hits.sort((a, b) => {\n if (a.archiveLabel !== b.archiveLabel) return a.archiveLabel < b.archiveLabel ? -1 : 1;\n if (a.sourceWorkstream !== b.sourceWorkstream)\n return a.sourceWorkstream < b.sourceWorkstream ? -1 : 1;\n if (a.originalLocalId !== b.originalLocalId)\n return a.originalLocalId < b.originalLocalId ? -1 : 1;\n return 0;\n });\n return hits.slice(0, limit);\n}\n","// mu — restore archived task graphs back into a fresh workstream.\n\nimport { type Db, resolveWorkstreamId, tryResolveWorkstreamId } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport {\n WorkstreamExistsError,\n WorkstreamNameInvalidError,\n ensureWorkstream,\n isValidWorkstreamName,\n} from \"../workstream.js\";\nimport { resolveArchiveId } from \"./core.js\";\n\nexport class ArchiveSourceAmbiguousError extends Error implements HasNextSteps {\n override readonly name = \"ArchiveSourceAmbiguousError\";\n constructor(\n public readonly label: string,\n public readonly sources: readonly string[],\n ) {\n super(\n sources.length === 0\n ? `archive ${label} contains no source workstreams`\n : `archive ${label} requires --source <orig-ws-name>. Available: ${sources.join(\", \")}`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Inspect archive sources\", command: `mu archive show ${this.label}` },\n ...this.sources.map((source) => ({\n intent: `Restore source workstream ${source}`,\n command: `mu archive restore ${this.label} --source ${source} --as <new-workstream>`,\n })),\n ];\n }\n}\n\nexport interface RestoreArchiveOptions {\n sourceWorkstream?: string;\n}\n\nexport interface RestoreArchiveResult {\n archiveLabel: string;\n sourceWorkstream: string;\n workstreamName: string;\n restoredTasks: number;\n restoredEdges: number;\n restoredNotes: number;\n}\n\nexport function restoreArchive(\n db: Db,\n label: string,\n asWorkstream: string,\n opts: RestoreArchiveOptions = {},\n): RestoreArchiveResult {\n const archiveId = resolveArchiveId(db, label);\n const sources = listSources(db, archiveId);\n if (!isValidWorkstreamName(asWorkstream)) throw new WorkstreamNameInvalidError(asWorkstream);\n\n const sourceWorkstream = opts.sourceWorkstream ?? sources[0];\n if (sourceWorkstream === undefined) throw new ArchiveSourceAmbiguousError(label, sources);\n if (opts.sourceWorkstream === undefined && sources.length > 1) {\n throw new ArchiveSourceAmbiguousError(label, sources);\n }\n if (opts.sourceWorkstream !== undefined && !sources.includes(opts.sourceWorkstream)) {\n throw new ArchiveSourceAmbiguousError(label, sources);\n }\n if (tryResolveWorkstreamId(db, asWorkstream) !== null) {\n throw new WorkstreamExistsError(asWorkstream);\n }\n\n captureSnapshot(db, `archive restore ${label} as ${asWorkstream}`, null);\n\n return db.transaction(() => {\n ensureWorkstream(db, asWorkstream);\n const wsId = resolveWorkstreamId(db, asWorkstream);\n\n const restoredTasks = db\n .prepare(\n `INSERT INTO tasks\n (workstream_id, local_id, title, status, impact, effort_days, owner_id, created_at, updated_at)\n SELECT ?, original_local_id, title, status, impact, effort_days, NULL,\n original_created_at, original_updated_at\n FROM archived_tasks\n WHERE archive_id = ? AND source_workstream = ?\n ORDER BY id`,\n )\n .run(wsId, archiveId, sourceWorkstream).changes;\n\n const now = new Date().toISOString();\n const restoredEdges = db\n .prepare(\n `INSERT OR IGNORE INTO task_edges (from_task_id, to_task_id, created_at)\n SELECT live_from.id, live_to.id, ?\n FROM archived_edges e\n JOIN archived_tasks arch_from ON arch_from.id = e.from_archived_id\n JOIN archived_tasks arch_to ON arch_to.id = e.to_archived_id\n JOIN tasks live_from ON live_from.workstream_id = ?\n AND live_from.local_id = arch_from.original_local_id\n JOIN tasks live_to ON live_to.workstream_id = ?\n AND live_to.local_id = arch_to.original_local_id\n WHERE e.archive_id = ?\n AND arch_from.source_workstream = ?\n AND arch_to.source_workstream = ?`,\n )\n .run(now, wsId, wsId, archiveId, sourceWorkstream, sourceWorkstream).changes;\n\n const restoredNotes = db\n .prepare(\n `INSERT INTO task_notes (task_id, author, content, created_at)\n SELECT live.id, n.author, n.content, n.created_at\n FROM archived_notes n\n JOIN archived_tasks arch ON arch.id = n.archived_task_id\n JOIN tasks live ON live.workstream_id = ?\n AND live.local_id = arch.original_local_id\n WHERE n.archive_id = ? AND arch.source_workstream = ?\n ORDER BY n.id`,\n )\n .run(wsId, archiveId, sourceWorkstream).changes;\n\n emitEvent(\n db,\n asWorkstream,\n `archive restore ${label} source=${sourceWorkstream} as ${asWorkstream} (tasks=${restoredTasks}, edges=${restoredEdges}, notes=${restoredNotes})`,\n );\n return {\n archiveLabel: label,\n sourceWorkstream,\n workstreamName: asWorkstream,\n restoredTasks,\n restoredEdges,\n restoredNotes,\n };\n })();\n}\n\nfunction listSources(db: Db, archiveId: number): string[] {\n return (\n db\n .prepare(\n `SELECT source_workstream AS name\n FROM archived_tasks\n WHERE archive_id = ?\n GROUP BY source_workstream\n ORDER BY source_workstream`,\n )\n .all(archiveId) as { name: string }[]\n ).map((row) => row.name);\n}\n","// mu — TaskStatus enum + helpers.\n//\n// Single source of truth for \"what statuses can a task have\". The\n// schema (db.ts) has a CHECK clause that mirrors TASK_STATUSES; if you\n// add a status, update both places.\n//\n// Extracted from src/tasks.ts as part of refactor_split_large_src_files.\n\nexport type TaskStatus = \"OPEN\" | \"IN_PROGRESS\" | \"CLOSED\" | \"REJECTED\" | \"DEFERRED\";\n\n/** Every legal task status, in canonical order (matches the schema\n * CHECK clause). Exported so CLI surfaces (`--status` validators,\n * --help text, error messages) name them all in one place; missing\n * one used to silently lie about the supported set. */\nexport const TASK_STATUSES: readonly TaskStatus[] = [\n \"OPEN\",\n \"IN_PROGRESS\",\n \"CLOSED\",\n \"REJECTED\",\n \"DEFERRED\",\n];\n\n/** Statuses that count as 'no longer scheduled work' — used by the\n * goals view and by the dependent-check on reject/defer.\n *\n * (The complement — 'statuses that satisfy a blocked-by edge' — is\n * just `[\"CLOSED\"]` and is hardcoded inline in the SQL views in\n * src/db.ts. A constant for it was tried and reverted: a one-element\n * array doesn't earn its keep, and parameterising the SQL views from\n * a TS const would be brittle.) */\nexport const STATUSES_TERMINAL_OR_PARKED: readonly TaskStatus[] = [\n \"CLOSED\",\n \"REJECTED\",\n \"DEFERRED\",\n];\n\nexport function isTaskStatus(s: string): s is TaskStatus {\n return (TASK_STATUSES as readonly string[]).includes(s);\n}\n\n/** Pipe-separated list of every legal status, e.g.\n * 'OPEN | IN_PROGRESS | CLOSED | REJECTED | DEFERRED'. Single source\n * of truth for --help text and error messages so adding a new status\n * doesn't leave stale lists rotting in the CLI surface. */\nexport const TASK_STATUS_LIST = TASK_STATUSES.join(\" | \");\n","// mu — task edit/write verbs: add task, add note, update, delete.\n\nimport { type Db, resolveWorkstreamId } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport { ensureWorkstream } from \"../workstream.js\";\nimport { taskIdFor, touchTask } from \"./core.js\";\nimport { dedupeBlockersById, wouldCreateCycle } from \"./edges.js\";\nimport {\n CrossWorkstreamEdgeError,\n CycleError,\n TaskExistsError,\n TaskIdInvalidError,\n TaskNotFoundError,\n} from \"./errors.js\";\nimport { isValidTaskId } from \"./id.js\";\nimport { getTask } from \"./queries.js\";\n\nexport interface AddTaskOptions {\n localId: string;\n workstream: string;\n title: string;\n /** 1..100; enforced by schema CHECK. */\n impact: number;\n /** > 0; enforced by schema CHECK. */\n effortDays: number;\n /**\n * Tasks that block this one. Edges inserted as `blocker -> newTask`.\n * Each blocker must already exist AND share this task's workstream\n * (cross-workstream edges are forbidden); cycle check guards each\n * edge. The CLI surfaces this as `--blocked-by`; the SDK key matches.\n */\n blockedBy?: string[];\n}\n\n/**\n * Atomically create a task and (optionally) its incoming blocked-by\n * edges.\n *\n * The task insert + every edge insert + cycle check happen inside one\n * SQLite transaction. If any blocker is missing or any edge would\n * create a cycle, the entire add rolls back.\n *\n * Cycle check for `addTask` is structurally trivial (a fresh task has\n * no outgoing edges, so `to -> ... -> from` is impossible). It's still\n * called here so the same primitive is exercised by tests.\n */\nexport function addTask(db: Db, opts: AddTaskOptions) {\n if (!isValidTaskId(opts.localId)) {\n throw new TaskIdInvalidError(opts.localId);\n }\n\n return db.transaction(() => {\n // Auto-create the workstream row so tasks.workstream_id FK is\n // satisfied (preserves spawn-without-init ergonomics).\n ensureWorkstream(db, opts.workstream);\n const wsId = resolveWorkstreamId(db, opts.workstream);\n\n // Per-workstream uniqueness: a duplicate local_id within the same\n // workstream throws TaskExistsError. Different workstreams may\n // legitimately share local_ids in v5.\n const existing = db\n .prepare(\"SELECT id FROM tasks WHERE workstream_id = ? AND local_id = ?\")\n .get(wsId, opts.localId) as { id: number } | undefined;\n if (existing) {\n throw new TaskExistsError(opts.localId);\n }\n\n const now = new Date().toISOString();\n const insertResult = db\n .prepare(\n `INSERT INTO tasks (workstream_id, local_id, title, status, impact, effort_days, created_at, updated_at)\n VALUES (?, ?, ?, 'OPEN', ?, ?, ?, ?)`,\n )\n .run(wsId, opts.localId, opts.title, opts.impact, opts.effortDays, now, now);\n const newTaskId = Number(insertResult.lastInsertRowid);\n\n let canonicalBlockedBy: string[] = [];\n\n if (opts.blockedBy && opts.blockedBy.length > 0) {\n // Prefer the same-workstream blocker first (v5 per-workstream\n // local_id), then fall back to a global lookup so a cross-ws\n // blocker still surfaces CrossWorkstreamEdgeError (not\n // TaskNotFoundError). Without the same-ws preference, two\n // blockers of the same local_id (one in this ws, one elsewhere)\n // could silently bind to the wrong row\n // (bug_v5_name_clash_silent_misroute).\n const blockerLookupSameWs = db.prepare(\n `SELECT t.id AS id, ws.name AS workstream FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE t.local_id = ? AND t.workstream_id = ?`,\n );\n const blockerLookupAnyWs = db.prepare(\n `SELECT t.id AS id, ws.name AS workstream FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE t.local_id = ? LIMIT 1`,\n );\n const requestedBlockers = opts.blockedBy.map((blocker) => {\n const row = (blockerLookupSameWs.get(blocker, wsId) ?? blockerLookupAnyWs.get(blocker)) as\n | { id: number; workstream: string }\n | undefined;\n if (!row) {\n throw new TaskNotFoundError(blocker);\n }\n if (row.workstream !== opts.workstream) {\n throw new CrossWorkstreamEdgeError(\n blocker,\n row.workstream,\n opts.localId,\n opts.workstream,\n );\n }\n if (wouldCreateCycle(db, row.id, newTaskId)) {\n throw new CycleError(blocker, opts.localId);\n }\n return { localId: blocker, id: row.id };\n });\n const canonicalBlockers = dedupeBlockersById(requestedBlockers);\n canonicalBlockedBy = canonicalBlockers.map((blocker) => blocker.localId);\n const insertEdge = db.prepare(\n \"INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)\",\n );\n for (const blocker of canonicalBlockers) {\n insertEdge.run(blocker.id, newTaskId, now);\n }\n }\n\n const row = getTask(db, opts.localId, opts.workstream);\n if (!row) throw new Error(`addTask: row missing after insert: ${opts.localId}`);\n const blockedBy =\n canonicalBlockedBy.length > 0 ? `, blocked-by=${canonicalBlockedBy.join(\",\")}` : \"\";\n emitEvent(\n db,\n opts.workstream,\n `task add ${opts.localId} (impact=${opts.impact}, effort=${opts.effortDays}${blockedBy})`,\n );\n return row;\n })();\n}\n\nexport interface AddNoteOptions {\n /** Free-form author label. Convention: agent name, \"user\", or \"orchestrator\". */\n author?: string;\n /** Workstream context (operator-facing name). v5: tasks.local_id is\n * per-workstream unique, so this is required to disambiguate. */\n workstream: string;\n}\n\nexport function addNote(db: Db, taskLocalId: string, content: string, opts: AddNoteOptions) {\n const task = getTask(db, taskLocalId, opts.workstream);\n if (!task) {\n throw new TaskNotFoundError(taskLocalId);\n }\n const taskId = taskIdFor(db, task.name, task.workstreamName);\n if (taskId === null) throw new TaskNotFoundError(taskLocalId);\n const now = new Date().toISOString();\n const result = db.transaction(() => {\n const r = db\n .prepare(\"INSERT INTO task_notes (task_id, author, content, created_at) VALUES (?, ?, ?, ?)\")\n .run(taskId, opts.author ?? null, content, now);\n // Bump the parent task so `mu task list --sort recency` surfaces\n // freshly-noted tasks (task_updatedat_not_bumped_by_reparent).\n touchTask(db, taskId, now);\n return r;\n })();\n const noteId = Number(result.lastInsertRowid);\n emitEvent(\n db,\n task.workstreamName,\n `task note ${taskLocalId} (note #${noteId} by ${opts.author ?? \"orchestrator\"})`,\n opts.author ?? \"system\",\n );\n return {\n author: opts.author ?? null,\n content,\n createdAt: now,\n };\n}\n\nexport interface DeleteTaskResult {\n /** True iff the row existed and was deleted. False on a dry-run\n * (preview) AND on the idempotent missing-row case. */\n deleted: boolean;\n /** Number of `task_edges` rows cascaded out (informational). On a\n * dry-run, this is the would-be count. */\n deletedEdges: number;\n /** Number of `task_notes` rows cascaded out (informational). On a\n * dry-run, this is the would-be count. */\n deletedNotes: number;\n /** True iff this was a dry-run (`opts.dryRun: true`). On a\n * dry-run `deleted` is false and the counts are the would-be\n * counts; the DB is unchanged. Always false on a commit / on a\n * missing-row idempotent no-op. */\n dryRun: boolean;\n /** True iff a matching task row was found at the time of the\n * call. Discriminator for the CLI: a dry-run that found nothing\n * (`present: false`) renders differently from a dry-run that\n * found an existing task with zero edges and zero notes\n * (`present: true, deletedEdges: 0, deletedNotes: 0`). */\n present: boolean;\n}\n\nexport interface DeleteTaskOptions {\n /** When true, return the cascade preview (would-be edge / note\n * counts) without mutating and without snapshotting. The CLI uses\n * this to power the bare `mu task delete <id>` two-phase pattern\n * (mirrors `mu workstream destroy` / `mu archive delete` /\n * `mu snapshot prune`). Surfaced by feedback ws task\n * fb_task_delete_no_yes (impact=30): a dogfood report typed\n * `mu task delete X --yes` (mirroring workstream destroy) and got\n * 'unknown option --yes' — the verb took no confirmation flag at\n * all. Two failed deletes left long-named tasks lingering. */\n dryRun?: boolean;\n}\n\n/**\n * Delete a task. FK CASCADE on `task_edges` (from + to) and\n * `task_notes` cleans the joined rows automatically. Idempotent on\n * a missing task (returns `deleted: false`).\n *\n * Pre-counts the cascade victims for reporting because SQLite's\n * `changes()` only reports rows directly affected by the DELETE.\n *\n * With `opts.dryRun: true`, returns the would-be counts without\n * touching the DB and without taking a snapshot (no mutation = no\n * snapshot — same reasoning that gates the closeTask snap on the\n * idempotent no-op path). The CLI bare `mu task delete <id>` form\n * uses this; `--yes` calls through with `dryRun: false`.\n */\nexport function deleteTask(\n db: Db,\n localId: string,\n workstream: string,\n opts: DeleteTaskOptions = {},\n): DeleteTaskResult {\n const dryRun = opts.dryRun === true;\n const before = getTask(db, localId, workstream);\n if (!before) {\n // Idempotent on a missing row regardless of dryRun.\n return { deleted: false, deletedEdges: 0, deletedNotes: 0, dryRun, present: false };\n }\n const taskId = taskIdFor(db, localId, before.workstreamName);\n if (taskId === null) {\n return { deleted: false, deletedEdges: 0, deletedNotes: 0, dryRun, present: false };\n }\n const edgesBefore = (\n db\n .prepare(\"SELECT COUNT(*) AS n FROM task_edges WHERE from_task_id = ? OR to_task_id = ?\")\n .get(taskId, taskId) as { n: number }\n ).n;\n const notesBefore = (\n db.prepare(\"SELECT COUNT(*) AS n FROM task_notes WHERE task_id = ?\").get(taskId) as {\n n: number;\n }\n ).n;\n if (dryRun) {\n return {\n deleted: false,\n deletedEdges: edgesBefore,\n deletedNotes: notesBefore,\n dryRun: true,\n present: true,\n };\n }\n // Pre-mutation snapshot. delete cascades into task_edges and\n // task_notes; no per-row history can reconstruct it. Taken AFTER\n // the dry-run early-return so a preview never touches snapshots.\n captureSnapshot(db, `task delete ${localId}`, before.workstreamName);\n const result = db.prepare(\"DELETE FROM tasks WHERE id = ?\").run(taskId);\n const deleted = result.changes > 0;\n if (deleted) {\n emitEvent(\n db,\n before.workstreamName,\n `task delete ${localId} (cascade: ${edgesBefore} edges, ${notesBefore} notes)`,\n );\n }\n return {\n deleted,\n deletedEdges: edgesBefore,\n deletedNotes: notesBefore,\n dryRun: false,\n present: true,\n };\n}\n\nexport interface UpdateTaskOptions {\n title?: string;\n /** 1..100; enforced by schema CHECK. */\n impact?: number;\n /** > 0; enforced by schema CHECK. */\n effortDays?: number;\n}\n\nexport interface UpdateTaskResult {\n /** True iff at least one field actually changed. */\n updated: boolean;\n /** The fields whose values differ post-update (in `UpdateTaskOptions`'s\n * camelCase shape). Empty when `updated: false`. */\n changedFields: string[];\n}\n\n/**\n * Update scalar fields on a task. Each option is independently optional;\n * passing none is a typed no-op (returns `updated: false, changedFields: []`).\n * Fields whose new value equals the current value are skipped (no row change).\n *\n * NOT for status (use `closeTask` / `openTask` / `setTaskStatus`), owner\n * (use `claimTask` / `releaseTask`), local_id (rename is deferred), or\n * workstream (cross-workstream moves are deferred).\n */\nexport interface UpdateTaskScopeOption {\n workstream: string;\n}\n\nexport function updateTask(\n db: Db,\n localId: string,\n opts: UpdateTaskOptions,\n scope: UpdateTaskScopeOption,\n): UpdateTaskResult {\n const before = getTask(db, localId, scope.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n const taskId = taskIdFor(db, before.name, before.workstreamName);\n if (taskId === null) throw new TaskNotFoundError(localId);\n\n const setters: string[] = [];\n const params: unknown[] = [];\n const changedFields: string[] = [];\n\n if (opts.title !== undefined && opts.title !== before.title) {\n setters.push(\"title = ?\");\n params.push(opts.title);\n changedFields.push(\"title\");\n }\n if (opts.impact !== undefined && opts.impact !== before.impact) {\n setters.push(\"impact = ?\");\n params.push(opts.impact);\n changedFields.push(\"impact\");\n }\n if (opts.effortDays !== undefined && opts.effortDays !== before.effortDays) {\n setters.push(\"effort_days = ?\");\n params.push(opts.effortDays);\n changedFields.push(\"effortDays\");\n }\n\n if (setters.length === 0) {\n return { updated: false, changedFields: [] };\n }\n\n setters.push(\"updated_at = ?\");\n params.push(new Date().toISOString());\n params.push(taskId);\n\n db.prepare(`UPDATE tasks SET ${setters.join(\", \")} WHERE id = ?`).run(...params);\n emitEvent(\n db,\n before.workstreamName,\n `task update ${localId} (changed: ${changedFields.join(\", \")})`,\n );\n return { updated: true, changedFields };\n}\n","// mu — task lifecycle verbs: setTaskStatus, closeTask, openTask,\n// rejectTask, deferTask + supporting types.\n//\n// Lifecycle = \"transition a task from one status to another, with\n// the right side effects (auto-snapshot before mutating, emit\n// agent_logs event, refresh pane title via the caller, validate\n// guard rails like 'reject would strand dependents')\".\n//\n// EvidenceOption is shared with claim/release (in tasks/claim.ts) and\n// re-exported here as the canonical home; claim.ts imports from this\n// file.\n//\n// Extracted from src/tasks.ts as part of refactor_split_large_src_files.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport { getTaskEdgesWithStatus } from \"./edges.js\";\nimport { addNote } from \"./edit.js\";\nimport { TaskHasOpenDependentsError, TaskNotFoundError } from \"./errors.js\";\nimport { getTask } from \"./queries.js\";\nimport type { TaskStatus } from \"./status.js\";\n\nexport interface SetStatusResult {\n /** Status before the call. */\n previousStatus: TaskStatus;\n /** Status after the call (== requested status). */\n status: TaskStatus;\n /** True iff the row actually changed. False on idempotent no-op. */\n changed: boolean;\n}\n\n/**\n * Optional evidence string carried on lifecycle verbs (close / open /\n * claim / release). Lands in the auto-emitted `kind='event'` payload\n * verbatim, prefixed with `evidence=`. The first inch of distinguishing\n * \"observed\" from \"claimed\" state per an internal critique: the\n * verb still trusts the caller (it's not a verifier), but the audit\n * trail records what the caller said it relied on.\n */\nexport interface EvidenceOption {\n evidence?: string;\n}\n\n/** Render the optional `--evidence \"<text>\"` payload as the trailing\n * ' evidence=\"...\"' on every state-changing event. Exported because\n * claimTask/releaseTask in src/tasks/claim.ts also use it. */\nexport function evidenceSuffix(opts: EvidenceOption | undefined): string {\n if (!opts || opts.evidence === undefined) return \"\";\n return ` evidence=${JSON.stringify(opts.evidence)}`;\n}\n\n/**\n * Flip a task's status to any of OPEN / IN_PROGRESS / CLOSED.\n * Idempotent: setting a task to its current status is a no-op (returns\n * `changed: false`) rather than throwing. Owner is unchanged.\n */\nexport function setTaskStatus(\n db: Db,\n localId: string,\n status: TaskStatus,\n opts: EvidenceOption & { workstream: string },\n): SetStatusResult {\n const before = getTask(db, localId, opts.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n if (before.status === status) {\n return { previousStatus: before.status, status, changed: false };\n }\n // v5: tasks.local_id is per-workstream unique. Scope to the row's\n // workstream so the UPDATE doesn't accidentally touch a same-named\n // task in another workstream.\n db.prepare(\n `UPDATE tasks SET status = ?, updated_at = ?\n WHERE local_id = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)`,\n ).run(status, new Date().toISOString(), localId, before.workstreamName);\n emitEvent(\n db,\n before.workstreamName,\n `task status ${localId} (${before.status} → ${status})${evidenceSuffix(opts)}`,\n );\n return { previousStatus: before.status, status, changed: true };\n}\n\n/** Result of `closeTask` when called with `ifReady: true` and the\n * task is NOT yet ready to close (still has at least one OPEN /\n * IN_PROGRESS blocker). Distinguished from a regular `SetStatusResult`\n * by the literal `skipped` field; the CLI keys on it to switch\n * between the \"closed\" and \"waiting\" rendering paths.\n *\n * Surfaced in `fb_umbrella_no_auto_close` (impact=60): a wave umbrella\n * with N blockers stayed OPEN after every blocker reached a terminal\n * status. `--if-ready` is the cheap fix: bare `mu task close` is\n * unchanged (closes regardless), `--if-ready` is a no-op unless every\n * blocker is in a terminal status (CLOSED / REJECTED / DEFERRED).\n * Reject and defer satisfy the predicate too because `--if-ready`'s\n * job is to fire when the umbrella has nothing left to wait for, and\n * a rejected/deferred blocker is no longer being waited on. */\nexport interface CloseSkippedResult {\n /** Always 'not_ready' when set; future cause-codes can extend this\n * without reshaping the JSON payload (the literal-union narrows\n * safely in the CLI rendering path). */\n skipped: \"not_ready\";\n /** Status before the call (always the current status, no change). */\n previousStatus: TaskStatus;\n /** Status after the call (== previousStatus, since we no-op). */\n status: TaskStatus;\n /** Always false on a skip (no row mutated). */\n changed: false;\n /** Local ids of every blocker still in OPEN or IN_PROGRESS, sorted\n * alphabetically for deterministic rendering. Empty list is\n * impossible on this branch — the no-op only fires when ≥1\n * blocker is non-terminal. */\n blockingIds: string[];\n}\n\nexport interface CloseTaskOptions extends EvidenceOption {\n workstream: string;\n /** When true, no-op the close unless every blocker is in a terminal\n * status (CLOSED / REJECTED / DEFERRED). Returns a\n * `CloseSkippedResult` carrying the still-blocking ids; the CLI\n * renders the skip with a Next: hint pointing at `mu task wait`.\n * When false / omitted, behaves as bare `closeTask` (closes\n * regardless of blocker status). */\n ifReady?: boolean;\n /** Optional actor identity attributed to the synthetic `CLOSE: …`\n * note auto-inserted when `evidence` is non-empty (see closeTask\n * body). The CLI resolves this via `resolveActorIdentity()` so the\n * note carries the closing worker's name; SDK callers (tests,\n * internal use) may omit it (the note then carries no author, same\n * as a bare `addNote` without `--author`). Surfaced in mufeedback\n * task_close_evidence_does_not_append_the. */\n author?: string;\n}\n\n/** Convenience: setTaskStatus(db, id, \"CLOSED\"). Accepts evidence.\n * Pre-snapshots the DB (snap_design §CAPTURE STRATEGY > WHEN). Skipped\n * for the idempotent no-op (already CLOSED) so we don't accumulate\n * empty-delta snapshots on retry loops.\n *\n * With `ifReady: true`, returns a `CloseSkippedResult` (no mutation,\n * no snapshot) when any blocker is still OPEN / IN_PROGRESS. Used by\n * `mu task close --if-ready` so an orchestrator can fire-and-forget\n * the umbrella close after every blocker resolves without first\n * re-querying the graph. */\nexport function closeTask(\n db: Db,\n localId: string,\n opts: CloseTaskOptions,\n): SetStatusResult | CloseSkippedResult {\n const before = getTask(db, localId, opts.workstream);\n if (opts.ifReady && before) {\n // Inspect direct blockers only — the umbrella convention is one\n // hop (umbrella -[blocked-by]→ each wave task). Transitive depth\n // doesn't matter: if any direct blocker is non-terminal, the\n // umbrella isn't ready; if every direct blocker is terminal,\n // their own ancestry was satisfied as a precondition for them\n // closing/rejecting/deferring in the first place.\n const edges = getTaskEdgesWithStatus(db, localId, before.workstreamName);\n const blocking = edges.blockers\n .filter((e) => e.status === \"OPEN\" || e.status === \"IN_PROGRESS\")\n .map((e) => e.name)\n .sort();\n if (blocking.length > 0) {\n return {\n skipped: \"not_ready\",\n previousStatus: before.status,\n status: before.status,\n changed: false,\n blockingIds: blocking,\n };\n }\n }\n if (before && before.status !== \"CLOSED\") {\n captureSnapshot(db, `task close ${localId}`, before.workstreamName);\n }\n const r = setTaskStatus(db, localId, \"CLOSED\", opts);\n // mufeedback task_close_evidence_does_not_append_the: when the\n // operator passes `--evidence \"...\"`, the string lands in the\n // agent_logs event payload but was invisible to `mu task notes <id>`\n // / `mu task show <id>`. Workers were skipping the \"drop a final\n // note before close\" contract on the (reasonable) assumption that\n // --evidence was sufficient. Auto-insert a synthetic note so the\n // evidence joins the note timeline. Only fires when evidence is a\n // non-empty string AND the close actually mutated something\n // (idempotent re-close on an already-CLOSED task: skip; nothing\n // newly attested). Empty-string evidence is treated as none.\n if (r.changed && before && opts.evidence !== undefined && opts.evidence !== \"\") {\n const noteOpts: { author?: string; workstream: string } = {\n workstream: before.workstreamName,\n };\n if (opts.author !== undefined && opts.author !== \"\") noteOpts.author = opts.author;\n addNote(db, localId, `CLOSE: ${opts.evidence}`, noteOpts);\n }\n return r;\n}\n\n/** Convenience: setTaskStatus(db, id, \"OPEN\"). Owner intentionally NOT\n * cleared — use `releaseTask` for that. Accepts evidence. */\nexport function openTask(\n db: Db,\n localId: string,\n opts: EvidenceOption & { workstream: string },\n): SetStatusResult {\n return setTaskStatus(db, localId, \"OPEN\", opts);\n}\n\n// ─── rejectTask / deferTask (terminal-but-blocking transitions) ────\n//\n// REJECTED and DEFERRED both leave the task off the active scheduler\n// (gone from `ready`, `goals`, track count) but, unlike CLOSED, do NOT\n// satisfy a `--blocked-by` edge. A REJECTED / DEFERRED task therefore\n// silently strands every OPEN/IN_PROGRESS dependent. We refuse the\n// transition unless either there are no open dependents OR the caller\n// passes `--cascade` to apply the same status to every transitive\n// dependent.\n\nexport interface RejectDeferOptions extends EvidenceOption {\n /** Workstream context for the root task. All internal task lookups\n * (including the dependent walk) scope to this workstream. */\n workstream: string;\n /** If true, walk the transitive dependent closure and (with `yes`)\n * apply the same status to every dependent, atomically. Without\n * `yes`, runs as a dry-run: returns the list of tasks that WOULD\n * be swept (changedIds) with `dryRun: true` and changes nothing.\n * Logs one event per task (via setTaskStatus) on commit. */\n cascade?: boolean;\n /** Required to actually commit a `cascade` operation. Without it,\n * cascade is dry-run only — prints the affected dependents so the\n * caller can verify before sweeping. Mirrors `mu workstream destroy\n * --yes`. Surfaced in mufeedback bug_cascade_reject_too_aggressive\n * when an accidentally-cascaded reject swept hud_dogfood (which had\n * independent merit and needed reopening). */\n yes?: boolean;\n}\n\nexport interface RejectDeferResult {\n /** Tasks that actually changed status, in cascade order (root first). */\n changedIds: string[];\n /** The status now stamped on every changedId. */\n status: TaskStatus;\n /** True iff anything changed. False on a clean idempotent no-op\n * (root task already in target status, no dependents). */\n changed: boolean;\n /** True iff this was a `cascade` dry-run (cascade requested without\n * `yes`). In that case `changedIds` lists tasks that WOULD be\n * swept; the DB is unchanged. */\n dryRun: boolean;\n /** Tasks that would be touched by a cascade. Same as `changedIds`\n * on a dry-run; populated even on a commit so the caller can\n * report what was swept. */\n affectedIds: string[];\n}\n\n/** Reject a task: terminal 'won't do' (out of scope, duplicate, wontfix).\n * Refuses if dependents are open unless `--cascade`.\n * Pre-snapshots once at the verb level so a cascade onto N children\n * produces a single snapshot, not N. Skipped for the idempotent no-op. */\nexport function rejectTask(db: Db, localId: string, opts: RejectDeferOptions): RejectDeferResult {\n const before = getTask(db, localId, opts.workstream);\n if (before && before.status !== \"REJECTED\") {\n captureSnapshot(db, `task reject ${localId}`, before.workstreamName);\n }\n return setTerminalOrParked(db, localId, \"REJECTED\", opts);\n}\n\n/** Defer a task: parked, may revisit. Same dependent-stranding semantics\n * as reject (DEFERRED also doesn't satisfy a `--blocked-by` edge).\n * Pre-snapshots once at the verb level. Skipped for the idempotent no-op. */\nexport function deferTask(db: Db, localId: string, opts: RejectDeferOptions): RejectDeferResult {\n const before = getTask(db, localId, opts.workstream);\n if (before && before.status !== \"DEFERRED\") {\n captureSnapshot(db, `task defer ${localId}`, before.workstreamName);\n }\n return setTerminalOrParked(db, localId, \"DEFERRED\", opts);\n}\n\nfunction setTerminalOrParked(\n db: Db,\n localId: string,\n status: \"REJECTED\" | \"DEFERRED\",\n opts: RejectDeferOptions,\n): RejectDeferResult {\n const before = getTask(db, localId, opts.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n\n // Find all open (OPEN or IN_PROGRESS) tasks that transitively depend\n // on this one. Forward-edge recursive CTE from localId, scoped by\n // the root task's workstream.\n const openDependents = findOpenDependents(db, localId, before.workstreamName);\n\n if (openDependents.length > 0 && !opts.cascade) {\n const verb = status === \"REJECTED\" ? \"reject\" : \"defer\";\n throw new TaskHasOpenDependentsError(localId, verb, openDependents);\n }\n\n const affectedIds =\n openDependents.length > 0 && opts.cascade ? [localId, ...openDependents] : [localId];\n\n // Cascade dry-run: cascade requested but --yes missing. Don't touch\n // the DB; return the would-be-affected list so the CLI can render\n // a 'about to sweep these N tasks; rerun with --yes' preview.\n // Mirrors `mu workstream destroy` semantics. Single-task case\n // (openDependents == 0, cascade flag irrelevant) skips the dry-run\n // since there's nothing to preview.\n if (opts.cascade && !opts.yes && openDependents.length > 0) {\n return {\n changedIds: affectedIds,\n status,\n changed: false,\n dryRun: true,\n affectedIds,\n };\n }\n\n // Apply to root first, then dependents in BFS order. setTaskStatus\n // emits one event per task and is idempotent (no-op if already in\n // target status). Every UPDATE scopes to the root's workstream\n // (dependents must share it — cross-ws edges are forbidden).\n const childOpts: EvidenceOption & { workstream: string } = {\n workstream: before.workstreamName,\n ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}),\n };\n const changedIds: string[] = [];\n for (const id of affectedIds) {\n const r = setTaskStatus(db, id, status, childOpts);\n if (r.changed) changedIds.push(id);\n }\n\n return {\n changedIds,\n status,\n changed: changedIds.length > 0,\n dryRun: false,\n affectedIds,\n };\n}\n\n/** Open dependents that would be stranded if `taskId` were rejected /\n * deferred. The walk PRUNES at CLOSED nodes: a CLOSED intermediate\n * has already satisfied its blocked-by edge, so its downstream is\n * independent of whatever happens to `taskId` and must NOT be swept.\n * REJECTED / DEFERRED intermediates also stop the walk — their\n * downstream is already stranded by them, not by `taskId`, and a\n * cascade from here would (a) double-flip them or (b) overwrite a\n * previous explicit decision.\n *\n * Ordering: BFS-equivalent via DISTINCT + ORDER BY local_id; cascade\n * applies one row at a time so each setTaskStatus is logged. */\nfunction findOpenDependents(db: Db, taskLocalId: string, workstream: string): string[] {\n // Resolve the seed task to its surrogate id, then walk forward\n // edges in surrogate-id space; project back to local_id at the end.\n // Scope the seed by workstream (v5 per-workstream local_id) so a\n // same-named task elsewhere can't seed a cascade in this workstream\n // (bug_v5_name_clash_silent_misroute).\n const seed = db\n .prepare(\n `SELECT id FROM tasks WHERE local_id = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)`,\n )\n .get(taskLocalId, workstream) as { id: number } | undefined;\n if (!seed) return [];\n const rows = db\n .prepare(\n `WITH RECURSIVE forward(node) AS (\n SELECT e.to_task_id\n FROM task_edges e\n JOIN tasks t ON t.id = e.to_task_id\n WHERE e.from_task_id = ?\n AND t.status IN ('OPEN', 'IN_PROGRESS')\n UNION\n SELECT e.to_task_id\n FROM task_edges e\n JOIN forward f ON f.node = e.from_task_id\n JOIN tasks t ON t.id = e.to_task_id\n WHERE t.status IN ('OPEN', 'IN_PROGRESS')\n )\n SELECT DISTINCT t.local_id AS local_id FROM forward f\n JOIN tasks t ON t.id = f.node\n ORDER BY t.local_id`,\n )\n .all(seed.id) as { local_id: string }[];\n return rows.map((r) => r.local_id);\n}\n","// mu — claim/release/resolveActorIdentity verbs.\n//\n// claimTask is the heart of mu's coordination protocol: an atomic\n// CAS via a single SQL UPDATE, with two flavours:\n//\n// \"worker claim\" : --for <name> sets owner=<name> (FK to agents.name)\n// \"anonymous claim\": --self keeps owner=NULL but flips status to\n// IN_PROGRESS and records the actor\n// in agent_logs\n//\n// resolveActorIdentity is the env-aware identity helper: pane title\n// > MU_AGENT_NAME > USER > 'orchestrator'. Used by --self.\n//\n// Extracted from src/tasks.ts as part of refactor_split_large_src_files.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent, formatClaimEvent } from \"../logs.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport { currentAgentName } from \"../tmux.js\";\nimport { ClaimerNotRegisteredError, TaskAlreadyOwnedError, TaskNotFoundError } from \"./errors.js\";\nimport { type EvidenceOption, evidenceSuffix } from \"./lifecycle.js\";\nimport { getTask } from \"./queries.js\";\nimport type { TaskStatus } from \"./status.js\";\n\nexport interface ReleaseResult {\n /** The previous owner (null if the task was already unowned). */\n previousOwnerName: string | null;\n /** Status before the release. */\n previousStatus: TaskStatus;\n /** Status after the release. */\n status: TaskStatus;\n /** True iff owner OR status actually changed. */\n changed: boolean;\n}\n\nexport interface ReleaseTaskOptions extends EvidenceOption {\n /** Workstream context for the task (v5: tasks.local_id is\n * per-workstream unique). */\n workstream: string;\n /** Force `status = OPEN` regardless of the current status. Without\n * this flag, `IN_PROGRESS` is also flipped to `OPEN` automatically\n * (so a released task isn't left structurally stranded with\n * `owner=NULL, status=IN_PROGRESS`); CLOSED / REJECTED / DEFERRED\n * are preserved. `--reopen` is the override for the rarer \"un-\n * close and hand back to the pool\" workflow. */\n reopen?: boolean;\n}\n\n/**\n * Release a task: clear `tasks.owner`.\n *\n * Status side-effects (review_release_open_in_progress_inconsistency):\n * - IN_PROGRESS → OPEN automatically (without it, the task is\n * stranded: no owner to drive it forward, but `mu task next`\n * skips it because it's not OPEN).\n * - OPEN / CLOSED / REJECTED / DEFERRED preserved.\n * - `--reopen` forces OPEN regardless of current status — the\n * escape hatch for un-closing a CLOSED owned task in one verb.\n *\n * Idempotent: releasing an already-unowned task with no `--reopen` and\n * no IN_PROGRESS status is a no-op (returns `changed: false`).\n * Throws TaskNotFoundError on missing.\n */\nexport function releaseTask(db: Db, localId: string, opts: ReleaseTaskOptions): ReleaseResult {\n const before = getTask(db, localId, opts.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n\n // Default: auto-flip IN_PROGRESS → OPEN so the released task isn't\n // left in the structurally weird owner=NULL/IN_PROGRESS limbo.\n // --reopen still wins for any status (CLOSED / REJECTED / DEFERRED).\n const newStatus: TaskStatus = opts.reopen\n ? \"OPEN\"\n : before.status === \"IN_PROGRESS\"\n ? \"OPEN\"\n : before.status;\n const ownerChanges = before.ownerName !== null;\n const statusChanges = newStatus !== before.status;\n\n if (!ownerChanges && !statusChanges) {\n return {\n previousOwnerName: before.ownerName,\n previousStatus: before.status,\n status: before.status,\n changed: false,\n };\n }\n\n // Pre-mutation snapshot — release wipes ownership which is\n // irrecoverable from history (we'd lose 'who was working on this').\n captureSnapshot(db, `task release ${localId}`, before.workstreamName);\n\n db.prepare(\n `UPDATE tasks SET owner_id = NULL, status = ?, updated_at = ?\n WHERE local_id = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)`,\n ).run(newStatus, new Date().toISOString(), localId, before.workstreamName);\n const statusBit = statusChanges ? `, ${before.status} → ${newStatus}` : \"\";\n emitEvent(\n db,\n before.workstreamName,\n `task release ${localId} (was owner=${before.ownerName ?? \"none\"}${statusBit})${evidenceSuffix(opts)}`,\n );\n return {\n previousOwnerName: before.ownerName,\n previousStatus: before.status,\n status: newStatus,\n changed: true,\n };\n}\n\n// ─── claimTask (verb) ──────────────────────────────────────────────────\n\nexport interface ClaimTaskOptions extends EvidenceOption {\n /** Workstream context for both the task and the claiming agent.\n * v5: agents.name and tasks.local_id are per-workstream unique;\n * the task lookup AND the agent FK lookup scope to this\n * workstream so a same-named task or worker elsewhere can't be\n * silently picked. The CLI always passes this from the resolved\n * -w / $MU_SESSION. */\n workstream: string;\n /**\n * Override the agent name. If omitted, derived from the current pane's\n * title via `tmux display-message -t $TMUX_PANE -p '#{pane_title}'`.\n *\n * Mutually exclusive with `self: true`.\n */\n agentName?: string;\n /**\n * Workstream that the claimer agent lives in. When omitted, defaults\n * to `opts.workstream` (today's same-workstream behaviour). Set by\n * the CLI when `mu task claim X -w A --for B/worker-1` qualifies the\n * `--for` ref with a different workstream prefix\n * (`task_claim_for_cross_workstream`).\n *\n * Cross-workstream ownership is structurally allowed by the schema:\n * `tasks.owner_id` is an INTEGER FK to `agents.id` with no\n * workstream qualifier on the agent side. The per-workstream UNIQUE\n * on `agents(workstream_id, name)` is what previously made the\n * SDK's name → id lookup scope to one workstream; this option\n * widens that lookup to a different workstream when the operator\n * dispatches across a workstream boundary. The agent's own\n * workstream remains unchanged — only the task's `owner_id` points\n * out-of-workstream.\n */\n agentWorkstream?: string;\n /**\n * Anonymous claim: write `owner = NULL` instead of resolving an agent\n * name and checking the FK. Use when the actor is the orchestrator\n * (or a script, or a human) doing direct work in a workstream they\n * aren't a registered worker in.\n *\n * The actor name is still recorded — it ends up in `agent_logs.source`\n * for the auto-emitted `task claim` event — so provenance is preserved.\n * Just not in the FK column.\n *\n * Resolution order for the actor name (used as the log source):\n * 1. `actor` if explicitly passed.\n * 2. Current pane title (when `$TMUX_PANE` is set).\n * 3. `$USER`.\n * 4. The literal string 'unknown'.\n *\n * Mutually exclusive with `agentName` (the two are alternative\n * answers to \"who's the actor for this claim?\"). Passing both is a\n * usage error.\n */\n self?: boolean;\n /**\n * Override the actor name used for the log source when `self: true`.\n * Ignored when `self: false`. Useful when the orchestrator wants to\n * attribute the work to a meaningful name rather than the pane\n * title (e.g. \"deploy-bot\" rather than \"pi-mu\").\n */\n actor?: string;\n}\n\nexport interface ClaimResult {\n /** The agent now owning the task, or null when the claim was anonymous (--self). */\n ownerName: string | null;\n /** The actor recorded in the agent_logs event — the agent name for a\n * registered-worker claim, or the resolved actor for --self. */\n actorName: string;\n /** The previous owner (null if it was unowned). */\n previousOwnerName: string | null;\n /** The status BEFORE the claim; post-claim is IN_PROGRESS unless was CLOSED. */\n previousStatus: TaskStatus;\n /** The status AFTER the claim. */\n status: TaskStatus;\n}\n\n/**\n * Claim a task. Two modes:\n *\n * Worker claim (default):\n * Resolve an agent name from `opts.agentName` or from $TMUX_PANE's\n * pane title. The name MUST exist in the agents table (FK on\n * tasks.owner). Sets `owner = <name>`. This is what mu-spawned\n * workers do, and what `mu task claim --for <worker>` does for\n * orchestrator dispatch.\n *\n * Anonymous claim (--self):\n * Skip the name -> agents FK lookup entirely. Sets `owner = NULL`.\n * Records the actor in `agent_logs.source` instead. This is the\n * orchestrator-doing-direct-work path — the actor is logged but\n * not registered as a worker pane.\n *\n * Status side-effect: OPEN -> IN_PROGRESS; IN_PROGRESS / CLOSED unchanged.\n *\n * Concurrency: the worker-claim path uses a single-statement CAS UPDATE\n * with `WHERE owner IS NULL OR owner = ?` so two workers racing to\n * claim the same task can't both win. The anonymous path uses\n * `WHERE owner IS NULL` (anonymous claims don't 'own' the task in any\n * exclusive sense; if it's already owned by anyone, the anonymous claim\n * is a TaskAlreadyOwnedError just like a worker claim would be).\n */\nexport async function claimTask(\n db: Db,\n localId: string,\n opts: ClaimTaskOptions,\n): Promise<ClaimResult> {\n if (opts.self === true && opts.agentName !== undefined) {\n throw new Error(\"claimTask: --self and --for are mutually exclusive\");\n }\n\n if (opts.self === true) {\n return claimSelf(db, localId, opts);\n }\n\n // ── Worker claim path (registered agent owns the task) ──\n // currentAgentName() parses 'name · status · task' titles back to\n // just the name token — the registry FK is keyed on agents.name,\n // so the parser is essential after composeAgentTitle decorates.\n const agentName = opts.agentName ?? (await currentAgentName());\n if (!agentName) {\n throw new Error(\n \"claimTask: no agent name (pass opts.agentName, run inside an mu-spawned pane with $TMUX_PANE set, or pass --self for an anonymous claim)\",\n );\n }\n\n // Resolve the claiming agent to its surrogate id within the agent's\n // workstream — defaults to opts.workstream (today's same-ws path),\n // or opts.agentWorkstream when the CLI dispatched across a\n // workstream boundary via a qualified `--for <ws>/<name>` ref\n // (task_claim_for_cross_workstream).\n //\n // The schema permits cross-workstream owner_id assignment (FK to\n // agents.id only); the per-workstream UNIQUE on agents.name is the\n // only reason this SELECT was scoped narrowly before. Bare-name\n // dispatch keeps that scope to honour today's behaviour; qualified\n // dispatch widens it to the named workstream so the agent resolves\n // there.\n const claimerWorkstream = opts.agentWorkstream ?? opts.workstream;\n const claimerRow = db\n .prepare(\n `SELECT a.id AS id\n FROM agents a JOIN workstreams ws ON ws.id = a.workstream_id\n WHERE a.name = ? AND ws.name = ?`,\n )\n .get(agentName, claimerWorkstream) as { id: number } | undefined;\n if (!claimerRow) {\n const paneIdFromEnv = opts.agentName === undefined ? (process.env.TMUX_PANE ?? null) : null;\n throw new ClaimerNotRegisteredError(agentName, paneIdFromEnv);\n }\n\n return db.transaction(() => {\n // Resolve the task within opts.workstream. This locks the\n // (workstream, local_id) pair for the rest of the transaction.\n const before = getTask(db, localId, opts.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n\n const now = new Date().toISOString();\n const result = db\n .prepare(\n `UPDATE tasks\n SET owner_id = ?,\n status = CASE WHEN status = 'OPEN' THEN 'IN_PROGRESS' ELSE status END,\n updated_at = ?\n WHERE local_id = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)\n AND (owner_id IS NULL OR owner_id = ?)`,\n )\n .run(claimerRow.id, now, localId, opts.workstream, claimerRow.id);\n\n if (result.changes === 0) {\n throw new TaskAlreadyOwnedError(localId, before.ownerName ?? \"<unknown>\");\n }\n\n const after = getTask(db, localId, opts.workstream);\n if (!after) throw new Error(`claimTask: row missing after update: ${localId}`);\n const statusBit = after.status !== before.status ? `, ${before.status} → ${after.status}` : \"\";\n emitEvent(\n db,\n opts.workstream,\n formatClaimEvent({\n localId,\n actor: agentName,\n anonymous: false,\n prose: `task claim ${localId} by ${agentName} (was owner=${before.ownerName ?? \"none\"}${statusBit})${evidenceSuffix(opts)}`,\n }),\n agentName,\n );\n return {\n ownerName: agentName,\n actorName: agentName,\n previousOwnerName: before.ownerName,\n previousStatus: before.status,\n status: after.status,\n };\n })();\n}\n\n/**\n * Resolve the current actor's identity for attribution in task notes,\n * --self claims, and any other write that wants 'who did this?'.\n *\n * Resolution order:\n * 1. $MU_AGENT_NAME env var (set by mu spawnAgent on every managed\n * pane; surfaced from the f3d4bdd commit). Authoritative when\n * present — you're inside a mu-spawned worker, no ambiguity.\n * 2. tmux pane title (the pane-title identity step). Works\n * when running inside any pane mu manages OR adopted.\n * 3. $USER (when running outside tmux entirely).\n * 4. The literal 'orchestrator' as a last-resort default.\n *\n * Why prefer env over pane title: pane titles are a tmux-server-wide\n * resource that anything can rewrite. The env var is set per-pane at\n * spawn time and is unforgeable from outside without explicit\n * `--actor` override. Pane title is the only identity available for\n * adopted panes that didn't go through mu's spawn path.\n */\nexport async function resolveActorIdentity(): Promise<string> {\n const muAgent = process.env.MU_AGENT_NAME;\n if (muAgent !== undefined && muAgent !== \"\") return muAgent;\n const paneTitle = await currentAgentName();\n if (paneTitle !== undefined && paneTitle !== \"\") return paneTitle;\n const user = process.env.USER;\n if (user !== undefined && user !== \"\") return user;\n return \"orchestrator\";\n}\n\nasync function claimSelf(db: Db, localId: string, opts: ClaimTaskOptions): Promise<ClaimResult> {\n const actor =\n opts.actor !== undefined && opts.actor !== \"\" ? opts.actor : await resolveActorIdentity();\n return db.transaction(() => {\n // Scope by the operator's workstream so a same-named task\n // elsewhere can't be self-claimed by accident.\n const before = getTask(db, localId, opts.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n\n // Anonymous claim: owner stays NULL, status flips OPEN -> IN_PROGRESS.\n // Gate on `owner_id IS NULL` so an in-flight worker claim can't be\n // silently overwritten.\n const now = new Date().toISOString();\n const result = db\n .prepare(\n `UPDATE tasks\n SET status = CASE WHEN status = 'OPEN' THEN 'IN_PROGRESS' ELSE status END,\n updated_at = ?\n WHERE local_id = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)\n AND owner_id IS NULL`,\n )\n .run(now, localId, before.workstreamName);\n\n if (result.changes === 0) {\n // Task exists but is already owned (by someone). Mirror the\n // worker-path error so callers can pattern-match consistently.\n throw new TaskAlreadyOwnedError(localId, before.ownerName ?? \"<unknown>\");\n }\n\n const after = getTask(db, localId, before.workstreamName);\n if (!after) throw new Error(`claimTask: row missing after update: ${localId}`);\n const statusBit = after.status !== before.status ? `, ${before.status} → ${after.status}` : \"\";\n emitEvent(\n db,\n before.workstreamName,\n formatClaimEvent({\n localId,\n actor,\n anonymous: true,\n prose: `task claim ${localId} by ${actor} --self (anonymous, owner stays NULL${statusBit})${evidenceSuffix(opts)}`,\n }),\n actor,\n );\n return {\n ownerName: null,\n actorName: actor,\n previousOwnerName: before.ownerName,\n previousStatus: before.status,\n status: after.status,\n };\n })();\n}\n","// mu — waitForTasks: block until tasks reach a target status.\n//\n// The orchestrator pattern: dispatch N workers via mu task claim --for,\n// then wait until they're all done before reviewing/merging.\n//\n// Pre-existing alternatives + why this verb exists:\n//\n// awk pipe over `mu log --tail`: works for ONE event but the\n// awk script becomes stateful (tracking 'which of N have closed?')\n// for multi-task waits. Bad shape for SKILL examples.\n//\n// Implementation:\n//\n// 1. Initial check — if the wait condition is already satisfied,\n// exit immediately. No subscription needed.\n// 2. Otherwise, poll the tasks table every pollMs. Same cadence as\n// mu log --tail (default 1000ms). We don't subscribe to\n// agent_logs because (a) we'd still need to re-query tasks to\n// learn the current status, (b) some status changes happen via\n// mu sql which doesn't emit events, and (c) the polling cost is\n// one indexed SELECT every second — cheaper than parsing the\n// log stream.\n// 3. Exit on success (all/any reached) OR timeout. Caller maps\n// timeout to exit code 5.\n//\n// Extracted from src/tasks.ts as part of refactor_split_large_src_files.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { StallDetectedDuringWaitError, TaskNotFoundError } from \"./errors.js\";\nimport { getTask } from \"./queries.js\";\nimport type { TaskStatus } from \"./status.js\";\n\n// ─── Test seams: poll-sleep + poll counter + stuck-warn writer ─────────\n//\n// Mirror src/tmux.ts's setSleepForTests pattern. Default sleep is a real\n// setTimeout; tests can swap in an instant + counted version to assert\n// poll cadence (the bug fixed alongside this hook silently sleeps a full\n// pollMs past the deadline when pollMs > timeoutMs — see test/tasks.test.ts\n// 'waitForTasks' regression cases).\n//\n// The stuck-warn writer is the second seam: agent_close_discipline_gap\n// added a per-poll \"this task is IN_PROGRESS but owner is needs_input\n// for too long\" warning emitted to stderr; tests intercept it via\n// setWaitStuckWarnForTests so they can assert exactly-once dedupe\n// without scraping process.stderr.\n\nlet currentWaitSleep: (ms: number) => Promise<void> = (ms) =>\n new Promise((resolve) => setTimeout(resolve, ms));\nlet pollCount = 0;\nconst defaultStuckWarn: (msg: string) => void = (msg) => {\n process.stderr.write(msg);\n};\nlet currentStuckWarn: (msg: string) => void = defaultStuckWarn;\n\nexport function setWaitSleepForTests(\n impl: ((ms: number) => Promise<void>) | undefined,\n): (ms: number) => Promise<void> {\n const previous = currentWaitSleep;\n currentWaitSleep = impl ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));\n return previous;\n}\n\n/** Test seam: swap the stderr writer used by the stuck-task warning so\n * unit tests can capture warnings without spying on process.stderr. */\nexport function setWaitStuckWarnForTests(\n impl: ((msg: string) => void) | undefined,\n): (msg: string) => void {\n const previous = currentStuckWarn;\n currentStuckWarn = impl ?? defaultStuckWarn;\n return previous;\n}\n\n/** Total number of polls performed across all `waitForTasks` calls in this\n * process. Tests typically reset before exercising and read after. */\nexport function getWaitPollCount(): number {\n return pollCount;\n}\n\nexport function resetWaitPollCount(): void {\n pollCount = 0;\n}\n\n/** A single task ref the wait verb is watching. Cross-workstream\n * waits arrive as a heterogeneous list of (workstream, name) pairs;\n * the legacy single-workstream call passes the same workstream on\n * every ref. task_wait_cross_workstream. */\nexport interface TaskWaitRef {\n /** The workstream the task lives in. Each ref carries its own so\n * the SDK doesn't need a single \"the workstream\" — cross-ws waits\n * pass refs from multiple workstreams in one call. */\n workstreamName: string;\n /** The task's per-workstream-unique local id. */\n name: string;\n}\n\nexport interface TaskWaitOptions {\n /** Target status. Default 'CLOSED'. */\n status?: TaskStatus;\n /** When true, succeed as soon as ONE listed task reaches the target.\n * Default false: every listed task must reach the target. */\n any?: boolean;\n /** Maximum time to wait, in milliseconds. Default 600_000 (10 min).\n * Pass 0 to wait forever. */\n timeoutMs?: number;\n /** Polling interval. Default 1000ms; overridable for tests. */\n pollMs?: number;\n /** Workstream context applied to bare-string ids. Required when the\n * caller passes `string[]`; ignored when the caller passes\n * `TaskWaitRef[]` (each ref carries its own ws). The legacy\n * single-ws SDK call site keeps its today's shape; the cross-ws\n * callers (CLI verb) pass `TaskWaitRef[]` and omit `workstream`.\n * task_wait_cross_workstream. */\n workstream?: string;\n /** Emit a yellow STUCK warning to stderr (once per task per wait call)\n * when an IN_PROGRESS task's owner has been in `needs_input` for at\n * least this many milliseconds since the agent row's last update.\n * Default 300_000 (5 min). Pass 0 to disable.\n *\n * Surfaced by agent_close_discipline_gap in mufeedback: workers\n * occasionally finish + commit + go idle without running\n * `mu task close <id>`, leaving wait blocked indefinitely. The\n * warning is observation-only — wait keeps polling so the operator\n * (or a wrapping policy) decides whether to force-close, re-prompt,\n * or escalate. */\n stuckAfterMs?: number;\n /** What to do when the `--stuck-after` predicate fires on a watched\n * task. `'warn'` (default) = today's behaviour: yellow STUCK line\n * to stderr (deduped per task per wait call) + corroborating\n * `kind='event'` agent_logs row; wait keeps polling. `'exit'` =\n * same emit + persist, but THEN throw `StallDetectedDuringWaitError`\n * so the CLI wrapper exits 7 (STALL_DETECTED). The exit-action is\n * the unattended-orchestrator escape: a wrapping policy can branch\n * on 7 (idle, ambiguous — operator decides poke vs release) vs 6\n * (dead pane, unambiguous — re-dispatch).\n *\n * Carve-out (lives at the call site, not here): the CLI passes\n * `'exit'` only when the wait target is CLOSED — mirrors exit-6's\n * reaper-flip suppression. With `--status OPEN` the worker reaching\n * needs_input might BE the success path. See\n * task_wait_stall_action_flag. */\n onStall?: \"warn\" | \"exit\";\n /** Optional async hook run BEFORE every snapshot (initial + each\n * poll iteration). The CLI uses this to reconcile the workstream\n * each tick (reaper flips IN_PROGRESS → OPEN for dead-pane\n * workers) and to throw a typed error when a reaper-flip on a\n * watched task should abandon the wait — see\n * task_wait_reconcile_dead_panes. Throwing from `beforePoll`\n * propagates out of `waitForTasks` unchanged.\n *\n * Kept as a generic seam (not a `--reconcile`-shaped option) so\n * the SDK module stays free of tmux/reconcile imports — that\n * layering belongs above the SDK in the CLI wrapper. */\n beforePoll?: () => Promise<void>;\n}\n\nexport interface TaskWaitTaskState {\n /** The workstream this task lives in. Cross-workstream waits\n * return a mixed list; the workstream is part of identity.\n * task_wait_cross_workstream. */\n workstreamName: string;\n /** The task's per-workstream-unique name. */\n name: string;\n /** Current status (at the moment we exit). */\n status: TaskStatus;\n /** Owner at exit time (NULL when unowned, after release, or after\n * the reaper flipped IN_PROGRESS → OPEN due to a dead pane). */\n owner: string | null;\n /** True when this task's status equals the target. */\n reachedTarget: boolean;\n /** True when the task is IN_PROGRESS, owned by a registered agent\n * whose detected status is `needs_input` for >= `stuckAfterMs`.\n * Surfaces the agent_close_discipline_gap pattern: worker finished +\n * committed but skipped `mu task close <id>`. Backwards-compatible\n * signal — callers ignoring it see no behaviour change. */\n stuck: boolean;\n}\n\nexport interface TaskWaitResult {\n /** Per-task state at exit time. Same length and order as the input\n * list. The caller derives all-reached / any-reached / elapsed\n * from this list (count `r.reachedTarget`) and from its own\n * startedAt clock — keeping the SDK return minimal. */\n refs: TaskWaitTaskState[];\n /** True when we exited because of the timeout, not because the wait\n * condition was met. Refs that did reach the target are still\n * reflected in `refs[i].reachedTarget` on partial-progress timeout. */\n timedOut: boolean;\n}\n\n/**\n * Block until a set of tasks reaches `opts.status` (default CLOSED).\n * Returns a result describing the final state — the caller decides\n * whether to treat partial-progress timeouts as success or failure\n * (the CLI maps a clean exit to 0, a timeout to 5).\n *\n * Pre-flight: every task in `localIds` MUST exist; missing ones throw\n * TaskNotFoundError before any waiting begins. This is loud-fail by\n * design — a typo'd id silently waiting forever is the worst-case UX.\n */\nexport async function waitForTasks(\n db: Db,\n input: readonly TaskWaitRef[] | readonly string[],\n opts: TaskWaitOptions,\n): Promise<TaskWaitResult> {\n if (input.length === 0) {\n throw new Error(\"waitForTasks: refs must be non-empty\");\n }\n // Normalise to TaskWaitRef[]. The string[] shape preserves today's\n // single-workstream SDK contract (callers pass `workstream` on\n // opts); the TaskWaitRef[] shape carries its own ws per ref so\n // cross-workstream waits don't need a sentinel.\n const refs: readonly TaskWaitRef[] = input.map((entry) => {\n if (typeof entry === \"string\") {\n if (opts.workstream === undefined)\n throw new Error(\n \"waitForTasks: string ids require opts.workstream; pass TaskWaitRef[] for cross-workstream waits\",\n );\n return { workstreamName: opts.workstream, name: entry };\n }\n return entry;\n });\n const target: TaskStatus = opts.status ?? \"CLOSED\";\n const wantAny = opts.any === true;\n const timeoutMs = opts.timeoutMs ?? 600_000;\n const pollMs = opts.pollMs ?? 1000;\n const stuckAfterMs = opts.stuckAfterMs ?? 300_000;\n const onStall: \"warn\" | \"exit\" = opts.onStall ?? \"warn\";\n const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY;\n\n // Pre-flight: every ref must exist in its own workstream scope.\n // Cross-workstream waits validate each ref against its declared ws\n // — a typo in the workstream half of `<ws>/<name>` should land here\n // with a clear TaskNotFoundError, not silently wait forever.\n for (const ref of refs) {\n if (getTask(db, ref.name, ref.workstreamName) === undefined)\n throw new TaskNotFoundError(`${ref.workstreamName}/${ref.name}`);\n }\n\n // Per-(ws,name) dedupe: emit the STUCK warning at most ONCE per wait\n // call, not once per poll cycle. Operators don't want their stderr\n // filled with the same yellow line every second; one nudge is enough.\n const stuckWarned = new Set<string>();\n const refKey = (ref: TaskWaitRef): string => `${ref.workstreamName}/${ref.name}`;\n\n /**\n * Detect the agent_close_discipline_gap pattern for one task:\n * IN_PROGRESS in the DB, owned by a registered agent whose status\n * is `needs_input` and whose `updated_at` is older than\n * `stuckAfterMs`. We query agents directly (not via getAgent) to\n * avoid an import cycle (src/agents.ts already imports from\n * src/tasks.ts).\n */\n const isStuck = (status: TaskStatus, owner: string | null, workstream: string): boolean => {\n if (stuckAfterMs <= 0) return false;\n if (status !== \"IN_PROGRESS\" || !owner) return false;\n // owner is the operator-facing agent name; agents.name is\n // per-workstream unique in v5. Scope the lookup by workstream so\n // a same-named worker elsewhere doesn't spuriously mark this task\n // stuck.\n const row = db\n .prepare(\n `SELECT a.status AS status, a.updated_at AS updated_at\n FROM agents a\n JOIN workstreams ws ON ws.id = a.workstream_id\n WHERE a.name = ? AND ws.name = ?`,\n )\n .get(owner, workstream) as { status: string; updated_at: string } | undefined;\n if (!row || row.status !== \"needs_input\") return false;\n const ageMs = Date.now() - new Date(row.updated_at).getTime();\n return ageMs >= stuckAfterMs;\n };\n\n /** Read current state of all tasks; returns the result shape. */\n const snapshot = (): TaskWaitResult => {\n const refStates: TaskWaitTaskState[] = refs.map((ref) => {\n const row = getTask(db, ref.name, ref.workstreamName);\n // Defensive: if a task was deleted mid-wait, treat as 'never\n // reached'. (Not the same as TaskNotFoundError pre-flight —\n // deletion mid-wait shouldn't crash the wait; it's a legitimate\n // state change.)\n const status = (row?.status ?? \"OPEN\") as TaskStatus;\n const owner = row?.ownerName ?? null;\n const stuck = isStuck(status, owner, ref.workstreamName);\n const key = refKey(ref);\n if (stuck && !stuckWarned.has(key)) {\n stuckWarned.add(key);\n // Yellow ANSI escape inline (no picocolors import — keeps the\n // SDK module dep-free; the CLI layer already pulls picocolors).\n // The message is one line, prefixed with `mu task wait:` so\n // log greppers can target it. Cross-ws waits include the\n // qualified `<ws>/<name>` so the operator sees which.\n currentStuckWarn(\n `\\x1b[33mmu task wait: ${key} stuck — owner=${owner ?? \"<none>\"} in needs_input ` +\n `(>= ${stuckAfterMs}ms since last status change). ` +\n `Worker likely committed but skipped \\`mu task close ${ref.name}\\`.\\x1b[0m\\n`,\n );\n // Persist a corroborating kind='event' row so other consumers\n // (mu state, mu log --kind event, dashboards) see the same\n // signal that the new derived `idle` flag surfaces in the\n // agents table. The stderr warning stays — observation-only,\n // operator decides recovery. See idle_assigned_agent_detection.\n const ageSecs = Math.round(stuckAfterMs / 1000);\n emitEvent(\n db,\n ref.workstreamName,\n `agent stalled ${owner ?? \"<none>\"} owns ${ref.name} for ${ageSecs}s`,\n owner ?? \"system\",\n );\n // task_wait_stall_action_flag: with `--on-stall exit` (the\n // unattended-orchestrator escape), the same emit + persist\n // happens but we then throw — the CLI's classifyError maps\n // this to exit code 7 (STALL_DETECTED). The carve-out\n // (target=CLOSED only) lives at the call site: the CLI\n // wrapper passes `onStall: 'exit'` only when target=CLOSED.\n // Precedence vs exit-6 (dead pane): the reaper-flip check\n // throws inside `beforePoll` BEFORE snapshot() runs, so a\n // watched task that's both reaper-flipped AND stale never\n // reaches this branch (status would already be OPEN).\n if (onStall === \"exit\") {\n throw new StallDetectedDuringWaitError(ref.name, owner, ref.workstreamName, ageSecs);\n }\n }\n return {\n workstreamName: ref.workstreamName,\n name: ref.name,\n status,\n owner,\n reachedTarget: status === target,\n stuck,\n };\n });\n return {\n refs: refStates,\n timedOut: false,\n };\n };\n\n /** Has the wait condition been met? */\n const isDone = (snap: TaskWaitResult): boolean => {\n const reached = snap.refs.filter((r) => r.reachedTarget).length;\n return wantAny ? reached > 0 : reached === snap.refs.length;\n };\n\n // Initial check: maybe we're already done. Run beforePoll first so\n // the CLI's per-poll reconcile (task_wait_reconcile_dead_panes) runs\n // even on the immediate-exit path — a dead-pane worker that died\n // BEFORE the operator typed `mu task wait` should still fail fast.\n if (opts.beforePoll) await opts.beforePoll();\n let snap = snapshot();\n if (isDone(snap)) return snap;\n\n // Poll loop.\n //\n // Sleep is clamped to `min(pollMs, deadline - now)` so the function\n // returns within `timeoutMs + small slack`, never `pollMs` later.\n // Without the clamp, `pollMs=10000, timeoutMs=100` sleeps a full 10s\n // before noticing the deadline expired. When the clamp goes <= 0 we\n // skip the sleep entirely and re-snapshot before bailing on the\n // timeout — gives the wait one last chance at a winning state right\n // at the deadline boundary, and avoids passing 0 / negatives to\n // setTimeout (which has implementation-defined behaviour).\n for (;;) {\n const now = Date.now();\n if (now >= deadline) {\n return { ...snap, timedOut: true };\n }\n const sleepMs =\n deadline === Number.POSITIVE_INFINITY ? pollMs : Math.min(pollMs, deadline - now);\n if (sleepMs > 0) {\n await currentWaitSleep(sleepMs);\n }\n pollCount += 1;\n if (opts.beforePoll) await opts.beforePoll();\n snap = snapshot();\n if (isDone(snap)) return snap;\n }\n}\n","// mu — adoptAgent: register an existing tmux pane as a managed agent.\n//\n// The inverse of `mu agent spawn` — instead of creating the pane,\n// adoptAgent hooks an already-existing pane into mu's registry.\n//\n// Two flavours of resolution:\n// - by paneId ('%15') : look up directly via tmux\n// - by paneTitle ('worker-1') : scan tmux panes for matching title\n//\n// Common cases for adopt:\n// - operator manually started a CLI in a pane before installing mu\n// - mu agent close was followed by re-attaching the pane via tmux\n// - migrating from a previous orchestrator\n//\n// Extracted from src/agents.ts as part of refactor_split_large_src_files.\n\nimport {\n type AgentRow,\n getAgent,\n getAgentByPane,\n insertAgent,\n isValidAgentName,\n} from \"../agents.js\";\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport {\n PaneNotFoundError,\n type TmuxPane,\n listPanesInSession,\n paneExists,\n parseAgentNameFromTitle,\n setPaneTitle,\n} from \"../tmux.js\";\nimport { AgentExistsError, AgentNotInWorkstreamError } from \"./errors.js\";\n\nexport interface AdoptAgentOptions {\n /** tmux pane id (e.g. '%15'). Must already exist on the tmux server. */\n paneId: string;\n /** Workstream to adopt the pane into. The pane MUST be in the\n * matching tmux session (`mu-<workstream>`); cross-session adopt is\n * rejected. */\n workstream: string;\n /** Override the pane's title with this name. When omitted, the pane's\n * current title becomes the agent name (zero-config adoption). */\n name?: string;\n /** Defaults to 'pi' via the schema DEFAULT. */\n cli?: string;\n /** 'full-access' (default) or 'read-only'. */\n role?: string;\n /** Override the tmux session lookup. Defaults to `mu-<workstream>`. */\n tmuxSession?: string;\n}\n\nexport interface AdoptAgentResult {\n agent: AgentRow;\n /** True when the pane already had a matching agents row — the call\n * was a no-op (idempotent). */\n alreadyAdopted: boolean;\n /** The pane title before adopt, or null if the pane had no title. */\n previousTitle: string | null;\n /** The title the pane was set to (== agent.name post-adopt). Equal to\n * previousTitle when no retitle happened. */\n paneTitleSetTo: string;\n}\n\n/**\n * Register an existing tmux pane as a managed mu agent. Inverse of the\n * 'orphan' state surfaced by `mu agent list`: a pane that looks like an\n * agent (running pi/claude/codex) but has no DB row.\n *\n * Identity contract (matches the claim protocol invariant):\n * - Post-adopt, the pane's title equals the agent's name.\n * - When `name` is omitted, the pane's existing title becomes the\n * agent name verbatim. Adopting a pane titled 'pi' would fail name\n * validation — caller must supply --name in that case.\n *\n * Idempotent: adopting the same pane twice with the same name is a\n * no-op (returns alreadyAdopted=true). Adopting a different pane under\n * an existing agent name throws AgentExistsError.\n *\n * Validation order (matches the design in note #100):\n * 1. Pane id format -> assertValidPaneId via paneExists / setPaneTitle\n * 2. Pane exists -> PaneNotFoundError\n * 3. Pane is in session -> AgentNotInWorkstreamError (cross-session)\n * 4. Resolved name OK -> isValidAgentName / Error('agent name invalid')\n * 5. Idempotent check -> if pane already owned by an agent of this\n * name, return alreadyAdopted=true\n * 6. Name not taken -> AgentExistsError (else)\n * 7. Insert + retitle.\n *\n * Status starts at 'free' — reconcile/detect will update it on the next\n * `mu agent list` based on actual pane content (the pi prompt yields\n * 'free'; an agent mid-thought yields 'busy'; etc.). We don't run\n * detection inline here because the caller may not have $TMUX, and\n * adoption shouldn't depend on a captured-pane probe succeeding.\n */\nexport async function adoptAgent(db: Db, opts: AdoptAgentOptions): Promise<AdoptAgentResult> {\n // Step 1+2: pane format + existence.\n if (!(await paneExists(opts.paneId))) {\n throw new PaneNotFoundError(opts.paneId);\n }\n\n // Step 3: pane must be in the workstream's tmux session.\n const expectedSession = opts.tmuxSession ?? `mu-${opts.workstream}`;\n const panesInSession: TmuxPane[] = await listPanesInSession(expectedSession);\n const matchingPane = panesInSession.find((p) => p.paneId === opts.paneId);\n if (!matchingPane) {\n // Pane exists (passed step 2) but isn't in the expected session.\n // Synthesise the cross-session error using the same shape as the\n // existing AgentNotInWorkstreamError path so the CLI's exit-code\n // mapping handles it identically. We don't know the actual session\n // name without another tmux query; the message just says 'a\n // different session' — actionable enough.\n throw new AgentNotInWorkstreamError(\n `pane ${opts.paneId}`,\n opts.workstream,\n \"a different tmux session\",\n );\n }\n\n // Step 4: resolved name. Default to the pane's current title —\n // unwrapping a possibly-composed mu title ('name · <STATUS_EMOJI> · task')\n // back to just the name token. Re-adoption of a pane that mu previously\n // owned must work; without parseAgentNameFromTitle the ' · <glyph>'\n // suffix would fail isValidAgentName.\n const previousTitle = matchingPane.title.length > 0 ? matchingPane.title : null;\n const candidate =\n opts.name ?? (previousTitle !== null ? parseAgentNameFromTitle(previousTitle) : \"\");\n const resolvedName = candidate;\n if (!isValidAgentName(resolvedName)) {\n if (opts.name === undefined) {\n throw new Error(\n `pane ${opts.paneId} title '${previousTitle ?? \"(empty)\"}' is not a valid agent name; pass --name <name>`,\n );\n }\n throw new Error(`agent name invalid: '${resolvedName}'`);\n }\n\n // Step 5: idempotency. If an agent already owns this pane id, check\n // whether it's the same name; same name == no-op, different name ==\n // conflict (we don't move agents between pane ids silently).\n const existingByPane = getAgentByPane(db, opts.paneId);\n if (existingByPane) {\n if (existingByPane.name === resolvedName) {\n return {\n agent: existingByPane,\n alreadyAdopted: true,\n previousTitle,\n paneTitleSetTo: resolvedName,\n };\n }\n throw new AgentExistsError(existingByPane.name);\n }\n\n // Step 6: name not taken in THIS workstream. v5 makes agent names\n // per-workstream unique — 'worker-1' may legally exist in another\n // workstream; only refuse when it collides in the workstream we're\n // adopting into (bug_v5_name_clash_silent_misroute).\n const existingByName = getAgent(db, resolvedName, opts.workstream);\n if (existingByName) {\n throw new AgentExistsError(resolvedName);\n }\n\n // Step 7: insert + (conditional) retitle.\n const inserted = insertAgent(db, {\n name: resolvedName,\n workstream: opts.workstream,\n paneId: opts.paneId,\n status: \"free\",\n cli: opts.cli,\n role: opts.role,\n });\n if (resolvedName !== previousTitle) {\n await setPaneTitle(opts.paneId, resolvedName);\n }\n emitEvent(\n db,\n opts.workstream,\n `agent adopt ${resolvedName} (pane ${opts.paneId}, was title='${previousTitle ?? \"\"}')`,\n );\n return {\n agent: inserted,\n alreadyAdopted: false,\n previousTitle,\n paneTitleSetTo: resolvedName,\n };\n}\n","// mu — kickAgent: signal a wedged worker pane's foreground process\n// group from outside the pane.\n//\n// Why this verb exists: pi/claude/codex CLIs catch SIGINT themselves\n// (Ctrl-C is a UI input, not a process signal). When a worker is\n// wedged on a long-running tool subprocess (an unbounded `find /`,\n// a busy-wait loop, ...) the orchestrator's options were:\n//\n// - `mu agent send` — queues; the message is read after the tool\n// subprocess returns, which is exactly the wait we're trying to\n// shortcut.\n// - `tmux send-keys C-c` against the pane — the wrapping CLI eats\n// the signal as TUI input; doesn't reach the tool subprocess.\n// - drop out of mu, `pgrep -af \"find /\"`, `kill <pid>` — works,\n// but breaks the orchestrator's mental model and is fiddly.\n//\n// `mu agent kick` looks up the pane's TTY via tmux, asks `ps -t <tty>`\n// for the foreground process group (the pgid whose `stat` field\n// contains a `+`), and kills(2) that pgid directly. Default signal\n// is SIGINT (graceful, matches what Ctrl-C would do if it\n// propagated); --signal SIGTERM / SIGKILL escalate.\n//\n// Source feedback: workers_commonly_attempt_unbounded_find. The\n// PART A spec.\n\nimport { type AgentRow, getAgent } from \"../agents.js\";\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport { paneTTY } from \"../tmux.js\";\nimport { AgentNotFoundError } from \"./errors.js\";\n\n// ─── Allowed signals ─────────────────────────────────────────────────\n\n/** The signal set kick supports. SIGINT is graceful (matches Ctrl-C\n * semantics — what the operator probably wanted in the first place);\n * SIGTERM is the polite escalation; SIGKILL is the unblockable\n * hammer. We deliberately don't expose arbitrary signals — the\n * three above are the actionable ones for \"interrupt a wedged\n * foreground tool subprocess.\" */\nexport type KickSignal = \"SIGINT\" | \"SIGTERM\" | \"SIGKILL\";\n\nconst ALLOWED_SIGNALS: readonly KickSignal[] = [\"SIGINT\", \"SIGTERM\", \"SIGKILL\"];\n\nexport function isKickSignal(s: string): s is KickSignal {\n return (ALLOWED_SIGNALS as readonly string[]).includes(s);\n}\n\n// ─── Errors ──────────────────────────────────────────────────────────\n\n/**\n * Thrown when the foreground pgid lookup on a pane's TTY yields\n * either no rows at all (the pane is sitting at an idle shell with\n * no foreground job) or only the wrapping shell itself (the LLM CLI\n * — pi/claude/codex — is the foreground; signalling it would close\n * the agent, which is what `mu agent close` is for).\n *\n * Maps to the generic exit code 1 in handle.ts (this is a\n * runtime-state condition, not a typed not-found / conflict).\n */\nexport class NoForegroundProcessError extends Error implements HasNextSteps {\n override readonly name = \"NoForegroundProcessError\";\n constructor(\n public readonly agentName: string,\n public readonly tty: string,\n public readonly reason: \"no-foreground\" | \"shell-only\",\n ) {\n const detail =\n reason === \"no-foreground\"\n ? `no foreground process group on tty ${tty} (pane is idle)`\n : `the only foreground process on tty ${tty} is the agent's wrapping CLI itself; refusing to signal it (use \\`mu agent close ${agentName}\\` to close the agent)`;\n super(`agent ${agentName}: ${detail}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Inspect what's running in the pane\",\n command: `mu agent show ${this.agentName} -n 50`,\n },\n {\n intent: \"Close the agent (kills the wrapping CLI + pane)\",\n command: `mu agent close ${this.agentName}`,\n },\n ];\n }\n}\n\n// ─── Process executor (swappable for tests) ──────────────────────────\n//\n// Mirrors the `setTmuxExecutor` pattern in src/tmux.ts so unit tests\n// can mock `ps -t <tty>` and `kill -<sig> -<pgid>` without touching\n// real processes.\n\nexport interface KickProcessExecResult {\n stdout: string;\n stderr: string;\n exitCode: number | null;\n}\n\nexport type KickProcessExecutor = (\n cmd: string,\n args: readonly string[],\n) => Promise<KickProcessExecResult>;\n\nconst realExecutor: KickProcessExecutor = async (cmd, args) => {\n // Lazy-load execa: this module is on the cold path (kick is rare),\n // and the unit tests swap in a mock executor before any real call\n // would be made.\n const { execa } = await import(\"execa\");\n const result = await execa(cmd, [...args], { reject: false });\n return {\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n exitCode: result.exitCode ?? null,\n };\n};\n\nlet currentExecutor: KickProcessExecutor = realExecutor;\n\n/** Install a custom executor (for tests). Returns the previous one so\n * tests can restore cleanly. */\nexport function setKickProcessExecutor(executor: KickProcessExecutor): KickProcessExecutor {\n const previous = currentExecutor;\n currentExecutor = executor;\n return previous;\n}\n\n/** Restore the real executor. */\nexport function resetKickProcessExecutor(): void {\n currentExecutor = realExecutor;\n}\n\n// ─── Foreground pgid lookup ──────────────────────────────────────────\n\ninterface PsRow {\n pid: number;\n pgid: number;\n /** ps's `stat` (or `state`) field. The presence of `+` means\n * \"foreground process group on its controlling tty\". */\n stat: string;\n /** Process command (just the comm; truncated, used for diagnostics). */\n comm: string;\n}\n\n/**\n * Parse `ps -t <tty> -o pid=,pgid=,stat=,comm=` output. Each non-blank\n * line is one process: four whitespace-separated fields. Defensive\n * about leading whitespace and command names with embedded spaces\n * (the comm is the LAST field — join the tail).\n */\nexport function parsePsTtyOutput(output: string): PsRow[] {\n const rows: PsRow[] = [];\n for (const raw of output.split(\"\\n\")) {\n const line = raw.trim();\n if (line === \"\") continue;\n const parts = line.split(/\\s+/);\n if (parts.length < 4) continue;\n const [pidStr, pgidStr, stat, ...commParts] = parts;\n if (pidStr === undefined || pgidStr === undefined || stat === undefined) continue;\n const pid = Number.parseInt(pidStr, 10);\n const pgid = Number.parseInt(pgidStr, 10);\n if (!Number.isFinite(pid) || !Number.isFinite(pgid)) continue;\n rows.push({ pid, pgid, stat, comm: commParts.join(\" \") });\n }\n return rows;\n}\n\n/**\n * Resolve the foreground process group id for a TTY device path. The\n * canonical signal `ps`'s `stat` field uses is `+` (BSD/Darwin AND\n * Linux procps). We pick the first row whose stat contains `+`; its\n * `pgid` is the foreground pgid of that controlling terminal.\n *\n * Returns:\n * - `{ kind: \"ok\", pgid, fgRow }` on success\n * - `{ kind: \"no-foreground\" }` when no row carries `+` AND there\n * are no candidate rows at all\n * - `{ kind: \"shell-only\", pgid, fgRow }` when the foreground pgid\n * resolves to a shell whose comm is the agent's wrapping CLI\n * (caller decides whether to refuse — kick refuses)\n *\n * The wrapping-CLI guard is intentionally narrow: we only refuse\n * when the foreground process command matches one of the known\n * pi/claude/codex/zsh/bash shapes. Anything else (a `find`, a\n * `cargo build`, a `python script.py`) is exactly what we want to\n * signal — that's the unbounded-tool case the verb was built for.\n */\nexport interface ForegroundLookup {\n kind: \"ok\" | \"shell-only\" | \"no-foreground\";\n pgid?: number;\n fgRow?: PsRow;\n /** All rows ps returned for the tty, for diagnostics / tests. */\n rows: PsRow[];\n}\n\n/** Comm names we treat as the agent's wrapping CLI / shell. Signalling\n * these via kick would just terminate the agent itself; that's\n * `mu agent close`'s job. The match is loose (suffix on basename)\n * so paths like `/usr/local/bin/pi` and aliases like `pi-meta` match\n * the right family. */\nconst WRAPPER_COMM_PREFIXES: readonly string[] = [\n \"pi\",\n \"claude\",\n \"codex\",\n \"bash\",\n \"zsh\",\n \"sh\",\n \"fish\",\n \"dash\",\n];\n\nfunction isWrapperComm(comm: string): boolean {\n // ps -o comm= returns the basename of the executable, possibly with\n // a leading `-` (login shell). Strip both.\n const cleaned = comm.replace(/^-/, \"\").trim();\n if (cleaned === \"\") return false;\n // Match exactly OR with a `-suffix` (e.g. `pi-meta`, `bash-3.2`).\n for (const prefix of WRAPPER_COMM_PREFIXES) {\n if (cleaned === prefix) return true;\n if (cleaned.startsWith(`${prefix}-`)) return true;\n }\n return false;\n}\n\nexport async function foregroundPgid(tty: string): Promise<ForegroundLookup> {\n // Strip the `/dev/` prefix because some `ps` implementations want\n // the tty as e.g. `ttys012` rather than `/dev/ttys012`. macOS's\n // ps accepts both; Linux's procps wants the short form. Pass the\n // short form everywhere for portability.\n const ttyShort = tty.startsWith(\"/dev/\") ? tty.slice(\"/dev/\".length) : tty;\n const result = await currentExecutor(\"ps\", [\"-t\", ttyShort, \"-o\", \"pid=,pgid=,stat=,comm=\"]);\n // ps exits 1 with empty output when no process is attached to the\n // tty; treat as no-foreground rather than throw.\n if (result.exitCode !== 0 && result.stdout.trim() === \"\") {\n return { kind: \"no-foreground\", rows: [] };\n }\n const rows = parsePsTtyOutput(result.stdout);\n if (rows.length === 0) return { kind: \"no-foreground\", rows };\n // Find the foreground row: stat contains `+`.\n const fg = rows.find((r) => r.stat.includes(\"+\"));\n if (!fg) {\n // ps returned rows but none is foreground. Treat as no-foreground.\n return { kind: \"no-foreground\", rows };\n }\n if (isWrapperComm(fg.comm)) {\n return { kind: \"shell-only\", pgid: fg.pgid, fgRow: fg, rows };\n }\n return { kind: \"ok\", pgid: fg.pgid, fgRow: fg, rows };\n}\n\n// ─── kill(2) the process group ────────────────────────────────────────\n\n/**\n * Send `signal` to process group `pgid`. The `kill -SIG -<pgid>` form\n * (negative pid) targets the whole pgrp, which is what we want — a\n * single `find` invocation may have spawned helpers; we want to take\n * the whole tree down with one signal.\n */\nasync function killPgrp(pgid: number, signal: KickSignal): Promise<void> {\n const result = await currentExecutor(\"kill\", [`-${signal}`, `-${pgid}`]);\n if (result.exitCode !== 0) {\n // ESRCH (pgid already gone) is benign — the process completed\n // between our ps and our kill. Treat as success.\n if (/no such process/i.test(result.stderr)) return;\n throw new Error(\n `kill -${signal} -${pgid} failed (exit ${result.exitCode}): ${result.stderr.trim() || \"no stderr\"}`,\n );\n }\n}\n\n// ─── Public verb ──────────────────────────────────────────────────────\n\nexport interface KickAgentOptions {\n workstream: string;\n /** Defaults to SIGINT (matches Ctrl-C semantics). */\n signal?: KickSignal;\n}\n\nexport interface KickAgentResult {\n agentName: string;\n paneId: string;\n /** TTY device path the foreground pgid was resolved against. */\n tty: string;\n /** The pgid we signalled. */\n signaledPgid: number;\n signal: KickSignal;\n /** The comm of the foreground process at the time of signal — useful\n * diagnostic in the event log (\"we kicked a `find`, not a `cargo`\"). */\n foregroundComm: string;\n}\n\n/**\n * Send `signal` to the foreground process group of an agent's pane\n * TTY. Default signal is SIGINT.\n *\n * Errors:\n * - `AgentNotFoundError` — the agent doesn't exist in this workstream.\n * - `PaneNotFoundError` (from paneTTY) — the agent's pane has vanished.\n * - `NoForegroundProcessError` — pane has no foreground job, OR the\n * foreground is the wrapping CLI itself (refuse; use `mu agent close`).\n *\n * Emits an `agent kick <name> (signal=..., pgid=..., comm=...)` event\n * on success.\n */\nexport async function kickAgent(\n db: Db,\n name: string,\n opts: KickAgentOptions,\n): Promise<KickAgentResult> {\n const signal: KickSignal = opts.signal ?? \"SIGINT\";\n const agent: AgentRow | undefined = getAgent(db, name, opts.workstream);\n if (!agent) throw new AgentNotFoundError(name, opts.workstream);\n const tty = await paneTTY(agent.paneId);\n const lookup = await foregroundPgid(tty);\n if (lookup.kind === \"no-foreground\") {\n throw new NoForegroundProcessError(name, tty, \"no-foreground\");\n }\n if (lookup.kind === \"shell-only\") {\n throw new NoForegroundProcessError(name, tty, \"shell-only\");\n }\n // kind === \"ok\"\n const pgid = lookup.pgid;\n const fgRow = lookup.fgRow;\n if (pgid === undefined || fgRow === undefined) {\n // Should be unreachable given the discriminator; defensive throw\n // satisfies noUncheckedIndexedAccess + future-refactor safety.\n throw new NoForegroundProcessError(name, tty, \"no-foreground\");\n }\n await killPgrp(pgid, signal);\n emitEvent(\n db,\n agent.workstreamName,\n `agent kick ${name} (signal=${signal}, pgid=${pgid}, comm=${fgRow.comm})`,\n );\n return {\n agentName: name,\n paneId: agent.paneId,\n tty,\n signaledPgid: pgid,\n signal,\n foregroundComm: fgRow.comm,\n };\n}\n","// mu — agent registry CRUD primitives + the five high-level verbs\n// (spawn, send, read, list, close) that the CLI in step 7 will wrap.\n//\n// Layering inside this file:\n//\n// - Types & raw-row mapping (RawAgentRow / rowFromDb)\n// - CRUD primitives (insertAgent, getAgent, listAgents,\n// updateAgentStatus, deleteAgent)\n// - Verbs (spawnAgent, sendToAgent, readAgent,\n// closeAgent, listLiveAgents)\n//\n// The verbs compose the CRUD primitives with src/tmux.ts and\n// src/reconcile.ts. They are deliberately thin — each one is essentially\n// \"look up the agent, do the tmux thing, update the registry.\"\n\nimport { type Db, resolveWorkstreamId, tryResolveWorkstreamId } from \"./db.js\";\nimport type { AgentStatus } from \"./detect.js\";\nimport { emitEvent } from \"./logs.js\";\nimport { type ReconcileMode, type ReconcileReport, reconcile } from \"./reconcile.js\";\nimport { captureSnapshot } from \"./snapshots.js\";\nimport { addNote, listTasksByOwner } from \"./tasks.js\";\n// Re-export the cluster modules so external callers continue to\n// `import { AgentNotFoundError, spawnAgent, ... } from \"./agents.js\"`.\nexport {\n AgentDiedOnSpawnError,\n AgentExistsError,\n AgentNotFoundError,\n AgentNotInWorkstreamError,\n AgentSpawnCliNotFoundError,\n AgentSpawnStartupError,\n WorkspacePreservedError,\n} from \"./agents/errors.js\";\nexport {\n type CommandResolutionResult,\n type CommandResolver,\n type SpawnAgentOptions,\n checkCommandResolvable,\n defaultSpawnLivenessMs,\n envVarNameForCli,\n resetCommandResolverForTests,\n resolveCliCommand,\n resolveCliCommandWithSource,\n setCommandResolverForTests,\n spawnAgent,\n} from \"./agents/spawn.js\";\nexport {\n type AdoptAgentOptions,\n type AdoptAgentResult,\n adoptAgent,\n} from \"./agents/adopt.js\";\nexport {\n type KickAgentOptions,\n type KickAgentResult,\n type KickSignal,\n type KickProcessExecutor,\n NoForegroundProcessError,\n foregroundPgid,\n isKickSignal,\n kickAgent,\n parsePsTtyOutput,\n resetKickProcessExecutor,\n setKickProcessExecutor,\n} from \"./agents/kick.js\";\nimport { AgentNotFoundError, WorkspacePreservedError } from \"./agents/errors.js\";\nimport {\n type CaptureOptions,\n type SendOptions,\n type TmuxPane,\n capturePane,\n killPane,\n sendToPane,\n setPaneTitle,\n} from \"./tmux.js\";\nimport { freeWorkspace, getWorkspaceForAgent, isWorkspaceClean } from \"./workspace.js\";\n// (freeWorkspace is used by the spawn rollback paths below, not by closeAgent.\n// Closing an agent is intentionally a separate concern from freeing its workspace;\n// see the closeAgent docstring.)\nimport { ensureWorkstream } from \"./workstream.js\";\n\nexport type { AgentStatus };\n\nexport interface AgentRow {\n name: string;\n /** Foreign-name reference to the owning workstream. */\n workstreamName: string;\n cli: string;\n paneId: string;\n status: AgentStatus;\n role: string;\n /** Window name; null when the agent has its own window named after itself. */\n tab: string | null;\n /** ISO 8601 timestamp. */\n createdAt: string;\n /** ISO 8601 timestamp. */\n updatedAt: string;\n /**\n * Derived 'idle but assigned' flag (idle_assigned_agent_detection).\n * Set ONLY by `listLiveAgents` (and the helper `computeAgentIdle`);\n * never stored in the DB. Predicate:\n * status === 'needs_input'\n * AND owns ≥1 IN_PROGRESS task in this workstream\n * AND (now - updated_at) >= MU_IDLE_THRESHOLD_MS (default 300_000ms)\n *\n * Surfaces the third lifecycle state (alive but assigned, no recent\n * progress) to `mu state` renders + `mu state --json`. Omitted (i.e.\n * absent — NOT `false`) when the predicate doesn't fire, so JSON\n * consumers can do a simple `if (agent.idle)` check and the field\n * stays out of the way for callers that don't care.\n */\n idle?: boolean;\n}\n\n/** Default idle threshold. Matches today's `mu task wait --stuck-after`\n * default so the two paths agree on what counts as 'stalled'. */\nconst DEFAULT_IDLE_THRESHOLD_MS = 300_000;\n\n/**\n * Read the operator-tunable idle threshold (`MU_IDLE_THRESHOLD_MS`).\n * Returns the default on any unparsable / negative value rather than\n * throwing — env-var typos shouldn't crash `mu state`.\n */\nexport function idleThresholdMs(): number {\n const env = process.env.MU_IDLE_THRESHOLD_MS;\n if (env === undefined || env === \"\") return DEFAULT_IDLE_THRESHOLD_MS;\n const n = Number.parseInt(env, 10);\n if (!Number.isFinite(n) || n < 0) return DEFAULT_IDLE_THRESHOLD_MS;\n return n;\n}\n\n/**\n * Decide whether an agent is in the 'idle but assigned' state. Pure\n * read on (agents, tasks); no side effects. Exported so `listLiveAgents`,\n * the renderers, and tests can share one source of truth.\n */\nexport function computeAgentIdle(db: Db, agent: AgentRow, now: number = Date.now()): boolean {\n if (agent.status !== \"needs_input\") return false;\n const threshold = idleThresholdMs();\n if (threshold <= 0) return false;\n const updated = Date.parse(agent.updatedAt);\n if (!Number.isFinite(updated)) return false;\n if (now - updated < threshold) return false;\n const wsId = tryResolveWorkstreamId(db, agent.workstreamName);\n if (wsId === null) return false;\n const row = db\n .prepare(\n `SELECT COUNT(*) AS n\n FROM tasks t\n JOIN agents a ON a.id = t.owner_id\n WHERE a.name = ? AND a.workstream_id = ? AND t.status = 'IN_PROGRESS'`,\n )\n .get(agent.name, wsId) as { n: number };\n return row.n > 0;\n}\n\nexport interface InsertAgentInput {\n name: string;\n workstream: string;\n paneId: string;\n status: AgentStatus;\n /** Defaults to \"pi\" via schema DEFAULT. */\n cli?: string;\n /** Defaults to \"full-access\" via schema DEFAULT. */\n role?: string;\n tab?: string | null;\n}\n\ninterface RawAgentRow {\n name: string;\n /** Joined from workstreams.name. */\n workstream: string;\n cli: string;\n pane_id: string;\n status: string;\n role: string;\n tab: string | null;\n created_at: string;\n updated_at: string;\n}\n\n/** SELECT clause that joins agents to workstreams, exposing the\n * operator-facing workstream name as `workstream`. Used by every\n * read path. */\nconst SELECT_AGENT_COLS = `\n a.name AS name,\n ws.name AS workstream,\n a.cli AS cli,\n a.pane_id AS pane_id,\n a.status AS status,\n a.role AS role,\n a.tab AS tab,\n a.created_at AS created_at,\n a.updated_at AS updated_at\n`;\n\nconst AGENT_FROM_JOIN = \"FROM agents a JOIN workstreams ws ON ws.id = a.workstream_id\";\n\nfunction rowFromDb(row: RawAgentRow): AgentRow {\n return {\n name: row.name,\n workstreamName: row.workstream,\n cli: row.cli,\n paneId: row.pane_id,\n status: row.status as AgentStatus,\n role: row.role,\n tab: row.tab,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\n/** Resolve an agent's surrogate id by (workstream, name). Returns\n * null on miss. */\nfunction agentIdByName(db: Db, name: string, workstream: string): number | null {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return null;\n const row = db\n .prepare(\"SELECT id FROM agents WHERE name = ? AND workstream_id = ?\")\n .get(name, wsId) as { id: number } | undefined;\n return row ? row.id : null;\n}\n\nexport function insertAgent(db: Db, input: InsertAgentInput): AgentRow {\n // Auto-create the workstreams row if missing so the FK on\n // agents.workstream_id is always satisfied. Preserves the ergonomics\n // where you could spawn without explicit `mu init`.\n ensureWorkstream(db, input.workstream);\n const workstreamId = resolveWorkstreamId(db, input.workstream);\n const now = new Date().toISOString();\n db.prepare(\n `INSERT INTO agents (name, workstream_id, cli, pane_id, status, role, tab, created_at, updated_at)\n VALUES (@name, @workstreamId, COALESCE(@cli, 'pi'), @paneId, @status,\n COALESCE(@role, 'full-access'), @tab, @now, @now)`,\n ).run({\n name: input.name,\n workstreamId,\n cli: input.cli ?? null,\n paneId: input.paneId,\n status: input.status,\n role: input.role ?? null,\n tab: input.tab ?? null,\n now,\n });\n const row = getAgent(db, input.name, input.workstream);\n if (!row) throw new Error(`agents.insertAgent: row not found after insert: ${input.name}`);\n return row;\n}\n\n/**\n * Look up an agent by its tmux pane id (e.g. `%4`). Returns undefined if\n * no agent currently owns that pane. Used by `mu me` and friends to\n * answer \"which agent am I?\" from `$TMUX_PANE` without the LLM having to\n * remember its own name.\n *\n * Note: `pane_id` is not declared UNIQUE in the schema (a managed agent\n * could in theory be re-spawned into the same recycled pane id) but in\n * practice tmux pane ids are unique within a server's lifetime, and\n * reconcile prunes ghosts. We return the first match.\n */\nexport function getAgentByPane(db: Db, paneId: string): AgentRow | undefined {\n const row = db\n .prepare(`SELECT ${SELECT_AGENT_COLS} ${AGENT_FROM_JOIN} WHERE a.pane_id = ? LIMIT 1`)\n .get(paneId) as RawAgentRow | undefined;\n return row ? rowFromDb(row) : undefined;\n}\n\nexport function getAgent(db: Db, name: string, workstream: string): AgentRow | undefined {\n // v5: agents.name is per-workstream unique, not globally unique.\n // Workstream is required so the same name in two workstreams\n // resolves unambiguously.\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return undefined;\n const row = db\n .prepare(\n `SELECT ${SELECT_AGENT_COLS} ${AGENT_FROM_JOIN} WHERE a.name = ? AND a.workstream_id = ?`,\n )\n .get(name, wsId) as RawAgentRow | undefined;\n return row ? rowFromDb(row) : undefined;\n}\n\nexport function listAgents(db: Db, opts: { workstream?: string } = {}): AgentRow[] {\n if (opts.workstream === undefined) {\n const rows = db\n .prepare(`SELECT ${SELECT_AGENT_COLS} ${AGENT_FROM_JOIN} ORDER BY ws.name, a.name`)\n .all() as RawAgentRow[];\n return rows.map(rowFromDb);\n }\n const wsId = tryResolveWorkstreamId(db, opts.workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(\n `SELECT ${SELECT_AGENT_COLS} ${AGENT_FROM_JOIN} WHERE a.workstream_id = ? ORDER BY a.name`,\n )\n .all(wsId) as RawAgentRow[];\n return rows.map(rowFromDb);\n}\n\n/**\n * Update an agent's status. Returns true if a row was matched.\n * Also bumps updated_at. Workstream is required (v5: agents.name is\n * per-workstream unique).\n */\nexport function updateAgentStatus(\n db: Db,\n name: string,\n status: AgentStatus,\n workstream: string,\n): boolean {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return false;\n const result = db\n .prepare(\"UPDATE agents SET status = ?, updated_at = ? WHERE name = ? AND workstream_id = ?\")\n .run(status, new Date().toISOString(), name, wsId);\n return result.changes > 0;\n}\n\n/**\n * Decide whether a scrollback-detected status should overwrite the\n * persisted one.\n *\n * `free` is sticky until the agent shows real activity:\n * - free + needs_input → stay free (user explicitly marked it free;\n * idle prompt isn't activity)\n * - free + busy → flip to busy\n * - free + needs_permission → flip (a permission prompt IS activity)\n *\n * Every other persisted status is auto-derived; overwrite freely. This\n * lets `spawning → busy/needs_input/needs_permission` happen on the\n * first reconcile after spawn.\n *\n * Lives on the agent (not on reconcile) because it's a property of the\n * agent's status field — both the periodic-reconcile loop and the\n * inline single-agent reconcile in `mu agent show` share this policy.\n */\nexport function shouldOverwriteAgentStatus(current: AgentStatus, detected: AgentStatus): boolean {\n if (current === \"free\") {\n return detected === \"busy\" || detected === \"needs_permission\";\n }\n return true;\n}\n\n// ─── Pane title composition (mu's interpreted state on the border) ───\n//\n// The pane border (set by enableMuPaneBorders) renders\n// `[mu] #{pane_title}` as tmux chrome. mu owns the pane title and uses\n// it to carry interpreted state at a glance. The status glyph in each\n// example below is whatever STATUS_EMOJI resolves to today (see the\n// table 30 lines down) — do NOT duplicate the codepoints in this\n// comment, they have drifted from production once already.\n//\n// worker-a (no claim, status not\n// worth surfacing yet)\n// worker-a · <STATUS_EMOJI.busy> (busy, no claim)\n// worker-a · <STATUS_EMOJI.busy> · build_x (busy, owns one task)\n// worker-a · <STATUS_EMOJI.needs_input> · build_x\n// worker-a · <STATUS_EMOJI.needs_permission> · build_x\n// worker-a · <STATUS_EMOJI.free> (free, no claim)\n// worker-a · <STATUS_EMOJI.busy> · ⊕2 tasks (multi-claim case)\n//\n// The agent name MUST remain the first ' · '-separated token so the\n// claim protocol's pane-title-as-identity fallback (currentPaneTitle\n// in src/tmux.ts) keeps working. Adopted panes that haven't been\n// re-titled by mu just have the name (one token) — still parses.\n\n/** Plain-text emoji map for the agent status. Mirrors statusIcon in\n * cli.ts but without picocolors (tmux pane titles don't render ANSI\n * colour). 'spawning' is omitted on purpose — the title gets the\n * initial render before status detection runs, and 'spawning' is a\n * transient state. */\n// Single-codepoint, single-cell-width Nerd Font glyphs (nf-fa family).\n// Picked over Unicode emoji so cli-table3's column widths line up:\n// Unicode emoji like a gear-with-variation-selector are TWO\n// codepoints, which cli-table3 counts as length-2 and uses to size\n// columns; but terminals render them as ONE cell wide, so adjacent\n// rows that mix 1-codepoint and 2-codepoint emoji misalign. Nerd Font\n// glyphs are private-use codepoints, all length-1 and all 1-cell-wide.\n//\n// Requires a Nerd Font on the operator's terminal (mu's substrate is\n// pi, which assumes Nerd Fonts; the rest of mu's TUI uses Nerd Font\n// glyphs already in cli-table3 box-drawing). Without one, every\n// glyph below renders as a placeholder box — the columns still align\n// (which was the bug we were fixing).\nexport const STATUS_EMOJI: Record<AgentStatus, string> = {\n spawning: \"\\uf251\", // nf-fa-hourglass_start\n busy: \"\\uf013\", // nf-fa-cog\n needs_input: \"\\uf186\", // nf-fa-moon_o\n needs_permission: \"\\uf023\", // nf-fa-lock\n free: \"\\uf058\", // nf-fa-check_circle\n unreachable: \"\\uf059\", // nf-fa-question_circle\n terminated: \"\\uf057\", // nf-fa-times_circle\n};\n\n/** Maximum total length for a composed pane title. tmux truncates\n * silently in some chrome positions; we truncate the task id\n * ourselves so the suffix is predictable. */\nconst MAX_TITLE_LEN = 64;\n\n/**\n * Placeholder pane-id prefix used during the `--workspace` pre-stage in\n * spawnAgent (src/agents/spawn.ts).\n *\n * The placeholder unblocks the FK-ordering cycle:\n * - vcs_workspaces.agent FK requires an agents row\n * - agents.pane_id is NOT NULL\n * - pane creation needs the workspace path as cwd\n * So we insert the agent with a placeholder pane_id, then create the\n * workspace, then the real pane, then patch pane_id.\n *\n * Because no real tmux pane has this prefix, ANY mutating reconcile\n * pass would treat the placeholder row as a ghost and prune it\n * (→ FK-failure on the workspace insert mid-spawn). Callers MUST:\n * - either guard against it explicitly (refreshAgentTitle), OR\n * - run reconcile in mode: \"status-only\" or \"report-only\"\n * (status pollers and read-only diagnostic verbs; see\n * `listLiveAgents.mode` rationale below). status-only also\n * skips status detection on placeholder rows directly\n * (reconcile() uses isPendingPaneId for that).\n *\n * Bug surfaced as bug_agent_spawn_workspace_fk_failure.\n */\nexport const PENDING_PANE_PREFIX = \"%pending-\";\n\n/** Build the placeholder pane id for an agent during workspace pre-stage. */\nexport function pendingPaneIdFor(agentName: string): string {\n return `${PENDING_PANE_PREFIX}${agentName}`;\n}\n\n/** True iff `paneId` is a `--workspace` pre-stage placeholder (not yet patched\n * to the real tmux pane id). */\nexport function isPendingPaneId(paneId: string): boolean {\n return paneId.startsWith(PENDING_PANE_PREFIX);\n}\n\n/** Build the pane title for `agent` based on current DB state.\n * Pure (no tmux side effect; no DB write). Read-only on the DB. */\nexport function composeAgentTitle(db: Db, agent: AgentRow): string {\n // 'spawning' is the initial state at row insert. Don't decorate —\n // surfaces as just the agent name until detection runs.\n const showStatus = agent.status !== \"spawning\";\n // Scope by the agent's workstream so a same-named worker in another\n // workstream can't pollute this title's task list.\n const tasks = listTasksByOwner(db, agent.workstreamName, agent.name);\n let title = agent.name;\n if (showStatus) {\n title += ` · ${STATUS_EMOJI[agent.status]}`;\n }\n if (tasks.length === 1) {\n title += ` · ${tasks[0]?.name}`;\n } else if (tasks.length > 1) {\n title += ` · ⊕${tasks.length} tasks`;\n }\n if (title.length > MAX_TITLE_LEN) {\n // Truncate from the END (preserves agent name + status prefix).\n title = `${title.slice(0, MAX_TITLE_LEN - 1)}…`;\n }\n return title;\n}\n\n/** Push a fresh pane title for `agentName`. Best-effort — a missing\n * agent, a placeholder pane id, or a tmux failure are all swallowed\n * silently (titles are decorative; never block the calling verb). */\nexport async function refreshAgentTitle(\n db: Db,\n agentName: string,\n workstream: string,\n): Promise<void> {\n const agent = getAgent(db, agentName, workstream);\n if (!agent) return;\n if (isPendingPaneId(agent.paneId)) return; // workspace pre-stage placeholder; see PENDING_PANE_PREFIX\n const title = composeAgentTitle(db, agent);\n await setPaneTitle(agent.paneId, title).catch(() => {});\n}\n\n/**\n * Delete an agent row. Returns true if a row was matched. Idempotent;\n * deleting an agent that doesn't exist returns false without throwing.\n *\n * Reaper side-effect: any task that was IN_PROGRESS owned by this\n * agent gets flipped back to OPEN with a `[reaper]` task_note and a\n * `task reap` event in `agent_logs`. The FK on `tasks.owner` is\n * `ON DELETE SET NULL` so the owner column resets automatically; the\n * extra step here is the status revert. Without this an agent that\n * crashed (or was explicitly closed mid-task) leaves the task graph\n * in a wrong state — IN_PROGRESS forever, with no owner to release.\n */\nexport function deleteAgent(db: Db, name: string, workstream: string): boolean {\n // Wrap the whole reaper sequence (snapshot stuck tasks → DELETE\n // agent → per-task UPDATE + addNote + emitEvent) in a single\n // synchronous better-sqlite3 transaction. Without this, a throw\n // mid-loop (FK race after workstream destroy, addNote/emitEvent\n // regression, OOM, …) would leave the agent row deleted (FK\n // CASCADE already SET NULL on tasks.owner_id) but only PART of\n // the reaper trail written: leftover IN_PROGRESS tasks with no\n // owner and no `[reaper]` note explaining how they got there.\n // Reconcile / `mu task wait --stuck-after` would then surface\n // them as ownerless zombies with no breadcrumb.\n return db.transaction(() => {\n // Snapshot the stuck tasks BEFORE the DELETE; the FK CASCADE\n // (SET NULL on owner_id) makes the post-delete query indistinguishable\n // from \"never owned by this agent.\"\n const agentId = agentIdByName(db, name, workstream);\n if (agentId === null) {\n // Already gone — idempotent return. (Could happen if reconcile\n // pruned a ghost concurrently.) The DELETE is a no-op.\n return false;\n }\n const stuck = db\n .prepare(\n `SELECT t.id AS taskId, t.local_id AS localId, ws.name AS workstream\n FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE t.owner_id = ? AND t.status = 'IN_PROGRESS'`,\n )\n .all(agentId) as Array<{ taskId: number; localId: string; workstream: string }>;\n\n const result = db.prepare(\"DELETE FROM agents WHERE id = ?\").run(agentId);\n if (result.changes === 0) return false;\n\n for (const t of stuck) {\n db.prepare(\"UPDATE tasks SET status = 'OPEN', updated_at = ? WHERE id = ?\").run(\n new Date().toISOString(),\n t.taskId,\n );\n addNote(\n db,\n t.localId,\n `[reaper] previous owner ${name} gone (agent removed); status reverted IN_PROGRESS → OPEN, owner cleared`,\n { author: \"reaper\", workstream: t.workstream },\n );\n emitEvent(\n db,\n t.workstream,\n `task reap ${t.localId} (previous owner ${name} gone, IN_PROGRESS → OPEN)`,\n );\n }\n return true;\n })();\n}\n\n// ────────────────────────────────────────────────────────────────────────\n// High-level verbs (spawn, send, read, list, close)\n// ────────────────────────────────────────────────────────────────────────\n\n/** Allowed agent name shape: lowercase alpha first, then alnum/underscore/\n * hyphen. Mirrors VOCABULARY.md §\"Naming conventions\". */\nconst AGENT_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;\n\nexport function isValidAgentName(name: string): boolean {\n return AGENT_NAME_RE.test(name);\n}\n\n/**\n * Send a single line of text to an agent's pane and submit it. Uses the\n * canonical bracketed-paste protocol from src/tmux.ts.\n */\nexport async function sendToAgent(\n db: Db,\n name: string,\n text: string,\n opts: SendOptions & { workstream: string },\n): Promise<void> {\n const agent = getAgent(db, name, opts.workstream);\n if (!agent) throw new AgentNotFoundError(name);\n await sendToPane(agent.paneId, text, opts);\n}\n\n/**\n * Read scrollback from an agent's pane. With no options, returns the full\n * scrollback (`-S - -E -`); with `lines: N`, returns only the last N lines.\n */\nexport async function readAgent(\n db: Db,\n name: string,\n opts: CaptureOptions & { workstream: string },\n): Promise<string> {\n const agent = getAgent(db, name, opts.workstream);\n if (!agent) throw new AgentNotFoundError(name);\n return capturePane(agent.paneId, opts);\n}\n\n// ─── freeAgent (verb) ─────────────────────────────────────────────────────\n\nexport interface FreeAgentResult {\n /** Status before the call. */\n previousStatus: AgentStatus;\n /** Status after the call (always 'free' on success). */\n status: AgentStatus;\n /** True iff the row actually changed. False on idempotent no-op. */\n changed: boolean;\n}\n\n/**\n * Mark an agent's status as `free` — the explicit \"I'm done with you\n * for now; you're available\" signal. The agent's pane and DB row are\n * untouched; reconcile treats `free` as sticky (only flips back to busy\n * on real activity, never on an idle prompt) so this verb composes\n * cleanly with the existing scrollback detector.\n *\n * Idempotent: setting an already-free agent to free is a no-op (returns\n * `changed: false`). Throws AgentNotFoundError on missing.\n */\nexport function freeAgent(db: Db, name: string, workstream: string): FreeAgentResult {\n const before = getAgent(db, name, workstream);\n if (!before) throw new AgentNotFoundError(name);\n if (before.status === \"free\") {\n return { previousStatus: before.status, status: \"free\", changed: false };\n }\n updateAgentStatus(db, name, \"free\", before.workstreamName);\n emitEvent(db, before.workstreamName, `agent free ${name} (was ${before.status})`);\n return { previousStatus: before.status, status: \"free\", changed: true };\n}\n\nexport interface CloseAgentOptions {\n /**\n * Lossy override: when true, free the agent's workspace BEFORE\n * deleting the agent regardless of whether it's clean. (We control\n * the order rather than relying on FK cascade, which leaves the\n * on-disk dir orphaned.) Any pending changes / commits since fork\n * are gone unless the caller frees with `--commit` separately first.\n *\n * When false (default), behaviour depends on workspace state:\n * - clean (no uncommitted changes AND no commits since fork):\n * silently auto-free. allow_mu_agent_close_without_discard.\n * - dirty (uncommitted changes OR commits since fork): throw\n * WorkspacePreservedError so the caller decides explicitly.\n * Surfaced as a real bug in the multi-agent dogfood teardown.\n */\n discardWorkspace?: boolean;\n}\n\nexport interface CloseAgentResult {\n killedPane: boolean;\n deletedRow: boolean;\n /** True iff the agent had an associated workspace AND we proactively\n * freed it — either because the caller passed `discardWorkspace:\n * true` (lossy) or because the workspace was clean and we\n * auto-freed (allow_mu_agent_close_without_discard). False on the\n * no-workspace path (nothing to free) and on the refused path (we\n * threw before doing anything). */\n workspaceFreed: boolean;\n /** True iff `workspaceFreed` was triggered by the clean-workspace\n * auto-free path (no uncommitted changes AND no commits since\n * fork) rather than the explicit `discardWorkspace: true` override.\n * Lets the CLI render an accurate message (\"auto-freed (clean)\"\n * vs \"workspace discarded\") and gives JSON consumers a stable\n * signal. False on every other path. */\n workspaceAutoFreedClean: boolean;\n}\n\n/**\n * Close an agent: kill its tmux pane and remove its DB row. Idempotent:\n * - if the agent doesn't exist in the DB, returns a no-op result\n * - if the tmux pane is already gone, killPane swallows the error\n *\n * Workspace handling: closing an agent and freeing its workspace are\n * separate concerns (agent lifecycle vs disk artifacts). Three cases:\n *\n * - No workspace: close proceeds normally.\n * - Workspace exists AND is CLEAN (no uncommitted changes, no\n * commits since fork): silently auto-free (so a workspace that\n * contains nothing worth preserving doesn't make the operator\n * type --discard-workspace just to clean it up). Surfaced by\n * allow_mu_agent_close_without_discard — a misconfigured-spawn\n * teardown was needlessly forced through the lossy flag.\n * - Workspace exists AND has either uncommitted changes OR commits\n * since fork: REFUSE with WorkspacePreservedError so the operator\n * decides explicitly. Two resolutions:\n * 1. `freeWorkspace(db, name)` first, then `closeAgent(db, name)`.\n * Preserves the option to `--commit` pending changes.\n * 2. `closeAgent(db, name, { discardWorkspace: true })`.\n * One-shot; lossy.\n *\n * The CLI surfaces these as the two actionable nextSteps on the\n * `WorkspacePreservedError` thrown by the refuse path.\n */\nexport async function closeAgent(\n db: Db,\n name: string,\n opts: CloseAgentOptions & { workstream: string },\n): Promise<CloseAgentResult> {\n const agent = getAgent(db, name, opts.workstream);\n if (!agent) {\n return {\n killedPane: false,\n deletedRow: false,\n workspaceFreed: false,\n workspaceAutoFreedClean: false,\n };\n }\n const ws = getWorkspaceForAgent(db, name, agent.workstreamName);\n // allow_mu_agent_close_without_discard: silently auto-free a clean\n // workspace (no uncommitted changes AND no commits since fork) so\n // the user doesn't have to type --discard-workspace for a workspace\n // that contains nothing worth preserving. Only refuse when there's\n // actually something to lose. The flag stays as the lossy override\n // for non-clean workspaces.\n let autoFreeClean = false;\n if (ws !== undefined && opts.discardWorkspace !== true) {\n autoFreeClean = await isWorkspaceClean(ws);\n if (!autoFreeClean) {\n throw new WorkspacePreservedError(name, ws.path);\n }\n }\n // Pre-mutation snapshot (snap_design §CAPTURE STRATEGY > WHEN).\n // Captures the agent row + the FK SET NULL ripple onto tasks.owner +\n // (when --discard-workspace or auto-free) the vcs_workspaces row.\n // Workstream is recorded so this snapshot is filterable in `mu\n // snapshot list`.\n captureSnapshot(db, `agent close ${name}`, agent.workstreamName);\n // Free the workspace BEFORE the agent (so the on-disk dir is\n // removed cleanly, not orphaned by FK cascade). freeWorkspace is\n // idempotent on missing rows.\n let workspaceFreed = false;\n if (ws !== undefined && (opts.discardWorkspace === true || autoFreeClean)) {\n await freeWorkspace(db, name, { commit: false, workstream: agent.workstreamName });\n workspaceFreed = true;\n }\n await killPane(agent.paneId).catch(() => {\n /* idempotent — pane may already be gone */\n });\n const deletedRow = deleteAgent(db, name, agent.workstreamName);\n emitEvent(\n db,\n agent.workstreamName,\n `agent close ${name} (pane=${agent.paneId}${\n workspaceFreed\n ? autoFreeClean\n ? \", workspace auto-freed (clean)\"\n : \", workspace discarded\"\n : \"\"\n })`,\n );\n return {\n killedPane: true,\n deletedRow,\n workspaceFreed,\n workspaceAutoFreedClean: workspaceFreed && autoFreeClean,\n };\n}\n\nexport interface ListLiveAgentsOptions {\n workstream: string;\n tmuxSession?: string;\n /**\n * Which kind of reconciliation pass to run. Forwarded to\n * `reconcile()`'s same-name option. Default `\"full\"` (the\n * documented mutating behaviour `mu agent list` has always had).\n *\n * Read-only callers split two ways:\n * - `mu state`, `mu agent attach` →\n * `\"status-only\"`: refresh status + title (writes to DB),\n * skip prune + reap. The operator's primary signal\n * (busy/needs_input) stays fresh without a periodic poll\n * racing in-flight spawns.\n * - `mu doctor`, `mu undo` → `\"report-only\"`: count drift,\n * mutate nothing. `mu undo` MUST use this so a post-restore\n * reconcile doesn't delete the rows the snapshot just\n * restored (snap_undo_reconcile_destroys_recovered_agents).\n *\n * Skipping prune is what protects mid-spawn placeholders (pane\n * id `%pending-<name>`) from being treated as ghosts and pruned\n * out from under `createWorkspace`'s FK insert\n * (bug_agent_spawn_workspace_fk_failure).\n *\n * BREAKING: this replaces the previous `dryRun?: boolean`\n * option. Migration: `dryRun: true` → `mode: \"report-only\"`;\n * default (`dryRun: false` / unset) → `mode: \"full\"`.\n */\n mode?: ReconcileMode;\n}\n\nexport interface LiveAgentsView {\n /** All registered agents in the workstream, post-reconcile. */\n agents: AgentRow[];\n /** Panes in the tmux session that look like agents but aren't registered. */\n orphans: TmuxPane[];\n /** Diagnostic numbers from the reconcile pass; useful for `mu doctor`. */\n report: ReconcileReport;\n}\n\n/**\n * Return the live, reality-reconciled view of agents in a workstream.\n * `mu agent list` calls this with `mode: \"full\"` (mutating); status\n * pollers (`mu state`, `mu agent attach`) call it with\n * `mode: \"status-only\"` to refresh status without pruning; read-only\n * diagnostic / restore paths (`mu doctor`, `mu undo`) call it with\n * `mode: \"report-only\"` to mutate nothing at all.\n */\nexport async function listLiveAgents(db: Db, opts: ListLiveAgentsOptions): Promise<LiveAgentsView> {\n const report = await reconcile(db, {\n workstream: opts.workstream,\n ...(opts.tmuxSession !== undefined ? { tmuxSession: opts.tmuxSession } : {}),\n ...(opts.mode !== undefined ? { mode: opts.mode } : {}),\n });\n const baseAgents = listAgents(db, { workstream: opts.workstream });\n // Enrich with the derived `idle` flag (idle_assigned_agent_detection).\n // One COUNT per agent — cheap; the agents table in any one workstream\n // is small (typical wave: <10 rows). We add the field only when\n // idle=true, so non-idle rows JSON-serialize without the noise.\n const now = Date.now();\n const agents: AgentRow[] = baseAgents.map((a) =>\n computeAgentIdle(db, a, now) ? { ...a, idle: true } : a,\n );\n return { agents, orphans: report.orphans, report };\n}\n","// mu — task-DAG read + ASCII forest rendering helpers.\n//\n// Shared by the static `mu task tree` command and the read-only TUI DAG\n// popup. Pure rendering lives here so the box-drawing characters and\n// diamond-collapse semantics have one implementation.\n\nimport pc from \"picocolors\";\nimport type { Db } from \"./db.js\";\nimport { type TaskRow, getTask, listTasks } from \"./tasks.js\";\nimport type { TaskStatus } from \"./tasks/status.js\";\n\n// One-line marker appended to a tree node when its subtree was already\n// rendered earlier in the forest (DAG diamond collapse). Symbol-only\n// + dimmed: the ↻ glyph carries the recurrence semantics, the dim\n// keeps it from competing with the task title for the eye.\nconst RECURRENCE_MARKER = ` ${pc.dim(\"(↻)\")}`;\n\nexport interface FullDag {\n /** Root tasks: no incoming `blocks` edge (no blockers). */\n roots: TaskRow[];\n /** Edges map parent task name → child task names (what parent blocks). */\n edges: Map<string, string[]>;\n /** All tasks in the workstream, keyed by operator-facing name. */\n tasks: Map<string, TaskRow>;\n}\n\nexport type TaskStatusLabelFn = (task: TaskRow) => string;\n\nexport interface RenderTreeOptions {\n /** Include the task title after the name + status label. Default: true. */\n includeTitle?: boolean;\n}\n\nexport interface LoadFullDagOptions {\n /** Optional visible-status filter. Omitted = every task status. */\n statuses?: ReadonlySet<TaskStatus>;\n}\n\nexport function loadFullDag(db: Db, workstream: string, opts: LoadFullDagOptions = {}): FullDag {\n const tasks = listTasks(db, workstream).filter(\n (t) => opts.statuses === undefined || opts.statuses.has(t.status),\n );\n const byName = new Map(tasks.map((t) => [t.name, t]));\n const incoming = new Set<string>();\n const edges = new Map<string, string[]>();\n\n for (const task of tasks) {\n edges.set(task.name, []);\n }\n\n const rows = db\n .prepare(\n `SELECT src.local_id AS parent, dst.local_id AS child\n FROM task_edges e\n JOIN tasks src ON src.id = e.from_task_id\n JOIN tasks dst ON dst.id = e.to_task_id\n JOIN workstreams ws ON ws.id = src.workstream_id\n WHERE ws.name = ?\n AND dst.workstream_id = src.workstream_id\n ORDER BY src.local_id, dst.local_id`,\n )\n .all(workstream) as { parent: string; child: string }[];\n\n for (const row of rows) {\n if (!byName.has(row.parent) || !byName.has(row.child)) continue;\n incoming.add(row.child);\n const children = edges.get(row.parent) ?? [];\n children.push(row.child);\n edges.set(row.parent, children);\n }\n\n const roots = tasks.filter((t) => !incoming.has(t.name));\n return { roots, edges, tasks: byName };\n}\n\n/**\n * Render a DAG forest in the same ASCII shape as `mu task tree --down`:\n * each root is printed as a header node, dependents are below it, and\n * DAG diamonds collapse after the first full subtree render with a\n * one-line recurrence marker.\n */\nexport function renderForest(\n roots: readonly TaskRow[],\n edges: ReadonlyMap<string, readonly string[]>,\n statusFn: TaskStatusLabelFn,\n tasksByName?: ReadonlyMap<string, TaskRow>,\n opts: RenderTreeOptions = {},\n): string {\n const byName = new Map(tasksByName ?? roots.map((t) => [t.name, t]));\n const seen = new Set<string>();\n const sections: string[] = [];\n\n for (const root of roots) {\n if (!byName.has(root.name)) byName.set(root.name, root);\n const lines = [formatTreeNodeLabel(root, statusFn, opts)];\n if (seen.has(root.name)) {\n lines[0] = `${lines[0]}${RECURRENCE_MARKER}`;\n } else {\n seen.add(root.name);\n renderForestChildren(root.name, \"\", edges, byName, statusFn, seen, lines, opts);\n }\n sections.push(lines.join(\"\\n\"));\n }\n\n return sections.join(\"\\n\\n\");\n}\n\nexport function renderTaskTree(\n db: Db,\n workstream: string,\n root: TaskRow,\n direction: \"blockers\" | \"dependents\",\n statusFn: TaskStatusLabelFn,\n opts: RenderTreeOptions = {},\n): string {\n const edges = new Map<string, string[]>();\n const byName = new Map<string, TaskRow>([[root.name, root]]);\n const visited = new Set<string>();\n collectTreeEdges(db, workstream, root.name, direction, edges, byName, visited);\n return renderForest([root], edges, statusFn, byName, opts);\n}\n\nfunction collectTreeEdges(\n db: Db,\n workstream: string,\n taskName: string,\n direction: \"blockers\" | \"dependents\",\n edges: Map<string, string[]>,\n byName: Map<string, TaskRow>,\n visited: Set<string>,\n): void {\n if (visited.has(taskName)) return;\n visited.add(taskName);\n const rows = db\n .prepare(\n direction === \"dependents\"\n ? `SELECT child.local_id AS name\n FROM task_edges e\n JOIN tasks parent ON parent.id = e.from_task_id\n JOIN tasks child ON child.id = e.to_task_id\n JOIN workstreams ws ON ws.id = parent.workstream_id\n WHERE ws.name = ? AND parent.local_id = ?\n ORDER BY child.local_id`\n : `SELECT parent.local_id AS name\n FROM task_edges e\n JOIN tasks parent ON parent.id = e.from_task_id\n JOIN tasks child ON child.id = e.to_task_id\n JOIN workstreams ws ON ws.id = child.workstream_id\n WHERE ws.name = ? AND child.local_id = ?\n ORDER BY parent.local_id`,\n )\n .all(workstream, taskName) as { name: string }[];\n const children = rows.map((r) => r.name);\n edges.set(taskName, children);\n\n for (const childName of children) {\n if (!byName.has(childName)) {\n const child = getTask(db, childName, workstream);\n if (child) byName.set(childName, child);\n }\n collectTreeEdges(db, workstream, childName, direction, edges, byName, visited);\n }\n}\n\nfunction renderForestChildren(\n taskName: string,\n prefix: string,\n edges: ReadonlyMap<string, readonly string[]>,\n byName: Map<string, TaskRow>,\n statusFn: TaskStatusLabelFn,\n seen: Set<string>,\n lines: string[],\n opts: RenderTreeOptions,\n): void {\n const children = edges.get(taskName) ?? [];\n for (let i = 0; i < children.length; i++) {\n const childName = children[i];\n if (childName === undefined) continue;\n const isLast = i === children.length - 1;\n const branch = isLast ? \"└── \" : \"├── \";\n const childPrefix = prefix + (isLast ? \" \" : \"│ \");\n const child = byName.get(childName);\n\n if (!child) {\n lines.push(`${prefix}${branch}${childName} (missing!)`);\n continue;\n }\n\n if (seen.has(childName)) {\n lines.push(\n `${prefix}${branch}${formatTreeNodeLabel(child, statusFn, opts)}${RECURRENCE_MARKER}`,\n );\n continue;\n }\n\n lines.push(`${prefix}${branch}${formatTreeNodeLabel(child, statusFn, opts)}`);\n seen.add(childName);\n renderForestChildren(childName, childPrefix, edges, byName, statusFn, seen, lines, opts);\n }\n}\n\nexport function formatTreeNodeLabel(\n t: TaskRow,\n statusFn: TaskStatusLabelFn,\n opts: RenderTreeOptions = {},\n): string {\n const base = `${t.name} ${statusFn(t)}`;\n if (opts.includeTitle === false) return base;\n return `${base} ${t.title}`;\n}\n","// mu — whole-DB export/import sync SDK.\n//\n// Export is a SQLite VACUUM INTO copy plus a tiny manifest. Import is\n// deliberately sharp: classify each workstream, refuse drift by default,\n// and only clobber with --force-source after parking the local loser.\n\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { CURRENT_SCHEMA_VERSION, type Db, defaultStateDir, openDb } from \"./db.js\";\nimport { latestSeq } from \"./logs.js\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\nimport { captureSnapshot } from \"./snapshots.js\";\n\nexport interface DbExportManifestWorkstream {\n name: string;\n tasks: number;\n edges: number;\n notes: number;\n latestSeq: number;\n}\n\nexport interface DbExportManifest {\n muVersion: string;\n schemaVersion: number;\n machineId: string;\n hostname: string | null;\n exportedAt: string;\n workstreams: DbExportManifestWorkstream[];\n}\n\nexport interface ExportDbOptions {\n force?: boolean;\n}\n\nexport interface ExportDbResult {\n file: string;\n manifestPath: string;\n manifest: DbExportManifest;\n overwritten: boolean;\n}\n\nexport class DbExportTargetExistsError extends Error implements HasNextSteps {\n override readonly name = \"DbExportTargetExistsError\";\n constructor(public readonly file: string) {\n super(`DB export target already exists: ${file}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Choose a different target\", command: \"mu db export <new-file>\" },\n { intent: \"Overwrite this target\", command: `mu db export ${shellQuote(this.file)} --force` },\n ];\n }\n}\n\nexport class DbImportManifestMissingError extends Error implements HasNextSteps {\n override readonly name = \"DbImportManifestMissingError\";\n constructor(public readonly manifestPath: string) {\n super(`DB import manifest not found: ${manifestPath}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Export the DB with its sidecar\", command: \"mu db export /tmp/mu.db --force\" },\n { intent: \"Copy the sidecar too\", command: `scp <host>:${shellQuote(this.manifestPath)} .` },\n ];\n }\n}\n\nexport class DbImportSchemaTooOldError extends Error implements HasNextSteps {\n override readonly name = \"DbImportSchemaTooOldError\";\n constructor(public readonly sourceVersion: number) {\n super(\n `source DB schema v${sourceVersion} is older than local mu requires (v${CURRENT_SCHEMA_VERSION})`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Upgrade mu on the source machine\",\n command: \"npm run build && mu db export <file> --force\",\n },\n { intent: \"Then retry this import\", command: \"mu db import <file> --apply\" },\n ];\n }\n}\n\nexport class DbImportSchemaTooNewError extends Error implements HasNextSteps {\n override readonly name = \"DbImportSchemaTooNewError\";\n constructor(public readonly sourceVersion: number) {\n super(\n `source DB schema v${sourceVersion} is newer than this mu supports (v${CURRENT_SCHEMA_VERSION}); upgrade local mu`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Upgrade local mu\", command: \"git pull && npm install && npm run build\" },\n { intent: \"Then retry this import\", command: \"mu db import <file> --apply\" },\n ];\n }\n}\n\nexport class DbImportSourceStaleError extends Error implements HasNextSteps {\n override readonly name = \"DbImportSourceStaleError\";\n constructor(public readonly workstreams: readonly string[]) {\n super(`source DB is stale for local-ahead workstream(s): ${workstreams.join(\", \")}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Re-export from this machine\", command: \"mu db export /tmp/mu-fresh.db --force\" },\n { intent: \"Dry-run the incoming file first\", command: \"mu db import <file>\" },\n ];\n }\n}\n\nexport class DbImportConflictError extends Error implements HasNextSteps {\n override readonly name = \"DbImportConflictError\";\n constructor(public readonly workstreams: readonly string[]) {\n super(`source and local both advanced for workstream(s): ${workstreams.join(\", \")}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Preview the conflicting workstreams\", command: \"mu db import <file> --json\" },\n {\n intent: \"Clobber from source after parking local divergence\",\n command: \"mu db import <file> --apply --force-source\",\n },\n ];\n }\n}\n\nexport type DbImportDecision =\n | \"IDENTICAL\"\n | \"FAST_FORWARD\"\n | \"LOCAL_AHEAD\"\n | \"CONFLICT\"\n | \"IMPORT\"\n | \"LEAVE_ALONE\";\n\nexport interface DbImportSummaryItem {\n workstream: string;\n decision: DbImportDecision;\n delta: Record<string, unknown>;\n needs?: string;\n parkPath?: string;\n}\n\nexport interface ImportDbOptions {\n apply?: boolean;\n forceSource?: boolean;\n onlyWorkstreams?: readonly string[];\n}\n\nexport interface ImportDbResult {\n machineId: string;\n sourceFile: string;\n dryRun: boolean;\n applied: boolean;\n snapshotId?: number;\n summary: DbImportSummaryItem[];\n}\n\ninterface MachineIdentityRow {\n machine_id: string;\n hostname: string | null;\n created_at?: string;\n}\n\ninterface WorkstreamIdRow {\n id: number;\n name: string;\n}\n\ninterface WorkstreamRow {\n id: number;\n name: string;\n created_at: string;\n}\n\ninterface TaskCopyRow {\n local_id: string;\n title: string;\n status: string;\n impact: number;\n effort_days: number;\n owner_name: string | null;\n created_at: string;\n updated_at: string;\n}\n\ninterface EdgeCopyRow {\n from_local_id: string;\n to_local_id: string;\n created_at: string;\n}\n\ninterface NoteCopyRow {\n task_local_id: string;\n author: string | null;\n content: string;\n created_at: string;\n}\n\ninterface LogCopyRow {\n seq: number;\n source: string;\n kind: string;\n payload: string;\n created_at: string;\n}\n\ninterface AgentCopyRow {\n name: string;\n cli: string;\n pane_id: string;\n status: string;\n role: string;\n tab: string | null;\n created_at: string;\n updated_at: string;\n}\n\ninterface WorkspaceCopyRow {\n agent_name: string;\n backend: string;\n path: string;\n parent_ref: string | null;\n created_at: string;\n}\n\ninterface CopyWorkstreamOptions {\n includeMachineLocalRows: boolean;\n preserveLogSeq: boolean;\n includeSync: boolean;\n}\n\nexport function exportDb(db: Db, file: string, opts: ExportDbOptions = {}): ExportDbResult {\n const target = file;\n const manifestPath = `${target}.manifest.json`;\n const targetExists = existsSync(target);\n if (targetExists && opts.force !== true) throw new DbExportTargetExistsError(target);\n\n const manifest = buildExportManifest(db);\n mkdirSync(dirname(target), { recursive: true });\n try {\n if (targetExists) unlinkSync(target);\n db.exec(`VACUUM INTO ${quoteSqlString(target)}`);\n writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`, \"utf8\");\n } catch (err) {\n try {\n if (existsSync(target)) unlinkSync(target);\n } catch {\n // Ignore — preserve the original export failure.\n }\n throw err;\n }\n\n return { file: target, manifestPath, manifest, overwritten: targetExists };\n}\n\nexport function importDb(db: Db, file: string, opts: ImportDbOptions = {}): ImportDbResult {\n const manifest = readImportManifest(file);\n assertImportSchemaCompatible(manifest.schemaVersion);\n\n const sourceDb = openDb({ path: file, readonly: true });\n try {\n const summary = buildImportPlan(db, manifest, file, opts.onlyWorkstreams);\n if (opts.apply !== true) {\n return {\n machineId: manifest.machineId,\n sourceFile: file,\n dryRun: true,\n applied: false,\n summary,\n };\n }\n\n const stale = summary.filter((s) => s.decision === \"LOCAL_AHEAD\").map((s) => s.workstream);\n if (stale.length > 0) throw new DbImportSourceStaleError(stale);\n const conflicts = summary.filter((s) => s.decision === \"CONFLICT\").map((s) => s.workstream);\n if (conflicts.length > 0 && opts.forceSource !== true)\n throw new DbImportConflictError(conflicts);\n\n const mutating = summary.some((s) => shouldReplace(s.decision, opts.forceSource === true));\n const snapshot = mutating ? captureSnapshot(db, `db import ${file}`, null) : undefined;\n\n for (const item of summary) {\n if (!shouldReplace(item.decision, opts.forceSource === true)) continue;\n if (item.decision === \"CONFLICT\") {\n item.parkPath = parkLocalWorkstream(db, item.workstream);\n }\n const sourceWs = manifest.workstreams.find((w) => w.name === item.workstream);\n const sourceSeq = sourceWs?.latestSeq ?? 0;\n replaceWorkstreamFromSource(db, sourceDb, item.workstream, manifest.machineId, sourceSeq);\n }\n\n return {\n machineId: manifest.machineId,\n sourceFile: file,\n dryRun: false,\n applied: true,\n ...(snapshot ? { snapshotId: snapshot.id } : {}),\n summary,\n };\n } finally {\n sourceDb.close();\n }\n}\n\nexport function buildImportPlan(\n localDb: Db,\n manifest: DbExportManifest,\n sourceFile: string,\n onlyWorkstreams?: readonly string[],\n): DbImportSummaryItem[] {\n const sourceByName = new Map(manifest.workstreams.map((w) => [w.name, w]));\n const localByName = new Map(listLocalWorkstreams(localDb).map((w) => [w.name, w]));\n const localMachineId = getMachineIdentity(localDb)?.machine_id ?? \"\";\n const only = normaliseOnlyWorkstreams(onlyWorkstreams);\n const names = Array.from(new Set([...sourceByName.keys(), ...localByName.keys()]))\n .filter((name) => only.size === 0 || only.has(name))\n .sort();\n\n return names.map((name) => {\n const source = sourceByName.get(name);\n const local = localByName.get(name);\n const sourceSeq = source?.latestSeq ?? 0;\n const localSeq = local ? latestSeq(localDb, local.id) : 0;\n const synced =\n source !== undefined && local !== undefined && manifest.machineId === localMachineId\n ? { sourceSeq: Math.min(sourceSeq, localSeq), localSeq: Math.min(sourceSeq, localSeq) }\n : local\n ? lastKnownPeerSync(localDb, local.id, manifest.machineId)\n : { sourceSeq: 0, localSeq: 0 };\n\n const decision = classifyWorkstream({\n hasSource: source !== undefined,\n hasLocal: local !== undefined,\n sourceSeq,\n localSeq,\n syncedSourceSeq: synced.sourceSeq,\n syncedLocalSeq: synced.localSeq,\n });\n return {\n workstream: name,\n decision,\n delta: {\n sourceFile,\n sourceSeq,\n localSeq,\n lastSynced: synced.sourceSeq,\n localSynced: synced.localSeq,\n source: source ? countsFromManifest(source) : null,\n local: local ? countWorkstream(localDb, local.id) : null,\n },\n ...(decision === \"LOCAL_AHEAD\" ? { needs: \"re-export from this machine\" } : {}),\n ...(decision === \"CONFLICT\" ? { needs: \"--force-source\" } : {}),\n };\n });\n}\n\nfunction classifyWorkstream(opts: {\n hasSource: boolean;\n hasLocal: boolean;\n sourceSeq: number;\n localSeq: number;\n syncedSourceSeq: number;\n syncedLocalSeq: number;\n}): DbImportDecision {\n if (opts.hasSource && !opts.hasLocal) return \"IMPORT\";\n if (!opts.hasSource && opts.hasLocal)\n return opts.syncedSourceSeq > 0 || opts.syncedLocalSeq > 0 ? \"LOCAL_AHEAD\" : \"LEAVE_ALONE\";\n if (!opts.hasSource && !opts.hasLocal) return \"IDENTICAL\";\n\n const sourceAdvanced = opts.sourceSeq > opts.syncedSourceSeq;\n const localAdvanced = opts.localSeq > opts.syncedLocalSeq;\n if (!sourceAdvanced && !localAdvanced) return \"IDENTICAL\";\n if (sourceAdvanced && !localAdvanced) return \"FAST_FORWARD\";\n if (!sourceAdvanced && localAdvanced) return \"LOCAL_AHEAD\";\n return \"CONFLICT\";\n}\n\nfunction shouldReplace(decision: DbImportDecision, forceSource: boolean): boolean {\n return (\n decision === \"FAST_FORWARD\" || decision === \"IMPORT\" || (decision === \"CONFLICT\" && forceSource)\n );\n}\n\nfunction replaceWorkstreamFromSource(\n localDb: Db,\n sourceDb: Db,\n workstream: string,\n sourceMachineId: string,\n sourceSeq: number,\n): void {\n localDb.transaction(() => {\n const existing = localDb.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as\n | { id: number }\n | undefined;\n if (existing) {\n localDb.prepare(\"DELETE FROM vcs_workspaces WHERE workstream_id = ?\").run(existing.id);\n localDb.prepare(\"DELETE FROM agents WHERE workstream_id = ?\").run(existing.id);\n localDb.prepare(\"DELETE FROM workstreams WHERE id = ?\").run(existing.id);\n }\n copyWorkstreamRows(sourceDb, localDb, workstream, {\n includeMachineLocalRows: false,\n preserveLogSeq: false,\n includeSync: false,\n });\n const wsId = (\n localDb.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as\n | { id: number }\n | undefined\n )?.id;\n if (wsId === undefined) throw new Error(`importDb: failed to import workstream ${workstream}`);\n writeSyncState(localDb, wsId, sourceMachineId, sourceSeq);\n })();\n}\n\nfunction parkLocalWorkstream(db: Db, workstream: string): string {\n const dir = join(defaultStateDir(), \"divergence\");\n mkdirSync(dir, { recursive: true });\n const path = join(\n dir,\n `${workstream}-${new Date().toISOString()}-${randomUUID().slice(0, 8)}.db`,\n );\n const parkDb = openDb({ path });\n try {\n const identity = getMachineIdentity(db);\n if (identity) {\n parkDb\n .prepare(\n `UPDATE machine_identity\n SET machine_id = ?, hostname = ?, created_at = ?\n WHERE id = 1`,\n )\n .run(\n identity.machine_id,\n identity.hostname,\n identity.created_at ?? new Date().toISOString(),\n );\n }\n copyWorkstreamRows(db, parkDb, workstream, {\n includeMachineLocalRows: true,\n preserveLogSeq: true,\n includeSync: true,\n });\n } catch (err) {\n try {\n parkDb.close();\n } catch {\n // keep original error\n }\n try {\n if (existsSync(path)) unlinkSync(path);\n } catch {\n // keep original error\n }\n throw err;\n }\n parkDb.close();\n return path;\n}\n\nfunction copyWorkstreamRows(\n sourceDb: Db,\n targetDb: Db,\n workstream: string,\n opts: CopyWorkstreamOptions,\n): void {\n const sourceWs = sourceDb\n .prepare(\"SELECT id, name, created_at FROM workstreams WHERE name = ?\")\n .get(workstream) as WorkstreamRow | undefined;\n if (!sourceWs) throw new Error(`copyWorkstreamRows: no such workstream ${workstream}`);\n\n targetDb\n .prepare(\"INSERT INTO workstreams (name, created_at) VALUES (?, ?)\")\n .run(sourceWs.name, sourceWs.created_at);\n const targetWsId = (\n targetDb.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as { id: number }\n ).id;\n\n if (opts.includeMachineLocalRows) copyAgents(sourceDb, targetDb, sourceWs.id, targetWsId);\n copyTasks(sourceDb, targetDb, sourceWs.id, targetWsId, opts.includeMachineLocalRows);\n copyEdges(sourceDb, targetDb, sourceWs.id, targetWsId);\n copyNotes(sourceDb, targetDb, sourceWs.id, targetWsId);\n copyLogs(sourceDb, targetDb, sourceWs.id, targetWsId, opts.preserveLogSeq);\n if (opts.includeMachineLocalRows) copyWorkspaces(sourceDb, targetDb, sourceWs.id, targetWsId);\n if (opts.includeSync) copySync(sourceDb, targetDb, sourceWs.id, targetWsId);\n}\n\nfunction copyAgents(sourceDb: Db, targetDb: Db, sourceWsId: number, targetWsId: number): void {\n const rows = sourceDb\n .prepare(\n `SELECT name, cli, pane_id, status, role, tab, created_at, updated_at\n FROM agents\n WHERE workstream_id = ?\n ORDER BY id`,\n )\n .all(sourceWsId) as AgentCopyRow[];\n const insert = targetDb.prepare(\n `INSERT INTO agents (workstream_id, name, cli, pane_id, status, role, tab, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n for (const row of rows) {\n insert.run(\n targetWsId,\n row.name,\n row.cli,\n row.pane_id,\n row.status,\n row.role,\n row.tab,\n row.created_at,\n row.updated_at,\n );\n }\n}\n\nfunction copyTasks(\n sourceDb: Db,\n targetDb: Db,\n sourceWsId: number,\n targetWsId: number,\n includeOwners: boolean,\n): void {\n const rows = sourceDb\n .prepare(\n `SELECT t.local_id, t.title, t.status, t.impact, t.effort_days, a.name AS owner_name,\n t.created_at, t.updated_at\n FROM tasks t\n LEFT JOIN agents a ON a.id = t.owner_id\n WHERE t.workstream_id = ?\n ORDER BY t.id`,\n )\n .all(sourceWsId) as TaskCopyRow[];\n const ownerLookup = targetDb.prepare(\n \"SELECT id FROM agents WHERE workstream_id = ? AND name = ?\",\n );\n const insert = targetDb.prepare(\n `INSERT INTO tasks (workstream_id, local_id, title, status, impact, effort_days, owner_id, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n for (const row of rows) {\n const ownerId =\n includeOwners && row.owner_name !== null\n ? ((ownerLookup.get(targetWsId, row.owner_name) as { id: number } | undefined)?.id ?? null)\n : null;\n insert.run(\n targetWsId,\n row.local_id,\n row.title,\n row.status,\n row.impact,\n row.effort_days,\n ownerId,\n row.created_at,\n row.updated_at,\n );\n }\n}\n\nfunction copyEdges(sourceDb: Db, targetDb: Db, sourceWsId: number, targetWsId: number): void {\n const rows = sourceDb\n .prepare(\n `SELECT f.local_id AS from_local_id, t.local_id AS to_local_id, e.created_at\n FROM task_edges e\n JOIN tasks f ON f.id = e.from_task_id\n JOIN tasks t ON t.id = e.to_task_id\n WHERE f.workstream_id = ? AND t.workstream_id = ?\n ORDER BY e.created_at, f.local_id, t.local_id`,\n )\n .all(sourceWsId, sourceWsId) as EdgeCopyRow[];\n const insert = targetDb.prepare(\n `INSERT OR IGNORE INTO task_edges (from_task_id, to_task_id, created_at)\n SELECT f.id, t.id, ?\n FROM tasks f, tasks t\n WHERE f.workstream_id = ? AND f.local_id = ?\n AND t.workstream_id = ? AND t.local_id = ?`,\n );\n for (const row of rows) {\n insert.run(row.created_at, targetWsId, row.from_local_id, targetWsId, row.to_local_id);\n }\n}\n\nfunction copyNotes(sourceDb: Db, targetDb: Db, sourceWsId: number, targetWsId: number): void {\n const rows = sourceDb\n .prepare(\n `SELECT t.local_id AS task_local_id, n.author, n.content, n.created_at\n FROM task_notes n\n JOIN tasks t ON t.id = n.task_id\n WHERE t.workstream_id = ?\n ORDER BY n.id`,\n )\n .all(sourceWsId) as NoteCopyRow[];\n const insert = targetDb.prepare(\n `INSERT INTO task_notes (task_id, author, content, created_at)\n SELECT id, ?, ?, ? FROM tasks WHERE workstream_id = ? AND local_id = ?`,\n );\n for (const row of rows) {\n insert.run(row.author, row.content, row.created_at, targetWsId, row.task_local_id);\n }\n}\n\nfunction copyLogs(\n sourceDb: Db,\n targetDb: Db,\n sourceWsId: number,\n targetWsId: number,\n preserveSeq: boolean,\n): void {\n const rows = sourceDb\n .prepare(\n `SELECT seq, source, kind, payload, created_at\n FROM agent_logs\n WHERE workstream_id = ?\n ORDER BY seq`,\n )\n .all(sourceWsId) as LogCopyRow[];\n const insertPreserve = targetDb.prepare(\n \"INSERT INTO agent_logs (seq, workstream_id, source, kind, payload, created_at) VALUES (?, ?, ?, ?, ?, ?)\",\n );\n const insertRenumber = targetDb.prepare(\n \"INSERT INTO agent_logs (workstream_id, source, kind, payload, created_at) VALUES (?, ?, ?, ?, ?)\",\n );\n for (const row of rows) {\n if (preserveSeq) {\n insertPreserve.run(row.seq, targetWsId, row.source, row.kind, row.payload, row.created_at);\n } else {\n insertRenumber.run(targetWsId, row.source, row.kind, row.payload, row.created_at);\n }\n }\n}\n\nfunction copyWorkspaces(sourceDb: Db, targetDb: Db, sourceWsId: number, targetWsId: number): void {\n const rows = sourceDb\n .prepare(\n `SELECT a.name AS agent_name, v.backend, v.path, v.parent_ref, v.created_at\n FROM vcs_workspaces v\n JOIN agents a ON a.id = v.agent_id\n WHERE v.workstream_id = ?\n ORDER BY v.id`,\n )\n .all(sourceWsId) as WorkspaceCopyRow[];\n const agentLookup = targetDb.prepare(\n \"SELECT id FROM agents WHERE workstream_id = ? AND name = ?\",\n );\n const insert = targetDb.prepare(\n `INSERT INTO vcs_workspaces (agent_id, workstream_id, backend, path, parent_ref, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n );\n for (const row of rows) {\n const agentId = (agentLookup.get(targetWsId, row.agent_name) as { id: number } | undefined)?.id;\n if (agentId === undefined) continue;\n insert.run(agentId, targetWsId, row.backend, row.path, row.parent_ref, row.created_at);\n }\n}\n\nfunction copySync(sourceDb: Db, targetDb: Db, sourceWsId: number, targetWsId: number): void {\n const row = sourceDb\n .prepare(\"SELECT last_known_peer_seqs FROM workstream_sync WHERE workstream_id = ?\")\n .get(sourceWsId) as { last_known_peer_seqs: string } | undefined;\n if (!row) return;\n targetDb\n .prepare(\"INSERT INTO workstream_sync (workstream_id, last_known_peer_seqs) VALUES (?, ?)\")\n .run(targetWsId, row.last_known_peer_seqs);\n}\n\nfunction writeSyncState(\n db: Db,\n workstreamId: number,\n sourceMachineId: string,\n sourceSeq: number,\n): void {\n const localSeq = latestSeq(db, workstreamId);\n const peers: Record<string, number> = {\n [sourceMachineId]: sourceSeq,\n [localSeqKey(sourceMachineId)]: localSeq,\n };\n db.prepare(\n `INSERT OR REPLACE INTO workstream_sync (workstream_id, last_known_peer_seqs)\n VALUES (?, ?)`,\n ).run(workstreamId, JSON.stringify(peers));\n}\n\nfunction lastKnownPeerSync(\n db: Db,\n workstreamId: number,\n machineId: string,\n): { sourceSeq: number; localSeq: number } {\n const row = db\n .prepare(\"SELECT last_known_peer_seqs FROM workstream_sync WHERE workstream_id = ?\")\n .get(workstreamId) as { last_known_peer_seqs: string } | undefined;\n if (!row) return { sourceSeq: 0, localSeq: 0 };\n const parsed = parsePeerSeqs(row.last_known_peer_seqs);\n const sourceSeq = parsed[machineId] ?? 0;\n return { sourceSeq, localSeq: parsed[localSeqKey(machineId)] ?? sourceSeq };\n}\n\nfunction localSeqKey(machineId: string): string {\n return `${machineId}:local`;\n}\n\nfunction parsePeerSeqs(raw: string): Record<string, number> {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return {};\n const result: Record<string, number> = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (typeof value === \"number\" && Number.isFinite(value)) result[key] = value;\n }\n return result;\n } catch {\n return {};\n }\n}\n\nfunction readImportManifest(file: string): DbExportManifest {\n const manifestPath = `${file}.manifest.json`;\n if (!existsSync(manifestPath)) throw new DbImportManifestMissingError(manifestPath);\n return JSON.parse(readFileSync(manifestPath, \"utf8\")) as DbExportManifest;\n}\n\nfunction assertImportSchemaCompatible(sourceVersion: number): void {\n if (sourceVersion < CURRENT_SCHEMA_VERSION) throw new DbImportSchemaTooOldError(sourceVersion);\n if (sourceVersion > CURRENT_SCHEMA_VERSION) throw new DbImportSchemaTooNewError(sourceVersion);\n}\n\nfunction buildExportManifest(db: Db): DbExportManifest {\n const identity = getMachineIdentity(db);\n const schemaRow = db.prepare(\"SELECT version FROM schema_version WHERE id = 1\").get() as\n | { version: number }\n | undefined;\n const workstreams = listLocalWorkstreams(db);\n\n return {\n muVersion: readPackageVersion(),\n schemaVersion: schemaRow?.version ?? CURRENT_SCHEMA_VERSION,\n machineId: identity?.machine_id ?? \"\",\n hostname: identity?.hostname ?? hostname(),\n exportedAt: new Date().toISOString(),\n workstreams: workstreams.map((ws) => ({\n name: ws.name,\n tasks: count(db, \"SELECT COUNT(*) AS n FROM tasks WHERE workstream_id = ?\", ws.id),\n edges: count(\n db,\n `SELECT COUNT(*) AS n\n FROM task_edges e\n JOIN tasks f ON f.id = e.from_task_id\n JOIN tasks t ON t.id = e.to_task_id\n WHERE f.workstream_id = ? AND t.workstream_id = ?`,\n ws.id,\n ws.id,\n ),\n notes: count(\n db,\n `SELECT COUNT(*) AS n\n FROM task_notes n\n JOIN tasks t ON t.id = n.task_id\n WHERE t.workstream_id = ?`,\n ws.id,\n ),\n latestSeq: latestSeq(db, ws.id),\n })),\n };\n}\n\nfunction listLocalWorkstreams(db: Db): WorkstreamIdRow[] {\n return db.prepare(\"SELECT id, name FROM workstreams ORDER BY name\").all() as WorkstreamIdRow[];\n}\n\nfunction getMachineIdentity(db: Db): MachineIdentityRow | undefined {\n return db\n .prepare(\"SELECT machine_id, hostname, created_at FROM machine_identity WHERE id = 1\")\n .get() as MachineIdentityRow | undefined;\n}\n\nfunction countWorkstream(db: Db, wsId: number): Record<string, number> {\n return {\n tasks: count(db, \"SELECT COUNT(*) AS n FROM tasks WHERE workstream_id = ?\", wsId),\n edges: count(\n db,\n `SELECT COUNT(*) AS n\n FROM task_edges e\n JOIN tasks f ON f.id = e.from_task_id\n JOIN tasks t ON t.id = e.to_task_id\n WHERE f.workstream_id = ? AND t.workstream_id = ?`,\n wsId,\n wsId,\n ),\n notes: count(\n db,\n `SELECT COUNT(*) AS n\n FROM task_notes n\n JOIN tasks t ON t.id = n.task_id\n WHERE t.workstream_id = ?`,\n wsId,\n ),\n };\n}\n\nfunction countsFromManifest(ws: DbExportManifestWorkstream): Record<string, number> {\n return { tasks: ws.tasks, edges: ws.edges, notes: ws.notes };\n}\n\nfunction normaliseOnlyWorkstreams(input: readonly string[] | undefined): Set<string> {\n if (!input || input.length === 0) return new Set();\n return new Set(\n input\n .flatMap((v) => v.split(\",\"))\n .map((v) => v.trim())\n .filter((v) => v.length > 0),\n );\n}\n\nfunction count(db: Db, sql: string, ...params: unknown[]): number {\n const row = db.prepare(sql).get(...params) as { n: number } | undefined;\n return row?.n ?? 0;\n}\n\nfunction quoteSqlString(s: string): string {\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nfunction readPackageVersion(): string {\n try {\n const here = dirname(fileURLToPath(import.meta.url));\n const raw = readFileSync(join(here, \"..\", \"package.json\"), \"utf8\");\n const parsed = JSON.parse(raw) as { version?: unknown };\n return typeof parsed.version === \"string\" ? parsed.version : \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\"'\"'`)}'`;\n}\n\nexport {\n DbReplayLocalIdConflictError,\n DbReplayWorkstreamMissingError,\n buildReplayPlan,\n replayDb,\n type DbReplayEdgeItem,\n type DbReplayNoteItem,\n type DbReplayPlan,\n type DbReplayResult,\n type DbReplayTaskConflict,\n type DbReplayTaskItem,\n type ReplayDbOptions,\n} from \"./db-sync-replay.js\";\n","// mu — manual replay of divergence sidecars parked by `mu db import --force-source`.\n\nimport { createHash } from \"node:crypto\";\nimport { type Db, openDb } from \"./db.js\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\nimport { captureSnapshot } from \"./snapshots.js\";\n\nexport class DbReplayWorkstreamMissingError extends Error implements HasNextSteps {\n override readonly name = \"DbReplayWorkstreamMissingError\";\n constructor(public readonly workstream: string) {\n super(\n `replay sidecar is for workstream \"${workstream}\", which does not exist locally; restore it first via mu db import or mu archive restore`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Restore this workstream from a DB export\",\n command: \"mu db import <file> --apply\",\n },\n {\n intent: \"Or restore it from an archive\",\n command: `mu archive restore <label> --as ${this.workstream}`,\n },\n ];\n }\n}\n\nexport interface DbReplayTaskConflict {\n localId: string;\n local: { title: string; status: string };\n sidecar: { title: string; status: string };\n}\n\nexport class DbReplayLocalIdConflictError extends Error implements HasNextSteps {\n override readonly name = \"DbReplayLocalIdConflictError\";\n constructor(\n public readonly workstream: string,\n public readonly conflicts: readonly DbReplayTaskConflict[],\n ) {\n super(\n `sidecar task id collides with different local content in ${workstream}: ${conflicts\n .map(\n (c) =>\n `${c.localId} (local: ${c.local.status} ${JSON.stringify(c.local.title)}; sidecar: ${c.sidecar.status} ${JSON.stringify(c.sidecar.title)})`,\n )\n .join(\", \")}`,\n );\n }\n errorNextSteps(): NextStep[] {\n const first = this.conflicts[0];\n return [\n {\n intent: \"Create a renamed local task manually, then replay notes if desired\",\n command: first\n ? `mu task add ${first.localId}-replay -w ${this.workstream} -t ${shellQuote(first.sidecar.title)} -i <impact> -e <effort>`\n : `mu task add <renamed-id> -w ${this.workstream} -t <title> -i <impact> -e <effort>`,\n },\n {\n intent: \"Skip the colliding id and replay another task\",\n command: \"mu db replay <sidecar> --task <other-id> --apply\",\n },\n ];\n }\n}\n\nexport interface ReplayDbOptions {\n apply?: boolean;\n tasks?: readonly string[];\n notes?: readonly string[];\n all?: boolean;\n}\n\nexport interface DbReplayTaskItem {\n localId: string;\n title: string;\n status: string;\n impact: number;\n effortDays: number;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface DbReplayNoteItem {\n taskLocalId: string;\n author: string | null;\n content: string;\n createdAt: string;\n hash: string;\n}\n\nexport interface DbReplayEdgeItem {\n fromLocalId: string;\n toLocalId: string;\n createdAt: string;\n}\n\nexport interface DbReplayPlan {\n sourceFile: string;\n workstream: string;\n tasks: DbReplayTaskItem[];\n notes: DbReplayNoteItem[];\n edges: DbReplayEdgeItem[];\n conflicts: DbReplayTaskConflict[];\n}\n\nexport interface DbReplayResult extends DbReplayPlan {\n dryRun: boolean;\n applied: boolean;\n snapshotId?: number;\n added: { tasks: number; notes: number; edges: number };\n warnings: string[];\n}\n\ninterface WorkstreamIdRow {\n id: number;\n name: string;\n}\n\ninterface ReplayTaskRow {\n local_id: string;\n title: string;\n status: string;\n impact: number;\n effort_days: number;\n created_at: string;\n updated_at: string;\n}\n\nexport function replayDb(db: Db, file: string, opts: ReplayDbOptions = {}): DbReplayResult {\n const sidecarDb = openDb({ path: file, readonly: true });\n try {\n const plan = buildReplayPlan(db, sidecarDb, file);\n const taskFilter = new Set(opts.tasks ?? []);\n const noteFilter = new Set(opts.notes ?? []);\n const selectedTaskIds =\n opts.all === true ? new Set(plan.tasks.map((t) => t.localId)) : taskFilter;\n const selectedNoteIds =\n opts.all === true ? new Set(plan.notes.map((n) => n.taskLocalId)) : noteFilter;\n const hasSelectors = opts.all === true || selectedTaskIds.size > 0 || selectedNoteIds.size > 0;\n const noteTaskIds = new Set([...selectedNoteIds, ...selectedTaskIds]);\n const hasWrites =\n plan.tasks.some((t) => selectedTaskIds.has(t.localId)) ||\n plan.notes.some((n) => noteTaskIds.has(n.taskLocalId)) ||\n plan.edges.some(\n (e) =>\n opts.all === true ||\n selectedTaskIds.has(e.fromLocalId) ||\n selectedTaskIds.has(e.toLocalId),\n );\n const relevantConflicts =\n opts.all === true\n ? plan.conflicts\n : plan.conflicts.filter((c) => selectedTaskIds.has(c.localId));\n if (relevantConflicts.length > 0) {\n throw new DbReplayLocalIdConflictError(plan.workstream, relevantConflicts);\n }\n if (opts.apply !== true || !hasSelectors) return replayResult(plan, true, false);\n if (!hasWrites) return replayResult(plan, false, true);\n\n const snapshot = captureSnapshot(db, `db replay ${file}`, null);\n const applied = applyReplayPlan(db, plan, selectedTaskIds, selectedNoteIds, opts.all === true);\n return { ...replayResult(plan, false, true), snapshotId: snapshot.id, ...applied };\n } finally {\n sidecarDb.close();\n }\n}\n\nexport function buildReplayPlan(localDb: Db, sidecarDb: Db, sourceFile: string): DbReplayPlan {\n const sidecarWorkstreams = listLocalWorkstreams(sidecarDb);\n const sidecarWs = sidecarWorkstreams[0];\n if (sidecarWorkstreams.length !== 1 || !sidecarWs) {\n throw new Error(\n `replay sidecar must contain exactly one workstream; found ${sidecarWorkstreams.length}`,\n );\n }\n const localWs = listLocalWorkstreams(localDb).find((w) => w.name === sidecarWs.name);\n if (!localWs) throw new DbReplayWorkstreamMissingError(sidecarWs.name);\n\n const localTasks = new Map(\n (\n localDb\n .prepare(\"SELECT local_id, title, status FROM tasks WHERE workstream_id = ?\")\n .all(localWs.id) as {\n local_id: string;\n title: string;\n status: string;\n }[]\n ).map((t) => [t.local_id, t]),\n );\n const tasks: DbReplayTaskItem[] = [];\n const conflicts: DbReplayTaskConflict[] = [];\n for (const task of listReplayTasks(sidecarDb, sidecarWs.id)) {\n const local = localTasks.get(task.localId);\n if (!local) tasks.push(task);\n else if (local.title !== task.title || local.status !== task.status) {\n conflicts.push({\n localId: task.localId,\n local: { title: local.title, status: local.status },\n sidecar: { title: task.title, status: task.status },\n });\n }\n }\n\n const localNoteHashes = new Set(listReplayNotes(localDb, localWs.id).map((n) => n.hash));\n const localEdges = new Set(listReplayEdges(localDb, localWs.id).map(edgeKey));\n return {\n sourceFile,\n workstream: sidecarWs.name,\n tasks,\n notes: listReplayNotes(sidecarDb, sidecarWs.id).filter((n) => !localNoteHashes.has(n.hash)),\n edges: listReplayEdges(sidecarDb, sidecarWs.id).filter((e) => !localEdges.has(edgeKey(e))),\n conflicts,\n };\n}\n\nfunction applyReplayPlan(\n db: Db,\n plan: DbReplayPlan,\n selectedTaskIds: ReadonlySet<string>,\n selectedNoteIds: ReadonlySet<string>,\n replayAllEdges: boolean,\n): { added: { tasks: number; notes: number; edges: number }; warnings: string[] } {\n const warnings: string[] = [];\n const added = db.transaction(() => {\n const wsId = (\n db.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(plan.workstream) as\n | WorkstreamIdRow\n | undefined\n )?.id;\n if (wsId === undefined) throw new DbReplayWorkstreamMissingError(plan.workstream);\n const taskIds = new Set(selectedTaskIds);\n const noteTaskIds = new Set([...selectedNoteIds, ...taskIds]);\n let tasks = 0;\n let notes = 0;\n let edges = 0;\n\n const insertTask = db.prepare(\n `INSERT OR IGNORE INTO tasks (workstream_id, local_id, title, status, impact, effort_days, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n for (const task of plan.tasks) {\n if (!taskIds.has(task.localId)) continue;\n const result = insertTask.run(\n wsId,\n task.localId,\n task.title,\n task.status,\n task.impact,\n task.effortDays,\n task.createdAt,\n task.updatedAt,\n );\n if (result.changes > 0) tasks += 1;\n }\n\n const existingNoteHashes = new Set(listReplayNotes(db, wsId).map((n) => n.hash));\n const insertNote = db.prepare(\n `INSERT INTO task_notes (task_id, author, content, created_at)\n SELECT id, ?, ?, ? FROM tasks WHERE workstream_id = ? AND local_id = ?`,\n );\n for (const note of plan.notes) {\n if (!noteTaskIds.has(note.taskLocalId) || existingNoteHashes.has(note.hash)) continue;\n const result = insertNote.run(\n note.author,\n note.content,\n note.createdAt,\n wsId,\n note.taskLocalId,\n );\n if (result.changes > 0) {\n notes += 1;\n existingNoteHashes.add(note.hash);\n }\n }\n\n const insertEdge = db.prepare(\n `INSERT OR IGNORE INTO task_edges (from_task_id, to_task_id, created_at)\n SELECT f.id, t.id, ?\n FROM tasks f, tasks t\n WHERE f.workstream_id = ? AND f.local_id = ?\n AND t.workstream_id = ? AND t.local_id = ?`,\n );\n for (const edge of plan.edges) {\n if (!replayAllEdges && !taskIds.has(edge.fromLocalId) && !taskIds.has(edge.toLocalId)) {\n continue;\n }\n if (!hasTask(db, wsId, edge.fromLocalId) || !hasTask(db, wsId, edge.toLocalId)) {\n warnings.push(\n `skipped edge ${edge.fromLocalId} -> ${edge.toLocalId}: one endpoint is missing locally`,\n );\n continue;\n }\n const result = insertEdge.run(edge.createdAt, wsId, edge.fromLocalId, wsId, edge.toLocalId);\n if (result.changes > 0) edges += 1;\n }\n return { tasks, notes, edges };\n })();\n return { added, warnings };\n}\n\nfunction replayResult(plan: DbReplayPlan, dryRun: boolean, applied: boolean): DbReplayResult {\n return { ...plan, dryRun, applied, added: { tasks: 0, notes: 0, edges: 0 }, warnings: [] };\n}\n\nfunction listLocalWorkstreams(db: Db): WorkstreamIdRow[] {\n return db.prepare(\"SELECT id, name FROM workstreams ORDER BY name\").all() as WorkstreamIdRow[];\n}\n\nfunction listReplayTasks(db: Db, wsId: number): DbReplayTaskItem[] {\n return (\n db\n .prepare(\n `SELECT local_id, title, status, impact, effort_days, created_at, updated_at\n FROM tasks\n WHERE workstream_id = ?\n ORDER BY local_id`,\n )\n .all(wsId) as ReplayTaskRow[]\n ).map((row) => ({\n localId: row.local_id,\n title: row.title,\n status: row.status,\n impact: row.impact,\n effortDays: row.effort_days,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }));\n}\n\nfunction listReplayNotes(db: Db, wsId: number): DbReplayNoteItem[] {\n const rows = db\n .prepare(\n `SELECT t.local_id AS taskLocalId, n.author, n.content, n.created_at AS createdAt\n FROM task_notes n\n JOIN tasks t ON t.id = n.task_id\n WHERE t.workstream_id = ?\n ORDER BY n.created_at, n.id`,\n )\n .all(wsId) as Omit<DbReplayNoteItem, \"hash\">[];\n return rows.map((row) => ({ ...row, hash: noteHash(row) }));\n}\n\nfunction listReplayEdges(db: Db, wsId: number): DbReplayEdgeItem[] {\n return db\n .prepare(\n `SELECT f.local_id AS fromLocalId, t.local_id AS toLocalId, e.created_at AS createdAt\n FROM task_edges e\n JOIN tasks f ON f.id = e.from_task_id\n JOIN tasks t ON t.id = e.to_task_id\n WHERE f.workstream_id = ? AND t.workstream_id = ?\n ORDER BY f.local_id, t.local_id`,\n )\n .all(wsId, wsId) as DbReplayEdgeItem[];\n}\n\nfunction noteHash(note: Omit<DbReplayNoteItem, \"hash\">): string {\n return createHash(\"sha256\")\n .update(`${note.taskLocalId}\\0${note.content}\\0${note.createdAt}`)\n .digest(\"hex\");\n}\n\nfunction edgeKey(edge: DbReplayEdgeItem): string {\n return `${edge.fromLocalId}\\0${edge.toLocalId}`;\n}\n\nfunction hasTask(db: Db, wsId: number, localId: string): boolean {\n return (\n (db\n .prepare(\"SELECT 1 FROM tasks WHERE workstream_id = ? AND local_id = ?\")\n .get(wsId, localId) as { \"1\": number } | undefined) !== undefined\n );\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\"'\"'`)}'`;\n}\n","// mu — parallel-track detection via union-find with diamond merge.\n//\n// Port of a parallel-tracks union-find algorithm cribbed from a\n// prior internal multi-agent runtime. The killer feature: when two goals share a prerequisite, their subgraphs\n// overlap and they collapse into ONE track, so two agents are never\n// assigned tasks that share a dependency.\n//\n// goal_a goal_b goal_a goal_b\n// \\ / \\ /\n// shared → shared (1 track)\n// | |\n// leaf leaf\n//\n// Algorithm:\n// 1. Get all open goals (tasks with no outgoing edges, not CLOSED).\n// 2. For each goal, compute its prerequisite subgraph\n// (everything transitively reachable via reverse edges).\n// 3. Build union-find: merge any two goals whose subgraphs intersect.\n// 4. Each connected component is one Track.\n\nimport type { Db } from \"./db.js\";\nimport {\n STATUSES_TERMINAL_OR_PARKED,\n type TaskRow,\n getPrerequisites,\n listGoals,\n listReady,\n} from \"./tasks.js\";\n\nexport interface Track {\n /** Goal tasks (no outgoing edges) belonging to this track. */\n roots: TaskRow[];\n /** Every task id reachable as a prerequisite of any root in this track. */\n taskIds: ReadonlySet<string>;\n /** Number of READY tasks (per the SQL view) within this track's subgraph. */\n readyCount: number;\n}\n\n/**\n * Identify independent task subtrees suitable for parallel assignment\n * within a workstream. Open goals only; CLOSED goals are excluded as\n * they no longer represent work to schedule.\n *\n * Scoping: only goals belonging to `workstream` are considered.\n * Cross-workstream edges are forbidden by addTask, so a goal's\n * prerequisite subgraph is naturally workstream-internal.\n */\nexport function getParallelTracks(db: Db, workstream: string): Track[] {\n // listGoals already filters via the SQL view (NOT IN CLOSED/REJECTED/\n // DEFERRED), but defence-in-depth: a stale db snapshot or future view\n // tweak shouldn't let parked/terminal goals leak into track count.\n const goals = listGoals(db, workstream).filter(\n (g) => !STATUSES_TERMINAL_OR_PARKED.includes(g.status),\n );\n if (goals.length === 0) return [];\n\n // 2. Compute prerequisite subgraph for each goal.\n const subgraphs = new Map<string, Set<string>>();\n for (const goal of goals) {\n subgraphs.set(goal.name, getPrerequisites(db, goal.name, workstream));\n }\n\n // 3. Union-find: merge goals whose subgraphs overlap.\n const uf = new UnionFind(goals.map((g) => g.name));\n for (let i = 0; i < goals.length; i++) {\n const a = goals[i];\n if (!a) continue;\n for (let j = i + 1; j < goals.length; j++) {\n const b = goals[j];\n if (!b) continue;\n const subA = subgraphs.get(a.name);\n const subB = subgraphs.get(b.name);\n if (subA && subB && overlaps(subA, subB)) {\n uf.union(a.name, b.name);\n }\n }\n }\n\n // 4. Group goals + subgraph task ids by union-find root.\n const componentTaskIds = new Map<string, Set<string>>();\n const componentRoots = new Map<string, TaskRow[]>();\n for (const goal of goals) {\n const root = uf.find(goal.name);\n let bucket = componentTaskIds.get(root);\n if (!bucket) {\n bucket = new Set<string>();\n componentTaskIds.set(root, bucket);\n componentRoots.set(root, []);\n }\n componentRoots.get(root)?.push(goal);\n const sub = subgraphs.get(goal.name);\n if (sub) {\n for (const id of sub) bucket.add(id);\n }\n }\n\n // 5. Compute ready counts per track.\n const readyIds = new Set(listReady(db, workstream).map((t) => t.name));\n const tracks: Track[] = [];\n for (const [root, taskIds] of componentTaskIds) {\n const trackRoots = componentRoots.get(root) ?? [];\n let readyCount = 0;\n for (const id of taskIds) if (readyIds.has(id)) readyCount++;\n tracks.push({ roots: trackRoots, taskIds, readyCount });\n }\n\n // Stable order: by primary root's localId so output is deterministic.\n tracks.sort((a, b) => {\n const an = a.roots[0]?.name ?? \"\";\n const bn = b.roots[0]?.name ?? \"\";\n return an.localeCompare(bn);\n });\n return tracks;\n}\n\nfunction overlaps(a: Set<string>, b: Set<string>): boolean {\n // Iterate the smaller set for O(min(|a|, |b|)) lookups.\n const [small, large] = a.size <= b.size ? [a, b] : [b, a];\n for (const x of small) if (large.has(x)) return true;\n return false;\n}\n\nclass UnionFind {\n private readonly parent = new Map<string, string>();\n private readonly rank = new Map<string, number>();\n\n constructor(items: readonly string[]) {\n for (const item of items) {\n this.parent.set(item, item);\n this.rank.set(item, 0);\n }\n }\n\n find(x: string): string {\n let root = x;\n while (true) {\n const next = this.parent.get(root);\n if (next === undefined || next === root) break;\n root = next;\n }\n // Path compression.\n let curr = x;\n while (curr !== root) {\n const next = this.parent.get(curr);\n if (next === undefined) break;\n this.parent.set(curr, root);\n curr = next;\n }\n return root;\n }\n\n union(a: string, b: string): void {\n const rootA = this.find(a);\n const rootB = this.find(b);\n if (rootA === rootB) return;\n const rankA = this.rank.get(rootA) ?? 0;\n const rankB = this.rank.get(rootB) ?? 0;\n if (rankA < rankB) {\n this.parent.set(rootA, rootB);\n } else if (rankA > rankB) {\n this.parent.set(rootB, rootA);\n } else {\n this.parent.set(rootB, rootA);\n this.rank.set(rootA, rankA + 1);\n }\n }\n}\n","// Doctor summary — a TUI-friendly slice of `mu doctor`'s checks.\n//\n// The full `mu doctor` verb (src/cli/doctor.ts) renders a textual card\n// with: tmux/env presence, DB schema integrity, schema_version,\n// journal_mode, foreign_keys, per-workstream agent/task/log counts,\n// and reconcile drift (would-be-pruned ghosts + orphan panes). The\n// TUI's slot-9 Doctor card needs the same SIGNAL but at poll-tick\n// cost: an array of checks the card can render row-by-row.\n//\n// Per feat_card_9_doctor (workstream `tui-impl`), promoted from the\n// last reserved slot in design_global_keymap.\n//\n// CHEAPNESS RULES (so this is safe to run on every snapshot tick):\n// - Synchronous DB pragmas + COUNT(*)-shape SELECTs only.\n// - Reads `view.report.prunedGhosts` and `view.orphans` straight\n// out of the WorkstreamSnapshot — `loadWorkstreamSnapshot`\n// already runs `listLiveAgents(..., mode: \"status-only\")` once\n// per tick, which populates `report.prunedGhosts` (the\n// would-be-pruned count; status-only is non-mutating).\n// - Reads `workspaceOrphans` straight out of the snapshot too.\n// - NO subprocess shellouts. The `tmux -V` check from the textual\n// `mu doctor` is intentionally OMITTED here — the TUI is\n// already running inside a terminal so tmux's binary presence\n// is implicit, and adding a per-tick subprocess fork to a\n// polled dashboard is the wrong tradeoff. If a future task\n// wants tmux-presence on the dashboard, add it as a\n// once-per-session probe in <App> (mirrors probeClipboardBackend),\n// not as a per-tick check here.\n//\n// CHECK SHAPE (matches the textual doctor's per-row vocabulary):\n// { name, status: \"ok\" | \"warn\" | \"fail\", detail }\n//\n// The card filters to non-OK rows for its body; subtitle counts\n// warn+fail. When everything is OK the card renders a quiet\n// \"all healthy\" line so the operator's eye learns to read the\n// presence of rows as \"something needs attention.\"\n\nimport { CURRENT_SCHEMA_VERSION, type Db, EXPECTED_TABLES } from \"./db.js\";\nimport type { WorkstreamSnapshot } from \"./state.js\";\n\nexport type DoctorStatus = \"ok\" | \"warn\" | \"fail\";\n\nexport interface DoctorCheck {\n /** Short, stable identifier — used as the row label. Lowercase\n * one-word tokens so the column-aligned card layout looks tidy. */\n name: string;\n status: DoctorStatus;\n /** Free-form prose for the row's right-hand column. Kept short so\n * the card's CLIP column doesn't truncate it on common widths. */\n detail: string;\n}\n\nexport interface DoctorSummary {\n /** Every check that ran, in stable display order. The card filters\n * to non-OK rows for its body but keeps the OK rows so the popup\n * (when it ships under feat_more_cards_umbrella) can render the\n * full list. */\n checks: readonly DoctorCheck[];\n /** Convenience: how many rows are warn or fail. Card subtitle\n * reads this directly. Pure derivation from `checks`. */\n problemCount: number;\n}\n\n/**\n * Compute the doctor summary for a workstream. Pure-ish: runs cheap\n * synchronous DB queries + reads from the supplied snapshot. Callers\n * that don't want to compute a snapshot first (or are running inside\n * `loadWorkstreamSnapshot` mid-build) can omit `snapshot` — the\n * snapshot-derived checks (ghosts / orphans / workspace-orphans) are\n * skipped in that case.\n */\nexport function loadDoctorSummary(db: Db, snapshot: WorkstreamSnapshot | null): DoctorSummary {\n const checks: DoctorCheck[] = [];\n\n // ─ schema (table presence) ──────────────────────────────────────\n try {\n const tables = (\n db\n .prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name\",\n )\n .all() as { name: string }[]\n ).map((r) => r.name);\n const missing = EXPECTED_TABLES.filter((t) => !tables.includes(t));\n if (missing.length === 0) {\n checks.push({\n name: \"schema\",\n status: \"ok\",\n detail: `${EXPECTED_TABLES.length} tables`,\n });\n } else {\n checks.push({\n name: \"schema\",\n status: \"fail\",\n detail: `missing: ${missing.join(\", \")}`,\n });\n }\n } catch (err) {\n checks.push({\n name: \"schema\",\n status: \"fail\",\n detail: err instanceof Error ? err.message : String(err),\n });\n }\n\n // ─ schema_version ────────────────────────────────────────────────\n try {\n const row = db.prepare(\"SELECT version FROM schema_version WHERE id = 1\").get() as\n | { version: number }\n | undefined;\n const v = row?.version;\n if (v === undefined) {\n checks.push({\n name: \"schema_version\",\n status: \"fail\",\n detail: `missing row (expected ${CURRENT_SCHEMA_VERSION})`,\n });\n } else if (v === CURRENT_SCHEMA_VERSION) {\n checks.push({ name: \"schema_version\", status: \"ok\", detail: String(v) });\n } else if (v < CURRENT_SCHEMA_VERSION) {\n checks.push({\n name: \"schema_version\",\n status: \"warn\",\n detail: `${v} (code expects ${CURRENT_SCHEMA_VERSION})`,\n });\n } else {\n checks.push({\n name: \"schema_version\",\n status: \"fail\",\n detail: `${v} (code expects ${CURRENT_SCHEMA_VERSION}; downgrade?)`,\n });\n }\n } catch {\n checks.push({\n name: \"schema_version\",\n status: \"fail\",\n detail: \"unreadable\",\n });\n }\n\n // ─ journal_mode ─────────────────────────────────────────────────\n try {\n const journal = db.pragma(\"journal_mode\", { simple: true });\n if (journal === \"wal\") {\n checks.push({ name: \"journal_mode\", status: \"ok\", detail: String(journal) });\n } else {\n checks.push({\n name: \"journal_mode\",\n status: \"warn\",\n detail: `${journal} (expected wal)`,\n });\n }\n } catch (err) {\n checks.push({\n name: \"journal_mode\",\n status: \"warn\",\n detail: err instanceof Error ? err.message : String(err),\n });\n }\n\n // ─ foreign_keys ─────────────────────────────────────────────────\n try {\n const fk = db.pragma(\"foreign_keys\", { simple: true });\n if (fk === 1) {\n checks.push({ name: \"foreign_keys\", status: \"ok\", detail: \"on\" });\n } else {\n checks.push({\n name: \"foreign_keys\",\n status: \"fail\",\n detail: `off (${fk})`,\n });\n }\n } catch (err) {\n checks.push({\n name: \"foreign_keys\",\n status: \"fail\",\n detail: err instanceof Error ? err.message : String(err),\n });\n }\n\n // ─ snapshot-derived checks (ghosts / orphans) ───────────────────\n if (snapshot !== null) {\n const ghosts = snapshot.view.report.prunedGhosts;\n if (ghosts === 0) {\n checks.push({ name: \"agents\", status: \"ok\", detail: \"no ghost panes\" });\n } else {\n checks.push({\n name: \"agents\",\n status: \"warn\",\n detail: `${ghosts} ghost pane${ghosts === 1 ? \"\" : \"s\"}; run \\`mu agent list\\``,\n });\n }\n const orphanPanes = snapshot.view.orphans.length;\n if (orphanPanes === 0) {\n checks.push({ name: \"panes\", status: \"ok\", detail: \"no orphan panes\" });\n } else {\n checks.push({\n name: \"panes\",\n status: \"warn\",\n detail: `${orphanPanes} orphan pane${orphanPanes === 1 ? \"\" : \"s\"}; run \\`mu agent adopt\\``,\n });\n }\n const orphanWorkspaces = snapshot.workspaceOrphans.length;\n if (orphanWorkspaces === 0) {\n checks.push({ name: \"workspaces\", status: \"ok\", detail: \"no orphan dirs\" });\n } else {\n checks.push({\n name: \"workspaces\",\n status: \"warn\",\n detail: `${orphanWorkspaces} orphan dir${orphanWorkspaces === 1 ? \"\" : \"s\"}; run \\`mu workspace orphans\\``,\n });\n }\n }\n\n return { checks, problemCount: countProblems(checks) };\n}\n\n/** Count of warn + fail rows. Pure; exported for unit tests. */\nexport function countProblems(checks: readonly DoctorCheck[]): number {\n let n = 0;\n for (const c of checks) if (c.status !== \"ok\") n++;\n return n;\n}\n\n/**\n * Return the full check array (OK + warn + fail) in stable display\n * order. Used by the TUI's slot-9 Doctor popup\n * (feat_popup_9_doctor, workstream `tui-impl`) which renders every\n * row — not just the non-OK subset Card 9 surfaces.\n *\n * Thin wrapper over `loadDoctorSummary` so the SDK seam stays\n * single: `loadDoctorSummary` is the source of truth for the\n * check vocabulary, and the popup's `loadDoctorChecks` view is\n * just `.checks`. Pure-ish (same cheap synchronous DB reads as\n * `loadDoctorSummary`).\n */\nexport function loadDoctorChecks(\n db: Db,\n snapshot: WorkstreamSnapshot | null,\n): readonly DoctorCheck[] {\n return loadDoctorSummary(db, snapshot).checks;\n}\n\n// ─── pure helpers (per-check remediation hints) ────────────────────\n//\n// These map a `DoctorCheck.name` to either (a) a single-line\n// informational shell command suitable for yank/paste, or (b) a\n// short multi-line paragraph explaining the failure shape. They\n// originally lived next to the slot-9 Doctor popup\n// (src/cli/tui/popups/doctor.tsx) but moved here per\n// review_tui_doctor_remediation_lives_in_popup: neither function\n// has any rendering concern (both return plain strings) and the\n// popup's `renderDrillBody` was the only render-layer caller.\n// Living here keeps every per-check fact in one file so adding a\n// new check is a single touchpoint, and lets future SDK consumers\n// (e.g. a `mu doctor --remediation` flag) reach the same data\n// without depending on the TUI render layer.\n//\n// READ-ONLY by construction. Even when a check is `fail`, the yank\n// recipe is the diagnostic verb the operator should RUN MANUALLY,\n// never a mutating fix. Schema-shape checks (no actionable\n// mutation) yank a `# ...` comment line so the muscle-memory of\n// `y` still gives feedback.\n\n/**\n * Map a check row to the most useful informational command the\n * operator might paste. Read-only by construction: `mu agent list`,\n * `mu workspace orphans`, `mu doctor` are all SELECT-shape verbs;\n * `# ...` lines are visibly inert. Per the slot-9 popup spec KEY\n * MAP block this is INFORMATIONAL, never a mutating recipe — so\n * even when the check is `fail`, we yank the diagnostic verb the\n * operator should RUN MANUALLY, not a fix command.\n *\n * Pure; exported for unit tests + SDK reuse.\n */\nexport function yankCommandForCheck(check: Pick<DoctorCheck, \"name\" | \"status\">): string {\n switch (check.name) {\n case \"agents\":\n // Diagnostic: list live + ghost panes. Operator decides\n // whether to `mu agent close` from the list output.\n return \"mu agent list\";\n case \"panes\":\n // Orphan tmux panes — the standard adoption recipe.\n return \"mu agent adopt\";\n case \"workspaces\":\n // Diagnostic: list orphan workspace dirs. Operator decides\n // whether to `mu workspace free` from the list output.\n return \"mu workspace orphans\";\n case \"schema\":\n case \"schema_version\":\n case \"journal_mode\":\n case \"foreign_keys\":\n // No actionable mutation an operator should yank for\n // schema-shape checks (the migration runner is the SDK seam,\n // not a CLI verb). Yank a no-op comment so the muscle memory\n // of `y` still gives feedback. When fail, the textual\n // `mu doctor` carries the full diagnostic.\n return `# ${check.name}: see \\`mu doctor\\` for full diagnostic`;\n default:\n // Forward-compat: any future check name falls back to the\n // textual `mu doctor` verb. Read-only.\n return \"mu doctor\";\n }\n}\n\n/**\n * A short paragraph (one paragraph per check name) explaining the\n * shape of the failure / warning. Returned as a `readonly string[]`\n * so the popup's drill body can interleave the lines with other\n * content; CLI consumers can `.join(\"\\n\")` themselves.\n *\n * Pure; exported for unit tests + SDK reuse.\n */\nexport function remediationParagraph(check: DoctorCheck): readonly string[] {\n switch (check.name) {\n case \"agents\":\n return [\n \"A 'ghost pane' is a tmux pane that mu's reconcile pass would\",\n \"prune on the next mutation. Run `mu agent list` to see the\",\n \"current state, then `mu agent close <name>` if the agent is\",\n \"stale. The TUI is read-only — no auto-prune.\",\n ];\n case \"panes\":\n return [\n \"An 'orphan pane' is a live tmux pane in the workstream's\",\n \"session that mu doesn't know about. Adopt it via\",\n \"`mu agent adopt <pane-id>` to register it as a managed agent,\",\n \"or kill it manually if it's not yours.\",\n ];\n case \"workspaces\":\n return [\n \"An 'orphan workspace dir' is a per-agent VCS workspace under\",\n \"the workstream that has no matching mu agent row. Run\",\n \"`mu workspace orphans -w <ws>` to list them, then\",\n \"`mu workspace free <agent>` to release each one.\",\n ];\n case \"schema\":\n return [\n \"Missing tables typically mean an older mu binary opened the\",\n \"DB without running migrations. Rebuild mu (npm run build)\",\n \"and re-open; openDb runs the migration block on every\",\n \"process start.\",\n ];\n case \"schema_version\":\n return [\n \"Schema version mismatch means the DB was opened by a\",\n \"different mu binary than the one running now. If `<` the\",\n \"expected version, openDb should have migrated — check the\",\n \"build. If `>` the expected, you may have a downgrade in\",\n \"progress; restore from a snapshot rather than continuing.\",\n ];\n case \"journal_mode\":\n return [\n \"WAL is the default journal mode for SQLite under mu. A\",\n \"different mode (e.g. `delete`) means an external tool\",\n \"rewrote the DB pragma. Re-open the DB with mu to restore.\",\n ];\n case \"foreign_keys\":\n return [\n \"Foreign keys must be ON for mu's CASCADE deletes to work.\",\n \"An OFF value usually means an external SQLite client opened\",\n \"the DB without `PRAGMA foreign_keys = ON`. Re-open with mu.\",\n ];\n default:\n return [\"See `mu doctor` for the canonical textual diagnostic.\"];\n }\n}\n","// SDK seam for `mu state` (static) and the interactive TUI.\n//\n// Both renderers (the legacy cli-table3-based static fallback in\n// src/cli/state.ts and the new ink-based TUI in src/cli/tui/) consume\n// the same WorkstreamSnapshot. Pure data + a few small derivation\n// helpers — no rendering. See design_sdk_seam in workstream `tui` for\n// the rationale (`mu task notes design_sdk_seam -w tui`).\n\nimport { type AgentRow, type AgentStatus, type LiveAgentsView, listLiveAgents } from \"./agents.js\";\nimport type { Db } from \"./db.js\";\nimport { type DoctorSummary, loadDoctorSummary } from \"./doctor-summary.js\";\nimport { type LogRow, listLogs } from \"./logs.js\";\nimport {\n type TaskRow,\n listBlocked,\n listInProgress,\n listReady,\n listRecentClosed,\n listTasks,\n listTasksByOwner,\n} from \"./tasks.js\";\nimport { type Track, getParallelTracks } from \"./tracks.js\";\nimport { type CommitSummary, type VcsBackendName, detectBackend } from \"./vcs.js\";\nimport {\n type WorkspaceOrphan,\n type WorkspaceRow,\n decorateWithDirty,\n decorateWithStaleness,\n listWorkspaceOrphans,\n listWorkspaces,\n} from \"./workspace.js\";\n\n// ─── WorkstreamSnapshot ───────────────────────────────────────────\n\nexport interface WorkstreamSnapshot {\n workstreamName: string;\n view: LiveAgentsView;\n tracks: Track[];\n ready: TaskRow[];\n inProgress: TaskRow[];\n blocked: TaskRow[];\n recentClosed: TaskRow[];\n /** Populated only when callers explicitly pass `withAllTasks: true`.\n * The TUI dashboard fast tick leaves this empty and the all-tasks\n * popup reads its exhaustive list directly from SQLite while open. */\n allTasks: TaskRow[];\n workspaces: WorkspaceRow[];\n workspaceOrphans: WorkspaceOrphan[];\n recent: LogRow[];\n /** Last N commits from the project root (process.cwd()), populated\n * when `loadWorkstreamSnapshot` is called with withRecentCommits.\n * This is intentionally NOT a per-agent workspace log. */\n recentCommits: CommitSummary[];\n /** Backend that produced recentCommits. Null when recent commits were\n * not requested or no VCS backend was detected. */\n commitsBackend?: VcsBackendName | null;\n /** Populated when `loadWorkstreamSnapshot` is called with\n * `withDoctor: true`. Used by the TUI's slot-9 Doctor card to\n * render a glanceable health badge on the dashboard\n * (feat_card_9_doctor, workstream `tui-impl`). The static `mu\n * state` card and `mu doctor` itself don't consume it — they\n * read the textual doctor card directly. Null when not requested. */\n doctor: DoctorSummary | null;\n}\n\nexport interface LoadWorkstreamSnapshotOptions {\n /** Recent-events cap (default 200). */\n eventLimit?: number;\n /** When true, slow snapshot loading also populates `WorkspaceRow.dirty`\n * via decorateWithDirty (one `git status --porcelain` shellout per row,\n * capped at DECORATE_CONCURRENCY). The TUI caches this slow-tier value\n * and merges it into every fast SQL tick. */\n withDirty?: boolean;\n /** When true, slow snapshot loading also populates\n * `WorkstreamSnapshot.doctor` via `loadDoctorSummary`. The summary is\n * cheap SQL, but it reports tmux/workspace drift from slow-tier fields,\n * so the TUI refreshes it with the subprocess tier. */\n withDoctor?: boolean;\n /** Optional full task list for the TUI all-tasks popup. */\n withAllTasks?: true;\n /** Optional recent-project-commits slice for the TUI Commits card /\n * popup. Uses process.cwd() as the project root on purpose: the TUI\n * is launched from the project checkout, while worker workspaces live\n * elsewhere under the mu state dir. */\n withRecentCommits?: { limit: number };\n}\n\nexport interface WorkstreamSnapshotSlowFields {\n view: LiveAgentsView;\n /** Workspace rows decorated with slow-tier VCS observations\n * (`commitsBehindMain`, and `dirty` when requested). */\n workspaces: WorkspaceRow[];\n recentCommits: CommitSummary[];\n commitsBackend?: VcsBackendName | null;\n doctor: DoctorSummary | null;\n}\n\n/**\n * Fast TUI/state snapshot tier: pure SQLite reads only. Subprocess-backed\n * fields are intentionally empty placeholders so callers can merge the last\n * slow-tier values without blocking a 1s render tick on tmux or VCS probes.\n */\nexport async function loadWorkstreamSnapshotFast(\n db: Db,\n workstream: string,\n opts: LoadWorkstreamSnapshotOptions = {},\n): Promise<WorkstreamSnapshot> {\n const eventLimit = opts.eventLimit ?? 200;\n return {\n workstreamName: workstream,\n view: emptyLiveAgentsView(),\n tracks: getParallelTracks(db, workstream),\n ready: listReady(db, workstream).sort(byRoiDesc),\n inProgress: listInProgress(db, workstream),\n blocked: listBlocked(db, workstream),\n recentClosed: listRecentClosed(db, workstream),\n allTasks: opts.withAllTasks === true ? listTasks(db, workstream) : [],\n workspaces: listWorkspaces(db, workstream),\n workspaceOrphans: listWorkspaceOrphans(db, workstream),\n recent: listLogs(db, { workstream, kind: \"event\", limit: eventLimit }),\n recentCommits: [],\n commitsBackend: null,\n doctor: null,\n };\n}\n\n/**\n * Slow snapshot tier: fields backed by tmux / VCS subprocess probes (plus\n * doctor, which reports over those slow-tier observations). Returns only the\n * fields the fast snapshot deliberately leaves empty or undecorated.\n *\n * status-only refresh: don't prune mid-spawn placeholders or reap\n * unreachable agents — every render-mode is a polling read surface.\n */\nexport async function loadWorkstreamSnapshotSlow(\n db: Db,\n workstream: string,\n opts: LoadWorkstreamSnapshotOptions = {},\n baseSnapshot?: WorkstreamSnapshot,\n): Promise<WorkstreamSnapshotSlowFields> {\n const view = await listLiveAgents(db, { workstream, mode: \"status-only\" });\n let workspaces = listWorkspaces(db, workstream);\n if (opts.withDirty === true) workspaces = await decorateWithDirty(workspaces);\n const commits = await loadRecentCommits(opts.withRecentCommits);\n const slow: WorkstreamSnapshotSlowFields = {\n view,\n workspaces,\n recentCommits: commits.items,\n commitsBackend: commits.backend,\n doctor: null,\n };\n if (opts.withDoctor === true) {\n slow.doctor = loadDoctorSummary(\n db,\n mergeSnapshotFastSlow(baseSnapshot ?? minimalSnapshot(workstream), slow),\n );\n }\n return slow;\n}\n\n/** Merge the latest slow-tier subprocess observations into a fresh fast tier. */\nexport function mergeSnapshotFastSlow(\n fast: WorkstreamSnapshot,\n slow: WorkstreamSnapshotSlowFields | null,\n): WorkstreamSnapshot {\n if (slow === null) return fast;\n return {\n ...fast,\n view: slow.view,\n workspaces: mergeWorkspaceSlowFields(fast.workspaces, slow.workspaces),\n recentCommits: slow.recentCommits,\n commitsBackend: slow.commitsBackend ?? null,\n doctor: slow.doctor,\n };\n}\n\n/**\n * Back-compat wrapper for non-TUI callers: return the historical union shape\n * by composing the new fast SQL tier with one slow subprocess tier.\n */\nexport async function loadWorkstreamSnapshot(\n db: Db,\n workstream: string,\n opts: LoadWorkstreamSnapshotOptions = {},\n): Promise<WorkstreamSnapshot> {\n const fast = await loadWorkstreamSnapshotFast(db, workstream, opts);\n const fastWithStaleness: WorkstreamSnapshot = {\n ...fast,\n workspaces: await decorateWithStaleness(fast.workspaces),\n };\n const slow = await loadWorkstreamSnapshotSlow(db, workstream, opts, fastWithStaleness);\n return mergeSnapshotFastSlow(fastWithStaleness, slow);\n}\n\nfunction emptyLiveAgentsView(): LiveAgentsView {\n return {\n agents: [],\n orphans: [],\n report: { prunedGhosts: 0, statusChanges: 0, orphans: [], mode: \"status-only\" },\n };\n}\n\nfunction minimalSnapshot(workstream: string): WorkstreamSnapshot {\n return {\n workstreamName: workstream,\n view: emptyLiveAgentsView(),\n tracks: [],\n ready: [],\n inProgress: [],\n blocked: [],\n recentClosed: [],\n allTasks: [],\n workspaces: [],\n workspaceOrphans: [],\n recent: [],\n recentCommits: [],\n commitsBackend: null,\n doctor: null,\n };\n}\n\nfunction mergeWorkspaceSlowFields(\n fastRows: readonly WorkspaceRow[],\n slowRows: readonly WorkspaceRow[],\n): WorkspaceRow[] {\n const slowByAgent = new Map(slowRows.map((row) => [row.agentName, row]));\n return fastRows.map((fast) => {\n const slow = slowByAgent.get(fast.agentName);\n if (slow === undefined) return fast;\n return {\n ...fast,\n commitsBehindMain: slow.commitsBehindMain ?? fast.commitsBehindMain,\n dirty: slow.dirty,\n };\n });\n}\n\nasync function loadRecentCommits(\n opt: LoadWorkstreamSnapshotOptions[\"withRecentCommits\"],\n): Promise<{ backend: VcsBackendName | null; items: CommitSummary[] }> {\n if (opt === undefined) return { backend: null, items: [] };\n const projectRoot = process.cwd();\n const backend = await detectBackend(projectRoot);\n if (backend.name === \"none\") return { backend: null, items: [] };\n return { backend: backend.name, items: await backend.recentCommits(projectRoot, opt.limit) };\n}\n\n// ─── ROI helpers ───────────────────────────────────────────────────\n\n/**\n * ROI tiers used to colour task rows. Pure: returns the bucket name; the\n * consumer maps bucket → picocolors function (or ink text colour).\n * Magic numbers (≥100 high, ≥50 mid) lifted from the previous HUD impl.\n */\nexport type RoiBucket = \"high\" | \"mid\" | \"low\" | \"infinite\";\n\nexport function roiBucket(impact: number, effortDays: number): RoiBucket {\n const r = effortDays > 0 ? impact / effortDays : Number.POSITIVE_INFINITY;\n if (!Number.isFinite(r)) return \"infinite\";\n if (r >= 100) return \"high\";\n if (r >= 50) return \"mid\";\n return \"low\";\n}\n\n/** ROI sort comparator (descending). Used by loadWorkstreamSnapshot.ready. */\nfunction byRoiDesc(a: TaskRow, b: TaskRow): number {\n const ra = a.effortDays > 0 ? a.impact / a.effortDays : Number.POSITIVE_INFINITY;\n const rb = b.effortDays > 0 ? b.impact / b.effortDays : Number.POSITIVE_INFINITY;\n if (rb !== ra) return rb - ra;\n if (a.effortDays !== b.effortDays) return a.effortDays - b.effortDays;\n return a.name.localeCompare(b.name);\n}\n\n// ─── Agent helpers ─────────────────────────────────────────────────\n\n/** Histogram of agents by status. Pure derivation (no colour render). */\nexport function agentStatusHistogram(\n agents: readonly AgentRow[],\n): ReadonlyMap<AgentStatus, number> {\n const out = new Map<AgentStatus, number>();\n for (const a of agents) {\n out.set(a.status, (out.get(a.status) ?? 0) + 1);\n }\n return out;\n}\n\n// ─── Task helpers ──────────────────────────────────────────────────\n\nexport interface OwnedTasksSummary {\n /** Display token: \"—\" (none) | \"<task_id>\" (one) | \"⊕<N>\" (many). */\n bit: string;\n /** Underlying count for callers that want their own format. */\n count: number;\n /** The single owned task's local id, when count===1. */\n onlyTaskId?: string;\n}\n\n/**\n * Per-agent task summary: condensed display token + raw count. Used by\n * both the static Agents table and the ink Agents card. Pure on the\n * input rows — caller (e.g. loadWorkstreamSnapshot consumer) does the\n * listTasksByOwner query upstream and feeds the rows in.\n */\nexport function summarizeOwnedTasks(owned: readonly TaskRow[]): OwnedTasksSummary {\n const count = owned.length;\n if (count === 0) return { bit: \"—\", count: 0 };\n if (count === 1) {\n const only = owned[0];\n if (!only) return { bit: \"—\", count: 0 };\n return { bit: only.name, count: 1, onlyTaskId: only.name };\n }\n return { bit: `⊕${count}`, count };\n}\n\n// Re-export for convenience: callers wanting to combine listTasksByOwner\n// with summarizeOwnedTasks in one import.\nexport { listTasksByOwner };\n"],"mappings":"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","names":["resolve","out","panes","mkdirSync","join","dirname","join","rowFromDb","join","dirname","mkdirSync","join","rowFromDb","existsSync","statSync","unlinkSync","existsSync","unlinkSync","rowFromDb","statSync","existsSync","unlinkSync","Database","existsSync","unlinkSync","Database","rowFromDb","join","rowFromDb","workspacePath","join","existsSync","rmSync","homedir","resolve","workspacePath","existsSync","resolve","execFile","existsSync","dirname","promisify","workspacePath","promisify","execFile","dirname","existsSync","existsSync","workspacePath","resolve","existsSync","existsSync","workspacePath","existsSync","existsSync","workspacePath","existsSync","existsSync","workspacePath","resolve","homedir","existsSync","rmSync","rowFromDb","rows","join","join","rowFromDb","sql","rowFromDb","existsSync","readdirSync","join","resolve","existsSync","mkdirSync","statSync","dirname","join","existsSync","dirname","join","statSync","mkdirSync","result","join","existsSync","readdirSync","resolve","row","resolve","snap","realExecutor","execa","currentExecutor","rowFromDb","rows","pc","pc","randomUUID","existsSync","mkdirSync","readFileSync","unlinkSync","writeFileSync","hostname","dirname","join","fileURLToPath","createHash","shellQuote","createHash","shellQuote","existsSync","mkdirSync","dirname","unlinkSync","quoteSqlString","writeFileSync","listLocalWorkstreams","join","randomUUID","readFileSync","hostname","fileURLToPath","count"]}
|
|
1
|
+
{"version":3,"sources":["../src/db.ts","../src/logs.ts","../src/detect.ts","../src/tmux.ts","../src/reconcile.ts","../src/snapshots/capture.ts","../src/snapshots/core.ts","../src/snapshots/prune.ts","../src/snapshots/restore.ts","../src/tasks/core.ts","../src/staleness.ts","../src/workspace/core.ts","../src/workspace/crud.ts","../src/agents/spawn.ts","../src/output.ts","../src/agents/errors.ts","../src/vcs/git.ts","../src/vcs/helpers.ts","../src/vcs/types.ts","../src/vcs/jj.ts","../src/vcs/none.ts","../src/vcs/sl.ts","../src/vcs/index.ts","../src/workspace/decorate.ts","../src/workspace/orphans.ts","../src/workspace/recreate.ts","../src/tasks/queries.ts","../src/tasks/id.ts","../src/tasks/errors.ts","../src/tasks/edges.ts","../src/workstream.ts","../src/exporting.ts","../src/archives/core.ts","../src/archives/addremove.ts","../src/archives/delete.ts","../src/archives/query.ts","../src/archives/restore.ts","../src/tasks/status.ts","../src/parked.ts","../src/tasks/edit.ts","../src/tasks/lifecycle.ts","../src/tasks/claim.ts","../src/tasks/wait.ts","../src/agents/adopt.ts","../src/agents/kick.ts","../src/agents.ts","../src/dag.ts","../src/db-sync.ts","../src/db-sync-replay.ts","../src/tracks.ts","../src/doctor-summary.ts","../src/state.ts"],"sourcesContent":["// mu — DB module.\n//\n// Opens ~/.mu/mu.db (or MU_DB_PATH override), enables WAL + foreign keys,\n// applies the schema idempotently, and exposes the live Database handle.\n//\n// Schema (see CHANGELOG.md §\"Schema\"):\n// - core tables: workstreams, agents, tasks, task_edges, task_notes,\n// agent_logs, vcs_workspaces, snapshots\n// (+5 v6 archive_* tables; -1 approvals dropped in v7;\n// +2 v8 sync-substrate tables)\n// - 2 singleton-ish meta tables: schema_version, machine_identity\n// - 3 views: ready, blocked, goals\n//\n// v5 (this version) is the surrogate-INTEGER-PK shape per\n// docs/ARCHITECTURE.md § Surrogate-PK + SDK-boundary discipline.\n// Every entity table has an INTEGER PK; FKs reference INTEGER ids;\n// the operator-facing TEXT name is per-scope unique via\n// UNIQUE (<scope_id>, <name>).\n//\n// IMPORTANT: src/db.ts knows ONLY the v5 shape. Pre-v5 DBs are\n// rejected at openDb time with SchemaTooOldError; the operator\n// recovers the one-shot v4→v5 migration script from git history\n// (`git log --all --diff-filter=D -- scripts/migrate-v4-to-v5.ts`).\n// The old in-process forward-only migration ladder (v1→v2, v2→v3,\n// v3→v4) was removed in schema_v5_drop_migrations_ts: with the\n// loud-fail hook below catching every pre-v5 DB before openDb\n// returns, none of those migration paths could ever run.\n\nimport { randomUUID } from \"node:crypto\";\nimport { mkdirSync } from \"node:fs\";\nimport { homedir, hostname } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport Database, { type Database as DatabaseType } from \"better-sqlite3\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\n\nexport type Db = DatabaseType;\n\nexport interface OpenDbOptions {\n /**\n * Absolute path to the SQLite file. Defaults to MU_DB_PATH env var or\n * the XDG state path (see `defaultDbPath`). Use a per-test temp path\n * in tests.\n */\n path?: string;\n\n /**\n * If true, opens the DB read-only. Used by `mu sql` and similar read-only\n * surfaces to enforce no-mutation guarantees at the connection level.\n */\n readonly?: boolean;\n}\n\n/**\n * Resolve the canonical mu state directory:\n * MU_STATE_DIR > $XDG_STATE_HOME/mu > ~/.local/state/mu\n */\nexport function defaultStateDir(): string {\n if (process.env.MU_STATE_DIR) return process.env.MU_STATE_DIR;\n const stateHome = process.env.XDG_STATE_HOME ?? join(homedir(), \".local\", \"state\");\n return join(stateHome, \"mu\");\n}\n\n/**\n * Resolve the canonical DB path:\n * MU_DB_PATH > <state-dir>/mu.db\n */\nexport function defaultDbPath(): string {\n if (process.env.MU_DB_PATH) return process.env.MU_DB_PATH;\n return join(defaultStateDir(), \"mu.db\");\n}\n\n/**\n * Open the mu database. Creates the parent directory and applies the schema\n * idempotently on every open. Safe to call from many short-lived processes\n * concurrently — WAL mode handles cross-process writes.\n */\nexport function openDb(options: OpenDbOptions = {}): Db {\n const path = options.path ?? defaultDbPath();\n refuseUserDbDuringTests(path);\n mkdirSync(dirname(path), { recursive: true });\n\n const db = new Database(path, { readonly: options.readonly ?? false });\n\n if (!options.readonly) {\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"foreign_keys = ON\");\n // Detect schema version BEFORE applySchema so a real v<5 DB is not\n // silently stamped as v5 by the CREATE-IF-NOT-EXISTS in applySchema.\n const detectedVersion = detectExistingSchemaVersion(db);\n if (detectedVersion !== null && detectedVersion < MIN_ACCEPTED_SCHEMA_VERSION) {\n // Loud-fail: refuse to touch a pre-v5 DB. The operator\n // restores the one-shot migrator from git history and retries.\n // (See docs/ARCHITECTURE.md § Surrogate-PK + SDK-boundary\n // discipline for the v5 substrate; the migrator was deleted\n // in the post-landing cleanup per the temp-impl-artifact rule.)\n // v5 DBs are forward-bumped to v6 in `applySchema` (purely\n // additive change).\n try {\n db.close();\n } catch {\n // best effort\n }\n throw new SchemaTooOldError(detectedVersion, MIN_ACCEPTED_SCHEMA_VERSION);\n }\n applySchema(db);\n seedMachineIdentity(db);\n } else {\n db.pragma(\"foreign_keys = ON\");\n }\n\n return db;\n}\n\n/**\n * Hard guard (Layer \"db\" of bug_test_flake_round_2): refuse to open\n * the user's REAL default mu.db when running under vitest. Tests\n * MUST point at a per-test temp DB (via MU_DB_PATH or the explicit\n * `{ path }` option). A test that forgets to override either one\n * silently mutated the dev box's live state — we observed a stray\n * 'demo' workstream row replicated from test/tui-acceptance.integration.test.ts\n * into ~/.local/state/mu/mu.db. The guard throws a useful diagnostic\n * the moment the offending openDb() call is made; the failing test's\n * stack trace then names the leak source directly.\n *\n * Test mode = `process.env.VITEST` is defined OR `NODE_ENV === \"test\"`.\n * vitest sets VITEST=\"true\" in every fork; the NODE_ENV branch is for\n * other runners that may invoke openDb during tests.\n *\n * The user's REAL DB path is computed from HOME / XDG_STATE_HOME\n * directly (NOT from defaultDbPath() — which would honour MU_DB_PATH\n * and produce the temp path the test set, defeating the check).\n * Production code paths (the `mu` CLI binary) never set VITEST, so\n * the guard is a complete no-op outside the test runner.\n */\nfunction refuseUserDbDuringTests(path: string): void {\n const inTest = process.env.VITEST !== undefined || process.env.NODE_ENV === \"test\";\n if (!inTest) return;\n const home = process.env.HOME ?? homedir();\n const xdg = process.env.XDG_STATE_HOME ?? join(home, \".local\", \"state\");\n const realDb = resolve(join(xdg, \"mu\", \"mu.db\"));\n if (resolve(path) === realDb) {\n throw new Error(\n `openDb refused: tests must NEVER write to the user DB (${realDb}). Set MU_DB_PATH to a per-test temp path (test/_runCli.ts does this automatically) or pass an explicit { path } argument. The leak source is the call site of openDb in this stack frame.`,\n );\n }\n}\n\n/**\n * Thrown by openDb when the on-disk DB is at a schema version older\n * than v5. v5 dropped the in-process forward migrator; the one-shot\n * v4→v5 migration script lives in git history (recover via\n * `git log --all --diff-filter=D -- scripts/migrate-v4-to-v5.ts`).\n *\n * Maps to exit code 4 (conflict) in cli.ts handle().\n */\n// ─── Resolve helpers (operator-facing name -> surrogate id) ───────────\n//\n// docs/ARCHITECTURE.md § Surrogate-PK + SDK-boundary discipline:\n//\n// PUBLIC SDK functions take operator-facing names (workstream + local\n// id + agent name). Internal helpers take surrogate ids. Resolution\n// happens at the public-function entry, exactly once.\n//\n// `resolveWorkstreamId` is the only resolve helper that throws a typed\n// error from this leaf module — `WorkstreamNotFoundError` is defined\n// here, so there is no cycle. The task / agent resolvers RETURN\n// `number | null` (`tryResolveTaskId` / `tryResolveAgentId`) and let\n// SDK callers in `src/tasks/*.ts` / `src/agents.ts` throw the typed\n// `TaskNotFoundError` / `AgentNotFoundError` they own. That keeps\n// `cli/handle.ts`'s `instanceof`-based exit-code map (3 = not-found)\n// honest: a leaf throwing a plain `Error` whose `.name` was monkey-\n// patched to `\"TaskNotFoundError\"` flunks `instanceof TaskNotFoundError`\n// and falls through to the generic exit 1\n// (review_substrate_resolve_id_anonymous_errors).\n\nexport class WorkstreamNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"WorkstreamNotFoundError\";\n constructor(public readonly workstream: string) {\n super(`no such workstream: ${workstream}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List workstreams\", command: \"mu workstream list\" },\n {\n intent: \"Initialise this workstream\",\n command: `mu workstream init ${this.workstream}`,\n },\n ];\n }\n}\n\n/** Resolve a workstream name to its INTEGER surrogate id. Throws\n * WorkstreamNotFoundError on miss. Pure: no auto-create — callers\n * that want the auto-create-or-resolve semantics use\n * `ensureWorkstream` from src/workstream.ts (which returns void;\n * follow up with `resolveWorkstreamId` if the id is needed).\n */\nexport function resolveWorkstreamId(db: Db, workstream: string): number {\n const row = db.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as\n | { id: number }\n | undefined;\n if (!row) throw new WorkstreamNotFoundError(workstream);\n return row.id;\n}\n\n/** Resolve a workstream name to its id, returning null on miss instead\n * of throwing. Useful for read paths that want to early-return [] on\n * a non-existent workstream (e.g. listTasks). */\nexport function tryResolveWorkstreamId(db: Db, workstream: string): number | null {\n const row = db.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as\n | { id: number }\n | undefined;\n return row ? row.id : null;\n}\n\n/** Resolve a (workstream_id, local_id) pair to the task's surrogate\n * id, returning `null` on miss. SDK callers in `src/tasks/*.ts`\n * wrap the null return in `TaskNotFoundError` so the CLI's typed-\n * error → exit-code map (3 = not-found) fires. The leaf intentionally\n * does NOT throw a typed error (it would either pull in a cyclic\n * import or — as before — fake the `.name` and silently flunk\n * `instanceof TaskNotFoundError`, falling through to exit 1).\n * Renamed from `resolveTaskId` in\n * review_substrate_resolve_id_anonymous_errors. */\nexport function tryResolveTaskId(db: Db, workstreamId: number, localId: string): number | null {\n const row = db\n .prepare(\"SELECT id FROM tasks WHERE workstream_id = ? AND local_id = ?\")\n .get(workstreamId, localId) as { id: number } | undefined;\n return row ? row.id : null;\n}\n\n/** Resolve a (workstream_id, agent_name) pair to the agent's surrogate\n * id, returning `null` on miss. SDK callers in `src/agents.ts` wrap\n * the null return in `AgentNotFoundError` so the CLI's typed-error →\n * exit-code map (3 = not-found) fires. See `tryResolveTaskId` for the\n * full rationale (review_substrate_resolve_id_anonymous_errors). */\nexport function tryResolveAgentId(db: Db, workstreamId: number, name: string): number | null {\n const row = db\n .prepare(\"SELECT id FROM agents WHERE workstream_id = ? AND name = ?\")\n .get(workstreamId, name) as { id: number } | undefined;\n return row ? row.id : null;\n}\n\nexport class SchemaTooOldError extends Error implements HasNextSteps {\n override readonly name = \"SchemaTooOldError\";\n constructor(\n public readonly detectedVersion: number,\n public readonly requiredVersion: number,\n ) {\n super(\n `Detected v${detectedVersion} schema; v${requiredVersion} is required. The one-shot v4→v5 migration script (scripts/migrate-v4-to-v5.ts) was deleted post-landing; recover it from git history and run it once, then retry your command.`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Recover the one-shot v4→v5 migration script from git history\",\n command: \"git log --all --diff-filter=D -- scripts/migrate-v4-to-v5.ts | head\",\n },\n {\n intent: \"Then run it once against the DB\",\n command:\n \"git show <commit>:scripts/migrate-v4-to-v5.ts > /tmp/migrate.ts && npx tsx /tmp/migrate.ts\",\n },\n {\n intent: \"Then retry the original command\",\n command: \"# (your original mu invocation)\",\n },\n {\n intent: \"Inspect the on-disk DB version\",\n command: `sqlite3 \"$MU_DB_PATH\" 'SELECT version FROM schema_version'`,\n },\n ];\n }\n}\n\n/**\n * Sniff an existing DB's schema version BEFORE applySchema runs, so we\n * can distinguish:\n * - Brand-new DB: no tables at all -> returns null (fresh, will be\n * stamped to CURRENT_SCHEMA_VERSION by applySchema).\n * - Pre-versioning DB (had v1 tables before schema_version existed):\n * workstreams exists, schema_version doesn't -> returns 1.\n * - Already-versioned DB: schema_version row present -> returns its\n * value.\n */\nfunction detectExistingSchemaVersion(db: Db): number | null {\n const hasVersionTable = db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'\")\n .get() as { name: string } | undefined;\n if (hasVersionTable) {\n const row = db.prepare(\"SELECT version FROM schema_version WHERE id = 1\").get() as\n | { version: number }\n | undefined;\n return row?.version ?? null;\n }\n // No schema_version table. Check whether any of the original v1\n // tables exist; if so this is a pre-versioning v1 DB.\n const hasWorkstreams = db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='workstreams'\")\n .get() as { name: string } | undefined;\n if (hasWorkstreams) return 1;\n return null;\n}\n\n/**\n * Apply the schema. Idempotent: tables use CREATE TABLE IF NOT EXISTS;\n * views are dropped and recreated so the latest definition always wins.\n *\n * For fresh DBs this writes the current schema shape and stamps\n * schema_version = CURRENT_SCHEMA_VERSION. For existing DBs this is a\n * no-op for the table CREATEs (IF NOT EXISTS) but DOES recreate the\n * views. Pre-v5 DBs never reach this function — openDb's loud-fail\n * hook rejects them with SchemaTooOldError first.\n *\n * v5 → v6 in-place bump: v6 was purely additive (5 new archive_*\n * tables; no existing column / FK / view touched). The CREATE TABLE\n * IF NOT EXISTS blocks above create the new tables on a v5 DB.\n *\n * v6 → v7 in-place bump: v7 is destructive — drops the `approvals`\n * table (zero usage in 200+ task dogfood; anti-anticipatory pruning\n * per VISION.md \"no traits with zero implementors\"). The DROP runs\n * BEFORE the version stamp so a partial migration doesn't leave a\n * v7-stamped DB with the v6 table still present. Gated on the\n * detected pre-bump version so it's a one-shot for v6 DBs and a\n * harmless `IF EXISTS` no-op for fresh v7 DBs.\n *\n * v7 → v8 in-place bump: v8 is additive (machine_identity and\n * workstream_sync tables). openDb seeds machine_identity after this\n * schema block so v7 DBs upgraded in place get an identity too.\n */\nfunction seedMachineIdentity(db: Db): void {\n const row = db.prepare(\"SELECT COUNT(*) AS count FROM machine_identity\").get() as {\n count: number;\n };\n if (row.count !== 0) return;\n db.prepare(\n `INSERT OR IGNORE INTO machine_identity (id, machine_id, hostname, created_at)\n VALUES (1, ?, ?, ?)`,\n ).run(randomUUID(), hostname(), new Date().toISOString());\n}\n\nfunction applySchema(db: Db): void {\n // Sniff the recorded version BEFORE the schema CREATEs land — needed\n // to decide whether the v6 → v7 destructive migration runs (only on\n // a DB that's at v6 or older but ≥ v5, the openDb floor).\n const preBumpVersion = detectExistingSchemaVersion(db);\n db.exec(CURRENT_SCHEMA);\n // v6 → v7 destructive migration: drop the approvals table on any\n // pre-v7 DB. IF EXISTS so a fresh v7 DB (no approvals table ever\n // created) is a no-op too. The DROP must precede the version\n // stamp below: a partial migration that crashed mid-DROP would\n // re-run on next open instead of silently leaving the table.\n if (preBumpVersion !== null && preBumpVersion < 7) {\n db.exec(\"DROP INDEX IF EXISTS idx_approvals_status\");\n db.exec(\"DROP INDEX IF EXISTS idx_approvals_workstream\");\n db.exec(\"DROP TABLE IF EXISTS approvals\");\n }\n // Stamp the version on a fresh DB. INSERT OR IGNORE so we don't\n // overwrite the version on an existing v5+ DB.\n db.prepare(\"INSERT OR IGNORE INTO schema_version (id, version) VALUES (1, ?)\").run(\n CURRENT_SCHEMA_VERSION,\n );\n // Forward-additive bump for in-place transitions (v5 → v6 archive\n // tables, v6 → v7 approvals removal). Guarded by `version < ?` so a\n // future open against a same-or-newer DB doesn't accidentally\n // downgrade.\n db.prepare(\"UPDATE schema_version SET version = ? WHERE id = 1 AND version < ?\").run(\n CURRENT_SCHEMA_VERSION,\n CURRENT_SCHEMA_VERSION,\n );\n}\n\n/** The schema version a fresh DB starts at. v8 adds the\n * machine_identity and workstream_sync sync substrate on top of v7\n * (which dropped the approvals table), v6 (which added 5 archive_*\n * tables), and v5's surrogate-PK substrate. The refusal floor is\n * v5 — pre-v5 DBs throw `SchemaTooOldError`; v5+ DBs are\n * forward-bumped in place by `applySchema`. */\nexport const CURRENT_SCHEMA_VERSION = 8;\n\n/** The lowest schema version `openDb` will accept. v5+ DBs are\n * forward-bumped to the current version in place (v5 → v6 added\n * archive tables; v6 → v7 dropped the approvals table; v7 → v8\n * adds the sync substrate). Pre-v5 DBs throw `SchemaTooOldError`. */\nconst MIN_ACCEPTED_SCHEMA_VERSION = 5;\n\n/** Tables a healthy DB must contain. Single source of truth so\n * `mu doctor` and any other consumer don't drift. Adding a new table\n * = one new entry here AND a CREATE TABLE in CURRENT_SCHEMA. (Schema\n * changes that aren't compatible with prior schemas bump\n * CURRENT_SCHEMA_VERSION and ship with a one-shot script under\n * scripts/ (the v4→v5 transition was the canonical example\n * before the script was deleted post-landing). */\nexport const EXPECTED_TABLES: readonly string[] = [\n \"agent_logs\",\n \"agents\",\n \"archived_edges\",\n \"archived_events\",\n \"archived_notes\",\n \"archived_tasks\",\n \"archives\",\n \"machine_identity\",\n \"schema_version\",\n \"snapshots\",\n \"task_edges\",\n \"task_notes\",\n \"tasks\",\n \"vcs_workspaces\",\n \"workstream_sync\",\n \"workstreams\",\n];\n\n// ─── View DDL — single source of truth ────────────────────────────────\n//\n// The three views (ready, blocked, goals) get DROPped + CREATEd by\n// applySchema on every openDb. Each constant is self-contained:\n// DROP IF EXISTS + CREATE. Running DROP twice in a row is harmless,\n// so callers that already DROP up-front can still re-execute these\n// without churn.\n//\n// Exported as named constants so consumers can reference the canonical\n// shape (e.g. one-shot migration scripts under scripts/) without\n// duplicating SQL.\n\nexport const READY_VIEW_SQL = `\nDROP VIEW IF EXISTS ready;\nCREATE VIEW ready AS\n SELECT t.*\n FROM tasks t\n WHERE t.status = 'OPEN'\n AND NOT EXISTS (\n SELECT 1\n FROM task_edges e\n JOIN tasks b ON e.from_task_id = b.id\n WHERE e.to_task_id = t.id\n AND b.status <> 'CLOSED'\n );\n`;\n\nexport const BLOCKED_VIEW_SQL = `\nDROP VIEW IF EXISTS blocked;\nCREATE VIEW blocked AS\n SELECT t.*\n FROM tasks t\n WHERE t.status = 'OPEN'\n AND EXISTS (\n SELECT 1\n FROM task_edges e\n JOIN tasks b ON e.from_task_id = b.id\n WHERE e.to_task_id = t.id\n AND b.status <> 'CLOSED'\n );\n`;\n\n// A goal is an active endpoint of the DAG — a task with no dependents\n// that we're still working toward. CLOSED, REJECTED, and DEFERRED are\n// all excluded: a finished/abandoned/parked leaf is not an active goal.\n// (REJECTED and DEFERRED still BLOCK dependents per the views above\n// — they're terminal/parked from the perspective of 'what's a goal',\n// but they don't satisfy a blocked-by edge: only CLOSED does that.)\nexport const GOALS_VIEW_SQL = `\nDROP VIEW IF EXISTS goals;\nCREATE VIEW goals AS\n SELECT t.*\n FROM tasks t\n WHERE t.status NOT IN ('CLOSED', 'REJECTED', 'DEFERRED')\n AND NOT EXISTS (\n SELECT 1 FROM task_edges WHERE from_task_id = t.id\n );\n`;\n\n// ─── v5 SCHEMA ────────────────────────────────────────────────────────\n//\n// Per docs/ARCHITECTURE.md § Surrogate-PK + SDK-boundary discipline.\n// Every entity table has:\n// - INTEGER PRIMARY KEY AUTOINCREMENT (surrogate identity)\n// - <scope>_id INTEGER NOT NULL REFERENCES <parent>(id) ON DELETE CASCADE\n// - <name> TEXT NOT NULL (operator-facing, mutable)\n// - UNIQUE (<scope>_id, <name>)\n//\n// Foreign keys are INTEGER. Renames become single-row UPDATEs (no\n// cascade chain). The TEXT name is just an attribute. snapshots is\n// the documented exception (intentionally NO FK on workstream so a\n// destroy snapshot outlives its workstream).\n\nconst CURRENT_SCHEMA = `\n-- ─── Schema versioning ────────────────────────────────────────────────\n--\n-- Single-row table tracking which schema version this DB is at. Migrations\n-- read and update this; the row is INSERT-OR-IGNOREd by applySchema with\n-- the current version on a fresh DB.\nCREATE TABLE IF NOT EXISTS schema_version (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n version INTEGER NOT NULL\n);\n\n-- machine_identity: one durable identity per DB/machine, seeded by\n-- openDb after schema creation. hostname is advisory only.\nCREATE TABLE IF NOT EXISTS machine_identity (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n machine_id TEXT NOT NULL,\n hostname TEXT,\n created_at TEXT NOT NULL\n);\n\n-- ─── Tables ───────────────────────────────────────────────────────────\n\n-- workstreams: top of the hierarchy. name stays globally unique\n-- because it IS a tmux session name; no <scope_id> column because\n-- there's no parent.\nCREATE TABLE IF NOT EXISTS workstreams (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT UNIQUE NOT NULL,\n created_at TEXT NOT NULL -- ISO 8601\n);\n\n-- workstream_sync: per-workstream cross-machine drift state. Rows are\n-- created on demand by db import/export code, not pre-seeded.\nCREATE TABLE IF NOT EXISTS workstream_sync (\n workstream_id INTEGER PRIMARY KEY REFERENCES workstreams (id) ON DELETE CASCADE,\n last_known_peer_seqs TEXT NOT NULL DEFAULT '{}'\n);\n\n-- agents: one row per managed pane. Per-workstream unique on name.\nCREATE TABLE IF NOT EXISTS agents (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n workstream_id INTEGER NOT NULL REFERENCES workstreams (id) ON DELETE CASCADE,\n name TEXT NOT NULL, -- per-workstream unique\n cli TEXT NOT NULL DEFAULT 'pi',\n pane_id TEXT NOT NULL,\n status TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'full-access',\n tab TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE (workstream_id, name),\n CHECK (status IN (\n 'spawning', 'busy', 'needs_input', 'needs_permission',\n 'free', 'unreachable', 'terminated'\n )),\n CHECK (role IN ('full-access', 'read-only'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_agents_workstream ON agents (workstream_id);\nCREATE INDEX IF NOT EXISTS idx_agents_status ON agents (status);\n\n-- tasks: per-workstream unique on local_id (TRULY local now —\n-- different workstreams may reuse the same local_id).\nCREATE TABLE IF NOT EXISTS tasks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n workstream_id INTEGER NOT NULL REFERENCES workstreams (id) ON DELETE CASCADE,\n local_id TEXT NOT NULL, -- per-workstream unique\n title TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'OPEN',\n -- OPEN | IN_PROGRESS | CLOSED | REJECTED | DEFERRED — see VOCABULARY.md.\n impact INTEGER NOT NULL,\n effort_days REAL NOT NULL,\n owner_id INTEGER REFERENCES agents (id) ON DELETE SET NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE (workstream_id, local_id),\n CHECK (impact BETWEEN 1 AND 100),\n CHECK (effort_days > 0),\n CHECK (status IN ('OPEN', 'IN_PROGRESS', 'CLOSED', 'REJECTED', 'DEFERRED'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_tasks_workstream ON tasks (workstream_id);\nCREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks (status);\nCREATE INDEX IF NOT EXISTS idx_tasks_owner ON tasks (owner_id);\n\n-- task_edges: composite PK by pair. INTEGER FKs into tasks.id.\nCREATE TABLE IF NOT EXISTS task_edges (\n from_task_id INTEGER NOT NULL REFERENCES tasks (id) ON DELETE CASCADE,\n to_task_id INTEGER NOT NULL REFERENCES tasks (id) ON DELETE CASCADE,\n created_at TEXT NOT NULL,\n PRIMARY KEY (from_task_id, to_task_id),\n CHECK (from_task_id <> to_task_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_task_edges_to ON task_edges (to_task_id);\n\n-- task_notes: append-only context. author stays free-text\n-- (\"orchestrator\", \"user\", \"π - mu\", \"system\") — not always a\n-- registered agent.\nCREATE TABLE IF NOT EXISTS task_notes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_id INTEGER NOT NULL REFERENCES tasks (id) ON DELETE CASCADE,\n author TEXT,\n content TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_task_notes_task ON task_notes (task_id);\n\n-- agent_logs: append-only timeline. source stays free-text (\"system\",\n-- \"user\", \"orchestrator\", or any agent name) — not an FK relation.\n-- workstream_id is nullable (a future machine-wide event might exist)\n-- but every current emitter sets it; CASCADE on workstream destroy.\nCREATE TABLE IF NOT EXISTS agent_logs (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n workstream_id INTEGER REFERENCES workstreams (id) ON DELETE CASCADE,\n source TEXT NOT NULL,\n kind TEXT NOT NULL DEFAULT 'message',\n payload TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_agent_logs_seq ON agent_logs (seq);\nCREATE INDEX IF NOT EXISTS idx_agent_logs_ws_seq ON agent_logs (workstream_id, seq);\nCREATE INDEX IF NOT EXISTS idx_agent_logs_source ON agent_logs (source);\n\n-- vcs_workspaces: one isolated working copy per agent.\n-- UNIQUE (agent_id) enforces the 1:1 invariant; workstream_id is\n-- denormalised for query convenience. path is UNIQUE because two\n-- agents pointing at the same on-disk workspace would defeat the\n-- purpose.\nCREATE TABLE IF NOT EXISTS vcs_workspaces (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n agent_id INTEGER NOT NULL UNIQUE REFERENCES agents (id) ON DELETE CASCADE,\n workstream_id INTEGER NOT NULL REFERENCES workstreams (id) ON DELETE CASCADE,\n backend TEXT NOT NULL CHECK (backend IN ('jj', 'sl', 'git', 'none')),\n path TEXT NOT NULL UNIQUE,\n parent_ref TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_vcs_workspaces_workstream ON vcs_workspaces (workstream_id);\n\n-- snapshots: documented exception. NO FK on workstream — a destroy\n-- snapshot must outlive its workstream. workstream column stays TEXT\n-- so the snapshot remains readable even after every reference is gone.\nCREATE TABLE IF NOT EXISTS snapshots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n workstream TEXT,\n label TEXT NOT NULL,\n db_path TEXT NOT NULL,\n schema_version INTEGER NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_snapshots_created_at ON snapshots (created_at);\nCREATE INDEX IF NOT EXISTS idx_snapshots_workstream ON snapshots (workstream);\n\n-- ─── v6 archive tables (additive on top of v5) ────────────────────\n--\n-- 5 new tables landed in v6 to back the mu archive verb (cross-workstream\n-- preservation of CLOSED/REJECTED/DEFERRED tasks before destroy).\n-- Additive only: no existing column / FK / view touched. The v5 → v6\n-- transition is in-place via applySchema (no separate migration\n-- script). See docs/VOCABULARY.md § archive for terminology.\n--\n-- Design constraint: archives outlive workstreams. archives.label is\n-- globally unique (NOT per-workstream), and archived_tasks columns\n-- that refer to the source workstream are TEXT (not FKs) so the\n-- destroyed workstream's name stays readable post-destroy.\n\n-- archives: one row per operator-named archive bucket. label is\n-- globally unique because archives outlive workstreams (an archive\n-- whose label was scoped to a workstream would lose its name when\n-- the workstream is destroyed).\nCREATE TABLE IF NOT EXISTS archives (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n label TEXT UNIQUE NOT NULL,\n description TEXT,\n created_at TEXT NOT NULL,\n last_added_at TEXT NOT NULL -- bumped on every successful add (additive accumulation invariant)\n);\n\n-- archived_tasks: snapshot of a task at archive time. source_workstream\n-- is intentionally TEXT (the source workstream may be destroyed after\n-- archive); owner_name is snapshotted for the same reason. The\n-- (archive_id, source_workstream, original_local_id) UNIQUE is the\n-- idempotency lever for mu archive add re-runs.\nCREATE TABLE IF NOT EXISTS archived_tasks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n archive_id INTEGER NOT NULL REFERENCES archives (id) ON DELETE CASCADE,\n source_workstream TEXT NOT NULL,\n original_local_id TEXT NOT NULL,\n title TEXT NOT NULL,\n status TEXT NOT NULL,\n impact INTEGER NOT NULL,\n effort_days REAL NOT NULL,\n owner_name TEXT,\n archived_at_status TEXT NOT NULL,\n archived_at TEXT NOT NULL,\n original_created_at TEXT NOT NULL,\n original_updated_at TEXT NOT NULL,\n UNIQUE (archive_id, source_workstream, original_local_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_archived_tasks_archive ON archived_tasks (archive_id);\nCREATE INDEX IF NOT EXISTS idx_archived_tasks_source ON archived_tasks (archive_id, source_workstream);\n\n-- archived_edges: composite PK by pair of archived_tasks ids.\n-- archive_id is denormalised so a CASCADE on the archive cleans every\n-- edge in one shot.\nCREATE TABLE IF NOT EXISTS archived_edges (\n archive_id INTEGER NOT NULL REFERENCES archives (id) ON DELETE CASCADE,\n from_archived_id INTEGER NOT NULL REFERENCES archived_tasks (id) ON DELETE CASCADE,\n to_archived_id INTEGER NOT NULL REFERENCES archived_tasks (id) ON DELETE CASCADE,\n PRIMARY KEY (archive_id, from_archived_id, to_archived_id)\n);\n\n-- archived_notes: snapshot of task_notes for archived tasks. author\n-- stays free-text, mirroring task_notes.\nCREATE TABLE IF NOT EXISTS archived_notes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n archive_id INTEGER NOT NULL REFERENCES archives (id) ON DELETE CASCADE,\n archived_task_id INTEGER NOT NULL REFERENCES archived_tasks (id) ON DELETE CASCADE,\n author TEXT,\n content TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_archived_notes_task ON archived_notes (archived_task_id);\n\n-- archived_events: snapshot of kind='event' rows from agent_logs for\n-- the source workstream at archive time. Only events (not the full\n-- message log; that's recoverable via snapshot+undo if ever needed).\nCREATE TABLE IF NOT EXISTS archived_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n archive_id INTEGER NOT NULL REFERENCES archives (id) ON DELETE CASCADE,\n source_workstream TEXT NOT NULL,\n seq INTEGER NOT NULL,\n source TEXT NOT NULL,\n payload TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_archived_events_archive ON archived_events (archive_id, source_workstream);\n\n-- ─── Views (always replaced so the latest definition wins) ────────────\n-- See READY_VIEW_SQL / BLOCKED_VIEW_SQL / GOALS_VIEW_SQL above for the\n-- canonical DDL — interpolated here so applySchema is one db.exec().\n${READY_VIEW_SQL}\n${BLOCKED_VIEW_SQL}\n${GOALS_VIEW_SQL}\n`;\n","// mu — agent_logs: append-only timeline of activity in a workstream.\n//\n// Three roles in one table:\n// 1. Manual broadcasts (`mu log \"...\"` from a shell or agent pane)\n// 2. System events (auto-emitted by every state-changing verb;\n// wired in a follow-up commit so this surface is reviewable\n// first)\n// 3. External script entries via `mu log --as ...`\n//\n// The seq column (AUTOINCREMENT INTEGER PK) is the cursor. A tail\n// subscriber stores the last seq it saw and re-queries with\n// `seq > <last>`; AUTOINCREMENT guarantees seq never recycles even\n// after deletes, so the cursor is durable.\n\nimport { type Db, tryResolveWorkstreamId } from \"./db.js\";\n\nexport type LogKind = \"message\" | \"event\" | \"broadcast\" | string;\n\nexport interface LogRow {\n /** Monotonic AUTOINCREMENT id. Use as the cursor for `--since`. */\n seq: number;\n /** Workstream this entry belongs to, or `null` for machine-wide. */\n workstreamName: string | null;\n /** Free TEXT: agent name, \"system\", \"user\", or anything a caller picks. */\n source: string;\n /** Free TEXT: \"message\" (default), \"event\" (auto state changes),\n * \"broadcast\" (explicit cross-agent), or any caller-defined value. */\n kind: LogKind;\n /** Free utf-8 string. May be JSON if the kind suggests structure. */\n payload: string;\n /** ISO 8601 timestamp set at insert time. */\n createdAt: string;\n}\n\ninterface RawLogRow {\n seq: number;\n /** Joined from workstreams.name. Null when workstream_id is NULL. */\n workstream: string | null;\n source: string;\n kind: string;\n payload: string;\n created_at: string;\n}\n\n/** SELECT clause for joining workstream_id back to the operator-facing\n * workstream name. Used by every read path so the JS-side row shape\n * is operator-facing TEXT names (not surrogate ids). */\nconst SELECT_LOG_COLS = `\n l.seq AS seq,\n ws.name AS workstream,\n l.source AS source,\n l.kind AS kind,\n l.payload AS payload,\n l.created_at AS created_at\n`;\n\nconst LOG_FROM_JOIN = \"FROM agent_logs l LEFT JOIN workstreams ws ON ws.id = l.workstream_id\";\n\nfunction rowFromDb(row: RawLogRow): LogRow {\n return {\n seq: row.seq,\n workstreamName: row.workstream,\n source: row.source,\n kind: row.kind,\n payload: row.payload,\n createdAt: row.created_at,\n };\n}\n\nexport interface AppendLogOptions {\n /** Workstream this entry belongs to. `null` for machine-wide. */\n workstream: string | null;\n /** Who emitted this. Agent name, \"system\", \"user\", or arbitrary. */\n source: string;\n /** Defaults to \"message\". */\n kind?: LogKind;\n /** Free utf-8. Multi-line allowed. */\n payload: string;\n}\n\n/**\n * Append a log entry. Returns the inserted row (with assigned `seq`).\n * Constant-time. Single INSERT; safe to call from any state-changing\n * verb without a transaction wrapper.\n */\nexport function appendLog(db: Db, opts: AppendLogOptions): LogRow {\n const kind = opts.kind ?? \"message\";\n const createdAt = new Date().toISOString();\n // Resolve workstream name -> surrogate id. Null stays null. We do NOT\n // throw on a missing workstream here — an event payload may legitimately\n // reference a workstream the row for which is being concurrently dropped\n // (e.g. workstream destroy emits its own log row with workstream=null\n // for exactly this reason). Best-effort resolution.\n const workstreamId =\n opts.workstream === null ? null : tryResolveWorkstreamId(db, opts.workstream);\n const result = db\n .prepare(\n `INSERT INTO agent_logs (workstream_id, source, kind, payload, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n )\n .run(workstreamId, opts.source, kind, opts.payload, createdAt);\n return {\n seq: Number(result.lastInsertRowid),\n workstreamName: opts.workstream,\n source: opts.source,\n kind,\n payload: opts.payload,\n createdAt,\n };\n}\n\nexport interface ListLogsOptions {\n /** Filter by workstream. `undefined` = every workstream + machine-wide.\n * `null` = ONLY machine-wide entries. */\n workstream?: string | null;\n /** Strictly > this seq. Use to resume a tail. */\n since?: number;\n /** Cap the result. With `since`, returns the FIRST N matching (oldest\n * first). Without `since`, returns the LAST N (most recent),\n * re-sorted oldest-first. */\n limit?: number;\n source?: string;\n kind?: string;\n}\n\n/**\n * List log entries. Always returns oldest-first. Use `since` for\n * cursor-based reads (the canonical tail pattern); use `limit` alone\n * for \"show me the most recent N\" reads.\n */\nexport function listLogs(db: Db, opts: ListLogsOptions = {}): LogRow[] {\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (opts.workstream === null) {\n conditions.push(\"l.workstream_id IS NULL\");\n } else if (opts.workstream !== undefined) {\n // Resolve once; if the workstream doesn't exist the result set is empty.\n const wsId = tryResolveWorkstreamId(db, opts.workstream);\n if (wsId === null) return [];\n conditions.push(\"l.workstream_id = ?\");\n params.push(wsId);\n }\n if (opts.since !== undefined) {\n conditions.push(\"l.seq > ?\");\n params.push(opts.since);\n }\n if (opts.source !== undefined) {\n conditions.push(\"l.source = ?\");\n params.push(opts.source);\n }\n if (opts.kind !== undefined) {\n conditions.push(\"l.kind = ?\");\n params.push(opts.kind);\n }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n\n // Two query shapes:\n // - When `since` is set, ascending order is what we want directly.\n // - When `limit` is set without `since`, fetch the most-recent N\n // (descending) then reverse so the caller still sees oldest-first.\n if (opts.limit !== undefined && opts.since === undefined) {\n const rowsDesc = db\n .prepare(`SELECT ${SELECT_LOG_COLS} ${LOG_FROM_JOIN} ${where} ORDER BY l.seq DESC LIMIT ?`)\n .all(...params, opts.limit) as RawLogRow[];\n return rowsDesc.reverse().map(rowFromDb);\n }\n\n let sql = `SELECT ${SELECT_LOG_COLS} ${LOG_FROM_JOIN} ${where} ORDER BY l.seq ASC`;\n if (opts.limit !== undefined) {\n sql += \" LIMIT ?\";\n params.push(opts.limit);\n }\n const rows = db.prepare(sql).all(...params) as RawLogRow[];\n return rows.map(rowFromDb);\n}\n\n/**\n * Return the latest seq currently in the table (or 0 if empty). Used\n * by `mu log --tail` to start the cursor at \"now\" so the subscriber\n * only sees NEW entries unless they explicitly pass `--since 0`.\n */\nexport function latestSeq(db: Db, workstreamId?: number): number {\n const row =\n workstreamId === undefined\n ? (db.prepare(\"SELECT MAX(seq) AS s FROM agent_logs\").get() as { s: number | null })\n : (db\n .prepare(\"SELECT MAX(seq) AS s FROM agent_logs WHERE workstream_id = ?\")\n .get(workstreamId) as { s: number | null });\n return row.s ?? 0;\n}\n\n/**\n * One-line helper for state-changing SDK functions to auto-emit a\n * `kind='event'` log entry. Called AFTER the mutation succeeds, only\n * when the mutation actually produced a change (no-ops stay quiet).\n *\n * `source` defaults to 'system' since this is the auto-emission path;\n * a different source means \"a specific agent caused this\" and is set\n * by callers like `claimTask` (source = the claiming agent).\n */\nexport function emitEvent(\n db: Db,\n workstream: string | null,\n payload: string,\n source = \"system\",\n): void {\n appendLog(db, { workstream, source, kind: \"event\", payload });\n}\n\n// ─── claim-event structured prefix ─────────────────────────────────\n//\n// `task claim` events are the one place where a state-changing verb\n// emits TWO actors per row: the agent recorded as `source`, and the\n// `actor=` field that may differ on the --self anonymous-claim path\n// (where source == actor but tasks.owner stays NULL). The original\n// payload was free prose (`task claim foo by bar (was owner=...)`)\n// and the consumer (lastClaimActor below) prefix-matched the prose\n// — brittle: any rename silently nulled out the attribution.\n//\n// The fix keeps the prose suffix for human readability but prepends\n// a tab-delimited structured prefix that lastClaimActor parses\n// robustly. Format:\n//\n// task.claim<TAB><localId><TAB>actor=<actor><TAB>self=<0|1><TAB><prose>\n//\n// The trailing prose still starts with `task claim <localId> ...` so\n// event renderers (which strip the structured prefix via\n// displayEventPayload before colouring) keep working unchanged.\n//\n// See: review_code_last_claim_actor_brittle.\n\n/** Structured-prefix sentinel used by claim event payloads. The dot\n * distinguishes it from the prose `task claim ...` tail. */\nexport const CLAIM_EVENT_PREFIX = \"task.claim\";\n\n/** Build the structured payload for a `task claim` event. */\nexport function formatClaimEvent(opts: {\n localId: string;\n actor: string;\n anonymous: boolean;\n prose: string;\n}): string {\n const self = opts.anonymous ? \"1\" : \"0\";\n return `${CLAIM_EVENT_PREFIX}\\t${opts.localId}\\tactor=${opts.actor}\\tself=${self}\\t${opts.prose}`;\n}\n\n/** Strip the structured `task.claim` prefix and return the human-prose\n * tail. For non-claim payloads, returns the input unchanged. Used by\n * `mu log`, static state, and the TUI so the user sees the prose, not\n * the delimiter-noise. */\nexport function displayEventPayload(payload: string): string {\n if (!payload.startsWith(`${CLAIM_EVENT_PREFIX}\\t`)) return payload;\n // task.claim<TAB><id><TAB>actor=...<TAB>self=...<TAB><prose>\n // Split into 5 fields; the prose may itself contain tabs (it doesn't\n // today, but be defensive: rejoin with TAB so we never lose data).\n const parts = payload.split(\"\\t\");\n if (parts.length < 5) return payload;\n return parts.slice(4).join(\"\\t\");\n}\n\n/** Parse the actor= field out of a structured claim payload. Returns\n * null when the payload isn't a claim event or is malformed. */\nexport function parseClaimEventActor(payload: string): string | null {\n if (!payload.startsWith(`${CLAIM_EVENT_PREFIX}\\t`)) return null;\n for (const field of payload.split(\"\\t\")) {\n if (field.startsWith(\"actor=\")) return field.slice(\"actor=\".length);\n }\n return null;\n}\n\n/**\n * Find the actor of the most recent `task claim <id>` event for a\n * given task. Used to surface 'who's working on this' when\n * `tasks.owner IS NULL` (the --self anonymous-claim path). Returns\n * null when no claim event exists for this task.\n *\n * Implementation: indexed lookup on (workstream, seq) with a LIKE\n * against the structured prefix. Unbounded — the previous limit=100\n * ceiling silently dropped attribution on long-lived workstreams.\n * The structured prefix (CLAIM_EVENT_PREFIX) makes the match\n * robust against payload-prose churn.\n */\nfunction lastClaimEvent(\n db: Db,\n workstream: string,\n localId: string,\n): { payload: string; created_at: string } | null {\n // localId is validated by isValidTaskId — alnum + `_` + `-`. The\n // `_` is a LIKE wildcard, so escape it (and `%` and `\\` for\n // completeness, even though they can't appear in a valid id).\n const escaped = localId.replace(/[\\\\%_]/g, (c) => `\\\\${c}`);\n const pattern = `${CLAIM_EVENT_PREFIX}\\t${escaped}\\t%`;\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return null;\n const row = db\n .prepare(\n `SELECT payload, created_at FROM agent_logs\n WHERE workstream_id = ? AND kind = 'event' AND payload LIKE ? ESCAPE '\\\\'\n ORDER BY seq DESC LIMIT 1`,\n )\n .get(wsId, pattern) as { payload: string; created_at: string } | undefined;\n return row ?? null;\n}\n\nexport function lastClaimActor(db: Db, workstream: string, localId: string): string | null {\n const row = lastClaimEvent(db, workstream, localId);\n return row ? parseClaimEventActor(row.payload) : null;\n}\n\n/**\n * Find the `created_at` timestamp of the most recent `task claim`\n * event for a given task (the structured `task.claim<TAB>...` payload\n * emitted by claim.ts, both worker-claim and `--self` paths).\n *\n * Used by `mu task notes --since-claim` to slice the note timeline at\n * the most recent claim, so an operator dispatching a worker can see\n * only the post-claim notes (the spec was added before the claim;\n * the worker's progress lives after).\n *\n * Returns null when no claim event exists for this task — the CLI's\n * `--since-claim` then degrades gracefully to no filter (equivalent\n * to `--since-beginning`). Mirrors `lastClaimActor`'s LIKE-with-\n * escape pattern so a same-prefix id (`foo` vs `foo_2`) can't\n * cross-match.\n */\nexport function lastClaimEventAt(db: Db, workstream: string, localId: string): string | null {\n return lastClaimEvent(db, workstream, localId)?.created_at ?? null;\n}\n\n/**\n * Canonical list of two-token verb prefixes that `emitEvent` callers\n * use as the leading words of a payload. Single source of truth for\n * event renderers so they can never drift away from the actual emitter\n * sites.\n *\n * Maintenance contract: when you add an `emitEvent(...)` call whose\n * payload starts with a new two-word verb, add the verb here. A\n * regression test walks every entry and asserts the classifier\n * recognises it; the test fails if you add an emitter without adding\n * its verb here.\n *\n * Audit (2026-05): every `emitEvent` callsite under src/ produces a\n * payload that starts with one of these. Verified by\n * `grep -rn emitEvent src/ | grep -v import`.\n */\nexport const EVENT_VERB_PREFIXES: readonly string[] = [\n // src/tasks.ts + src/tasks/*.ts\n \"task add\",\n \"task note\",\n \"task status\",\n // `task claim` is the prose-tail of a `task.claim\\t...` structured\n // payload (see CLAIM_EVENT_PREFIX above); displayEventPayload\n // strips the structured prefix before renderers classify it, so the\n // prose tail starting with `task claim` still matches.\n \"task claim\",\n \"task release\",\n \"task update\",\n \"task delete\",\n \"task reap\",\n \"task block\",\n \"task unblock\",\n \"task reparent\",\n // src/agents.ts + src/agents/*.ts\n \"agent spawn\",\n \"agent close\",\n \"agent free\",\n \"agent adopt\",\n \"agent kick\",\n // src/tasks/wait.ts — emitted when --stuck-after fires (alive +\n // assigned + no recent progress; idle_assigned_agent_detection).\n \"agent stalled\",\n // src/workspace.ts\n \"workspace create\",\n \"workspace free\",\n \"workspace refresh\",\n \"workspace recreate\",\n // src/workstream.ts\n \"workstream init\",\n \"workstream destroy\",\n \"workstream export\",\n // src/archives.ts — v6 archive verbs. Machine-wide events\n // (workstream=null) because archives outlive workstreams.\n \"archive create\",\n \"archive delete\",\n \"archive add\",\n \"archive remove\",\n \"archive restore\",\n // src/exporting.ts — archive export emits the bucket-render summary\n // as a machine-wide event (workstream=null; the export spans every\n // source-ws in the archive).\n \"archive export\",\n // src/db-sync.ts — emitted per-workstream after a successful\n // `mu db export`. Used as the marker for src/parked.ts (the\n // \"presumed parked on another machine\" heuristic for `mu workstream\n // list` / TUI tab strip).\n \"db export\",\n];\n\n// ─── Verb classification (for renderers that colour by verb) ──────\n\nexport interface ClassifiedEvent {\n /** One of EVENT_VERB_PREFIXES. */\n verb: string;\n /** Payload past the verb token; preserves leading separator (\" \" or \"\\t\"). */\n rest: string;\n}\n\n/**\n * Match `payload` against EVENT_VERB_PREFIXES. Returns {verb, rest} on\n * match; null otherwise. The verb-boundary check is `next is space, tab,\n * or end-of-string` so we don't false-match e.g. `task addnote`.\n *\n * Pure parser. Consumers (the static state card, the ink Activity-log\n * card) apply their own colour to `verb` after matching.\n */\nexport function classifyEventVerb(payload: string): ClassifiedEvent | null {\n for (const verb of EVENT_VERB_PREFIXES) {\n if (!payload.startsWith(verb)) continue;\n const next = payload.charCodeAt(verb.length);\n if (!Number.isNaN(next) && next !== 0x20 && next !== 0x09) continue;\n return { verb, rest: payload.slice(verb.length) };\n }\n return null;\n}\n","// mu — Pi status detector.\n//\n// Derives an agent's runtime status (busy / needs_input / needs_permission)\n// from its tmux pane scrollback. 0.1.0 is pi-only — we know pi's exact\n// markers from its source. Heterogeneous CLI support (claude, codex) is\n// on the roadmap and will land alongside ground-truth scrollback fixtures.\n//\n// Algorithm (cribbed from a prior internal multi-agent runtime's per-CLI detector):\n//\n// 1. Take the last TAIL_WINDOW_LINES (100) lines of scrollback.\n// 2. Strip trailing blank lines (TUIs pad blanks below content).\n// 3. From what remains, take the last TAIL_LINES (20).\n// 4. If a permission pattern is present in the tail → needs_permission.\n// 5. Else if a busy pattern is present → busy.\n// 6. Else → needs_input.\n//\n// The tail-window is what makes detection robust: pi's startup banner\n// contains \"to interrupt\" in its keybindings hint list. By only looking\n// at the bottom of the pane, we ignore stale matches that have scrolled\n// out of view.\n\n/**\n * The full agent lifecycle status. Most values are scrollback-derived\n * (`DetectedStatus`); the rest are set by the lifecycle layer.\n */\nexport type AgentStatus =\n | \"spawning\"\n | \"busy\"\n | \"needs_input\"\n | \"needs_permission\"\n | \"free\"\n | \"unreachable\"\n | \"terminated\";\n\n/** Status that can be inferred from pane scrollback. */\nexport type DetectedStatus = \"busy\" | \"needs_input\" | \"needs_permission\";\n\n/**\n * Number of lines from the bottom of the scrollback to consider.\n * Larger than TAIL_LINES so we can strip trailing blanks first without\n * running out of content.\n */\nconst TAIL_WINDOW_LINES = 100;\n\n/**\n * After stripping trailing blanks from the window, look at this many lines.\n * Narrow enough that a stale prompt one screen up doesn't match.\n */\nconst TAIL_LINES = 20;\n\n/**\n * Pi prints `Working... (Esc to interrupt)` via its loading animation while\n * streaming an LLM response or running a tool. We require the closing paren\n * (`to interrupt)`) to distinguish from pi's startup banner, which renders\n * the same hint as `<key> to interrupt` (no parens) in a list of\n * keybindings. Without the paren, a fresh pane with the banner still\n * visible would false-positive busy.\n */\nconst PI_BUSY_PATTERNS: readonly string[] = [\"to interrupt)\"];\n\n/**\n * Fallback busy signal: any character in the Unicode Braille block\n * (U+2800–U+28FF). Every TUI spinner library worth using cycles a\n * subset of these glyphs (⠇⠏⠙⠧⠷⠿⠟⠋⠈ …) every ~80ms while\n * blocking work runs. They essentially never appear in agent prose\n * output, so the false-positive risk is tiny.\n *\n * This catches every wrapper around pi (pi-meta + solo, claude-code,\n * codex …) whose chrome differs from vanilla pi enough that the\n * `to interrupt)` literal isn't in the tail — surfaced by the\n * multi-agent dogfood when pi-meta workers all classified as\n * `needs_input` mid-work. Tracked in roadmap-v0-2\n * `bug_status_detector_pi_solo_misclassifies`.\n */\nconst BRAILLE_SPINNER_RE = /[\\u2800-\\u28FF]/;\n\n/**\n * Pi's confirm / select / input dialogs render footer hints like\n * `(Esc to cancel, Enter to submit)`. Both `to submit)` (with closing\n * paren) and `to cancel,` (with the comma artifact of being the first of\n * the parenthesised pair) only appear inside dialog footers — not in\n * prose, not in the banner.\n */\nconst PI_PERMISSION_PATTERNS: readonly string[] = [\"to submit)\", \"to cancel,\"];\n\n/**\n * Run the detector against pane scrollback and return the inferred\n * status. Permission overrides busy.\n */\nexport function detectPiStatus(scrollback: string): DetectedStatus {\n const tail = extractTail(scrollback);\n if (matchesAny(tail, PI_PERMISSION_PATTERNS)) return \"needs_permission\";\n if (matchesAny(tail, PI_BUSY_PATTERNS)) return \"busy\";\n if (BRAILLE_SPINNER_RE.test(tail)) return \"busy\";\n return \"needs_input\";\n}\n\n/**\n * Public for tests — extract the tail window the detector actually\n * inspects. Take last TAIL_WINDOW_LINES, strip trailing blanks, take\n * last TAIL_LINES.\n */\nexport function extractTail(scrollback: string): string {\n const lines = scrollback.split(\"\\n\");\n const window = lines.slice(-TAIL_WINDOW_LINES);\n // Strip trailing blank lines (Codex/pi pad with blanks below content).\n let end = window.length;\n while (end > 0 && (window[end - 1] ?? \"\").trim() === \"\") {\n end--;\n }\n const start = Math.max(0, end - TAIL_LINES);\n return window.slice(start, end).join(\"\\n\");\n}\n\nfunction matchesAny(text: string, patterns: readonly string[]): boolean {\n for (const pattern of patterns) {\n if (text.includes(pattern)) return true;\n }\n return false;\n}\n","// mu — tmux substrate.\n//\n// Single source of truth for all tmux interactions. Every tmux invocation\n// goes through `tmux(args)`, which wraps execa and produces structured\n// `TmuxError`s carrying args + stderr.\n//\n// The send protocol is the bracketed-paste sequence (canonical\n// implementation lives in `sendToPane` below):\n// 1. copy-mode -q (silent if not in copy mode)\n// 2. set-buffer (load text into a uniquely named buffer)\n// 3. paste-buffer -p -d -r (bracketed paste, delete buffer, preserve LF)\n// 4. delay (MU_SEND_DELAY_MS, default 500)\n// 5. send-keys Enter\n//\n// Naive `tmux send-keys \"<text>\"` is broken: characters like /, ?, f get\n// interpreted by the agent's TUI (Claude, Codex, less, vim) or by tmux's\n// copy mode if the user has scrolled up. Use `sendToPane()`.\n\nimport { execa } from \"execa\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\n\n// ─── Error type ────────────────────────────────────────────────────────\n\nexport class TmuxError extends Error implements HasNextSteps {\n constructor(\n public readonly args: readonly string[],\n public readonly stderr: string,\n public readonly stdout: string,\n public readonly exitCode: number | null,\n ) {\n const detail = stderr.trim() || stdout.trim() || \"no output\";\n super(`tmux ${args.join(\" \")} failed (exit ${exitCode}): ${detail}`);\n this.name = \"TmuxError\";\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Run health check\", command: \"mu doctor\" },\n {\n intent: \"Verify tmux is running and reachable\",\n command: \"tmux info | head\",\n },\n {\n intent: \"Check the failing tmux command in isolation\",\n command: `tmux ${this.args.join(\" \")}`,\n },\n ];\n }\n}\n\n/**\n * Thrown when a verb references a tmux pane id that doesn't exist on\n * the running tmux server. Distinct from TmuxError (which wraps any\n * tmux command failure) so callers can map it to a specific exit code\n * (`mu` maps it to 5 — substrate failure — alongside other tmux\n * issues, but the message is more actionable than a raw tmux stderr).\n */\nexport class PaneNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"PaneNotFoundError\";\n constructor(public readonly paneId: string) {\n super(`tmux pane not found: ${paneId}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: `Verify the pane id ${this.paneId} actually exists`,\n command: `tmux display-message -t ${this.paneId} -p '#{pane_id} #{pane_title}'`,\n },\n {\n intent: \"List all live panes across all sessions\",\n command:\n \"tmux list-panes -a -F '#{session_name}:#{window_id}.#{pane_id}\\\\t#{pane_title}\\\\t#{pane_current_command}'\",\n },\n {\n intent: \"List workstreams to choose the right scope\",\n command: \"mu workstream list\",\n },\n {\n intent: \"List registered agents and orphan panes in that scope\",\n command: \"mu agent list -w <workstream>\",\n },\n ];\n }\n}\n\n// ─── Pane ID validation ────────────────────────────────────────────────\n\n/**\n * Stable tmux pane IDs are of the form `%N` (e.g. \"%15\"). They never change\n * for the lifetime of the pane. **Pane indexes** (0, 1, 2…) are volatile and\n * shift when other panes close — never store or pass them.\n */\nexport const PANE_ID_RE = /^%\\d+$/;\n\nexport function isValidPaneId(s: string): boolean {\n return PANE_ID_RE.test(s);\n}\n\nexport function assertValidPaneId(s: string): void {\n if (!isValidPaneId(s)) {\n throw new TypeError(`invalid tmux pane id: ${JSON.stringify(s)} (expected /^%\\\\d+$/)`);\n }\n}\n\n// ─── Configurable delay ────────────────────────────────────────────────\n\n/**\n * Delay between bracketed-paste and Enter, in milliseconds. Claude/Codex/pi\n * process pasted text asynchronously; without this delay, Enter can arrive\n * before the agent has ingested the text. Defaults to 500; lower for tests,\n * raise for slow remotes via `MU_SEND_DELAY_MS`.\n */\nexport function defaultSendDelayMs(): number {\n const raw = process.env.MU_SEND_DELAY_MS;\n if (raw === undefined) return 500;\n const parsed = Number.parseInt(raw, 10);\n if (Number.isNaN(parsed) || parsed < 0) return 500;\n return parsed;\n}\n\n// ─── Executor (swappable for tests) ────────────────────────────────────\n\nexport interface TmuxExecResult {\n stdout: string;\n stderr: string;\n exitCode: number | null;\n}\n\nexport type TmuxExecutor = (args: readonly string[]) => Promise<TmuxExecResult>;\n\n/**\n * Optional global-flag prefix to splice in front of every tmux args\n * vector. When `MU_TMUX_SOCKET=<name>` is set, this returns\n * `[\"-L\", name, \"-f\", \"/dev/null\"]`:\n *\n * `-L <name>` routes every call through a private tmux server with\n * the given socket name (Linux: `/tmp/tmux-<uid>/<name>`,\n * macOS: `$TMPDIR/tmux-<uid>/<name>`) instead of the user's default\n * `/tmp/tmux-<uid>/default`. Set by the test harness in Layer 3 of\n * bug_test_suite_flake_leaks_isolation so the integration suite\n * can never observe — or contaminate — the user's interactive\n * tmux server.\n *\n * `-f /dev/null` skips the user's `~/.tmux.conf`. This matters\n * because tmux auto-starts the server on the first client call if\n * none is running (e.g. after a test’s last `kill-session`\n * shuts the server down). A typical user config uses `run-shell`\n * for status-bar plugins (TPM, hostname/network probes), and each\n * such hook adds ~1–4s to that auto-start. Without `-f /dev/null`\n * a single integration test grows from 3s to 48s on a configured\n * dev box. The suite drives tmux through the documented protocol,\n * not through bound keys, so the user's config is irrelevant.\n *\n * Read fresh on every call so a setupFiles hook that mutates\n * `process.env.MU_TMUX_SOCKET` mid-run takes effect immediately.\n *\n * Production code never sets this; it's a test-isolation seam.\n */\nfunction tmuxGlobalFlags(): readonly string[] {\n const socket = process.env.MU_TMUX_SOCKET;\n if (socket === undefined || socket.length === 0) return [];\n return [\"-L\", socket, \"-f\", \"/dev/null\"];\n}\n\nconst realExecutor: TmuxExecutor = async (args) => {\n const result = await execa(\"tmux\", [...tmuxGlobalFlags(), ...args], { reject: false });\n return {\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n exitCode: result.exitCode ?? null,\n };\n};\n\nlet currentExecutor: TmuxExecutor = realExecutor;\n\n/**\n * Install a custom executor (for tests). Returns the previous executor so\n * tests can restore it cleanly. Production code should never call this.\n */\nexport function setTmuxExecutor(executor: TmuxExecutor): TmuxExecutor {\n const previous = currentExecutor;\n currentExecutor = executor;\n return previous;\n}\n\n/** Restore the real (execa-backed) executor. */\nexport function resetTmuxExecutor(): void {\n currentExecutor = realExecutor;\n}\n\n/**\n * Run an arbitrary tmux command. The single point of contact with the\n * tmux binary; every higher-level operation in this module goes through it.\n *\n * Throws `TmuxError` on non-zero exit. Returns stdout on success.\n */\nexport async function tmux(args: readonly string[]): Promise<string> {\n const result = await currentExecutor(args);\n if (result.exitCode !== 0) {\n throw new TmuxError([...args], result.stderr, result.stdout, result.exitCode);\n }\n return result.stdout;\n}\n\n// ─── Sleep helper (testable) ──────────────────────────────────────────\n\nlet currentSleep: (ms: number) => Promise<void> = (ms) =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\nexport function setSleepForTests(\n impl: (ms: number) => Promise<void>,\n): (ms: number) => Promise<void> {\n const previous = currentSleep;\n currentSleep = impl;\n return previous;\n}\n\nexport function resetSleep(): void {\n currentSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Test-aware sleep — honours `setSleepForTests`. Public so other modules\n * (notably `agents.ts` for spawn liveness polling) get free no-op-ing in\n * tests without re-implementing the swap. */\nexport function sleep(ms: number): Promise<void> {\n return currentSleep(ms);\n}\n\n// ─── Domain types ──────────────────────────────────────────────────────\n\nexport interface TmuxSession {\n name: string;\n}\n\nexport interface TmuxWindow {\n /** tmux window id, e.g. \"@1\". */\n id: string;\n name: string;\n /** Session this window belongs to (only set by cross-session listings). */\n sessionName?: string;\n}\n\nexport interface TmuxPane {\n /** Stable tmux pane id, e.g. \"%15\". */\n paneId: string;\n /** Pane title set via `select-pane -T`. The agent's name in mu's convention. */\n title: string;\n /** Current foreground command (e.g. \"claude\", \"node\", \"bash\"). */\n command: string;\n /** Window this pane lives in. Only set by cross-window listings. */\n windowId?: string;\n /** Session this pane lives in. Only set by cross-session listings. */\n sessionName?: string;\n}\n\n// ─── Sessions ──────────────────────────────────────────────────────────\n\nexport async function listSessions(): Promise<TmuxSession[]> {\n // `list-sessions` exits 1 when no sessions exist; treat as empty.\n try {\n const out = await tmux([\"list-sessions\", \"-F\", \"#{session_name}\"]);\n return out\n .split(\"\\n\")\n .filter((line) => line.length > 0)\n .map((name) => ({ name }));\n } catch (err) {\n if (err instanceof TmuxError && /no server running|no sessions/i.test(err.stderr)) {\n return [];\n }\n throw err;\n }\n}\n\nexport async function sessionExists(name: string): Promise<boolean> {\n const result = await currentExecutor([\"has-session\", \"-t\", name]);\n return result.exitCode === 0;\n}\n\nexport interface NewSessionOptions {\n detached?: boolean;\n windowName?: string;\n command?: string;\n /** Initial working directory for the first pane (`-c <path>`). */\n cwd?: string;\n /** Extra env vars to set in the new pane via tmux `-e KEY=VALUE`.\n * Available since tmux 3.0; sets the variable in the new pane's\n * environment without polluting the tmux server's global env. */\n env?: Record<string, string>;\n}\n\nexport async function newSession(name: string, opts: NewSessionOptions = {}): Promise<void> {\n const args = [\"new-session\"];\n if (opts.detached !== false) args.push(\"-d\");\n args.push(\"-s\", name);\n if (opts.windowName) args.push(\"-n\", opts.windowName);\n if (opts.cwd) args.push(\"-c\", opts.cwd);\n appendEnvFlags(args, opts.env);\n if (opts.command) args.push(opts.command);\n await tmux(args);\n}\n\nexport interface NewSessionWithPaneOptions {\n windowName: string;\n command: string;\n cwd?: string;\n detached?: boolean;\n /** Extra env vars to set in the new pane via tmux `-e KEY=VALUE`. */\n env?: Record<string, string>;\n}\n\n/**\n * Create a tmux session AND its first window+pane in one atomic call.\n * Returns the new pane's stable id. Used by mu when spawning the first\n * agent in a workstream so we never end up with an empty `mu-<workstream>`\n * session left behind by a failed spawn.\n */\nexport async function newSessionWithPane(\n name: string,\n opts: NewSessionWithPaneOptions,\n): Promise<string> {\n const args = [\"new-session\"];\n if (opts.detached !== false) args.push(\"-d\");\n args.push(\"-s\", name, \"-n\", opts.windowName);\n if (opts.cwd) args.push(\"-c\", opts.cwd);\n appendEnvFlags(args, opts.env);\n args.push(\"-P\", \"-F\", \"#{pane_id}\", opts.command);\n const out = (await tmux(args)).trim();\n assertValidPaneId(out);\n return out;\n}\n\n/**\n * Idempotent: succeeds even if the session is already gone.\n *\n * Three swallowed shapes:\n * - \"can't find session: <name>\" — session never existed.\n * - \"session not found\" — alternate phrasing on some tmux builds.\n * - \"no server running on <path>\" — the tmux server itself has exited\n * (typical when the test suite runs against a private `tmux -L\n * <socket>` server and the just-killed session was its last; tmux\n * quietly shuts the server down). Without this, killSession would\n * throw on the very next idempotent call — only visible under\n * Layer 3 of bug_test_suite_flake_leaks_isolation.\n */\nexport async function killSession(name: string): Promise<void> {\n const result = await currentExecutor([\"kill-session\", \"-t\", name]);\n if (\n result.exitCode !== 0 &&\n !/can't find session|session not found|no server running/i.test(result.stderr)\n ) {\n throw new TmuxError(\n [\"kill-session\", \"-t\", name],\n result.stderr,\n result.stdout,\n result.exitCode,\n );\n }\n}\n\n// ─── Windows ───────────────────────────────────────────────────────────\n\nexport async function listWindows(session?: string): Promise<TmuxWindow[]> {\n if (session) {\n const out = await tmux([\"list-windows\", \"-t\", session, \"-F\", \"#{window_id}\\t#{window_name}\"]);\n return parseWindows(out);\n }\n // Cross-session: include the session name.\n const out = await tmux([\n \"list-windows\",\n \"-a\",\n \"-F\",\n \"#{session_name}\\t#{window_id}\\t#{window_name}\",\n ]);\n const windows: TmuxWindow[] = [];\n for (const line of out.split(\"\\n\")) {\n if (line.length === 0) continue;\n const [sessionName, id, name] = line.split(\"\\t\");\n if (!sessionName || !id || name === undefined) continue;\n windows.push({ id, name, sessionName });\n }\n return windows;\n}\n\nfunction parseWindows(output: string): TmuxWindow[] {\n const windows: TmuxWindow[] = [];\n for (const line of output.split(\"\\n\")) {\n if (line.length === 0) continue;\n const [id, name] = line.split(\"\\t\");\n if (!id || name === undefined) continue;\n windows.push({ id, name });\n }\n return windows;\n}\n\nexport interface NewWindowOptions {\n /** Target session. Required if invoking outside an existing tmux client. */\n session?: string;\n /** Window name. Maps to the agent's `tab:` value (or its name if no tab). */\n name: string;\n /** Command to run in the first pane. */\n command: string;\n /** If true, do not switch focus. Defaults to true. */\n detached?: boolean;\n /** Initial working directory (`-c <path>`). */\n cwd?: string;\n /** Extra env vars to set in the new pane via tmux `-e KEY=VALUE`. */\n env?: Record<string, string>;\n}\n\n/**\n * Create a new tmux window with one pane. Returns the new pane's stable\n * pane id (e.g. `%15`).\n */\nexport async function newWindow(opts: NewWindowOptions): Promise<string> {\n const args = [\"new-window\"];\n if (opts.detached !== false) args.push(\"-d\");\n if (opts.session) args.push(\"-t\", opts.session);\n args.push(\"-n\", opts.name);\n if (opts.cwd) args.push(\"-c\", opts.cwd);\n appendEnvFlags(args, opts.env);\n args.push(\"-P\", \"-F\", \"#{pane_id}\", opts.command);\n const out = (await tmux(args)).trim();\n assertValidPaneId(out);\n return out;\n}\n\n// ─── Panes ─────────────────────────────────────────────────────────────\n\n/**\n * List ALL panes in a tmux session (across every window). Used by\n * reconciliation to find every pane in the workstream's session.\n *\n * Note `list-panes -t <session>` (no -s) lists panes in the current\n * *window* of that session, not the whole session — a common gotcha.\n * `-s` is the flag that says \"all panes in this session.\"\n *\n * Returns `[]` (not throws) when the session doesn't exist or has no\n * panes. tmux destroys a session as soon as its last pane closes, so the\n * \"session was just here a moment ago\" case is normal during reconcile.\n * tmux's error wording in this case varies (\"can't find session\" or\n * \"can't find window\"), so we match either.\n */\nexport async function listPanesInSession(session: string): Promise<TmuxPane[]> {\n const args = [\n \"list-panes\",\n \"-s\",\n \"-t\",\n session,\n \"-F\",\n \"#{window_id}\\t#{pane_id}\\t#{pane_title}\\t#{pane_current_command}\",\n ];\n const result = await currentExecutor(args);\n if (result.exitCode !== 0) {\n if (/can't find (session|window)|no server running|no sessions/i.test(result.stderr)) {\n return [];\n }\n throw new TmuxError(args, result.stderr, result.stdout, result.exitCode);\n }\n const panes: TmuxPane[] = [];\n for (const line of result.stdout.split(\"\\n\")) {\n if (line.length === 0) continue;\n const [windowId, paneId, title, command] = line.split(\"\\t\");\n if (!windowId || !paneId || command === undefined) continue;\n panes.push({ paneId, title: title ?? \"\", command, windowId });\n }\n return panes;\n}\n\n/**\n * List panes in the current session, a specific window/session target, or\n * all panes across all sessions when `target` is the literal \"*\".\n */\nexport async function listPanes(target?: string): Promise<TmuxPane[]> {\n if (target === \"*\") {\n const out = await tmux([\n \"list-panes\",\n \"-a\",\n \"-F\",\n \"#{session_name}\\t#{window_id}\\t#{pane_id}\\t#{pane_title}\\t#{pane_current_command}\",\n ]);\n const panes: TmuxPane[] = [];\n for (const line of out.split(\"\\n\")) {\n if (line.length === 0) continue;\n const [sessionName, windowId, paneId, title, command] = line.split(\"\\t\");\n if (!sessionName || !windowId || !paneId || command === undefined) continue;\n panes.push({ paneId, title: title ?? \"\", command, windowId, sessionName });\n }\n return panes;\n }\n\n const args = [\"list-panes\"];\n if (target !== undefined) args.push(\"-t\", target);\n args.push(\"-F\", \"#{pane_id}\\t#{pane_title}\\t#{pane_current_command}\");\n const out = await tmux(args);\n const panes: TmuxPane[] = [];\n for (const line of out.split(\"\\n\")) {\n if (line.length === 0) continue;\n const [paneId, title, command] = line.split(\"\\t\");\n if (!paneId || command === undefined) continue;\n panes.push({ paneId, title: title ?? \"\", command });\n }\n return panes;\n}\n\nexport interface SplitWindowOptions {\n /** Target window or pane (e.g. \":Backend\" or \"%15\"). */\n target: string;\n command: string;\n /** Horizontal split (side-by-side). Default true. */\n horizontal?: boolean;\n detached?: boolean;\n /** Initial working directory for the new pane (`-c <path>`). */\n cwd?: string;\n /** Extra env vars to set in the new pane via tmux `-e KEY=VALUE`. */\n env?: Record<string, string>;\n}\n\n/**\n * Split a window and run a command in the new pane. Returns the new pane's\n * stable pane id.\n */\nexport async function splitWindow(opts: SplitWindowOptions): Promise<string> {\n const args = [\"split-window\"];\n if (opts.horizontal !== false) args.push(\"-h\");\n if (opts.detached !== false) args.push(\"-d\");\n args.push(\"-t\", opts.target);\n if (opts.cwd) args.push(\"-c\", opts.cwd);\n appendEnvFlags(args, opts.env);\n args.push(\"-P\", \"-F\", \"#{pane_id}\", opts.command);\n const out = (await tmux(args)).trim();\n assertValidPaneId(out);\n return out;\n}\n\n/**\n * Push one `-e KEY=VALUE` flag per entry into `args`, validating that\n * keys are non-empty and contain no `=` (tmux would error obscurely\n * otherwise; throwing TypeError keeps the failure at the call site).\n * No-op when `env` is undefined or empty.\n *\n * Iteration order follows Object.entries (insertion order); tests\n * shouldn't depend on a specific ordering, only on the presence of\n * each `-e KEY=VALUE` pair in the captured args.\n */\nfunction appendEnvFlags(args: string[], env: Record<string, string> | undefined): void {\n if (!env) return;\n for (const [k, v] of Object.entries(env)) {\n if (k.length === 0) {\n throw new TypeError(\"tmux env key must be non-empty\");\n }\n if (k.includes(\"=\")) {\n throw new TypeError(`tmux env key must not contain '=': ${JSON.stringify(k)}`);\n }\n args.push(\"-e\", `${k}=${v}`);\n }\n}\n\n/** Idempotent: succeeds even if the pane is already gone. */\nexport async function killPane(paneId: string): Promise<void> {\n assertValidPaneId(paneId);\n const result = await currentExecutor([\"kill-pane\", \"-t\", paneId]);\n if (result.exitCode !== 0 && !/can't find pane/i.test(result.stderr)) {\n throw new TmuxError([\"kill-pane\", \"-t\", paneId], result.stderr, result.stdout, result.exitCode);\n }\n}\n\nexport async function paneExists(paneId: string): Promise<boolean> {\n if (!isValidPaneId(paneId)) return false;\n // tmux's `display-message -t <bogus>` exits 0 but emits empty output; we\n // must check that the echoed pane id matches what we asked for.\n const result = await currentExecutor([\"display-message\", \"-t\", paneId, \"-p\", \"#{pane_id}\"]);\n if (result.exitCode !== 0) return false;\n return result.stdout.trim() === paneId;\n}\n\nexport async function setPaneTitle(paneId: string, title: string): Promise<void> {\n assertValidPaneId(paneId);\n await tmux([\"select-pane\", \"-t\", paneId, \"-T\", title]);\n}\n\n/**\n * Look up the window id (e.g. `@42`) that contains a given pane id\n * (e.g. `%15`). Used by spawn so we can apply window-scoped options\n * (`pane-border-status`) to the freshly created window.\n *\n * Returns undefined if the pane no longer exists.\n */\nexport async function getWindowIdForPane(paneId: string): Promise<string | undefined> {\n if (!isValidPaneId(paneId)) return undefined;\n const result = await currentExecutor([\"display-message\", \"-t\", paneId, \"-p\", \"#{window_id}\"]);\n if (result.exitCode !== 0) return undefined;\n const id = result.stdout.trim();\n return id.length > 0 ? id : undefined;\n}\n\n/**\n * Single source of truth for the operator opt-out from the mu pane\n * banner / border decorations. Set `MU_BANNER_QUIET=1` to disable.\n * All `enableMuPaneBorders*` helpers self-check this so callers\n * don't have to wrap them in env guards (a footgun: forget the\n * guard and you set the border even when the operator wanted\n * quiet).\n */\nfunction muBannersDisabled(): boolean {\n return process.env.MU_BANNER_QUIET === \"1\";\n}\n\n/**\n * Apply the mu pane border (status=top, format='[mu] #{pane_title}')\n * to EVERY window currently in `session`. Idempotent. Best-effort:\n * windows that have vanished mid-iteration are silently skipped. Used\n * by `mu workstream init` (covers the placeholder `_mu` window plus\n * any windows that already exist, e.g. on re-init of an upgraded\n * mu-pre-border session) and by `mu agent spawn` (covers the\n * just-created window so the border shows immediately on attach).\n *\n * No-op (returns 0) when `MU_BANNER_QUIET=1`.\n *\n * Returns the number of windows that received the option.\n */\nexport async function enableMuPaneBordersForSession(session: string): Promise<number> {\n if (muBannersDisabled()) return 0;\n const windows = await listWindows(session).catch(() => []);\n let n = 0;\n for (const w of windows) {\n try {\n await enableMuPaneBorders(w.id);\n n += 1;\n } catch {\n // Window vanished; skip silently. Border is decorative.\n }\n }\n return n;\n}\n\n/**\n * Apply the mu pane border to the window containing `paneId`. This is\n * the spawn/adopt shape: callers have a pane id (from `new-window` or\n * from an adopt target), and need to resolve the enclosing window\n * before calling `enableMuPaneBorders` (a window-scoped option).\n *\n * Self-checks `MU_BANNER_QUIET` and swallows tmux errors — the border\n * is decorative; failing to set it is never load-bearing.\n */\nexport async function enableMuPaneBordersForPane(paneId: string): Promise<void> {\n if (muBannersDisabled()) return;\n const wid = await getWindowIdForPane(paneId).catch(() => undefined);\n if (wid) await enableMuPaneBorders(wid).catch(() => {});\n}\n\n/**\n * Enable a one-line top pane border on a specific window/session target,\n * showing `[mu] <pane-title>`. Idempotent (set-option is a write, not\n * a toggle).\n *\n * IMPORTANT: tmux's `pane-border-status` and `pane-border-format` are\n * **window** options, not session options. `set-option -t <session>`\n * only updates the active window at call time — windows created later\n * inherit from the GLOBAL value (which is `off` by default and which\n * we deliberately do NOT touch, since changing the global would\n * affect every other tmux session on the user's machine, including\n * dotfile-curated ones).\n *\n * Therefore mu must call this twice:\n * 1. At `mu workstream init` time on the placeholder `_mu` window\n * (so an attached operator sees a border immediately).\n * 2. On every `mu agent spawn` (which calls `tmux new-window`),\n * against the new window's id.\n *\n * The border is tmux chrome, not pane content: it doesn't scroll, it\n * survives copy-mode, and the inner CLI never sees it.\n *\n * Designed as the pane-border visual cue for mu-managed panes.\n */\nexport async function enableMuPaneBorders(target: string): Promise<void> {\n if (muBannersDisabled()) return;\n await tmux([\"set-option\", \"-w\", \"-t\", target, \"pane-border-status\", \"top\"]);\n await tmux([\"set-option\", \"-w\", \"-t\", target, \"pane-border-format\", \" [mu] #{pane_title} \"]);\n // Bottom + sides: heavy box-drawing lines so a mu-managed pane is\n // visually distinct even when not the active pane (top carries the\n // labeled status text; the rest of the frame carries the visual\n // \"this is mu\" cue). Cyan-bold for the active pane, dim brightblack\n // for inactive ones, so the operator's eye lands on the pane that\n // currently has focus.\n await tmux([\"set-option\", \"-w\", \"-t\", target, \"pane-border-lines\", \"heavy\"]);\n await tmux([\"set-option\", \"-w\", \"-t\", target, \"pane-active-border-style\", \"fg=cyan,bold\"]);\n await tmux([\"set-option\", \"-w\", \"-t\", target, \"pane-border-style\", \"fg=brightblack\"]);\n}\n\n/**\n * Look up the TTY device path for a pane (e.g. `/dev/ttys012` on macOS,\n * `/dev/pts/3` on Linux). Used by `mu agent kick` to find the\n * foreground process group on the pane's TTY so it can be signalled\n * directly — `tmux send-keys C-c` does NOT propagate to wrapped\n * subprocesses inside a CLI like pi/claude/codex (the CLI catches it\n * itself and treats it as a UI input). The escape hatch is signalling\n * the foreground pgid of the underlying TTY from outside the pane.\n *\n * Throws `PaneNotFoundError` when the pane id is invalid or the pane\n * has vanished. Throws `TmuxError` on any other tmux failure.\n */\nexport async function paneTTY(paneId: string): Promise<string> {\n assertValidPaneId(paneId);\n const result = await currentExecutor([\"display-message\", \"-t\", paneId, \"-p\", \"#{pane_tty}\"]);\n if (result.exitCode !== 0) {\n if (/can't find pane|pane not found/i.test(result.stderr)) {\n throw new PaneNotFoundError(paneId);\n }\n throw new TmuxError(\n [\"display-message\", \"-t\", paneId, \"-p\", \"#{pane_tty}\"],\n result.stderr,\n result.stdout,\n result.exitCode,\n );\n }\n const tty = result.stdout.trim();\n if (tty === \"\") throw new PaneNotFoundError(paneId);\n return tty;\n}\n\nexport async function getPaneTitle(paneId: string): Promise<string | undefined> {\n if (!isValidPaneId(paneId)) return undefined;\n const result = await currentExecutor([\"display-message\", \"-t\", paneId, \"-p\", \"#{pane_title}\"]);\n if (result.exitCode !== 0) return undefined;\n return result.stdout.trimEnd();\n}\n\n/**\n * Read the title of the *current* pane (the one whose shell is running this\n * process), via $TMUX_PANE. Returns undefined when not inside tmux. Used by\n * `mu claim` to derive the agent identity from the pane title — the claim\n * protocol's zero-config identity step.\n */\nexport async function currentPaneTitle(): Promise<string | undefined> {\n const paneId = process.env.TMUX_PANE;\n if (!paneId || !isValidPaneId(paneId)) return undefined;\n return getPaneTitle(paneId);\n}\n\n/**\n * Extract the agent-name token from a (possibly composed) pane title.\n * mu's composeAgentTitle renders titles as `name · <glyph> · task_id`,\n * where <glyph> is a Nerd Font codepoint from STATUS_EMOJI (see\n * src/agents.ts). The agent name is always the first ' · '-separated\n * token. Adopted panes that haven't been re-titled by mu have just the\n * name (one token) — still parses.\n *\n * Returns trimmed name, or the input unchanged if no separator.\n */\nexport function parseAgentNameFromTitle(title: string): string {\n const idx = title.indexOf(\" · \");\n return idx === -1 ? title.trim() : title.slice(0, idx).trim();\n}\n\n/**\n * Convenience: read the current pane's title and extract the agent name.\n */\nexport async function currentAgentName(): Promise<string | undefined> {\n const title = await currentPaneTitle();\n if (title === undefined) return undefined;\n return parseAgentNameFromTitle(title);\n}\n\nexport async function selectLayout(window: string, layout: string): Promise<void> {\n await tmux([\"select-layout\", \"-t\", window, layout]);\n}\n\n// ─── Send protocol (the canonical bracketed-paste sequence) ────────────\n\nexport interface SendOptions {\n /** Override the default delay between paste and Enter, in ms. */\n delayMs?: number;\n}\n\n/**\n * Send a single line of text to a pane and submit it.\n *\n * Sequence:\n * 1. exit copy mode (silent if not in copy mode)\n * 2. load text into a uniquely-named tmux buffer\n * 3. paste with bracketed-paste mode (-p) so apps treat as literal text;\n * delete buffer after paste (-d); preserve LF (-r)\n * 4. wait MU_SEND_DELAY_MS (default 500) so the agent ingests the text\n * 5. send Enter as a real key event\n *\n * Naive `send-keys \"<text>\"` would let characters like /, ?, f, : be\n * interpreted by the agent's TUI or by tmux's copy mode. Always use this.\n */\nexport async function sendToPane(\n paneId: string,\n text: string,\n opts: SendOptions = {},\n): Promise<void> {\n assertValidPaneId(paneId);\n\n // 1. Exit copy mode silently. -q suppresses errors when not in copy mode.\n const copyResult = await currentExecutor([\"copy-mode\", \"-q\", \"-t\", paneId]);\n // Even with -q, some tmux versions report errors. Swallow non-fatal.\n if (copyResult.exitCode !== 0 && /can't find pane|no current target/i.test(copyResult.stderr)) {\n throw new TmuxError(\n [\"copy-mode\", \"-q\", \"-t\", paneId],\n copyResult.stderr,\n copyResult.stdout,\n copyResult.exitCode,\n );\n }\n\n // 2. Load text into a uniquely-named buffer.\n const bufferName = `mu-send-${process.pid}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;\n await tmux([\"set-buffer\", \"-b\", bufferName, text]);\n\n // 3. Bracketed paste: -p wraps in \\e[200~...\\e[201~ so apps see literal\n // text; -d deletes buffer after paste; -r preserves LF (no CR conversion).\n try {\n await tmux([\"paste-buffer\", \"-p\", \"-d\", \"-r\", \"-b\", bufferName, \"-t\", paneId]);\n } catch (err) {\n // Best-effort buffer cleanup if paste failed before -d took effect.\n await currentExecutor([\"delete-buffer\", \"-b\", bufferName]).catch(() => {});\n throw err;\n }\n\n // 4. Wait for the agent CLI to ingest the pasted text.\n const delay = opts.delayMs ?? defaultSendDelayMs();\n if (delay > 0) await currentSleep(delay);\n\n // 5. Submit. Enter must be a real key event, not part of the paste.\n await tmux([\"send-keys\", \"-t\", paneId, \"Enter\"]);\n}\n\n// ─── Capture ───────────────────────────────────────────────────────────\n\nexport interface CaptureOptions {\n /**\n * Number of trailing lines to capture. Omitted = full scrollback.\n * 0 = visible pane only.\n */\n lines?: number;\n}\n\n/**\n * Read pane scrollback as plain text (no ANSI escapes).\n *\n * - No options: full scrollback (`-S - -E -`)\n * - `lines: 0`: visible pane only\n * - `lines: N`: last N lines (`-S -N`)\n */\nexport async function capturePane(paneId: string, opts: CaptureOptions = {}): Promise<string> {\n assertValidPaneId(paneId);\n const args = [\"capture-pane\", \"-t\", paneId, \"-p\"];\n if (opts.lines === undefined) {\n args.push(\"-S\", \"-\", \"-E\", \"-\");\n } else if (opts.lines > 0) {\n args.push(\"-S\", `-${opts.lines}`);\n }\n return tmux(args);\n}\n","// mu — the canonical \"reality wins\" reconciliation routine.\n//\n// Three steps, in order:\n//\n// 1. Prune ghost rows whose pane no longer exists in tmux.\n// 2. Detect status from pane scrollback for surviving agents.\n// 3. Surface orphan panes that look like agents but have no DB row.\n// Do NOT auto-adopt — `mu agent list` shows orphans under a separate\n// section and the user runs `mu agent adopt` to formally claim.\n//\n// `mu state`, `mu agent list`, and `mu doctor` all call this. It's the only\n// place where the registry's view of the world is reconciled against tmux's\n// view.\n\nimport * as agentSdk from \"./agents.js\";\nimport type { Db } from \"./db.js\";\nimport { detectPiStatus } from \"./detect.js\";\nimport { type TmuxPane, capturePane, listPanesInSession } from \"./tmux.js\";\n\n/**\n * What kind of reconciliation pass to run.\n *\n * \"full\" Default for `mu state` and `mu agent list`. Prunes\n * ghosts (deleting the registry row, which fires the\n * deleteAgent reaper that flips IN_PROGRESS tasks back\n * to OPEN with [reaper] notes), runs status detection\n * against surviving panes, surfaces orphans.\n *\n * \"report-only\" Pure observation. Counts would-be-pruned ghosts\n * without deleting; skips status detection entirely\n * (no DB writes, no tmux title writes); surfaces\n * orphans (pure read). Used by `mu undo` (the\n * post-restore pass MUST NOT delete rows the snapshot\n * just restored — see\n * snap_undo_reconcile_destroys_recovered_agents) and\n * `mu doctor` (read-only diagnostic).\n *\n * Mid-spawn placeholders are protected directly in the prune loop, so read\n * paths no longer need a separate mode just to avoid racing spawn's workspace\n * pre-stage.\n */\nexport type ReconcileMode = \"full\" | \"report-only\";\n\nexport interface ReconcileOptions {\n /** The workstream whose registry rows we're reconciling. */\n workstream: string;\n /**\n * Override the tmux session name. Defaults to `mu-<workstream>`. Useful\n * for tests and for the rare case where a workstream's tmux session was\n * created with a non-default name.\n */\n tmuxSession?: string;\n /**\n * Which kind of pass to run. Default is `\"full\"` (the documented\n * mutating behaviour `mu agent list` has always had). See\n * `ReconcileMode` for the full per-mode contract.\n *\n * BREAKING: this replaces the previous `dryRun?: boolean` flag.\n * Migration: `dryRun: true` → `mode: \"report-only\"`; default\n * (`dryRun: false` / unset) → `mode: \"full\"`.\n */\n mode?: ReconcileMode;\n}\n\nexport interface ReconcileReport {\n /** Number of registry rows whose pane was gone. In `report-only` mode\n * this is the count of rows that WOULD have been pruned; in `full`\n * mode it's the count actually deleted. */\n prunedGhosts: number;\n /** Number of agents whose status was changed by scrollback detection.\n * Always 0 in `report-only` mode (status detection is skipped). */\n statusChanges: number;\n /** Panes in the workstream's tmux session that look like agents but\n * aren't in the registry. NOT auto-adopted. */\n orphans: TmuxPane[];\n /** Which mode this report was generated in. Lets callers switch their\n * output text (\"agents pruned\" vs \"would-be-pruned (suppressed)\")\n * without re-deriving from options. */\n mode: ReconcileMode;\n}\n\n/**\n * Pane commands that suggest \"this is an agent, surface it as an orphan.\"\n * 0.1.0 scope: pi only is detected, but the orphan list will surface\n * claude/codex panes too so the user can adopt them later.\n *\n * Also includes any env-overridden binary names (e.g. `MU_PI_COMMAND=pi-alt`\n * makes \"pi-alt\" agent-worthy) so externally-spawned panes running the\n * user's actual pi binary are still surfaced as orphans.\n */\nconst BASE_AGENT_CLIS: readonly string[] = [\"pi\", \"claude\", \"codex\"];\n\nfunction knownAgentCommands(): ReadonlySet<string> {\n const names = new Set<string>(BASE_AGENT_CLIS);\n for (const cli of BASE_AGENT_CLIS) {\n names.add(agentSdk.resolveCliCommand(cli));\n }\n return names;\n}\n\n/**\n * Build a recogniser closure that captures one snapshot of\n * knownAgentCommands() (which itself reads MU_<CLI>_COMMAND env vars).\n * Hoisted out of the orphan-surface loop so reconcile() reads each env\n * var at most once per pass instead of once per pane: dozens of panes\n * × three env vars per call = needless syscalls in a `mu state` poll\n * tick. Also pins the env-var snapshot for the loop, so test suites\n * that twiddle MU_PI_COMMAND in afterEach can't race the inner check.\n */\nfunction buildAgentPaneRecogniser(): (pane: TmuxPane) => boolean {\n const known = knownAgentCommands();\n return (pane) => known.has(pane.command);\n}\n\nexport async function reconcile(db: Db, opts: ReconcileOptions): Promise<ReconcileReport> {\n const sessionName = opts.tmuxSession ?? `mu-${opts.workstream}`;\n const mode: ReconcileMode = opts.mode ?? \"full\";\n const dbAgents = agentSdk.listAgents(db, { workstream: opts.workstream });\n const tmuxPanes = await listPanesInSession(sessionName);\n const tmuxByPaneId = new Map(tmuxPanes.map((p) => [p.paneId, p]));\n\n let prunedGhosts = 0;\n let statusChanges = 0;\n const orphans: TmuxPane[] = [];\n\n // 1. Prune ghosts (DB row references a pane that no longer exists).\n // `full` mode deletes (and therefore reaps); `report-only` counts\n // the would-be-prunes so callers can surface drift, but leaves the\n // row in place. Mid-spawn placeholder pane ids are protected by\n // the defensive skip that lets read paths use full mode safely.\n // Placeholder rows survive this step, but are split out so status\n // detection and orphan exclusion only reason about real tmux panes.\n const survivors: agentSdk.AgentRow[] = [];\n const pendingSurvivors: agentSdk.AgentRow[] = [];\n for (const agent of dbAgents) {\n if (agentSdk.isPendingPaneId(agent.paneId)) {\n pendingSurvivors.push(agent);\n } else if (tmuxByPaneId.has(agent.paneId)) {\n survivors.push(agent);\n } else {\n if (mode === \"full\") agentSdk.deleteAgent(db, agent.name, agent.workstreamName);\n prunedGhosts++;\n }\n }\n\n // 2. Detect status from scrollback for survivors. capturePane uses the\n // last 100 lines, which is the same window the detector operates on.\n //\n // `report-only` skips this entirely — status detection writes to\n // the DB (updateAgentStatus + refreshAgentTitle), and the\n // report-only contract is \"no mutation\".\n //\n // Full mode iterates real-pane survivors only. Mid-spawn\n // placeholders have no usable scrollback yet and were split out in\n // step 1, so no sentinel-aware branch belongs here.\n if (mode === \"full\") {\n for (const agent of survivors) {\n const scrollback = await capturePane(agent.paneId, { lines: 100 });\n const detected = detectPiStatus(scrollback);\n if (\n agentSdk.shouldOverwriteAgentStatus(agent.status, detected) &&\n detected !== agent.status\n ) {\n agentSdk.updateAgentStatus(db, agent.name, detected, agent.workstreamName);\n statusChanges++;\n }\n // ALWAYS refresh the pane title (even when status didn't change),\n // so that:\n // 1. Inner CLIs that self-set their pane title (pi, pi-meta, vim,\n // tmux's default 'host - dir') get overwritten with mu's\n // composed title.\n // 2. Task-ownership changes that happen between reconciles\n // (claim / release / close) re-propagate even if the status\n // detector didn't flip.\n // Best-effort: a tmux failure here never blocks the reconcile report.\n await agentSdk.refreshAgentTitle(db, agent.name, agent.workstreamName);\n }\n }\n\n // 3. Surface orphan panes. `looksLikeAgentPane` is conservative:\n // pane.command must be one we recognise as an agent CLI. A bash\n // pane the user spawned for their own use is never an orphan.\n // Pure read; runs in every mode.\n const dbPaneIds = new Set(survivors.map((a) => a.paneId));\n const looksLikeAgentPane = buildAgentPaneRecogniser();\n for (const pane of tmuxPanes) {\n if (dbPaneIds.has(pane.paneId)) continue;\n if (looksLikeAgentPane(pane)) orphans.push(pane);\n }\n\n return { prunedGhosts, statusChanges, orphans, mode };\n}\n","// mu — capture and list snapshots.\n\nimport { existsSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { CURRENT_SCHEMA_VERSION, type Db } from \"../db.js\";\nimport {\n type CaptureSnapshotResult,\n type ListSnapshotsOptions,\n type RawSnapshotRow,\n type SnapshotRow,\n gcMaxAgeDays,\n gcMaxCount,\n rowFromDb,\n snapshotsDir,\n} from \"./core.js\";\n\nexport function captureSnapshot(\n db: Db,\n label: string,\n workstream: string | null = null,\n): CaptureSnapshotResult {\n const dir = snapshotsDir(db);\n mkdirSync(dir, { recursive: true });\n\n const insert = db\n .prepare(\n \"INSERT INTO snapshots (workstream, label, db_path, schema_version, created_at) VALUES (?, ?, ?, ?, ?)\",\n )\n .run(workstream, label, \"\", CURRENT_SCHEMA_VERSION, new Date().toISOString());\n const id = Number(insert.lastInsertRowid);\n const dbPath = join(dir, `${id}.db`);\n\n try {\n db.prepare(\"UPDATE snapshots SET db_path = ? WHERE id = ?\").run(dbPath, id);\n if (existsSync(dbPath)) unlinkSync(dbPath);\n db.exec(`VACUUM INTO ${quoteSqlString(dbPath)}`);\n } catch (err) {\n db.prepare(\"DELETE FROM snapshots WHERE id = ?\").run(id);\n try {\n if (existsSync(dbPath)) unlinkSync(dbPath);\n } catch {\n // Ignore — we're already on the failure path.\n }\n throw err;\n }\n\n try {\n gcSnapshots(db);\n } catch {\n // Insurance, not version history: GC failure must not break the destructive verb.\n }\n\n return { id, dbPath };\n}\n\nfunction quoteSqlString(s: string): string {\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nexport function listSnapshots(db: Db, opts: ListSnapshotsOptions = {}): SnapshotRow[] {\n const conditions: string[] = [];\n const params: unknown[] = [];\n if (opts.workstream !== undefined) {\n conditions.push(\"(workstream = ? OR workstream IS NULL)\");\n params.push(opts.workstream);\n }\n const where = conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n const limit = opts.limit !== undefined ? `LIMIT ${Math.max(0, Math.floor(opts.limit))}` : \"\";\n const rows = db\n .prepare(`SELECT * FROM snapshots ${where} ORDER BY id DESC ${limit}`)\n .all(...params) as RawSnapshotRow[];\n return rows.map(rowFromDb);\n}\n\nexport function gcSnapshots(db: Db): { deletedRows: number; deletedFiles: number } {\n const keepLast = gcMaxCount();\n const cutoffDate = new Date(Date.now() - gcMaxAgeDays() * 24 * 60 * 60 * 1000).toISOString();\n const protectedIds = (\n db.prepare(`SELECT id FROM snapshots ORDER BY id DESC LIMIT ${keepLast}`).all() as Array<{\n id: number;\n }>\n ).map((r) => r.id);\n const placeholders = protectedIds.length > 0 ? protectedIds.map(() => \"?\").join(\",\") : \"NULL\";\n const victims = db\n .prepare(\n `SELECT id, db_path FROM snapshots WHERE id NOT IN (${placeholders}) OR created_at < ?`,\n )\n .all(...protectedIds, cutoffDate) as Array<{ id: number; db_path: string }>;\n\n if (victims.length === 0) return { deletedRows: 0, deletedFiles: 0 };\n\n let deletedFiles = 0;\n for (const v of victims) {\n try {\n if (existsSync(v.db_path)) {\n unlinkSync(v.db_path);\n deletedFiles += 1;\n }\n } catch {\n // ignore — orphan file is preferable to a half-completed GC\n }\n }\n\n const ids = victims.map((v) => v.id);\n const inList = ids.map(() => \"?\").join(\",\");\n const result = db.prepare(`DELETE FROM snapshots WHERE id IN (${inList})`).run(...ids);\n return { deletedRows: result.changes, deletedFiles };\n}\n","// mu — snapshot shared types, errors, paths, and row helpers.\n\nimport { statSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { CURRENT_SCHEMA_VERSION, type Db, defaultStateDir } from \"../db.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\n\nexport interface SnapshotRow {\n /** Operator-facing snapshot id. EXCEPTION to the no-surrogate-ids rule:\n * snapshots have no human-meaningful name; the id is what the\n * operator types in `mu undo --to <id>` / `mu snapshot show <id>`. */\n id: number;\n /** NULL for whole-DB snapshots (e.g. workstream destroy). */\n workstreamName: string | null;\n /** Human-readable operation label, e.g. \"task close design\". */\n label: string;\n /** Absolute path to the .db file on disk. */\n dbPath: string;\n /** schema_version at the moment of capture. */\n schemaVersion: number;\n /** ISO-8601 capture timestamp. */\n createdAt: string;\n}\n\nexport interface RawSnapshotRow {\n id: number;\n workstream: string | null;\n label: string;\n db_path: string;\n schema_version: number;\n created_at: string;\n}\n\nexport function rowFromDb(r: RawSnapshotRow): SnapshotRow {\n return {\n id: r.id,\n workstreamName: r.workstream,\n label: r.label,\n dbPath: r.db_path,\n schemaVersion: r.schema_version,\n createdAt: r.created_at,\n };\n}\n\nexport interface ListSnapshotsOptions {\n /** Filter to one workstream. NULL-workstream rows are also returned\n * when this is set, since they (workstream-destroy snapshots) span\n * every workstream including this one. */\n workstream?: string;\n /** Cap the number of rows returned. Default: no cap. */\n limit?: number;\n}\n\nexport interface CaptureSnapshotResult {\n id: number;\n dbPath: string;\n}\n\nexport interface RestoreSnapshotResult {\n id: number;\n /** The path the snapshot was copied to (the live DB path). */\n restoredTo: string;\n /** schema_version of the restored snapshot (== CURRENT_SCHEMA_VERSION\n * by virtue of having passed the version check). */\n schemaVersion: number;\n}\n\nexport class SnapshotNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"SnapshotNotFoundError\";\n constructor(public readonly snapshotId: number) {\n super(`no such snapshot: ${snapshotId}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List available snapshots\", command: \"mu snapshot list\" },\n {\n intent: \"Look one up directly\",\n command: `mu sql \"SELECT id, label, created_at FROM snapshots ORDER BY id DESC\"`,\n },\n ];\n }\n}\n\nexport class SnapshotVersionMismatchError extends Error implements HasNextSteps {\n override readonly name = \"SnapshotVersionMismatchError\";\n constructor(\n public readonly snapshotId: number,\n public readonly snapshotVersion: number,\n public readonly currentVersion: number,\n ) {\n const direction =\n snapshotVersion < currentVersion\n ? \"older — your DB has migrated past it\"\n : \"newer — written by a newer mu binary\";\n super(\n `snapshot ${snapshotId} is at schema v${snapshotVersion}; current DB is at v${currentVersion} (${direction}). mu does not auto-migrate snapshots; refusing restore.`,\n );\n }\n errorNextSteps(): NextStep[] {\n const olderSnapshot = this.snapshotVersion < this.currentVersion;\n return olderSnapshot\n ? [\n {\n intent: \"Pick a newer snapshot at the current schema\",\n command: `mu sql \"SELECT id, label, created_at FROM snapshots WHERE schema_version = ${this.currentVersion} ORDER BY id DESC\"`,\n },\n {\n intent: \"Inspect the stale snapshot read-only (snapshot is forensic; bypass mu)\",\n command: `sqlite3 <snapshot-path> \"SELECT * FROM tasks\"`,\n },\n ]\n : [\n {\n intent: \"Run mu with a newer binary that knows this schema\",\n command: \"npm install -g @martintrojer/mu@latest\",\n },\n ];\n }\n}\n\nexport class SnapshotFileMissingError extends Error implements HasNextSteps {\n override readonly name = \"SnapshotFileMissingError\";\n constructor(\n public readonly snapshotId: number,\n public readonly dbPath: string,\n ) {\n super(`snapshot ${snapshotId} row exists but file is missing: ${dbPath}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Drop the orphan row\",\n command: `mu sql \"DELETE FROM snapshots WHERE id = ${this.snapshotId}\"`,\n },\n { intent: \"List remaining snapshots\", command: \"mu snapshot list\" },\n ];\n }\n}\n\nconst DEFAULT_GC_MAX_COUNT = 100;\nconst DEFAULT_GC_MAX_AGE_DAYS = 14;\n\nexport function gcMaxCount(): number {\n const env = process.env.MU_SNAPSHOT_KEEP_LAST;\n if (env === undefined || env === \"\") return DEFAULT_GC_MAX_COUNT;\n const n = Number.parseInt(env, 10);\n if (!Number.isFinite(n) || n < 0) return DEFAULT_GC_MAX_COUNT;\n return n;\n}\n\nexport function gcMaxAgeDays(): number {\n const env = process.env.MU_SNAPSHOT_MAX_AGE_DAYS;\n if (env === undefined || env === \"\") return DEFAULT_GC_MAX_AGE_DAYS;\n const n = Number.parseInt(env, 10);\n if (!Number.isFinite(n) || n < 0) return DEFAULT_GC_MAX_AGE_DAYS;\n return n;\n}\n\nexport function snapshotsDir(db?: Db): string {\n if (db) {\n const livePath = (db as Db & { name: string }).name;\n if (livePath && livePath !== \":memory:\") {\n return join(dirname(livePath), \"snapshots\");\n }\n }\n return join(defaultStateDir(), \"snapshots\");\n}\n\nexport function isStaleVersion(row: { schemaVersion: number }): boolean {\n return row.schemaVersion !== CURRENT_SCHEMA_VERSION;\n}\n\nexport function snapshotFileSize(snapshot: SnapshotRow): number | null {\n try {\n return statSync(snapshot.dbPath).size;\n } catch {\n return null;\n }\n}\n","// mu — manual snapshot cleanup verbs.\n\nimport { existsSync, statSync, unlinkSync } from \"node:fs\";\nimport type { Db } from \"../db.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport { captureSnapshot } from \"./capture.js\";\nimport { listSnapshots } from \"./capture.js\";\nimport {\n type RawSnapshotRow,\n SnapshotNotFoundError,\n type SnapshotRow,\n gcMaxAgeDays,\n gcMaxCount,\n isStaleVersion,\n rowFromDb,\n snapshotFileSize,\n} from \"./core.js\";\n\nexport type PruneMode = \"gc\" | \"keep-last\" | \"older-than\" | \"stale-version\" | \"all\";\n\nexport interface PruneOptions {\n mode: PruneMode;\n keepLast?: number;\n olderThanDays?: number;\n dryRun?: boolean;\n}\n\nexport interface PruneResult {\n victims: SnapshotRow[];\n freedBytes: number;\n deletedRows: number;\n deletedFiles: number;\n safetyNetSnapshotId?: number;\n}\n\nexport class PruneOptionsInvalidError extends Error implements HasNextSteps {\n override readonly name = \"PruneOptionsInvalidError\";\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Show prune options\", command: \"mu snapshot prune --help\" },\n { intent: \"List snapshots\", command: \"mu snapshot list\" },\n ];\n }\n}\n\nexport function pruneSnapshots(db: Db, opts: PruneOptions): PruneResult {\n const mode = opts.mode;\n let victims: SnapshotRow[];\n let safetyNetSnapshotId: number | undefined;\n switch (mode) {\n case \"gc\": {\n victims = computeGcVictims(db);\n break;\n }\n case \"keep-last\": {\n if (opts.keepLast === undefined || !Number.isInteger(opts.keepLast) || opts.keepLast < 0) {\n throw new PruneOptionsInvalidError(\n `--keep-last requires a non-negative integer; got ${JSON.stringify(opts.keepLast)}`,\n );\n }\n victims = computeKeepLastVictims(db, opts.keepLast);\n break;\n }\n case \"older-than\": {\n if (\n opts.olderThanDays === undefined ||\n !Number.isFinite(opts.olderThanDays) ||\n opts.olderThanDays < 0\n ) {\n throw new PruneOptionsInvalidError(\n `--older-than requires a non-negative number of days; got ${JSON.stringify(opts.olderThanDays)}`,\n );\n }\n victims = computeOlderThanVictims(db, opts.olderThanDays);\n break;\n }\n case \"stale-version\": {\n victims = listSnapshots(db).filter(isStaleVersion);\n break;\n }\n case \"all\": {\n victims = listSnapshots(db);\n break;\n }\n default: {\n throw new PruneOptionsInvalidError(`unknown prune mode: ${JSON.stringify(mode)}`);\n }\n }\n\n let freedBytes = 0;\n for (const v of victims) {\n const sz = snapshotFileSize(v);\n if (sz !== null) freedBytes += sz;\n }\n\n if (opts.dryRun === true) {\n return { victims, freedBytes, deletedRows: 0, deletedFiles: 0 };\n }\n\n if (mode === \"all\") {\n const cap = captureSnapshot(db, \"snapshot prune --all (safety-net)\", null);\n safetyNetSnapshotId = cap.id;\n }\n\n if (victims.length === 0) {\n return {\n victims,\n freedBytes,\n deletedRows: 0,\n deletedFiles: 0,\n ...(safetyNetSnapshotId !== undefined ? { safetyNetSnapshotId } : {}),\n };\n }\n\n let deletedFiles = 0;\n for (const v of victims) {\n try {\n if (existsSync(v.dbPath)) {\n unlinkSync(v.dbPath);\n deletedFiles += 1;\n }\n } catch {\n // ignore — orphan file is preferable to a half-completed prune\n }\n }\n\n const ids = victims.map((v) => v.id);\n const inList = ids.map(() => \"?\").join(\",\");\n const result = db.prepare(`DELETE FROM snapshots WHERE id IN (${inList})`).run(...ids);\n return {\n victims,\n freedBytes,\n deletedRows: result.changes,\n deletedFiles,\n ...(safetyNetSnapshotId !== undefined ? { safetyNetSnapshotId } : {}),\n };\n}\n\nfunction computeGcVictims(db: Db): SnapshotRow[] {\n const keepLast = gcMaxCount();\n const cutoffDate = new Date(Date.now() - gcMaxAgeDays() * 24 * 60 * 60 * 1000).toISOString();\n const protectedIds = (\n db.prepare(`SELECT id FROM snapshots ORDER BY id DESC LIMIT ${keepLast}`).all() as Array<{\n id: number;\n }>\n ).map((r) => r.id);\n const placeholders = protectedIds.length > 0 ? protectedIds.map(() => \"?\").join(\",\") : \"NULL\";\n const rows = db\n .prepare(\n `SELECT * FROM snapshots WHERE id NOT IN (${placeholders}) OR created_at < ? ORDER BY id DESC`,\n )\n .all(...protectedIds, cutoffDate) as RawSnapshotRow[];\n return rows.map(rowFromDb);\n}\n\nfunction computeKeepLastVictims(db: Db, n: number): SnapshotRow[] {\n const rows = db\n .prepare(\n `SELECT * FROM snapshots\n WHERE id NOT IN (SELECT id FROM snapshots ORDER BY id DESC LIMIT ?)\n ORDER BY id DESC`,\n )\n .all(n) as RawSnapshotRow[];\n return rows.map(rowFromDb);\n}\n\nfunction computeOlderThanVictims(db: Db, days: number): SnapshotRow[] {\n const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();\n const rows = db\n .prepare(\"SELECT * FROM snapshots WHERE created_at < ? ORDER BY id DESC\")\n .all(cutoff) as RawSnapshotRow[];\n return rows.map(rowFromDb);\n}\n\nexport interface DeleteSnapshotResult {\n deleted: true;\n deletedFiles: 0 | 1;\n freedBytes: number;\n}\n\nexport function deleteSnapshot(db: Db, snapshotId: number): DeleteSnapshotResult {\n const row = db.prepare(\"SELECT * FROM snapshots WHERE id = ?\").get(snapshotId) as\n | RawSnapshotRow\n | undefined;\n if (!row) throw new SnapshotNotFoundError(snapshotId);\n let freedBytes = 0;\n let deletedFiles: 0 | 1 = 0;\n try {\n if (existsSync(row.db_path)) {\n freedBytes = statSync(row.db_path).size;\n unlinkSync(row.db_path);\n deletedFiles = 1;\n }\n } catch {\n // best-effort; the row goes either way\n }\n db.prepare(\"DELETE FROM snapshots WHERE id = ?\").run(snapshotId);\n return { deleted: true, deletedFiles, freedBytes };\n}\n","// mu — restore snapshots.\n\nimport {\n closeSync,\n copyFileSync,\n existsSync,\n openSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from \"node:fs\";\nimport Database from \"better-sqlite3\";\nimport { CURRENT_SCHEMA_VERSION, type Db } from \"../db.js\";\nimport { captureSnapshot } from \"./capture.js\";\nimport {\n type RawSnapshotRow,\n type RestoreSnapshotResult,\n SnapshotFileMissingError,\n SnapshotNotFoundError,\n SnapshotVersionMismatchError,\n} from \"./core.js\";\n\nexport function restoreSnapshot(db: Db, snapshotId: number): RestoreSnapshotResult {\n const row = db.prepare(\"SELECT * FROM snapshots WHERE id = ?\").get(snapshotId) as\n | RawSnapshotRow\n | undefined;\n if (!row) throw new SnapshotNotFoundError(snapshotId);\n if (!existsSync(row.db_path)) {\n throw new SnapshotFileMissingError(snapshotId, row.db_path);\n }\n if (row.schema_version !== CURRENT_SCHEMA_VERSION) {\n throw new SnapshotVersionMismatchError(snapshotId, row.schema_version, CURRENT_SCHEMA_VERSION);\n }\n\n const pre = captureSnapshot(db, `pre-restore of snapshot ${snapshotId}`, row.workstream);\n const preCreatedAt =\n (\n db.prepare(\"SELECT created_at FROM snapshots WHERE id = ?\").get(pre.id) as\n | { created_at: string }\n | undefined\n )?.created_at ?? new Date().toISOString();\n\n const livePath = (db as Db & { name: string }).name;\n if (!livePath || livePath === \":memory:\") {\n throw new Error(\n `restoreSnapshot: refusing to restore over a non-file DB handle (path=${JSON.stringify(livePath)})`,\n );\n }\n\n db.close();\n\n const tmpPath = `${livePath}.restore-${snapshotId}.tmp`;\n copyFileSync(row.db_path, tmpPath);\n try {\n const fd = openSync(tmpPath, \"r+\");\n try {\n writeSync(fd, Buffer.alloc(0), 0, 0, 0);\n } finally {\n closeSync(fd);\n }\n } catch {\n // ignore — fsync is best-effort here\n }\n renameSync(tmpPath, livePath);\n\n for (const sidecar of [`${livePath}-wal`, `${livePath}-shm`]) {\n if (existsSync(sidecar)) {\n try {\n unlinkSync(sidecar);\n } catch {\n // ignore — sqlite will recreate on next open\n }\n }\n }\n\n const tmp = new Database(livePath);\n try {\n tmp.pragma(\"foreign_keys = ON\");\n tmp\n .prepare(\n \"INSERT OR IGNORE INTO snapshots (id, workstream, label, db_path, schema_version, created_at) VALUES (?, ?, ?, ?, ?, ?)\",\n )\n .run(\n pre.id,\n row.workstream,\n `pre-restore of snapshot ${snapshotId}`,\n pre.dbPath,\n CURRENT_SCHEMA_VERSION,\n preCreatedAt,\n );\n } finally {\n tmp.close();\n }\n\n return {\n id: snapshotId,\n restoredTo: livePath,\n schemaVersion: row.schema_version,\n };\n}\n","// mu — task graph shared row-shape helpers.\n//\n// This module owns the snake_case → camelCase task/note mappings and\n// surrogate-id resolution helpers consumed by the task SDK cluster.\n// It stays below the public hub (`src/tasks.ts`): cluster files import\n// from this file or sibling files, never from the hub they're re-exported\n// through.\n\nimport { type Db, tryResolveWorkstreamId } from \"../db.js\";\nimport type { TaskStatus } from \"./status.js\";\n\nexport interface TaskRow {\n /** Per-workstream-unique TEXT name. The operator-facing identifier. */\n name: string;\n /** Foreign-name reference to the owning workstream. */\n workstreamName: string;\n title: string;\n status: TaskStatus;\n impact: number;\n effortDays: number;\n /** Foreign-name reference to the owning agent (NULL when unowned). */\n ownerName: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface TaskNoteRow {\n author: string | null;\n content: string;\n createdAt: string;\n}\n\nexport interface RawTaskRow {\n /** Surrogate id (v5). */\n id: number;\n local_id: string;\n /** Joined from workstreams.name. */\n workstream: string;\n title: string;\n status: string;\n impact: number;\n effort_days: number;\n /** Joined from agents.name via owner_id. NULL when unowned. */\n owner: string | null;\n created_at: string;\n updated_at: string;\n}\n\nexport interface RawTaskNoteRow {\n author: string | null;\n content: string;\n created_at: string;\n}\n\n// SELECT clause for v5 task reads. Joins workstreams + agents to expose\n// the operator-facing names as `workstream` and `owner`. Used by every\n// read path so callers don't see surrogate ids.\nexport const SELECT_TASK_COLS = `\n t.id AS id,\n t.local_id AS local_id,\n ws.name AS workstream,\n t.title AS title,\n t.status AS status,\n t.impact AS impact,\n t.effort_days AS effort_days,\n ag.name AS owner,\n t.created_at AS created_at,\n t.updated_at AS updated_at\n`;\n\nexport const TASK_FROM_JOIN = `\n FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id\n LEFT JOIN agents ag ON ag.id = t.owner_id\n`;\n\nexport const SELECT_NOTE_COLS = `\n n.author AS author,\n n.content AS content,\n n.created_at AS created_at\n`;\n\nexport function rowFromDb(row: RawTaskRow): TaskRow {\n return {\n name: row.local_id,\n workstreamName: row.workstream,\n title: row.title,\n status: row.status as TaskStatus,\n impact: row.impact,\n effortDays: row.effort_days,\n ownerName: row.owner,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport function noteFromDb(row: RawTaskNoteRow): TaskNoteRow {\n return {\n author: row.author,\n content: row.content,\n createdAt: row.created_at,\n };\n}\n\n/** Look up a task by local_id across every workstream. Returns the\n * first match (sorted by workstream name for determinism). Used by\n * edge verbs (addBlockEdge / reparentTask) to resolve a blocker so a\n * cross-workstream blocker can surface `CrossWorkstreamEdgeError`\n * rather than the less-actionable `TaskNotFoundError`. NOT for\n * operator-facing reads — use `getTask(db, localId, workstream)`\n * for those. */\nexport function lookupTaskAnyWorkstream(db: Db, localId: string): TaskRow | undefined {\n const row = db\n .prepare(\n `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} WHERE t.local_id = ? ORDER BY ws.name LIMIT 1`,\n )\n .get(localId) as RawTaskRow | undefined;\n return row ? rowFromDb(row) : undefined;\n}\n\n/** Resolve a (workstream, localId) pair to the surrogate task id.\n * Returns null on miss. */\nexport function taskIdFor(db: Db, localId: string, workstream: string): number | null {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return null;\n const row = db\n .prepare(\"SELECT id FROM tasks WHERE workstream_id = ? AND local_id = ?\")\n .get(wsId, localId) as { id: number } | undefined;\n return row ? row.id : null;\n}\n\n/**\n * Bump `tasks.updated_at` on a surrogate task id. Used by every write\n * that mutates a child row (notes, edges) without touching the task\n * row itself, so `mu task list --sort recency` reflects 'last write of\n * any kind' rather than only 'last status/field change'\n * (task_updatedat_not_bumped_by_reparent). Status changes, field\n * updates, and claim/release already update `updated_at` directly in\n * their own UPDATE statements; don't double-bump from here.\n *\n * Always called inside the same transaction as the child-row mutation\n * so the bump rolls back together with the mutation on error.\n */\nexport function touchTask(db: Db, taskId: number, now?: string): void {\n db.prepare(\"UPDATE tasks SET updated_at = ? WHERE id = ?\").run(\n now ?? new Date().toISOString(),\n taskId,\n );\n}\n","// Shared workspace staleness threshold + predicate.\n//\n// The TUI Workspaces card originally owned the magic number (red bucket\n// at ≥10 commits behind main). Dispatch-time warnings need the same\n// definition without importing ink/react code, so the threshold lives here.\n\nexport const WORKSPACE_STALE_THRESHOLD = 10;\n\nexport function isWorkspaceStale(behind: number | null | undefined): boolean {\n return behind !== null && behind !== undefined && behind >= WORKSPACE_STALE_THRESHOLD;\n}\n","// mu — shared workspace row shapes, path helpers, and typed errors.\n\nimport { join } from \"node:path\";\nimport { defaultStateDir } from \"../db.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport type { VcsBackendName } from \"../vcs.js\";\n\nexport interface WorkspaceRow {\n agentName: string;\n workstreamName: string;\n backend: VcsBackendName;\n path: string;\n parentRef: string | null;\n createdAt: string;\n /** How many commits the workspace's parent_ref is behind the project's\n * default branch HEAD, as of the last time the workspace's local refs\n * cache was updated. Undefined when not yet computed (the listWorkspaces\n * fast path leaves it unset; call decorateWithStaleness to populate).\n * Null when staleness was queried but cannot be computed (no main found,\n * none-backend, missing parent_ref, command failure). */\n commitsBehindMain?: number | null;\n /** True when the workspace has uncommitted / unstaged / untracked-not-\n * ignored files, as observed by the backend's `listDirtyFiles`.\n * Undefined when not yet computed (the listWorkspaces fast path leaves\n * it unset; call decorateWithDirty to populate). Null when the dirty\n * check could not be performed (backend command failure). For jj /\n * none backends — which have no operator-visible \"dirty\" concept —\n * this is always false (their listDirtyFiles returns []). */\n dirty?: boolean | null;\n}\n\nexport interface RawWorkspaceRow {\n /** Joined from agents.name. */\n agent: string;\n /** Joined from workstreams.name. */\n workstream: string;\n backend: string;\n path: string;\n parent_ref: string | null;\n created_at: string;\n}\n\n// SELECT clause that joins vcs_workspaces back to operator-facing\n// agent + workstream names (v5 stores surrogate ids; the JS row shape\n// is operator-facing TEXT names).\nexport const SELECT_WS_COLS = `\n ag.name AS agent,\n ws.name AS workstream,\n v.backend AS backend,\n v.path AS path,\n v.parent_ref AS parent_ref,\n v.created_at AS created_at\n`;\n\nexport const WS_FROM_JOIN = `FROM vcs_workspaces v\n JOIN agents ag ON ag.id = v.agent_id\n JOIN workstreams ws ON ws.id = v.workstream_id`;\n\nexport function rowFromDb(row: RawWorkspaceRow): WorkspaceRow {\n return {\n agentName: row.agent,\n workstreamName: row.workstream,\n backend: row.backend as VcsBackendName,\n path: row.path,\n parentRef: row.parent_ref,\n createdAt: row.created_at,\n };\n}\n\nexport class WorkspaceExistsError extends Error implements HasNextSteps {\n override readonly name = \"WorkspaceExistsError\";\n constructor(public readonly agent: string) {\n super(`workspace already exists for agent: ${agent}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Show its on-disk path\", command: `mu workspace path ${this.agent}` },\n {\n intent: \"Free it (optionally --commit pending changes first)\",\n command: `mu workspace free ${this.agent} (--commit to commit pending changes first)`,\n },\n {\n intent: \"Then re-create with a different backend or base ref\",\n command: `mu workspace create ${this.agent} --backend <jj|sl|git|none> --from <ref>`,\n },\n ];\n }\n}\n\nexport class WorkspaceNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"WorkspaceNotFoundError\";\n constructor(public readonly agent: string) {\n super(`no workspace for agent: ${agent}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List workspaces in current workstream\", command: \"mu workspace list\" },\n { intent: \"List workspaces across all workstreams\", command: \"mu workspace list --all\" },\n {\n intent: \"Create one for this agent\",\n command: `mu workspace create ${this.agent}`,\n },\n ];\n }\n}\n\n/**\n * Thrown by createWorkspace when the on-disk path it would create is\n * already occupied. Distinct from WorkspaceExistsError (which is about\n * the DB row) so the recovery is clear: the dir is orphaned (no DB\n * row points at it) and needs cleanup.\n *\n * Maps to exit code 4 (conflict).\n */\nexport class WorkspacePathNotEmptyError extends Error implements HasNextSteps {\n override readonly name = \"WorkspacePathNotEmptyError\";\n constructor(\n public readonly agent: string,\n public readonly workstream: string,\n public readonly workspacePath: string,\n ) {\n super(\n `workspace dir already on disk for agent ${agent} (${workspacePath}); refusing to overwrite`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"List every orphan workspace dir in this workstream\",\n command: `mu workspace orphans -w ${this.workstream}`,\n },\n {\n intent: \"If the dir is intentional (orphan from older mu), free it via mu first\",\n command: `mu workspace free ${this.agent} -w ${this.workstream} # also runs backend cleanup if a row remains`,\n },\n {\n intent: \"Or delete it manually if the registry has no row\",\n command: `rm -rf ${this.workspacePath}`,\n },\n {\n intent: \"For git workspaces specifically: also prune the worktree registration\",\n command: \"cd <project-root> && git worktree prune\",\n },\n ];\n }\n}\n\n/**\n * Thrown by createWorkspace when the resolved projectRoot is the\n * user's $HOME.\n *\n * Maps to exit code 4 (conflict).\n */\nexport class HomeDirAsProjectRootError extends Error implements HasNextSteps {\n override readonly name = \"HomeDirAsProjectRootError\";\n constructor(\n public readonly agent: string,\n public readonly workstream: string,\n public readonly homeDir: string,\n ) {\n super(\n `refusing to create workspace with projectRoot=$HOME (${homeDir}); a recursive copy/clone of your home directory is almost never what you want`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Re-run from inside a real project directory\",\n command: `cd <your-project> && mu workspace create ${this.agent} -w ${this.workstream}`,\n },\n {\n intent: \"Or pass --project-root explicitly\",\n command: `mu workspace create ${this.agent} -w ${this.workstream} --project-root <your-project>`,\n },\n ];\n }\n}\n\n/**\n * Compose the canonical on-disk path for an agent's workspace. Used by\n * createWorkspace and reachable from `mu workspace path` so the user\n * can `cd $(mu workspace path foo)` even before the directory exists.\n */\nexport function workspacePath(workstream: string, agent: string): string {\n return join(defaultStateDir(), \"workspaces\", workstream, agent);\n}\n\n/** Root dir for a workstream's workspaces — the parent of all\n * per-agent workspace dirs. Used by listWorkspaceOrphans to scan\n * the filesystem. */\nexport function workspacesRoot(workstream: string): string {\n return join(defaultStateDir(), \"workspaces\", workstream);\n}\n\nexport interface WorkspaceStaleness {\n agentName: string;\n workstreamName: string;\n commitsBehindMain: number | null;\n isStale: boolean;\n}\n","// mu — workspace registry CRUD and free/list/commits helpers.\n\nimport { existsSync, rmSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { resolve } from \"node:path\";\nimport { AgentNotFoundError } from \"../agents/errors.js\";\nimport { type Db, tryResolveWorkstreamId } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport {\n type CommitSummary,\n type RebaseResult,\n type VcsBackend,\n type VcsBackendName,\n backendByName,\n detectBackend,\n} from \"../vcs.js\";\nimport {\n HomeDirAsProjectRootError,\n type RawWorkspaceRow,\n SELECT_WS_COLS,\n WS_FROM_JOIN,\n WorkspaceExistsError,\n WorkspaceNotFoundError,\n WorkspacePathNotEmptyError,\n type WorkspaceRow,\n rowFromDb,\n workspacePath,\n} from \"./core.js\";\n\nexport interface CreateWorkspaceOptions {\n agent: string;\n workstream: string;\n /** Project root to branch from. Defaults to the current working\n * directory (the `mu` invocation site, which is normally what the\n * user wants). */\n projectRoot?: string;\n /** Override backend detection. Default: walk `detectBackend`.\n * Accepts either a name (\"jj\" / \"sl\" / \"git\" / \"none\") OR a\n * pre-built `VcsBackend` object — the object form lets tests inject\n * a fresh fake backend without mutating the exported singletons. */\n backend?: VcsBackendName | VcsBackend;\n /** Optional ref to base the workspace on. Backend-specific. */\n parentRef?: string;\n /** INTERNAL. When false, suppress the `workspace create` system\n * event. Used by `recreateWorkspace` so the audit trail records\n * ONE atomic `workspace recreate` line instead of separate\n * free + create entries. Defaults to true. */\n _suppressEvent?: boolean;\n}\n\n/**\n * Create a fresh workspace for an agent. Allocates the on-disk\n * directory, records the row, emits a system event. Idempotent ONLY\n * to the extent that the row check is up-front; if the row exists\n * we throw `WorkspaceExistsError` rather than silently re-using a\n * possibly-stale on-disk state. Callers should `freeWorkspace` first.\n */\nexport async function createWorkspace(db: Db, opts: CreateWorkspaceOptions): Promise<WorkspaceRow> {\n if (getWorkspaceForAgent(db, opts.agent, opts.workstream) !== undefined) {\n throw new WorkspaceExistsError(opts.agent);\n }\n\n const projectRoot = opts.projectRoot ?? process.cwd();\n\n // Footgun guard: refuse projectRoot=$HOME. resolve() normalises a\n // trailing slash, `.`, symlinks-in-name, etc., so `cd && mu workspace\n // create ...` and `--project-root ~/` are all blocked the same way.\n // Direct children of $HOME (e.g. ~/Documents) are NOT blocked —\n // that would be overreach. See snap_dogfood Finding 4.\n if (resolve(projectRoot) === resolve(homedir())) {\n throw new HomeDirAsProjectRootError(opts.agent, opts.workstream, homedir());\n }\n\n const backend =\n opts.backend === undefined\n ? await detectBackend(projectRoot)\n : typeof opts.backend === \"string\"\n ? backendByName(opts.backend)\n : opts.backend;\n const path = workspacePath(opts.workstream, opts.agent);\n\n // Surface the dir-already-exists case as a typed error WITH actionable\n // nextSteps before we delegate to the backend (which throws a bare\n // Error). This is the orphan-from-older-mu case: a workspace dir from\n // before the cccba88 close-refuses fix landed; it has no DB row.\n if (existsSync(path)) {\n throw new WorkspacePathNotEmptyError(opts.agent, opts.workstream, path);\n }\n\n const createOpts: { projectRoot: string; workspacePath: string; parentRef?: string } = {\n projectRoot,\n workspacePath: path,\n };\n if (opts.parentRef !== undefined) createOpts.parentRef = opts.parentRef;\n\n // Wrap the backend's on-disk side effect in a cleanup guard. If the\n // backend throws mid-way (cp -a hits a DRM-protected file, git\n // worktree add fails after creating the dir, an interrupt during a\n // long copy), the partial dir would otherwise be left behind with\n // no DB row — exactly the failure mode from snap_dogfood Finding 4,\n // which then blocked subsequent `mu workspace create` calls with\n // WorkspacePathNotEmptyError. Best-effort: if the rm itself fails,\n // surface the original error and let the user clean up via\n // `mu workspace orphans`.\n let created: Awaited<ReturnType<typeof backend.createWorkspace>>;\n try {\n created = await backend.createWorkspace(createOpts);\n } catch (err) {\n try {\n rmSync(path, { recursive: true, force: true });\n } catch {\n // best-effort; original error wins\n }\n throw err;\n }\n\n // Roll back the on-disk + VCS-registry side effect if the DB row\n // insert fails (FK violation, schema constraint, sqlite_busy timeout).\n const now = new Date().toISOString();\n try {\n const wsId = tryResolveWorkstreamId(db, opts.workstream);\n if (wsId === null) {\n throw new Error(\n `createWorkspace: workstream not found: ${opts.workstream} (insertAgent should have ensured this)`,\n );\n }\n const agentRow = db\n .prepare(\"SELECT id FROM agents WHERE name = ? AND workstream_id = ? LIMIT 1\")\n .get(opts.agent, wsId) as { id: number } | undefined;\n if (!agentRow) {\n throw new AgentNotFoundError(opts.agent, opts.workstream);\n }\n db.prepare(\n `INSERT INTO vcs_workspaces (agent_id, workstream_id, backend, path, parent_ref, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n ).run(agentRow.id, wsId, backend.name, path, created.parentRef, now);\n } catch (err) {\n await backend.freeWorkspace({ workspacePath: path, commit: false }).catch(() => {});\n throw err;\n }\n\n if (opts._suppressEvent !== true) {\n emitEvent(\n db,\n opts.workstream,\n `workspace create ${opts.agent} (backend=${backend.name}, path=${path}${created.parentRef ? `, parent=${created.parentRef.slice(0, 12)}` : \"\"})`,\n );\n }\n\n return {\n agentName: opts.agent,\n workstreamName: opts.workstream,\n backend: backend.name,\n path,\n parentRef: created.parentRef,\n createdAt: now,\n };\n}\n\nexport function getWorkspaceForAgent(\n db: Db,\n agent: string,\n workstream: string,\n): WorkspaceRow | undefined {\n // v5: agents.name is per-workstream unique — the lookup must scope\n // by (workstream, agent) so a same-named worker elsewhere can't be\n // resolved instead.\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return undefined;\n const row = db\n .prepare(`SELECT ${SELECT_WS_COLS} ${WS_FROM_JOIN} WHERE ag.name = ? AND v.workstream_id = ?`)\n .get(agent, wsId) as RawWorkspaceRow | undefined;\n return row ? rowFromDb(row) : undefined;\n}\n\nexport function listWorkspaces(db: Db, workstream?: string): WorkspaceRow[] {\n if (workstream === undefined) {\n const rows = db\n .prepare(`SELECT ${SELECT_WS_COLS} ${WS_FROM_JOIN} ORDER BY ws.name, ag.name`)\n .all() as RawWorkspaceRow[];\n return rows.map(rowFromDb);\n }\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(`SELECT ${SELECT_WS_COLS} ${WS_FROM_JOIN} WHERE v.workstream_id = ? ORDER BY ag.name`)\n .all(wsId) as RawWorkspaceRow[];\n return rows.map(rowFromDb);\n}\n\nexport interface FreeWorkspaceOptions {\n /** If true, attempt to commit pending changes before tearing down.\n * Backend-specific; see VcsBackend.freeWorkspace. */\n commit?: boolean;\n /** INTERNAL. When false, suppress the `workspace free` system\n * event AND skip the pre-mutation snapshot capture. Used by\n * `recreateWorkspace` so the audit trail records ONE atomic\n * `workspace recreate` line and one snapshot for the whole\n * free+create cycle. Defaults to true. */\n _suppressEvent?: boolean;\n}\n\nexport interface FreeWorkspaceResult {\n /** The committed ref, when `commit` was true and there was something\n * to commit. */\n committedRef?: string;\n /** True iff the on-disk path was actually removed. */\n removed: boolean;\n /** True iff the DB row was actually deleted. */\n rowDeleted: boolean;\n}\n\n/**\n * Tear down an agent's workspace. Calls the backend to remove the\n * on-disk directory (with optional auto-commit), then DELETEs the row.\n * Idempotent on a missing workspace (returns all-false).\n */\nexport async function freeWorkspace(\n db: Db,\n agent: string,\n opts: FreeWorkspaceOptions & { workstream: string },\n): Promise<FreeWorkspaceResult> {\n const row = getWorkspaceForAgent(db, agent, opts.workstream);\n if (!row) return { removed: false, rowDeleted: false };\n\n // Pre-mutation snapshot — the row deletion + on-disk teardown is\n // not recoverable from history. Snapshot is DB-only (the worktree\n // is not rolled back; that's the design's tmux/disk honesty point).\n // recreateWorkspace owns its own snapshot label so the undo trail\n // shows one `workspace recreate` step, not free + create.\n if (opts._suppressEvent !== true) {\n captureSnapshot(db, `workspace free ${agent}`, row.workstreamName);\n }\n\n const backend = backendByName(row.backend);\n const result = await backend.freeWorkspace({\n workspacePath: row.path,\n commit: opts.commit ?? false,\n });\n\n // Resolve to surrogate ids scoped by the row's workstream.\n const wsIdForDel = tryResolveWorkstreamId(db, row.workstreamName);\n const del =\n wsIdForDel === null\n ? { changes: 0 }\n : db\n .prepare(\n `DELETE FROM vcs_workspaces\n WHERE agent_id = (SELECT id FROM agents WHERE name = ? AND workstream_id = ?)\n AND workstream_id = ?`,\n )\n .run(agent, wsIdForDel, wsIdForDel);\n if (opts._suppressEvent !== true) {\n emitEvent(\n db,\n row.workstreamName,\n `workspace free ${agent} (backend=${row.backend}, path=${row.path}${result.committedRef ? `, committed=${result.committedRef.slice(0, 12)}` : \"\"})`,\n );\n }\n\n return {\n removed: result.removed,\n rowDeleted: del.changes > 0,\n ...(result.committedRef !== undefined ? { committedRef: result.committedRef } : {}),\n };\n}\n\nexport interface RefreshWorkspaceOptions {\n agent: string;\n workstream: string;\n /** Optional override of the rebase target. When undefined, the\n * backend resolves its own default (origin/HEAD for git,\n * `trunk()` for jj/sl). */\n fromRef?: string;\n}\n\nexport interface RefreshWorkspaceResult extends RebaseResult {\n /** Backend name (mirrors the row) so a JSON consumer doesn't have\n * to look up the workspace separately to know what kind of rebase\n * it just got. */\n vcs: VcsBackendName;\n /** The workspace's on-disk path (so the JSON shape is self-contained\n * for piping to a downstream `jq` script). */\n workspacePath: string;\n}\n\n/**\n * Refresh an agent's workspace by rebasing it onto `fromRef` (or the\n * backend's default base). The agent / pane are NOT touched — only\n * the on-disk working copy moves. Bumps the row's `created_at` proxy\n * via the emit event; the row itself is otherwise unchanged.\n */\nexport async function refreshWorkspace(\n db: Db,\n opts: RefreshWorkspaceOptions,\n): Promise<RefreshWorkspaceResult> {\n const row = getWorkspaceForAgent(db, opts.agent, opts.workstream);\n if (!row) throw new WorkspaceNotFoundError(opts.agent);\n const backend = backendByName(row.backend);\n const result = await backend.rebaseTo(row.path, opts.fromRef);\n emitEvent(\n db,\n row.workstreamName,\n `workspace refresh ${opts.agent} (backend=${row.backend}, fromRef=${result.fromRef}, replayed=${result.replayed.length})`,\n );\n return { ...result, vcs: row.backend, workspacePath: row.path };\n}\n\nexport interface ListCommitsOptions {\n workstream: string;\n /** Optional override of the base ref (default: the workspace row's\n * parent_ref). Useful when the operator wants to ask \"what's on\n * top of an arbitrary ref\" without re-creating the workspace. */\n since?: string;\n}\n\nexport interface ListCommitsResult {\n /** Backend name (mirrors the row). */\n vcs: VcsBackendName;\n /** The base ref actually used. */\n baseRef: string;\n /** The commits, oldest-first. Empty when the workspace is exactly\n * at baseRef. */\n commits: CommitSummary[];\n /** The workspace's on-disk path (so JSON consumers don't have to\n * call `mu workspace path` separately). */\n workspacePath: string;\n}\n\n/**\n * List commits the workspace has on top of its `parent_ref` (or the\n * `--since` override), oldest-first. Promotes the dogfood-painful\n * cd $(mu workspace path X) && git log <base>..HEAD\n * incantation into a typed verb that knows the workspace's\n * recorded fork point.\n */\nexport async function listCommitsForWorkspace(\n db: Db,\n agent: string,\n opts: ListCommitsOptions,\n): Promise<ListCommitsResult> {\n const row = getWorkspaceForAgent(db, agent, opts.workstream);\n if (!row) throw new WorkspaceNotFoundError(agent);\n const backend = backendByName(row.backend);\n const baseRef = opts.since ?? row.parentRef;\n if (baseRef === null || baseRef.length === 0) {\n if (row.backend === \"none\") {\n await backend.commitsSinceBase(row.path, \"\");\n }\n throw new Error(`workspace ${agent} has no recorded parent_ref; pass --since <ref> explicitly`);\n }\n const commits = await backend.commitsSinceBase(row.path, baseRef);\n return { vcs: row.backend, baseRef, commits, workspacePath: row.path };\n}\n\n/**\n * \"Is this workspace safe to silently free on agent close?\" — i.e.\n * does it have ZERO uncommitted changes AND ZERO commits since its\n * fork point. Used by closeAgent to auto-free clean workspaces.\n */\nexport async function isWorkspaceClean(row: WorkspaceRow): Promise<boolean> {\n const backend = backendByName(row.backend);\n let clean: boolean;\n try {\n clean = await backend.isClean(row.path);\n } catch {\n return false;\n }\n if (!clean) return false;\n if (row.backend === \"none\") return true;\n if (row.parentRef === null || row.parentRef.length === 0) return false;\n try {\n const commits = await backend.commitsSinceBase(row.path, row.parentRef);\n return commits.length === 0;\n } catch {\n return false;\n }\n}\n","// mu — spawnAgent + supporting helpers (resolveCliCommand,\n// awaitSpawnLiveness, createOrReusePane, defaultSpawnLivenessMs,\n// prestageWorkspace, finalizeAgentRow, rollbackSpawn).\n//\n// spawnAgent's flow is documented inline. The interesting bit is the\n// `--workspace` cycle (workspace row FKs agent.name; agent row needs\n// pane_id which needs workspace path as cwd) — see prestageWorkspace.\n//\n// Extracted from src/agents.ts as part of refactor_split_large_src_files.\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport {\n type AgentRow,\n deleteAgent,\n getAgent,\n insertAgent,\n isValidAgentName,\n pendingPaneIdFor,\n refreshAgentTitle,\n} from \"../agents.js\";\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { type NextStep, isJsonMode } from \"../output.js\";\nimport {\n capturePane,\n enableMuPaneBordersForPane,\n killPane,\n listWindows,\n newSessionWithPane,\n newWindow,\n paneExists,\n sessionExists,\n setPaneTitle,\n sleep,\n splitWindow,\n} from \"../tmux.js\";\nimport type { VcsBackendName } from \"../vcs.js\";\nimport { createWorkspace, freeWorkspace } from \"../workspace.js\";\nimport {\n AgentDiedOnSpawnError,\n AgentExistsError,\n AgentSpawnCliNotFoundError,\n AgentSpawnStartupError,\n} from \"./errors.js\";\n\n/**\n * Smallest-unused-suffix naming convention for agent names: a role\n * (lowercase alpha + digits) followed by `-<n>` where `<n>` is one or\n * more digits. Examples that pass: `worker-1`, `reviewer-2`, `scout-12`.\n * Examples that warn: `worker-tests`, `alice`, `db-leader`, `x-y-1`.\n *\n * Source: feedback ws task fb_agent_naming_convention. The dogfood report\n * was operators (LLMs included) drifting to descriptive names like\n * `worker-tests` after a few spawns, breaking the convention silently.\n * The hint surfaced by `maybeWarnNonConventionalAgentName` is a lint, not\n * a rule — `isValidAgentName` still accepts the broader\n * `^[a-z][a-z0-9_-]{0,31}$` shape.\n */\nconst AGENT_NAME_CONVENTION_RE = /^[a-z][a-z0-9]*(?:-[0-9]+)$/;\n\n/**\n * Stderr lint when an agent name doesn't match the smallest-unused-suffix\n * convention. Mirrors the slugify-truncation stderr hint in cmdTaskAdd\n * (commit 28a13af): stderr-only, exit 0, suppressed under --json so\n * machine consumers stay clean. The spawn already succeeded by the time\n * this runs — we're just nudging the operator toward `<role>-<n>`.\n */\nfunction maybeWarnNonConventionalAgentName(name: string): void {\n if (AGENT_NAME_CONVENTION_RE.test(name)) return;\n if (isJsonMode()) return;\n process.stderr.write(\n `hint: agent name \"${name}\" does not match the smallest-unused-suffix convention (<role>-<n>; e.g. worker-1, reviewer-2). Accepted; consider renaming if you spawn additional workers.\\n`,\n );\n}\n\n/**\n * Resolve the actual executable to launch in an agent's pane for a given\n * `cli`. Honours the env var `MU_<UPPER_CLI>_COMMAND` (e.g. `MU_PI_COMMAND=\n * pi-alt` makes `--cli pi` actually exec `pi-alt`). Falls back to the cli\n * name itself, which is what users expect when their `pi` binary is on\n * `$PATH` under that exact name.\n *\n * Used by `spawnAgent` to pick the spawned command, and by reconcile's\n * orphan detector so externally-spawned panes running the resolved binary\n * are still recognised as agents.\n */\nexport function resolveCliCommand(cli: string): string {\n const envName = envVarNameForCli(cli);\n const override = process.env[envName];\n return override && override.trim() !== \"\" ? override : cli;\n}\n\n/**\n * Compute the `MU_<UPPER_CLI>_COMMAND` env var name mu consults when\n * resolving `--cli <key>`. Hyphens in the cli key become underscores\n * (env var names can't contain `-`); this matches the operator-aliases\n * convention documented in the mu skill (e.g. `--cli pi-meta` →\n * `MU_PI_META_COMMAND`).\n */\nexport function envVarNameForCli(cli: string): string {\n return `MU_${cli.toUpperCase().replace(/-/g, \"_\")}_COMMAND`;\n}\n\n/**\n * Resolve `--cli <key>` to its actual command string AND tell the\n * caller whether the resolution came from a `MU_<UPPER_CLI>_COMMAND`\n * env var or fell through to the bare cli name. The CLI uses this to\n * surface env-var attribution in the spawn-success line so config\n * issues are visible without `mu agent show`\n * (fb_agent_spawn_no_validation, part C).\n */\nexport function resolveCliCommandWithSource(cli: string): {\n command: string;\n envVar: string;\n resolvedFromEnv: boolean;\n} {\n const envVar = envVarNameForCli(cli);\n const override = process.env[envVar];\n if (override !== undefined && override.trim() !== \"\") {\n return { command: override, envVar, resolvedFromEnv: true };\n }\n return { command: cli, envVar, resolvedFromEnv: false };\n}\n\n// ─── Pre-flight PATH check (fb_agent_spawn_no_validation, part A) ──\n//\n// Operator typo'd `mu agent spawn worker-1 --cli pi-meta` on a host\n// where `pi-meta` wasn't on PATH; the spawn appeared to succeed but\n// the pane died with `command not found` and the existing 1.5s\n// liveness check sometimes missed it (shells stay alive past a failed\n// exec). Pre-flight the PATH resolution so a typo never:\n// 1. creates an orphan workspace dir\n// 2. creates an orphan tmux pane\n// 3. inserts a half-spawned DB row\n// The check fires AFTER `resolveCliCommand` so `MU_<UPPER>_COMMAND`\n// overrides are honoured AND the diagnostic mentions the env var by\n// name. Hookable via `setCommandResolverForTests` so the test suite\n// can simulate \"binary present\" / \"binary absent\" without polluting\n// the real PATH.\n\nexport interface CommandResolutionResult {\n ok: boolean;\n /** First whitespace-separated token of the command — the binary\n * whose presence on PATH we checked. */\n binary: string;\n /** Absolute path of the resolved binary on PATH, when ok=true. */\n resolvedPath?: string;\n}\n\nexport type CommandResolver = (command: string) => Promise<CommandResolutionResult>;\n\nconst execFileP = promisify(execFile);\n\n/** Default resolver: shell-out to `command -v <binary>` (POSIX-portable\n * equivalent of `which`). Returns ok=false when the lookup exits\n * non-zero. Always returns the parsed `binary` field so the typed\n * error can quote what we actually searched for. */\nasync function defaultCommandResolver(command: string): Promise<CommandResolutionResult> {\n const binary = parseFirstToken(command);\n if (binary === \"\") return { ok: false, binary };\n try {\n // `command -v` is the POSIX builtin lookup. Spawned via /bin/sh\n // -c so the builtin is available (it's not an external program).\n const { stdout } = await execFileP(\"/bin/sh\", [\"-c\", `command -v -- ${shellQuote(binary)}`], {\n env: process.env,\n });\n const resolvedPath = stdout.trim();\n if (resolvedPath === \"\") return { ok: false, binary };\n return { ok: true, binary, resolvedPath };\n } catch {\n return { ok: false, binary };\n }\n}\n\n/** Single-quote a token for /bin/sh -c. Only used on the binary name,\n * which `parseFirstToken` already restricted to a non-whitespace run\n * — no embedded single-quotes expected. Escape defensively anyway. */\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** First whitespace-separated token of a command string (\"pi-meta\n * --no-solo\" → \"pi-meta\"). Returns \"\" on an empty / whitespace-only\n * input. */\nfunction parseFirstToken(command: string): string {\n const trimmed = command.trim();\n if (trimmed === \"\") return \"\";\n const match = trimmed.match(/^\\S+/);\n return match ? match[0] : \"\";\n}\n\nlet activeCommandResolver: CommandResolver = defaultCommandResolver;\n\n/** Override the PATH resolver. Tests use this to simulate \"binary\n * absent\" / \"binary present\" without depending on what's actually\n * installed. Production callers should never touch this. */\nexport function setCommandResolverForTests(resolver: CommandResolver): void {\n activeCommandResolver = resolver;\n}\n\n/** Restore the default PATH resolver. */\nexport function resetCommandResolverForTests(): void {\n activeCommandResolver = defaultCommandResolver;\n}\n\n/**\n * Verify the first token of `command` resolves to a binary on PATH.\n * Public so tests can call it directly; spawnAgent calls it before\n * prestageWorkspace so a bad --cli never creates an orphan workspace.\n */\nexport async function checkCommandResolvable(command: string): Promise<CommandResolutionResult> {\n return activeCommandResolver(command);\n}\n\nexport interface SpawnAgentOptions {\n name: string;\n workstream: string;\n /** Defaults to \"pi\". 0.1.0 only really supports \"pi\" but the column\n * accepts any string for forward-compat with future multi-CLI support\n * (claude/codex). */\n cli?: string;\n /** The actual command to run in the pane. Defaults to the cli value. */\n command?: string;\n /** Window name to group this agent under. Defaults to the agent's name\n * (so each agent gets its own window). Multiple agents sharing a `tab`\n * share a window with multiple panes. */\n tab?: string;\n /** \"full-access\" (default) or \"read-only\". The schema stores it; today\n * the role isn't enforced (deferred to a future capabilities pass). */\n role?: string;\n /** Initial working directory for the spawned pane (`tmux -c <path>`).\n * When `workspace: true` is passed, this is ignored — the workspace\n * path is used instead. */\n cwd?: string;\n /** Override the tmux session name. Defaults to `mu-<workstream>`. */\n tmuxSession?: string;\n /** Auto-create a VCS workspace for this agent before spawning the\n * pane and use the workspace path as cwd. Backend defaults to\n * detection (jj > sl > git > none). */\n workspace?: boolean;\n /** Force a specific VCS backend (only meaningful with `workspace: true`). */\n workspaceBackend?: VcsBackendName;\n /** Optional ref to base the workspace on (only meaningful with\n * `workspace: true`). Backend-specific. */\n workspaceFrom?: string;\n /** Project root the workspace branches from (only meaningful with\n * `workspace: true`). Defaults to `process.cwd()`. */\n workspaceProjectRoot?: string;\n}\n\n/**\n * Spawn a new agent in its tmux pane and register it in the DB.\n *\n * Phases:\n * 1. Validate name + uniqueness.\n * 2. If --workspace: prestageWorkspace() (placeholder agent row +\n * workspace dir + workspace row).\n * 3. createOrReusePane() in the workspace path (or opts.cwd).\n * 4. setPaneTitle + enableMuPaneBordersForPane.\n * 5. finalizeAgentRow() — patch placeholder pane_id to real (workspace\n * path), or insert a fresh agent row (no-workspace path).\n * 6. awaitSpawnLiveness().\n *\n * Failure between any of (3)–(6) calls rollbackSpawn() to undo the\n * pane + row + workspace. The caller-visible error is preserved.\n */\nexport async function spawnAgent(db: Db, opts: SpawnAgentOptions): Promise<AgentRow> {\n if (!isValidAgentName(opts.name)) {\n throw new TypeError(\n `invalid agent name: ${JSON.stringify(opts.name)} (expected /^[a-z][a-z0-9_-]{0,31}$/)`,\n );\n }\n // Per-workstream uniqueness check: v5 allows the same agent name in\n // different workstreams. Scope the existence check to the spawn's\n // workstream so two operators spawning 'worker-1' in wsA and wsB\n // both succeed (bug_v5_name_clash_silent_misroute).\n if (getAgent(db, opts.name, opts.workstream) !== undefined) {\n throw new AgentExistsError(opts.name);\n }\n\n const session = opts.tmuxSession ?? `mu-${opts.workstream}`;\n const windowName = opts.tab ?? opts.name;\n const cli = opts.cli ?? \"pi\";\n const command = opts.command ?? resolveCliCommand(cli);\n\n // Pre-flight PATH resolution. Catches `--cli does-not-exist` (and\n // typos in `MU_<UPPER>_COMMAND` overrides) before any side effect.\n // See `checkCommandResolvable` / `AgentSpawnCliNotFoundError` for\n // the rationale; in short: prevents orphan workspace dirs + orphan\n // panes + half-spawned DB rows on a typo.\n //\n // We do NOT pre-flight an explicit `--command \"...\"` value: the\n // operator who passed --command gave us the literal string they\n // want spawned (often a wrapper like `pi-meta --no-solo` whose\n // first token IS on PATH but with arg-quoting that the resolver\n // would mis-parse; or a shell snippet like `bash -lc '...'`). For\n // those, the existing post-spawn liveness check is the safety net.\n if (opts.command === undefined) {\n const check = await checkCommandResolvable(command);\n if (!check.ok) {\n throw new AgentSpawnCliNotFoundError(cli, check.binary, envVarNameForCli(cli));\n }\n }\n\n // Workspace pre-stage. See `prestageWorkspace` for the FK-ordering\n // rationale. Returns the workspace path (used as the pane's cwd) or\n // undefined when --workspace wasn't requested.\n const workspacePathStr = opts.workspace ? await prestageWorkspace(db, opts, cli) : undefined;\n\n // Inject identity env vars into the pane so anything running inside\n // (pi extensions, claim-protocol scripts, status segments) can branch\n // on 'I am a mu-managed worker' without scraping pane titles or DB\n // lookups. Set via tmux `-e KEY=VALUE` (per-pane; doesn't pollute the\n // tmux server's global env).\n //\n // These are NOT exposed via SpawnAgentOptions — mu identity is not\n // user-tunable. Adding more keys here means every spawned pane sees\n // them automatically.\n const paneEnv: Record<string, string> = {\n MU_MANAGED_AGENT: \"1\",\n MU_AGENT_NAME: opts.name,\n MU_WORKSTREAM: opts.workstream,\n };\n\n const hasWorkspace = workspacePathStr !== undefined;\n\n // Single outer try wrapping every step that can throw AFTER the\n // workspace has been prestaged: pane create/reuse, pane title +\n // borders, agent-row finalize, liveness check. Earlier shape had\n // createOrReusePane outside the try, so a tmux failure (e.g. no\n // workstream session and tmux refusing to create one) left the\n // prestaged workspace dir + placeholder agent row behind as an\n // orphan — task agent_spawn_abort_leaves_orphan_workspace.\n //\n // paneId is undefined until createOrReusePane returns; rollbackSpawn\n // skips killPane in that case. The inner try/catches that previously\n // wrapped finalize and liveness were folded into this single catch\n // (clarity > belt-and-suspenders; rollbackSpawn is idempotent and\n // best-effort regardless).\n let paneId: string | undefined;\n let agent: AgentRow;\n try {\n paneId = await createOrReusePane({\n session,\n windowName,\n command,\n cwd: workspacePathStr ?? opts.cwd,\n env: paneEnv,\n });\n await setPaneTitle(paneId, opts.name);\n // Apply the mu pane border to the new window. Window-scoped option;\n // see enableMuPaneBorders docstring for why this is required per\n // window (and not just per session). Self-checks MU_BANNER_QUIET\n // and is best-effort — the border is decorative.\n await enableMuPaneBordersForPane(paneId);\n agent = finalizeAgentRow(db, { opts, cli, paneId, hasWorkspace });\n // Liveness check: wait briefly, then verify the pane is still alive.\n // Catches the silent-spawn-failure class of bugs where the CLI dies\n // immediately (lock conflict, bad credentials, etc.). On failure,\n // surface a typed error with whatever scrollback tmux still has.\n await awaitSpawnLiveness(paneId, opts.name);\n } catch (err) {\n await rollbackSpawn(db, opts.name, paneId, hasWorkspace, opts.workstream);\n if (hasWorkspace) attachOrphanCleanupHint(err, opts.name, opts.workstream);\n throw err;\n }\n emitEvent(\n db,\n opts.workstream,\n `agent spawn ${opts.name} (cli=${cli}, role=${opts.role ?? \"full-access\"}, pane=${paneId})`,\n );\n // Initial title push: the agent row is in 'spawning' state at this\n // point, which composeAgentTitle renders as bare name (no decoration\n // until the first detect cycle). Reconcile will re-push as soon as\n // the operator runs any status-reading verb.\n await refreshAgentTitle(db, opts.name, opts.workstream);\n // Lint-grade stderr nudge when the operator picked a name that\n // doesn't fit the <role>-<n> smallest-unused-suffix convention. The\n // spawn already succeeded; this is advice, not an error. Done LAST\n // so the hint trails the spawn's own stdout output and so a partial\n // failure (rolled back above) never emits it.\n maybeWarnNonConventionalAgentName(opts.name);\n return agent;\n}\n\n/**\n * Stage 1 of `--workspace` spawn: insert the agent row with a placeholder\n * pane id, then create the VCS workspace (whose row FKs the agent name).\n *\n * Why placeholder-first? `vcs_workspaces.agent` FK + ON DELETE CASCADE\n * means the workspace row needs an agents row to exist before we insert\n * it; but we can't insert agents without a pane_id (NOT NULL), and we\n * can't create the pane until we know the workspace's on-disk path\n * (used as cwd). The placeholder unblocks the cycle.\n *\n * The placeholder is a publicly-visible quirk — see `PENDING_PANE_PREFIX`\n * and the consumers it documents (refreshAgentTitle and reconcile's\n * pending-pane prune skip). The deeper fix (eliminate the placeholder\n * by reordering ws-dir → pane → atomic dual-insert) is filed as a\n * separate refactor; this shape is the established equilibrium.\n *\n * Throws after best-effort rollback (deleteAgent) if workspace creation\n * fails. Returns the workspace's on-disk path.\n */\nasync function prestageWorkspace(db: Db, opts: SpawnAgentOptions, cli: string): Promise<string> {\n insertAgent(db, {\n name: opts.name,\n workstream: opts.workstream,\n cli,\n paneId: pendingPaneIdFor(opts.name),\n status: \"spawning\",\n role: opts.role,\n tab: opts.tab ?? null,\n });\n try {\n const wsOpts: Parameters<typeof createWorkspace>[1] = {\n agent: opts.name,\n workstream: opts.workstream,\n };\n if (opts.workspaceBackend !== undefined) wsOpts.backend = opts.workspaceBackend;\n if (opts.workspaceFrom !== undefined) wsOpts.parentRef = opts.workspaceFrom;\n if (opts.workspaceProjectRoot !== undefined) wsOpts.projectRoot = opts.workspaceProjectRoot;\n const ws = await createWorkspace(db, wsOpts);\n return ws.path;\n } catch (err) {\n deleteAgent(db, opts.name, opts.workstream);\n throw err;\n }\n}\n\n/**\n * Stage 2 of spawn (after the real pane exists): either patch the\n * placeholder row to the real pane id (workspace path), or insert a\n * fresh agent row (no-workspace path). Throws if the patch UPDATE\n * silently affects no row (the placeholder row vanished mid-spawn —\n * historically the bug_agent_spawn_workspace_fk_failure class, now\n * patched by reconcile's pending-pane prune skip).\n */\nfunction finalizeAgentRow(\n db: Db,\n args: { opts: SpawnAgentOptions; cli: string; paneId: string; hasWorkspace: boolean },\n): AgentRow {\n const { opts, cli, paneId, hasWorkspace } = args;\n if (!hasWorkspace) {\n return insertAgent(db, {\n name: opts.name,\n workstream: opts.workstream,\n cli,\n paneId,\n status: \"spawning\",\n role: opts.role,\n tab: opts.tab ?? null,\n });\n }\n // Scope the patch to the spawn's workstream so a same-named worker\n // in another workstream isn't repointed at this pane\n // (bug_v5_name_clash_silent_misroute).\n db.prepare(\n `UPDATE agents SET pane_id = ?, updated_at = ?\n WHERE name = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)`,\n ).run(paneId, new Date().toISOString(), opts.name, opts.workstream);\n const row = getAgent(db, opts.name, opts.workstream);\n if (!row) throw new Error(`spawnAgent: agent vanished after workspace stage: ${opts.name}`);\n return row;\n}\n\n/**\n * Roll back a failed spawn. Idempotent and best-effort: every step\n * swallows its own errors so a partial-cleanup substrate (already-killed\n * pane, no agent row) never masks the original failure.\n *\n * `paneId` is `undefined` when createOrReusePane itself threw before\n * returning a pane id — there's nothing to kill. `hasWorkspace` is\n * `true` when prestageWorkspace succeeded; freeWorkspace is idempotent\n * on a missing workspace so calling it after a same-cycle failure is\n * safe. deleteAgent is also idempotent (no-op on a missing row).\n */\nasync function rollbackSpawn(\n db: Db,\n name: string,\n paneId: string | undefined,\n hasWorkspace: boolean,\n workstream: string,\n): Promise<void> {\n if (paneId !== undefined) await killPane(paneId).catch(() => {});\n if (hasWorkspace) {\n // Scope cleanup to the spawn's workstream so a same-named worker\n // elsewhere isn't torn down by accident\n // (bug_v5_name_clash_silent_misroute).\n await freeWorkspace(db, name, { workstream }).catch(() => {});\n }\n deleteAgent(db, name, workstream);\n}\n\n/**\n * After a failed spawn that prestaged a workspace, augment the thrown\n * error with orphan-cleanup hints (`mu workspace orphans` +\n * `mu workspace free`). rollbackSpawn is best-effort — if the\n * freeWorkspace call inside it failed (rmdir blocked, vcs backend\n * refusing to remove a workspace with uncommitted state, etc.), the\n * dir survives and the operator needs to know how to clean it up.\n *\n * Uses the duck-typed `errorNextSteps()` convention (see\n * `hasNextSteps()` in src/output.ts) so this works whether the inner\n * error is a typed mu error already exposing nextSteps\n * (AgentDiedOnSpawnError) or a bare TmuxError / Error from\n * createOrReusePane that has none. Existing hints (if any) are\n * preserved and listed first; the orphan-cleanup hints are appended\n * so the diagnostic-first ordering of typed errors is kept.\n */\nfunction attachOrphanCleanupHint(err: unknown, agent: string, workstream: string): void {\n if (typeof err !== \"object\" || err === null) return;\n const target = err as { errorNextSteps?: () => NextStep[] };\n const existing =\n typeof target.errorNextSteps === \"function\" ? target.errorNextSteps.bind(target) : null;\n const orphanHints: NextStep[] = [\n {\n intent: \"Check for an orphan workspace dir (rollback is best-effort; may have failed)\",\n command: `mu workspace orphans -w ${workstream}`,\n },\n {\n intent: \"Free the workspace if it survived the rollback (idempotent on missing)\",\n command: `mu workspace free ${agent} -w ${workstream}`,\n },\n ];\n target.errorNextSteps = () => [...(existing ? existing() : []), ...orphanHints];\n}\n\n/**\n * Default liveness window in milliseconds. 0 disables the check (useful\n * for fast tests that don't want to wait). Override via env var\n * `MU_SPAWN_LIVENESS_MS`.\n */\nexport function defaultSpawnLivenessMs(): number {\n const raw = process.env.MU_SPAWN_LIVENESS_MS;\n if (raw === undefined) return 1500;\n const parsed = Number.parseInt(raw, 10);\n if (Number.isNaN(parsed) || parsed < 0) return 1500;\n return parsed;\n}\n\n/**\n * Curated list of patterns that indicate the spawned CLI hit a fatal\n * provider/auth error during startup and is now parked at an error\n * prompt instead of becoming a working agent. Source:\n * `agent_spawn_model_auth_failure_counts_as_live` in the feedback ws.\n *\n * Kept short and case-insensitive on purpose: every entry must match a\n * real, observed dogfood failure mode. Don't add patterns speculatively\n * — the false-positive cost is to roll back a healthy spawn, which the\n * operator then has to re-attempt without the scan (`MU_SPAWN_LIVENESS_MS=0`).\n *\n * Mitigation against scrollback noise from harmless prior-session text:\n * the caller (`awaitSpawnLiveness`) only scans the LAST `STARTUP_ERROR_TAIL_LINES`\n * lines of the capture taken right after the liveness sleep, so matches\n * naturally come from the CLI's first ~1.5s of output (the spawned\n * pane has had no time to scroll older content into view).\n */\nconst STARTUP_ERROR_PATTERNS: readonly RegExp[] = [\n /No API key found for [\\w-]+/i,\n /Error: invalid API key/i,\n /Authentication failed/i,\n /401 Unauthorized/i,\n /Could not authenticate/i,\n // fb_agent_spawn_no_validation part B: post-spawn detection of the\n // \"binary not found at exec time\" failure mode. The pre-flight check\n // above catches the common typo BEFORE any side effect, but a few\n // edge cases still slip through and only surface in the pane:\n // - `--command \"...\"` skips the pre-flight (operator opt-out).\n // - PATH inside the spawned shell differs from PATH in mu's\n // process (login shell rc files, /etc/paths.d, etc.).\n // - Race: binary on PATH at spawn time, gone 1.5s later.\n //\n // Anchored to lines that LOOK like a shell error rather than prose\n // mentioning the marker (review_substrate_startup_err_patterns_too_broad):\n // require the marker at end-of-line, optionally followed by a single\n // `: <name>` token (the zsh form: `zsh: command not found: pi-meta`).\n // This still trips the canonical cases —\n // `bash: pi-meta: command not found`\n // `zsh: command not found: pi-meta`\n // `sh: pi-meta: No such file or directory`\n // — but rejects banner prose like\n // `I noticed earlier you saw 'command not found'`\n // `type \\`mu\\` — command not found? install with …`\n // that previously triggered a full spawn rollback. The pre-flight\n // PATH check remains the deterministic safety net for typo'd `--cli`.\n /^(?:.*: )?(?:command not found|No such file or directory)(?::\\s*\\S+)?$/i,\n];\n\n/**\n * Number of trailing lines of the post-liveness scrollback to scan for\n * `STARTUP_ERROR_PATTERNS`. The capture is `lines: 50`; tailing the last\n * 30 keeps the scan focused on the spawned CLI's own startup output\n * (the pane is brand-new, so anything earlier than that is the shell\n * banner / our own command-line, both safe).\n */\nconst STARTUP_ERROR_TAIL_LINES = 30;\n\n/**\n * Scan the tail of a freshly-captured pane buffer for known startup-\n * failure patterns. Returns the matched line on first hit, or undefined\n * if the buffer is clean.\n *\n * Exported for the test suite; not part of the SDK surface.\n */\nexport function detectSpawnStartupError(scrollback: string): string | undefined {\n const lines = scrollback.split(/\\r?\\n/);\n const tail = lines.slice(Math.max(0, lines.length - STARTUP_ERROR_TAIL_LINES));\n for (const line of tail) {\n for (const pattern of STARTUP_ERROR_PATTERNS) {\n if (pattern.test(line)) return line;\n }\n }\n return undefined;\n}\n\nasync function awaitSpawnLiveness(paneId: string, agentName: string): Promise<void> {\n const ms = defaultSpawnLivenessMs();\n if (ms === 0) return;\n await sleep(ms);\n // Capture-pane first so we have something to attach to the error if the\n // pane is in the process of being torn down (the buffer survives a beat\n // longer than the pane's existence in some tmux builds).\n const scrollback = await capturePane(paneId, { lines: 50 }).catch(() => undefined);\n if (!(await paneExists(paneId))) {\n throw new AgentDiedOnSpawnError(agentName, paneId, scrollback);\n }\n // Pane is alive — but \"alive\" doesn't mean \"working\". Scan the tail of\n // the capture for known provider/auth startup errors\n // (agent_spawn_model_auth_failure_counts_as_live). The pane stays at\n // an error prompt forever otherwise, and the orchestrator only\n // discovers the dud minutes later when `mu task wait` stalls.\n if (scrollback !== undefined) {\n const matchedLine = detectSpawnStartupError(scrollback);\n if (matchedLine !== undefined) {\n throw new AgentSpawnStartupError(agentName, paneId, matchedLine, scrollback);\n }\n }\n}\n\n/**\n * Three cases, all returning a stable pane id:\n * - session doesn't exist → create session+window+pane in one shot\n * - session exists, no such window → create a new window for this agent\n * - session and window both exist → split the window to add a pane\n */\nasync function createOrReusePane(opts: {\n session: string;\n windowName: string;\n command: string;\n cwd?: string;\n env?: Record<string, string>;\n}): Promise<string> {\n if (!(await sessionExists(opts.session))) {\n return newSessionWithPane(opts.session, {\n windowName: opts.windowName,\n command: opts.command,\n cwd: opts.cwd,\n env: opts.env,\n });\n }\n\n const windows = await listWindows(opts.session);\n const matching = windows.find((w) => w.name === opts.windowName);\n\n if (matching) {\n return splitWindow({\n target: `${opts.session}:${opts.windowName}`,\n command: opts.command,\n cwd: opts.cwd,\n env: opts.env,\n });\n }\n\n return newWindow({\n session: opts.session,\n name: opts.windowName,\n command: opts.command,\n cwd: opts.cwd,\n env: opts.env,\n });\n}\n","// mu — self-documenting output helpers.\n//\n// Every successful verb output should answer \"what changed AND what's\n// the natural next step?\". Every error should answer \"why AND what are\n// the actionable resolutions?\". The same data shape feeds both human\n// (dim text) and JSON (`nextSteps` array) consumers.\n//\n// Why a separate module: this is shared by cli.ts (success-path\n// rendering), the typed errors in src/agents.ts / src/tasks.ts /\n// src/workstream.ts (error nextSteps), and tests. Keeping the type +\n// helpers in one place avoids circular imports.\n\nimport Table from \"cli-table3\";\nimport type { Command } from \"commander\";\nimport picocolors from \"picocolors\";\n\n/**\n * Should we emit ANSI color escapes from this process?\n *\n * picocolors ships an `isColorSupported` flag, but it bakes its env\n * inspection (NO_COLOR / FORCE_COLOR / isTTY) ONCE at module-load\n * time. That makes the function untestable without dynamic re-imports\n * — and worse, it loses colors whenever stdout is a pipe, most\n * painfully under `watch --color mu state` and `tmux display-popup -E\n * 'mu state' | cat`, where the surrounding pane is a real terminal but\n * our own stdout is a pipe.\n *\n * We therefore re-implement the decision from scratch at call time,\n * reading every signal directly from `process.env` / `process.stdout`\n * so tests can flip env vars and observe the result without the\n * vi.resetModules + vi.doMock dance (per task\n * review_test_color_enabled_no_color_module_load_caveat).\n *\n * Order of precedence (first match wins):\n * - `NO_COLOR` set (cross-tool opt-out, https://no-color.org/) →\n * OFF, even when TMUX/MU_FORCE_COLOR/FORCE_COLOR are set. We\n * treat any defined value (including \"\") as set, matching the\n * no-color.org convention and picocolors' own behavior.\n * - `MU_FORCE_COLOR` truthy → ON (mu-specific override). Values\n * \"\", \"0\", and \"false\" are explicit opt-outs, matching chalk's\n * FORCE_COLOR convention.\n * - `FORCE_COLOR` truthy → ON (the standard env var picocolors /\n * chalk consult). Values \"\", \"0\", and \"false\" opt out.\n * - `TMUX` set → ON (the load-bearing fix for `watch` inside tmux:\n * the surrounding pane is a real terminal even though our stdout\n * is a pipe).\n * - Fall back to the standard TTY heuristic: stdout is a TTY AND\n * TERM !== \"dumb\". This mirrors what picocolors itself does in\n * `isColorSupported` for the happy-path case.\n *\n * See task hud_colors_stripped_under_watch_and for the original repro.\n */\nfunction envForceTrue(key: string): boolean {\n const v = process.env[key];\n if (v === undefined) return false;\n if (v === \"\" || v === \"0\" || v.toLowerCase() === \"false\") return false;\n return true;\n}\n\nexport function colorEnabled(): boolean {\n if (process.env.NO_COLOR !== undefined) return false;\n if (envForceTrue(\"MU_FORCE_COLOR\")) return true;\n if (envForceTrue(\"FORCE_COLOR\")) return true;\n if (process.env.TMUX !== undefined) return true;\n return Boolean(process.stdout.isTTY) && process.env.TERM !== \"dumb\";\n}\n\n/**\n * The single picocolors instance the rest of the codebase imports.\n * Built once at module load with `colorEnabled()` baked in, so every\n * caller (cli.ts, src/cli/*.ts) renders consistently regardless of\n * isTTY heuristics. Any other module that needs `pc` should import\n * this one rather than reaching for `picocolors` directly.\n */\n// picocolors is still used as the renderer (createColors honors the\n// flag we pass), but the *decision* of whether to render is ours.\nexport const pc = picocolors.createColors(colorEnabled());\n\n/**\n * One actionable next step. The `intent` is human-prose (\"Drop notes\n * as you work\"); the `command` is a literal shell command the user (or\n * an LLM) can copy-paste or `eval` directly.\n *\n * Used both for success-path hints (post-verb) and for typed-error\n * resolutions (in the error message + JSON output).\n */\nexport interface NextStep {\n /** Short human-prose label, e.g. \"Drop notes as you work\". */\n intent: string;\n /** Literal shell command, e.g. `mu task note foo \"...\"`. */\n command: string;\n}\n\n/**\n * Print a block of next-step hints to stdout, dimmed so humans can\n * skim past them but agents reading the captured output still get\n * them. No-op when the array is empty.\n *\n * Format:\n *\n * Next:\n * <intent padded>: <command>\n * <intent padded>: <command>\n *\n * The padding aligns the colons so visual scanning is easy.\n */\nexport function printNextSteps(steps: readonly NextStep[]): void {\n printNextStepsTo(steps, \"stdout\");\n}\n\n/** Same as `printNextSteps` but routes to either stdout or stderr.\n * Errors emit nextSteps to stderr (so success vs failure paths\n * capture cleanly when scripts redirect them separately); success\n * paths emit to stdout. Single source of truth for the formatting\n * (review_code_print_next_steps_duplicated). */\nexport function printNextStepsTo(steps: readonly NextStep[], sink: \"stdout\" | \"stderr\"): void {\n if (steps.length === 0) return;\n const labelWidth = Math.max(...steps.map((s) => s.intent.length));\n const out = sink === \"stderr\" ? console.error : console.log;\n out(pc.dim(\"Next:\"));\n for (const step of steps) {\n const label = step.intent.padEnd(labelWidth);\n out(pc.dim(` ${label} : ${step.command}`));\n }\n}\n\n/**\n * Build a cli-table3 Table with the mu-standard safety belt:\n * `wordWrap: false` (cells wider than their column truncate with `…`\n * instead of wrapping to a second visual row), per-column max widths\n * applied only where the caller asks (`null` = auto), and a default\n * borderless style mirroring the state/workspace/workstream tables.\n *\n * Callers should pre-truncate values they care about via the\n * `truncate()` / `truncateFront()` helpers in cli.ts (the proactive\n * path); `wordWrap: false` is the safety belt for the cells they\n * miss. This is load-bearing for renderers with fixed row budgets:\n * a single wrapped cell silently blows out the promised section height.\n *\n * Surfaced live by `mu workspace list` blowing the terminal width on\n * the `path` column (tables_truncate_long_cols_audit). Don't try to\n * cap every column — apply `colWidths` only on the column(s) the\n * operator is least likely to read in full and most likely to be\n * long. Don't add a `--full` / `--no-truncate` flag per verb either;\n * `--json` already emits the full value.\n */\nexport function muTable(opts: {\n head: string[];\n /** Per-column max widths in cells (`null` = auto width). When\n * supplied, the array length must match `head`. cli-table3\n * truncates with `…` because we set `wordWrap: false`. */\n colWidths?: (number | null)[];\n /** Style override; defaults to `{ head: [], border: [] }` (mu's\n * borderless look). Pass `{ head: [] }` to keep cli-table3's\n * default border styling. */\n style?: { head?: string[]; border?: string[] };\n}): InstanceType<typeof Table> {\n return new Table({\n head: opts.head,\n ...(opts.colWidths !== undefined ? { colWidths: opts.colWidths } : {}),\n style: opts.style ?? { head: [], border: [] },\n wordWrap: false,\n });\n}\n\n/**\n * The typed-error wire format for `--json` output. Errors that carry\n * actionable resolutions (most of them) implement `errorNextSteps()`;\n * the handler in cli.ts wraps them in this shape and emits to stderr.\n */\nexport interface ErrorJson {\n /** Class name (e.g. \"ClaimerNotRegisteredError\"). */\n error: string;\n /** Human-readable message (the same one printed to stderr in non-JSON mode). */\n message: string;\n /** Actionable resolutions. May be empty. */\n nextSteps: NextStep[];\n /** Process exit code that will follow. */\n exitCode: number;\n}\n\n/**\n * Marker interface for typed errors that carry actionable resolutions.\n * The handler checks this with a duck-typed `typeof err.errorNextSteps\n * === \"function\"` rather than instanceof so cross-realm errors (e.g.\n * thrown from a different module instance after a hot-reload) still\n * surface their nextSteps.\n */\nexport interface HasNextSteps {\n errorNextSteps(): NextStep[];\n}\n\n/**\n * Detect whether the current invocation requested `--json`. Used by the\n * error handler to decide between human-prose stderr and structured\n * JSON stderr. Reads `process.argv` directly because commander has\n * already consumed it by the time the handler runs, and threading it\n * through every verb wrapper would be invasive.\n *\n * Tolerates `--json=...` form (commander supports both) but mu's verbs\n * only use the bare `--json` flag.\n */\nexport function isJsonMode(): boolean {\n return process.argv.some((a) => a === \"--json\" || a.startsWith(\"--json=\"));\n}\n\n/**\n * Has the current err object produced its own actionable nextSteps?\n * Encapsulates the duck-type check so the handler stays readable.\n */\nexport function hasNextSteps(err: unknown): err is HasNextSteps {\n return (\n typeof err === \"object\" &&\n err !== null &&\n typeof (err as { errorNextSteps?: unknown }).errorNextSteps === \"function\"\n );\n}\n\n// ─── Usage rendering for the validation-error contract ──────────────\n//\n// Every operator-error path (commander-thrown CommanderError, handler-\n// thrown UsageError, typed *Invalid* errors) gets the same surface:\n// (1) the error line, (2) the failing subcommand's --help. The human\n// path prints commander's own helpInformation() (so future commander\n// version bumps automatically pick up any rendering improvements);\n// the --json path renders a structured shape so a script orchestrator\n// can introspect the verb without re-shelling for --help.\n//\n// Why structured-not-string for JSON: the entire point of --json is\n// that consumers never have to free-text-parse mu output. Embedding a\n// multi-kilobyte rendered help blob in the JSON envelope defeats that\n// — every option is already structured on the Command object.\n\n/** Structured rendition of a verb's --help, for JSON error envelopes. */\nexport interface UsageJson {\n /** Full canonical name including parent commands (e.g. \"mu task add\"). */\n command: string;\n /** The single-line synopsis (e.g. \"mu task add [options] [id]\"). */\n synopsis: string;\n /** Verb description (the one-paragraph prose under the synopsis). */\n description: string;\n /** Positional arguments in declared order. */\n args: Array<{ name: string; required: boolean; variadic: boolean; description: string }>;\n /** Options in declared order. `mandatory: true` iff the option was\n * declared via `.requiredOption()` (i.e. the operator MUST pass it).\n * `valueRequired: true` for `<value>`-style options whose value is\n * required when the flag IS passed. The two are independent. */\n options: Array<{\n flags: string;\n description: string;\n mandatory: boolean;\n valueRequired: boolean;\n }>;\n}\n\n/** Walk parent chain so subcommand renderings include the full path\n * (\"mu task add\" not just \"add\"). */\nfunction commandFullName(cmd: Command): string {\n const parts: string[] = [];\n let cur: Command | null = cmd;\n while (cur) {\n parts.unshift(cur.name());\n cur = cur.parent;\n }\n return parts.join(\" \");\n}\n\n/** Extract the structured usage shape for `--json` error envelopes. */\nexport function renderUsageJson(cmd: Command): UsageJson {\n return {\n command: commandFullName(cmd),\n synopsis: `${commandFullName(cmd)} ${cmd.usage()}`,\n description: cmd.description(),\n args: cmd.registeredArguments.map((a) => ({\n name: a.name(),\n required: a.required,\n variadic: a.variadic,\n description: a.description ?? \"\",\n })),\n options: cmd.options.map((o) => ({\n flags: o.flags,\n description: o.description ?? \"\",\n mandatory: o.mandatory ?? false,\n valueRequired: o.required ?? false,\n })),\n };\n}\n\n/** Render the human --help block (commander's own `helpInformation()`)\n * to stderr. Single source of truth for the post-error help dump. */\nexport function printUsageHuman(cmd: Command): void {\n // helpInformation() returns the full \"Usage: ...\\n\\n<desc>\\n\\nOptions:\\n ...\" block.\n // Print to stderr (errors only) so success-path stdout is never polluted.\n process.stderr.write(`\\n${cmd.helpInformation()}`);\n}\n","// mu — agent error classes.\n//\n// Every agent verb that can fail in a typed way has its own error class\n// here. The CLI's classifyError() (src/cli.ts) maps them to exit codes:\n// not found → 3 (AgentNotFoundError)\n// conflict → 4 (AgentExistsError, AgentNotInWorkstreamError,\n// AgentDiedOnSpawnError, AgentSpawnStartupError,\n// WorkspacePreservedError)\n//\n// AgentDiedOnSpawnError + AgentSpawnStartupError reach into spawn.ts for\n// defaultSpawnLivenessMs — a single, narrow cross-cluster import that\n// documents itself in the error message (\"agent died within Nms of\n// spawn\" / \"agent reported a startup error within Nms of spawn\").\n//\n// AgentSpawnCliNotFoundError is the pre-flight cousin of the two\n// post-spawn-detect errors above: thrown BEFORE prestageWorkspace when\n// the resolved `--cli` command's first token doesn't exist on PATH.\n// Distinct from AgentSpawnStartupError so the operator can tell\n// 'I never had a working CLI' from 'CLI started but parked at an error'.\n//\n// Extracted from src/agents.ts as part of refactor_split_large_src_files.\n\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport { defaultSpawnLivenessMs } from \"./spawn.js\";\n\n/**\n * Pre-flight failure: the command mu would have spawned in the new\n * pane doesn't resolve to a binary on PATH (and isn't an absolute /\n * relative path that exists + is executable). Thrown by `spawnAgent`\n * BEFORE `prestageWorkspace` so a typo in `--cli` never leaves an\n * orphan workspace dir behind.\n *\n * Source: feedback ws task `fb_agent_spawn_no_validation`. Live\n * dogfood report: `mu agent spawn worker-1 --cli pi-meta` on a host\n * where the `pi-meta` binary wasn't on PATH printed `Spawned worker-1\n * (pi-meta)` and the pane immediately died with `command not found`;\n * the existing 1.5s liveness check sometimes missed it (the shell\n * stays alive after the failed exec). Pre-flighting the PATH lookup\n * surfaces the typo before any side effects (workspace, pane, DB row).\n *\n * Distinct from `AgentSpawnStartupError` (pane alive but parked at an\n * error prompt) and `AgentDiedOnSpawnError` (pane vanished within the\n * liveness window). All three carry different remediation hints, so\n * they're separate types.\n */\nexport class AgentSpawnCliNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"AgentSpawnCliNotFoundError\";\n constructor(\n public readonly cli: string,\n /** First whitespace-separated token of the resolved command — the\n * thing actually missing on PATH. Surfaced verbatim in the\n * message so the operator sees what mu searched for (which may\n * differ from `cli` when `$MU_<UPPER_CLI>_COMMAND` rewrites it). */\n public readonly binary: string,\n /** Name of the env var that mu consulted before falling back to\n * the bare `cli` value (e.g. `MU_PI_META_COMMAND`). Always set\n * to the conventional name so the nextSteps hint can recommend\n * exporting it. */\n public readonly envVarChecked: string,\n ) {\n super(\n `--cli ${cli} resolved to binary \"${binary}\" which is not on PATH (and not an executable absolute/relative path). Refusing to spawn — would create a pane that dies immediately on \"command not found\".`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Try the default CLI (the one mu's substrate ships against)\",\n command: \"mu agent spawn <name> --cli pi\",\n },\n {\n intent: \"If you meant a custom alias, set the env var to its real path\",\n command: `export ${this.envVarChecked}=\"<absolute-path-to-binary> [args...]\"`,\n },\n {\n intent: \"List installed CLIs typically supported by mu\",\n command: \"which pi pi-meta claude codex\",\n },\n ];\n }\n}\n\nexport class AgentExistsError extends Error implements HasNextSteps {\n override readonly name = \"AgentExistsError\";\n constructor(public readonly agentName: string) {\n // v5: agent names are UNIQUE per (workstream, name) — the same\n // name can legitimately exist in two different workstreams. The\n // pre-v5 message claimed global uniqueness, which (a) lied about\n // the schema and (b) misled operators into closing the existing\n // agent when the actual fix is `-w <other-ws>`.\n super(`agent already exists in this workstream: ${agentName}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n // v5: agents.workstream_id → workstreams.id; there is no\n // `agents.workstream` column. Use the join so this hint\n // actually runs against a v5 DB.\n intent: \"List which workstream(s) already have an agent by this name\",\n command: `mu sql \"SELECT a.name, ws.name AS workstream FROM agents a JOIN workstreams ws ON ws.id = a.workstream_id WHERE a.name = '${this.agentName}'\"`,\n },\n {\n intent: \"Spawn it in a different workstream (per-workstream unique → no clash)\",\n command: `mu agent spawn ${this.agentName} -w <other-workstream>`,\n },\n {\n intent: \"Or close the existing agent in this workstream and re-spawn\",\n command: `mu agent close ${this.agentName} && mu agent spawn ${this.agentName}`,\n },\n { intent: \"Or pick a different name\", command: \"mu agent spawn <new-name>\" },\n ];\n }\n}\n\nexport class AgentNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"AgentNotFoundError\";\n constructor(\n public readonly agentName: string,\n /** Optional workstream context. When set, the message is enriched\n * with `(in workstream <ws>)` so the verb that hit the miss\n * (e.g. `mu workspace create <agent> -w <ws>`) doesn't leave the\n * operator guessing which scope was searched. Optional so existing\n * call sites that only know the agent name keep their original\n * one-line message. */\n public readonly workstream?: string,\n ) {\n super(\n workstream === undefined\n ? `no such agent: ${agentName}`\n : `no such agent: ${agentName} (in workstream ${workstream})`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List agents in current workstream\", command: \"mu agent list\" },\n { intent: \"List workstreams to choose the right scope\", command: \"mu workstream list\" },\n {\n intent: \"Spawn it now\",\n command: `mu agent spawn ${this.agentName} -w <workstream>`,\n },\n ];\n }\n}\n\n/**\n * Thrown when an entity-targeted verb is invoked with `-w/--workstream\n * <name>` but the named agent lives in a different workstream.\n * Mirrors `TaskNotInWorkstreamError`. Maps to exit code 4 (conflict /\n * wrong scope). Distinguishes \"the user typo'd the workstream\" from\n * \"the agent doesn't exist anywhere\" (which surfaces as\n * `AgentNotFoundError`).\n */\nexport class AgentNotInWorkstreamError extends Error implements HasNextSteps {\n override readonly name = \"AgentNotInWorkstreamError\";\n constructor(\n public readonly agentName: string,\n public readonly expectedWorkstream: string,\n public readonly actualWorkstream: string,\n ) {\n super(`agent ${agentName} is in workstream ${actualWorkstream}, not ${expectedWorkstream}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Use the agent's actual workstream\",\n command: `mu agent show ${this.agentName} -w ${this.actualWorkstream}`,\n },\n {\n intent: \"List agents in the requested workstream\",\n command: `mu agent list -w ${this.expectedWorkstream}`,\n },\n ];\n }\n}\n\n/**\n * Thrown when an agent's pane is created and titled successfully but the\n * spawned process exits within the liveness window (default 1500ms;\n * configurable via `MU_SPAWN_LIVENESS_MS`). The most common cause is the\n * underlying CLI failing fast: a wrapper CLI blocking on a single-instance\n * lock, `claude` rejecting an invalid API key, etc. The agent's last\n * scrollback (when capturable) is attached to help diagnose.\n */\nexport class AgentDiedOnSpawnError extends Error implements HasNextSteps {\n override readonly name = \"AgentDiedOnSpawnError\";\n constructor(\n public readonly agentName: string,\n public readonly paneId: string,\n public readonly scrollback: string | undefined,\n ) {\n const tail = scrollback?.trim();\n const detail = tail ? `\\n\\n--- pane scrollback ---\\n${tail}\\n--- end scrollback ---` : \"\";\n super(\n `agent ${agentName} died within ${defaultSpawnLivenessMs()}ms of spawn (pane ${paneId}). Most common cause: the spawned CLI exited immediately (e.g. a wrapper CLI blocking on its instance lock; set MU_<UPPER_CLI>_COMMAND to a non-blocking variant to bypass).${detail}`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Inspect the dead pane's scrollback for the underlying error\",\n command: `mu agent read ${this.agentName} -n 100`,\n },\n {\n // agent_spawn_liveness_check_trips_on: per-spawn override is\n // the right scope for one-offs (e.g. wrapper CLIs blocking\n // on a per-project solo lock). Listed first so operators\n // reach for it before exporting an env var that leaks into\n // every subsequent spawn in the shell.\n intent: \"Override per spawn (one-off; no env-var leak)\",\n command: `mu agent spawn ${this.agentName} --command \"<cli> <bypass-flag>\" (e.g. pi-meta --no-solo)`,\n },\n {\n intent:\n \"Make the override the default for this CLI (applies to every subsequent spawn in this shell)\",\n command: 'export MU_PI_COMMAND=\"pi-alt --some-flag\"',\n },\n {\n intent: \"Disable the liveness check (CI / known long-lived sh subprocess)\",\n command: \"export MU_SPAWN_LIVENESS_MS=0\",\n },\n { intent: \"Run health check\", command: \"mu doctor\" },\n ];\n }\n}\n\n/**\n * Thrown when an agent's pane is alive AND staying alive after the\n * liveness window, but its first burst of output matches a known\n * provider-startup-failure pattern (missing API key, auth rejected, …).\n * Source: feedback ws task `agent_spawn_model_auth_failure_counts_as_live`.\n * Live dogfood report: `pi-meta --no-solo --model sonnet:high` printed\n * `Error: No API key found for amazon-bedrock` and parked at a prompt.\n * The pane stayed alive (1.5s liveness check passed) but the worker\n * could never do work — the orchestrator only discovered this when\n * `mu task wait` stalled minutes later.\n *\n * Distinct from `AgentDiedOnSpawnError`:\n * - `AgentDiedOnSpawnError` → pane vanished within the liveness window\n * (CLI exited fast).\n * - `AgentSpawnStartupError` → pane alive, but the captured scrollback\n * tail contains a curated provider-auth-failure pattern.\n * The two carry different remediation hints (CLI override vs. fix the\n * env var), so they're separate types instead of one with a flag.\n *\n * The pattern list is curated and short to keep false-positive risk low\n * — the scan only looks at the last ~30 lines of the 50-line capture\n * taken right after the liveness sleep, so matches naturally come from\n * the CLI's first ~1.5s of output (not arbitrary later prompts the\n * agent might type into).\n */\nexport class AgentSpawnStartupError extends Error implements HasNextSteps {\n override readonly name = \"AgentSpawnStartupError\";\n constructor(\n public readonly agentName: string,\n public readonly paneId: string,\n /** The single scrollback line that matched a known startup-error\n * pattern. Surfaced verbatim in the message so the operator sees\n * what mu saw. */\n public readonly matchedLine: string,\n /** Full captured scrollback (tail-trimmed already by\n * awaitSpawnLiveness). Attached to the message for context. */\n public readonly scrollback: string,\n ) {\n super(\n `agent ${agentName} reported a startup error within ${defaultSpawnLivenessMs()}ms of spawn (pane ${paneId}). The pane is alive but the spawned CLI parked at an error prompt instead of becoming a working agent.\\n\\nMatched line: ${matchedLine.trim()}\\n\\n--- pane scrollback ---\\n${scrollback.trim()}\\n--- end scrollback ---`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Inspect the parked pane's scrollback for the full error\",\n command: `mu agent read ${this.agentName} -n 100`,\n },\n {\n // Most common today: the operator picked a model whose\n // provider has no credentials in this env. Default Anthropic\n // is the safe fallback for pi-meta.\n intent: \"Re-spawn with a CLI command whose provider credentials are present\",\n command: `mu agent spawn ${this.agentName} --command \"pi-meta --no-solo\" # default Anthropic`,\n },\n {\n intent: \"Or set the missing API key env var for the provider you wanted, then re-spawn\",\n command:\n \"export ANTHROPIC_API_KEY=... # or AWS_BEARER_TOKEN_BEDROCK, OPENAI_API_KEY, ...\",\n },\n {\n intent:\n \"Disable the startup-error scan if you actually wanted that prompt (CI / scripted recovery)\",\n command: \"export MU_SPAWN_LIVENESS_MS=0\",\n },\n ];\n }\n}\n\n/**\n * Thrown when `closeAgent` is called on an agent that has an associated\n * workspace AND the caller didn't explicitly opt into discarding it.\n *\n * Background: the FK on `vcs_workspaces.agent` cascades on agent\n * delete, so a naive `closeAgent` drops the workspace registry row\n * but leaves the on-disk dir orphaned (mu can't see it via\n * `mu workspace list / free / path` afterwards). Surfaced during\n * the multi-agent dogfood teardown when three workspaces went\n * orphaned silently.\n *\n * The fix: refuse close if a workspace exists; force the caller to\n * decide. Two actionable resolutions:\n * - `mu workspace free <agent>` first, then close cleanly.\n * - `mu agent close <agent> --discard-workspace` to free the\n * workspace AND close the agent in one shot (lossy: pending\n * changes in the workspace are gone).\n *\n * Maps to exit code 4 (conflict) via the cli.ts handler.\n */\nexport class WorkspacePreservedError extends Error implements HasNextSteps {\n override readonly name = \"WorkspacePreservedError\";\n constructor(\n public readonly agentName: string,\n public readonly workspacePath: string,\n ) {\n super(\n `agent ${agentName} has a workspace at ${workspacePath}; refusing to close (would orphan the on-disk dir)`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Free the workspace first (preserves agent for next step)\",\n command: `mu workspace free ${this.agentName} (--commit to commit pending changes first)`,\n },\n {\n intent: \"Or close + discard the workspace in one shot (lossy)\",\n command: `mu agent close ${this.agentName} --discard-workspace`,\n },\n {\n intent: \"If the workstream was archived, restore task memory under a fresh name\",\n command: \"mu archive restore <label> --as <new-workstream> --source <workstream>\",\n },\n {\n intent: \"Or just inspect what's in the workspace\",\n command: `cd ${this.workspacePath}`,\n },\n ];\n }\n}\n","// mu — git VCS backend.\n\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport {\n commitSummary,\n ensureParent,\n exec,\n probeVcsRoot,\n rmDirSync,\n run,\n runShow,\n saneLimit,\n} from \"./helpers.js\";\nimport {\n type CommitSummary,\n type FreeWorkspaceResult,\n type VcsBackend,\n WorkspaceConflictError,\n WorkspaceDirtyError,\n} from \"./types.js\";\n\n// Uses `git worktree` so the new workspace shares the .git store with\n// the project root. Cheap (no copy), fast, and integrates with the rest\n// of the agent's git workflow (push, log, etc) trivially.\n\nexport const gitBackend: VcsBackend = {\n name: \"git\",\n\n async detect(projectRoot) {\n return probeVcsRoot(\"git\", [\"rev-parse\", \"--show-toplevel\"], projectRoot);\n },\n\n async createWorkspace(opts) {\n if (existsSync(opts.workspacePath)) {\n throw new Error(`vcs git: workspacePath already exists: ${opts.workspacePath}`);\n }\n await ensureParent(opts.workspacePath);\n // Defensive prune: if a previous workspace at the same path was\n // freed by `rm -rf` (or otherwise lost its dir without proper\n // teardown), git's worktree registry still points at it. Then\n // `git worktree add` fails with 'missing but already registered\n // worktree'. `git worktree prune` is idempotent and cheap; running\n // it BEFORE every add costs ~10ms and immunises us against the\n // mufeedback workspace_free_cleanup_leaves_git case.\n await run(\"git\", [\"worktree\", \"prune\"], opts.projectRoot).catch(() => {\n /* prune is best-effort; if it fails we'll get a clear error from `add` next */\n });\n // `git worktree add <path> [<ref>]`. Without a ref, the new worktree\n // checks out a detached HEAD at the project's current HEAD, which\n // matches the \"fresh per-agent workspace\" semantics we want. Use a\n // detached HEAD by default so two agents from the same parent ref\n // don't collide on a branch name.\n const args = [\"worktree\", \"add\", \"--detach\", opts.workspacePath];\n if (opts.parentRef) args.push(opts.parentRef);\n await run(\"git\", args, opts.projectRoot);\n // Resolve the actual SHA we ended up on for the parentRef record.\n const sha = await run(\"git\", [\"rev-parse\", \"HEAD\"], opts.workspacePath);\n return { parentRef: sha };\n },\n\n // Working-copy clean check: empty `git status --porcelain` output\n // means no working-tree, staged, or untracked-not-ignored changes.\n // Returns false on any failure (workspace path missing, git\n // explodes) — be conservative; auto-free should never \"silently\n // succeed\" because we couldn't check.\n async isClean(workspacePath) {\n if (!existsSync(workspacePath)) return false;\n try {\n const files = await listGitDirtyFiles(workspacePath);\n return files.length === 0;\n } catch {\n return false;\n }\n },\n\n // Compute commits-behind as: count of commits reachable from main\n // but not from `ref`. Resolves \"main\" via origin/HEAD (the symbolic\n // ref the remote advertises), falling back to origin/main and then\n // origin/master. Returns null when none of those resolve, or when\n // the rev-list call fails (e.g. ref unknown locally).\n //\n // Pure observation: NO `git fetch`. The number is as fresh as the\n // last time the user (or some other process) updated the local\n // remote-tracking refs.\n async commitsBehind(workspacePath, ref) {\n if (!existsSync(workspacePath)) return null;\n const main = await resolveGitMainRef(workspacePath);\n if (main === undefined) return null;\n try {\n const out = await run(\"git\", [\"rev-list\", \"--count\", `${ref}..${main}`], workspacePath);\n const n = Number.parseInt(out.trim(), 10);\n return Number.isFinite(n) ? n : null;\n } catch {\n return null;\n }\n },\n\n // Rebase the worktree onto `fromRef` (default = origin/HEAD via\n // resolveGitMainRef). Refuses on a dirty WC; returns the replayed\n // commit subjects oldest-first; on conflict aborts the rebase and\n // throws WorkspaceConflictError so the operator never inherits a\n // half-rebased worktree from us.\n //\n // We DO `git fetch` first — otherwise the rebase target would only\n // be as fresh as the local refs cache, and the operator running\n // `mu workspace refresh` is explicitly asking for the latest. This\n // is the one-and-only place mu fetches; commitsBehind() stays pure.\n async rebaseTo(workspacePath, fromRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs git: workspace path missing: ${workspacePath}`);\n }\n // Dirty-check first: refuse before any side effect.\n const dirtyFiles = await listGitDirtyFiles(workspacePath);\n if (dirtyFiles.length > 0) {\n throw new WorkspaceDirtyError(workspacePath, dirtyFiles);\n }\n // Best-effort fetch so the resolved main ref is fresh. Failure is\n // ignored (offline / no remote) — we still attempt the rebase\n // against whatever the local refs cache holds.\n await run(\"git\", [\"fetch\", \"--quiet\"], workspacePath).catch(() => {});\n let resolvedRef: string;\n if (fromRef !== undefined) {\n resolvedRef = fromRef;\n } else {\n const main = await resolveGitMainRef(workspacePath);\n if (main === undefined) {\n throw new Error(\n `vcs git: cannot resolve default branch (no origin/HEAD, origin/main, or origin/master) in ${workspacePath}; pass --from <ref>`,\n );\n }\n resolvedRef = main;\n }\n // Capture the pre-rebase HEAD so we can compute `replayed` as the\n // commits that ended up on top of resolvedRef after the rebase.\n const preHead = await run(\"git\", [\"rev-parse\", \"HEAD\"], workspacePath);\n try {\n await run(\n \"git\",\n [\"-c\", \"user.email=mu@local\", \"-c\", \"user.name=mu\", \"rebase\", resolvedRef],\n workspacePath,\n );\n } catch (err) {\n // Capture the conflicting paths BEFORE aborting (the abort\n // resets the index and the unmerged paths disappear).\n const conflicts = await listGitUnmergedPaths(workspacePath);\n await run(\"git\", [\"rebase\", \"--abort\"], workspacePath).catch(() => {});\n if (conflicts.length > 0) {\n throw new WorkspaceConflictError(workspacePath, resolvedRef, conflicts);\n }\n // Non-conflict rebase failure (e.g. unknown ref). Surface it raw.\n throw err;\n }\n // Replayed commits = the new HEAD..resolvedRef gap, but oldest-first\n // and limited to what was actually replayed from preHead. We use\n // `git log --reverse <merge-base>..HEAD` where the merge base is\n // computed against resolvedRef, since after a successful rebase\n // HEAD's history above the base IS the replayed set.\n const mergeBase = await run(\"git\", [\"merge-base\", \"HEAD\", resolvedRef], workspacePath).catch(\n () => preHead,\n );\n const logOut = await run(\n \"git\",\n [\"log\", \"--reverse\", \"--format=%s\", `${mergeBase}..HEAD`],\n workspacePath,\n );\n const replayed = logOut.length === 0 ? [] : logOut.split(\"\\n\");\n return { fromRef: resolvedRef, replayed, conflicts: [] };\n },\n\n // List commits in (baseRef..HEAD), oldest-first. The format string\n // packs four NUL-delimited fields per record, then a record\n // separator '\\x1e' (RECORD-SEPARATOR control char) so subjects /\n // bodies with embedded newlines or NULs survive parsing. We don't\n // use a JSON template because git's `--format` is field-oriented;\n // %x00 (NUL) and %x1e (RS) are git's portable escape sequences.\n async commitsSinceBase(workspacePath, baseRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs git: workspace path missing: ${workspacePath}`);\n }\n const out = await run(\n \"git\",\n [\"log\", \"--reverse\", \"-z\", \"--format=%H%x00%s%x00%b%x00%aI\", `${baseRef}..HEAD`],\n workspacePath,\n );\n if (out.length === 0) return [];\n // -z makes git use NUL as the record separator; combined with our\n // %x00 field separators each commit looks like:\n // <sha>\\0<subject>\\0<body>\\0<authorDate>\\0\n // i.e. four fields followed by the record-terminating NUL git\n // injects with -z. Splitting on NUL leaves us with 4N fields plus\n // a trailing empty string we drop.\n const fields = out.split(\"\\x00\");\n const records: CommitSummary[] = [];\n for (let i = 0; i + 3 < fields.length; i += 4) {\n const sha = fields[i] ?? \"\";\n const subject = fields[i + 1] ?? \"\";\n const body = fields[i + 2] ?? \"\";\n const authorDate = fields[i + 3] ?? \"\";\n if (sha.length === 0) continue;\n records.push(commitSummary(sha, subject, body, authorDate));\n }\n return records;\n },\n\n async recentCommits(projectRoot, limit) {\n if (!existsSync(projectRoot)) {\n throw new Error(`vcs git: project root missing: ${projectRoot}`);\n }\n const n = saneLimit(limit);\n if (n === 0) return [];\n const out = await run(\n \"git\",\n [\"log\", `--max-count=${n}`, \"-z\", \"--format=%H%x00%s%x00%b%x00%aI%x00%an\"],\n projectRoot,\n );\n return parseGitZRecords(out);\n },\n\n async showCommit(projectRoot, sha) {\n return runShow(\"git\", [\"show\", sha, \"--stat\", \"-p\", \"--color=always\"], projectRoot);\n },\n\n async freeWorkspace(opts) {\n // Disk-missing case: a previous caller (or the user) ran `rm -rf`\n // out from under us, but the git worktree registry STILL has an\n // entry pointing here. Without a prune, the next `git worktree\n // add` at this path errors out (the mufeedback case). We can't\n // reach the project root via the workspace itself (the .git\n // pointer file is gone with the dir), but `worktree prune` runs\n // from inside any git repo and reaps every dead worktree. We\n // can't reliably guess WHICH project root, so log it as a hint\n // in the result rather than running prune ourselves; the spawn\n // path's defensive prune (above) will clean it on next use.\n if (!existsSync(opts.workspacePath)) {\n return { removed: false };\n }\n let committedRef: string | undefined;\n if (opts.commit) {\n // Commit only if there's anything to commit. Reuse the same\n // dirty-file semantics as isClean() and rebaseTo(): `git status\n // --porcelain` includes working-tree, staged, and untracked-not-\n // ignored changes.\n const dirty = (await listGitDirtyFiles(opts.workspacePath)).length > 0;\n if (dirty) {\n await run(\"git\", [\"add\", \"-A\"], opts.workspacePath);\n await run(\n \"git\",\n [\n \"-c\",\n \"user.email=mu@local\",\n \"-c\",\n \"user.name=mu\",\n \"commit\",\n \"-m\",\n \"mu workspace free auto-commit\",\n ],\n opts.workspacePath,\n );\n committedRef = await run(\"git\", [\"rev-parse\", \"HEAD\"], opts.workspacePath);\n }\n }\n // Tear down: git worktree remove --force <path> cleans both the\n // on-disk directory AND the git/worktrees/<name>/ admin entry. We\n // can't easily run it from the project root (we don't store it on\n // the workspace row), but git accepts the worktree's own path from\n // anywhere with --force, so we resolve a usable cwd via git's own\n // pointer back to the project's .git dir.\n const projectRoot = await resolveGitProjectRoot(opts.workspacePath);\n if (projectRoot) {\n await run(\"git\", [\"worktree\", \"remove\", \"--force\", opts.workspacePath], projectRoot);\n } else {\n // Lost the link — just rm the directory. git's admin entry will\n // be cleaned by the next `git worktree prune` invocation.\n rmDirSync(opts.workspacePath);\n }\n const result: FreeWorkspaceResult = { removed: true };\n if (committedRef !== undefined) result.committedRef = committedRef;\n return result;\n },\n\n async listDirtyFiles(workspacePath) {\n if (!existsSync(workspacePath)) return [];\n return listGitDirtyFiles(workspacePath);\n },\n};\n\n/**\n * Resolve the workspace's notion of \"main\". Tries, in order:\n * 1. `origin/HEAD` — the symbolic ref the remote published\n * (e.g. \"refs/remotes/origin/main\"). The most accurate signal.\n * 2. `refs/remotes/origin/main` — the convention.\n * 3. `refs/remotes/origin/master` — pre-rename convention.\n * Returns the resolved ref string (suitable for `git rev-list`) or\n * undefined if none of the three resolve.\n */\nasync function resolveGitMainRef(workspacePath: string): Promise<string | undefined> {\n for (const candidate of [\n \"refs/remotes/origin/HEAD\",\n \"refs/remotes/origin/main\",\n \"refs/remotes/origin/master\",\n ]) {\n try {\n await exec(\"git\", [\"rev-parse\", \"--verify\", \"--quiet\", candidate], { cwd: workspacePath });\n return candidate;\n } catch {\n /* try next */\n }\n }\n return undefined;\n}\n\n/**\n * Return the list of dirty paths (working-tree modifications + staged\n * + untracked-not-ignored), one entry per file. Empty when clean.\n * Used by `rebaseTo` to refuse with WorkspaceDirtyError carrying the\n * file list — one error message tells the operator both that the\n * workspace is dirty and which files to deal with.\n */\nasync function listGitDirtyFiles(workspacePath: string): Promise<string[]> {\n // `git status --porcelain` prints one line per changed file with\n // a 2-char status prefix; trim the prefix to get the path. Untracked\n // files appear as `?? path` and are included by default.\n const { stdout } = await exec(\"git\", [\"status\", \"--porcelain\"], { cwd: workspacePath });\n const lines = stdout.split(\"\\n\").filter((l) => l.length > 0);\n return lines.map((l) => l.slice(3));\n}\n\n/**\n * Return the unmerged paths after a failed `git rebase`. Used to\n * enrich WorkspaceConflictError before we abort. Empty list means the\n * rebase failed for a non-conflict reason (unknown ref, etc).\n */\nasync function listGitUnmergedPaths(workspacePath: string): Promise<string[]> {\n try {\n const out = await run(\"git\", [\"diff\", \"--name-only\", \"--diff-filter=U\"], workspacePath);\n return out.length === 0 ? [] : out.split(\"\\n\").filter((l) => l.length > 0);\n } catch {\n return [];\n }\n}\n\n/**\n * Resolve the project root that owns this worktree. Returns undefined\n * if the link is broken (e.g. project root deleted). git rev-parse\n * --git-common-dir gives us the parent project's .git dir; the worktree's\n * project root is its parent.\n */\nasync function resolveGitProjectRoot(workspacePath: string): Promise<string | undefined> {\n try {\n const { stdout } = await exec(\n \"git\",\n [\"rev-parse\", \"--path-format=absolute\", \"--git-common-dir\"],\n { cwd: workspacePath },\n );\n return resolve(stdout.trim(), \"..\");\n } catch {\n return undefined;\n }\n}\n\nfunction parseGitZRecords(raw: string): CommitSummary[] {\n if (raw.length === 0) return [];\n const fields = raw.split(\"\\x00\");\n const records: CommitSummary[] = [];\n for (let i = 0; i + 4 < fields.length; i += 5) {\n const sha = fields[i] ?? \"\";\n if (sha.length === 0) continue;\n records.push(\n commitSummary(\n sha,\n fields[i + 1] ?? \"\",\n fields[i + 2] ?? \"\",\n fields[i + 3] ?? \"\",\n fields[i + 4] ?? \"\",\n ),\n );\n }\n return records;\n}\n","// mu — shared helpers for concrete VCS backends.\n\nimport { execFile } from \"node:child_process\";\nimport { existsSync, rmSync } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { type CommitSummary, SHOW_COMMIT_MAX_CHARS, type ShowCommitResult } from \"./types.js\";\n\nexport const exec = promisify(execFile);\n\nexport async function probeVcsRootPath(\n cmd: string,\n args: string[],\n cwd: string,\n): Promise<string | null> {\n try {\n const stdout = await run(cmd, args, cwd);\n const root = stdout.trim();\n return root.length > 0 ? root : null;\n } catch {\n // Tool not installed, or tool exited non-zero because cwd is not\n // in that backend's repo type. Both mean \"not this backend\".\n return null;\n }\n}\n\nexport async function probeVcsRoot(cmd: string, args: string[], cwd: string): Promise<boolean> {\n return (await probeVcsRootPath(cmd, args, cwd)) !== null;\n}\n\nexport function isSaplingDotdir(dotdir: string): boolean {\n return /(?:^|[\\\\/])\\.(?:sl|hg)$/.test(dotdir);\n}\n\nexport async function ensureParent(path: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n}\n\nexport function rmDirSync(path: string): boolean {\n if (!existsSync(path)) return false;\n rmSync(path, { recursive: true, force: true });\n return true;\n}\n\n/**\n * Run a binary with args. Throws a typed Error on non-zero exit. Stdout\n * is returned trimmed; stderr is appended to the Error message.\n */\nexport async function run(bin: string, args: readonly string[], cwd?: string): Promise<string> {\n try {\n const { stdout } = await exec(bin, [...args], { cwd, maxBuffer: 16 * 1024 * 1024 });\n return stdout.trim();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`vcs ${bin} ${args.join(\" \")} failed: ${msg}`);\n }\n}\n\nexport async function runShow(\n bin: string,\n args: readonly string[],\n cwd?: string,\n): Promise<ShowCommitResult> {\n try {\n const { stdout } = await exec(bin, [...args], {\n cwd,\n maxBuffer: SHOW_COMMIT_MAX_CHARS * 2,\n });\n if (stdout.length > SHOW_COMMIT_MAX_CHARS) {\n return {\n text: `${stdout.slice(0, SHOW_COMMIT_MAX_CHARS)}\\n…(truncated at ${SHOW_COMMIT_MAX_CHARS} chars)`,\n truncated: true,\n };\n }\n return { text: stdout, truncated: false };\n } catch (err) {\n return {\n text: \"\",\n truncated: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nfunction relTimeFromIso(iso: string): string {\n const t = Date.parse(iso);\n if (!Number.isFinite(t)) return \"—\";\n const sec = Math.max(0, Math.floor((Date.now() - t) / 1000));\n if (sec < 60) return `${sec}s`;\n const min = Math.floor(sec / 60);\n if (min < 60) return `${min}m`;\n const hr = Math.floor(min / 60);\n if (hr < 24) return `${hr}h`;\n const day = Math.floor(hr / 24);\n if (day < 7) return `${day}d`;\n return `${Math.floor(day / 7)}w`;\n}\n\nexport function commitSummary(\n sha: string,\n subject: string,\n body: string,\n authorDate: string,\n author = \"\",\n): CommitSummary {\n return { sha, subject, body, author, authorDate, relTime: relTimeFromIso(authorDate) };\n}\n\nexport function saneLimit(limit: number): number {\n if (!Number.isFinite(limit)) return 25;\n return Math.max(0, Math.min(200, Math.floor(limit)));\n}\n\n/**\n * Parse the NUL-field / \\x1e-record format used by the jj/sl\n * commitsSinceBase impls. Each record is `sha\\0subject\\0body\\0date`\n * terminated by \\x1e. Empty input → [].\n */\nexport function parseNulRecords(raw: string): CommitSummary[] {\n if (raw.length === 0) return [];\n const records: CommitSummary[] = [];\n for (const rec of raw.split(\"\\x1e\")) {\n if (rec.length === 0) continue;\n const fields = rec.split(\"\\x00\");\n const sha = fields[0] ?? \"\";\n if (sha.length === 0) continue;\n records.push(\n commitSummary(sha, fields[1] ?? \"\", fields[2] ?? \"\", fields[3] ?? \"\", fields[4] ?? \"\"),\n );\n }\n return records;\n}\n","// mu — shared VCS backend contracts and typed errors.\n\nimport type { HasNextSteps, NextStep } from \"../output.js\";\n\nexport type VcsBackendName = \"jj\" | \"sl\" | \"git\" | \"none\";\n\n// ─── Refresh / rebase result + typed errors ──────────────────────────\n//\n// rebaseTo is the backend-side of `mu workspace refresh`. The errors\n// live in this module (not src/workspace.ts) because they're thrown\n// from inside the backend impls; workspace.ts imports vcs.ts, and the\n// root vcs hub re-exports these concrete definitions.\n\nexport interface RebaseResult {\n /** The ref the workspace was actually rebased onto (resolved\n * symbolic-or-revset → concrete name). For git that is the\n * resolveGitMainRef() symbolic ref; for jj/sl it's the literal\n * `trunk()` revset (or whatever the operator passed via fromRef). */\n fromRef: string;\n /** Commit subjects (or descriptions) that got replayed, oldest-first.\n * Empty when the workspace was already at fromRef (no-op). */\n replayed: string[];\n /** Files / commits that conflicted during the rebase. Always\n * empty for a successful rebase — a non-empty conflicts list\n * means we threw WorkspaceConflictError before returning. The\n * field exists so the error's serialised payload can carry it. */\n conflicts: string[];\n}\n\nexport interface CommitSummary {\n /** Full commit / change id. */\n sha: string;\n /** First-line description / subject. */\n subject: string;\n /** Remainder of the commit message (may be empty). */\n body: string;\n /** Author display name, when the backend exposes one. */\n author: string;\n /** ISO-8601 author / commit timestamp. */\n authorDate: string;\n /** Compact relative author time (e.g. \"3m\", \"2d\"). */\n relTime: string;\n}\n\nexport interface ShowCommitResult {\n /** Captured VCS show output (possibly truncated). Empty string on error. */\n text: string;\n /** True when stdout exceeded SHOW_COMMIT_MAX_CHARS and was clipped. */\n truncated: boolean;\n /** Human-readable error message; omitted on success. */\n error?: string;\n}\n\n/** Cap captured `show` output so giant merge commits can't eat the TUI. */\nexport const SHOW_COMMIT_MAX_CHARS = 100_000;\n\n/**\n * Thrown by `rebaseTo` / `commitsSinceBase` on the `none` backend\n * (cp -a snapshots have no notion of a rebase target / fork point).\n * Maps to exit code 4.\n */\nexport class WorkspaceVcsRequiredError extends Error implements HasNextSteps {\n override readonly name = \"WorkspaceVcsRequiredError\";\n constructor(\n public readonly verb: string,\n public readonly workspacePath: string,\n ) {\n super(\n `vcs none: \\`mu workspace ${verb}\\` requires a real VCS (jj/sl/git); ${workspacePath} is a cp -a snapshot`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Free the snapshot and re-create with a real VCS backend\",\n command: \"mu workspace free <agent> && mu workspace create <agent> --backend <jj|sl|git>\",\n },\n ];\n }\n}\n\n/**\n * Thrown by `rebaseTo` when the workspace has uncommitted changes\n * the rebase would clobber. Carries the dirty file list so the operator\n * can decide between commit/stash/--force. Maps to exit code 4.\n */\nexport class WorkspaceDirtyError extends Error implements HasNextSteps {\n override readonly name = \"WorkspaceDirtyError\";\n /** The verb that refused (\"rebase\", \"recreate\", ...). Used to make\n * the error message + nextSteps point the operator at the right\n * escape hatch (e.g. recreate's `--force`). Default \"rebase\" for\n * backward compatibility with the original rebaseTo call sites. */\n public readonly verb: string;\n constructor(\n public readonly workspacePath: string,\n public readonly files: readonly string[],\n verb = \"rebase\",\n ) {\n super(\n `workspace dirty (${files.length} uncommitted file(s)): ${workspacePath}; refusing to ${verb}`,\n );\n this.verb = verb;\n }\n errorNextSteps(): NextStep[] {\n const steps: NextStep[] = [\n {\n intent: \"Inspect the dirty files\",\n command: `(cd ${this.workspacePath} && git status -s) # or jj st / sl st`,\n },\n {\n intent: `Commit them first, then retry ${this.verb}`,\n command: `(cd ${this.workspacePath} && git add -A && git commit -m WIP)`,\n },\n {\n intent: \"Or stash them first (git only)\",\n command: `(cd ${this.workspacePath} && git stash)`,\n },\n ];\n if (this.verb === \"recreate\") {\n steps.push({\n intent: \"Or DISCARD all uncommitted changes (the lossy escape)\",\n command: \"mu workspace recreate <agent> --force\",\n });\n }\n return steps;\n }\n}\n\n/**\n * Thrown by `rebaseTo` when the rebase produced conflicts the\n * operator must resolve manually. Carries the conflicting paths.\n * Maps to exit code 5.\n */\nexport class WorkspaceConflictError extends Error implements HasNextSteps {\n override readonly name = \"WorkspaceConflictError\";\n constructor(\n public readonly workspacePath: string,\n public readonly fromRef: string,\n public readonly conflicts: readonly string[],\n ) {\n super(`rebase onto ${fromRef} produced ${conflicts.length} conflict(s): ${workspacePath}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"cd into the workspace and resolve\",\n command: `cd ${this.workspacePath} # then resolve & commit; or: git rebase --abort / jj abandon / sl rebase --abort`,\n },\n ];\n }\n}\n\nexport interface CreateWorkspaceOptions {\n /** The repository being branched from. Absolute path. */\n projectRoot: string;\n /** Where to place the new workspace. Absolute path; must NOT exist. */\n workspacePath: string;\n /** Optional commit / branch / changeset id to base off. Backend-specific:\n * git uses it as a `git worktree add`'s ref, jj as a revset, sl as a\n * rev. Undefined = current head. */\n parentRef?: string;\n}\n\nexport interface CreateWorkspaceResult {\n /** The actual ref the workspace points at (resolved to a stable id\n * when possible). Stored on the row; useful for `mu workspace list`\n * and for `--commit` flows. May be null for backends that don't\n * expose a meaningful parent (e.g. `none`). */\n parentRef: string | null;\n}\n\nexport interface FreeWorkspaceOptions {\n workspacePath: string;\n /** If true, attempt to commit any pending changes BEFORE removal.\n * Backend-specific: jj auto-commits via `jj describe + jj new`, git\n * needs an explicit commit on the worktree, sl needs `sl commit`,\n * none has nothing to commit. If pending changes exist and `commit`\n * is false, the on-disk directory still gets removed and changes are\n * lost — the verb prints a clear warning. */\n commit: boolean;\n}\n\nexport interface FreeWorkspaceResult {\n /** The commit id that captured the pending changes, when `commit` was\n * true and there was something to commit. Otherwise undefined. */\n committedRef?: string;\n /** True iff the on-disk path was actually removed (vs. already gone). */\n removed: boolean;\n}\n\nexport interface VcsBackend {\n readonly name: VcsBackendName;\n\n /** True iff this backend should handle `projectRoot`. Implementations\n * check for the relevant marker dir (`.jj`, `.sl`, `.git`); `none`\n * always returns true and is consulted last. */\n detect(projectRoot: string): Promise<boolean>;\n\n createWorkspace(opts: CreateWorkspaceOptions): Promise<CreateWorkspaceResult>;\n\n freeWorkspace(opts: FreeWorkspaceOptions): Promise<FreeWorkspaceResult>;\n\n /**\n * Count commits that the project's default branch (\"main\") has but\n * `ref` does not — i.e. how many commits `ref` is BEHIND main.\n *\n * Used by `mu workspace list` and `mu state` to surface staleness\n * (bug_workspace_stale_parent_silent_drift). Cheap, pure-observation:\n * NO automatic fetch. We compare against whatever main resolves to in\n * the workspace's LOCAL refs cache. The user can `git fetch` (or\n * equivalent) themselves if they want a fresher number.\n *\n * Returns null when:\n * - main / trunk cannot be resolved (no origin/HEAD, no origin/main,\n * no origin/master, no trunk() bookmark, etc.)\n * - the underlying VCS command fails for any reason (detached worktree,\n * missing refs, the `none` backend which has no notion of \"main\")\n *\n * Callers treat null as \"unknown — render — — and don't warn\".\n */\n commitsBehind(workspacePath: string, ref: string): Promise<number | null>;\n\n /**\n * Rebase the workspace onto `fromRef` (or the backend's tracked\n * base when undefined: `origin/HEAD` for git, `trunk()` for jj/sl).\n * Returns the resolved ref + replayed commits + conflicts list.\n *\n * Backend-specific behaviour:\n * - git: refuses on dirty WC (WorkspaceDirtyError); fetches first;\n * `git rebase <ref>`. On conflict, aborts the rebase and throws\n * WorkspaceConflictError so the operator resolves manually.\n * - jj: always-snapshotted, so dirty is never an issue. After\n * `jj rebase -d <ref>` the conflict-set is queried via\n * `jj log -r 'conflict()'`. Conflicts surface as\n * WorkspaceConflictError without an abort (jj's conflict markers\n * persist as commits; the operator resolves in-place).\n * - sl: similar to jj. `sl rebase -d <ref>`; conflicts via\n * `sl resolve -l`. On dirty WC sl errors itself; we wrap that\n * into WorkspaceDirtyError.\n * - none: throws WorkspaceVcsRequiredError unconditionally.\n *\n * Surfaced by fb_workspace_recycle_verb: dogfood between waves\n * needed `close → free → spawn` to refresh a worker against new\n * main; that killed the worker's LLM context. `refresh` updates\n * the on-disk dir without touching the agent or pane.\n */\n rebaseTo(workspacePath: string, fromRef?: string): Promise<RebaseResult>;\n\n /**\n * Cheap \"is the working copy clean?\" probe used by close-auto-free\n * (allow_mu_agent_close_without_discard). Definition: ZERO uncommitted\n * changes (no working-tree modifications, no staged changes, no\n * untracked-not-ignored files). Pure observation; no fetch, no commit.\n *\n * Backend-specific:\n * - git: empty `git status --porcelain` output.\n * - jj: jj is auto-snapshotted, so the @ commit IS the WC; clean\n * here means @ has no diff from its parent (empty `jj diff\n * -r @ --summary`). A description-only difference still\n * counts as clean.\n * - sl: empty `sl status` output.\n * - none: meaningless (cp -a snapshot has no notion of\n * \"committed\" vs \"uncommitted\"); always returns true so the\n * close-auto-free path treats every none-workspace as\n * eligible for silent free (no commits can be lost; the only\n * loss is local file edits, which the operator implicitly\n * accepts by closing the agent).\n *\n * Returns false on any backend command failure — be conservative\n * (we'd rather refuse a close than auto-free a workspace whose\n * cleanliness we couldn't verify).\n */\n isClean(workspacePath: string): Promise<boolean>;\n\n /**\n * List commits the workspace has on top of `baseRef`, oldest-first.\n * Used by `mu workspace commits` (fb_workspace_commits_verb) to\n * promote the dogfood-painful\n * cd $(mu workspace path X) && git log <base>..HEAD\n * incantation into a typed verb that knows the workspace's\n * parent_ref. The CommitSummary fields survive subjects/bodies with\n * embedded newlines (NUL-delimited record format on the wire).\n *\n * `none` throws WorkspaceVcsRequiredError. Returns `[]` when the\n * workspace is exactly at baseRef (no commits since fork). Throws\n * on backend command failure (unknown ref, missing repo).\n */\n commitsSinceBase(workspacePath: string, baseRef: string): Promise<CommitSummary[]>;\n\n /** Last N commits on the project root, newest-first. Used by the\n * TUI Commits card / popup. Unlike commitsSinceBase, this is NOT\n * a per-workspace since-fork query. */\n recentCommits(projectRoot: string, limit: number): Promise<CommitSummary[]>;\n\n /** Show one commit / change from the project root, capped for TUI\n * rendering. Backend-specific equivalent of `git show <sha>`. */\n showCommit(projectRoot: string, sha: string): Promise<ShowCommitResult>;\n\n /**\n * Return the list of dirty (uncommitted / unstaged / untracked-not-\n * ignored) paths in the workspace. Empty array = clean.\n *\n * Used by `mu workspace recreate` to refuse a free+create cycle on\n * a dirty workspace unless the operator passes `--force` (the lossy\n * escape hatch). Mirrors the dirty-check `rebaseTo` does internally.\n *\n * Backend semantics:\n * - git: `git status --porcelain` (working-tree + staged +\n * untracked-not-ignored, mirroring the rebaseTo path).\n * - sl: `sl status` parsed for non-empty output.\n * - jj: always-snapshotted, so no concept of \"dirty\" — returns [].\n * - none: cp -a snapshots have no VCS, so we can't decide \"dirty\";\n * returns [] so the caller doesn't refuse for an unanswerable\n * question.\n *\n * Throws on backend command failure (the operator should see a\n * real error, not a silent \"clean\").\n */\n listDirtyFiles(workspacePath: string): Promise<string[]>;\n}\n","// mu — jj VCS backend.\n\nimport { existsSync } from \"node:fs\";\nimport {\n ensureParent,\n parseNulRecords,\n probeVcsRoot,\n rmDirSync,\n run,\n runShow,\n saneLimit,\n} from \"./helpers.js\";\nimport { type FreeWorkspaceResult, type VcsBackend, WorkspaceConflictError } from \"./types.js\";\n\n// `jj workspace add --name <name> <path>` shares the .jj/repo store\n// while giving each agent its own working copy. Workspaces are named;\n// we use basename(workspacePath) which (per our on-disk layout) is the\n// agent name.\n//\n// Free is two-step: `jj workspace forget <name>` from the workspace\n// itself unregisters; then we rm the dir since jj leaves the files\n// behind.\n//\n// --commit semantics for jj: jj's working copy is always automatically\n// snapshotted, so \"commit\" is really \"capture the current change_id\n// as the result.\" Nothing is ever lost; jj keeps all operations in\n// its op log indefinitely. We additionally call `jj describe` to set\n// a description IF the current commit's description is empty so the\n// captured ref is human-discoverable.\n\nexport const jjBackend: VcsBackend = {\n name: \"jj\",\n\n async detect(projectRoot) {\n return probeVcsRoot(\"jj\", [\"root\"], projectRoot);\n },\n\n async createWorkspace(opts) {\n if (existsSync(opts.workspacePath)) {\n throw new Error(`vcs jj: workspacePath already exists: ${opts.workspacePath}`);\n }\n await ensureParent(opts.workspacePath);\n const name = jjWorkspaceName(opts.workspacePath);\n const args = [\"workspace\", \"add\", \"--name\", name];\n if (opts.parentRef) args.push(\"--revision\", opts.parentRef);\n args.push(opts.workspacePath);\n await run(\"jj\", args, opts.projectRoot);\n const commitId = await jjCommitId(opts.workspacePath);\n return { parentRef: commitId };\n },\n\n async freeWorkspace(opts) {\n if (!existsSync(opts.workspacePath)) {\n return { removed: false };\n }\n const name = jjWorkspaceName(opts.workspacePath);\n let committedRef: string | undefined;\n if (opts.commit) {\n const desc = await run(\n \"jj\",\n [\n \"log\",\n \"-r\",\n \"@\",\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"--template\",\n \"description\",\n ],\n opts.workspacePath,\n );\n if (desc.trim().length === 0) {\n await run(\"jj\", [\"describe\", \"-m\", \"mu workspace free auto-commit\"], opts.workspacePath);\n }\n committedRef = await jjCommitId(opts.workspacePath);\n }\n // `jj workspace forget` works from inside the workspace itself.\n // jj prints a hint about the working copy becoming orphaned;\n // we resolve that immediately by rm-ing the dir.\n await run(\"jj\", [\"workspace\", \"forget\", name], opts.workspacePath);\n rmDirSync(opts.workspacePath);\n const result: FreeWorkspaceResult = { removed: true };\n if (committedRef !== undefined) result.committedRef = committedRef;\n return result;\n },\n\n // jj working-copy clean: @ has no diff from its parent.\n // `jj diff -r @ --summary` prints one line per changed file; empty\n // stdout = clean. jj's auto-snapshotting means there's no separate\n // \"untracked\" bucket — every working-tree change is already in @.\n async isClean(workspacePath) {\n if (!existsSync(workspacePath)) return false;\n try {\n const out = await run(\n \"jj\",\n [\"diff\", \"-r\", \"@\", \"--summary\", \"--no-pager\", \"--color\", \"never\"],\n workspacePath,\n );\n return out.length === 0;\n } catch {\n return false;\n }\n },\n\n // Compute commits-behind via jj's `trunk()` revset, which resolves\n // to the project's configured trunk (default-branch heuristic).\n // Returns null when trunk() is unresolvable (e.g. fresh repo with\n // no configured trunk) or when the log call fails.\n //\n // Pure observation: NO `jj git fetch`.\n async commitsBehind(workspacePath, ref) {\n if (!existsSync(workspacePath)) return null;\n try {\n // `<ref>..trunk()` is the set of commits reachable from trunk\n // but not from ref — exactly the staleness number. Template `\"x\\n\"`\n // gives one line per commit, which we count.\n const out = await run(\n \"jj\",\n [\n \"log\",\n \"-r\",\n `${ref}..trunk()`,\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"--template\",\n '\"x\\\\n\"',\n ],\n workspacePath,\n );\n if (out.length === 0) return 0;\n return out.split(\"\\n\").filter((l) => l.length > 0).length;\n } catch {\n return null;\n }\n },\n\n // Rebase the workspace's @ onto `fromRef` (default = `trunk()`).\n // jj is always-snapshotted so dirty WC is never an issue — the auto-\n // snapshot becomes part of the rebase. After the rebase we query\n // `conflict()` to surface any commits that ended up conflicted; jj\n // doesn't auto-abort on conflicts (they materialise as commits with\n // conflict markers), so the workspace is left in a state the\n // operator can resolve in-place.\n async rebaseTo(workspacePath, fromRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs jj: workspace path missing: ${workspacePath}`);\n }\n const target = fromRef ?? \"trunk()\";\n // Snapshot the pre-rebase change_id so we can compute replayed\n // descriptions afterwards. `@` is the working-copy commit.\n const preRev = await run(\n \"jj\",\n [\"log\", \"-r\", \"@\", \"--no-graph\", \"--no-pager\", \"--color\", \"never\", \"--template\", \"change_id\"],\n workspacePath,\n );\n await run(\"jj\", [\"rebase\", \"-d\", target], workspacePath);\n // Replayed = descriptions of commits in (target..@), oldest-first.\n // Template prints `description ++ \"\\n\\x00\"` so multi-line descs\n // survive splitting; we keep the first non-empty line as subject.\n const replayedRaw = await run(\n \"jj\",\n [\n \"log\",\n \"-r\",\n `${target}..@`,\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"--reversed\",\n \"--template\",\n 'description.first_line() ++ \"\\\\n\"',\n ],\n workspacePath,\n ).catch(() => \"\");\n const replayed = replayedRaw\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n // Conflict surface: list change_ids of commits in the rebased\n // range that are conflicted. Empty = clean rebase.\n const conflictRaw = await run(\n \"jj\",\n [\n \"log\",\n \"-r\",\n `(${target}..@) & conflict()`,\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"--template\",\n 'change_id.short() ++ \"\\\\n\"',\n ],\n workspacePath,\n ).catch(() => \"\");\n const conflicts = conflictRaw\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n if (conflicts.length > 0) {\n throw new WorkspaceConflictError(workspacePath, target, conflicts);\n }\n // Use preRev so future-resolution of @ at call time is irrelevant.\n void preRev;\n return { fromRef: target, replayed, conflicts: [] };\n },\n\n // List jj commits in (baseRef..@), oldest-first. jj's templating\n // gives us per-field strings; we glue them with NUL field-separators\n // and \\x1e record-separators so multi-line descriptions/bodies\n // round-trip cleanly. The author timestamp template is\n // `author.timestamp().format(\"%Y-%m-%dT%H:%M:%S%:z\")` which is\n // ISO-8601 (matches git's --aiso strict / --aI).\n async commitsSinceBase(workspacePath, baseRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs jj: workspace path missing: ${workspacePath}`);\n }\n const out = await run(\n \"jj\",\n [\n \"log\",\n \"-r\",\n `${baseRef}..@`,\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"--reversed\",\n \"--template\",\n jjCommitSummaryTemplate,\n ],\n workspacePath,\n );\n return parseNulRecords(out);\n },\n\n async recentCommits(projectRoot, limit) {\n if (!existsSync(projectRoot)) {\n throw new Error(`vcs jj: project root missing: ${projectRoot}`);\n }\n const n = saneLimit(limit);\n if (n === 0) return [];\n const out = await run(\n \"jj\",\n [\n \"log\",\n \"--no-graph\",\n \"--no-pager\",\n \"--color\",\n \"never\",\n \"-r\",\n \"::@\",\n \"--limit\",\n String(n),\n \"--template\",\n jjCommitSummaryTemplate,\n ],\n projectRoot,\n );\n return parseNulRecords(out);\n },\n\n async showCommit(projectRoot, sha) {\n return runShow(\"jj\", [\"show\", sha, \"--color\", \"always\"], projectRoot);\n },\n\n // jj is always-snapshotted: there is no \"uncommitted\" state. The\n // working copy is itself a commit; the next snapshot folds any\n // edits in. Surface that by returning [] so `recreateWorkspace`\n // never refuses a jj workspace as \"dirty\".\n async listDirtyFiles(_workspacePath) {\n return [];\n },\n};\n\nconst jjCommitSummaryTemplate =\n 'commit_id ++ \"\\\\x00\" ++ description.first_line() ++ \"\\\\x00\" ++ description ++ \"\\\\x00\" ++ author.timestamp().format(\"%Y-%m-%dT%H:%M:%S%:z\") ++ \"\\\\x00\" ++ author.name() ++ \"\\\\x1e\"';\n\nfunction jjWorkspaceName(workspacePath: string): string {\n // basename of /foo/bar/worker-1 → worker-1\n return workspacePath.replace(/\\/+$/, \"\").split(\"/\").pop() ?? workspacePath;\n}\n\nasync function jjCommitId(workspacePath: string): Promise<string> {\n return run(\n \"jj\",\n [\"log\", \"-r\", \"@\", \"--no-graph\", \"--no-pager\", \"--color\", \"never\", \"--template\", \"commit_id\"],\n workspacePath,\n );\n}\n","// mu — non-VCS fallback backend.\n\nimport { existsSync } from \"node:fs\";\nimport { ensureParent, rmDirSync, run } from \"./helpers.js\";\nimport { type VcsBackend, WorkspaceVcsRequiredError } from \"./types.js\";\n\n// The fallback for projects that aren't under any VCS we recognise.\n// `cp -a` is heavy but correct; the workspace is a full snapshot.\n// Free deletes the snapshot. No commit semantics (no VCS to commit\n// against), so `--commit` is silently ignored.\n\nexport const noneBackend: VcsBackend = {\n name: \"none\",\n\n async detect(_projectRoot) {\n return true;\n },\n\n async createWorkspace(opts) {\n if (existsSync(opts.workspacePath)) {\n throw new Error(`vcs none: workspacePath already exists: ${opts.workspacePath}`);\n }\n await ensureParent(opts.workspacePath);\n // `cp -a` is GNU/BSD-portable for \"preserve everything, recursive\".\n await run(\"cp\", [\"-a\", `${opts.projectRoot}/.`, opts.workspacePath]);\n return { parentRef: null };\n },\n\n async freeWorkspace(opts) {\n const removed = rmDirSync(opts.workspacePath);\n return { removed };\n },\n\n // The `none` backend has no VCS to compare against — there's no\n // notion of \"main\" for a `cp -a` snapshot. Always null.\n async commitsBehind(_workspacePath, _ref) {\n return null;\n },\n\n // none has no notion of clean — a cp -a snapshot doesn't track\n // committed vs uncommitted state. Returning true makes the\n // close-auto-free path silently free a none-workspace (consistent\n // with the fact that there are no commits to lose).\n async isClean(_workspacePath) {\n return true;\n },\n\n // none has no upstream to rebase onto. Throw a typed error so the\n // CLI's handle() maps it to exit 4 with a clean Next: hint.\n async rebaseTo(workspacePath, _fromRef) {\n throw new WorkspaceVcsRequiredError(\"refresh\", workspacePath);\n },\n\n // none has no notion of a fork point either — a cp -a snapshot\n // doesn't track history. Same typed error as rebaseTo.\n async commitsSinceBase(workspacePath, _baseRef) {\n throw new WorkspaceVcsRequiredError(\"commits\", workspacePath);\n },\n\n async recentCommits(_projectRoot, _limit) {\n return [];\n },\n\n async showCommit(_projectRoot, _sha) {\n return { text: \"\", truncated: false, error: \"vcs none: no commits to show\" };\n },\n\n // No VCS → nothing to compare against; \"dirty\" is unanswerable.\n // Caller (`recreateWorkspace`) treats [] as \"clean\" and proceeds.\n async listDirtyFiles(_workspacePath) {\n return [];\n },\n};\n","// mu — Sapling VCS backend.\n\nimport { existsSync } from \"node:fs\";\nimport {\n ensureParent,\n isSaplingDotdir,\n parseNulRecords,\n probeVcsRootPath,\n rmDirSync,\n run,\n runShow,\n saneLimit,\n} from \"./helpers.js\";\nimport {\n type FreeWorkspaceResult,\n type VcsBackend,\n WorkspaceConflictError,\n WorkspaceDirtyError,\n} from \"./types.js\";\n\n// `sl worktree` exists in Sapling but only for EdenFS-backed repos.\n// For portability we use `sl clone` instead, which works on any\n// sapling install. The trade-off is heavier (history copy) vs lighter\n// (shared store), but the workspace is fully isolated either way —\n// which is what we care about. EdenFS-specific worktree optimization\n// can layer on later if anyone hits the friction.\n//\n// Free is just rm -rf: sapling has no formal \"unclone\" because each\n// clone is a self-contained repo.\n\nexport const slBackend: VcsBackend = {\n name: \"sl\",\n\n async detect(projectRoot) {\n const root = await probeVcsRootPath(\"sl\", [\"root\"], projectRoot);\n if (root === null) return false;\n // Meta's Sapling build can transparently operate in plain git repos\n // by creating `.git/sl`. That should not beat git in BACKENDS order.\n // `--dotdir` keeps true sl / hg-compat repos (`.sl` / `.hg`) distinct.\n const dotdir = await probeVcsRootPath(\"sl\", [\"root\", \"--dotdir\"], projectRoot);\n return dotdir !== null && isSaplingDotdir(dotdir);\n },\n\n async createWorkspace(opts) {\n if (existsSync(opts.workspacePath)) {\n throw new Error(`vcs sl: workspacePath already exists: ${opts.workspacePath}`);\n }\n await ensureParent(opts.workspacePath);\n const args = [\"clone\"];\n if (opts.parentRef) args.push(\"-r\", opts.parentRef);\n args.push(opts.projectRoot, opts.workspacePath);\n await run(\"sl\", args);\n const commitId = await slCommitId(opts.workspacePath);\n return { parentRef: commitId };\n },\n\n async freeWorkspace(opts) {\n if (!existsSync(opts.workspacePath)) {\n return { removed: false };\n }\n let committedRef: string | undefined;\n if (opts.commit && (await slIsDirty(opts.workspacePath))) {\n // sl commit -A: stage all (including untracked), commit. Exits\n // non-zero if nothing changed, but we just guarded above.\n await run(\n \"sl\",\n [\n \"--config\",\n \"ui.username=mu <mu@local>\",\n \"commit\",\n \"-A\",\n \"-m\",\n \"mu workspace free auto-commit\",\n ],\n opts.workspacePath,\n );\n committedRef = await slCommitId(opts.workspacePath);\n }\n rmDirSync(opts.workspacePath);\n const result: FreeWorkspaceResult = { removed: true };\n if (committedRef !== undefined) result.committedRef = committedRef;\n return result;\n },\n\n // sl working-copy clean: empty `sl status` output. Same shape as\n // listSlDirtyFiles below but inlined to keep the failure-mode\n // boundary tight (any throw → not clean).\n async isClean(workspacePath) {\n if (!existsSync(workspacePath)) return false;\n try {\n const out = await run(\"sl\", [\"status\"], workspacePath);\n return out.length === 0;\n } catch {\n return false;\n }\n },\n\n // Same shape as the jj impl: count commits in trunk() not reachable\n // from ref. Sapling's revset language is close enough to jj's that\n // the same idiom works. Returns null when trunk() is unresolvable\n // (fresh repo, missing remote bookmark, etc.) or the log fails.\n //\n // Pure observation: NO `sl pull`.\n async commitsBehind(workspacePath, ref) {\n if (!existsSync(workspacePath)) return null;\n try {\n const out = await run(\n \"sl\",\n [\"log\", \"-r\", `${ref}::trunk() - ${ref}`, \"--template\", \"x\\\\n\"],\n workspacePath,\n );\n if (out.length === 0) return 0;\n return out.split(\"\\n\").filter((l) => l.length > 0).length;\n } catch {\n return null;\n }\n },\n\n // Rebase the active draft chain onto `fromRef` (default = `trunk()`).\n // Sapling refuses on dirty WC by default — we pre-check and convert\n // its error into the typed WorkspaceDirtyError. Conflict surface\n // post-rebase via `sl resolve --list --tool=internal:dumpjson` is\n // brittle across versions, so we use the textual `sl resolve --list`\n // output and look for the U-prefixed lines (unresolved).\n async rebaseTo(workspacePath, fromRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs sl: workspace path missing: ${workspacePath}`);\n }\n const target = fromRef ?? \"trunk()\";\n const dirtyFiles = await listSlDirtyFiles(workspacePath);\n if (dirtyFiles.length > 0) {\n throw new WorkspaceDirtyError(workspacePath, dirtyFiles);\n }\n await run(\n \"sl\",\n [\"--config\", \"ui.username=mu <mu@local>\", \"rebase\", \"-d\", target],\n workspacePath,\n ).catch(() => {\n // Rebase failure is acceptable here — the conflict-listing call\n // below will tell us what happened. Bare exception loss is OK\n // since `sl resolve` is the source of truth on conflicts.\n });\n const conflicts = await listSlUnresolved(workspacePath);\n if (conflicts.length > 0) {\n // Best-effort abort so the workspace returns to a clean state\n // — mirrors the git impl's never-leave-half-rebased policy.\n await run(\"sl\", [\"rebase\", \"--abort\"], workspacePath).catch(() => {});\n throw new WorkspaceConflictError(workspacePath, target, conflicts);\n }\n // Replayed = log of `target..` post-rebase, oldest-first. Single-\n // line subjects via `{desc|firstline}`. Empty when nothing replayed.\n const replayedRaw = await run(\n \"sl\",\n [\"log\", \"-r\", `${target}::. - ${target}`, \"--template\", \"{desc|firstline}\\\\n\"],\n workspacePath,\n ).catch(() => \"\");\n const replayed = replayedRaw\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n return { fromRef: target, replayed, conflicts: [] };\n },\n\n // List sl commits in (baseRef..., minus baseRef itself), oldest-\n // first. Same NUL-field / \\x1e-record format as the jj impl;\n // sl's templating uses {field} braces (not jj's `++` operator) but\n // the field set is equivalent ({node}, {desc|firstline}, {desc},\n // {date|isodate}).\n async commitsSinceBase(workspacePath, baseRef) {\n if (!existsSync(workspacePath)) {\n throw new Error(`vcs sl: workspace path missing: ${workspacePath}`);\n }\n const out = await run(\n \"sl\",\n [\"log\", \"-r\", `${baseRef}::. - ${baseRef}`, \"--template\", slCommitSummaryTemplate],\n workspacePath,\n );\n // sl emits oldest-last by default; reverse to oldest-first to match\n // the git/jj contract.\n return parseNulRecords(out).reverse();\n },\n\n async recentCommits(projectRoot, limit) {\n if (!existsSync(projectRoot)) {\n throw new Error(`vcs sl: project root missing: ${projectRoot}`);\n }\n const n = saneLimit(limit);\n if (n === 0) return [];\n const out = await run(\n \"sl\",\n [\"log\", \"-l\", String(n), \"--template\", slCommitSummaryTemplate],\n projectRoot,\n );\n return parseNulRecords(out);\n },\n\n async showCommit(projectRoot, sha) {\n return runShow(\"sl\", [\"show\", sha, \"--color=always\"], projectRoot);\n },\n\n async listDirtyFiles(workspacePath) {\n if (!existsSync(workspacePath)) return [];\n return listSlDirtyFiles(workspacePath);\n },\n};\n\n/**\n * List dirty paths for a sapling workspace. `sl status` prints one\n * line per changed file: `<status> <path>`. Empty = clean.\n */\nasync function listSlDirtyFiles(workspacePath: string): Promise<string[]> {\n const out = await run(\"sl\", [\"status\"], workspacePath);\n if (out.length === 0) return [];\n return out\n .split(\"\\n\")\n .filter((l) => l.length > 0)\n .map((l) => l.slice(2));\n}\n\n/**\n * List unresolved (conflicting) paths after an `sl rebase`. `sl resolve\n * --list` prints `<U|R> <path>` per file; U = unresolved.\n */\nasync function listSlUnresolved(workspacePath: string): Promise<string[]> {\n try {\n const out = await run(\"sl\", [\"resolve\", \"--list\"], workspacePath);\n if (out.length === 0) return [];\n return out\n .split(\"\\n\")\n .filter((l) => l.startsWith(\"U \"))\n .map((l) => l.slice(2));\n } catch {\n return [];\n }\n}\n\nconst slCommitSummaryTemplate =\n \"{node}\\\\0{desc|firstline}\\\\0{desc}\\\\0{date|isodatesec}\\\\0{author|user}\\\\x1e\";\n\nasync function slCommitId(workspacePath: string): Promise<string> {\n return run(\"sl\", [\"log\", \"-r\", \".\", \"--template\", \"{node}\"], workspacePath);\n}\n\nasync function slIsDirty(workspacePath: string): Promise<boolean> {\n // sl status prints one line per changed file; empty stdout = clean.\n const out = await run(\"sl\", [\"status\"], workspacePath);\n return out.length > 0;\n}\n","// mu — VCS backend dispatcher.\n\nimport { gitBackend } from \"./git.js\";\nimport { jjBackend } from \"./jj.js\";\nimport { noneBackend } from \"./none.js\";\nimport { slBackend } from \"./sl.js\";\nimport type { VcsBackend, VcsBackendName } from \"./types.js\";\n\nexport { gitBackend } from \"./git.js\";\nexport { jjBackend } from \"./jj.js\";\nexport { noneBackend } from \"./none.js\";\nexport { slBackend } from \"./sl.js\";\nexport {\n SHOW_COMMIT_MAX_CHARS,\n type CommitSummary,\n type CreateWorkspaceOptions,\n type CreateWorkspaceResult,\n type FreeWorkspaceOptions,\n type FreeWorkspaceResult,\n type RebaseResult,\n type ShowCommitResult,\n type VcsBackend,\n type VcsBackendName,\n WorkspaceConflictError,\n WorkspaceDirtyError,\n WorkspaceVcsRequiredError,\n} from \"./types.js\";\n\n/**\n * Detection precedence: jj > sl > git > none. The first backend whose\n * detect() returns true wins. `none` is always last. Detection shells\n * out to each VCS's canonical root probe (`jj root`, `sl root`, `git\n * rev-parse --show-toplevel`) so worktrees and gitdir-pointer files are\n * handled by the owning tool instead of a brittle marker-dir heuristic.\n */\nconst BACKENDS: readonly VcsBackend[] = [jjBackend, slBackend, gitBackend, noneBackend];\n\n/** Return the backend that should handle projectRoot. Walks BACKENDS\n * in precedence order; never returns undefined because noneBackend\n * always claims. */\nexport async function detectBackend(projectRoot: string): Promise<VcsBackend> {\n for (const backend of BACKENDS) {\n if (await backend.detect(projectRoot)) return backend;\n }\n return noneBackend;\n}\n\n/** Look up a backend by name. Throws on unknown name. Used by\n * `mu workspace create --backend ...` to honour an explicit override. */\nexport function backendByName(name: VcsBackendName): VcsBackend {\n for (const backend of BACKENDS) {\n if (backend.name === name) return backend;\n }\n throw new Error(`unknown vcs backend: ${name}`);\n}\n","// mu — workspace staleness and dirty decorators.\n\nimport type { Db } from \"../db.js\";\nimport { isWorkspaceStale } from \"../staleness.js\";\nimport { backendByName } from \"../vcs.js\";\nimport type { WorkspaceRow, WorkspaceStaleness } from \"./core.js\";\nimport { getWorkspaceForAgent } from \"./crud.js\";\n\nconst DECORATE_CONCURRENCY = 4;\n\nexport async function getWorkspaceStaleness(\n db: Db,\n agentName: string,\n workstreamName: string,\n): Promise<WorkspaceStaleness | null> {\n const row = getWorkspaceForAgent(db, agentName, workstreamName);\n if (row === undefined) return null;\n const [decorated] = await decorateWithStaleness([row]);\n const commitsBehindMain = decorated?.commitsBehindMain ?? null;\n return {\n agentName,\n workstreamName,\n commitsBehindMain,\n isStale: isWorkspaceStale(commitsBehindMain),\n };\n}\n\n/**\n * Decorate each row with `commitsBehindMain` by asking the row's backend\n * how far the parent_ref is behind the project's default branch HEAD.\n * Cheap, pure observation: NO automatic `git fetch` / `jj git fetch` /\n * `sl pull`. The number is as fresh as the workspace's local refs cache.\n *\n * Returns a NEW array; does not mutate the input. Rows whose parent_ref\n * is missing, or whose backend's commitsBehind throws / returns null,\n * get `commitsBehindMain: null`.\n */\nexport async function decorateWithStaleness(\n rows: readonly WorkspaceRow[],\n): Promise<WorkspaceRow[]> {\n const cache = new Map<string, Promise<number | null>>();\n const fetchBehind = (r: WorkspaceRow): Promise<number | null> => {\n const parentRef = r.parentRef;\n if (parentRef === null) return Promise.resolve(null);\n const key = `${r.backend}\\x00${parentRef}`;\n const cached = cache.get(key);\n if (cached !== undefined) return cached;\n const p = (async (): Promise<number | null> => {\n try {\n const backend = backendByName(r.backend);\n return await backend.commitsBehind(r.path, parentRef);\n } catch {\n return null;\n }\n })();\n cache.set(key, p);\n return p;\n };\n return mapWithConcurrency(rows, DECORATE_CONCURRENCY, async (r) => ({\n ...r,\n commitsBehindMain: await fetchBehind(r),\n }));\n}\n\n/**\n * Decorate every row with a `dirty` marker — true when the backend's\n * `listDirtyFiles` reports any uncommitted / unstaged / untracked-not-\n * ignored files; false when clean; null on backend-command failure.\n *\n * Returns a NEW array; does not mutate the input.\n */\nexport async function decorateWithDirty(rows: readonly WorkspaceRow[]): Promise<WorkspaceRow[]> {\n return mapWithConcurrency(rows, DECORATE_CONCURRENCY, async (r) => {\n let dirty: boolean | null;\n try {\n const backend = backendByName(r.backend);\n const files = await backend.listDirtyFiles(r.path);\n dirty = files.length > 0;\n } catch {\n dirty = null;\n }\n return { ...r, dirty };\n });\n}\n\n/**\n * Tiny p-limit-style helper. Keeps at most `limit` callbacks in flight\n * at once and preserves input order in the result. Stays in this file\n * because it has exactly two local decorator callers; promote out only\n * when a second cluster needs it.\n */\nasync function mapWithConcurrency<T, R>(\n items: readonly T[],\n limit: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let next = 0;\n const worker = async (): Promise<void> => {\n while (true) {\n const i = next++;\n if (i >= items.length) return;\n const item = items[i] as T;\n results[i] = await fn(item, i);\n }\n };\n const workerCount = Math.min(Math.max(1, limit), items.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n return results;\n}\n","// mu — workspace orphan directory detection.\n\nimport { readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type Db, defaultStateDir, tryResolveWorkstreamId } from \"../db.js\";\nimport { workspacesRoot } from \"./core.js\";\nimport { listWorkspaces } from \"./crud.js\";\n\nexport interface WorkspaceOrphan {\n /** The on-disk dir name (the agent name it WOULD be for, if mu had\n * registered it). */\n agentName: string;\n /** Workstream the dir is filed under. */\n workstreamName: string;\n /** Absolute path to the orphan dir. */\n path: string;\n}\n\n/**\n * Like WorkspaceOrphan but additionally flags whether the parent\n * workstream itself is gone (no row in `workstreams`). Returned by\n * listAllOrphanWorkspaces; the per-workstream listWorkspaceOrphans\n * doesn't carry this since by construction it only runs against an\n * existing workstream.\n */\nexport interface StrandedWorkspaceOrphan extends WorkspaceOrphan {\n /** True iff the parent workstream has no DB row (the dir was left\n * behind by a `mu workstream destroy` or a manual DELETE). */\n stranded: boolean;\n}\n\n/**\n * Scan `<state-dir>/workspaces/<workstream>/` for directories that\n * have no row in `vcs_workspaces`.\n *\n * Returns `[]` when the workstream's workspaces dir doesn't exist,\n * or when every dir on disk has a corresponding DB row. Filesystem\n * read is best-effort: a missing/inaccessible dir returns `[]`.\n */\nexport function listWorkspaceOrphans(db: Db, workstream: string): WorkspaceOrphan[] {\n const root = workspacesRoot(workstream);\n let dirs: string[];\n try {\n dirs = readdirSync(root, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n } catch {\n return [];\n }\n const registered = new Set(listWorkspaces(db, workstream).map((w) => w.path));\n const orphans: WorkspaceOrphan[] = [];\n for (const agentDir of dirs) {\n const fullPath = join(root, agentDir);\n if (!registered.has(fullPath)) {\n orphans.push({ agentName: agentDir, workstreamName: workstream, path: fullPath });\n }\n }\n return orphans;\n}\n\n/**\n * Cross-workstream variant of listWorkspaceOrphans. Reads\n * `<state-dir>/workspaces/`, recurses one level (per-ws subdir →\n * per-agent subdir), and surfaces every dir with no row in\n * `vcs_workspaces`.\n */\nexport function listAllOrphanWorkspaces(db: Db): StrandedWorkspaceOrphan[] {\n const root = join(defaultStateDir(), \"workspaces\");\n let wsDirs: string[];\n try {\n wsDirs = readdirSync(root, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n } catch {\n return [];\n }\n const registered = new Set(listWorkspaces(db).map((w) => w.path));\n const orphans: StrandedWorkspaceOrphan[] = [];\n for (const wsName of wsDirs) {\n const wsRoot = join(root, wsName);\n let agentDirs: string[];\n try {\n agentDirs = readdirSync(wsRoot, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n } catch {\n continue;\n }\n const stranded = tryResolveWorkstreamId(db, wsName) === null;\n for (const agentDir of agentDirs) {\n const fullPath = join(wsRoot, agentDir);\n if (!registered.has(fullPath)) {\n orphans.push({\n agentName: agentDir,\n workstreamName: wsName,\n path: fullPath,\n stranded,\n });\n }\n }\n }\n return orphans;\n}\n","// mu — workspace recreate verb.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport {\n type VcsBackend,\n type VcsBackendName,\n WorkspaceDirtyError,\n backendByName,\n} from \"../vcs.js\";\nimport { WorkspaceNotFoundError, type WorkspaceRow } from \"./core.js\";\nimport {\n type CreateWorkspaceOptions,\n createWorkspace,\n freeWorkspace,\n getWorkspaceForAgent,\n} from \"./crud.js\";\n\nexport interface RecreateWorkspaceOptions {\n /** Same as createWorkspace; defaults to cwd. */\n projectRoot?: string;\n /** Same as createWorkspace; if undefined the previous backend is\n * reused (auto-detection re-runs only when --backend was passed). */\n backend?: VcsBackendName | VcsBackend;\n /** Same as createWorkspace; if undefined the new workspace bases on\n * the backend's current head (for git/jj/sl: the project's main),\n * which is the whole point of the verb. */\n parentRef?: string;\n /** When true, skip the dirty-check refusal and discard any\n * uncommitted changes in the existing workspace. The lossy escape\n * hatch — mirrors the implicit semantics of `mu workspace free`\n * without --commit. */\n force?: boolean;\n}\n\nexport interface RecreateWorkspaceResult {\n /** The freshly-created workspace row (the previous row is already\n * gone by the time we return). */\n workspace: WorkspaceRow;\n /** parent_ref of the WORKSPACE BEFORE recreate, so callers (and the\n * CLI's success message) can show \"bumped from <old> -> <new>\". */\n previousParentRef: string | null;\n}\n\n/**\n * Free + create in one atomic-ish verb. Between waves the operator\n * wants the SAME agent name with a fresh workspace pinned to current\n * main; doing `free` then `create` manually was the dogfood-painful\n * pattern.\n */\nexport async function recreateWorkspace(\n db: Db,\n agent: string,\n opts: RecreateWorkspaceOptions & { workstream: string },\n): Promise<RecreateWorkspaceResult> {\n const row = getWorkspaceForAgent(db, agent, opts.workstream);\n if (!row) throw new WorkspaceNotFoundError(agent);\n\n // Dirty-check the OLD workspace before we destroy it. Same\n // safety semantics as `free` (without --commit): refuse rather\n // than silently lose uncommitted edits. `--force` is the lossy\n // escape hatch.\n if (opts.force !== true) {\n const oldBackend = backendByName(row.backend);\n const dirty = await oldBackend.listDirtyFiles(row.path);\n if (dirty.length > 0) {\n throw new WorkspaceDirtyError(row.path, dirty, \"recreate\");\n }\n }\n\n // One snapshot for the whole free+create cycle; one event line at\n // the end. The internal `_suppressEvent` flag on free/create is\n // private to this module — not part of the SDK contract.\n captureSnapshot(db, `workspace recreate ${agent}`, row.workstreamName);\n\n await freeWorkspace(db, agent, {\n workstream: opts.workstream,\n commit: false,\n _suppressEvent: true,\n });\n\n const createOpts: CreateWorkspaceOptions = {\n agent,\n workstream: opts.workstream,\n _suppressEvent: true,\n };\n if (opts.projectRoot !== undefined) createOpts.projectRoot = opts.projectRoot;\n // Default to the prior backend so a between-wave refresh stays on\n // the same VCS regardless of cwd. Explicit override wins.\n if (opts.backend !== undefined) {\n createOpts.backend = opts.backend;\n } else {\n createOpts.backend = row.backend;\n }\n if (opts.parentRef !== undefined) createOpts.parentRef = opts.parentRef;\n\n const fresh = await createWorkspace(db, createOpts);\n\n emitEvent(\n db,\n opts.workstream,\n `workspace recreate ${agent} (backend=${fresh.backend}, path=${fresh.path}, old_parent=${row.parentRef ? row.parentRef.slice(0, 12) : \"—\"}, new_parent=${fresh.parentRef ? fresh.parentRef.slice(0, 12) : \"—\"})`,\n );\n\n return { workspace: fresh, previousParentRef: row.parentRef };\n}\n","// mu — task read/query primitives.\n\nimport type { Db } from \"../db.js\";\nimport { tryResolveWorkstreamId } from \"../db.js\";\nimport { lastClaimEventAt } from \"../logs.js\";\nimport {\n type RawTaskNoteRow,\n type RawTaskRow,\n SELECT_NOTE_COLS,\n SELECT_TASK_COLS,\n TASK_FROM_JOIN,\n type TaskNoteRow,\n type TaskRow,\n noteFromDb,\n rowFromDb,\n taskIdFor,\n} from \"./core.js\";\nimport type { TaskStatus } from \"./status.js\";\n\nexport function getTask(db: Db, localId: string, workstream: string): TaskRow | undefined {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return undefined;\n const row = db\n .prepare(\n `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} WHERE t.workstream_id = ? AND t.local_id = ?`,\n )\n .get(wsId, localId) as RawTaskRow | undefined;\n return row ? rowFromDb(row) : undefined;\n}\n\n/**\n * List tasks. With no `workstream` arg returns every row — used by `mu sql`\n * and by tests; CLI surfaces always pass a workstream so users only see\n * their own.\n */\nexport interface ListTasksOptions {\n /** Filter to one or more lifecycle statuses. Omitted = all statuses. */\n status?: TaskStatus | readonly TaskStatus[];\n}\n\nexport function listTasks(db: Db, workstream?: string, opts: ListTasksOptions = {}): TaskRow[] {\n const statuses =\n opts.status === undefined\n ? undefined\n : Array.isArray(opts.status)\n ? (opts.status as TaskStatus[])\n : [opts.status as TaskStatus];\n\n const where: string[] = [];\n const params: unknown[] = [];\n if (workstream !== undefined) {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n where.push(\"t.workstream_id = ?\");\n params.push(wsId);\n }\n if (statuses !== undefined) {\n where.push(`t.status IN (${statuses.map(() => \"?\").join(\", \")})`);\n params.push(...statuses);\n }\n const sql =\n where.length === 0\n ? `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} ORDER BY t.local_id`\n : `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} WHERE ${where.join(\" AND \")} ORDER BY t.local_id`;\n const rows = db.prepare(sql).all(...params) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\n// The three views (ready, blocked, goals) project tasks.* directly,\n// so they expose v5 columns (id, workstream_id, owner_id). We wrap\n// them with the same JOINs as TASK_FROM_JOIN to translate back to the\n// operator-facing TaskRow shape (workstream + owner as TEXT names).\nconst VIEW_FROM_JOIN = (view: string) => `\n FROM ${view} v\n JOIN workstreams ws ON ws.id = v.workstream_id\n LEFT JOIN agents ag ON ag.id = v.owner_id\n`;\nconst SELECT_VIEW_COLS = `\n v.id AS id,\n v.local_id AS local_id,\n ws.name AS workstream,\n v.title AS title,\n v.status AS status,\n v.impact AS impact,\n v.effort_days AS effort_days,\n ag.name AS owner,\n v.created_at AS created_at,\n v.updated_at AS updated_at\n`;\n\n/** Options for listReady. The optional `statuses` filter composes\n * on top of the `ready` view (which itself constrains to\n * `status='OPEN'`); passing only OPEN is identical to today's no-\n * filter shape, passing only non-OPEN values returns []. Exists so\n * `mu task next --status` can mirror the multi-status flag shape\n * shipped on `mu task list` (task_list_multi_status_union). */\nexport interface ListReadyOptions {\n status?: TaskStatus | readonly TaskStatus[];\n}\n\nexport function listReady(db: Db, workstream: string, opts: ListReadyOptions = {}): TaskRow[] {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const statuses =\n opts.status === undefined\n ? undefined\n : Array.isArray(opts.status)\n ? (opts.status as TaskStatus[])\n : [opts.status as TaskStatus];\n const where: string[] = [\"v.workstream_id = ?\"];\n const params: unknown[] = [wsId];\n if (statuses !== undefined && statuses.length > 0) {\n where.push(`v.status IN (${statuses.map(() => \"?\").join(\", \")})`);\n params.push(...statuses);\n }\n const rows = db\n .prepare(\n `SELECT ${SELECT_VIEW_COLS} ${VIEW_FROM_JOIN(\"ready\")} WHERE ${where.join(\" AND \")} ORDER BY v.local_id`,\n )\n .all(...params) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\nexport function listBlocked(db: Db, workstream: string): TaskRow[] {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(\n `SELECT ${SELECT_VIEW_COLS} ${VIEW_FROM_JOIN(\"blocked\")} WHERE v.workstream_id = ? ORDER BY v.local_id`,\n )\n .all(wsId) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\nexport function listGoals(db: Db, workstream: string): TaskRow[] {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(\n `SELECT ${SELECT_VIEW_COLS} ${VIEW_FROM_JOIN(\"goals\")} WHERE v.workstream_id = ? ORDER BY v.local_id`,\n )\n .all(wsId) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\n/** All IN_PROGRESS tasks in a workstream, most-recently-touched first.\n * Used by `mu state` to populate its in-progress slice; exposed as a\n * named SDK helper so CLI renderers don't re-derive the row-shape\n * conversion (review_code_raw_task_state_duplicate). */\nexport function listInProgress(db: Db, workstream: string): TaskRow[] {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(\n `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} WHERE t.workstream_id = ? AND t.status = 'IN_PROGRESS' ORDER BY t.updated_at DESC`,\n )\n .all(wsId) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\n/** Most-recently-closed tasks in a workstream, newest first, capped at\n * `limit` (default 5). Used by `mu state` for its 'recent closed'\n * slice; exposed as a named SDK helper so the CLI no longer needs the\n * raw-row type that was duplicating RawTaskRow\n * (review_code_raw_task_state_duplicate). */\nexport function listRecentClosed(db: Db, workstream: string, limit = 5): TaskRow[] {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(\n `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN} WHERE t.workstream_id = ? AND t.status = 'CLOSED' ORDER BY t.updated_at DESC LIMIT ?`,\n )\n .all(wsId, limit) as RawTaskRow[];\n return rows.map(rowFromDb);\n}\n\n/** Optional filter knobs for `listNotes`. Default-everything-undefined\n * preserves the historical \"return every note, oldest-first\" shape so\n * every existing caller (cmdTaskShow's notes block, exporting.ts's\n * bucket renderer, agents.test.ts) keeps working unchanged.\n *\n * Filters compose multiplicatively when both apply (`since` AND\n * `tail`): the timestamp filter is applied first, then `tail` slices\n * the last N of what survived. The CLI surface (`mu task notes\n * --tail / --since / --since-claim`) lives in src/cli/tasks/edit.ts;\n * the mutex between `--since` and `--since-claim` is a CLI concern,\n * not enforced here — if both arrive at the SDK, `since` wins (it's\n * the explicit one) and `sinceClaim` is ignored. The auto-resolve\n * for `sinceClaim` (look up the most recent `task claim` event in\n * agent_logs) happens here so the SDK is self-contained for scripted\n * callers. */\nexport interface ListNotesOptions {\n /** Print only the last N notes (after any timestamp filter). Must\n * be a positive integer; a value of 0 returns no rows but is not\n * an error here — CLI-side validation rejects `--tail 0`. */\n tail?: number;\n /** ISO-8601 cutoff: only notes with `created_at > since` survive.\n * Comparison is lexicographic on the ISO string (matches the way\n * the rest of the codebase compares ISO timestamps). */\n since?: string;\n /** When true and `since` is unset, look up the `created_at` of the\n * most recent `task claim` event for this task and use it as the\n * cutoff. Falls back to no filter when no claim event exists\n * (equivalent to `--since-beginning`). */\n sinceClaim?: boolean;\n}\n\n/** List notes for a task. Operator-facing local_id; resolves to the\n * surrogate task id via taskIdFor (with optional workstream scope).\n *\n * Optional filters: see {@link ListNotesOptions}. Default behaviour\n * (no opts) is unchanged — every note, oldest-first. */\nexport function listNotes(\n db: Db,\n taskLocalId: string,\n workstream: string,\n opts: ListNotesOptions = {},\n): TaskNoteRow[] {\n const taskId = taskIdFor(db, taskLocalId, workstream);\n if (taskId === null) return [];\n // Resolve the cutoff once: explicit `since` wins; otherwise\n // `sinceClaim` resolves via lastClaimEventAt (null → no filter).\n let cutoff: string | undefined = opts.since;\n if (cutoff === undefined && opts.sinceClaim === true) {\n const at = lastClaimEventAt(db, workstream, taskLocalId);\n if (at !== null) cutoff = at;\n }\n const rows =\n cutoff !== undefined\n ? (db\n .prepare(\n `SELECT ${SELECT_NOTE_COLS} FROM task_notes n JOIN tasks t ON t.id = n.task_id\n WHERE n.task_id = ? AND n.created_at > ? ORDER BY n.id`,\n )\n .all(taskId, cutoff) as RawTaskNoteRow[])\n : (db\n .prepare(\n `SELECT ${SELECT_NOTE_COLS} FROM task_notes n JOIN tasks t ON t.id = n.task_id\n WHERE n.task_id = ? ORDER BY n.id`,\n )\n .all(taskId) as RawTaskNoteRow[]);\n const mapped = rows.map(noteFromDb);\n if (opts.tail !== undefined && opts.tail >= 0) {\n return opts.tail === 0 ? [] : mapped.slice(-opts.tail);\n }\n return mapped;\n}\n\n/**\n * All tasks currently owned by `agent` in a given workstream\n * (v5: agents.name is per-workstream unique). Sorted by local_id.\n *\n * Defaults to **excluding CLOSED** since the verb's purpose is \"what\n * is X currently working on?\" and a closed task is no longer being\n * worked on. closeTask intentionally preserves `owner` as a\n * historical record (so audit/notes can attribute decisions); pass\n * `{ includeClosed: true }` to surface that history.\n */\nexport function listTasksByOwner(\n db: Db,\n workstream: string,\n owner: string,\n opts: { includeClosed?: boolean } = {},\n): TaskRow[] {\n // 'Live work' = not in any terminal-or-parked state. CLOSED is the\n // obvious one; REJECTED and DEFERRED are also off the agent's plate\n // (the user has decided 'won't do' or 'not now'). includeClosed\n // re-includes ALL of those so historical attribution is recoverable.\n // Filter on the joined ag.name so the operator-facing owner string\n // still drives the lookup; FK is now via owner_id.\n const filter = opts.includeClosed ? \"\" : \"AND t.status NOT IN ('CLOSED', 'REJECTED', 'DEFERRED')\";\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return [];\n const sql = `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN}\n WHERE ag.name = ? AND t.workstream_id = ? ${filter}\n ORDER BY t.local_id`;\n return (db.prepare(sql).all(owner, wsId) as RawTaskRow[]).map(rowFromDb);\n}\n\n/**\n * Cross-workstream variant of `listTasksByOwner`. Returns tasks owned\n * by ANY agent of the given name across every workstream. Used by\n * `mu task owned-by --all` for the genuine cross-workstream view\n * (audit / dashboards). The bare name is the join key, so two\n * distinct same-named agents in different workstreams contribute\n * their tasks to the same result list.\n */\nexport function listTasksByOwnerCrossWorkstream(\n db: Db,\n owner: string,\n opts: { includeClosed?: boolean } = {},\n): TaskRow[] {\n const filter = opts.includeClosed ? \"\" : \"AND t.status NOT IN ('CLOSED', 'REJECTED', 'DEFERRED')\";\n const sql = `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN}\n WHERE ag.name = ? ${filter}\n ORDER BY ws.name, t.local_id`;\n return (db.prepare(sql).all(owner) as RawTaskRow[]).map(rowFromDb);\n}\n\nexport interface SearchTasksOptions {\n /** Restrict to one workstream; undefined = search across all. */\n workstream?: string;\n /** Also search `task_notes.content` (default false: titles + ids only). */\n includeNotes?: boolean;\n}\n\n/**\n * Substring search on task `title` and `local_id`, case-insensitive.\n * With `includeNotes: true` also searches `task_notes.content`. The\n * pattern is wrapped in `%...%` automatically so callers don't need\n * SQL LIKE knowledge — for explicit globs (or regex), use `mu sql`.\n */\nexport function searchTasks(db: Db, pattern: string, opts: SearchTasksOptions = {}): TaskRow[] {\n const like = `%${pattern.toLowerCase()}%`;\n const wsClause = opts.workstream === undefined ? \"\" : \"ws.name = ? AND\";\n const wsParams = opts.workstream === undefined ? [] : [opts.workstream];\n const orderBy =\n opts.workstream === undefined ? \"ORDER BY ws.name, t.local_id\" : \"ORDER BY t.local_id\";\n\n if (opts.includeNotes) {\n const sql = `SELECT DISTINCT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN}\n LEFT JOIN task_notes n ON n.task_id = t.id\n WHERE ${wsClause} (\n LOWER(t.title) LIKE ?\n OR LOWER(t.local_id) LIKE ?\n OR LOWER(n.content) LIKE ?\n )\n ${orderBy}`;\n return (db.prepare(sql).all(...wsParams, like, like, like) as RawTaskRow[]).map(rowFromDb);\n }\n\n const sql = `SELECT ${SELECT_TASK_COLS} ${TASK_FROM_JOIN}\n WHERE ${wsClause} (LOWER(t.title) LIKE ? OR LOWER(t.local_id) LIKE ?)\n ${orderBy}`;\n return (db.prepare(sql).all(...wsParams, like, like) as RawTaskRow[]).map(rowFromDb);\n}\n","// mu — task id validation and title slug helpers.\n\nimport type { Db } from \"../db.js\";\nimport { getTask } from \"./queries.js\";\n\n/** Lowercase alpha first, then alnum / underscore / hyphen, ≤64 chars. */\nconst TASK_ID_RE = /^[a-z][a-z0-9_-]{0,63}$/;\n\nexport function isValidTaskId(id: string): boolean {\n return TASK_ID_RE.test(id);\n}\n\n/**\n * Derive a task id from a free-form title.\n *\n * \"Build the auth module\" → \"build_the_auth_module\"\n * \"FILES: foo.ts (refactor)\" → \"files_foo_ts_refactor\"\n */\n\n/**\n * Soft cap for auto-generated slugs. The collision-suffix loop in\n * idFromTitle can push past this (`_2`, `_3`, ...) without going past\n * the hard ceiling. 40 chars hits the sweet spot of 'short enough to\n * type and to look reasonable in mu task tree' without losing too\n * much of the title's meaning.\n */\nconst SLUG_SOFT_CAP = 40;\n\n/**\n * Hard ceiling for any generated id. Schema has no length limit, but\n * 64 keeps ids comfortable in tables, JSON, and tmux pane titles.\n */\nconst SLUG_HARD_CAP = 64;\n\n/**\n * Lowercase title; collapse non-alnum runs into single `_`; trim\n * leading/trailing `_`; prefix `t_` if the result starts with a digit\n * (schema requires first char letter); apply the soft cap with\n * word-boundary trim (cut at the last `_` at-or-before SLUG_SOFT_CAP\n * when one exists, else hard-truncate). Mirrors `tg`'s `id_from_title`\n * but adds the soft cap.\n *\n * Throws if `title` yields an empty slug after stripping.\n */\nexport function slugifyTitle(title: string): string {\n return slugifyTitleVerbose(title).slug;\n}\n\n/**\n * Result of `slugifyTitleVerbose`: the slug plus enough metadata for\n * the CLI to decide whether to warn the user that meaning was lost.\n *\n * slug — the same string `slugifyTitle` returns.\n * strippedLength — length of the post-strip pre-cap slug. When this\n * exceeds the SLUG_SOFT_CAP the verbose form had to\n * cut at a word boundary (or hard-truncate); the\n * cut clauses are gone with no in-band signal.\n * originalSlug — what the slug WOULD have been without the\n * SLUG_SOFT_CAP cut: full stripped slug with the\n * same `t_` digit-prefix correction and the same\n * SLUG_HARD_CAP ceiling, but no word-boundary\n * truncation. Equal to `slug` when nothing was\n * cut. The CLI surfaces this in `mu task add\n * --json` so scripted callers can detect the\n * truncation without grepping stderr.\n * truncated — true iff `slug.length < strippedLength` AFTER the\n * `t_` digit-prefix correction, i.e. real bytes were\n * dropped. False for any title that fits under the\n * soft cap or whose only diff vs the stripped slug\n * is the `t_` prefix.\n *\n * The CLI's `mu task add` uses `truncated` to print a one-line stderr\n * hint pointing at the `<id>` positional override and (under --json)\n * to surface `originalSlug` alongside `truncated:true`\n * (slugifytitle_silently_drops_clauses; task_add_slugify_silently_truncates_ids).\n */\nexport interface SlugifyResult {\n slug: string;\n strippedLength: number;\n originalSlug: string;\n truncated: boolean;\n}\n\n/**\n * Verbose sibling of `slugifyTitle`: returns the slug AND a\n * `truncated` flag so the CLI can hint to the user when the soft cap\n * dropped clauses (the meaning-shift hazard documented in\n * slugifytitle_silently_drops_clauses).\n *\n * Algorithm is byte-for-byte identical to `slugifyTitle`; this just\n * surfaces the metadata that the plain form throws away.\n */\nexport function slugifyTitleVerbose(title: string): SlugifyResult {\n const stripped = title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n if (stripped.length === 0) {\n throw new Error(`title yields empty slug: ${JSON.stringify(title)}`);\n }\n // Soft cap with word-boundary preference: if the slug exceeds the\n // soft cap, look for the last `_` at-or-before the cap and cut there\n // (so we never break a word). If no underscore is in the cap window\n // (ie the title is one giant word), fall back to a hard truncate at\n // the soft cap. The result is always <= SLUG_SOFT_CAP after this.\n let trimmed: string;\n if (stripped.length <= SLUG_SOFT_CAP) {\n trimmed = stripped;\n } else {\n const window = stripped.slice(0, SLUG_SOFT_CAP);\n const lastSep = window.lastIndexOf(\"_\");\n trimmed = lastSep > 0 ? window.slice(0, lastSep) : window;\n }\n // First char must be a letter → prefix `t_` if it isn't. v5 has no\n // global namespace and no reserved prefix; `mu_foo` is a perfectly\n // valid local_id (per-workstream unique).\n const applyPrefix = (s: string): string =>\n /^[a-z]/.test(s) ? s.slice(0, SLUG_HARD_CAP) : `t_${s}`.slice(0, SLUG_HARD_CAP);\n const slug = applyPrefix(trimmed);\n // `originalSlug` is what the slug would have been without the\n // soft-cap word-boundary cut: same prefix correction, same hard\n // cap, no word-boundary trim. Used by `mu task add --json` so\n // scripted callers can detect truncation programmatically.\n const originalSlug = applyPrefix(stripped);\n // `truncated` reflects whether real characters were dropped — the\n // `t_` prefix doesn't count as truncation. We compare the trimmed\n // length against the stripped length (both pre-prefix) so a digit-\n // led title that fit under the cap still reports truncated=false.\n return {\n slug,\n strippedLength: stripped.length,\n originalSlug,\n truncated: trimmed.length < stripped.length,\n };\n}\n\n/**\n * Generate a unique task id from a title. v5: tasks.local_id is\n * per-workstream unique, so the collision check scopes to one\n * workstream. On collision, appends `_2`, `_3`, … until unique.\n */\nexport function idFromTitle(db: Db, workstream: string, title: string): string {\n return idFromTitleVerbose(db, workstream, title).id;\n}\n\n/**\n * Result of `idFromTitleVerbose`: the unique-in-workstream id plus the\n * truncated flag from the underlying slugify pass. Used by `mu task\n * add` to decide whether to surface the stderr hint about lost clauses\n * (slugifytitle_silently_drops_clauses) and to surface the un-truncated\n * slug in `--json` (task_add_slugify_silently_truncates_ids).\n *\n * id — the unique-in-workstream task id.\n * truncated — true iff the underlying slugify pass cut real\n * characters (collision-suffixing does NOT flip\n * this).\n * originalSlug — what the slug would have been without the\n * SLUG_SOFT_CAP cut. Equal to `id` when nothing was\n * cut AND no collision suffix was appended; for\n * the truncation-detection use case the only thing\n * the CLI cares about is the lossy-vs-not\n * comparison surfaced via `truncated`.\n */\nexport interface IdFromTitleResult {\n id: string;\n truncated: boolean;\n originalSlug: string;\n}\n\n/**\n * Verbose sibling of `idFromTitle`: returns the unique id, the\n * `truncated` flag from the slugify pass, and the un-truncated\n * `originalSlug` for `--json` consumers. Collision-suffixing (`_2`,\n * `_3`, …) does not flip `truncated` — the underlying slug's lossiness\n * is what the CLI hint cares about.\n */\nexport function idFromTitleVerbose(db: Db, workstream: string, title: string): IdFromTitleResult {\n const { slug: base, truncated, originalSlug } = slugifyTitleVerbose(title);\n if (getTask(db, base, workstream) === undefined) return { id: base, truncated, originalSlug };\n for (let i = 2; i < 1000; i++) {\n const candidate = `${base}_${i}`.slice(0, SLUG_HARD_CAP);\n if (getTask(db, candidate, workstream) === undefined)\n return { id: candidate, truncated, originalSlug };\n }\n throw new Error(`could not derive a unique id from title in workstream ${workstream}: ${title}`);\n}\n\n/**\n * Sanitise a free-form string into a candidate task id.\n *\n * Lowercases, replaces every non-`[a-z0-9_-]` char with `_`, trims any\n * leading non-letter (the schema requires the first char to be a\n * letter), truncates to 64 chars. Returns `\"task\"` when the input has\n * no usable letters at all so the suggestion in\n * `TaskIdInvalidError.errorNextSteps()` is always a runnable command.\n *\n * Mirrors `slugifyTitle`'s prefix corrections so suggested ids will\n * pass `isValidTaskId` if the user runs them verbatim. Lives next to\n * `slugifyTitle` rather than in `tasks/errors.ts` because it's a slug\n * helper, not an error helper — the only caller happens to be\n * `TaskIdInvalidError.errorNextSteps()`.\n */\nexport function sanitiseTaskId(input: string): string {\n const s = input\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, \"_\")\n .replace(/^[^a-z]+/, \"\")\n .slice(0, SLUG_HARD_CAP);\n return s.length === 0 ? \"task\" : s;\n}\n","// mu — task error classes.\n//\n// Every task verb that can fail in a typed way has its own error class\n// here. The CLI's `classifyError()` (src/cli.ts) maps them to exit codes:\n// not found → 3 (TaskNotFoundError)\n// conflict → 4 (TaskExistsError, TaskNotInWorkstreamError,\n// TaskAlreadyOwnedError, TaskHasOpenDependentsError,\n// ClaimerNotRegisteredError, CrossWorkstreamEdgeError,\n// TaskIdInvalidError)\n// cycle → 4 (CycleError — also a conflict)\n//\n// Each error implements HasNextSteps so the CLI can render a per-error\n// `Next:` block with the most useful follow-up commands.\n//\n// Extracted from src/tasks.ts as part of refactor_split_large_src_files.\n\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport { WORKSPACE_STALE_THRESHOLD, type WorkspaceStaleness } from \"../workspace.js\";\nimport { sanitiseTaskId } from \"./id.js\";\n\nexport class TaskNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"TaskNotFoundError\";\n constructor(public readonly taskId: string) {\n super(`no such task: ${taskId}`);\n }\n errorNextSteps(): NextStep[] {\n // v5: tasks.workstream (TEXT) was replaced by tasks.workstream_id\n // (FK → workstreams.id). The pre-v5 recipe SELECTed a column that\n // no longer exists and crashed at runtime — and this is THE first\n // hint a user sees on a missed-task lookup, so the breakage was\n // high-traffic. Mirror the join pattern used by AgentExistsError.\n const idLit = this.taskId.replace(/'/g, \"''\").toLowerCase();\n const recipe = `mu sql \"SELECT ws.name AS workstream, t.local_id, t.status, t.title FROM tasks t JOIN workstreams ws ON ws.id = t.workstream_id WHERE LOWER(t.local_id) LIKE '%${idLit}%' OR LOWER(t.title) LIKE '%${idLit}%'\"`;\n return [\n { intent: \"List tasks in workstream\", command: \"mu task list -w <workstream>\" },\n { intent: \"Search by substring (id + title)\", command: recipe },\n { intent: \"Find which workstream owns it\", command: recipe },\n ];\n }\n}\n\n/**\n * Thrown by `addTask` when `localId` violates the schema regex\n * `/^[a-z][a-z0-9_-]{0,63}$/`. Replaces a bare `TypeError` so the\n * CLI's `handle()` wrapper can map it to exit code 4 (validation /\n * conflict) and surface a `--json` `nextSteps` block pointing at\n * the auto-derived-id workflow and a sanitised candidate.\n */\nexport class TaskIdInvalidError extends Error implements HasNextSteps {\n override readonly name = \"TaskIdInvalidError\";\n constructor(public readonly attempted: string) {\n super(`invalid task id: ${JSON.stringify(attempted)} (expected /^[a-z][a-z0-9_-]{0,63}$/)`);\n }\n errorNextSteps(): NextStep[] {\n const sanitised = sanitiseTaskId(this.attempted);\n return [\n {\n intent: \"Use the auto-derived id (drop --id and pass --title)\",\n command: 'mu task add --title \"...\" --impact <n> --effort-days <n>',\n },\n {\n intent: \"Sanitise to a valid id\",\n command: `mu task add ${sanitised} --title \"...\" --impact <n> --effort-days <n>`,\n },\n ];\n }\n}\n\nexport class TaskExistsError extends Error implements HasNextSteps {\n override readonly name = \"TaskExistsError\";\n constructor(public readonly taskId: string) {\n super(`task already exists: ${taskId}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Show the existing task\", command: `mu task show ${this.taskId}` },\n {\n intent: \"Update fields on the existing task\",\n command: `mu task update ${this.taskId} --title \"...\" --impact <n> --effort-days <n>`,\n },\n {\n intent: \"Pick a different id\",\n command: 'mu task add <new-id> --title \"...\" --impact <n> --effort-days <n>',\n },\n ];\n }\n}\n\n/**\n * Thrown when a verb is invoked with `-w/--workstream <name>` but the\n * named task lives in a different workstream. Distinguishes \"the user\n * typo'd the workstream\" from \"the task doesn't exist anywhere\"\n * (which surfaces as `TaskNotFoundError`). Maps to exit code 4\n * (conflict / wrong scope).\n */\nexport class TaskNotInWorkstreamError extends Error implements HasNextSteps {\n override readonly name = \"TaskNotInWorkstreamError\";\n constructor(\n public readonly taskId: string,\n public readonly expectedWorkstream: string,\n public readonly actualWorkstream: string,\n ) {\n super(`task ${taskId} is in workstream ${actualWorkstream}, not ${expectedWorkstream}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Use the correct workstream\",\n command: `mu task show ${this.taskId} -w ${this.actualWorkstream}`,\n },\n {\n intent: \"List tasks in the requested workstream\",\n command: `mu task list -w ${this.expectedWorkstream}`,\n },\n ];\n }\n}\n\nexport class TaskAlreadyOwnedError extends Error implements HasNextSteps {\n override readonly name = \"TaskAlreadyOwnedError\";\n constructor(\n public readonly taskId: string,\n public readonly currentOwner: string,\n ) {\n super(`task ${taskId} is already owned by ${currentOwner}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"See the current owner's task list\",\n command: `mu task owned-by ${this.currentOwner}`,\n },\n {\n intent: \"Release the current claim (if you ARE the owner)\",\n command: `mu task release ${this.taskId}`,\n },\n { intent: \"Show full task state\", command: `mu task show ${this.taskId}` },\n ];\n }\n}\n\n/**\n * Thrown by `rejectTask` / `deferTask` when the target task has\n * dependents that are still OPEN or IN_PROGRESS. Rejecting or\n * deferring such a task would silently strand the dependents (they'd\n * remain blocked by a prereq that's never going to satisfy the edge),\n * so we refuse and force an explicit decision: pass `--cascade` to\n * apply the same status to every transitive dependent, drop the\n * blocking edge first with `mu task unblock`, or address the\n * dependents individually. Maps to exit code 4.\n */\nexport class TaskHasOpenDependentsError extends Error implements HasNextSteps {\n override readonly name = \"TaskHasOpenDependentsError\";\n constructor(\n public readonly taskId: string,\n public readonly verb: \"reject\" | \"defer\",\n public readonly dependents: readonly string[],\n ) {\n super(\n `cannot ${verb} ${taskId}: ${dependents.length} open dependent(s) would be stranded (${dependents.slice(0, 5).join(\", \")}${dependents.length > 5 ? \", …\" : \"\"}). Pick one resolution and re-run.`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: `Preview the cascade (lists dependents that would be ${this.verb}ed; --cascade alone is dry-run)`,\n command: `mu task ${this.verb} ${this.taskId} --cascade`,\n },\n {\n intent: `${this.verb.charAt(0).toUpperCase() + this.verb.slice(1)} the whole sub-tree (commit; rerun with --yes after previewing)`,\n command: `mu task ${this.verb} ${this.taskId} --cascade --yes`,\n },\n {\n intent: \"Drop the blocking edge from a dependent first\",\n command: `mu task unblock <dep> --by ${this.taskId}`,\n },\n {\n intent: \"Address dependents individually first\",\n command: `mu task ${this.verb} <dep>`,\n },\n ];\n }\n}\n\n/**\n * Thrown when `mu task claim` resolves a claimer agent name (from the\n * pane title or --for) that has no matching row in the agents table.\n *\n * The FK on `tasks.owner` references `agents.name`; without this guard\n * the claim attempt would fail with the unhelpful 'FOREIGN KEY constraint\n * failed' from SQLite. This typed error gives the user actionable next\n * steps (run `mu agent adopt <pane-id>` to register, or use --for to pick a\n * different agent).\n *\n * Maps to exit code 4 (conflict) via the cli.ts handler.\n */\nexport class ClaimerNotRegisteredError extends Error implements HasNextSteps {\n override readonly name = \"ClaimerNotRegisteredError\";\n constructor(\n public readonly agentName: string,\n public readonly paneId: string | null,\n ) {\n const paneHint = paneId !== null ? ` (pane ${paneId})` : \"\";\n super(\n `claimer '${agentName}'${paneHint} is not a registered mu agent (no row in agents table)`,\n );\n }\n\n /**\n * Three actionable resolutions in expected-frequency order:\n * 1. --self : orchestrator pattern (working directly)\n * 2. --for : dispatcher pattern (assigning to a worker)\n * 3. mu agent adopt: registration pattern (promote pane to worker)\n */\n errorNextSteps(): NextStep[] {\n const steps: NextStep[] = [\n { intent: \"Work directly (anonymous)\", command: \"mu task claim <id> --self\" },\n { intent: \"Dispatch to a worker\", command: \"mu task claim <id> --for <worker>\" },\n ];\n steps.push(\n this.paneId !== null\n ? { intent: \"Register this pane\", command: `mu agent adopt ${this.paneId}` }\n : {\n intent: \"Register a pane\",\n command: \"mu agent adopt <pane-id> (must be in mu-<workstream> tmux session)\",\n },\n );\n return steps;\n }\n}\n\nexport class TaskClaimStaleWorkspaceError extends Error implements HasNextSteps {\n override readonly name = \"TaskClaimStaleWorkspaceError\";\n constructor(public readonly staleness: WorkspaceStaleness) {\n super(\n `${staleness.agentName} workspace is ${staleness.commitsBehindMain} commits behind main (≥${WORKSPACE_STALE_THRESHOLD} = stale); refresh before dispatch or rerun without --strict-staleness`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Refresh first\",\n command: `mu workspace refresh ${this.staleness.agentName} -w ${this.staleness.workstreamName}`,\n },\n ];\n }\n}\n\nexport class CycleError extends Error implements HasNextSteps {\n override readonly name = \"CycleError\";\n constructor(\n public readonly from: string,\n public readonly to: string,\n ) {\n super(`adding edge ${from} -> ${to} would create a cycle`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Show the dependency tree\",\n command: `mu task tree ${this.to} --down`,\n },\n {\n intent: \"Show the prereq tree (what blocks the from-task)\",\n command: `mu task tree ${this.from}`,\n },\n {\n intent: \"Remove an edge in the path to break the cycle\",\n command: \"mu task unblock <blocked> --by <blocker>\",\n },\n ];\n }\n}\n\n/**\n * Thrown by the `mu task wait` CLI wrapper when the per-poll\n * reconciler detects that a watched task transitioned\n * `IN_PROGRESS → OPEN` between polls (the reaper saw the owner's\n * pane was gone and flipped the task back). With `--status CLOSED`\n * (the default) the wait can never satisfy by progress — the worker\n * is dead — so we abort fast instead of running out the operator's\n * `--timeout`.\n *\n * Maps to exit code 6 (REAPER_DETECTED) via the cli.ts handler. The\n * suppression rule (only fire when target=CLOSED) lives in the\n * caller; this error type is a pure data carrier.\n *\n * Surfaced live by task_wait_reconcile_dead_panes (twice in one\n * v0.3 dispatch wave: tmux restart killed worker panes; `mu task\n * wait --timeout 1800` blocked silently for 25 min instead of\n * failing in seconds).\n */\nexport class ReaperDetectedDuringWaitError extends Error implements HasNextSteps {\n override readonly name = \"ReaperDetectedDuringWaitError\";\n constructor(\n public readonly taskId: string,\n public readonly previousOwner: string | null,\n public readonly workstream: string,\n ) {\n const ownerBit = previousOwner !== null ? `owner=${previousOwner}` : \"owner=<unknown>\";\n super(\n `task ${taskId} was IN_PROGRESS ${ownerBit} until just now; reaper detected dead pane and flipped to OPEN. wait abandoned. Re-dispatch a worker and retry.`,\n );\n }\n errorNextSteps(): NextStep[] {\n const ws = this.workstream;\n return [\n {\n intent: \"Inspect the task's current state\",\n command: `mu task show ${this.taskId} -w ${ws}`,\n },\n {\n intent: \"List live agents in the workstream (post-reap)\",\n command: `mu agent list -w ${ws}`,\n },\n {\n intent: \"Re-dispatch a fresh worker, then re-run the wait\",\n command: `mu agent spawn <name> -w ${ws} && mu task claim ${this.taskId} --for <name> -w ${ws}`,\n },\n ];\n }\n}\n\n/**\n * Thrown by the `mu task wait` CLI wrapper when `--on-stall exit` is\n * in effect and the existing `--stuck-after` predicate fires on a\n * watched task — the task is IN_PROGRESS, owned by a registered\n * agent whose detected status is `needs_input` for `>= stuckAfterMs`.\n *\n * Pairs with `ReaperDetectedDuringWaitError` (exit 6, dead pane).\n * Stall is the AMBIGUOUS sibling: the worker is alive but not\n * progressing — the operator decides whether it's transient (poke +\n * retry) or terminal (release + reopen). Exit code 7 = STALL_DETECTED\n * via classifyError, distinct from 6 so consumer scripts can branch.\n *\n * Carve-out (lives at the call site, not here): only fires when the\n * wait target is CLOSED — same logic as exit-6's reaper-flip\n * suppression. With `--status OPEN`/etc the worker reaching\n * needs_input might BE the success path.\n *\n * Surfaced by task_wait_stall_action_flag (the warn-only behaviour\n * pre-dates this; the typed-throw path is the new escape hatch for\n * unattended orchestrators).\n */\nexport class StallDetectedDuringWaitError extends Error implements HasNextSteps {\n override readonly name = \"StallDetectedDuringWaitError\";\n constructor(\n public readonly taskName: string,\n public readonly owner: string | null,\n public readonly workstream: string,\n public readonly ageSecs: number,\n ) {\n const ownerBit = owner !== null ? owner : \"<unknown>\";\n super(\n `task ${taskName} owned by ${ownerBit} has been needs_input for ${ageSecs}s; exiting per --on-stall exit. Re-dispatch a worker or send a poke (mu agent send ${ownerBit} \"...\") and re-run wait.`,\n );\n }\n errorNextSteps(): NextStep[] {\n const ws = this.workstream;\n const ownerBit = this.owner !== null ? this.owner : \"<owner>\";\n return [\n {\n intent: \"Poke the worker (often unblocks a transient stall)\",\n command: `mu agent send ${ownerBit} '<retry-instruction>' -w ${ws}`,\n },\n {\n intent: \"Inspect the worker's recent scrollback\",\n command: `mu agent show ${ownerBit} -w ${ws} -n 60`,\n },\n {\n intent: \"Release the task back to OPEN (declare the stall terminal)\",\n command: `mu task release ${this.taskName} --reopen -w ${ws}`,\n },\n {\n intent: \"Inspect the task's current state\",\n command: `mu task show ${this.taskName} -w ${ws}`,\n },\n ];\n }\n}\n\nexport class CrossWorkstreamEdgeError extends Error implements HasNextSteps {\n override readonly name = \"CrossWorkstreamEdgeError\";\n constructor(\n public readonly blocker: string,\n public readonly blockerWorkstream: string,\n public readonly dependent: string,\n public readonly dependentWorkstream: string,\n ) {\n super(\n `cross-workstream edge: blocker '${blocker}' is in workstream '${blockerWorkstream}', dependent '${dependent}' is in workstream '${dependentWorkstream}'`,\n );\n }\n errorNextSteps(): NextStep[] {\n // schema v5+: tasks.workstream_id is an INTEGER FK to\n // workstreams.id (no tasks.workstream column), and (workstream_id,\n // local_id) is the per-workstream unique key — so the move-blocker\n // recipe must scope by BOTH the source workstream's id AND set the\n // destination workstream's id via subselects. The v4-shaped\n // `UPDATE tasks SET workstream='…' WHERE local_id='…'` recipe we\n // used to print here errored at runtime (\"no such column:\n // workstream\") and was also ambiguous across workstreams.\n //\n // We also dropped the \"rename one workstream to the other\" hint:\n // it silently moves *every* task in the source workstream and\n // fails outright when the destination name already exists\n // (UNIQUE violation). Operators almost always want to move just\n // the blocker — or duplicate it — not merge whole workstreams.\n return [\n {\n intent: \"Move the blocker into the dependent's workstream\",\n command: `mu sql \"UPDATE tasks SET workstream_id=(SELECT id FROM workstreams WHERE name='${this.dependentWorkstream}') WHERE local_id='${this.blocker}' AND workstream_id=(SELECT id FROM workstreams WHERE name='${this.blockerWorkstream}')\"`,\n },\n {\n intent: \"Or duplicate the blocker (typed verb deferred)\",\n command: `mu task add <new-id> -w ${this.dependentWorkstream} --title \"<copy of ${this.blocker}>\" --impact <n> --effort-days <n>`,\n },\n ];\n }\n}\n","// mu — task edge reads and mutations.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { lookupTaskAnyWorkstream, taskIdFor, touchTask } from \"./core.js\";\nimport { CrossWorkstreamEdgeError, CycleError, TaskNotFoundError } from \"./errors.js\";\nimport { getTask } from \"./queries.js\";\nimport type { TaskStatus } from \"./status.js\";\n\nexport interface TaskEdges {\n /** Tasks that must close before this one can start (blockers). */\n blockers: string[];\n /** Tasks that this one blocks (dependents). */\n dependents: string[];\n}\n\nexport interface CanonicalBlocker {\n localId: string;\n id: number;\n}\n\nexport function dedupeBlockersById(blockers: readonly CanonicalBlocker[]): CanonicalBlocker[] {\n const seen = new Set<number>();\n const canonical: CanonicalBlocker[] = [];\n for (const blocker of blockers) {\n if (seen.has(blocker.id)) continue;\n seen.add(blocker.id);\n canonical.push(blocker);\n }\n return canonical;\n}\n\nfunction sameNumberSet(left: readonly number[], right: readonly number[]): boolean {\n if (left.length !== right.length) return false;\n const rightSet = new Set(right);\n if (rightSet.size !== right.length) return false;\n return left.every((value) => rightSet.has(value));\n}\n\n/** One end of an edge with the neighbour's current status attached.\n * Used by `mu task show` to group blockers/dependents into\n * \"still gating\" vs \"satisfied\" buckets without making the renderer\n * do a second round-trip to the DB per neighbour. */\nexport interface TaskEdgeWithStatus {\n name: string;\n status: TaskStatus;\n}\n\nexport interface TaskEdgesWithStatus {\n /** Tasks that must close before this one can start (blockers),\n * carrying each blocker's current status. */\n blockers: TaskEdgeWithStatus[];\n /** Tasks that this one blocks (dependents), carrying each\n * dependent's current status. */\n dependents: TaskEdgeWithStatus[];\n}\n\n/**\n * Direct (one-hop) edges for a task. For transitive prerequisites, use\n * `getPrerequisites()`; this helper is the immediate-neighbour view used\n * by `mu task show`.\n */\nexport function getTaskEdges(db: Db, taskLocalId: string, workstream: string): TaskEdges {\n const taskId = taskIdFor(db, taskLocalId, workstream);\n if (taskId === null) return { blockers: [], dependents: [] };\n const blockers = (\n db\n .prepare(\n `SELECT t.local_id AS id FROM task_edges e\n JOIN tasks t ON t.id = e.from_task_id\n WHERE e.to_task_id = ? ORDER BY t.local_id`,\n )\n .all(taskId) as { id: string }[]\n ).map((r) => r.id);\n const dependents = (\n db\n .prepare(\n `SELECT t.local_id AS id FROM task_edges e\n JOIN tasks t ON t.id = e.to_task_id\n WHERE e.from_task_id = ? ORDER BY t.local_id`,\n )\n .all(taskId) as { id: string }[]\n ).map((r) => r.id);\n return { blockers, dependents };\n}\n\n/**\n * Same one-hop edge view as `getTaskEdges`, but each neighbour is\n * returned as `{ name, status }` so callers can group / colour by\n * status without an N+1 round-trip. Used by `mu task show` to split\n * \"blocked by\" (still-gating) from \"satisfied\" (already-CLOSED)\n * blockers, and the symmetric split on the dependents side\n * (task_show_blocked_by_renders_closed). The status is the neighbour's\n * full TaskStatus, not just OPEN/CLOSED — REJECTED/DEFERRED still\n * gate downstream work, so the renderer keeps them in the\n * still-gating bucket.\n */\nexport function getTaskEdgesWithStatus(\n db: Db,\n taskLocalId: string,\n workstream: string,\n): TaskEdgesWithStatus {\n const taskId = taskIdFor(db, taskLocalId, workstream);\n if (taskId === null) return { blockers: [], dependents: [] };\n const blockers = db\n .prepare(\n `SELECT t.local_id AS name, t.status AS status FROM task_edges e\n JOIN tasks t ON t.id = e.from_task_id\n WHERE e.to_task_id = ? ORDER BY t.local_id`,\n )\n .all(taskId) as TaskEdgeWithStatus[];\n const dependents = db\n .prepare(\n `SELECT t.local_id AS name, t.status AS status FROM task_edges e\n JOIN tasks t ON t.id = e.to_task_id\n WHERE e.from_task_id = ? ORDER BY t.local_id`,\n )\n .all(taskId) as TaskEdgeWithStatus[];\n return { blockers, dependents };\n}\n\n/**\n * All tasks transitively reachable from `taskId` via reverse-edge\n * traversal (i.e. the set of tasks that block this one), including the\n * task itself.\n */\nexport function getPrerequisites(db: Db, taskLocalId: string, workstream: string): Set<string> {\n const taskId = taskIdFor(db, taskLocalId, workstream);\n if (taskId === null) return new Set([taskLocalId]);\n // Walk reverse edges in surrogate-id space, then translate back to\n // local_id strings. The seed must be the surrogate id; the result\n // includes the seed task itself (callers like the tracks union-find\n // rely on the inclusive set).\n const rows = db\n .prepare(\n `WITH RECURSIVE reach(node) AS (\n SELECT ?\n UNION\n SELECT from_task_id FROM task_edges, reach WHERE to_task_id = reach.node\n )\n SELECT t.local_id AS local_id FROM reach r JOIN tasks t ON t.id = r.node`,\n )\n .all(taskId) as { local_id: string }[];\n return new Set(rows.map((r) => r.local_id));\n}\n\n/**\n * Adding edge `from -> to` creates a cycle iff there's already a path\n * `to -> ... -> from`. SQL recursive CTE expresses this exactly.\n */\nexport function wouldCreateCycle(db: Db, fromId: number, toId: number): boolean {\n if (fromId === toId) return true;\n const result = db\n .prepare(\n `WITH RECURSIVE forward(node) AS (\n SELECT ?\n UNION\n SELECT to_task_id FROM task_edges, forward WHERE from_task_id = forward.node\n )\n SELECT 1 AS hit FROM forward WHERE node = ? LIMIT 1`,\n )\n .get(toId, fromId) as { hit: number } | undefined;\n return result !== undefined;\n}\n\nexport interface BlockEdgeResult {\n /** True iff a row was actually inserted (vs. already present). */\n added: boolean;\n}\n\n/**\n * Add the edge `blocker → blocked` ('blocker blocks blocked').\n * Idempotent (existing edge → `added: false`). Validates:\n *\n * - both tasks exist\n * - same workstream (cross-workstream edges forbidden)\n * - no cycle (the new edge wouldn't form a path blocked → ... → blocker)\n * - blocker ≠ blocked (no self-reference)\n */\nexport function addBlockEdge(\n db: Db,\n workstream: string,\n blocked: string,\n blocker: string,\n): BlockEdgeResult {\n if (blocked === blocker) {\n // Surface as a typed CycleError so the CLI maps it to exit 4 (conflict)\n // rather than letting the schema CHECK fire as a generic SQL error.\n throw new CycleError(blocker, blocked);\n }\n const blockedRow = getTask(db, blocked, workstream);\n if (!blockedRow) throw new TaskNotFoundError(blocked);\n // Resolve the blocker globally so a cross-workstream blocker surfaces\n // CrossWorkstreamEdgeError (clearer than TaskNotFoundError). Cycle\n // check + same-workstream guard run after.\n const blockerRow = lookupTaskAnyWorkstream(db, blocker);\n if (!blockerRow) throw new TaskNotFoundError(blocker);\n if (blockedRow.workstreamName !== blockerRow.workstreamName) {\n throw new CrossWorkstreamEdgeError(\n blocker,\n blockerRow.workstreamName,\n blocked,\n blockedRow.workstreamName,\n );\n }\n const blockedId = taskIdFor(db, blocked, blockedRow.workstreamName);\n const blockerId = taskIdFor(db, blocker, blockerRow.workstreamName);\n if (blockedId === null || blockerId === null) throw new TaskNotFoundError(blocked);\n if (wouldCreateCycle(db, blockerId, blockedId)) {\n throw new CycleError(blocker, blocked);\n }\n const now = new Date().toISOString();\n const added = db.transaction(() => {\n const result = db\n .prepare(\n \"INSERT OR IGNORE INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)\",\n )\n .run(blockerId, blockedId, now);\n if (result.changes > 0) {\n // Bump the BLOCKED task — its blocker set changed. The blocker\n // itself is unaffected. Aligned with reparentTask, which also\n // bumps the FROM_TASK side (the task whose blockers shifted).\n touchTask(db, blockedId, now);\n return true;\n }\n return false;\n })();\n if (added) emitEvent(db, blockedRow.workstreamName, `task block ${blocked} by ${blocker}`);\n return { added };\n}\n\nexport interface RemoveBlockEdgeResult {\n /** True iff a row was actually deleted (vs. no such edge). */\n removed: boolean;\n}\n\n/**\n * Remove the edge `blocker → blocked`. Idempotent (no edge →\n * `removed: false`). Does NOT validate task existence — if the\n * edge is gone there's nothing to do, regardless of whether the\n * tasks are gone too.\n */\nexport function removeBlockEdge(\n db: Db,\n workstream: string,\n blocked: string,\n blocker: string,\n): RemoveBlockEdgeResult {\n const blockedRow = getTask(db, blocked, workstream);\n if (!blockedRow) return { removed: false };\n const blockerRow = getTask(db, blocker, workstream);\n if (!blockerRow) return { removed: false };\n const blockedId = taskIdFor(db, blocked, blockedRow.workstreamName);\n const blockerId = taskIdFor(db, blocker, blockerRow.workstreamName);\n if (blockedId === null || blockerId === null) return { removed: false };\n const removed = db.transaction(() => {\n const result = db\n .prepare(\"DELETE FROM task_edges WHERE from_task_id = ? AND to_task_id = ?\")\n .run(blockerId, blockedId);\n if (result.changes > 0) {\n // Bump the BLOCKED task — its blocker set just shrank.\n touchTask(db, blockedId);\n return true;\n }\n return false;\n })();\n if (removed) {\n emitEvent(db, blockedRow.workstreamName, `task unblock ${blocked} by ${blocker}`);\n }\n return { removed };\n}\n\nexport interface ReparentTaskResult {\n /** Edges removed (i.e. all incoming `to_task = taskId` edges). */\n removedEdges: number;\n /** Edges added (after duplicate blockers are canonicalised). */\n addedEdges: number;\n}\n\n/**\n * Atomically replace every incoming edge of `taskId` with new ones\n * `blocker[i] → taskId`. Pass an empty `blockers` array to clear all\n * incoming edges (the task becomes ready iff its status allows).\n *\n * Validates ALL new blockers up-front (existence + same workstream +\n * cycle check); if any fails, no DELETE happens — the call is fully\n * atomic via a single transaction.\n *\n * Cycle reasoning: removing the existing incoming edges to `taskId`\n * doesn't change `taskId`'s OUTGOING reachability, so\n * `wouldCreateCycle(db, blocker, taskId)` evaluated against the\n * pre-state gives the right answer for each new edge.\n */\nexport function reparentTask(\n db: Db,\n taskLocalId: string,\n blockers: readonly string[],\n scope: { workstream: string },\n): ReparentTaskResult {\n const task = getTask(db, taskLocalId, scope.workstream);\n if (!task) throw new TaskNotFoundError(taskLocalId);\n const taskSurrogateId = taskIdFor(db, task.name, task.workstreamName);\n if (taskSurrogateId === null) throw new TaskNotFoundError(taskLocalId);\n\n // Resolve every blocker up-front to its surrogate id; do all\n // existence + same-workstream + cycle checks before any DELETE.\n // Look up blockers across all workstreams so a blocker that exists in\n // a DIFFERENT workstream surfaces CrossWorkstreamEdgeError (clearer\n // than TaskNotFoundError). Duplicate blockers are an ergonomic input\n // accident from repeat/CSV forms, so silently canonicalise by\n // surrogate id while preserving first-seen order.\n const requestedBlockers: CanonicalBlocker[] = [];\n for (const blockerLocalId of blockers) {\n if (blockerLocalId === taskLocalId) {\n throw new CycleError(blockerLocalId, taskLocalId);\n }\n const blocker = lookupTaskAnyWorkstream(db, blockerLocalId);\n if (!blocker) throw new TaskNotFoundError(blockerLocalId);\n if (blocker.workstreamName !== task.workstreamName) {\n throw new CrossWorkstreamEdgeError(\n blockerLocalId,\n blocker.workstreamName,\n taskLocalId,\n task.workstreamName,\n );\n }\n const blockerId = taskIdFor(db, blocker.name, blocker.workstreamName);\n if (blockerId === null) throw new TaskNotFoundError(blockerLocalId);\n if (wouldCreateCycle(db, blockerId, taskSurrogateId)) {\n throw new CycleError(blockerLocalId, taskLocalId);\n }\n requestedBlockers.push({ localId: blockerLocalId, id: blockerId });\n }\n const canonicalBlockers = dedupeBlockersById(requestedBlockers);\n const blockerIds = canonicalBlockers.map((blocker) => blocker.id);\n const currentBlockerIds = (\n db\n .prepare(\"SELECT from_task_id AS id FROM task_edges WHERE to_task_id = ?\")\n .all(taskSurrogateId) as { id: number }[]\n ).map((row) => row.id);\n\n if (sameNumberSet(currentBlockerIds, blockerIds)) {\n return { removedEdges: 0, addedEdges: 0 };\n }\n\n return db.transaction(() => {\n const removed = db.prepare(\"DELETE FROM task_edges WHERE to_task_id = ?\").run(taskSurrogateId);\n const insertEdge = db.prepare(\n \"INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)\",\n );\n const now = new Date().toISOString();\n for (const blockerId of blockerIds) {\n insertEdge.run(blockerId, taskSurrogateId, now);\n }\n // Bump the reparented task itself — its blocker set just changed.\n touchTask(db, taskSurrogateId, now);\n const blockerNames = canonicalBlockers.map((blocker) => blocker.localId);\n const blockersBit = blockerNames.length > 0 ? `, new=${blockerNames.join(\",\")}` : \"\";\n emitEvent(\n db,\n task.workstreamName,\n `task reparent ${taskLocalId} (removed ${removed.changes} edges, added ${blockerIds.length}${blockersBit})`,\n );\n return { removedEdges: removed.changes, addedEdges: blockerIds.length };\n })();\n}\n","// mu — workstream-level operations.\n//\n// One workstream = one tmux session + N agents + M tasks (and their\n// edges/notes) all sharing the workstream column. 0.1.0 ships `mu init`\n// (create the tmux session) and `mu destroy` (this module: nuke the\n// tmux session and every DB row tagged with the workstream name).\n//\n// `destroyWorkstream` is idempotent on every leg:\n// - tmux session already gone → killSession swallows the error\n// - no agents/tasks for this name → DELETE returns zero changes\n// - workstream never existed at all → returns all-zero counts\n//\n// Both summarize and destroy take an optional `tmuxSession` override so\n// tests (and the rare workstream whose tmux session was created with a\n// non-default name) work without env-var gymnastics.\n\nimport { existsSync, readdirSync, rmdirSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { type Db, defaultStateDir } from \"./db.js\";\nimport {\n type ExportManifest,\n type ExportSourceManifest,\n exportSourceForWorkstream,\n renderToBucket,\n} from \"./exporting.js\";\nimport { emitEvent } from \"./logs.js\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\nimport { parkedStatus } from \"./parked.js\";\nimport { captureSnapshot } from \"./snapshots.js\";\nimport { killSession, listSessions, sessionExists, tmux } from \"./tmux.js\";\nimport { type VcsBackend, type VcsBackendName, backendByName } from \"./vcs.js\";\nimport { listWorkspaces } from \"./workspace.js\";\n\n/**\n * Allowed workstream-name shape: lowercase alpha first, then alnum,\n * underscore, or hyphen, up to 32 chars total. Mirrors the agent-name\n * rule in VOCABULARY.md §\"Naming conventions\".\n *\n * Critically, this rule excludes `.` and `:` — tmux silently rewrites\n * `.` to `_` in session names (because `.` is the window/pane separator\n * in tmux's `session:window.pane` target syntax) and `:` is reserved\n * outright. A workstream name with `.` would create a session that mu\n * couldn't subsequently look up, breaking every downstream verb. We\n * fail loud at init time instead.\n */\nconst WORKSTREAM_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;\n\n/** Reserved prefix — mu auto-prepends `mu-` to derive the tmux session\n * name (so workstream `auth` lives in tmux session `mu-auth`). A\n * workstream named `mu-auth` would produce session `mu-mu-auth`,\n * which the user almost certainly didn't intend. Fail loud rather\n * than silently double-prefix. */\nexport const RESERVED_WORKSTREAM_PREFIX = \"mu-\";\n\nexport async function resolveTmuxSessionWorkstreamName(): Promise<string | null> {\n if (!process.env.TMUX) return null;\n try {\n const name = (await tmux([\"display-message\", \"-p\", \"#S\"])).trim();\n if (name.startsWith(RESERVED_WORKSTREAM_PREFIX)) {\n return name.slice(RESERVED_WORKSTREAM_PREFIX.length);\n }\n } catch {\n // fall through: tmux context is best-effort for workstream resolution\n }\n return null;\n}\n\nexport function isValidWorkstreamName(name: string): boolean {\n if (!WORKSTREAM_NAME_RE.test(name)) return false;\n if (name.startsWith(RESERVED_WORKSTREAM_PREFIX)) return false;\n return true;\n}\n\n/** Thrown by `ensureWorkstream` and `mu workstream init` when the name\n * doesn't match the rules. */\nexport class WorkstreamExistsError extends Error implements HasNextSteps {\n override readonly name: string = \"WorkstreamExistsError\";\n constructor(public readonly workstream: string) {\n super(`workstream already exists: ${workstream}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Pick a different workstream name\",\n command: \"mu archive restore <label> --as <new-name>\",\n },\n { intent: \"List existing workstreams\", command: \"mu workstream list\" },\n {\n intent: \"Destroy the existing workstream first\",\n command: `mu workstream destroy -w ${this.workstream} --yes`,\n },\n ];\n }\n}\n\nexport class WorkstreamNameInvalidError extends Error implements HasNextSteps {\n override readonly name = \"WorkstreamNameInvalidError\";\n constructor(public readonly attempted: string) {\n const reason = attempted.startsWith(RESERVED_WORKSTREAM_PREFIX)\n ? `the 'mu-' prefix is reserved (mu auto-prepends 'mu-' to derive the tmux session name; '${attempted}' would produce session 'mu-${attempted}', which is double-prefixed and almost never what you want). Drop the 'mu-' from the workstream name.`\n : `must match /^[a-z][a-z0-9_-]{0,31}$/. tmux silently rewrites '.' to '_' and reserves ':' as a target separator, so workstream names containing those characters would create tmux sessions mu couldn't look up afterwards. Use letters, digits, '_', and '-' only.`;\n super(`invalid workstream name ${JSON.stringify(attempted)}: ${reason}`);\n }\n errorNextSteps(): NextStep[] {\n // Suggest a sanitized form: strip the mu- prefix; replace dots and\n // colons with underscores; lowercase.\n const sanitized = this.attempted\n .toLowerCase()\n .replace(/^mu-/, \"\")\n .replace(/[.:]/g, \"_\")\n .slice(0, 32);\n // Branch the intent label on the failure class. For the mu-prefix\n // case the correction is unambiguous (drop the prefix), so phrase\n // the next-step as a direct action — \"Try a … (best guess)\" reads\n // as a hedge and dogfooding showed agents skip past the rationale\n // line entirely (workstream_init_name_rejected_mu in feedback ws).\n // For the regex/mangle branch the sanitiser really is guessing\n // (`.`/`:`/case all collapse), so the hedge stays honest there.\n const isPrefixCase = this.attempted.toLowerCase().startsWith(RESERVED_WORKSTREAM_PREFIX);\n const intent = isPrefixCase\n ? \"Retry without the 'mu-' prefix\"\n : \"Try a sanitized name (best guess)\";\n return [\n { intent, command: `mu workstream init ${sanitized || \"<name>\"}` },\n { intent: \"List existing workstreams\", command: \"mu workstream list\" },\n ];\n }\n}\n\nfunction assertValidWorkstreamName(name: string): void {\n if (!isValidWorkstreamName(name)) throw new WorkstreamNameInvalidError(name);\n}\n\n/**\n * Ensure a row exists in the `workstreams` table for `name`. Idempotent;\n * INSERT OR IGNORE so concurrent callers race safely. Called by\n * `insertAgent` and `addTask` so callers don't need to remember to call\n * `mu init` before adding a task / spawning an agent (preserves the\n * spawn-without-init ergonomics now that agents.workstream and\n * tasks.workstream are real FKs into this table).\n *\n * Validates the name before inserting; throws `WorkstreamNameInvalidError`\n * for names tmux would silently mangle (containing '.' or ':') or that\n * exceed 32 chars / start with a non-letter.\n *\n * Returns true iff a row was actually inserted (vs. already present).\n */\nexport function ensureWorkstream(db: Db, name: string): boolean {\n assertValidWorkstreamName(name);\n const result = db\n .prepare(\"INSERT OR IGNORE INTO workstreams (name, created_at) VALUES (?, ?)\")\n .run(name, new Date().toISOString());\n const created = result.changes > 0;\n if (created) emitEvent(db, name, `workstream init ${name}`);\n return created;\n}\n\nexport interface WorkstreamSummary {\n /** The workstream's own name. */\n name: string;\n /** Tmux session name, defaults to `mu-<name>`. */\n tmuxSession: string;\n /** True iff `tmux has-session -t <tmuxSession>` succeeds right now. */\n tmuxAlive: boolean;\n /** Rows in `agents` for this workstream. */\n agentCount: number;\n /** Rows in `tasks` for this workstream. */\n taskCount: number;\n /** Rows in `task_notes` whose task is in this workstream. */\n noteCount: number;\n /** Rows in `task_edges` whose `from_task` is in this workstream. */\n edgeCount: number;\n /** Rows in `vcs_workspaces` for this workstream. Surfaced so the\n * destroy dry-run can warn about per-agent worktrees that need\n * cleanup before the FK cascade silently nukes their rows. */\n workspaceCount: number;\n /** True iff a row exists in the `workstreams` table itself. False\n * for tmux-only `mu-*` sessions that mu never observed via\n * `mu workstream init`. Surfaced so destroy can clean up bare\n * registry rows (workstream row exists, no agents/tasks/etc.) —\n * otherwise such rows are orphaned forever (the previous\n * `nothingToDo` heuristic short-circuited on them). */\n registered: boolean;\n /** \"Presumed parked on another machine\" derived signal. Present\n * iff `parkedStatus(db, name)` reports `parked: true` (most recent\n * agent_logs row is a `db export` event, no alive agents, no\n * IN_PROGRESS tasks, threshold elapsed). Consumed by\n * `mu workstream list` and the TUI tab strip / workstreams card.\n * See src/parked.ts. */\n parked?: { sinceDays: number };\n}\n\nexport interface DestroyResult {\n /** True iff `tmux kill-session` actually killed something. */\n killedTmux: boolean;\n /** Number of `agents` rows deleted. */\n deletedAgents: number;\n /** Number of `tasks` rows deleted (edges/notes cascade via FK). */\n deletedTasks: number;\n /** Number of `task_notes` deleted by the cascade — informational. */\n deletedNotes: number;\n /** Number of `task_edges` deleted by the cascade — informational. */\n deletedEdges: number;\n /** Number of vcs_workspaces whose on-disk path was actually\n * removed by the backend on this destroy. Excludes\n * `alreadyGoneWorkspaces` (those were no-ops on disk). */\n freedWorkspaces: number;\n /** Number of vcs_workspaces whose registry row existed but\n * whose on-disk path was already gone (manual rm -rf or a prior\n * interrupted destroy). The DB row was cascade-deleted; the\n * backend did no filesystem work. Tracked separately so the\n * destroy report doesn't lie about how much cleanup it actually\n * performed. */\n alreadyGoneWorkspaces: number;\n /** Workspaces whose backend cleanup failed (e.g. `git worktree\n * remove` refused because of uncommitted changes). The DB row\n * was still cascade-deleted; the on-disk path remains and needs\n * manual cleanup. */\n failedWorkspaces: WorkspaceFailure[];\n}\n\nexport interface WorkspaceFailure {\n agent: string;\n backend: string;\n path: string;\n error: string;\n}\n\nexport interface WorkstreamOptions {\n workstream: string;\n /** Override the tmux session name. Defaults to `mu-<workstream>`. */\n tmuxSession?: string;\n /** Override the per-name VcsBackend resolver. Defaults to\n * `backendByName`. Lets tests inject a fake backend (e.g. one whose\n * `freeWorkspace` throws) without mutating the exported singletons —\n * same pattern as `createWorkspace`'s `opts.backend` accepting a\n * pre-built `VcsBackend` object. Production callers leave this\n * unset. */\n resolveBackend?: (name: VcsBackendName) => VcsBackend;\n}\n\nexport interface DestroyWorkstreamOptions extends WorkstreamOptions {\n /** Skip the per-workstream pre-mutation snapshot because the caller\n * already captured a broader snapshot for the whole destructive\n * operation. Used by `mu workstream destroy --empty` after its\n * sweep-level safety snapshot; direct destroy callers leave this\n * false so the default safety net is unchanged. */\n suppressSnapshot?: boolean;\n}\n\n/**\n * Discover every workstream visible on this machine. The union of:\n * - rows in the `workstreams` table (canonical DB source; populated by\n * `mu init` and auto-created by insertAgent / addTask)\n * - tmux sessions named `mu-*` (with the prefix stripped) — catches\n * externally-created `tmux new-session -s mu-foo` that mu hasn't\n * observed yet\n *\n * Returns one `WorkstreamSummary` per workstream, sorted by name.\n * Useful as a pre-flight before `mu init` (\"is this name taken?\") and\n * for `mu doctor`-style diagnostics.\n */\nexport async function listWorkstreams(db: Db): Promise<WorkstreamSummary[]> {\n const dbNames = new Set<string>(\n (db.prepare(\"SELECT name FROM workstreams\").all() as { name: string }[]).map((r) => r.name),\n );\n\n const tmuxNames = new Set<string>();\n for (const session of await listSessions()) {\n if (session.name.startsWith(RESERVED_WORKSTREAM_PREFIX))\n tmuxNames.add(session.name.slice(RESERVED_WORKSTREAM_PREFIX.length));\n }\n\n const allNames = Array.from(new Set([...dbNames, ...tmuxNames])).sort();\n return Promise.all(allNames.map((name) => summarizeWorkstream(db, { workstream: name })));\n}\n\n/**\n * Discover every workstream that has no user-meaningful state\n * attached. Two flavours unioned:\n *\n * 1. REGISTERED-empty: a row in `workstreams` with zero tasks,\n * zero agents, zero vcs_workspaces. Tmux\n * session presence and agent_logs entries do NOT disqualify\n * — the session itself was created at init time and contains\n * no agent panes; the events are audit, not state.\n *\n * 2. TMUX-only: a tmux session named `mu-*` with no row in the\n * `workstreams` table. Catches test litter and remnants of a\n * partial destroy where the DB row was wiped but the tmux\n * session survived (or sessions created out-of-band via\n * `tmux new-session -s mu-foo`). The synthetic summary has\n * `registered=false`, all counts 0, and `tmuxAlive=true` (it\n * wouldn't have been surfaced otherwise).\n *\n * The predicate is intentionally narrow on the prefix: only\n * `mu-*` sessions are eligible. Arbitrary tmux sessions the\n * operator created for unrelated work are NEVER matched — mu only\n * owns its own namespace.\n *\n * Used by `mu workstream destroy --empty` to sweep test-litter\n * workstreams in one command (instead of the per-name jq incantation\n * over `mu workstream list --json`).\n *\n * Returns one `WorkstreamSummary` per match, sorted by name (with\n * defensive dedup — a registered-empty and a tmux-only of the same\n * name can't both arise from the same call by construction, but\n * belt-and-braces).\n */\nexport async function listEmptyWorkstreams(db: Db): Promise<WorkstreamSummary[]> {\n const registeredRows = db\n .prepare(\n `SELECT ws.name AS name\n FROM workstreams ws\n LEFT JOIN tasks t ON t.workstream_id = ws.id\n LEFT JOIN agents a ON a.workstream_id = ws.id\n LEFT JOIN vcs_workspaces v ON v.workstream_id = ws.id\n GROUP BY ws.id, ws.name\n HAVING COUNT(DISTINCT t.id) = 0\n AND COUNT(DISTINCT a.id) = 0\n AND COUNT(DISTINCT v.id) = 0\n ORDER BY ws.name`,\n )\n .all() as { name: string }[];\n const registeredEmpty = await Promise.all(\n registeredRows.map((r) => summarizeWorkstream(db, { workstream: r.name })),\n );\n\n // Tmux-only mu-* sessions: enumerate every running tmux session,\n // keep the ones with the `mu-` prefix (strip it to get the\n // would-be workstream name), then subtract names already in the\n // `workstreams` table. The mirror of listWorkstreams above; see\n // its comment for the prefix rationale.\n const dbNames = new Set<string>(\n (db.prepare(\"SELECT name FROM workstreams\").all() as { name: string }[]).map((r) => r.name),\n );\n const tmuxOnlyNames: string[] = [];\n for (const session of await listSessions()) {\n if (!session.name.startsWith(\"mu-\")) continue;\n const name = session.name.slice(RESERVED_WORKSTREAM_PREFIX.length);\n if (dbNames.has(name)) continue;\n tmuxOnlyNames.push(name);\n }\n const tmuxOnly = await Promise.all(\n tmuxOnlyNames.map((name) => summarizeWorkstream(db, { workstream: name })),\n );\n\n // Compose + sort + dedup-by-name (defensive; no overlap is possible\n // by construction since tmuxOnlyNames excludes every dbName).\n const seen = new Set<string>();\n const all: WorkstreamSummary[] = [];\n for (const ws of [...registeredEmpty, ...tmuxOnly]) {\n if (seen.has(ws.name)) continue;\n seen.add(ws.name);\n all.push(ws);\n }\n all.sort((a, b) => a.name.localeCompare(b.name));\n return all;\n}\n\nexport async function summarizeWorkstream(\n db: Db,\n opts: WorkstreamOptions,\n): Promise<WorkstreamSummary> {\n const tmuxSession = opts.tmuxSession ?? `mu-${opts.workstream}`;\n const parked = parkedStatus(db, opts.workstream);\n return {\n name: opts.workstream,\n tmuxSession,\n tmuxAlive: await sessionExists(tmuxSession),\n agentCount: countAgents(db, opts.workstream),\n taskCount: countTasks(db, opts.workstream),\n noteCount: countNotes(db, opts.workstream),\n edgeCount: countEdges(db, opts.workstream),\n workspaceCount: listWorkspaces(db, opts.workstream).length,\n registered: isRegistered(db, opts.workstream),\n ...(parked.parked ? { parked: { sinceDays: parked.sinceDays ?? 0 } } : {}),\n };\n}\n\nfunction isRegistered(db: Db, workstream: string): boolean {\n const row = db.prepare(\"SELECT 1 AS x FROM workstreams WHERE name = ?\").get(workstream) as\n | { x: number }\n | undefined;\n return row !== undefined;\n}\n\n/**\n * Tear down a workstream: kill its tmux session and delete every DB row\n * tagged with its name. Cascades on `tasks` clean up `task_edges` and\n * `task_notes` automatically (FK ON DELETE CASCADE in the schema).\n *\n * Idempotent: safe to call against a workstream that never existed; safe\n * to call repeatedly. Returns counts so the caller can print a useful\n * summary.\n */\nexport async function destroyWorkstream(\n db: Db,\n opts: DestroyWorkstreamOptions,\n): Promise<DestroyResult> {\n const tmuxSession = opts.tmuxSession ?? `mu-${opts.workstream}`;\n\n // Pre-mutation snapshot (snap_design §EDGE CASES > WORKSTREAM\n // DESTROY). workstream=null because workstream-destroy snapshots\n // logically span every workstream in the DB (whole-DB backup;\n // anchoring to one name would lie about scope). If the snapshot\n // throws (disk full, perms), abort the destroy — better to refuse\n // than to delete irrecoverably.\n if (opts.suppressSnapshot !== true) {\n captureSnapshot(db, `workstream destroy ${opts.workstream}`, null);\n }\n\n // Pre-count the cascade victims so we can report them — SQLite's\n // changes() only reports rows directly affected by the last statement,\n // not cascade victims.\n const agentsBefore = countAgents(db, opts.workstream);\n const tasksBefore = countTasks(db, opts.workstream);\n const notesBefore = countNotes(db, opts.workstream);\n const edgesBefore = countEdges(db, opts.workstream);\n const workspacesBefore = listWorkspaces(db, opts.workstream);\n\n // Tmux first: if killSession throws we don't want the DB rows already\n // gone with no way to recover. (killSession is itself idempotent on\n // missing sessions — a real throw here is an unexpected tmux error.)\n const tmuxAliveBefore = await sessionExists(tmuxSession);\n if (tmuxAliveBefore) {\n await killSession(tmuxSession);\n }\n\n // Workspaces SECOND, before the FK cascade. The cascade silently\n // deletes vcs_workspaces rows but leaves the on-disk worktrees\n // (and the git worktree registry entries) behind — the bug from\n // mufeedback note #195. Per backend, the right cleanup is\n // 'git worktree remove --force' / 'jj workspace forget' / etc.,\n // not 'rm -rf'. We surface failures so the user can recover; we\n // do NOT abort the destroy on workspace failure (the workstream\n // semantics are 'tear it all down', not 'partial cleanup').\n let freedWorkspaces = 0;\n let alreadyGoneWorkspaces = 0;\n const failedWorkspaces: WorkspaceFailure[] = [];\n const resolveBackend = opts.resolveBackend ?? backendByName;\n for (const ws of workspacesBefore) {\n try {\n const backend = resolveBackend(ws.backend);\n const result = await backend.freeWorkspace({\n workspacePath: ws.path,\n commit: false,\n });\n if (result.removed) {\n // Backend actually removed the on-disk path. This is the\n // only case that counts as 'work done by destroy'.\n freedWorkspaces += 1;\n } else {\n // Path was already gone (manual rm -rf or interrupted prior\n // destroy). The DB row is cascade-deleted below either way,\n // but we don't claim to have freed anything on disk — it was\n // already in the desired state. Tracked separately so the\n // user can spot stale registry rows from past mishaps.\n alreadyGoneWorkspaces += 1;\n }\n } catch (err) {\n failedWorkspaces.push({\n agent: ws.agentName,\n backend: ws.backend,\n path: ws.path,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // After every per-agent worktree is freed, the parent\n // <state>/workspaces/<workstream>/ directory is empty — reap it\n // too. Best-effort: rmdir refuses if non-empty (e.g. backend\n // removal failed and left files behind), which is the right\n // outcome (don't silently rm -rf user data). Skipped if the\n // parent doesn't exist (workstream never had any workspaces).\n const parentDir = join(defaultStateDir(), \"workspaces\", opts.workstream);\n if (existsSync(parentDir)) {\n try {\n if (readdirSync(parentDir).length === 0) rmdirSync(parentDir);\n } catch {\n // Non-empty or otherwise unreapable. The failed-workspaces\n // list above already tells the user what to clean.\n }\n }\n\n // One DELETE: the FK CASCADE chain (workstreams → agents,\n // workstreams → tasks → task_edges + task_notes, workstreams →\n // agent_logs, workstreams → vcs_workspaces) cleans every row in\n // one shot, atomically. If the workstream was never registered\n // (e.g. an orphan tmux session that mu never observed),\n // changes() = 0 and we still report the killed tmux session\n // honestly.\n const result = db.prepare(\"DELETE FROM workstreams WHERE name = ?\").run(opts.workstream);\n // The destroy event itself goes to workstream=null (machine-wide)\n // because the FK CASCADE we just triggered would otherwise wipe\n // it on the same statement. Visible via `mu log --all`.\n if (result.changes > 0 || tmuxAliveBefore) {\n emitEvent(\n db,\n null,\n `workstream destroy ${opts.workstream} (agents=${agentsBefore}, tasks=${tasksBefore}, edges=${edgesBefore}, notes=${notesBefore}, workspaces=${freedWorkspaces}/${workspacesBefore.length}, already_gone=${alreadyGoneWorkspaces}, tmux=${tmuxAliveBefore})`,\n );\n }\n\n return {\n killedTmux: tmuxAliveBefore,\n deletedAgents: agentsBefore,\n deletedTasks: tasksBefore,\n deletedNotes: notesBefore,\n deletedEdges: edgesBefore,\n freedWorkspaces,\n alreadyGoneWorkspaces,\n failedWorkspaces,\n };\n}\n\n// ─── Counts ────────────────────────────────────────────────────────────\n\nfunction countAgents(db: Db, workstream: string): number {\n const row = db\n .prepare(\n `SELECT COUNT(*) AS n FROM agents a\n JOIN workstreams ws ON ws.id = a.workstream_id\n WHERE ws.name = ?`,\n )\n .get(workstream) as { n: number };\n return row.n;\n}\n\nfunction countTasks(db: Db, workstream: string): number {\n const row = db\n .prepare(\n `SELECT COUNT(*) AS n FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE ws.name = ?`,\n )\n .get(workstream) as { n: number };\n return row.n;\n}\n\nfunction countNotes(db: Db, workstream: string): number {\n const row = db\n .prepare(\n `SELECT COUNT(*) AS n\n FROM task_notes n\n JOIN tasks t ON t.id = n.task_id\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE ws.name = ?`,\n )\n .get(workstream) as { n: number };\n return row.n;\n}\n\nfunction countEdges(db: Db, workstream: string): number {\n // Count edges whose blocker (from_task) is in the workstream. Since\n // cross-workstream edges are forbidden by addTask, this equals the\n // edge count for the workstream subgraph.\n const row = db\n .prepare(\n `SELECT COUNT(*) AS n\n FROM task_edges e\n JOIN tasks t ON t.id = e.from_task_id\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE ws.name = ?`,\n )\n .get(workstream) as { n: number };\n return row.n;\n}\n\n// ─── exportWorkstream ──────────────────────────────────────────────────\n//\n// Thin sugar over `renderToBucket` (src/exporting.ts): one live\n// workstream → one ExportSource → bucket render. The renderer holds\n// every byte of layout knowledge; this wrapper just adapts the SDK\n// reads (listTasks / getTaskEdges / listNotes / latestSeq) and\n// emits the workstream-flavoured event.\n//\n// The on-disk shape is the v0.3 BUCKET layout (see src/exporting.ts):\n//\n// <outDir>/\n// README.md / INDEX.md / manifest.json # bucket-level\n// <workstream>/\n// README.md / INDEX.md / tasks/<id>.md # per-source-ws\n//\n// Re-export against the same outDir is additive: a different `-w`\n// adds a sibling subdir without touching the existing one. A re-run\n// with the same `-w` refreshes that subdir (sha256 short-circuit).\n//\n// Anti-features (preserved from the originating design note):\n// - re-import: out of scope\n// - HTML/PDF: markdown-only\n// - embedded VCS: caller can `git init && git add . && git commit`\n// - cross-workstream merge: source-ws subdirs stay separate\n\nexport interface ExportWorkstreamOptions {\n workstream: string;\n /** Output directory (the bucket). Defaults to `./<workstream>/`\n * in the cwd — i.e. the bucket and its single source-ws subdir\n * share a name. */\n outDir?: string;\n}\n\nexport interface ExportResult {\n outDir: string;\n /** Per-task files rewritten this call. */\n written: number;\n /** Per-task files sha256-skipped this call. */\n unchanged: number;\n /** Tasks present in a prior manifest that are no longer in the DB.\n * Their .md stays on disk; a banner is added once. */\n preserved: number;\n manifestPath: string;\n manifest: ExportManifest;\n /** Per-source-ws manifest entry for this workstream — convenience\n * for callers who only want one source's view. */\n source: ExportSourceManifest;\n}\n\n/**\n * Export one live workstream to a bucket directory. Idempotent +\n * additive: re-exporting the same workstream is sha256-skipped,\n * exporting a different workstream into the same bucket appends a\n * sibling subdir.\n *\n */\nexport function exportWorkstream(db: Db, opts: ExportWorkstreamOptions): ExportResult {\n const outDir = resolve(opts.outDir ?? join(process.cwd(), opts.workstream));\n const source = exportSourceForWorkstream(db, opts.workstream);\n const result = renderToBucket({\n sources: [source],\n bucketLabel: null,\n outDir,\n });\n const sourceManifest = result.manifest.sources[opts.workstream];\n if (!sourceManifest) {\n // Defensive: renderToBucket always inserts a manifest entry per\n // source it received. If this ever fires the renderer regressed.\n throw new Error(\n `exportWorkstream: renderer did not write a manifest entry for ${opts.workstream}`,\n );\n }\n emitEvent(\n db,\n opts.workstream,\n `workstream export ${opts.workstream} (out=${result.outDir}, tasks=${source.tasks.length}, written=${result.written}, unchanged=${result.unchanged}, preserved=${result.preserved})`,\n );\n return {\n outDir: result.outDir,\n written: result.written,\n unchanged: result.unchanged,\n preserved: result.preserved,\n manifestPath: result.manifestPath,\n manifest: result.manifest,\n source: sourceManifest,\n };\n}\n","// mu — unified bucket renderer for workstream / archive exports.\n//\n// One renderer, two entry points (`mu workstream export` and\n// `mu archive export`). Both produce the same on-disk shape: a\n// \"bucket\" directory whose top-level contains a bucket-wide README +\n// INDEX + manifest, and one subdirectory per source workstream that\n// holds the per-workstream README + INDEX + tasks/<id>.md files.\n//\n// The bucket layout is ADDITIVE: re-running `mu workstream export\n// -w X --out <bucket>` over an existing bucket either appends a new\n// source-ws subdirectory (if X wasn't there before) or refreshes the\n// existing subdirectory's contents in place (sha256 short-circuit).\n// Source-ws subdirectories from earlier exports are NEVER touched\n// by an unrelated source-ws's re-export.\n//\n// Disk shape (`bucketVersion: 2`):\n//\n// <bucket>/\n// README.md # bucket-level summary (every source-ws + dates + totals)\n// INDEX.md # union of all task tables; first column = source-ws\n// manifest.json # bucketVersion: 2 + per-source-ws sha256 + per-task sha256\n// <source-ws>/\n// README.md # per-source-ws (counts)\n// INDEX.md # per-source-ws (table of every task)\n// tasks/<id>.md # one .md per task; YAML frontmatter + notes\n//\n// Origin: this code was lifted out of `src/workstream.ts`'s\n// `exportWorkstream` (single-source rendering) and generalised to N\n// sources. The single-source case is preserved as a thin wrapper\n// (see exportWorkstream in src/workstream.ts) that builds a one-\n// element `sources` array and delegates here.\n\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { listArchivedTasks } from \"./archives.js\";\nimport type { Db } from \"./db.js\";\nimport { emitEvent, latestSeq } from \"./logs.js\";\nimport { getTaskEdges, listNotes, listTasks } from \"./tasks.js\";\nimport type { TaskNoteRow, TaskRow } from \"./tasks.js\";\nimport { isTaskStatus } from \"./tasks/status.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport const EXPORT_MANIFEST_VERSION = 2;\n\n/** One per-task summary inside a per-source-ws section of the manifest. */\nexport interface ExportTaskEntry {\n /** Task local_id == filename stem (`<id>.md`). Kept for v1 manifest compatibility. */\n id: string;\n /** Task local_id, duplicated under the operator-facing SDK name so bucket INDEX can render from manifest alone. */\n name: string;\n /** Compact summary fields needed for bucket-level INDEX.md without re-reading the DB. */\n title: string;\n status: TaskRow[\"status\"];\n impact: number;\n effortDays: number;\n /** Path relative to the bucket root (e.g. `auth/tasks/design.md`). */\n path: string;\n /** sha256 of the markdown body bytes; idempotency key. */\n sha256: string;\n /** ISO timestamp of the first observed export at which the task\n * was missing from the source. Absent for tasks still present. */\n deletedAt?: string;\n}\n\n/** Per-source-ws entry under `manifest.sources`. */\nexport interface ExportSourceManifest {\n /** ISO timestamp the source was first added to the bucket. */\n addedAt: string;\n /** ISO timestamp of the most recent re-export of this source. */\n lastReExportedAt: string;\n /** `latestSeq(db)` at the most recent re-export; for live workstreams\n * this is the live `agent_logs.seq` cursor. For archive sources\n * there is no equivalent live counter — we record the seq at\n * archive-add time when available, else 0. */\n eventsSeqAtExport: number;\n /** Per-task entries; sorted by id for stable diffs. */\n tasks: ExportTaskEntry[];\n}\n\n/** Top-level bucket manifest. `bucketVersion: 2` — the v0.3 disk layout.\n * `manifest_version` is the schema of the manifest JSON payload itself:\n * v1 lacked task summaries, v2 stores enough per-task data to render\n * bucket INDEX.md from `manifest.sources` alone. Manifests without\n * `bucketVersion: 2` fall through to the `corrupt` lane in `readManifest`. */\nexport interface ExportManifest {\n /** Disk-layout discriminator. Always 2 in this codebase. */\n bucketVersion: 2;\n /** Manifest-payload discriminator. Always 2 when written by this codebase. */\n manifest_version: typeof EXPORT_MANIFEST_VERSION;\n /** Operator-chosen bucket label (an archive label, or null for a\n * one-shot `mu workstream export`). Surfaced in README only. */\n bucketLabel: string | null;\n bucketCreatedAt: string;\n bucketLastUpdatedAt: string;\n muVersion: string;\n /** Per-source-ws map; key is the source workstream's TEXT name. */\n sources: Record<string, ExportSourceManifest>;\n}\n\n/** One source's worth of input: the per-task data the renderer needs.\n * Both entry points (workstream / archive) collapse to this shape. */\nexport interface ExportSource {\n /** Source workstream name. Becomes the subdirectory name. */\n name: string;\n tasks: TaskRow[];\n /** Per-task edges keyed on task name. Missing keys → no edges. */\n edges: Map<string, { blockers: string[]; dependents: string[] }>;\n /** Per-task notes keyed on task name. Missing keys → no notes. */\n notes: Map<string, TaskNoteRow[]>;\n /** `agent_logs.seq` cursor at this source's snapshot moment. 0 for\n * archive sources (no live cursor). */\n eventsSeqAtExport: number;\n}\n\nexport interface RenderBucketInput {\n sources: ExportSource[];\n /** Operator-chosen archive label, or null for a workstream export. */\n bucketLabel: string | null;\n outDir: string;\n}\n\nexport interface RenderBucketResult {\n outDir: string;\n /** Per-source-ws stat: how many task files were rewritten across\n * every source in this call. */\n written: number;\n /** Per-source-ws stat: how many task files were sha256-skipped. */\n unchanged: number;\n /** Per-source-ws stat: how many task files exist for a task that\n * has since vanished from the source. Banner is added once. */\n preserved: number;\n manifestPath: string;\n manifest: ExportManifest;\n}\n\n// ─── Markdown render helpers (per-task) ──────────────────────────────\n\n/** Wrap arbitrary text in a fenced code block, choosing a fence\n * longer than any backtick run inside `body` so the body's literal\n * ``` (or ````, etc.) survives intact. Used for note content,\n * which routinely contains markdown / code / triple-fences. */\nexport function fenceForBody(body: string): string {\n const longestRun = (body.match(/`+/g) ?? []).reduce((m, s) => Math.max(m, s.length), 0);\n return \"`\".repeat(Math.max(3, longestRun + 1));\n}\n\n/** YAML-ish scalar quote: always double-quoted, with `\"` and `\\\\`\n * escaped. Multi-line values are coerced to single-line by\n * replacing newlines with ` ` so the frontmatter block stays\n * valid YAML. */\nexport function yamlScalar(value: string): string {\n return `\"${value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \" \")}\"`;\n}\n\nexport function renderTaskMarkdown(\n task: TaskRow,\n edges: { blockers: string[]; dependents: string[] },\n notes: TaskNoteRow[],\n): string {\n const lines: string[] = [];\n lines.push(\"---\");\n lines.push(`id: ${yamlScalar(task.name)}`);\n lines.push(`workstream: ${yamlScalar(task.workstreamName)}`);\n lines.push(`status: ${task.status}`);\n lines.push(`impact: ${task.impact}`);\n lines.push(`effort_days: ${task.effortDays}`);\n // ROI is derived but a load-bearing field for operators ranking\n // closed tasks in retrospect; emit it precomputed so consumers\n // don't have to re-derive.\n lines.push(`roi: ${(task.impact / task.effortDays).toFixed(2)}`);\n lines.push(`owner: ${task.ownerName === null ? \"null\" : yamlScalar(task.ownerName)}`);\n lines.push(`created_at: ${yamlScalar(task.createdAt)}`);\n lines.push(`updated_at: ${yamlScalar(task.updatedAt)}`);\n lines.push(`blocked_by: [${edges.blockers.map(yamlScalar).join(\", \")}]`);\n lines.push(`blocks: [${edges.dependents.map(yamlScalar).join(\", \")}]`);\n lines.push(\"---\");\n lines.push(\"\");\n lines.push(`# ${task.title}`);\n lines.push(\"\");\n if (notes.length === 0) {\n lines.push(\"_No notes._\");\n lines.push(\"\");\n } else {\n lines.push(`## Notes (${notes.length})`);\n lines.push(\"\");\n for (const [i, note] of notes.entries()) {\n const author = note.author === null ? \"null\" : yamlScalar(note.author);\n lines.push(`### #${i + 1} by ${author}, ${note.createdAt}`);\n lines.push(\"\");\n const fence = fenceForBody(note.content);\n lines.push(fence);\n lines.push(note.content);\n lines.push(fence);\n lines.push(\"\");\n }\n }\n // Trailing newline so POSIX tools (and git diff) don't complain.\n return `${lines.join(\"\\n\")}`.replace(/\\n*$/, \"\\n\");\n}\n\n/** Per-source-ws INDEX.md — one row per task in this source. */\nexport function renderSourceIndexMarkdown(workstream: string, tasks: TaskRow[]): string {\n const lines: string[] = [];\n lines.push(`# ${workstream} — task index`);\n lines.push(\"\");\n if (tasks.length === 0) {\n lines.push(\"_No tasks._\");\n lines.push(\"\");\n return lines.join(\"\\n\");\n }\n lines.push(\"| id | status | impact | effort | ROI | title |\");\n lines.push(\"| --- | --- | --- | --- | --- | --- |\");\n for (const t of tasks) {\n const roi = (t.impact / t.effortDays).toFixed(2);\n const title = t.title.replace(/\\|/g, \"\\\\|\");\n lines.push(\n `| [\\`${t.name}\\`](tasks/${t.name}.md) | ${t.status} | ${t.impact} | ${t.effortDays} | ${roi} | ${title} |`,\n );\n }\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** Per-source-ws README.md — counts and pointer to INDEX.md. */\nexport function renderSourceReadmeMarkdown(\n workstream: string,\n tasks: TaskRow[],\n exportedAt: string,\n): string {\n const counts: Record<string, number> = {\n OPEN: 0,\n IN_PROGRESS: 0,\n CLOSED: 0,\n REJECTED: 0,\n DEFERRED: 0,\n };\n for (const t of tasks) counts[t.status] = (counts[t.status] ?? 0) + 1;\n const lines: string[] = [];\n lines.push(`# Source workstream: ${workstream}`);\n lines.push(\"\");\n lines.push(`Exported at: ${exportedAt}`);\n lines.push(\"\");\n lines.push(`- Tasks: ${tasks.length}`);\n for (const status of [\"OPEN\", \"IN_PROGRESS\", \"CLOSED\", \"REJECTED\", \"DEFERRED\"] as const) {\n lines.push(` - ${status}: ${counts[status] ?? 0}`);\n }\n lines.push(\"\");\n lines.push(\"See `INDEX.md` for the task table; one `.md` per task in `tasks/`.\");\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** Bucket-level README.md — multi-source summary. */\nexport function renderBucketReadmeMarkdown(manifest: ExportManifest): string {\n const lines: string[] = [];\n const label = manifest.bucketLabel ?? \"(no label)\";\n lines.push(`# Export bucket: ${label}`);\n lines.push(\"\");\n lines.push(`- Bucket created at: ${manifest.bucketCreatedAt}`);\n lines.push(`- Bucket last updated at: ${manifest.bucketLastUpdatedAt}`);\n lines.push(`- mu version: ${manifest.muVersion}`);\n lines.push(`- Bucket layout version: ${manifest.bucketVersion}`);\n lines.push(`- Manifest version: ${manifest.manifest_version}`);\n lines.push(\"\");\n const sources = Object.entries(manifest.sources).sort(([a], [b]) => a.localeCompare(b));\n lines.push(`## Sources (${sources.length})`);\n lines.push(\"\");\n if (sources.length === 0) {\n lines.push(\"_No sources yet._\");\n lines.push(\"\");\n } else {\n lines.push(\"| source workstream | tasks | added | last re-exported |\");\n lines.push(\"| --- | --- | --- | --- |\");\n for (const [name, src] of sources) {\n lines.push(\n `| [\\`${name}\\`](${name}/README.md) | ${src.tasks.length} | ${src.addedAt} | ${src.lastReExportedAt} |`,\n );\n }\n lines.push(\"\");\n }\n lines.push(\n \"_Bucket exports are additive: re-running `mu workstream export -w <ws> --out <this-dir>` appends or refreshes one source-ws subdirectory; `mu archive export <label> --out <this-dir>` (re)builds every source-ws from the named archive. See `INDEX.md` for the cross-source task table and `manifest.json` for per-task sha256s._\",\n );\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** Bucket-level INDEX.md — union of every source-ws's task table,\n * with a leading source-ws column to disambiguate cross-source. */\nfunction taskEntryName(entry: Pick<ExportTaskEntry, \"id\"> & { name?: string }): string {\n return entry.name ?? entry.id;\n}\n\nfunction taskEntryFromTask(task: TaskRow, path: string, sha256: string): ExportTaskEntry {\n return {\n id: task.name,\n name: task.name,\n title: task.title,\n status: task.status,\n impact: task.impact,\n effortDays: task.effortDays,\n path,\n sha256,\n };\n}\n\nexport function renderBucketIndexMarkdown(manifest: ExportManifest): string {\n const lines: string[] = [];\n const label = manifest.bucketLabel ?? \"(no label)\";\n lines.push(`# ${label} — task index (all sources)`);\n lines.push(\"\");\n const sourcesWithTasks = Object.entries(manifest.sources)\n .map(([name, source]) => ({\n name,\n tasks: source.tasks.filter((task) => task.deletedAt === undefined),\n }))\n .filter((source) => source.tasks.length > 0);\n if (sourcesWithTasks.length === 0) {\n lines.push(\"_No tasks._\");\n lines.push(\"\");\n return lines.join(\"\\n\");\n }\n lines.push(\"| source-ws | id | status | impact | effort | ROI | title |\");\n lines.push(\"| --- | --- | --- | --- | --- | --- | --- |\");\n // Stable sort across sources: source name then task name.\n const sortedSources = sourcesWithTasks.sort((a, b) => a.name.localeCompare(b.name));\n for (const src of sortedSources) {\n for (const t of src.tasks) {\n const name = taskEntryName(t);\n const roi = (t.impact / t.effortDays).toFixed(2);\n const title = t.title.replace(/\\|/g, \"\\\\|\");\n lines.push(\n `| ${src.name} | [\\`${name}\\`](${t.path}) | ${t.status} | ${t.impact} | ${t.effortDays} | ${roi} | ${title} |`,\n );\n }\n }\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n// ─── Deletion banner ─────────────────────────────────────────────────\n\nexport const DELETED_BANNER_PREFIX = \"> **Deleted from DB on \";\n\nexport function bannerFor(timestamp: string): string {\n return `${DELETED_BANNER_PREFIX}${timestamp}** — this task no longer exists in mu's database. The export below is the last-known state. Re-export will not regenerate it.\\n\\n`;\n}\n\n// ─── manifest.json read/parse ────────────────────────────────────────\n\n/** Read an existing bucket manifest. Returns `{ kind: \"v2\", manifest }`\n * for a v0.3+ bucket; `{ kind: \"absent\" }` if the file doesn't\n * exist; `{ kind: \"corrupt\" }` for anything else. The pre-0.3\n * (single-source, top-level `workstream` + `tasks`) shape is no\n * longer recognized — v0.3 shipped 2026-05-10 and there are no\n * pre-v0.3 buckets in the wild to keep a detection branch for. */\nexport type ManifestProbe =\n | { kind: \"v2\"; manifest: ExportManifest }\n | { kind: \"absent\" }\n | { kind: \"corrupt\" };\n\nexport function readManifest(path: string): ManifestProbe {\n if (!existsSync(path)) return { kind: \"absent\" };\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return { kind: \"corrupt\" };\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return { kind: \"corrupt\" };\n }\n if (typeof parsed !== \"object\" || parsed === null) return { kind: \"corrupt\" };\n const obj = parsed as Record<string, unknown>;\n if (obj.bucketVersion === 2 && typeof obj.sources === \"object\" && obj.sources !== null) {\n const manifest = migrateManifest(obj, dirname(path));\n return manifest ? { kind: \"v2\", manifest } : { kind: \"corrupt\" };\n }\n return { kind: \"corrupt\" };\n}\n\nfunction migrateManifest(obj: Record<string, unknown>, bucketDir: string): ExportManifest | null {\n const rawVersion = obj.manifest_version;\n const version = rawVersion === undefined ? 1 : rawVersion;\n if (version !== 1 && version !== 2) return null;\n const sources = migrateSources(obj.sources, bucketDir);\n if (sources === null) return null;\n return {\n bucketVersion: 2,\n manifest_version: EXPORT_MANIFEST_VERSION,\n bucketLabel:\n typeof obj.bucketLabel === \"string\" || obj.bucketLabel === null ? obj.bucketLabel : null,\n bucketCreatedAt: typeof obj.bucketCreatedAt === \"string\" ? obj.bucketCreatedAt : \"\",\n bucketLastUpdatedAt: typeof obj.bucketLastUpdatedAt === \"string\" ? obj.bucketLastUpdatedAt : \"\",\n muVersion: typeof obj.muVersion === \"string\" ? obj.muVersion : \"unknown\",\n sources,\n };\n}\n\nfunction migrateSources(\n raw: unknown,\n bucketDir: string,\n): Record<string, ExportSourceManifest> | null {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) return null;\n const out: Record<string, ExportSourceManifest> = {};\n for (const [sourceName, value] of Object.entries(raw)) {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return null;\n const source = value as Record<string, unknown>;\n const tasks = Array.isArray(source.tasks)\n ? source.tasks.map((task) => migrateTaskEntry(sourceName, task, bucketDir))\n : [];\n if (tasks.some((task) => task === null)) return null;\n out[sourceName] = {\n addedAt: typeof source.addedAt === \"string\" ? source.addedAt : \"\",\n lastReExportedAt: typeof source.lastReExportedAt === \"string\" ? source.lastReExportedAt : \"\",\n eventsSeqAtExport:\n typeof source.eventsSeqAtExport === \"number\" ? source.eventsSeqAtExport : 0,\n tasks: tasks.filter((task): task is ExportTaskEntry => task !== null),\n };\n }\n return out;\n}\n\nfunction migrateTaskEntry(\n sourceName: string,\n raw: unknown,\n bucketDir: string,\n): ExportTaskEntry | null {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) return null;\n const entry = raw as Record<string, unknown>;\n const id = typeof entry.id === \"string\" ? entry.id : undefined;\n const name = typeof entry.name === \"string\" ? entry.name : id;\n const path = typeof entry.path === \"string\" ? entry.path : `${sourceName}/tasks/${name ?? \"\"}.md`;\n const inferred = inferTaskSummaryFromMarkdown(join(bucketDir, path), name ?? id ?? \"\");\n const title = typeof entry.title === \"string\" ? entry.title : inferred.title;\n const status =\n typeof entry.status === \"string\" && isTaskStatus(entry.status) ? entry.status : inferred.status;\n const impact =\n typeof entry.impact === \"number\" && Number.isFinite(entry.impact)\n ? entry.impact\n : inferred.impact;\n const effortDays =\n typeof entry.effortDays === \"number\" &&\n Number.isFinite(entry.effortDays) &&\n entry.effortDays > 0\n ? entry.effortDays\n : inferred.effortDays;\n const sha256 = typeof entry.sha256 === \"string\" ? entry.sha256 : \"\";\n if (!id || !name) return null;\n const migrated: ExportTaskEntry = {\n id,\n name,\n title,\n status,\n impact,\n effortDays,\n path,\n sha256,\n };\n if (typeof entry.deletedAt === \"string\") migrated.deletedAt = entry.deletedAt;\n return migrated;\n}\n\nfunction inferTitleFromPath(path: string, fallback: string): string {\n const stem = basename(path).replace(/\\.md$/, \"\");\n return stem || fallback || \"(unknown title; manifest v1 fallback)\";\n}\n\nfunction inferTaskSummaryFromMarkdown(\n path: string,\n fallbackName: string,\n): Pick<ExportTaskEntry, \"title\" | \"status\" | \"impact\" | \"effortDays\"> {\n const fallback = {\n title: inferTitleFromPath(path, fallbackName),\n status: \"OPEN\" as TaskRow[\"status\"],\n impact: 0,\n effortDays: 1,\n };\n if (!existsSync(path)) return fallback;\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return fallback;\n }\n const lines = raw.split(\"\\n\");\n const firstFence = lines.findIndex((line) => line === \"---\");\n if (firstFence < 0) return fallback;\n let secondFence = -1;\n for (let i = firstFence + 1; i < lines.length; i += 1) {\n if (lines[i] === \"---\") {\n secondFence = i;\n break;\n }\n }\n if (secondFence < 0) return fallback;\n const fields: Record<string, string> = {};\n for (let i = firstFence + 1; i < secondFence; i += 1) {\n const line = lines[i] ?? \"\";\n const colon = line.indexOf(\":\");\n if (colon < 0) continue;\n fields[line.slice(0, colon).trim()] = line.slice(colon + 1).trim();\n }\n const status = fields.status && isTaskStatus(fields.status) ? fields.status : fallback.status;\n const impact = Number(fields.impact);\n const effortDays = Number(fields.effort_days);\n const titleLine = lines.slice(secondFence + 1).find((line) => line.startsWith(\"# \"));\n return {\n title: titleLine ? titleLine.slice(2).trim() : fallback.title,\n status,\n impact: Number.isFinite(impact) ? impact : fallback.impact,\n effortDays: Number.isFinite(effortDays) && effortDays > 0 ? effortDays : fallback.effortDays,\n };\n}\n\n// ─── sha256 + mu version ─────────────────────────────────────────────\n\nexport function sha256Hex(content: string): string {\n return createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\");\n}\n\n/** Read the package.json shipped next to the bundled CLI (or src/) so\n * the manifest records the mu version that produced it. Falls back\n * to \"unknown\" if the file isn't reachable. */\nexport function readMuVersion(): string {\n try {\n const here = dirname(fileURLToPath(import.meta.url));\n const raw = readFileSync(join(here, \"..\", \"package.json\"), \"utf8\");\n const parsed = JSON.parse(raw) as { version?: unknown };\n return typeof parsed.version === \"string\" ? parsed.version : \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\n// ─── Renderer ────────────────────────────────────────────────────────\n\n/**\n * Render `input.sources` to disk under `input.outDir` in the v0.3\n * bucket layout. Idempotent + additive:\n * - If the bucket doesn't exist, scaffold it.\n * - If it does exist with bucketVersion 2, MERGE: each source in\n * `input.sources` either appends (new) or refreshes (existing)\n * its subdirectory; sources NOT in `input.sources` are left\n * untouched.\n *\n * Per-task idempotency is sha256-keyed: a re-export of the same\n * source against an unchanged DB rewrites zero task files. Tasks\n * that disappear from a source between re-exports are preserved on\n * disk with a one-time `> **Deleted from DB on <ts>**` banner.\n */\nexport function renderToBucket(input: RenderBucketInput): RenderBucketResult {\n const outDir = input.outDir;\n if (existsSync(outDir)) {\n const stat = statSync(outDir);\n if (!stat.isDirectory()) {\n throw new Error(`renderToBucket: outDir exists and is not a directory: ${outDir}`);\n }\n } else {\n mkdirSync(outDir, { recursive: true });\n }\n\n const manifestPath = join(outDir, \"manifest.json\");\n const probe = readManifest(manifestPath);\n\n const now = new Date().toISOString();\n const muVersion = readMuVersion();\n const previous: ExportManifest | undefined = probe.kind === \"v2\" ? probe.manifest : undefined;\n // Start the new manifest from the previous one (so untouched\n // sources keep their entries) or a fresh scaffold.\n const manifest: ExportManifest = previous\n ? {\n bucketVersion: 2,\n manifest_version: EXPORT_MANIFEST_VERSION,\n bucketLabel: input.bucketLabel ?? previous.bucketLabel,\n bucketCreatedAt: previous.bucketCreatedAt,\n bucketLastUpdatedAt: now,\n muVersion,\n sources: { ...previous.sources },\n }\n : {\n bucketVersion: 2,\n manifest_version: EXPORT_MANIFEST_VERSION,\n bucketLabel: input.bucketLabel,\n bucketCreatedAt: now,\n bucketLastUpdatedAt: now,\n muVersion,\n sources: {},\n };\n\n let writtenTotal = 0;\n let unchangedTotal = 0;\n let preservedTotal = 0;\n\n for (const source of input.sources) {\n const sourceDir = join(outDir, source.name);\n const tasksDir = join(sourceDir, \"tasks\");\n mkdirSync(tasksDir, { recursive: true });\n\n const previousSource = previous?.sources[source.name];\n const previousById = new Map<string, ExportTaskEntry>();\n if (previousSource) {\n for (const t of previousSource.tasks) previousById.set(taskEntryName(t), t);\n }\n\n const liveIds = new Set(source.tasks.map((t) => t.name));\n const manifestEntries: ExportTaskEntry[] = [];\n let written = 0;\n let unchanged = 0;\n let preserved = 0;\n\n for (const task of source.tasks) {\n const edges = source.edges.get(task.name) ?? { blockers: [], dependents: [] };\n const notes = source.notes.get(task.name) ?? [];\n const md = renderTaskMarkdown(task, edges, notes);\n const sha = sha256Hex(md);\n const relPath = `${source.name}/tasks/${task.name}.md`;\n const absPath = join(outDir, relPath);\n\n const prev = previousById.get(task.name);\n const onDisk = existsSync(absPath);\n if (onDisk && prev?.sha256 === sha && prev.deletedAt === undefined) {\n unchanged += 1;\n } else {\n writeFileSync(absPath, md, \"utf8\");\n written += 1;\n }\n manifestEntries.push(taskEntryFromTask(task, relPath, sha));\n }\n\n // Preserve files for tasks that disappeared from the source.\n // Banner is one-time (idempotent across re-exports).\n for (const prev of previousById.values()) {\n if (liveIds.has(taskEntryName(prev))) continue;\n const absPath = join(outDir, prev.path);\n const deletedAt = prev.deletedAt ?? now;\n if (existsSync(absPath)) {\n const existing = readFileSync(absPath, \"utf8\");\n if (!existing.startsWith(DELETED_BANNER_PREFIX)) {\n writeFileSync(absPath, bannerFor(deletedAt) + existing, \"utf8\");\n }\n }\n manifestEntries.push({ ...prev, deletedAt });\n preserved += 1;\n }\n\n // Stable order — diffs across re-exports stay clean.\n manifestEntries.sort((a, b) => taskEntryName(a).localeCompare(taskEntryName(b)));\n\n // Per-source-ws scaffolding (cheap; always rewritten — but the\n // sha256 short-circuit on `tasks/<id>.md` is what matters for\n // mtime stability of the operator-visible files).\n writeFileSync(\n join(sourceDir, \"README.md\"),\n renderSourceReadmeMarkdown(source.name, source.tasks, now),\n \"utf8\",\n );\n writeFileSync(\n join(sourceDir, \"INDEX.md\"),\n renderSourceIndexMarkdown(source.name, source.tasks),\n \"utf8\",\n );\n\n manifest.sources[source.name] = {\n addedAt: previousSource?.addedAt ?? now,\n lastReExportedAt: now,\n eventsSeqAtExport: source.eventsSeqAtExport,\n tasks: manifestEntries,\n };\n\n writtenTotal += written;\n unchangedTotal += unchanged;\n preservedTotal += preserved;\n }\n\n // Bucket-level scaffolding covers EVERY source-ws in the merged\n // manifest, not just the ones refreshed by this call. Manifest v2\n // carries compact task summaries so INDEX.md can remain a true\n // cross-source union after additive one-workstream re-exports.\n const bucketReadme = renderBucketReadmeMarkdown(manifest);\n const bucketIndex = renderBucketIndexMarkdown(manifest);\n writeFileSync(join(outDir, \"README.md\"), bucketReadme, \"utf8\");\n writeFileSync(join(outDir, \"INDEX.md\"), bucketIndex, \"utf8\");\n\n writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`, \"utf8\");\n\n return {\n outDir,\n written: writtenTotal,\n unchanged: unchangedTotal,\n preserved: preservedTotal,\n manifestPath,\n manifest,\n };\n}\n\n// ─── Source builders ──────────────────────────────────────────────────\n\n/** Construct an ExportSource for one live workstream by reading the\n * current DB. Pure data assembly; renderer does the I/O. */\nexport function exportSourceForWorkstream(db: Db, workstream: string): ExportSource {\n const tasks = listTasks(db, workstream);\n const edges = new Map<string, { blockers: string[]; dependents: string[] }>();\n const notes = new Map<string, TaskNoteRow[]>();\n for (const t of tasks) {\n edges.set(t.name, getTaskEdges(db, t.name, t.workstreamName));\n notes.set(t.name, listNotes(db, t.name, t.workstreamName));\n }\n return {\n name: workstream,\n tasks,\n edges,\n notes,\n eventsSeqAtExport: latestSeq(db),\n };\n}\n\n/** Construct ExportSources for every source workstream that\n * contributed to an archive label. One ExportSource per\n * (archive_id, source_workstream) partition. The TaskRow shapes are\n * reconstructed from archived_* rows; `workstreamName` is set to\n * the source workstream so the rendered frontmatter reflects the\n * task's original home. */\nexport function exportSourcesForArchive(db: Db, label: string): ExportSource[] {\n // Pull every archived task in deterministic (source_workstream,\n // original_local_id) order.\n const allTasks = listArchivedTasks(db, label);\n if (allTasks.length === 0) return [];\n\n // archive_id (resolved internally) — we need it for the edge /\n // note / event queries below. Look it up via the first row's\n // archiveLabel (every row in `allTasks` shares the same archive).\n const archiveIdRow = db.prepare(\"SELECT id FROM archives WHERE label = ?\").get(label) as\n | { id: number }\n | undefined;\n if (!archiveIdRow) return []; // Should be unreachable: listArchivedTasks would have thrown.\n const archiveId = archiveIdRow.id;\n\n // Group tasks by source workstream.\n const bySource = new Map<string, typeof allTasks>();\n for (const t of allTasks) {\n const list = bySource.get(t.sourceWorkstream) ?? [];\n list.push(t);\n bySource.set(t.sourceWorkstream, list);\n }\n\n // Pre-load notes + edges per archived task. Two batched queries\n // is enough — the per-task loops below just dereference.\n const notesByArchivedId = new Map<number, TaskNoteRow[]>();\n const noteRows = db\n .prepare(\n `SELECT archived_task_id AS aid, author, content, created_at\n FROM archived_notes\n WHERE archive_id = ?\n ORDER BY id`,\n )\n .all(archiveId) as {\n aid: number;\n author: string | null;\n content: string;\n created_at: string;\n }[];\n for (const n of noteRows) {\n const list = notesByArchivedId.get(n.aid) ?? [];\n list.push({ author: n.author, content: n.content, createdAt: n.created_at });\n notesByArchivedId.set(n.aid, list);\n }\n\n // Edges: archived endpoint ids → original_local_id strings.\n // Build {from_archived_id → original_local_id} once, then map.\n const localIdByArchivedId = new Map<number, string>();\n for (const t of allTasks) localIdByArchivedId.set(t.id, t.originalLocalId);\n const blockersByArchivedId = new Map<number, string[]>();\n const dependentsByArchivedId = new Map<number, string[]>();\n const edgeRows = db\n .prepare(\n `SELECT from_archived_id AS f, to_archived_id AS t\n FROM archived_edges\n WHERE archive_id = ?`,\n )\n .all(archiveId) as { f: number; t: number }[];\n for (const e of edgeRows) {\n const fromId = localIdByArchivedId.get(e.f);\n const toId = localIdByArchivedId.get(e.t);\n if (fromId === undefined || toId === undefined) continue;\n // edge `from blocks to`: `from` is a blocker of `to`; `to` is\n // a dependent of `from`. (See getTaskEdges in src/tasks.ts.)\n const blockers = blockersByArchivedId.get(e.t) ?? [];\n blockers.push(fromId);\n blockersByArchivedId.set(e.t, blockers);\n const deps = dependentsByArchivedId.get(e.f) ?? [];\n deps.push(toId);\n dependentsByArchivedId.set(e.f, deps);\n }\n\n // archived_events: max(seq) per source workstream gives us the\n // best available \"events seq at archive time\" — the highest seq\n // of any event that contributed to this source's archive.\n const eventSeqRows = db\n .prepare(\n `SELECT source_workstream AS sw, MAX(seq) AS max_seq\n FROM archived_events\n WHERE archive_id = ?\n GROUP BY source_workstream`,\n )\n .all(archiveId) as { sw: string; max_seq: number }[];\n const eventsSeqBySource = new Map<string, number>();\n for (const r of eventSeqRows) eventsSeqBySource.set(r.sw, r.max_seq);\n\n const sources: ExportSource[] = [];\n for (const [sourceName, taskList] of bySource) {\n const tasks: TaskRow[] = taskList.map((t) => ({\n name: t.originalLocalId,\n workstreamName: t.sourceWorkstream,\n title: t.title,\n // Status as snapshotted; cast through the TaskStatus union by\n // way of any narrowed value (the renderer doesn't validate).\n status: t.status as TaskRow[\"status\"],\n impact: t.impact,\n effortDays: t.effortDays,\n ownerName: t.ownerName,\n createdAt: t.originalCreatedAt,\n updatedAt: t.originalUpdatedAt,\n }));\n const edges = new Map<string, { blockers: string[]; dependents: string[] }>();\n const notes = new Map<string, TaskNoteRow[]>();\n for (const t of taskList) {\n const blockers = (blockersByArchivedId.get(t.id) ?? []).sort((a, b) => a.localeCompare(b));\n const dependents = (dependentsByArchivedId.get(t.id) ?? []).sort((a, b) =>\n a.localeCompare(b),\n );\n edges.set(t.originalLocalId, { blockers, dependents });\n const ns = notesByArchivedId.get(t.id);\n if (ns) notes.set(t.originalLocalId, ns);\n }\n sources.push({\n name: sourceName,\n tasks,\n edges,\n notes,\n eventsSeqAtExport: eventsSeqBySource.get(sourceName) ?? 0,\n });\n }\n\n // Stable order across the bucket: source name ascending.\n sources.sort((a, b) => a.name.localeCompare(b.name));\n return sources;\n}\n\n// ─── Public verbs ────────────────────────────────────────────────────\n\nexport interface ExportArchiveOptions {\n label: string;\n /** Output directory (the bucket). Created if missing. */\n outDir: string;\n}\n\nexport interface ExportArchiveResult extends RenderBucketResult {\n archiveLabel: string;\n /** Number of source workstreams the renderer wrote / refreshed. */\n sourceCount: number;\n}\n\n/** Render every source-ws in an archive to a bucket directory.\n * Throws `ArchiveNotFoundError` (via listArchivedTasks) when the\n * label doesn't exist. */\nexport function exportArchive(db: Db, opts: ExportArchiveOptions): ExportArchiveResult {\n // Resolve up-front so a missing label fails before any disk I/O.\n // listArchivedTasks throws ArchiveNotFoundError on miss.\n listArchivedTasks(db, opts.label);\n const sources = exportSourcesForArchive(db, opts.label);\n const result = renderToBucket({\n sources,\n bucketLabel: opts.label,\n outDir: opts.outDir,\n });\n emitEvent(\n db,\n null,\n `archive export ${opts.label} (out=${result.outDir}, sources=${sources.length}, tasks=${sources.reduce((acc, s) => acc + s.tasks.length, 0)}, written=${result.written}, unchanged=${result.unchanged}, preserved=${result.preserved})`,\n );\n return { ...result, archiveLabel: opts.label, sourceCount: sources.length };\n}\n","// mu — archive shared types, label validation, errors, and row helpers.\n\nimport type { Db } from \"../db.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\n\n// ─── Label validation ────────────────────────────────────────────────\n\n/**\n * Allowed archive-label shape: lowercase alpha first, then alnum,\n * underscore, or hyphen, up to 64 chars total. Wider than the\n * workstream-name window (32 chars) because archive labels often\n * encode the workstream name PLUS a date / purpose (\"auth-2026-q1\",\n * \"rewrite-postmortem\").\n */\nconst ARCHIVE_LABEL_RE = /^[a-z][a-z0-9_-]{0,63}$/;\n\n/** True iff `label` matches the archive-label rule. Pure predicate. */\nexport function isValidArchiveLabel(label: string): boolean {\n return ARCHIVE_LABEL_RE.test(label);\n}\n\nexport function assertValidArchiveLabel(label: string): void {\n if (!isValidArchiveLabel(label)) throw new ArchiveLabelInvalidError(label);\n}\n\n// ─── Typed errors ────────────────────────────────────────────────────\n\nexport class ArchiveNotFoundError extends Error implements HasNextSteps {\n override readonly name = \"ArchiveNotFoundError\";\n constructor(public readonly label: string) {\n super(`no such archive: ${label}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List existing archives\", command: \"mu archive list\" },\n {\n intent: \"Create this archive\",\n command: `mu archive add ${this.label} -w <workstream>`,\n },\n ];\n }\n}\n\nexport class ArchiveAlreadyExistsError extends Error implements HasNextSteps {\n override readonly name = \"ArchiveAlreadyExistsError\";\n constructor(public readonly label: string) {\n super(`archive already exists: ${label}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Add a workstream to the existing archive (additive)\",\n command: `mu archive add ${this.label} -w <workstream>`,\n },\n {\n intent: \"Inspect the existing archive\",\n command: `mu archive show ${this.label}`,\n },\n ];\n }\n}\n\nexport class ArchiveLabelInvalidError extends Error implements HasNextSteps {\n override readonly name = \"ArchiveLabelInvalidError\";\n constructor(public readonly attempted: string) {\n super(\n `invalid archive label ${JSON.stringify(attempted)}: must match /^[a-z][a-z0-9_-]{0,63}$/. Use letters, digits, '_', and '-' only; start with a letter; up to 64 chars.`,\n );\n }\n errorNextSteps(): NextStep[] {\n const sanitized =\n this.attempted\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, \"_\")\n .replace(/^[^a-z]+/, \"\")\n .slice(0, 64) || \"archive\";\n return [\n {\n intent: \"Try a sanitized label (best guess)\",\n command: `mu archive add ${sanitized} -w <workstream>`,\n },\n { intent: \"List existing archives\", command: \"mu archive list\" },\n ];\n }\n}\n\n// ─── Domain types ─────────────────────────────────────────────────────\n\nexport interface Archive {\n /** Surrogate INTEGER id. Internal — operators identify by label. */\n id: number;\n /** Globally-unique operator-facing TEXT label. */\n label: string;\n /** Optional one-liner description set at create time. */\n description: string | null;\n /** ISO 8601, set when the archive was first created. */\n createdAt: string;\n /** ISO 8601, bumped on every successful add. */\n lastAddedAt: string;\n}\n\nexport interface ArchiveSourceSummary {\n /** TEXT name of the source workstream this snapshot came from. */\n name: string;\n /** Number of archived_tasks rows from this workstream in this archive. */\n taskCount: number;\n /** Earliest archived_at among this workstream's rows in this archive. */\n addedAt: string;\n}\n\nexport interface ArchiveSummary extends Archive {\n /** One row per source workstream that contributed to this archive,\n * sorted by source workstream name. */\n sourceWorkstreams: ArchiveSourceSummary[];\n /** Total archived_tasks rows across every source workstream. */\n totalTasks: number;\n}\n\nexport interface ArchivedTaskRow {\n /** Surrogate id of the archived_tasks row. */\n id: number;\n /** Operator-facing label of the parent archive. */\n archiveLabel: string;\n /** TEXT name of the source workstream (intentionally not an FK). */\n sourceWorkstream: string;\n /** The local_id the task had in its source workstream. */\n originalLocalId: string;\n title: string;\n /** Status as stored at archive time. */\n status: string;\n impact: number;\n effortDays: number;\n /** Owner agent name as snapshotted at archive time. */\n ownerName: string | null;\n /** Status at the moment of archive (pinned for re-add semantics). */\n archivedAtStatus: string;\n /** ISO 8601, when this row was added to the archive. */\n archivedAt: string;\n /** Original tasks.created_at preserved for retrospect ordering. */\n originalCreatedAt: string;\n /** Original tasks.updated_at preserved for retrospect ordering. */\n originalUpdatedAt: string;\n}\n\nexport interface AddToArchiveResult {\n /** Number of new archived_tasks rows actually inserted. Zero on a\n * re-run against the same workstream (idempotency). */\n addedTasks: number;\n /** Tasks present in the source workstream that were already in the\n * archive (skipped by the OR IGNORE). */\n skippedTasks: number;\n /** Number of new archived_edges rows actually inserted. */\n addedEdges: number;\n /** Number of new archived_notes rows inserted. */\n addedNotes: number;\n /** Number of new archived_events rows inserted. */\n addedEvents: number;\n}\n\nexport interface RemoveFromArchiveResult {\n /** Number of archived_tasks rows deleted (cascade cleans the rest). */\n removedTasks: number;\n /** Number of archived_edges rows removed by the cascade. */\n removedEdges: number;\n /** Number of archived_notes rows removed by the cascade. */\n removedNotes: number;\n /** Number of archived_events rows directly deleted. */\n removedEvents: number;\n}\n\n// ─── Internal row shapes ──────────────────────────────────────────────\n\nexport interface RawArchiveRow {\n id: number;\n label: string;\n description: string | null;\n created_at: string;\n last_added_at: string;\n}\n\nexport function rowFromArchive(r: RawArchiveRow): Archive {\n return {\n id: r.id,\n label: r.label,\n description: r.description,\n createdAt: r.created_at,\n lastAddedAt: r.last_added_at,\n };\n}\n\nexport interface RawArchivedTaskRow {\n id: number;\n archive_label: string;\n source_workstream: string;\n original_local_id: string;\n title: string;\n status: string;\n impact: number;\n effort_days: number;\n owner_name: string | null;\n archived_at_status: string;\n archived_at: string;\n original_created_at: string;\n original_updated_at: string;\n}\n\nexport function rowFromArchivedTask(r: RawArchivedTaskRow): ArchivedTaskRow {\n return {\n id: r.id,\n archiveLabel: r.archive_label,\n sourceWorkstream: r.source_workstream,\n originalLocalId: r.original_local_id,\n title: r.title,\n status: r.status,\n impact: r.impact,\n effortDays: r.effort_days,\n ownerName: r.owner_name,\n archivedAtStatus: r.archived_at_status,\n archivedAt: r.archived_at,\n originalCreatedAt: r.original_created_at,\n originalUpdatedAt: r.original_updated_at,\n };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\n/** Resolve an archive label to its surrogate id. Returns null on miss\n * so callers can pick between throwing (createArchive) and returning\n * empty (listArchivedTasks). archives.label is globally unique\n * (NOT per-workstream) by design — see schema doc in src/db.ts. */\nexport function tryResolveArchiveId(db: Db, label: string): number | null {\n const row = db.prepare(\"SELECT id FROM archives WHERE label = ?\").get(label) as\n | { id: number }\n | undefined;\n return row ? row.id : null;\n}\n\n/** Resolve an archive label to its surrogate id, throwing\n * ArchiveNotFoundError on miss. */\nexport function resolveArchiveId(db: Db, label: string): number {\n const id = tryResolveArchiveId(db, label);\n if (id === null) throw new ArchiveNotFoundError(label);\n return id;\n}\n\nexport function archiveByLabel(db: Db, label: string): Archive | null {\n const row = db\n .prepare(\n \"SELECT id, label, description, created_at, last_added_at FROM archives WHERE label = ?\",\n )\n .get(label) as RawArchiveRow | undefined;\n return row ? rowFromArchive(row) : null;\n}\n\nexport function summarizeArchive(db: Db, archive: Archive): ArchiveSummary {\n const sources = db\n .prepare(\n `SELECT source_workstream AS name,\n COUNT(*) AS task_count,\n MIN(archived_at) AS added_at\n FROM archived_tasks\n WHERE archive_id = ?\n GROUP BY source_workstream\n ORDER BY source_workstream`,\n )\n .all(archive.id) as { name: string; task_count: number; added_at: string }[];\n const sourceWorkstreams: ArchiveSourceSummary[] = sources.map((s) => ({\n name: s.name,\n taskCount: s.task_count,\n addedAt: s.added_at,\n }));\n const totalTasks = sourceWorkstreams.reduce((acc, s) => acc + s.taskCount, 0);\n return { ...archive, sourceWorkstreams, totalTasks };\n}\n","// mu — add/remove workstream snapshots to/from archives.\n\nimport { type Db, resolveWorkstreamId } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport type { AddToArchiveResult, RemoveFromArchiveResult } from \"./core.js\";\nimport { resolveArchiveId } from \"./core.js\";\n\n/**\n * Add every task in `workstream` to the archive identified by `label`.\n *\n * Idempotency invariant: re-running with the same (label, workstream)\n * pair is a no-op for tasks already present. The\n * (archive_id, source_workstream, original_local_id) UNIQUE on\n * archived_tasks is the lever; we INSERT OR IGNORE and skip notes /\n * events for the (archive, source_workstream) pair entirely when the\n * task copy added zero new rows.\n */\nexport function addToArchive(db: Db, label: string, workstream: string): AddToArchiveResult {\n const archiveId = resolveArchiveId(db, label);\n const wsId = resolveWorkstreamId(db, workstream);\n\n return db.transaction(() => {\n const now = new Date().toISOString();\n\n const sourceTasks = db\n .prepare(\n `SELECT t.id AS source_task_id,\n t.local_id AS original_local_id,\n t.title AS title,\n t.status AS status,\n t.impact AS impact,\n t.effort_days AS effort_days,\n ag.name AS owner_name,\n t.created_at AS original_created_at,\n t.updated_at AS original_updated_at\n FROM tasks t\n LEFT JOIN agents ag ON ag.id = t.owner_id\n WHERE t.workstream_id = ?\n ORDER BY t.id`,\n )\n .all(wsId) as {\n source_task_id: number;\n original_local_id: string;\n title: string;\n status: string;\n impact: number;\n effort_days: number;\n owner_name: string | null;\n original_created_at: string;\n original_updated_at: string;\n }[];\n\n const insertTask = db.prepare(\n `INSERT OR IGNORE INTO archived_tasks (\n archive_id, source_workstream, original_local_id, title, status,\n impact, effort_days, owner_name, archived_at_status, archived_at,\n original_created_at, original_updated_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n const lookupArchivedId = db.prepare(\n `SELECT id FROM archived_tasks\n WHERE archive_id = ? AND source_workstream = ? AND original_local_id = ?`,\n );\n\n let addedTasks = 0;\n let skippedTasks = 0;\n const newArchivedIds: number[] = [];\n const archivedIdBySourceId = new Map<number, number>();\n\n for (const t of sourceTasks) {\n const r = insertTask.run(\n archiveId,\n workstream,\n t.original_local_id,\n t.title,\n t.status,\n t.impact,\n t.effort_days,\n t.owner_name,\n t.status,\n now,\n t.original_created_at,\n t.original_updated_at,\n );\n const isNew = r.changes > 0;\n const lookup = lookupArchivedId.get(archiveId, workstream, t.original_local_id) as\n | { id: number }\n | undefined;\n if (!lookup) {\n throw new Error(\n `addToArchive: archived_tasks lookup missed after upsert: ${workstream}/${t.original_local_id}`,\n );\n }\n archivedIdBySourceId.set(t.source_task_id, lookup.id);\n if (isNew) {\n addedTasks += 1;\n newArchivedIds.push(lookup.id);\n } else {\n skippedTasks += 1;\n }\n }\n\n const sourceEdges = db\n .prepare(\n `SELECT e.from_task_id AS from_id, e.to_task_id AS to_id\n FROM task_edges e\n JOIN tasks tf ON tf.id = e.from_task_id\n JOIN tasks tt ON tt.id = e.to_task_id\n WHERE tf.workstream_id = ? AND tt.workstream_id = ?`,\n )\n .all(wsId, wsId) as { from_id: number; to_id: number }[];\n const insertEdge = db.prepare(\n `INSERT OR IGNORE INTO archived_edges (archive_id, from_archived_id, to_archived_id)\n VALUES (?, ?, ?)`,\n );\n let addedEdges = 0;\n for (const e of sourceEdges) {\n const fromArchivedId = archivedIdBySourceId.get(e.from_id);\n const toArchivedId = archivedIdBySourceId.get(e.to_id);\n if (fromArchivedId === undefined || toArchivedId === undefined) continue;\n const r = insertEdge.run(archiveId, fromArchivedId, toArchivedId);\n if (r.changes > 0) addedEdges += 1;\n }\n\n let addedNotes = 0;\n let addedEvents = 0;\n if (newArchivedIds.length > 0) {\n const insertNote = db.prepare(\n `INSERT INTO archived_notes (archive_id, archived_task_id, author, content, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n );\n const noteCopySql = db.prepare(\n `SELECT n.author AS author, n.content AS content, n.created_at AS created_at\n FROM task_notes n\n WHERE n.task_id = ?\n ORDER BY n.id`,\n );\n const sourceIdByArchivedId = new Map<number, number>();\n for (const [sId, aId] of archivedIdBySourceId) sourceIdByArchivedId.set(aId, sId);\n for (const archivedId of newArchivedIds) {\n const sourceId = sourceIdByArchivedId.get(archivedId);\n if (sourceId === undefined) continue;\n const notes = noteCopySql.all(sourceId) as {\n author: string | null;\n content: string;\n created_at: string;\n }[];\n for (const note of notes) {\n insertNote.run(archiveId, archivedId, note.author, note.content, note.created_at);\n addedNotes += 1;\n }\n }\n\n const events = db\n .prepare(\n `SELECT seq, source, payload, created_at\n FROM agent_logs\n WHERE workstream_id = ? AND kind = 'event'\n ORDER BY seq`,\n )\n .all(wsId) as {\n seq: number;\n source: string;\n payload: string;\n created_at: string;\n }[];\n const insertEvent = db.prepare(\n `INSERT INTO archived_events (\n archive_id, source_workstream, seq, source, payload, created_at\n ) VALUES (?, ?, ?, ?, ?, ?)`,\n );\n for (const ev of events) {\n insertEvent.run(archiveId, workstream, ev.seq, ev.source, ev.payload, ev.created_at);\n addedEvents += 1;\n }\n }\n\n db.prepare(\"UPDATE archives SET last_added_at = ? WHERE id = ?\").run(now, archiveId);\n const eventSummary =\n newArchivedIds.length === 0\n ? `tasks=${addedTasks}, edges=${addedEdges}, notes=${addedNotes}, events=0 (snapshot-only; re-add is task-incremental, not event-incremental), skipped_existing=${skippedTasks}`\n : `tasks=${addedTasks}, edges=${addedEdges}, notes=${addedNotes}, events=${addedEvents}, skipped_existing=${skippedTasks}`;\n emitEvent(db, null, `archive add ${label} -w ${workstream} (${eventSummary})`);\n\n return { addedTasks, skippedTasks, addedEdges, addedNotes, addedEvents };\n })();\n}\n\n/**\n * Remove every row contributed by `sourceWorkstream` from the named\n * archive. Other source workstreams' contributions are untouched\n * (additive accumulation invariant).\n */\nexport function removeFromArchive(\n db: Db,\n label: string,\n sourceWorkstream: string,\n): RemoveFromArchiveResult {\n const archiveId = resolveArchiveId(db, label);\n return db.transaction(() => {\n const countBefore = (sql: string, params: unknown[]) =>\n (db.prepare(sql).get(...params) as { n: number }).n;\n const removedTasks = countBefore(\n \"SELECT COUNT(*) AS n FROM archived_tasks WHERE archive_id = ? AND source_workstream = ?\",\n [archiveId, sourceWorkstream],\n );\n const removedNotes = countBefore(\n `SELECT COUNT(*) AS n\n FROM archived_notes an\n JOIN archived_tasks t ON t.id = an.archived_task_id\n WHERE t.archive_id = ? AND t.source_workstream = ?`,\n [archiveId, sourceWorkstream],\n );\n const removedEdges = countBefore(\n `SELECT COUNT(*) AS n\n FROM archived_edges e\n JOIN archived_tasks t ON t.id = e.from_archived_id\n WHERE e.archive_id = ? AND t.source_workstream = ?`,\n [archiveId, sourceWorkstream],\n );\n const removedEvents = countBefore(\n \"SELECT COUNT(*) AS n FROM archived_events WHERE archive_id = ? AND source_workstream = ?\",\n [archiveId, sourceWorkstream],\n );\n\n db.prepare(\"DELETE FROM archived_tasks WHERE archive_id = ? AND source_workstream = ?\").run(\n archiveId,\n sourceWorkstream,\n );\n db.prepare(\"DELETE FROM archived_events WHERE archive_id = ? AND source_workstream = ?\").run(\n archiveId,\n sourceWorkstream,\n );\n\n if (removedTasks > 0 || removedEvents > 0) {\n emitEvent(\n db,\n null,\n `archive remove ${label} -w ${sourceWorkstream} (tasks=${removedTasks}, edges=${removedEdges}, notes=${removedNotes}, events=${removedEvents})`,\n );\n }\n return { removedTasks, removedEdges, removedNotes, removedEvents };\n })();\n}\n","// mu — archive delete verb.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { resolveArchiveId } from \"./core.js\";\n\n/**\n * Delete an archive and every row that references it. The FK\n * CASCADE chain (archives → archived_tasks → archived_edges /\n * archived_notes; archives → archived_events) cleans every row in\n * one statement.\n *\n * Idempotent: throws `ArchiveNotFoundError` rather than silently\n * succeeding on a missing label (operator confusion safeguard).\n */\nexport function deleteArchive(db: Db, label: string): void {\n const id = resolveArchiveId(db, label);\n db.prepare(\"DELETE FROM archives WHERE id = ?\").run(id);\n emitEvent(db, null, `archive delete ${label}`);\n}\n","// mu — archive create/list/show/search/read helpers.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport {\n type Archive,\n ArchiveAlreadyExistsError,\n ArchiveNotFoundError,\n type ArchiveSummary,\n type ArchivedTaskRow,\n type RawArchiveRow,\n type RawArchivedTaskRow,\n archiveByLabel,\n assertValidArchiveLabel,\n resolveArchiveId,\n rowFromArchive,\n rowFromArchivedTask,\n summarizeArchive,\n tryResolveArchiveId,\n} from \"./core.js\";\n\n/**\n * Create a new archive bucket. Throws `ArchiveAlreadyExistsError` if\n * the label is already in use; throws `ArchiveLabelInvalidError` for\n * malformed labels.\n *\n * The archive starts EMPTY: created_at and last_added_at both equal\n * now(). Use `addToArchive(label, workstream)` to populate it.\n */\nexport function createArchive(db: Db, label: string, description?: string): Archive {\n assertValidArchiveLabel(label);\n if (tryResolveArchiveId(db, label) !== null) {\n throw new ArchiveAlreadyExistsError(label);\n }\n const now = new Date().toISOString();\n const result = db\n .prepare(\n `INSERT INTO archives (label, description, created_at, last_added_at)\n VALUES (?, ?, ?, ?)`,\n )\n .run(label, description ?? null, now, now);\n const id = Number(result.lastInsertRowid);\n emitEvent(db, null, `archive create ${label}`);\n return {\n id,\n label,\n description: description ?? null,\n createdAt: now,\n lastAddedAt: now,\n };\n}\n\n/**\n * List every archive on this machine, summarised with per-source-\n * workstream counts. Sorted by label ascending. Pure read; safe to\n * call against an empty DB (returns []).\n */\nexport function listArchives(db: Db): ArchiveSummary[] {\n const rows = db\n .prepare(\n \"SELECT id, label, description, created_at, last_added_at FROM archives ORDER BY label\",\n )\n .all() as RawArchiveRow[];\n return rows.map((r) => summarizeArchive(db, rowFromArchive(r)));\n}\n\n/**\n * Look up a single archive by label. Throws `ArchiveNotFoundError`\n * on miss.\n */\nexport function getArchive(db: Db, label: string): ArchiveSummary {\n const archive = archiveByLabel(db, label);\n if (archive === null) throw new ArchiveNotFoundError(label);\n return summarizeArchive(db, archive);\n}\n\nexport interface ListArchivedTasksOptions {\n /** Filter by source workstream. Omit to return every source's\n * contribution, sorted by (source_workstream, original_local_id). */\n sourceWorkstream?: string;\n}\n\nexport function listArchivedTasks(\n db: Db,\n label: string,\n opts: ListArchivedTasksOptions = {},\n): ArchivedTaskRow[] {\n const archiveId = resolveArchiveId(db, label);\n const conditions: string[] = [\"t.archive_id = ?\"];\n const params: unknown[] = [archiveId];\n if (opts.sourceWorkstream !== undefined) {\n conditions.push(\"t.source_workstream = ?\");\n params.push(opts.sourceWorkstream);\n }\n const where = conditions.join(\" AND \");\n const rows = db\n .prepare(\n `SELECT t.id AS id,\n a.label AS archive_label,\n t.source_workstream AS source_workstream,\n t.original_local_id AS original_local_id,\n t.title AS title,\n t.status AS status,\n t.impact AS impact,\n t.effort_days AS effort_days,\n t.owner_name AS owner_name,\n t.archived_at_status AS archived_at_status,\n t.archived_at AS archived_at,\n t.original_created_at AS original_created_at,\n t.original_updated_at AS original_updated_at\n FROM archived_tasks t\n JOIN archives a ON a.id = t.archive_id\n WHERE ${where}\n ORDER BY t.source_workstream ASC, t.original_local_id ASC`,\n )\n .all(...params) as RawArchivedTaskRow[];\n return rows.map(rowFromArchivedTask);\n}\n\nexport interface ArchiveSearchHit {\n /** Operator-facing label of the parent archive. */\n archiveLabel: string;\n /** TEXT name of the source workstream this row came from. */\n sourceWorkstream: string;\n /** local_id the task had in its source workstream. */\n originalLocalId: string;\n /** Snapshotted title (always present, even on a note match). */\n title: string;\n /** Where the match was found: the title column, or one of this\n * task's archived_notes.content rows. Title matches win when\n * both apply (the dedup pass below picks one row per task). */\n matchKind: \"title\" | \"note\";\n /** Up to ~120 chars of context centered on the FIRST occurrence\n * of the pattern in the matching field. Case-insensitive index;\n * the snippet itself preserves original casing. */\n matchSnippet: string;\n}\n\nexport interface SearchArchivesOptions {\n /** LIKE-style needle. Wrapped in `%…%` automatically. */\n pattern: string;\n /** Restrict to one archive label; undefined = search every\n * archive. Throws ArchiveNotFoundError on miss. */\n label?: string;\n /** Cap on hits returned. Default 50; values below 1 fall back to\n * the default. */\n limit?: number;\n}\n\nconst SEARCH_DEFAULT_LIMIT = 50;\nconst SNIPPET_WIDTH = 120;\n\nfunction snippetAround(haystack: string, needle: string): string {\n const literal = needle.replace(/[%_]/g, \"\");\n if (literal.length === 0) {\n return haystack.length <= SNIPPET_WIDTH ? haystack : `${haystack.slice(0, SNIPPET_WIDTH - 1)}…`;\n }\n const idx = haystack.toLowerCase().indexOf(literal.toLowerCase());\n if (idx < 0) {\n return haystack.length <= SNIPPET_WIDTH ? haystack : `${haystack.slice(0, SNIPPET_WIDTH - 1)}…`;\n }\n const half = Math.floor((SNIPPET_WIDTH - literal.length) / 2);\n const start = Math.max(0, idx - half);\n const end = Math.min(haystack.length, start + SNIPPET_WIDTH);\n const head = start > 0 ? \"…\" : \"\";\n const tail = end < haystack.length ? \"…\" : \"\";\n return `${head}${haystack.slice(start, end)}${tail}`;\n}\n\n/** LIKE-search archived task titles AND archived note content. */\nexport function searchArchives(db: Db, opts: SearchArchivesOptions): ArchiveSearchHit[] {\n const trimmed = opts.pattern.trim();\n if (trimmed.length === 0) {\n throw new Error(\"searchArchives: pattern must be non-empty\");\n }\n const like = `%${trimmed}%`;\n const limit =\n opts.limit !== undefined && opts.limit > 0 ? Math.floor(opts.limit) : SEARCH_DEFAULT_LIMIT;\n\n let archiveFilterSql = \"\";\n const archiveFilterParams: unknown[] = [];\n if (opts.label !== undefined) {\n const archive = getArchive(db, opts.label);\n archiveFilterSql = \" AND a.id = ?\";\n archiveFilterParams.push(archive.id);\n }\n\n const titleRows = db\n .prepare(\n `SELECT a.label AS archive_label,\n t.source_workstream AS source_workstream,\n t.original_local_id AS original_local_id,\n t.title AS title\n FROM archived_tasks t\n JOIN archives a ON a.id = t.archive_id\n WHERE LOWER(t.title) LIKE LOWER(?)${archiveFilterSql}`,\n )\n .all(like, ...archiveFilterParams) as {\n archive_label: string;\n source_workstream: string;\n original_local_id: string;\n title: string;\n }[];\n\n const noteRows = db\n .prepare(\n `SELECT a.label AS archive_label,\n t.source_workstream AS source_workstream,\n t.original_local_id AS original_local_id,\n t.title AS title,\n n.content AS content\n FROM archived_notes n\n JOIN archived_tasks t ON t.id = n.archived_task_id\n JOIN archives a ON a.id = t.archive_id\n WHERE LOWER(n.content) LIKE LOWER(?)${archiveFilterSql}\n ORDER BY n.id`,\n )\n .all(like, ...archiveFilterParams) as {\n archive_label: string;\n source_workstream: string;\n original_local_id: string;\n title: string;\n content: string;\n }[];\n\n const seen = new Set<string>();\n const hits: ArchiveSearchHit[] = [];\n for (const r of titleRows) {\n const key = `${r.archive_label}\\u0000${r.source_workstream}\\u0000${r.original_local_id}`;\n seen.add(key);\n hits.push({\n archiveLabel: r.archive_label,\n sourceWorkstream: r.source_workstream,\n originalLocalId: r.original_local_id,\n title: r.title,\n matchKind: \"title\",\n matchSnippet: snippetAround(r.title, trimmed),\n });\n }\n for (const r of noteRows) {\n const key = `${r.archive_label}\\u0000${r.source_workstream}\\u0000${r.original_local_id}`;\n if (seen.has(key)) continue;\n seen.add(key);\n hits.push({\n archiveLabel: r.archive_label,\n sourceWorkstream: r.source_workstream,\n originalLocalId: r.original_local_id,\n title: r.title,\n matchKind: \"note\",\n matchSnippet: snippetAround(r.content, trimmed),\n });\n }\n\n hits.sort((a, b) => {\n if (a.archiveLabel !== b.archiveLabel) return a.archiveLabel < b.archiveLabel ? -1 : 1;\n if (a.sourceWorkstream !== b.sourceWorkstream)\n return a.sourceWorkstream < b.sourceWorkstream ? -1 : 1;\n if (a.originalLocalId !== b.originalLocalId)\n return a.originalLocalId < b.originalLocalId ? -1 : 1;\n return 0;\n });\n return hits.slice(0, limit);\n}\n","// mu — restore archived task graphs back into a fresh workstream.\n\nimport { type Db, resolveWorkstreamId, tryResolveWorkstreamId } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport {\n WorkstreamExistsError,\n WorkstreamNameInvalidError,\n ensureWorkstream,\n isValidWorkstreamName,\n} from \"../workstream.js\";\nimport { resolveArchiveId } from \"./core.js\";\n\nexport class ArchiveSourceAmbiguousError extends Error implements HasNextSteps {\n override readonly name = \"ArchiveSourceAmbiguousError\";\n constructor(\n public readonly label: string,\n public readonly sources: readonly string[],\n ) {\n super(\n sources.length === 0\n ? `archive ${label} contains no source workstreams`\n : `archive ${label} requires --source <orig-ws-name>. Available: ${sources.join(\", \")}`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Inspect archive sources\", command: `mu archive show ${this.label}` },\n ...this.sources.map((source) => ({\n intent: `Restore source workstream ${source}`,\n command: `mu archive restore ${this.label} --source ${source} --as <new-workstream>`,\n })),\n ];\n }\n}\n\nexport interface RestoreArchiveOptions {\n sourceWorkstream?: string;\n}\n\nexport interface RestoreArchiveResult {\n archiveLabel: string;\n sourceWorkstream: string;\n workstreamName: string;\n restoredTasks: number;\n restoredEdges: number;\n restoredNotes: number;\n}\n\nexport function restoreArchive(\n db: Db,\n label: string,\n asWorkstream: string,\n opts: RestoreArchiveOptions = {},\n): RestoreArchiveResult {\n const archiveId = resolveArchiveId(db, label);\n const sources = listSources(db, archiveId);\n if (!isValidWorkstreamName(asWorkstream)) throw new WorkstreamNameInvalidError(asWorkstream);\n\n const sourceWorkstream = opts.sourceWorkstream ?? sources[0];\n if (sourceWorkstream === undefined) throw new ArchiveSourceAmbiguousError(label, sources);\n if (opts.sourceWorkstream === undefined && sources.length > 1) {\n throw new ArchiveSourceAmbiguousError(label, sources);\n }\n if (opts.sourceWorkstream !== undefined && !sources.includes(opts.sourceWorkstream)) {\n throw new ArchiveSourceAmbiguousError(label, sources);\n }\n if (tryResolveWorkstreamId(db, asWorkstream) !== null) {\n throw new WorkstreamExistsError(asWorkstream);\n }\n\n captureSnapshot(db, `archive restore ${label} as ${asWorkstream}`, null);\n\n return db.transaction(() => {\n ensureWorkstream(db, asWorkstream);\n const wsId = resolveWorkstreamId(db, asWorkstream);\n\n const restoredTasks = db\n .prepare(\n `INSERT INTO tasks\n (workstream_id, local_id, title, status, impact, effort_days, owner_id, created_at, updated_at)\n SELECT ?, original_local_id, title, status, impact, effort_days, NULL,\n original_created_at, original_updated_at\n FROM archived_tasks\n WHERE archive_id = ? AND source_workstream = ?\n ORDER BY id`,\n )\n .run(wsId, archiveId, sourceWorkstream).changes;\n\n const now = new Date().toISOString();\n const restoredEdges = db\n .prepare(\n `INSERT OR IGNORE INTO task_edges (from_task_id, to_task_id, created_at)\n SELECT live_from.id, live_to.id, ?\n FROM archived_edges e\n JOIN archived_tasks arch_from ON arch_from.id = e.from_archived_id\n JOIN archived_tasks arch_to ON arch_to.id = e.to_archived_id\n JOIN tasks live_from ON live_from.workstream_id = ?\n AND live_from.local_id = arch_from.original_local_id\n JOIN tasks live_to ON live_to.workstream_id = ?\n AND live_to.local_id = arch_to.original_local_id\n WHERE e.archive_id = ?\n AND arch_from.source_workstream = ?\n AND arch_to.source_workstream = ?`,\n )\n .run(now, wsId, wsId, archiveId, sourceWorkstream, sourceWorkstream).changes;\n\n const restoredNotes = db\n .prepare(\n `INSERT INTO task_notes (task_id, author, content, created_at)\n SELECT live.id, n.author, n.content, n.created_at\n FROM archived_notes n\n JOIN archived_tasks arch ON arch.id = n.archived_task_id\n JOIN tasks live ON live.workstream_id = ?\n AND live.local_id = arch.original_local_id\n WHERE n.archive_id = ? AND arch.source_workstream = ?\n ORDER BY n.id`,\n )\n .run(wsId, archiveId, sourceWorkstream).changes;\n\n emitEvent(\n db,\n asWorkstream,\n `archive restore ${label} source=${sourceWorkstream} as ${asWorkstream} (tasks=${restoredTasks}, edges=${restoredEdges}, notes=${restoredNotes})`,\n );\n return {\n archiveLabel: label,\n sourceWorkstream,\n workstreamName: asWorkstream,\n restoredTasks,\n restoredEdges,\n restoredNotes,\n };\n })();\n}\n\nfunction listSources(db: Db, archiveId: number): string[] {\n return (\n db\n .prepare(\n `SELECT source_workstream AS name\n FROM archived_tasks\n WHERE archive_id = ?\n GROUP BY source_workstream\n ORDER BY source_workstream`,\n )\n .all(archiveId) as { name: string }[]\n ).map((row) => row.name);\n}\n","// mu — TaskStatus enum + helpers.\n//\n// Single source of truth for \"what statuses can a task have\". The\n// schema (db.ts) has a CHECK clause that mirrors TASK_STATUSES; if you\n// add a status, update both places.\n//\n// Extracted from src/tasks.ts as part of refactor_split_large_src_files.\n\nexport type TaskStatus = \"OPEN\" | \"IN_PROGRESS\" | \"CLOSED\" | \"REJECTED\" | \"DEFERRED\";\n\n/** Every legal task status, in canonical order (matches the schema\n * CHECK clause). Exported so CLI surfaces (`--status` validators,\n * --help text, error messages) name them all in one place; missing\n * one used to silently lie about the supported set. */\nexport const TASK_STATUSES: readonly TaskStatus[] = [\n \"OPEN\",\n \"IN_PROGRESS\",\n \"CLOSED\",\n \"REJECTED\",\n \"DEFERRED\",\n];\n\n/** Statuses that count as 'no longer scheduled work' — used by the\n * goals view and by the dependent-check on reject/defer.\n *\n * (The complement — 'statuses that satisfy a blocked-by edge' — is\n * just `[\"CLOSED\"]` and is hardcoded inline in the SQL views in\n * src/db.ts. A constant for it was tried and reverted: a one-element\n * array doesn't earn its keep, and parameterising the SQL views from\n * a TS const would be brittle.) */\nexport const STATUSES_TERMINAL_OR_PARKED: readonly TaskStatus[] = [\n \"CLOSED\",\n \"REJECTED\",\n \"DEFERRED\",\n];\n\nexport function isTaskStatus(s: string): s is TaskStatus {\n return (TASK_STATUSES as readonly string[]).includes(s);\n}\n\n/** Pipe-separated list of every legal status, e.g.\n * 'OPEN | IN_PROGRESS | CLOSED | REJECTED | DEFERRED'. Single source\n * of truth for --help text and error messages so adding a new status\n * doesn't leave stale lists rotting in the CLI surface. */\nexport const TASK_STATUS_LIST = TASK_STATUSES.join(\" | \");\n","// \"Presumed parked on another machine\" detection.\n//\n// When a user runs `mu db export` to ship a workstream off to another\n// machine, then leaves the local copy alone for a while, the local\n// rows still consume a slot in `mu workstream list` and the TUI tab\n// strip. The user gets tempted to `mu workstream destroy` it, which\n// works but loses the `workstream_sync` row and degrades drift\n// detection on the next round-trip (the workstream re-imports as\n// IMPORT-on-clean rather than FAST_FORWARD).\n//\n// This module exposes a small read-only heuristic: a workstream is\n// \"parked\" iff it has been quiet since its most recent `db export`\n// event, has zero alive agents, and has zero IN_PROGRESS tasks. The\n// signal is consumed by `mu workstream list` (a `parked` column) and\n// the TUI tab strip (dim+prefix). No schema change; no new state.\n//\n// The detection key is the `db export` agent_logs row emitted by\n// `exportDb` in src/db-sync.ts: if the LATEST event in the workstream\n// is a `db export`, nothing local has happened since the export ran.\n// Any subsequent `task add` / `task note` / `agent spawn` / etc.\n// supersedes the marker and the workstream stops being parked.\n//\n// Threshold: at least one full day (24h) since the export event, so\n// \"I exported five minutes ago to test\" doesn't immediately trip the\n// banner. Configurable via WORKSTREAM_PARKED_THRESHOLD_DAYS.\n\nimport type { Db } from \"./db.js\";\n\n/** Days that must have elapsed since the most recent `db export`\n * event before a workstream is considered parked. Default 1: prevents\n * a same-session \"I exported to verify\" from instantly flipping the\n * TUI tab to dim. Tuning higher would just delay the banner. */\nexport const WORKSTREAM_PARKED_THRESHOLD_DAYS = 1;\n\nexport interface ParkedStatus {\n parked: boolean;\n /** Whole days since the most recent `db export` event. Present iff\n * `parked === true`. */\n sinceDays?: number;\n}\n\n/**\n * Compute the parked status for one workstream. Pure read; no writes.\n *\n * Returns `{ parked: false }` when:\n * - the workstream has no `db export` event in agent_logs, OR\n * - any agent_logs row newer than the most recent `db export` exists\n * (i.e. local activity since export), OR\n * - the workstream has any alive agents (status != 'closed'), OR\n * - the workstream has any IN_PROGRESS tasks, OR\n * - the most recent `db export` is younger than the threshold.\n *\n * Otherwise returns `{ parked: true, sinceDays: <whole days> }`.\n *\n * `now` defaults to wall-clock; tests pass it explicitly to keep the\n * threshold edge deterministic.\n */\nexport function parkedStatus(\n db: Db,\n workstream: string,\n opts: { now?: Date; thresholdDays?: number } = {},\n): ParkedStatus {\n const wsRow = db.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as\n | { id: number }\n | undefined;\n if (wsRow === undefined) return { parked: false };\n\n // Most recent agent_logs row for this workstream.\n const latest = db\n .prepare(\n \"SELECT kind, payload, created_at FROM agent_logs WHERE workstream_id = ? ORDER BY seq DESC LIMIT 1\",\n )\n .get(wsRow.id) as { kind: string; payload: string; created_at: string } | undefined;\n if (latest === undefined) return { parked: false };\n\n // The marker we look for: the most recent row IS a `db export`\n // event. Any other recent row supersedes it.\n if (latest.kind !== \"event\") return { parked: false };\n if (!latest.payload.startsWith(\"db export \")) return { parked: false };\n\n // Alive agents disqualify (someone is presumably working).\n const aliveAgent = db\n .prepare(\"SELECT 1 AS x FROM agents WHERE workstream_id = ? AND status != 'closed' LIMIT 1\")\n .get(wsRow.id) as { x: number } | undefined;\n if (aliveAgent !== undefined) return { parked: false };\n\n // IN_PROGRESS tasks disqualify (work is mid-flight even if no agent\n // is currently attached; the parked banner would lie).\n const inProgress = db\n .prepare(\"SELECT 1 AS x FROM tasks WHERE workstream_id = ? AND status = 'IN_PROGRESS' LIMIT 1\")\n .get(wsRow.id) as { x: number } | undefined;\n if (inProgress !== undefined) return { parked: false };\n\n const threshold = Math.max(0, opts.thresholdDays ?? WORKSTREAM_PARKED_THRESHOLD_DAYS);\n const exportedAt = Date.parse(latest.created_at);\n if (Number.isNaN(exportedAt)) return { parked: false };\n const now = (opts.now ?? new Date()).getTime();\n const deltaMs = now - exportedAt;\n const deltaDays = Math.floor(deltaMs / (24 * 60 * 60 * 1000));\n if (deltaDays < threshold) return { parked: false };\n return { parked: true, sinceDays: deltaDays };\n}\n","// mu — task edit/write verbs: add task, add note, update, delete.\n\nimport { type Db, resolveWorkstreamId } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport { ensureWorkstream } from \"../workstream.js\";\nimport { taskIdFor, touchTask } from \"./core.js\";\nimport { dedupeBlockersById, wouldCreateCycle } from \"./edges.js\";\nimport {\n CrossWorkstreamEdgeError,\n CycleError,\n TaskExistsError,\n TaskIdInvalidError,\n TaskNotFoundError,\n} from \"./errors.js\";\nimport { isValidTaskId } from \"./id.js\";\nimport { getTask } from \"./queries.js\";\n\nexport interface AddTaskOptions {\n localId: string;\n workstream: string;\n title: string;\n /** 1..100; enforced by schema CHECK. */\n impact: number;\n /** > 0; enforced by schema CHECK. */\n effortDays: number;\n /**\n * Tasks that block this one. Edges inserted as `blocker -> newTask`.\n * Each blocker must already exist AND share this task's workstream\n * (cross-workstream edges are forbidden); cycle check guards each\n * edge. The CLI surfaces this as `--blocked-by`; the SDK key matches.\n */\n blockedBy?: string[];\n}\n\n/**\n * Atomically create a task and (optionally) its incoming blocked-by\n * edges.\n *\n * The task insert + every edge insert + cycle check happen inside one\n * SQLite transaction. If any blocker is missing or any edge would\n * create a cycle, the entire add rolls back.\n *\n * Cycle check for `addTask` is structurally trivial (a fresh task has\n * no outgoing edges, so `to -> ... -> from` is impossible). It's still\n * called here so the same primitive is exercised by tests.\n */\nexport function addTask(db: Db, opts: AddTaskOptions) {\n if (!isValidTaskId(opts.localId)) {\n throw new TaskIdInvalidError(opts.localId);\n }\n\n return db.transaction(() => {\n // Auto-create the workstream row so tasks.workstream_id FK is\n // satisfied (preserves spawn-without-init ergonomics).\n ensureWorkstream(db, opts.workstream);\n const wsId = resolveWorkstreamId(db, opts.workstream);\n\n // Per-workstream uniqueness: a duplicate local_id within the same\n // workstream throws TaskExistsError. Different workstreams may\n // legitimately share local_ids in v5.\n const existing = db\n .prepare(\"SELECT id FROM tasks WHERE workstream_id = ? AND local_id = ?\")\n .get(wsId, opts.localId) as { id: number } | undefined;\n if (existing) {\n throw new TaskExistsError(opts.localId);\n }\n\n const now = new Date().toISOString();\n const insertResult = db\n .prepare(\n `INSERT INTO tasks (workstream_id, local_id, title, status, impact, effort_days, created_at, updated_at)\n VALUES (?, ?, ?, 'OPEN', ?, ?, ?, ?)`,\n )\n .run(wsId, opts.localId, opts.title, opts.impact, opts.effortDays, now, now);\n const newTaskId = Number(insertResult.lastInsertRowid);\n\n let canonicalBlockedBy: string[] = [];\n\n if (opts.blockedBy && opts.blockedBy.length > 0) {\n // Prefer the same-workstream blocker first (v5 per-workstream\n // local_id), then fall back to a global lookup so a cross-ws\n // blocker still surfaces CrossWorkstreamEdgeError (not\n // TaskNotFoundError). Without the same-ws preference, two\n // blockers of the same local_id (one in this ws, one elsewhere)\n // could silently bind to the wrong row\n // (bug_v5_name_clash_silent_misroute).\n const blockerLookupSameWs = db.prepare(\n `SELECT t.id AS id, ws.name AS workstream FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE t.local_id = ? AND t.workstream_id = ?`,\n );\n const blockerLookupAnyWs = db.prepare(\n `SELECT t.id AS id, ws.name AS workstream FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE t.local_id = ? LIMIT 1`,\n );\n const requestedBlockers = opts.blockedBy.map((blocker) => {\n const row = (blockerLookupSameWs.get(blocker, wsId) ?? blockerLookupAnyWs.get(blocker)) as\n | { id: number; workstream: string }\n | undefined;\n if (!row) {\n throw new TaskNotFoundError(blocker);\n }\n if (row.workstream !== opts.workstream) {\n throw new CrossWorkstreamEdgeError(\n blocker,\n row.workstream,\n opts.localId,\n opts.workstream,\n );\n }\n if (wouldCreateCycle(db, row.id, newTaskId)) {\n throw new CycleError(blocker, opts.localId);\n }\n return { localId: blocker, id: row.id };\n });\n const canonicalBlockers = dedupeBlockersById(requestedBlockers);\n canonicalBlockedBy = canonicalBlockers.map((blocker) => blocker.localId);\n const insertEdge = db.prepare(\n \"INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)\",\n );\n for (const blocker of canonicalBlockers) {\n insertEdge.run(blocker.id, newTaskId, now);\n }\n }\n\n const row = getTask(db, opts.localId, opts.workstream);\n if (!row) throw new Error(`addTask: row missing after insert: ${opts.localId}`);\n const blockedBy =\n canonicalBlockedBy.length > 0 ? `, blocked-by=${canonicalBlockedBy.join(\",\")}` : \"\";\n emitEvent(\n db,\n opts.workstream,\n `task add ${opts.localId} (impact=${opts.impact}, effort=${opts.effortDays}${blockedBy})`,\n );\n return row;\n })();\n}\n\nexport interface AddNoteOptions {\n /** Free-form author label. Convention: agent name, \"user\", or \"orchestrator\". */\n author?: string;\n /** Workstream context (operator-facing name). v5: tasks.local_id is\n * per-workstream unique, so this is required to disambiguate. */\n workstream: string;\n}\n\nexport function addNote(db: Db, taskLocalId: string, content: string, opts: AddNoteOptions) {\n const task = getTask(db, taskLocalId, opts.workstream);\n if (!task) {\n throw new TaskNotFoundError(taskLocalId);\n }\n const taskId = taskIdFor(db, task.name, task.workstreamName);\n if (taskId === null) throw new TaskNotFoundError(taskLocalId);\n const now = new Date().toISOString();\n const result = db.transaction(() => {\n const r = db\n .prepare(\"INSERT INTO task_notes (task_id, author, content, created_at) VALUES (?, ?, ?, ?)\")\n .run(taskId, opts.author ?? null, content, now);\n // Bump the parent task so `mu task list --sort recency` surfaces\n // freshly-noted tasks (task_updatedat_not_bumped_by_reparent).\n touchTask(db, taskId, now);\n return r;\n })();\n const noteId = Number(result.lastInsertRowid);\n emitEvent(\n db,\n task.workstreamName,\n `task note ${taskLocalId} (note #${noteId} by ${opts.author ?? \"orchestrator\"})`,\n opts.author ?? \"system\",\n );\n return {\n author: opts.author ?? null,\n content,\n createdAt: now,\n };\n}\n\nexport interface DeleteTaskResult {\n /** True iff the row existed and was deleted. False on a dry-run\n * (preview) AND on the idempotent missing-row case. */\n deleted: boolean;\n /** Number of `task_edges` rows cascaded out (informational). On a\n * dry-run, this is the would-be count. */\n deletedEdges: number;\n /** Number of `task_notes` rows cascaded out (informational). On a\n * dry-run, this is the would-be count. */\n deletedNotes: number;\n /** True iff this was a dry-run (`opts.dryRun: true`). On a\n * dry-run `deleted` is false and the counts are the would-be\n * counts; the DB is unchanged. Always false on a commit / on a\n * missing-row idempotent no-op. */\n dryRun: boolean;\n /** True iff a matching task row was found at the time of the\n * call. Discriminator for the CLI: a dry-run that found nothing\n * (`present: false`) renders differently from a dry-run that\n * found an existing task with zero edges and zero notes\n * (`present: true, deletedEdges: 0, deletedNotes: 0`). */\n present: boolean;\n}\n\nexport interface DeleteTaskOptions {\n /** When true, return the cascade preview (would-be edge / note\n * counts) without mutating and without snapshotting. The CLI uses\n * this to power the bare `mu task delete <id>` two-phase pattern\n * (mirrors `mu workstream destroy` / `mu archive delete` /\n * `mu snapshot prune`). Surfaced by feedback ws task\n * fb_task_delete_no_yes (impact=30): a dogfood report typed\n * `mu task delete X --yes` (mirroring workstream destroy) and got\n * 'unknown option --yes' — the verb took no confirmation flag at\n * all. Two failed deletes left long-named tasks lingering. */\n dryRun?: boolean;\n}\n\n/**\n * Delete a task. FK CASCADE on `task_edges` (from + to) and\n * `task_notes` cleans the joined rows automatically. Idempotent on\n * a missing task (returns `deleted: false`).\n *\n * Pre-counts the cascade victims for reporting because SQLite's\n * `changes()` only reports rows directly affected by the DELETE.\n *\n * With `opts.dryRun: true`, returns the would-be counts without\n * touching the DB and without taking a snapshot (no mutation = no\n * snapshot — same reasoning that gates the closeTask snap on the\n * idempotent no-op path). The CLI bare `mu task delete <id>` form\n * uses this; `--yes` calls through with `dryRun: false`.\n */\nexport function deleteTask(\n db: Db,\n localId: string,\n workstream: string,\n opts: DeleteTaskOptions = {},\n): DeleteTaskResult {\n const dryRun = opts.dryRun === true;\n const before = getTask(db, localId, workstream);\n if (!before) {\n // Idempotent on a missing row regardless of dryRun.\n return { deleted: false, deletedEdges: 0, deletedNotes: 0, dryRun, present: false };\n }\n const taskId = taskIdFor(db, localId, before.workstreamName);\n if (taskId === null) {\n return { deleted: false, deletedEdges: 0, deletedNotes: 0, dryRun, present: false };\n }\n const edgesBefore = (\n db\n .prepare(\"SELECT COUNT(*) AS n FROM task_edges WHERE from_task_id = ? OR to_task_id = ?\")\n .get(taskId, taskId) as { n: number }\n ).n;\n const notesBefore = (\n db.prepare(\"SELECT COUNT(*) AS n FROM task_notes WHERE task_id = ?\").get(taskId) as {\n n: number;\n }\n ).n;\n if (dryRun) {\n return {\n deleted: false,\n deletedEdges: edgesBefore,\n deletedNotes: notesBefore,\n dryRun: true,\n present: true,\n };\n }\n // Pre-mutation snapshot. delete cascades into task_edges and\n // task_notes; no per-row history can reconstruct it. Taken AFTER\n // the dry-run early-return so a preview never touches snapshots.\n captureSnapshot(db, `task delete ${localId}`, before.workstreamName);\n const result = db.prepare(\"DELETE FROM tasks WHERE id = ?\").run(taskId);\n const deleted = result.changes > 0;\n if (deleted) {\n emitEvent(\n db,\n before.workstreamName,\n `task delete ${localId} (cascade: ${edgesBefore} edges, ${notesBefore} notes)`,\n );\n }\n return {\n deleted,\n deletedEdges: edgesBefore,\n deletedNotes: notesBefore,\n dryRun: false,\n present: true,\n };\n}\n\nexport interface UpdateTaskOptions {\n title?: string;\n /** 1..100; enforced by schema CHECK. */\n impact?: number;\n /** > 0; enforced by schema CHECK. */\n effortDays?: number;\n}\n\nexport interface UpdateTaskResult {\n /** True iff at least one field actually changed. */\n updated: boolean;\n /** The fields whose values differ post-update (in `UpdateTaskOptions`'s\n * camelCase shape). Empty when `updated: false`. */\n changedFields: string[];\n}\n\n/**\n * Update scalar fields on a task. Each option is independently optional;\n * passing none is a typed no-op (returns `updated: false, changedFields: []`).\n * Fields whose new value equals the current value are skipped (no row change).\n *\n * NOT for status (use `closeTask` / `openTask` / `setTaskStatus`), owner\n * (use `claimTask` / `releaseTask`), local_id (rename is deferred), or\n * workstream (cross-workstream moves are deferred).\n */\nexport interface UpdateTaskScopeOption {\n workstream: string;\n}\n\nexport function updateTask(\n db: Db,\n localId: string,\n opts: UpdateTaskOptions,\n scope: UpdateTaskScopeOption,\n): UpdateTaskResult {\n const before = getTask(db, localId, scope.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n const taskId = taskIdFor(db, before.name, before.workstreamName);\n if (taskId === null) throw new TaskNotFoundError(localId);\n\n const setters: string[] = [];\n const params: unknown[] = [];\n const changedFields: string[] = [];\n\n if (opts.title !== undefined && opts.title !== before.title) {\n setters.push(\"title = ?\");\n params.push(opts.title);\n changedFields.push(\"title\");\n }\n if (opts.impact !== undefined && opts.impact !== before.impact) {\n setters.push(\"impact = ?\");\n params.push(opts.impact);\n changedFields.push(\"impact\");\n }\n if (opts.effortDays !== undefined && opts.effortDays !== before.effortDays) {\n setters.push(\"effort_days = ?\");\n params.push(opts.effortDays);\n changedFields.push(\"effortDays\");\n }\n\n if (setters.length === 0) {\n return { updated: false, changedFields: [] };\n }\n\n setters.push(\"updated_at = ?\");\n params.push(new Date().toISOString());\n params.push(taskId);\n\n db.prepare(`UPDATE tasks SET ${setters.join(\", \")} WHERE id = ?`).run(...params);\n emitEvent(\n db,\n before.workstreamName,\n `task update ${localId} (changed: ${changedFields.join(\", \")})`,\n );\n return { updated: true, changedFields };\n}\n","// mu — task lifecycle verbs: setTaskStatus, closeTask, openTask,\n// rejectTask, deferTask + supporting types.\n//\n// Lifecycle = \"transition a task from one status to another, with\n// the right side effects (auto-snapshot before mutating, emit\n// agent_logs event, refresh pane title via the caller, validate\n// guard rails like 'reject would strand dependents')\".\n//\n// EvidenceOption is shared with claim/release (in tasks/claim.ts) and\n// re-exported here as the canonical home; claim.ts imports from this\n// file.\n//\n// Extracted from src/tasks.ts as part of refactor_split_large_src_files.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport { getTaskEdgesWithStatus } from \"./edges.js\";\nimport { addNote } from \"./edit.js\";\nimport { TaskHasOpenDependentsError, TaskNotFoundError } from \"./errors.js\";\nimport { getTask } from \"./queries.js\";\nimport type { TaskStatus } from \"./status.js\";\n\nexport interface SetStatusResult {\n /** Status before the call. */\n previousStatus: TaskStatus;\n /** Status after the call (== requested status). */\n status: TaskStatus;\n /** True iff the row actually changed. False on idempotent no-op. */\n changed: boolean;\n}\n\n/**\n * Optional evidence string carried on lifecycle verbs (close / open /\n * claim / release). Lands in the auto-emitted `kind='event'` payload\n * verbatim, prefixed with `evidence=`. The first inch of distinguishing\n * \"observed\" from \"claimed\" state per an internal critique: the\n * verb still trusts the caller (it's not a verifier), but the audit\n * trail records what the caller said it relied on.\n */\nexport interface EvidenceOption {\n evidence?: string;\n}\n\n/** Render the optional `--evidence \"<text>\"` payload as the trailing\n * ' evidence=\"...\"' on every state-changing event. Exported because\n * claimTask/releaseTask in src/tasks/claim.ts also use it. */\nexport function evidenceSuffix(opts: EvidenceOption | undefined): string {\n if (!opts || opts.evidence === undefined) return \"\";\n return ` evidence=${JSON.stringify(opts.evidence)}`;\n}\n\n/**\n * Flip a task's status to any of OPEN / IN_PROGRESS / CLOSED.\n * Idempotent: setting a task to its current status is a no-op (returns\n * `changed: false`) rather than throwing. Owner is unchanged.\n */\nexport function setTaskStatus(\n db: Db,\n localId: string,\n status: TaskStatus,\n opts: EvidenceOption & { workstream: string },\n): SetStatusResult {\n const before = getTask(db, localId, opts.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n if (before.status === status) {\n return { previousStatus: before.status, status, changed: false };\n }\n // v5: tasks.local_id is per-workstream unique. Scope to the row's\n // workstream so the UPDATE doesn't accidentally touch a same-named\n // task in another workstream.\n db.prepare(\n `UPDATE tasks SET status = ?, updated_at = ?\n WHERE local_id = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)`,\n ).run(status, new Date().toISOString(), localId, before.workstreamName);\n emitEvent(\n db,\n before.workstreamName,\n `task status ${localId} (${before.status} → ${status})${evidenceSuffix(opts)}`,\n );\n return { previousStatus: before.status, status, changed: true };\n}\n\n/** Result of `closeTask` when called with `ifReady: true` and the\n * task is NOT yet ready to close (still has at least one OPEN /\n * IN_PROGRESS blocker). Distinguished from a regular `SetStatusResult`\n * by the literal `skipped` field; the CLI keys on it to switch\n * between the \"closed\" and \"waiting\" rendering paths.\n *\n * Surfaced in `fb_umbrella_no_auto_close` (impact=60): a wave umbrella\n * with N blockers stayed OPEN after every blocker reached a terminal\n * status. `--if-ready` is the cheap fix: bare `mu task close` is\n * unchanged (closes regardless), `--if-ready` is a no-op unless every\n * blocker is in a terminal status (CLOSED / REJECTED / DEFERRED).\n * Reject and defer satisfy the predicate too because `--if-ready`'s\n * job is to fire when the umbrella has nothing left to wait for, and\n * a rejected/deferred blocker is no longer being waited on. */\nexport interface CloseSkippedResult {\n /** Always 'not_ready' when set; future cause-codes can extend this\n * without reshaping the JSON payload (the literal-union narrows\n * safely in the CLI rendering path). */\n skipped: \"not_ready\";\n /** Status before the call (always the current status, no change). */\n previousStatus: TaskStatus;\n /** Status after the call (== previousStatus, since we no-op). */\n status: TaskStatus;\n /** Always false on a skip (no row mutated). */\n changed: false;\n /** Local ids of every blocker still in OPEN or IN_PROGRESS, sorted\n * alphabetically for deterministic rendering. Empty list is\n * impossible on this branch — the no-op only fires when ≥1\n * blocker is non-terminal. */\n blockingIds: string[];\n}\n\nexport interface CloseTaskOptions extends EvidenceOption {\n workstream: string;\n /** When true, no-op the close unless every blocker is in a terminal\n * status (CLOSED / REJECTED / DEFERRED). Returns a\n * `CloseSkippedResult` carrying the still-blocking ids; the CLI\n * renders the skip with a Next: hint pointing at `mu task wait`.\n * When false / omitted, behaves as bare `closeTask` (closes\n * regardless of blocker status). */\n ifReady?: boolean;\n /** Optional actor identity attributed to the synthetic `CLOSE: …`\n * note auto-inserted when `evidence` is non-empty (see closeTask\n * body). The CLI resolves this via `resolveActorIdentity()` so the\n * note carries the closing worker's name; SDK callers (tests,\n * internal use) may omit it (the note then carries no author, same\n * as a bare `addNote` without `--author`). Surfaced in mufeedback\n * task_close_evidence_does_not_append_the. */\n author?: string;\n}\n\n/** Convenience: setTaskStatus(db, id, \"CLOSED\"). Accepts evidence.\n * Pre-snapshots the DB (snap_design §CAPTURE STRATEGY > WHEN). Skipped\n * for the idempotent no-op (already CLOSED) so we don't accumulate\n * empty-delta snapshots on retry loops.\n *\n * With `ifReady: true`, returns a `CloseSkippedResult` (no mutation,\n * no snapshot) when any blocker is still OPEN / IN_PROGRESS. Used by\n * `mu task close --if-ready` so an orchestrator can fire-and-forget\n * the umbrella close after every blocker resolves without first\n * re-querying the graph. */\nexport function closeTask(\n db: Db,\n localId: string,\n opts: CloseTaskOptions,\n): SetStatusResult | CloseSkippedResult {\n const before = getTask(db, localId, opts.workstream);\n if (opts.ifReady && before) {\n // Inspect direct blockers only — the umbrella convention is one\n // hop (umbrella -[blocked-by]→ each wave task). Transitive depth\n // doesn't matter: if any direct blocker is non-terminal, the\n // umbrella isn't ready; if every direct blocker is terminal,\n // their own ancestry was satisfied as a precondition for them\n // closing/rejecting/deferring in the first place.\n const edges = getTaskEdgesWithStatus(db, localId, before.workstreamName);\n const blocking = edges.blockers\n .filter((e) => e.status === \"OPEN\" || e.status === \"IN_PROGRESS\")\n .map((e) => e.name)\n .sort();\n if (blocking.length > 0) {\n return {\n skipped: \"not_ready\",\n previousStatus: before.status,\n status: before.status,\n changed: false,\n blockingIds: blocking,\n };\n }\n }\n if (before && before.status !== \"CLOSED\") {\n captureSnapshot(db, `task close ${localId}`, before.workstreamName);\n }\n const r = setTaskStatus(db, localId, \"CLOSED\", opts);\n // mufeedback task_close_evidence_does_not_append_the: when the\n // operator passes `--evidence \"...\"`, the string lands in the\n // agent_logs event payload but was invisible to `mu task notes <id>`\n // / `mu task show <id>`. Workers were skipping the \"drop a final\n // note before close\" contract on the (reasonable) assumption that\n // --evidence was sufficient. Auto-insert a synthetic note so the\n // evidence joins the note timeline. Only fires when evidence is a\n // non-empty string AND the close actually mutated something\n // (idempotent re-close on an already-CLOSED task: skip; nothing\n // newly attested). Empty-string evidence is treated as none.\n if (r.changed && before && opts.evidence !== undefined && opts.evidence !== \"\") {\n const noteOpts: { author?: string; workstream: string } = {\n workstream: before.workstreamName,\n };\n if (opts.author !== undefined && opts.author !== \"\") noteOpts.author = opts.author;\n addNote(db, localId, `CLOSE: ${opts.evidence}`, noteOpts);\n }\n return r;\n}\n\n/** Convenience: setTaskStatus(db, id, \"OPEN\"). Owner intentionally NOT\n * cleared — use `releaseTask` for that. Accepts evidence. */\nexport function openTask(\n db: Db,\n localId: string,\n opts: EvidenceOption & { workstream: string },\n): SetStatusResult {\n return setTaskStatus(db, localId, \"OPEN\", opts);\n}\n\n// ─── rejectTask / deferTask (terminal-but-blocking transitions) ────\n//\n// REJECTED and DEFERRED both leave the task off the active scheduler\n// (gone from `ready`, `goals`, track count) but, unlike CLOSED, do NOT\n// satisfy a `--blocked-by` edge. A REJECTED / DEFERRED task therefore\n// silently strands every OPEN/IN_PROGRESS dependent. We refuse the\n// transition unless either there are no open dependents OR the caller\n// passes `--cascade` to apply the same status to every transitive\n// dependent.\n\nexport interface RejectDeferOptions extends EvidenceOption {\n /** Workstream context for the root task. All internal task lookups\n * (including the dependent walk) scope to this workstream. */\n workstream: string;\n /** If true, walk the transitive dependent closure and (with `yes`)\n * apply the same status to every dependent, atomically. Without\n * `yes`, runs as a dry-run: returns the list of tasks that WOULD\n * be swept (changedIds) with `dryRun: true` and changes nothing.\n * Logs one event per task (via setTaskStatus) on commit. */\n cascade?: boolean;\n /** Required to actually commit a `cascade` operation. Without it,\n * cascade is dry-run only — prints the affected dependents so the\n * caller can verify before sweeping. Mirrors `mu workstream destroy\n * --yes`. Surfaced in mufeedback bug_cascade_reject_too_aggressive\n * when an accidentally-cascaded reject swept hud_dogfood (which had\n * independent merit and needed reopening). */\n yes?: boolean;\n}\n\nexport interface RejectDeferResult {\n /** Tasks that actually changed status, in cascade order (root first). */\n changedIds: string[];\n /** The status now stamped on every changedId. */\n status: TaskStatus;\n /** True iff anything changed. False on a clean idempotent no-op\n * (root task already in target status, no dependents). */\n changed: boolean;\n /** True iff this was a `cascade` dry-run (cascade requested without\n * `yes`). In that case `changedIds` lists tasks that WOULD be\n * swept; the DB is unchanged. */\n dryRun: boolean;\n /** Tasks that would be touched by a cascade. Same as `changedIds`\n * on a dry-run; populated even on a commit so the caller can\n * report what was swept. */\n affectedIds: string[];\n}\n\n/** Reject a task: terminal 'won't do' (out of scope, duplicate, wontfix).\n * Refuses if dependents are open unless `--cascade`.\n * Pre-snapshots once at the verb level so a cascade onto N children\n * produces a single snapshot, not N. Skipped for the idempotent no-op. */\nexport function rejectTask(db: Db, localId: string, opts: RejectDeferOptions): RejectDeferResult {\n const before = getTask(db, localId, opts.workstream);\n if (before && before.status !== \"REJECTED\") {\n captureSnapshot(db, `task reject ${localId}`, before.workstreamName);\n }\n return setTerminalOrParked(db, localId, \"REJECTED\", opts);\n}\n\n/** Defer a task: parked, may revisit. Same dependent-stranding semantics\n * as reject (DEFERRED also doesn't satisfy a `--blocked-by` edge).\n * Pre-snapshots once at the verb level. Skipped for the idempotent no-op. */\nexport function deferTask(db: Db, localId: string, opts: RejectDeferOptions): RejectDeferResult {\n const before = getTask(db, localId, opts.workstream);\n if (before && before.status !== \"DEFERRED\") {\n captureSnapshot(db, `task defer ${localId}`, before.workstreamName);\n }\n return setTerminalOrParked(db, localId, \"DEFERRED\", opts);\n}\n\nfunction setTerminalOrParked(\n db: Db,\n localId: string,\n status: \"REJECTED\" | \"DEFERRED\",\n opts: RejectDeferOptions,\n): RejectDeferResult {\n const before = getTask(db, localId, opts.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n\n // Find all open (OPEN or IN_PROGRESS) tasks that transitively depend\n // on this one. Forward-edge recursive CTE from localId, scoped by\n // the root task's workstream.\n const openDependents = findOpenDependents(db, localId, before.workstreamName);\n\n if (openDependents.length > 0 && !opts.cascade) {\n const verb = status === \"REJECTED\" ? \"reject\" : \"defer\";\n throw new TaskHasOpenDependentsError(localId, verb, openDependents);\n }\n\n const affectedIds =\n openDependents.length > 0 && opts.cascade ? [localId, ...openDependents] : [localId];\n\n // Cascade dry-run: cascade requested but --yes missing. Don't touch\n // the DB; return the would-be-affected list so the CLI can render\n // a 'about to sweep these N tasks; rerun with --yes' preview.\n // Mirrors `mu workstream destroy` semantics. Single-task case\n // (openDependents == 0, cascade flag irrelevant) skips the dry-run\n // since there's nothing to preview.\n if (opts.cascade && !opts.yes && openDependents.length > 0) {\n return {\n changedIds: affectedIds,\n status,\n changed: false,\n dryRun: true,\n affectedIds,\n };\n }\n\n // Apply to root first, then dependents in BFS order. setTaskStatus\n // emits one event per task and is idempotent (no-op if already in\n // target status). Every UPDATE scopes to the root's workstream\n // (dependents must share it — cross-ws edges are forbidden).\n const childOpts: EvidenceOption & { workstream: string } = {\n workstream: before.workstreamName,\n ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}),\n };\n const changedIds: string[] = [];\n for (const id of affectedIds) {\n const r = setTaskStatus(db, id, status, childOpts);\n if (r.changed) changedIds.push(id);\n }\n\n return {\n changedIds,\n status,\n changed: changedIds.length > 0,\n dryRun: false,\n affectedIds,\n };\n}\n\n/** Open dependents that would be stranded if `taskId` were rejected /\n * deferred. The walk PRUNES at CLOSED nodes: a CLOSED intermediate\n * has already satisfied its blocked-by edge, so its downstream is\n * independent of whatever happens to `taskId` and must NOT be swept.\n * REJECTED / DEFERRED intermediates also stop the walk — their\n * downstream is already stranded by them, not by `taskId`, and a\n * cascade from here would (a) double-flip them or (b) overwrite a\n * previous explicit decision.\n *\n * Ordering: BFS-equivalent via DISTINCT + ORDER BY local_id; cascade\n * applies one row at a time so each setTaskStatus is logged. */\nfunction findOpenDependents(db: Db, taskLocalId: string, workstream: string): string[] {\n // Resolve the seed task to its surrogate id, then walk forward\n // edges in surrogate-id space; project back to local_id at the end.\n // Scope the seed by workstream (v5 per-workstream local_id) so a\n // same-named task elsewhere can't seed a cascade in this workstream\n // (bug_v5_name_clash_silent_misroute).\n const seed = db\n .prepare(\n `SELECT id FROM tasks WHERE local_id = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)`,\n )\n .get(taskLocalId, workstream) as { id: number } | undefined;\n if (!seed) return [];\n const rows = db\n .prepare(\n `WITH RECURSIVE forward(node) AS (\n SELECT e.to_task_id\n FROM task_edges e\n JOIN tasks t ON t.id = e.to_task_id\n WHERE e.from_task_id = ?\n AND t.status IN ('OPEN', 'IN_PROGRESS')\n UNION\n SELECT e.to_task_id\n FROM task_edges e\n JOIN forward f ON f.node = e.from_task_id\n JOIN tasks t ON t.id = e.to_task_id\n WHERE t.status IN ('OPEN', 'IN_PROGRESS')\n )\n SELECT DISTINCT t.local_id AS local_id FROM forward f\n JOIN tasks t ON t.id = f.node\n ORDER BY t.local_id`,\n )\n .all(seed.id) as { local_id: string }[];\n return rows.map((r) => r.local_id);\n}\n","// mu — claim/release/resolveActorIdentity verbs.\n//\n// claimTask is the heart of mu's coordination protocol: an atomic\n// CAS via a single SQL UPDATE, with two flavours:\n//\n// \"worker claim\" : --for <name> sets owner=<name> (FK to agents.name)\n// \"anonymous claim\": --self keeps owner=NULL but flips status to\n// IN_PROGRESS and records the actor\n// in agent_logs\n//\n// resolveActorIdentity is the env-aware identity helper: pane title\n// > MU_AGENT_NAME > USER > 'orchestrator'. Used by --self.\n//\n// Extracted from src/tasks.ts as part of refactor_split_large_src_files.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent, formatClaimEvent } from \"../logs.js\";\nimport { captureSnapshot } from \"../snapshots.js\";\nimport { currentAgentName } from \"../tmux.js\";\nimport { ClaimerNotRegisteredError, TaskAlreadyOwnedError, TaskNotFoundError } from \"./errors.js\";\nimport { type EvidenceOption, evidenceSuffix } from \"./lifecycle.js\";\nimport { getTask } from \"./queries.js\";\nimport type { TaskStatus } from \"./status.js\";\n\nexport interface ReleaseResult {\n /** The previous owner (null if the task was already unowned). */\n previousOwnerName: string | null;\n /** Status before the release. */\n previousStatus: TaskStatus;\n /** Status after the release. */\n status: TaskStatus;\n /** True iff owner OR status actually changed. */\n changed: boolean;\n}\n\nexport interface ReleaseTaskOptions extends EvidenceOption {\n /** Workstream context for the task (v5: tasks.local_id is\n * per-workstream unique). */\n workstream: string;\n /** Force `status = OPEN` regardless of the current status. Without\n * this flag, `IN_PROGRESS` is also flipped to `OPEN` automatically\n * (so a released task isn't left structurally stranded with\n * `owner=NULL, status=IN_PROGRESS`); CLOSED / REJECTED / DEFERRED\n * are preserved. `--reopen` is the override for the rarer \"un-\n * close and hand back to the pool\" workflow. */\n reopen?: boolean;\n}\n\n/**\n * Release a task: clear `tasks.owner`.\n *\n * Status side-effects (review_release_open_in_progress_inconsistency):\n * - IN_PROGRESS → OPEN automatically (without it, the task is\n * stranded: no owner to drive it forward, but `mu task next`\n * skips it because it's not OPEN).\n * - OPEN / CLOSED / REJECTED / DEFERRED preserved.\n * - `--reopen` forces OPEN regardless of current status — the\n * escape hatch for un-closing a CLOSED owned task in one verb.\n *\n * Idempotent: releasing an already-unowned task with no `--reopen` and\n * no IN_PROGRESS status is a no-op (returns `changed: false`).\n * Throws TaskNotFoundError on missing.\n */\nexport function releaseTask(db: Db, localId: string, opts: ReleaseTaskOptions): ReleaseResult {\n const before = getTask(db, localId, opts.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n\n // Default: auto-flip IN_PROGRESS → OPEN so the released task isn't\n // left in the structurally weird owner=NULL/IN_PROGRESS limbo.\n // --reopen still wins for any status (CLOSED / REJECTED / DEFERRED).\n const newStatus: TaskStatus = opts.reopen\n ? \"OPEN\"\n : before.status === \"IN_PROGRESS\"\n ? \"OPEN\"\n : before.status;\n const ownerChanges = before.ownerName !== null;\n const statusChanges = newStatus !== before.status;\n\n if (!ownerChanges && !statusChanges) {\n return {\n previousOwnerName: before.ownerName,\n previousStatus: before.status,\n status: before.status,\n changed: false,\n };\n }\n\n // Pre-mutation snapshot — release wipes ownership which is\n // irrecoverable from history (we'd lose 'who was working on this').\n captureSnapshot(db, `task release ${localId}`, before.workstreamName);\n\n db.prepare(\n `UPDATE tasks SET owner_id = NULL, status = ?, updated_at = ?\n WHERE local_id = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)`,\n ).run(newStatus, new Date().toISOString(), localId, before.workstreamName);\n const statusBit = statusChanges ? `, ${before.status} → ${newStatus}` : \"\";\n emitEvent(\n db,\n before.workstreamName,\n `task release ${localId} (was owner=${before.ownerName ?? \"none\"}${statusBit})${evidenceSuffix(opts)}`,\n );\n return {\n previousOwnerName: before.ownerName,\n previousStatus: before.status,\n status: newStatus,\n changed: true,\n };\n}\n\n// ─── claimTask (verb) ──────────────────────────────────────────────────\n\nexport interface ClaimTaskOptions extends EvidenceOption {\n /** Workstream context for both the task and the claiming agent.\n * v5: agents.name and tasks.local_id are per-workstream unique;\n * the task lookup AND the agent FK lookup scope to this\n * workstream so a same-named task or worker elsewhere can't be\n * silently picked. The CLI always passes this from the resolved\n * -w / $MU_SESSION. */\n workstream: string;\n /**\n * Override the agent name. If omitted, derived from the current pane's\n * title via `tmux display-message -t $TMUX_PANE -p '#{pane_title}'`.\n *\n * Mutually exclusive with `self: true`.\n */\n agentName?: string;\n /**\n * Workstream that the claimer agent lives in. When omitted, defaults\n * to `opts.workstream` (today's same-workstream behaviour). Set by\n * the CLI when `mu task claim X -w A --for B/worker-1` qualifies the\n * `--for` ref with a different workstream prefix\n * (`task_claim_for_cross_workstream`).\n *\n * Cross-workstream ownership is structurally allowed by the schema:\n * `tasks.owner_id` is an INTEGER FK to `agents.id` with no\n * workstream qualifier on the agent side. The per-workstream UNIQUE\n * on `agents(workstream_id, name)` is what previously made the\n * SDK's name → id lookup scope to one workstream; this option\n * widens that lookup to a different workstream when the operator\n * dispatches across a workstream boundary. The agent's own\n * workstream remains unchanged — only the task's `owner_id` points\n * out-of-workstream.\n */\n agentWorkstream?: string;\n /**\n * Anonymous claim: write `owner = NULL` instead of resolving an agent\n * name and checking the FK. Use when the actor is the orchestrator\n * (or a script, or a human) doing direct work in a workstream they\n * aren't a registered worker in.\n *\n * The actor name is still recorded — it ends up in `agent_logs.source`\n * for the auto-emitted `task claim` event — so provenance is preserved.\n * Just not in the FK column.\n *\n * Resolution order for the actor name (used as the log source):\n * 1. `actor` if explicitly passed.\n * 2. Current pane title (when `$TMUX_PANE` is set).\n * 3. `$USER`.\n * 4. The literal string 'unknown'.\n *\n * Mutually exclusive with `agentName` (the two are alternative\n * answers to \"who's the actor for this claim?\"). Passing both is a\n * usage error.\n */\n self?: boolean;\n /**\n * Override the actor name used for the log source when `self: true`.\n * Ignored when `self: false`. Useful when the orchestrator wants to\n * attribute the work to a meaningful name rather than the pane\n * title (e.g. \"deploy-bot\" rather than \"pi-mu\").\n */\n actor?: string;\n}\n\nexport interface ClaimResult {\n /** The agent now owning the task, or null when the claim was anonymous (--self). */\n ownerName: string | null;\n /** The actor recorded in the agent_logs event — the agent name for a\n * registered-worker claim, or the resolved actor for --self. */\n actorName: string;\n /** The previous owner (null if it was unowned). */\n previousOwnerName: string | null;\n /** The status BEFORE the claim; post-claim is IN_PROGRESS unless was CLOSED. */\n previousStatus: TaskStatus;\n /** The status AFTER the claim. */\n status: TaskStatus;\n}\n\n/**\n * Claim a task. Two modes:\n *\n * Worker claim (default):\n * Resolve an agent name from `opts.agentName` or from $TMUX_PANE's\n * pane title. The name MUST exist in the agents table (FK on\n * tasks.owner). Sets `owner = <name>`. This is what mu-spawned\n * workers do, and what `mu task claim --for <worker>` does for\n * orchestrator dispatch.\n *\n * Anonymous claim (--self):\n * Skip the name -> agents FK lookup entirely. Sets `owner = NULL`.\n * Records the actor in `agent_logs.source` instead. This is the\n * orchestrator-doing-direct-work path — the actor is logged but\n * not registered as a worker pane.\n *\n * Status side-effect: OPEN -> IN_PROGRESS; IN_PROGRESS / CLOSED unchanged.\n *\n * Concurrency: the worker-claim path uses a single-statement CAS UPDATE\n * with `WHERE owner IS NULL OR owner = ?` so two workers racing to\n * claim the same task can't both win. The anonymous path uses\n * `WHERE owner IS NULL` (anonymous claims don't 'own' the task in any\n * exclusive sense; if it's already owned by anyone, the anonymous claim\n * is a TaskAlreadyOwnedError just like a worker claim would be).\n */\nexport async function claimTask(\n db: Db,\n localId: string,\n opts: ClaimTaskOptions,\n): Promise<ClaimResult> {\n if (opts.self === true && opts.agentName !== undefined) {\n throw new Error(\"claimTask: --self and --for are mutually exclusive\");\n }\n\n if (opts.self === true) {\n return claimSelf(db, localId, opts);\n }\n\n // ── Worker claim path (registered agent owns the task) ──\n // currentAgentName() parses 'name · status · task' titles back to\n // just the name token — the registry FK is keyed on agents.name,\n // so the parser is essential after composeAgentTitle decorates.\n const agentName = opts.agentName ?? (await currentAgentName());\n if (!agentName) {\n throw new Error(\n \"claimTask: no agent name (pass opts.agentName, run inside an mu-spawned pane with $TMUX_PANE set, or pass --self for an anonymous claim)\",\n );\n }\n\n // Resolve the claiming agent to its surrogate id within the agent's\n // workstream — defaults to opts.workstream (today's same-ws path),\n // or opts.agentWorkstream when the CLI dispatched across a\n // workstream boundary via a qualified `--for <ws>/<name>` ref\n // (task_claim_for_cross_workstream).\n //\n // The schema permits cross-workstream owner_id assignment (FK to\n // agents.id only); the per-workstream UNIQUE on agents.name is the\n // only reason this SELECT was scoped narrowly before. Bare-name\n // dispatch keeps that scope to honour today's behaviour; qualified\n // dispatch widens it to the named workstream so the agent resolves\n // there.\n const claimerWorkstream = opts.agentWorkstream ?? opts.workstream;\n const claimerRow = db\n .prepare(\n `SELECT a.id AS id\n FROM agents a JOIN workstreams ws ON ws.id = a.workstream_id\n WHERE a.name = ? AND ws.name = ?`,\n )\n .get(agentName, claimerWorkstream) as { id: number } | undefined;\n if (!claimerRow) {\n const paneIdFromEnv = opts.agentName === undefined ? (process.env.TMUX_PANE ?? null) : null;\n throw new ClaimerNotRegisteredError(agentName, paneIdFromEnv);\n }\n\n return db.transaction(() => {\n // Resolve the task within opts.workstream. This locks the\n // (workstream, local_id) pair for the rest of the transaction.\n const before = getTask(db, localId, opts.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n\n const now = new Date().toISOString();\n const result = db\n .prepare(\n `UPDATE tasks\n SET owner_id = ?,\n status = CASE WHEN status = 'OPEN' THEN 'IN_PROGRESS' ELSE status END,\n updated_at = ?\n WHERE local_id = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)\n AND (owner_id IS NULL OR owner_id = ?)`,\n )\n .run(claimerRow.id, now, localId, opts.workstream, claimerRow.id);\n\n if (result.changes === 0) {\n throw new TaskAlreadyOwnedError(localId, before.ownerName ?? \"<unknown>\");\n }\n\n const after = getTask(db, localId, opts.workstream);\n if (!after) throw new Error(`claimTask: row missing after update: ${localId}`);\n const statusBit = after.status !== before.status ? `, ${before.status} → ${after.status}` : \"\";\n emitEvent(\n db,\n opts.workstream,\n formatClaimEvent({\n localId,\n actor: agentName,\n anonymous: false,\n prose: `task claim ${localId} by ${agentName} (was owner=${before.ownerName ?? \"none\"}${statusBit})${evidenceSuffix(opts)}`,\n }),\n agentName,\n );\n return {\n ownerName: agentName,\n actorName: agentName,\n previousOwnerName: before.ownerName,\n previousStatus: before.status,\n status: after.status,\n };\n })();\n}\n\n/**\n * Resolve the current actor's identity for attribution in task notes,\n * --self claims, and any other write that wants 'who did this?'.\n *\n * Resolution order:\n * 1. $MU_AGENT_NAME env var (set by mu spawnAgent on every managed\n * pane; surfaced from the f3d4bdd commit). Authoritative when\n * present — you're inside a mu-spawned worker, no ambiguity.\n * 2. tmux pane title (the pane-title identity step). Works\n * when running inside any pane mu manages OR adopted.\n * 3. $USER (when running outside tmux entirely).\n * 4. The literal 'orchestrator' as a last-resort default.\n *\n * Why prefer env over pane title: pane titles are a tmux-server-wide\n * resource that anything can rewrite. The env var is set per-pane at\n * spawn time and is unforgeable from outside without explicit\n * `--actor` override. Pane title is the only identity available for\n * adopted panes that didn't go through mu's spawn path.\n */\nexport async function resolveActorIdentity(): Promise<string> {\n const muAgent = process.env.MU_AGENT_NAME;\n if (muAgent !== undefined && muAgent !== \"\") return muAgent;\n const paneTitle = await currentAgentName();\n if (paneTitle !== undefined && paneTitle !== \"\") return paneTitle;\n const user = process.env.USER;\n if (user !== undefined && user !== \"\") return user;\n return \"orchestrator\";\n}\n\nasync function claimSelf(db: Db, localId: string, opts: ClaimTaskOptions): Promise<ClaimResult> {\n const actor =\n opts.actor !== undefined && opts.actor !== \"\" ? opts.actor : await resolveActorIdentity();\n return db.transaction(() => {\n // Scope by the operator's workstream so a same-named task\n // elsewhere can't be self-claimed by accident.\n const before = getTask(db, localId, opts.workstream);\n if (!before) throw new TaskNotFoundError(localId);\n\n // Anonymous claim: owner stays NULL, status flips OPEN -> IN_PROGRESS.\n // Gate on `owner_id IS NULL` so an in-flight worker claim can't be\n // silently overwritten.\n const now = new Date().toISOString();\n const result = db\n .prepare(\n `UPDATE tasks\n SET status = CASE WHEN status = 'OPEN' THEN 'IN_PROGRESS' ELSE status END,\n updated_at = ?\n WHERE local_id = ?\n AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)\n AND owner_id IS NULL`,\n )\n .run(now, localId, before.workstreamName);\n\n if (result.changes === 0) {\n // Task exists but is already owned (by someone). Mirror the\n // worker-path error so callers can pattern-match consistently.\n throw new TaskAlreadyOwnedError(localId, before.ownerName ?? \"<unknown>\");\n }\n\n const after = getTask(db, localId, before.workstreamName);\n if (!after) throw new Error(`claimTask: row missing after update: ${localId}`);\n const statusBit = after.status !== before.status ? `, ${before.status} → ${after.status}` : \"\";\n emitEvent(\n db,\n before.workstreamName,\n formatClaimEvent({\n localId,\n actor,\n anonymous: true,\n prose: `task claim ${localId} by ${actor} --self (anonymous, owner stays NULL${statusBit})${evidenceSuffix(opts)}`,\n }),\n actor,\n );\n return {\n ownerName: null,\n actorName: actor,\n previousOwnerName: before.ownerName,\n previousStatus: before.status,\n status: after.status,\n };\n })();\n}\n","// mu — waitForTasks: block until tasks reach a target status.\n//\n// The orchestrator pattern: dispatch N workers via mu task claim --for,\n// then wait until they're all done before reviewing/merging.\n//\n// Pre-existing alternatives + why this verb exists:\n//\n// awk pipe over `mu log --tail`: works for ONE event but the\n// awk script becomes stateful (tracking 'which of N have closed?')\n// for multi-task waits. Bad shape for SKILL examples.\n//\n// Implementation:\n//\n// 1. Initial check — if the wait condition is already satisfied,\n// exit immediately. No subscription needed.\n// 2. Otherwise, poll the tasks table every pollMs. Same cadence as\n// mu log --tail (default 1000ms). We don't subscribe to\n// agent_logs because (a) we'd still need to re-query tasks to\n// learn the current status, (b) some status changes happen via\n// mu sql which doesn't emit events, and (c) the polling cost is\n// one indexed SELECT every second — cheaper than parsing the\n// log stream.\n// 3. Exit on success (all/any reached) OR timeout. Caller maps\n// timeout to exit code 5.\n//\n// Extracted from src/tasks.ts as part of refactor_split_large_src_files.\n\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport { StallDetectedDuringWaitError, TaskNotFoundError } from \"./errors.js\";\nimport { getTask } from \"./queries.js\";\nimport type { TaskStatus } from \"./status.js\";\n\n// ─── Test seams: poll-sleep + poll counter + stuck-warn writer ─────────\n//\n// Mirror src/tmux.ts's setSleepForTests pattern. Default sleep is a real\n// setTimeout; tests can swap in an instant + counted version to assert\n// poll cadence (the bug fixed alongside this hook silently sleeps a full\n// pollMs past the deadline when pollMs > timeoutMs — see test/tasks.test.ts\n// 'waitForTasks' regression cases).\n//\n// The stuck-warn writer is the second seam: agent_close_discipline_gap\n// added a per-poll \"this task is IN_PROGRESS but owner is needs_input\n// for too long\" warning emitted to stderr; tests intercept it via\n// setWaitStuckWarnForTests so they can assert exactly-once dedupe\n// without scraping process.stderr.\n\nlet currentWaitSleep: (ms: number) => Promise<void> = (ms) =>\n new Promise((resolve) => setTimeout(resolve, ms));\nlet pollCount = 0;\nconst defaultStuckWarn: (msg: string) => void = (msg) => {\n process.stderr.write(msg);\n};\nlet currentStuckWarn: (msg: string) => void = defaultStuckWarn;\n\nexport function setWaitSleepForTests(\n impl: ((ms: number) => Promise<void>) | undefined,\n): (ms: number) => Promise<void> {\n const previous = currentWaitSleep;\n currentWaitSleep = impl ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));\n return previous;\n}\n\n/** Test seam: swap the stderr writer used by the stuck-task warning so\n * unit tests can capture warnings without spying on process.stderr. */\nexport function setWaitStuckWarnForTests(\n impl: ((msg: string) => void) | undefined,\n): (msg: string) => void {\n const previous = currentStuckWarn;\n currentStuckWarn = impl ?? defaultStuckWarn;\n return previous;\n}\n\n/** Total number of polls performed across all `waitForTasks` calls in this\n * process. Tests typically reset before exercising and read after. */\nexport function getWaitPollCount(): number {\n return pollCount;\n}\n\nexport function resetWaitPollCount(): void {\n pollCount = 0;\n}\n\n/** A single task ref the wait verb is watching. Cross-workstream\n * waits arrive as a heterogeneous list of (workstream, name) pairs;\n * the legacy single-workstream call passes the same workstream on\n * every ref. task_wait_cross_workstream. */\nexport interface TaskWaitRef {\n /** The workstream the task lives in. Each ref carries its own so\n * the SDK doesn't need a single \"the workstream\" — cross-ws waits\n * pass refs from multiple workstreams in one call. */\n workstreamName: string;\n /** The task's per-workstream-unique local id. */\n name: string;\n}\n\nexport interface TaskWaitOptions {\n /** Target status. Default 'CLOSED'. */\n status?: TaskStatus;\n /** When true, succeed as soon as ONE listed task reaches the target.\n * Default false: every listed task must reach the target. */\n any?: boolean;\n /** Maximum time to wait, in milliseconds. Default 600_000 (10 min).\n * Pass 0 to wait forever. */\n timeoutMs?: number;\n /** Polling interval. Default 1000ms; overridable for tests. */\n pollMs?: number;\n /** Workstream context applied to bare-string ids. Required when the\n * caller passes `string[]`; ignored when the caller passes\n * `TaskWaitRef[]` (each ref carries its own ws). The legacy\n * single-ws SDK call site keeps its today's shape; the cross-ws\n * callers (CLI verb) pass `TaskWaitRef[]` and omit `workstream`.\n * task_wait_cross_workstream. */\n workstream?: string;\n /** Emit a yellow STUCK warning to stderr (once per task per wait call)\n * when an IN_PROGRESS task's owner has been in `needs_input` for at\n * least this many milliseconds since the agent row's last update.\n * Default 300_000 (5 min). Pass 0 to disable.\n *\n * Surfaced by agent_close_discipline_gap in mufeedback: workers\n * occasionally finish + commit + go idle without running\n * `mu task close <id>`, leaving wait blocked indefinitely. The\n * warning is observation-only — wait keeps polling so the operator\n * (or a wrapping policy) decides whether to force-close, re-prompt,\n * or escalate. */\n stuckAfterMs?: number;\n /** What to do when the `--stuck-after` predicate fires on a watched\n * task. `'warn'` (default) = today's behaviour: yellow STUCK line\n * to stderr (deduped per task per wait call) + corroborating\n * `kind='event'` agent_logs row; wait keeps polling. `'exit'` =\n * same emit + persist, but THEN throw `StallDetectedDuringWaitError`\n * so the CLI wrapper exits 7 (STALL_DETECTED). The exit-action is\n * the unattended-orchestrator escape: a wrapping policy can branch\n * on 7 (idle, ambiguous — operator decides poke vs release) vs 6\n * (dead pane, unambiguous — re-dispatch).\n *\n * Carve-out (lives at the call site, not here): the CLI passes\n * `'exit'` only when the wait target is CLOSED — mirrors exit-6's\n * reaper-flip suppression. With `--status OPEN` the worker reaching\n * needs_input might BE the success path. See\n * task_wait_stall_action_flag. */\n onStall?: \"warn\" | \"exit\";\n /** Optional async hook run BEFORE every snapshot (initial + each\n * poll iteration). The CLI uses this to reconcile the workstream\n * each tick (reaper flips IN_PROGRESS → OPEN for dead-pane\n * workers) and to throw a typed error when a reaper-flip on a\n * watched task should abandon the wait — see\n * task_wait_reconcile_dead_panes. Throwing from `beforePoll`\n * propagates out of `waitForTasks` unchanged.\n *\n * Kept as a generic seam (not a `--reconcile`-shaped option) so\n * the SDK module stays free of tmux/reconcile imports — that\n * layering belongs above the SDK in the CLI wrapper. */\n beforePoll?: () => Promise<void>;\n}\n\nexport interface TaskWaitTaskState {\n /** The workstream this task lives in. Cross-workstream waits\n * return a mixed list; the workstream is part of identity.\n * task_wait_cross_workstream. */\n workstreamName: string;\n /** The task's per-workstream-unique name. */\n name: string;\n /** Current status (at the moment we exit). */\n status: TaskStatus;\n /** Owner at exit time (NULL when unowned, after release, or after\n * the reaper flipped IN_PROGRESS → OPEN due to a dead pane). */\n owner: string | null;\n /** True when this task's status equals the target. */\n reachedTarget: boolean;\n /** True when the task is IN_PROGRESS, owned by a registered agent\n * whose detected status is `needs_input` for >= `stuckAfterMs`.\n * Surfaces the agent_close_discipline_gap pattern: worker finished +\n * committed but skipped `mu task close <id>`. Backwards-compatible\n * signal — callers ignoring it see no behaviour change. */\n stuck: boolean;\n}\n\nexport interface TaskWaitResult {\n /** Per-task state at exit time. Same length and order as the input\n * list. The caller derives all-reached / any-reached / elapsed\n * from this list (count `r.reachedTarget`) and from its own\n * startedAt clock — keeping the SDK return minimal. */\n refs: TaskWaitTaskState[];\n /** True when we exited because of the timeout, not because the wait\n * condition was met. Refs that did reach the target are still\n * reflected in `refs[i].reachedTarget` on partial-progress timeout. */\n timedOut: boolean;\n}\n\n/**\n * Block until a set of tasks reaches `opts.status` (default CLOSED).\n * Returns a result describing the final state — the caller decides\n * whether to treat partial-progress timeouts as success or failure\n * (the CLI maps a clean exit to 0, a timeout to 5).\n *\n * Pre-flight: every task in `localIds` MUST exist; missing ones throw\n * TaskNotFoundError before any waiting begins. This is loud-fail by\n * design — a typo'd id silently waiting forever is the worst-case UX.\n */\nexport async function waitForTasks(\n db: Db,\n input: readonly TaskWaitRef[] | readonly string[],\n opts: TaskWaitOptions,\n): Promise<TaskWaitResult> {\n if (input.length === 0) {\n throw new Error(\"waitForTasks: refs must be non-empty\");\n }\n // Normalise to TaskWaitRef[]. The string[] shape preserves today's\n // single-workstream SDK contract (callers pass `workstream` on\n // opts); the TaskWaitRef[] shape carries its own ws per ref so\n // cross-workstream waits don't need a sentinel.\n const refs: readonly TaskWaitRef[] = input.map((entry) => {\n if (typeof entry === \"string\") {\n if (opts.workstream === undefined)\n throw new Error(\n \"waitForTasks: string ids require opts.workstream; pass TaskWaitRef[] for cross-workstream waits\",\n );\n return { workstreamName: opts.workstream, name: entry };\n }\n return entry;\n });\n const target: TaskStatus = opts.status ?? \"CLOSED\";\n const wantAny = opts.any === true;\n const timeoutMs = opts.timeoutMs ?? 600_000;\n const pollMs = opts.pollMs ?? 1000;\n const stuckAfterMs = opts.stuckAfterMs ?? 300_000;\n const onStall: \"warn\" | \"exit\" = opts.onStall ?? \"warn\";\n const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY;\n\n // Pre-flight: every ref must exist in its own workstream scope.\n // Cross-workstream waits validate each ref against its declared ws\n // — a typo in the workstream half of `<ws>/<name>` should land here\n // with a clear TaskNotFoundError, not silently wait forever.\n for (const ref of refs) {\n if (getTask(db, ref.name, ref.workstreamName) === undefined)\n throw new TaskNotFoundError(`${ref.workstreamName}/${ref.name}`);\n }\n\n // Per-(ws,name) dedupe: emit the STUCK warning at most ONCE per wait\n // call, not once per poll cycle. Operators don't want their stderr\n // filled with the same yellow line every second; one nudge is enough.\n const stuckWarned = new Set<string>();\n const refKey = (ref: TaskWaitRef): string => `${ref.workstreamName}/${ref.name}`;\n\n /**\n * Detect the agent_close_discipline_gap pattern for one task:\n * IN_PROGRESS in the DB, owned by a registered agent whose status\n * is `needs_input` and whose `updated_at` is older than\n * `stuckAfterMs`. We query agents directly (not via getAgent) to\n * avoid an import cycle (src/agents.ts already imports from\n * src/tasks.ts).\n */\n const isStuck = (status: TaskStatus, owner: string | null, workstream: string): boolean => {\n if (stuckAfterMs <= 0) return false;\n if (status !== \"IN_PROGRESS\" || !owner) return false;\n // owner is the operator-facing agent name; agents.name is\n // per-workstream unique in v5. Scope the lookup by workstream so\n // a same-named worker elsewhere doesn't spuriously mark this task\n // stuck.\n const row = db\n .prepare(\n `SELECT a.status AS status, a.updated_at AS updated_at\n FROM agents a\n JOIN workstreams ws ON ws.id = a.workstream_id\n WHERE a.name = ? AND ws.name = ?`,\n )\n .get(owner, workstream) as { status: string; updated_at: string } | undefined;\n if (!row || row.status !== \"needs_input\") return false;\n const ageMs = Date.now() - new Date(row.updated_at).getTime();\n return ageMs >= stuckAfterMs;\n };\n\n /** Read current state of all tasks; returns the result shape. */\n const snapshot = (): TaskWaitResult => {\n const refStates: TaskWaitTaskState[] = refs.map((ref) => {\n const row = getTask(db, ref.name, ref.workstreamName);\n // Defensive: if a task was deleted mid-wait, treat as 'never\n // reached'. (Not the same as TaskNotFoundError pre-flight —\n // deletion mid-wait shouldn't crash the wait; it's a legitimate\n // state change.)\n const status = (row?.status ?? \"OPEN\") as TaskStatus;\n const owner = row?.ownerName ?? null;\n const stuck = isStuck(status, owner, ref.workstreamName);\n const key = refKey(ref);\n if (stuck && !stuckWarned.has(key)) {\n stuckWarned.add(key);\n // Yellow ANSI escape inline (no picocolors import — keeps the\n // SDK module dep-free; the CLI layer already pulls picocolors).\n // The message is one line, prefixed with `mu task wait:` so\n // log greppers can target it. Cross-ws waits include the\n // qualified `<ws>/<name>` so the operator sees which.\n currentStuckWarn(\n `\\x1b[33mmu task wait: ${key} stuck — owner=${owner ?? \"<none>\"} in needs_input ` +\n `(>= ${stuckAfterMs}ms since last status change). ` +\n `Worker likely committed but skipped \\`mu task close ${ref.name}\\`.\\x1b[0m\\n`,\n );\n // Persist a corroborating kind='event' row so other consumers\n // (mu state, mu log --kind event, dashboards) see the same\n // signal that the new derived `idle` flag surfaces in the\n // agents table. The stderr warning stays — observation-only,\n // operator decides recovery. See idle_assigned_agent_detection.\n const ageSecs = Math.round(stuckAfterMs / 1000);\n emitEvent(\n db,\n ref.workstreamName,\n `agent stalled ${owner ?? \"<none>\"} owns ${ref.name} for ${ageSecs}s`,\n owner ?? \"system\",\n );\n // task_wait_stall_action_flag: with `--on-stall exit` (the\n // unattended-orchestrator escape), the same emit + persist\n // happens but we then throw — the CLI's classifyError maps\n // this to exit code 7 (STALL_DETECTED). The carve-out\n // (target=CLOSED only) lives at the call site: the CLI\n // wrapper passes `onStall: 'exit'` only when target=CLOSED.\n // Precedence vs exit-6 (dead pane): the reaper-flip check\n // throws inside `beforePoll` BEFORE snapshot() runs, so a\n // watched task that's both reaper-flipped AND stale never\n // reaches this branch (status would already be OPEN).\n if (onStall === \"exit\") {\n throw new StallDetectedDuringWaitError(ref.name, owner, ref.workstreamName, ageSecs);\n }\n }\n return {\n workstreamName: ref.workstreamName,\n name: ref.name,\n status,\n owner,\n reachedTarget: status === target,\n stuck,\n };\n });\n return {\n refs: refStates,\n timedOut: false,\n };\n };\n\n /** Has the wait condition been met? */\n const isDone = (snap: TaskWaitResult): boolean => {\n const reached = snap.refs.filter((r) => r.reachedTarget).length;\n return wantAny ? reached > 0 : reached === snap.refs.length;\n };\n\n // Initial check: maybe we're already done. Run beforePoll first so\n // the CLI's per-poll reconcile (task_wait_reconcile_dead_panes) runs\n // even on the immediate-exit path — a dead-pane worker that died\n // BEFORE the operator typed `mu task wait` should still fail fast.\n if (opts.beforePoll) await opts.beforePoll();\n let snap = snapshot();\n if (isDone(snap)) return snap;\n\n // Poll loop.\n //\n // Sleep is clamped to `min(pollMs, deadline - now)` so the function\n // returns within `timeoutMs + small slack`, never `pollMs` later.\n // Without the clamp, `pollMs=10000, timeoutMs=100` sleeps a full 10s\n // before noticing the deadline expired. When the clamp goes <= 0 we\n // skip the sleep entirely and re-snapshot before bailing on the\n // timeout — gives the wait one last chance at a winning state right\n // at the deadline boundary, and avoids passing 0 / negatives to\n // setTimeout (which has implementation-defined behaviour).\n for (;;) {\n const now = Date.now();\n if (now >= deadline) {\n return { ...snap, timedOut: true };\n }\n const sleepMs =\n deadline === Number.POSITIVE_INFINITY ? pollMs : Math.min(pollMs, deadline - now);\n if (sleepMs > 0) {\n await currentWaitSleep(sleepMs);\n }\n pollCount += 1;\n if (opts.beforePoll) await opts.beforePoll();\n snap = snapshot();\n if (isDone(snap)) return snap;\n }\n}\n","// mu — adoptAgent: register an existing tmux pane as a managed agent.\n//\n// The inverse of `mu agent spawn` — instead of creating the pane,\n// adoptAgent hooks an already-existing pane into mu's registry.\n//\n// Two flavours of resolution:\n// - by paneId ('%15') : look up directly via tmux\n// - by paneTitle ('worker-1') : scan tmux panes for matching title\n//\n// Common cases for adopt:\n// - operator manually started a CLI in a pane before installing mu\n// - mu agent close was followed by re-attaching the pane via tmux\n// - migrating from a previous orchestrator\n//\n// Extracted from src/agents.ts as part of refactor_split_large_src_files.\n\nimport {\n type AgentRow,\n getAgent,\n getAgentByPane,\n insertAgent,\n isValidAgentName,\n} from \"../agents.js\";\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport {\n PaneNotFoundError,\n type TmuxPane,\n listPanesInSession,\n paneExists,\n parseAgentNameFromTitle,\n setPaneTitle,\n} from \"../tmux.js\";\nimport { AgentExistsError, AgentNotInWorkstreamError } from \"./errors.js\";\n\nexport interface AdoptAgentOptions {\n /** tmux pane id (e.g. '%15'). Must already exist on the tmux server. */\n paneId: string;\n /** Workstream to adopt the pane into. The pane MUST be in the\n * matching tmux session (`mu-<workstream>`); cross-session adopt is\n * rejected. */\n workstream: string;\n /** Override the pane's title with this name. When omitted, the pane's\n * current title becomes the agent name (zero-config adoption). */\n name?: string;\n /** Defaults to 'pi' via the schema DEFAULT. */\n cli?: string;\n /** 'full-access' (default) or 'read-only'. */\n role?: string;\n /** Override the tmux session lookup. Defaults to `mu-<workstream>`. */\n tmuxSession?: string;\n}\n\nexport interface AdoptAgentResult {\n agent: AgentRow;\n /** True when the pane already had a matching agents row — the call\n * was a no-op (idempotent). */\n alreadyAdopted: boolean;\n /** The pane title before adopt, or null if the pane had no title. */\n previousTitle: string | null;\n /** The title the pane was set to (== agent.name post-adopt). Equal to\n * previousTitle when no retitle happened. */\n paneTitleSetTo: string;\n}\n\n/**\n * Register an existing tmux pane as a managed mu agent. Inverse of the\n * 'orphan' state surfaced by `mu agent list`: a pane that looks like an\n * agent (running pi/claude/codex) but has no DB row.\n *\n * Identity contract (matches the claim protocol invariant):\n * - Post-adopt, the pane's title equals the agent's name.\n * - When `name` is omitted, the pane's existing title becomes the\n * agent name verbatim. Adopting a pane titled 'pi' would fail name\n * validation — caller must supply --name in that case.\n *\n * Idempotent: adopting the same pane twice with the same name is a\n * no-op (returns alreadyAdopted=true). Adopting a different pane under\n * an existing agent name throws AgentExistsError.\n *\n * Validation order (matches the design in note #100):\n * 1. Pane id format -> assertValidPaneId via paneExists / setPaneTitle\n * 2. Pane exists -> PaneNotFoundError\n * 3. Pane is in session -> AgentNotInWorkstreamError (cross-session)\n * 4. Resolved name OK -> isValidAgentName / Error('agent name invalid')\n * 5. Idempotent check -> if pane already owned by an agent of this\n * name, return alreadyAdopted=true\n * 6. Name not taken -> AgentExistsError (else)\n * 7. Insert + retitle.\n *\n * Status starts at 'free' — reconcile/detect will update it on the next\n * `mu agent list` based on actual pane content (the pi prompt yields\n * 'free'; an agent mid-thought yields 'busy'; etc.). We don't run\n * detection inline here because the caller may not have $TMUX, and\n * adoption shouldn't depend on a captured-pane probe succeeding.\n */\nexport async function adoptAgent(db: Db, opts: AdoptAgentOptions): Promise<AdoptAgentResult> {\n // Step 1+2: pane format + existence.\n if (!(await paneExists(opts.paneId))) {\n throw new PaneNotFoundError(opts.paneId);\n }\n\n // Step 3: pane must be in the workstream's tmux session.\n const expectedSession = opts.tmuxSession ?? `mu-${opts.workstream}`;\n const panesInSession: TmuxPane[] = await listPanesInSession(expectedSession);\n const matchingPane = panesInSession.find((p) => p.paneId === opts.paneId);\n if (!matchingPane) {\n // Pane exists (passed step 2) but isn't in the expected session.\n // Synthesise the cross-session error using the same shape as the\n // existing AgentNotInWorkstreamError path so the CLI's exit-code\n // mapping handles it identically. We don't know the actual session\n // name without another tmux query; the message just says 'a\n // different session' — actionable enough.\n throw new AgentNotInWorkstreamError(\n `pane ${opts.paneId}`,\n opts.workstream,\n \"a different tmux session\",\n );\n }\n\n // Step 4: resolved name. Default to the pane's current title —\n // unwrapping a possibly-composed mu title ('name · <STATUS_EMOJI> · task')\n // back to just the name token. Re-adoption of a pane that mu previously\n // owned must work; without parseAgentNameFromTitle the ' · <glyph>'\n // suffix would fail isValidAgentName.\n const previousTitle = matchingPane.title.length > 0 ? matchingPane.title : null;\n const candidate =\n opts.name ?? (previousTitle !== null ? parseAgentNameFromTitle(previousTitle) : \"\");\n const resolvedName = candidate;\n if (!isValidAgentName(resolvedName)) {\n if (opts.name === undefined) {\n throw new Error(\n `pane ${opts.paneId} title '${previousTitle ?? \"(empty)\"}' is not a valid agent name; pass --name <name>`,\n );\n }\n throw new Error(`agent name invalid: '${resolvedName}'`);\n }\n\n // Step 5: idempotency. If an agent already owns this pane id, check\n // whether it's the same name; same name == no-op, different name ==\n // conflict (we don't move agents between pane ids silently).\n const existingByPane = getAgentByPane(db, opts.paneId);\n if (existingByPane) {\n if (existingByPane.name === resolvedName) {\n return {\n agent: existingByPane,\n alreadyAdopted: true,\n previousTitle,\n paneTitleSetTo: resolvedName,\n };\n }\n throw new AgentExistsError(existingByPane.name);\n }\n\n // Step 6: name not taken in THIS workstream. v5 makes agent names\n // per-workstream unique — 'worker-1' may legally exist in another\n // workstream; only refuse when it collides in the workstream we're\n // adopting into (bug_v5_name_clash_silent_misroute).\n const existingByName = getAgent(db, resolvedName, opts.workstream);\n if (existingByName) {\n throw new AgentExistsError(resolvedName);\n }\n\n // Step 7: insert + (conditional) retitle.\n const inserted = insertAgent(db, {\n name: resolvedName,\n workstream: opts.workstream,\n paneId: opts.paneId,\n status: \"free\",\n cli: opts.cli,\n role: opts.role,\n });\n if (resolvedName !== previousTitle) {\n await setPaneTitle(opts.paneId, resolvedName);\n }\n emitEvent(\n db,\n opts.workstream,\n `agent adopt ${resolvedName} (pane ${opts.paneId}, was title='${previousTitle ?? \"\"}')`,\n );\n return {\n agent: inserted,\n alreadyAdopted: false,\n previousTitle,\n paneTitleSetTo: resolvedName,\n };\n}\n","// mu — kickAgent: signal a wedged worker pane's foreground process\n// group from outside the pane.\n//\n// Why this verb exists: pi/claude/codex CLIs catch SIGINT themselves\n// (Ctrl-C is a UI input, not a process signal). When a worker is\n// wedged on a long-running tool subprocess (an unbounded `find /`,\n// a busy-wait loop, ...) the orchestrator's options were:\n//\n// - `mu agent send` — queues; the message is read after the tool\n// subprocess returns, which is exactly the wait we're trying to\n// shortcut.\n// - `tmux send-keys C-c` against the pane — the wrapping CLI eats\n// the signal as TUI input; doesn't reach the tool subprocess.\n// - drop out of mu, `pgrep -af \"find /\"`, `kill <pid>` — works,\n// but breaks the orchestrator's mental model and is fiddly.\n//\n// `mu agent kick` looks up the pane's TTY via tmux, asks `ps -t <tty>`\n// for the foreground process group (the pgid whose `stat` field\n// contains a `+`), and kills(2) that pgid directly. Default signal\n// is SIGINT (graceful, matches what Ctrl-C would do if it\n// propagated); --signal SIGTERM / SIGKILL escalate.\n//\n// Source feedback: workers_commonly_attempt_unbounded_find. The\n// PART A spec.\n\nimport { type AgentRow, getAgent } from \"../agents.js\";\nimport type { Db } from \"../db.js\";\nimport { emitEvent } from \"../logs.js\";\nimport type { HasNextSteps, NextStep } from \"../output.js\";\nimport { paneTTY } from \"../tmux.js\";\nimport { AgentNotFoundError } from \"./errors.js\";\n\n// ─── Allowed signals ─────────────────────────────────────────────────\n\n/** The signal set kick supports. SIGINT is graceful (matches Ctrl-C\n * semantics — what the operator probably wanted in the first place);\n * SIGTERM is the polite escalation; SIGKILL is the unblockable\n * hammer. We deliberately don't expose arbitrary signals — the\n * three above are the actionable ones for \"interrupt a wedged\n * foreground tool subprocess.\" */\nexport type KickSignal = \"SIGINT\" | \"SIGTERM\" | \"SIGKILL\";\n\nconst ALLOWED_SIGNALS: readonly KickSignal[] = [\"SIGINT\", \"SIGTERM\", \"SIGKILL\"];\n\nexport function isKickSignal(s: string): s is KickSignal {\n return (ALLOWED_SIGNALS as readonly string[]).includes(s);\n}\n\n// ─── Errors ──────────────────────────────────────────────────────────\n\n/**\n * Thrown when the foreground pgid lookup on a pane's TTY yields\n * either no rows at all (the pane is sitting at an idle shell with\n * no foreground job) or only the wrapping shell itself (the LLM CLI\n * — pi/claude/codex — is the foreground; signalling it would close\n * the agent, which is what `mu agent close` is for).\n *\n * Maps to the generic exit code 1 in handle.ts (this is a\n * runtime-state condition, not a typed not-found / conflict).\n */\nexport class NoForegroundProcessError extends Error implements HasNextSteps {\n override readonly name = \"NoForegroundProcessError\";\n constructor(\n public readonly agentName: string,\n public readonly tty: string,\n public readonly reason: \"no-foreground\" | \"shell-only\",\n ) {\n const detail =\n reason === \"no-foreground\"\n ? `no foreground process group on tty ${tty} (pane is idle)`\n : `the only foreground process on tty ${tty} is the agent's wrapping CLI itself; refusing to signal it (use \\`mu agent close ${agentName}\\` to close the agent)`;\n super(`agent ${agentName}: ${detail}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Inspect what's running in the pane\",\n command: `mu agent show ${this.agentName} -n 50`,\n },\n {\n intent: \"Close the agent (kills the wrapping CLI + pane)\",\n command: `mu agent close ${this.agentName}`,\n },\n ];\n }\n}\n\n// ─── Process executor (swappable for tests) ──────────────────────────\n//\n// Mirrors the `setTmuxExecutor` pattern in src/tmux.ts so unit tests\n// can mock `ps -t <tty>` and `kill -<sig> -<pgid>` without touching\n// real processes.\n\nexport interface KickProcessExecResult {\n stdout: string;\n stderr: string;\n exitCode: number | null;\n}\n\nexport type KickProcessExecutor = (\n cmd: string,\n args: readonly string[],\n) => Promise<KickProcessExecResult>;\n\nconst realExecutor: KickProcessExecutor = async (cmd, args) => {\n // Lazy-load execa: this module is on the cold path (kick is rare),\n // and the unit tests swap in a mock executor before any real call\n // would be made.\n const { execa } = await import(\"execa\");\n const result = await execa(cmd, [...args], { reject: false });\n return {\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n exitCode: result.exitCode ?? null,\n };\n};\n\nlet currentExecutor: KickProcessExecutor = realExecutor;\n\n/** Install a custom executor (for tests). Returns the previous one so\n * tests can restore cleanly. */\nexport function setKickProcessExecutor(executor: KickProcessExecutor): KickProcessExecutor {\n const previous = currentExecutor;\n currentExecutor = executor;\n return previous;\n}\n\n/** Restore the real executor. */\nexport function resetKickProcessExecutor(): void {\n currentExecutor = realExecutor;\n}\n\n// ─── Foreground pgid lookup ──────────────────────────────────────────\n\ninterface PsRow {\n pid: number;\n pgid: number;\n /** ps's `stat` (or `state`) field. The presence of `+` means\n * \"foreground process group on its controlling tty\". */\n stat: string;\n /** Process command (just the comm; truncated, used for diagnostics). */\n comm: string;\n}\n\n/**\n * Parse `ps -t <tty> -o pid=,pgid=,stat=,comm=` output. Each non-blank\n * line is one process: four whitespace-separated fields. Defensive\n * about leading whitespace and command names with embedded spaces\n * (the comm is the LAST field — join the tail).\n */\nexport function parsePsTtyOutput(output: string): PsRow[] {\n const rows: PsRow[] = [];\n for (const raw of output.split(\"\\n\")) {\n const line = raw.trim();\n if (line === \"\") continue;\n const parts = line.split(/\\s+/);\n if (parts.length < 4) continue;\n const [pidStr, pgidStr, stat, ...commParts] = parts;\n if (pidStr === undefined || pgidStr === undefined || stat === undefined) continue;\n const pid = Number.parseInt(pidStr, 10);\n const pgid = Number.parseInt(pgidStr, 10);\n if (!Number.isFinite(pid) || !Number.isFinite(pgid)) continue;\n rows.push({ pid, pgid, stat, comm: commParts.join(\" \") });\n }\n return rows;\n}\n\n/**\n * Resolve the foreground process group id for a TTY device path. The\n * canonical signal `ps`'s `stat` field uses is `+` (BSD/Darwin AND\n * Linux procps). We pick the first row whose stat contains `+`; its\n * `pgid` is the foreground pgid of that controlling terminal.\n *\n * Returns:\n * - `{ kind: \"ok\", pgid, fgRow }` on success\n * - `{ kind: \"no-foreground\" }` when no row carries `+` AND there\n * are no candidate rows at all\n * - `{ kind: \"shell-only\", pgid, fgRow }` when the foreground pgid\n * resolves to a shell whose comm is the agent's wrapping CLI\n * (caller decides whether to refuse — kick refuses)\n *\n * The wrapping-CLI guard is intentionally narrow: we only refuse\n * when the foreground process command matches one of the known\n * pi/claude/codex/zsh/bash shapes. Anything else (a `find`, a\n * `cargo build`, a `python script.py`) is exactly what we want to\n * signal — that's the unbounded-tool case the verb was built for.\n */\nexport interface ForegroundLookup {\n kind: \"ok\" | \"shell-only\" | \"no-foreground\";\n pgid?: number;\n fgRow?: PsRow;\n /** All rows ps returned for the tty, for diagnostics / tests. */\n rows: PsRow[];\n}\n\n/** Comm names we treat as the agent's wrapping CLI / shell. Signalling\n * these via kick would just terminate the agent itself; that's\n * `mu agent close`'s job. The match is loose (suffix on basename)\n * so paths like `/usr/local/bin/pi` and aliases like `pi-meta` match\n * the right family. */\nconst WRAPPER_COMM_PREFIXES: readonly string[] = [\n \"pi\",\n \"claude\",\n \"codex\",\n \"bash\",\n \"zsh\",\n \"sh\",\n \"fish\",\n \"dash\",\n];\n\nfunction isWrapperComm(comm: string): boolean {\n // ps -o comm= returns the basename of the executable, possibly with\n // a leading `-` (login shell). Strip both.\n const cleaned = comm.replace(/^-/, \"\").trim();\n if (cleaned === \"\") return false;\n // Match exactly OR with a `-suffix` (e.g. `pi-meta`, `bash-3.2`).\n for (const prefix of WRAPPER_COMM_PREFIXES) {\n if (cleaned === prefix) return true;\n if (cleaned.startsWith(`${prefix}-`)) return true;\n }\n return false;\n}\n\nexport async function foregroundPgid(tty: string): Promise<ForegroundLookup> {\n // Strip the `/dev/` prefix because some `ps` implementations want\n // the tty as e.g. `ttys012` rather than `/dev/ttys012`. macOS's\n // ps accepts both; Linux's procps wants the short form. Pass the\n // short form everywhere for portability.\n const ttyShort = tty.startsWith(\"/dev/\") ? tty.slice(\"/dev/\".length) : tty;\n const result = await currentExecutor(\"ps\", [\"-t\", ttyShort, \"-o\", \"pid=,pgid=,stat=,comm=\"]);\n // ps exits 1 with empty output when no process is attached to the\n // tty; treat as no-foreground rather than throw.\n if (result.exitCode !== 0 && result.stdout.trim() === \"\") {\n return { kind: \"no-foreground\", rows: [] };\n }\n const rows = parsePsTtyOutput(result.stdout);\n if (rows.length === 0) return { kind: \"no-foreground\", rows };\n // Find the foreground row: stat contains `+`.\n const fg = rows.find((r) => r.stat.includes(\"+\"));\n if (!fg) {\n // ps returned rows but none is foreground. Treat as no-foreground.\n return { kind: \"no-foreground\", rows };\n }\n if (isWrapperComm(fg.comm)) {\n return { kind: \"shell-only\", pgid: fg.pgid, fgRow: fg, rows };\n }\n return { kind: \"ok\", pgid: fg.pgid, fgRow: fg, rows };\n}\n\n// ─── kill(2) the process group ────────────────────────────────────────\n\n/**\n * Send `signal` to process group `pgid`. The `kill -SIG -<pgid>` form\n * (negative pid) targets the whole pgrp, which is what we want — a\n * single `find` invocation may have spawned helpers; we want to take\n * the whole tree down with one signal.\n */\nasync function killPgrp(pgid: number, signal: KickSignal): Promise<void> {\n const result = await currentExecutor(\"kill\", [`-${signal}`, `-${pgid}`]);\n if (result.exitCode !== 0) {\n // ESRCH (pgid already gone) is benign — the process completed\n // between our ps and our kill. Treat as success.\n if (/no such process/i.test(result.stderr)) return;\n throw new Error(\n `kill -${signal} -${pgid} failed (exit ${result.exitCode}): ${result.stderr.trim() || \"no stderr\"}`,\n );\n }\n}\n\n// ─── Public verb ──────────────────────────────────────────────────────\n\nexport interface KickAgentOptions {\n workstream: string;\n /** Defaults to SIGINT (matches Ctrl-C semantics). */\n signal?: KickSignal;\n}\n\nexport interface KickAgentResult {\n agentName: string;\n paneId: string;\n /** TTY device path the foreground pgid was resolved against. */\n tty: string;\n /** The pgid we signalled. */\n signaledPgid: number;\n signal: KickSignal;\n /** The comm of the foreground process at the time of signal — useful\n * diagnostic in the event log (\"we kicked a `find`, not a `cargo`\"). */\n foregroundComm: string;\n}\n\n/**\n * Send `signal` to the foreground process group of an agent's pane\n * TTY. Default signal is SIGINT.\n *\n * Errors:\n * - `AgentNotFoundError` — the agent doesn't exist in this workstream.\n * - `PaneNotFoundError` (from paneTTY) — the agent's pane has vanished.\n * - `NoForegroundProcessError` — pane has no foreground job, OR the\n * foreground is the wrapping CLI itself (refuse; use `mu agent close`).\n *\n * Emits an `agent kick <name> (signal=..., pgid=..., comm=...)` event\n * on success.\n */\nexport async function kickAgent(\n db: Db,\n name: string,\n opts: KickAgentOptions,\n): Promise<KickAgentResult> {\n const signal: KickSignal = opts.signal ?? \"SIGINT\";\n const agent: AgentRow | undefined = getAgent(db, name, opts.workstream);\n if (!agent) throw new AgentNotFoundError(name, opts.workstream);\n const tty = await paneTTY(agent.paneId);\n const lookup = await foregroundPgid(tty);\n if (lookup.kind === \"no-foreground\") {\n throw new NoForegroundProcessError(name, tty, \"no-foreground\");\n }\n if (lookup.kind === \"shell-only\") {\n throw new NoForegroundProcessError(name, tty, \"shell-only\");\n }\n // kind === \"ok\"\n const pgid = lookup.pgid;\n const fgRow = lookup.fgRow;\n if (pgid === undefined || fgRow === undefined) {\n // Should be unreachable given the discriminator; defensive throw\n // satisfies noUncheckedIndexedAccess + future-refactor safety.\n throw new NoForegroundProcessError(name, tty, \"no-foreground\");\n }\n await killPgrp(pgid, signal);\n emitEvent(\n db,\n agent.workstreamName,\n `agent kick ${name} (signal=${signal}, pgid=${pgid}, comm=${fgRow.comm})`,\n );\n return {\n agentName: name,\n paneId: agent.paneId,\n tty,\n signaledPgid: pgid,\n signal,\n foregroundComm: fgRow.comm,\n };\n}\n","// mu — agent registry CRUD primitives + the five high-level verbs\n// (spawn, send, read, list, close) that the CLI in step 7 will wrap.\n//\n// Layering inside this file:\n//\n// - Types & raw-row mapping (RawAgentRow / rowFromDb)\n// - CRUD primitives (insertAgent, getAgent, listAgents,\n// updateAgentStatus, deleteAgent)\n// - Verbs (spawnAgent, sendToAgent, readAgent,\n// closeAgent, listLiveAgents)\n//\n// The verbs compose the CRUD primitives with src/tmux.ts and\n// src/reconcile.ts. They are deliberately thin — each one is essentially\n// \"look up the agent, do the tmux thing, update the registry.\"\n\nimport { type Db, resolveWorkstreamId, tryResolveWorkstreamId } from \"./db.js\";\nimport type { AgentStatus } from \"./detect.js\";\nimport { emitEvent } from \"./logs.js\";\nimport { type ReconcileMode, type ReconcileReport, reconcile } from \"./reconcile.js\";\nimport { captureSnapshot } from \"./snapshots.js\";\nimport { addNote, listTasksByOwner } from \"./tasks.js\";\n// Re-export the cluster modules so external callers continue to\n// `import { AgentNotFoundError, spawnAgent, ... } from \"./agents.js\"`.\nexport {\n AgentDiedOnSpawnError,\n AgentExistsError,\n AgentNotFoundError,\n AgentNotInWorkstreamError,\n AgentSpawnCliNotFoundError,\n AgentSpawnStartupError,\n WorkspacePreservedError,\n} from \"./agents/errors.js\";\nexport {\n type CommandResolutionResult,\n type CommandResolver,\n type SpawnAgentOptions,\n checkCommandResolvable,\n defaultSpawnLivenessMs,\n envVarNameForCli,\n resetCommandResolverForTests,\n resolveCliCommand,\n resolveCliCommandWithSource,\n setCommandResolverForTests,\n spawnAgent,\n} from \"./agents/spawn.js\";\nexport {\n type AdoptAgentOptions,\n type AdoptAgentResult,\n adoptAgent,\n} from \"./agents/adopt.js\";\nexport {\n type KickAgentOptions,\n type KickAgentResult,\n type KickSignal,\n type KickProcessExecutor,\n NoForegroundProcessError,\n foregroundPgid,\n isKickSignal,\n kickAgent,\n parsePsTtyOutput,\n resetKickProcessExecutor,\n setKickProcessExecutor,\n} from \"./agents/kick.js\";\nimport { AgentNotFoundError, WorkspacePreservedError } from \"./agents/errors.js\";\nimport {\n type CaptureOptions,\n type SendOptions,\n type TmuxPane,\n capturePane,\n killPane,\n sendToPane,\n setPaneTitle,\n} from \"./tmux.js\";\nimport { freeWorkspace, getWorkspaceForAgent, isWorkspaceClean } from \"./workspace.js\";\n// (freeWorkspace is used by the spawn rollback paths below, not by closeAgent.\n// Closing an agent is intentionally a separate concern from freeing its workspace;\n// see the closeAgent docstring.)\nimport { ensureWorkstream } from \"./workstream.js\";\n\nexport type { AgentStatus };\n\nexport interface AgentRow {\n name: string;\n /** Foreign-name reference to the owning workstream. */\n workstreamName: string;\n cli: string;\n paneId: string;\n status: AgentStatus;\n role: string;\n /** Window name; null when the agent has its own window named after itself. */\n tab: string | null;\n /** ISO 8601 timestamp. */\n createdAt: string;\n /** ISO 8601 timestamp. */\n updatedAt: string;\n /**\n * Derived 'idle but assigned' flag (idle_assigned_agent_detection).\n * Set ONLY by `listLiveAgents` (and the helper `computeAgentIdle`);\n * never stored in the DB. Predicate:\n * status === 'needs_input'\n * AND owns ≥1 IN_PROGRESS task in this workstream\n * AND (now - updated_at) >= MU_IDLE_THRESHOLD_MS (default 300_000ms)\n *\n * Surfaces the third lifecycle state (alive but assigned, no recent\n * progress) to `mu state` renders + `mu state --json`. Omitted (i.e.\n * absent — NOT `false`) when the predicate doesn't fire, so JSON\n * consumers can do a simple `if (agent.idle)` check and the field\n * stays out of the way for callers that don't care.\n */\n idle?: boolean;\n}\n\n/** Default idle threshold. Matches today's `mu task wait --stuck-after`\n * default so the two paths agree on what counts as 'stalled'. */\nconst DEFAULT_IDLE_THRESHOLD_MS = 300_000;\n\n/**\n * Read the operator-tunable idle threshold (`MU_IDLE_THRESHOLD_MS`).\n * Returns the default on any unparsable / negative value rather than\n * throwing — env-var typos shouldn't crash `mu state`.\n */\nexport function idleThresholdMs(): number {\n const env = process.env.MU_IDLE_THRESHOLD_MS;\n if (env === undefined || env === \"\") return DEFAULT_IDLE_THRESHOLD_MS;\n const n = Number.parseInt(env, 10);\n if (!Number.isFinite(n) || n < 0) return DEFAULT_IDLE_THRESHOLD_MS;\n return n;\n}\n\n/**\n * Decide whether an agent is in the 'idle but assigned' state. Pure\n * read on (agents, tasks); no side effects. Exported so `listLiveAgents`,\n * the renderers, and tests can share one source of truth.\n */\nexport function computeAgentIdle(db: Db, agent: AgentRow, now: number = Date.now()): boolean {\n if (agent.status !== \"needs_input\") return false;\n const threshold = idleThresholdMs();\n if (threshold <= 0) return false;\n const updated = Date.parse(agent.updatedAt);\n if (!Number.isFinite(updated)) return false;\n if (now - updated < threshold) return false;\n const wsId = tryResolveWorkstreamId(db, agent.workstreamName);\n if (wsId === null) return false;\n const row = db\n .prepare(\n `SELECT COUNT(*) AS n\n FROM tasks t\n JOIN agents a ON a.id = t.owner_id\n WHERE a.name = ? AND a.workstream_id = ? AND t.status = 'IN_PROGRESS'`,\n )\n .get(agent.name, wsId) as { n: number };\n return row.n > 0;\n}\n\nexport interface InsertAgentInput {\n name: string;\n workstream: string;\n paneId: string;\n status: AgentStatus;\n /** Defaults to \"pi\" via schema DEFAULT. */\n cli?: string;\n /** Defaults to \"full-access\" via schema DEFAULT. */\n role?: string;\n tab?: string | null;\n}\n\ninterface RawAgentRow {\n name: string;\n /** Joined from workstreams.name. */\n workstream: string;\n cli: string;\n pane_id: string;\n status: string;\n role: string;\n tab: string | null;\n created_at: string;\n updated_at: string;\n}\n\n/** SELECT clause that joins agents to workstreams, exposing the\n * operator-facing workstream name as `workstream`. Used by every\n * read path. */\nconst SELECT_AGENT_COLS = `\n a.name AS name,\n ws.name AS workstream,\n a.cli AS cli,\n a.pane_id AS pane_id,\n a.status AS status,\n a.role AS role,\n a.tab AS tab,\n a.created_at AS created_at,\n a.updated_at AS updated_at\n`;\n\nconst AGENT_FROM_JOIN = \"FROM agents a JOIN workstreams ws ON ws.id = a.workstream_id\";\n\nfunction rowFromDb(row: RawAgentRow): AgentRow {\n return {\n name: row.name,\n workstreamName: row.workstream,\n cli: row.cli,\n paneId: row.pane_id,\n status: row.status as AgentStatus,\n role: row.role,\n tab: row.tab,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\n/** Resolve an agent's surrogate id by (workstream, name). Returns\n * null on miss. */\nfunction agentIdByName(db: Db, name: string, workstream: string): number | null {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return null;\n const row = db\n .prepare(\"SELECT id FROM agents WHERE name = ? AND workstream_id = ?\")\n .get(name, wsId) as { id: number } | undefined;\n return row ? row.id : null;\n}\n\nexport function insertAgent(db: Db, input: InsertAgentInput): AgentRow {\n // Auto-create the workstreams row if missing so the FK on\n // agents.workstream_id is always satisfied. Preserves the ergonomics\n // where you could spawn without explicit `mu init`.\n ensureWorkstream(db, input.workstream);\n const workstreamId = resolveWorkstreamId(db, input.workstream);\n const now = new Date().toISOString();\n db.prepare(\n `INSERT INTO agents (name, workstream_id, cli, pane_id, status, role, tab, created_at, updated_at)\n VALUES (@name, @workstreamId, COALESCE(@cli, 'pi'), @paneId, @status,\n COALESCE(@role, 'full-access'), @tab, @now, @now)`,\n ).run({\n name: input.name,\n workstreamId,\n cli: input.cli ?? null,\n paneId: input.paneId,\n status: input.status,\n role: input.role ?? null,\n tab: input.tab ?? null,\n now,\n });\n const row = getAgent(db, input.name, input.workstream);\n if (!row) throw new Error(`agents.insertAgent: row not found after insert: ${input.name}`);\n return row;\n}\n\n/**\n * Look up an agent by its tmux pane id (e.g. `%4`). Returns undefined if\n * no agent currently owns that pane. Used by `mu me` and friends to\n * answer \"which agent am I?\" from `$TMUX_PANE` without the LLM having to\n * remember its own name.\n *\n * Note: `pane_id` is not declared UNIQUE in the schema (a managed agent\n * could in theory be re-spawned into the same recycled pane id) but in\n * practice tmux pane ids are unique within a server's lifetime, and\n * reconcile prunes ghosts. We return the first match.\n */\nexport function getAgentByPane(db: Db, paneId: string): AgentRow | undefined {\n const row = db\n .prepare(`SELECT ${SELECT_AGENT_COLS} ${AGENT_FROM_JOIN} WHERE a.pane_id = ? LIMIT 1`)\n .get(paneId) as RawAgentRow | undefined;\n return row ? rowFromDb(row) : undefined;\n}\n\nexport function getAgent(db: Db, name: string, workstream: string): AgentRow | undefined {\n // v5: agents.name is per-workstream unique, not globally unique.\n // Workstream is required so the same name in two workstreams\n // resolves unambiguously.\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return undefined;\n const row = db\n .prepare(\n `SELECT ${SELECT_AGENT_COLS} ${AGENT_FROM_JOIN} WHERE a.name = ? AND a.workstream_id = ?`,\n )\n .get(name, wsId) as RawAgentRow | undefined;\n return row ? rowFromDb(row) : undefined;\n}\n\nexport function listAgents(db: Db, opts: { workstream?: string } = {}): AgentRow[] {\n if (opts.workstream === undefined) {\n const rows = db\n .prepare(`SELECT ${SELECT_AGENT_COLS} ${AGENT_FROM_JOIN} ORDER BY ws.name, a.name`)\n .all() as RawAgentRow[];\n return rows.map(rowFromDb);\n }\n const wsId = tryResolveWorkstreamId(db, opts.workstream);\n if (wsId === null) return [];\n const rows = db\n .prepare(\n `SELECT ${SELECT_AGENT_COLS} ${AGENT_FROM_JOIN} WHERE a.workstream_id = ? ORDER BY a.name`,\n )\n .all(wsId) as RawAgentRow[];\n return rows.map(rowFromDb);\n}\n\n/**\n * Update an agent's status. Returns true if a row was matched.\n * Also bumps updated_at. Workstream is required (v5: agents.name is\n * per-workstream unique).\n */\nexport function updateAgentStatus(\n db: Db,\n name: string,\n status: AgentStatus,\n workstream: string,\n): boolean {\n const wsId = tryResolveWorkstreamId(db, workstream);\n if (wsId === null) return false;\n const result = db\n .prepare(\"UPDATE agents SET status = ?, updated_at = ? WHERE name = ? AND workstream_id = ?\")\n .run(status, new Date().toISOString(), name, wsId);\n return result.changes > 0;\n}\n\n/**\n * Decide whether a scrollback-detected status should overwrite the\n * persisted one.\n *\n * `free` is sticky until the agent shows real activity:\n * - free + needs_input → stay free (user explicitly marked it free;\n * idle prompt isn't activity)\n * - free + busy → flip to busy\n * - free + needs_permission → flip (a permission prompt IS activity)\n *\n * Every other persisted status is auto-derived; overwrite freely. This\n * lets `spawning → busy/needs_input/needs_permission` happen on the\n * first reconcile after spawn.\n *\n * Lives on the agent (not on reconcile) because it's a property of the\n * agent's status field — both the periodic-reconcile loop and the\n * inline single-agent reconcile in `mu agent show` share this policy.\n */\nexport function shouldOverwriteAgentStatus(current: AgentStatus, detected: AgentStatus): boolean {\n if (current === \"free\") {\n return detected === \"busy\" || detected === \"needs_permission\";\n }\n return true;\n}\n\n// ─── Pane title composition (mu's interpreted state on the border) ───\n//\n// The pane border (set by enableMuPaneBorders) renders\n// `[mu] #{pane_title}` as tmux chrome. mu owns the pane title and uses\n// it to carry interpreted state at a glance. The status glyph in each\n// example below is whatever STATUS_EMOJI resolves to today (see the\n// table 30 lines down) — do NOT duplicate the codepoints in this\n// comment, they have drifted from production once already.\n//\n// worker-a (no claim, status not\n// worth surfacing yet)\n// worker-a · <STATUS_EMOJI.busy> (busy, no claim)\n// worker-a · <STATUS_EMOJI.busy> · build_x (busy, owns one task)\n// worker-a · <STATUS_EMOJI.needs_input> · build_x\n// worker-a · <STATUS_EMOJI.needs_permission> · build_x\n// worker-a · <STATUS_EMOJI.free> (free, no claim)\n// worker-a · <STATUS_EMOJI.busy> · ⊕2 tasks (multi-claim case)\n//\n// The agent name MUST remain the first ' · '-separated token so the\n// claim protocol's pane-title-as-identity fallback (currentPaneTitle\n// in src/tmux.ts) keeps working. Adopted panes that haven't been\n// re-titled by mu just have the name (one token) — still parses.\n\n/** Plain-text emoji map for the agent status. Mirrors statusIcon in\n * cli.ts but without picocolors (tmux pane titles don't render ANSI\n * colour). 'spawning' is omitted on purpose — the title gets the\n * initial render before status detection runs, and 'spawning' is a\n * transient state. */\n// Single-codepoint, single-cell-width Nerd Font glyphs (nf-fa family).\n// Picked over Unicode emoji so cli-table3's column widths line up:\n// Unicode emoji like a gear-with-variation-selector are TWO\n// codepoints, which cli-table3 counts as length-2 and uses to size\n// columns; but terminals render them as ONE cell wide, so adjacent\n// rows that mix 1-codepoint and 2-codepoint emoji misalign. Nerd Font\n// glyphs are private-use codepoints, all length-1 and all 1-cell-wide.\n//\n// Requires a Nerd Font on the operator's terminal (mu's substrate is\n// pi, which assumes Nerd Fonts; the rest of mu's TUI uses Nerd Font\n// glyphs already in cli-table3 box-drawing). Without one, every\n// glyph below renders as a placeholder box — the columns still align\n// (which was the bug we were fixing).\nexport const STATUS_EMOJI: Record<AgentStatus, string> = {\n spawning: \"\\uf251\", // nf-fa-hourglass_start\n busy: \"\\uf013\", // nf-fa-cog\n needs_input: \"\\uf186\", // nf-fa-moon_o\n needs_permission: \"\\uf023\", // nf-fa-lock\n free: \"\\uf058\", // nf-fa-check_circle\n unreachable: \"\\uf059\", // nf-fa-question_circle\n terminated: \"\\uf057\", // nf-fa-times_circle\n};\n\n/** Single rendering helper for agent status glyphs. Keep callers off\n * STATUS_EMOJI indexing so glyph fallback policy stays centralised. */\nexport function agentStatusGlyph(status: AgentStatus): string {\n return STATUS_EMOJI[status] ?? \"?\";\n}\n\n/** Maximum total length for a composed pane title. tmux truncates\n * silently in some chrome positions; we truncate the task id\n * ourselves so the suffix is predictable. */\nconst MAX_TITLE_LEN = 64;\n\n/**\n * Placeholder pane-id prefix used during the `--workspace` pre-stage in\n * spawnAgent (src/agents/spawn.ts).\n *\n * The placeholder unblocks the FK-ordering cycle:\n * - vcs_workspaces.agent FK requires an agents row\n * - agents.pane_id is NOT NULL\n * - pane creation needs the workspace path as cwd\n * So we insert the agent with a placeholder pane_id, then create the\n * workspace, then the real pane, then patch pane_id.\n *\n * Because no real tmux pane has this prefix, a naive mutating reconcile\n * pass would treat the placeholder row as a ghost and prune it\n * (→ FK-failure on the workspace insert mid-spawn). Reconcile now guards\n * against it explicitly via isPendingPaneId(), and refreshAgentTitle skips\n * placeholders for the same reason.\n *\n * Bug surfaced as bug_agent_spawn_workspace_fk_failure.\n */\nexport const PENDING_PANE_PREFIX = \"%pending-\";\n\n/** Build the placeholder pane id for an agent during workspace pre-stage. */\nexport function pendingPaneIdFor(agentName: string): string {\n return `${PENDING_PANE_PREFIX}${agentName}`;\n}\n\n/** True iff `paneId` is a `--workspace` pre-stage placeholder (not yet patched\n * to the real tmux pane id). */\nexport function isPendingPaneId(paneId: string): boolean {\n return paneId.startsWith(PENDING_PANE_PREFIX);\n}\n\n/** Build the pane title for `agent` based on current DB state.\n * Pure (no tmux side effect; no DB write). Read-only on the DB. */\nexport function composeAgentTitle(db: Db, agent: AgentRow): string {\n // 'spawning' is the initial state at row insert. Don't decorate —\n // surfaces as just the agent name until detection runs.\n const showStatus = agent.status !== \"spawning\";\n // Scope by the agent's workstream so a same-named worker in another\n // workstream can't pollute this title's task list.\n const tasks = listTasksByOwner(db, agent.workstreamName, agent.name);\n let title = agent.name;\n if (showStatus) {\n title += ` · ${agentStatusGlyph(agent.status)}`;\n }\n if (tasks.length === 1) {\n title += ` · ${tasks[0]?.name}`;\n } else if (tasks.length > 1) {\n title += ` · ⊕${tasks.length} tasks`;\n }\n if (title.length > MAX_TITLE_LEN) {\n // Truncate from the END (preserves agent name + status prefix).\n title = `${title.slice(0, MAX_TITLE_LEN - 1)}…`;\n }\n return title;\n}\n\n/** Push a fresh pane title for `agentName`. Best-effort — a missing\n * agent, a placeholder pane id, or a tmux failure are all swallowed\n * silently (titles are decorative; never block the calling verb). */\nexport async function refreshAgentTitle(\n db: Db,\n agentName: string,\n workstream: string,\n): Promise<void> {\n const agent = getAgent(db, agentName, workstream);\n if (!agent) return;\n if (isPendingPaneId(agent.paneId)) return; // workspace pre-stage placeholder; see PENDING_PANE_PREFIX\n const title = composeAgentTitle(db, agent);\n await setPaneTitle(agent.paneId, title).catch(() => {});\n}\n\n/**\n * Delete an agent row. Returns true if a row was matched. Idempotent;\n * deleting an agent that doesn't exist returns false without throwing.\n *\n * Reaper side-effect: any task that was IN_PROGRESS owned by this\n * agent gets flipped back to OPEN with a `[reaper]` task_note and a\n * `task reap` event in `agent_logs`. The FK on `tasks.owner` is\n * `ON DELETE SET NULL` so the owner column resets automatically; the\n * extra step here is the status revert. Without this an agent that\n * crashed (or was explicitly closed mid-task) leaves the task graph\n * in a wrong state — IN_PROGRESS forever, with no owner to release.\n */\nexport function deleteAgent(db: Db, name: string, workstream: string): boolean {\n // Wrap the whole reaper sequence (snapshot stuck tasks → DELETE\n // agent → per-task UPDATE + addNote + emitEvent) in a single\n // synchronous better-sqlite3 transaction. Without this, a throw\n // mid-loop (FK race after workstream destroy, addNote/emitEvent\n // regression, OOM, …) would leave the agent row deleted (FK\n // CASCADE already SET NULL on tasks.owner_id) but only PART of\n // the reaper trail written: leftover IN_PROGRESS tasks with no\n // owner and no `[reaper]` note explaining how they got there.\n // Reconcile / `mu task wait --stuck-after` would then surface\n // them as ownerless zombies with no breadcrumb.\n return db.transaction(() => {\n // Snapshot the stuck tasks BEFORE the DELETE; the FK CASCADE\n // (SET NULL on owner_id) makes the post-delete query indistinguishable\n // from \"never owned by this agent.\"\n const agentId = agentIdByName(db, name, workstream);\n if (agentId === null) {\n // Already gone — idempotent return. (Could happen if reconcile\n // pruned a ghost concurrently.) The DELETE is a no-op.\n return false;\n }\n const stuck = db\n .prepare(\n `SELECT t.id AS taskId, t.local_id AS localId, ws.name AS workstream\n FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id\n WHERE t.owner_id = ? AND t.status = 'IN_PROGRESS'`,\n )\n .all(agentId) as Array<{ taskId: number; localId: string; workstream: string }>;\n\n const result = db.prepare(\"DELETE FROM agents WHERE id = ?\").run(agentId);\n if (result.changes === 0) return false;\n\n for (const t of stuck) {\n db.prepare(\"UPDATE tasks SET status = 'OPEN', updated_at = ? WHERE id = ?\").run(\n new Date().toISOString(),\n t.taskId,\n );\n addNote(\n db,\n t.localId,\n `[reaper] previous owner ${name} gone (agent removed); status reverted IN_PROGRESS → OPEN, owner cleared`,\n { author: \"reaper\", workstream: t.workstream },\n );\n emitEvent(\n db,\n t.workstream,\n `task reap ${t.localId} (previous owner ${name} gone, IN_PROGRESS → OPEN)`,\n );\n }\n return true;\n })();\n}\n\n// ────────────────────────────────────────────────────────────────────────\n// High-level verbs (spawn, send, read, list, close)\n// ────────────────────────────────────────────────────────────────────────\n\n/** Allowed agent name shape: lowercase alpha first, then alnum/underscore/\n * hyphen. Mirrors VOCABULARY.md §\"Naming conventions\". */\nconst AGENT_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;\n\nexport function isValidAgentName(name: string): boolean {\n return AGENT_NAME_RE.test(name);\n}\n\n/**\n * Send a single line of text to an agent's pane and submit it. Uses the\n * canonical bracketed-paste protocol from src/tmux.ts.\n */\nexport async function sendToAgent(\n db: Db,\n name: string,\n text: string,\n opts: SendOptions & { workstream: string },\n): Promise<void> {\n const agent = getAgent(db, name, opts.workstream);\n if (!agent) throw new AgentNotFoundError(name);\n await sendToPane(agent.paneId, text, opts);\n}\n\n/**\n * Read scrollback from an agent's pane. With no options, returns the full\n * scrollback (`-S - -E -`); with `lines: N`, returns only the last N lines.\n */\nexport async function readAgent(\n db: Db,\n name: string,\n opts: CaptureOptions & { workstream: string },\n): Promise<string> {\n const agent = getAgent(db, name, opts.workstream);\n if (!agent) throw new AgentNotFoundError(name);\n return capturePane(agent.paneId, opts);\n}\n\n// ─── freeAgent (verb) ─────────────────────────────────────────────────────\n\nexport interface FreeAgentResult {\n /** Status before the call. */\n previousStatus: AgentStatus;\n /** Status after the call (always 'free' on success). */\n status: AgentStatus;\n /** True iff the row actually changed. False on idempotent no-op. */\n changed: boolean;\n}\n\n/**\n * Mark an agent's status as `free` — the explicit \"I'm done with you\n * for now; you're available\" signal. The agent's pane and DB row are\n * untouched; reconcile treats `free` as sticky (only flips back to busy\n * on real activity, never on an idle prompt) so this verb composes\n * cleanly with the existing scrollback detector.\n *\n * Idempotent: setting an already-free agent to free is a no-op (returns\n * `changed: false`). Throws AgentNotFoundError on missing.\n */\nexport function freeAgent(db: Db, name: string, workstream: string): FreeAgentResult {\n const before = getAgent(db, name, workstream);\n if (!before) throw new AgentNotFoundError(name);\n if (before.status === \"free\") {\n return { previousStatus: before.status, status: \"free\", changed: false };\n }\n updateAgentStatus(db, name, \"free\", before.workstreamName);\n emitEvent(db, before.workstreamName, `agent free ${name} (was ${before.status})`);\n return { previousStatus: before.status, status: \"free\", changed: true };\n}\n\nexport interface CloseAgentOptions {\n /**\n * Lossy override: when true, free the agent's workspace BEFORE\n * deleting the agent regardless of whether it's clean. (We control\n * the order rather than relying on FK cascade, which leaves the\n * on-disk dir orphaned.) Any pending changes / commits since fork\n * are gone unless the caller frees with `--commit` separately first.\n *\n * When false (default), behaviour depends on workspace state:\n * - clean (no uncommitted changes AND no commits since fork):\n * silently auto-free. allow_mu_agent_close_without_discard.\n * - dirty (uncommitted changes OR commits since fork): throw\n * WorkspacePreservedError so the caller decides explicitly.\n * Surfaced as a real bug in the multi-agent dogfood teardown.\n */\n discardWorkspace?: boolean;\n}\n\nexport interface CloseAgentResult {\n killedPane: boolean;\n deletedRow: boolean;\n /** True iff the agent had an associated workspace AND we proactively\n * freed it — either because the caller passed `discardWorkspace:\n * true` (lossy) or because the workspace was clean and we\n * auto-freed (allow_mu_agent_close_without_discard). False on the\n * no-workspace path (nothing to free) and on the refused path (we\n * threw before doing anything). */\n workspaceFreed: boolean;\n /** True iff `workspaceFreed` was triggered by the clean-workspace\n * auto-free path (no uncommitted changes AND no commits since\n * fork) rather than the explicit `discardWorkspace: true` override.\n * Lets the CLI render an accurate message (\"auto-freed (clean)\"\n * vs \"workspace discarded\") and gives JSON consumers a stable\n * signal. False on every other path. */\n workspaceAutoFreedClean: boolean;\n}\n\n/**\n * Close an agent: kill its tmux pane and remove its DB row. Idempotent:\n * - if the agent doesn't exist in the DB, returns a no-op result\n * - if the tmux pane is already gone, killPane swallows the error\n *\n * Workspace handling: closing an agent and freeing its workspace are\n * separate concerns (agent lifecycle vs disk artifacts). Three cases:\n *\n * - No workspace: close proceeds normally.\n * - Workspace exists AND is CLEAN (no uncommitted changes, no\n * commits since fork): silently auto-free (so a workspace that\n * contains nothing worth preserving doesn't make the operator\n * type --discard-workspace just to clean it up). Surfaced by\n * allow_mu_agent_close_without_discard — a misconfigured-spawn\n * teardown was needlessly forced through the lossy flag.\n * - Workspace exists AND has either uncommitted changes OR commits\n * since fork: REFUSE with WorkspacePreservedError so the operator\n * decides explicitly. Two resolutions:\n * 1. `freeWorkspace(db, name)` first, then `closeAgent(db, name)`.\n * Preserves the option to `--commit` pending changes.\n * 2. `closeAgent(db, name, { discardWorkspace: true })`.\n * One-shot; lossy.\n *\n * The CLI surfaces these as the two actionable nextSteps on the\n * `WorkspacePreservedError` thrown by the refuse path.\n */\nexport async function closeAgent(\n db: Db,\n name: string,\n opts: CloseAgentOptions & { workstream: string },\n): Promise<CloseAgentResult> {\n const agent = getAgent(db, name, opts.workstream);\n if (!agent) {\n return {\n killedPane: false,\n deletedRow: false,\n workspaceFreed: false,\n workspaceAutoFreedClean: false,\n };\n }\n const ws = getWorkspaceForAgent(db, name, agent.workstreamName);\n // allow_mu_agent_close_without_discard: silently auto-free a clean\n // workspace (no uncommitted changes AND no commits since fork) so\n // the user doesn't have to type --discard-workspace for a workspace\n // that contains nothing worth preserving. Only refuse when there's\n // actually something to lose. The flag stays as the lossy override\n // for non-clean workspaces.\n let autoFreeClean = false;\n if (ws !== undefined && opts.discardWorkspace !== true) {\n autoFreeClean = await isWorkspaceClean(ws);\n if (!autoFreeClean) {\n throw new WorkspacePreservedError(name, ws.path);\n }\n }\n // Pre-mutation snapshot (snap_design §CAPTURE STRATEGY > WHEN).\n // Captures the agent row + the FK SET NULL ripple onto tasks.owner +\n // (when --discard-workspace or auto-free) the vcs_workspaces row.\n // Workstream is recorded so this snapshot is filterable in `mu\n // snapshot list`.\n captureSnapshot(db, `agent close ${name}`, agent.workstreamName);\n // Free the workspace BEFORE the agent (so the on-disk dir is\n // removed cleanly, not orphaned by FK cascade). freeWorkspace is\n // idempotent on missing rows.\n let workspaceFreed = false;\n if (ws !== undefined && (opts.discardWorkspace === true || autoFreeClean)) {\n await freeWorkspace(db, name, { commit: false, workstream: agent.workstreamName });\n workspaceFreed = true;\n }\n await killPane(agent.paneId).catch(() => {\n /* idempotent — pane may already be gone */\n });\n const deletedRow = deleteAgent(db, name, agent.workstreamName);\n emitEvent(\n db,\n agent.workstreamName,\n `agent close ${name} (pane=${agent.paneId}${\n workspaceFreed\n ? autoFreeClean\n ? \", workspace auto-freed (clean)\"\n : \", workspace discarded\"\n : \"\"\n })`,\n );\n return {\n killedPane: true,\n deletedRow,\n workspaceFreed,\n workspaceAutoFreedClean: workspaceFreed && autoFreeClean,\n };\n}\n\nexport interface ListLiveAgentsOptions {\n workstream: string;\n tmuxSession?: string;\n /**\n * Which kind of reconciliation pass to run. Forwarded to\n * `reconcile()`'s same-name option. Default `\"full\"` (the\n * documented mutating behaviour `mu agent list` has always had,\n * now also used by `mu state` and `mu agent attach`).\n *\n * `mu doctor` and `mu undo` pass `\"report-only\"`: count drift,\n * mutate nothing. `mu undo` MUST use this so a post-restore\n * reconcile doesn't delete the rows the snapshot just restored\n * (snap_undo_reconcile_destroys_recovered_agents).\n *\n * Mid-spawn placeholders (pane id `%pending-<name>`) are protected\n * directly in reconcile's prune loop, independent of mode\n * (bug_agent_spawn_workspace_fk_failure).\n *\n * BREAKING: this replaces the previous `dryRun?: boolean`\n * option. Migration: `dryRun: true` → `mode: \"report-only\"`;\n * default (`dryRun: false` / unset) → `mode: \"full\"`.\n */\n mode?: ReconcileMode;\n}\n\nexport interface LiveAgentsView {\n /** All registered agents in the workstream, post-reconcile. */\n agents: AgentRow[];\n /** Panes in the tmux session that look like agents but aren't registered. */\n orphans: TmuxPane[];\n /** Diagnostic numbers from the reconcile pass; useful for `mu doctor`. */\n report: ReconcileReport;\n}\n\n/**\n * Return the live, reality-reconciled view of agents in a workstream.\n * `mu state`, `mu agent list`, and `mu agent attach` call this with the\n * default `mode: \"full\"` (mutating); read-only diagnostic / restore paths\n * (`mu doctor`, `mu undo`) call it with `mode: \"report-only\"` to mutate\n * nothing at all.\n */\nexport async function listLiveAgents(db: Db, opts: ListLiveAgentsOptions): Promise<LiveAgentsView> {\n const report = await reconcile(db, {\n workstream: opts.workstream,\n ...(opts.tmuxSession !== undefined ? { tmuxSession: opts.tmuxSession } : {}),\n ...(opts.mode !== undefined ? { mode: opts.mode } : {}),\n });\n const baseAgents = listAgents(db, { workstream: opts.workstream });\n // Enrich with the derived `idle` flag (idle_assigned_agent_detection).\n // One COUNT per agent — cheap; the agents table in any one workstream\n // is small (typical wave: <10 rows). We add the field only when\n // idle=true, so non-idle rows JSON-serialize without the noise.\n const now = Date.now();\n const agents: AgentRow[] = baseAgents.map((a) =>\n computeAgentIdle(db, a, now) ? { ...a, idle: true } : a,\n );\n return { agents, orphans: report.orphans, report };\n}\n","// mu — task-DAG read + ASCII forest rendering helpers.\n//\n// Shared by the static `mu task tree` command and the read-only TUI DAG\n// popup. Pure rendering lives here so the box-drawing characters and\n// diamond-collapse semantics have one implementation.\n\nimport pc from \"picocolors\";\nimport type { Db } from \"./db.js\";\nimport { type TaskRow, getTask, listTasks } from \"./tasks.js\";\nimport type { TaskStatus } from \"./tasks/status.js\";\n\n// One-line marker appended to a tree node when its subtree was already\n// rendered earlier in the forest (DAG diamond collapse). Symbol-only\n// + dimmed: the ↻ glyph carries the recurrence semantics, the dim\n// keeps it from competing with the task title for the eye.\nconst RECURRENCE_MARKER = ` ${pc.dim(\"(↻)\")}`;\n\nexport interface FullDag {\n /** Root tasks: no incoming `blocks` edge (no blockers). */\n roots: TaskRow[];\n /** Edges map parent task name → child task names (what parent blocks). */\n edges: Map<string, string[]>;\n /** All tasks in the workstream, keyed by operator-facing name. */\n tasks: Map<string, TaskRow>;\n}\n\nexport type TaskStatusLabelFn = (task: TaskRow) => string;\n\nexport interface RenderTreeOptions {\n /** Include the task title after the name + status label. Default: true. */\n includeTitle?: boolean;\n}\n\nexport interface LoadFullDagOptions {\n /** Optional visible-status filter. Omitted = every task status. */\n statuses?: ReadonlySet<TaskStatus>;\n}\n\nexport function loadFullDag(db: Db, workstream: string, opts: LoadFullDagOptions = {}): FullDag {\n const tasks = listTasks(db, workstream).filter(\n (t) => opts.statuses === undefined || opts.statuses.has(t.status),\n );\n const byName = new Map(tasks.map((t) => [t.name, t]));\n const incoming = new Set<string>();\n const edges = new Map<string, string[]>();\n\n for (const task of tasks) {\n edges.set(task.name, []);\n }\n\n const rows = db\n .prepare(\n `SELECT src.local_id AS parent, dst.local_id AS child\n FROM task_edges e\n JOIN tasks src ON src.id = e.from_task_id\n JOIN tasks dst ON dst.id = e.to_task_id\n JOIN workstreams ws ON ws.id = src.workstream_id\n WHERE ws.name = ?\n AND dst.workstream_id = src.workstream_id\n ORDER BY src.local_id, dst.local_id`,\n )\n .all(workstream) as { parent: string; child: string }[];\n\n for (const row of rows) {\n if (!byName.has(row.parent) || !byName.has(row.child)) continue;\n incoming.add(row.child);\n const children = edges.get(row.parent) ?? [];\n children.push(row.child);\n edges.set(row.parent, children);\n }\n\n const roots = tasks.filter((t) => !incoming.has(t.name));\n return { roots, edges, tasks: byName };\n}\n\n/**\n * Render a DAG forest in the same ASCII shape as `mu task tree --down`:\n * each root is printed as a header node, dependents are below it, and\n * DAG diamonds collapse after the first full subtree render with a\n * one-line recurrence marker.\n */\nexport function renderForest(\n roots: readonly TaskRow[],\n edges: ReadonlyMap<string, readonly string[]>,\n statusFn: TaskStatusLabelFn,\n tasksByName?: ReadonlyMap<string, TaskRow>,\n opts: RenderTreeOptions = {},\n): string {\n const byName = new Map(tasksByName ?? roots.map((t) => [t.name, t]));\n const seen = new Set<string>();\n const sections: string[] = [];\n\n for (const root of roots) {\n if (!byName.has(root.name)) byName.set(root.name, root);\n const lines = [formatTreeNodeLabel(root, statusFn, opts)];\n if (seen.has(root.name)) {\n lines[0] = `${lines[0]}${RECURRENCE_MARKER}`;\n } else {\n seen.add(root.name);\n renderForestChildren(root.name, \"\", edges, byName, statusFn, seen, lines, opts);\n }\n sections.push(lines.join(\"\\n\"));\n }\n\n return sections.join(\"\\n\\n\");\n}\n\nexport function renderTaskTree(\n db: Db,\n workstream: string,\n root: TaskRow,\n direction: \"blockers\" | \"dependents\",\n statusFn: TaskStatusLabelFn,\n opts: RenderTreeOptions = {},\n): string {\n const edges = new Map<string, string[]>();\n const byName = new Map<string, TaskRow>([[root.name, root]]);\n const visited = new Set<string>();\n collectTreeEdges(db, workstream, root.name, direction, edges, byName, visited);\n return renderForest([root], edges, statusFn, byName, opts);\n}\n\nfunction collectTreeEdges(\n db: Db,\n workstream: string,\n taskName: string,\n direction: \"blockers\" | \"dependents\",\n edges: Map<string, string[]>,\n byName: Map<string, TaskRow>,\n visited: Set<string>,\n): void {\n if (visited.has(taskName)) return;\n visited.add(taskName);\n const rows = db\n .prepare(\n direction === \"dependents\"\n ? `SELECT child.local_id AS name\n FROM task_edges e\n JOIN tasks parent ON parent.id = e.from_task_id\n JOIN tasks child ON child.id = e.to_task_id\n JOIN workstreams ws ON ws.id = parent.workstream_id\n WHERE ws.name = ? AND parent.local_id = ?\n ORDER BY child.local_id`\n : `SELECT parent.local_id AS name\n FROM task_edges e\n JOIN tasks parent ON parent.id = e.from_task_id\n JOIN tasks child ON child.id = e.to_task_id\n JOIN workstreams ws ON ws.id = child.workstream_id\n WHERE ws.name = ? AND child.local_id = ?\n ORDER BY parent.local_id`,\n )\n .all(workstream, taskName) as { name: string }[];\n const children = rows.map((r) => r.name);\n edges.set(taskName, children);\n\n for (const childName of children) {\n if (!byName.has(childName)) {\n const child = getTask(db, childName, workstream);\n if (child) byName.set(childName, child);\n }\n collectTreeEdges(db, workstream, childName, direction, edges, byName, visited);\n }\n}\n\nfunction renderForestChildren(\n taskName: string,\n prefix: string,\n edges: ReadonlyMap<string, readonly string[]>,\n byName: Map<string, TaskRow>,\n statusFn: TaskStatusLabelFn,\n seen: Set<string>,\n lines: string[],\n opts: RenderTreeOptions,\n): void {\n const children = edges.get(taskName) ?? [];\n for (let i = 0; i < children.length; i++) {\n const childName = children[i];\n if (childName === undefined) continue;\n const isLast = i === children.length - 1;\n const branch = isLast ? \"└── \" : \"├── \";\n const childPrefix = prefix + (isLast ? \" \" : \"│ \");\n const child = byName.get(childName);\n\n if (!child) {\n lines.push(`${prefix}${branch}${childName} (missing!)`);\n continue;\n }\n\n if (seen.has(childName)) {\n lines.push(\n `${prefix}${branch}${formatTreeNodeLabel(child, statusFn, opts)}${RECURRENCE_MARKER}`,\n );\n continue;\n }\n\n lines.push(`${prefix}${branch}${formatTreeNodeLabel(child, statusFn, opts)}`);\n seen.add(childName);\n renderForestChildren(childName, childPrefix, edges, byName, statusFn, seen, lines, opts);\n }\n}\n\nexport function formatTreeNodeLabel(\n t: TaskRow,\n statusFn: TaskStatusLabelFn,\n opts: RenderTreeOptions = {},\n): string {\n const base = `${t.name} ${statusFn(t)}`;\n if (opts.includeTitle === false) return base;\n return `${base} ${t.title}`;\n}\n","// mu — whole-DB export/import sync SDK.\n//\n// Export is a SQLite VACUUM INTO copy plus a tiny manifest. Import is\n// deliberately sharp: classify each workstream, refuse drift by default,\n// and only clobber with --force-source after parking the local loser.\n\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { CURRENT_SCHEMA_VERSION, type Db, defaultStateDir, openDb } from \"./db.js\";\nimport { emitEvent, latestSeq } from \"./logs.js\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\nimport { captureSnapshot } from \"./snapshots.js\";\n\nexport interface DbExportManifestWorkstream {\n name: string;\n tasks: number;\n edges: number;\n notes: number;\n latestSeq: number;\n}\n\nexport interface DbExportManifest {\n muVersion: string;\n schemaVersion: number;\n machineId: string;\n hostname: string | null;\n exportedAt: string;\n workstreams: DbExportManifestWorkstream[];\n}\n\nexport interface ExportDbOptions {\n force?: boolean;\n}\n\nexport interface ExportDbResult {\n file: string;\n manifestPath: string;\n manifest: DbExportManifest;\n overwritten: boolean;\n}\n\nexport class DbExportTargetExistsError extends Error implements HasNextSteps {\n override readonly name = \"DbExportTargetExistsError\";\n constructor(public readonly file: string) {\n super(`DB export target already exists: ${file}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Choose a different target\", command: \"mu db export <new-file>\" },\n { intent: \"Overwrite this target\", command: `mu db export ${shellQuote(this.file)} --force` },\n ];\n }\n}\n\nexport class DbImportManifestMissingError extends Error implements HasNextSteps {\n override readonly name = \"DbImportManifestMissingError\";\n constructor(public readonly manifestPath: string) {\n super(`DB import manifest not found: ${manifestPath}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Export the DB with its sidecar\", command: \"mu db export /tmp/mu.db --force\" },\n { intent: \"Copy the sidecar too\", command: `scp <host>:${shellQuote(this.manifestPath)} .` },\n ];\n }\n}\n\nexport class DbImportSchemaTooOldError extends Error implements HasNextSteps {\n override readonly name = \"DbImportSchemaTooOldError\";\n constructor(public readonly sourceVersion: number) {\n super(\n `source DB schema v${sourceVersion} is older than local mu requires (v${CURRENT_SCHEMA_VERSION})`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Upgrade mu on the source machine\",\n command: \"npm run build && mu db export <file> --force\",\n },\n { intent: \"Then retry this import\", command: \"mu db import <file> --apply\" },\n ];\n }\n}\n\nexport class DbImportSchemaTooNewError extends Error implements HasNextSteps {\n override readonly name = \"DbImportSchemaTooNewError\";\n constructor(public readonly sourceVersion: number) {\n super(\n `source DB schema v${sourceVersion} is newer than this mu supports (v${CURRENT_SCHEMA_VERSION}); upgrade local mu`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Upgrade local mu\", command: \"git pull && npm install && npm run build\" },\n { intent: \"Then retry this import\", command: \"mu db import <file> --apply\" },\n ];\n }\n}\n\nexport class DbImportSourceStaleError extends Error implements HasNextSteps {\n override readonly name = \"DbImportSourceStaleError\";\n constructor(public readonly workstreams: readonly string[]) {\n super(`source DB is stale for local-ahead workstream(s): ${workstreams.join(\", \")}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Re-export from this machine\", command: \"mu db export /tmp/mu-fresh.db --force\" },\n { intent: \"Dry-run the incoming file first\", command: \"mu db import <file>\" },\n ];\n }\n}\n\nexport class DbImportConflictError extends Error implements HasNextSteps {\n override readonly name = \"DbImportConflictError\";\n constructor(public readonly workstreams: readonly string[]) {\n super(`source and local both advanced for workstream(s): ${workstreams.join(\", \")}`);\n }\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"Preview the conflicting workstreams\", command: \"mu db import <file> --json\" },\n {\n intent: \"Clobber from source after parking local divergence\",\n command: \"mu db import <file> --apply --force-source\",\n },\n ];\n }\n}\n\nexport type DbImportDecision =\n | \"IDENTICAL\"\n | \"FAST_FORWARD\"\n | \"LOCAL_AHEAD\"\n | \"CONFLICT\"\n | \"IMPORT\"\n | \"LEAVE_ALONE\";\n\nexport interface DbImportSummaryItem {\n workstream: string;\n decision: DbImportDecision;\n delta: Record<string, unknown>;\n needs?: string;\n parkPath?: string;\n}\n\nexport interface ImportDbOptions {\n apply?: boolean;\n forceSource?: boolean;\n onlyWorkstreams?: readonly string[];\n}\n\nexport interface ImportDbResult {\n machineId: string;\n sourceFile: string;\n dryRun: boolean;\n applied: boolean;\n snapshotId?: number;\n summary: DbImportSummaryItem[];\n}\n\ninterface MachineIdentityRow {\n machine_id: string;\n hostname: string | null;\n created_at?: string;\n}\n\ninterface WorkstreamIdRow {\n id: number;\n name: string;\n}\n\ninterface WorkstreamRow {\n id: number;\n name: string;\n created_at: string;\n}\n\ninterface TaskCopyRow {\n local_id: string;\n title: string;\n status: string;\n impact: number;\n effort_days: number;\n owner_name: string | null;\n created_at: string;\n updated_at: string;\n}\n\ninterface EdgeCopyRow {\n from_local_id: string;\n to_local_id: string;\n created_at: string;\n}\n\ninterface NoteCopyRow {\n task_local_id: string;\n author: string | null;\n content: string;\n created_at: string;\n}\n\ninterface LogCopyRow {\n seq: number;\n source: string;\n kind: string;\n payload: string;\n created_at: string;\n}\n\ninterface AgentCopyRow {\n name: string;\n cli: string;\n pane_id: string;\n status: string;\n role: string;\n tab: string | null;\n created_at: string;\n updated_at: string;\n}\n\ninterface WorkspaceCopyRow {\n agent_name: string;\n backend: string;\n path: string;\n parent_ref: string | null;\n created_at: string;\n}\n\ninterface CopyWorkstreamOptions {\n includeMachineLocalRows: boolean;\n preserveLogSeq: boolean;\n includeSync: boolean;\n}\n\nexport function exportDb(db: Db, file: string, opts: ExportDbOptions = {}): ExportDbResult {\n const target = file;\n const manifestPath = `${target}.manifest.json`;\n const targetExists = existsSync(target);\n if (targetExists && opts.force !== true) throw new DbExportTargetExistsError(target);\n\n // Emit one `db export` event per included workstream BEFORE\n // VACUUM INTO + manifest capture. Two reasons:\n // 1. The exported file should contain the events themselves so the\n // parked-detection heuristic (src/parked.ts) works against the\n // exported DB if anyone ever opens it directly.\n // 2. Capturing the manifest AFTER the events means re-importing the\n // export back into the source DB classifies as IDENTICAL (manifest\n // seq == local seq); without this, the post-export seq bump would\n // trip the LOCAL_AHEAD branch in buildImportPlan.\n // The seq token in the payload is for human readers; nothing parses\n // it. The event verb-prefix is `db export` (registered in src/logs.ts).\n const preEventManifest = buildExportManifest(db);\n for (const ws of preEventManifest.workstreams) {\n emitEvent(db, ws.name, `db export ${ws.name} seq=${ws.latestSeq}`);\n }\n\n const manifest = buildExportManifest(db);\n mkdirSync(dirname(target), { recursive: true });\n try {\n if (targetExists) unlinkSync(target);\n db.exec(`VACUUM INTO ${quoteSqlString(target)}`);\n writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`, \"utf8\");\n } catch (err) {\n try {\n if (existsSync(target)) unlinkSync(target);\n } catch {\n // Ignore — preserve the original export failure.\n }\n throw err;\n }\n\n return { file: target, manifestPath, manifest, overwritten: targetExists };\n}\n\nexport function importDb(db: Db, file: string, opts: ImportDbOptions = {}): ImportDbResult {\n const manifest = readImportManifest(file);\n assertImportSchemaCompatible(manifest.schemaVersion);\n\n const sourceDb = openDb({ path: file, readonly: true });\n try {\n const summary = buildImportPlan(db, manifest, file, opts.onlyWorkstreams);\n if (opts.apply !== true) {\n return {\n machineId: manifest.machineId,\n sourceFile: file,\n dryRun: true,\n applied: false,\n summary,\n };\n }\n\n const stale = summary.filter((s) => s.decision === \"LOCAL_AHEAD\").map((s) => s.workstream);\n if (stale.length > 0) throw new DbImportSourceStaleError(stale);\n const conflicts = summary.filter((s) => s.decision === \"CONFLICT\").map((s) => s.workstream);\n if (conflicts.length > 0 && opts.forceSource !== true)\n throw new DbImportConflictError(conflicts);\n\n const mutating = summary.some((s) => shouldReplace(s.decision, opts.forceSource === true));\n const snapshot = mutating ? captureSnapshot(db, `db import ${file}`, null) : undefined;\n\n for (const item of summary) {\n if (!shouldReplace(item.decision, opts.forceSource === true)) continue;\n if (item.decision === \"CONFLICT\") {\n item.parkPath = parkLocalWorkstream(db, item.workstream);\n }\n const sourceWs = manifest.workstreams.find((w) => w.name === item.workstream);\n const sourceSeq = sourceWs?.latestSeq ?? 0;\n replaceWorkstreamFromSource(db, sourceDb, item.workstream, manifest.machineId, sourceSeq);\n }\n\n return {\n machineId: manifest.machineId,\n sourceFile: file,\n dryRun: false,\n applied: true,\n ...(snapshot ? { snapshotId: snapshot.id } : {}),\n summary,\n };\n } finally {\n sourceDb.close();\n }\n}\n\nexport function buildImportPlan(\n localDb: Db,\n manifest: DbExportManifest,\n sourceFile: string,\n onlyWorkstreams?: readonly string[],\n): DbImportSummaryItem[] {\n const sourceByName = new Map(manifest.workstreams.map((w) => [w.name, w]));\n const localByName = new Map(listLocalWorkstreams(localDb).map((w) => [w.name, w]));\n const localMachineId = getMachineIdentity(localDb)?.machine_id ?? \"\";\n const only = normaliseOnlyWorkstreams(onlyWorkstreams);\n const names = Array.from(new Set([...sourceByName.keys(), ...localByName.keys()]))\n .filter((name) => only.size === 0 || only.has(name))\n .sort();\n\n return names.map((name) => {\n const source = sourceByName.get(name);\n const local = localByName.get(name);\n const sourceSeq = source?.latestSeq ?? 0;\n const localSeq = local ? latestSeq(localDb, local.id) : 0;\n const synced =\n source !== undefined && local !== undefined && manifest.machineId === localMachineId\n ? { sourceSeq: Math.min(sourceSeq, localSeq), localSeq: Math.min(sourceSeq, localSeq) }\n : local\n ? lastKnownPeerSync(localDb, local.id, manifest.machineId)\n : { sourceSeq: 0, localSeq: 0 };\n\n const decision = classifyWorkstream({\n hasSource: source !== undefined,\n hasLocal: local !== undefined,\n sourceSeq,\n localSeq,\n syncedSourceSeq: synced.sourceSeq,\n syncedLocalSeq: synced.localSeq,\n });\n return {\n workstream: name,\n decision,\n delta: {\n sourceFile,\n sourceSeq,\n localSeq,\n lastSynced: synced.sourceSeq,\n localSynced: synced.localSeq,\n source: source ? countsFromManifest(source) : null,\n local: local ? countWorkstream(localDb, local.id) : null,\n },\n ...(decision === \"LOCAL_AHEAD\" ? { needs: \"re-export from this machine\" } : {}),\n ...(decision === \"CONFLICT\" ? { needs: \"--force-source\" } : {}),\n };\n });\n}\n\nfunction classifyWorkstream(opts: {\n hasSource: boolean;\n hasLocal: boolean;\n sourceSeq: number;\n localSeq: number;\n syncedSourceSeq: number;\n syncedLocalSeq: number;\n}): DbImportDecision {\n if (opts.hasSource && !opts.hasLocal) return \"IMPORT\";\n if (!opts.hasSource && opts.hasLocal)\n return opts.syncedSourceSeq > 0 || opts.syncedLocalSeq > 0 ? \"LOCAL_AHEAD\" : \"LEAVE_ALONE\";\n if (!opts.hasSource && !opts.hasLocal) return \"IDENTICAL\";\n\n const sourceAdvanced = opts.sourceSeq > opts.syncedSourceSeq;\n const localAdvanced = opts.localSeq > opts.syncedLocalSeq;\n if (!sourceAdvanced && !localAdvanced) return \"IDENTICAL\";\n if (sourceAdvanced && !localAdvanced) return \"FAST_FORWARD\";\n if (!sourceAdvanced && localAdvanced) return \"LOCAL_AHEAD\";\n return \"CONFLICT\";\n}\n\nfunction shouldReplace(decision: DbImportDecision, forceSource: boolean): boolean {\n return (\n decision === \"FAST_FORWARD\" || decision === \"IMPORT\" || (decision === \"CONFLICT\" && forceSource)\n );\n}\n\nfunction replaceWorkstreamFromSource(\n localDb: Db,\n sourceDb: Db,\n workstream: string,\n sourceMachineId: string,\n sourceSeq: number,\n): void {\n localDb.transaction(() => {\n const existing = localDb.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as\n | { id: number }\n | undefined;\n if (existing) {\n localDb.prepare(\"DELETE FROM vcs_workspaces WHERE workstream_id = ?\").run(existing.id);\n localDb.prepare(\"DELETE FROM agents WHERE workstream_id = ?\").run(existing.id);\n localDb.prepare(\"DELETE FROM workstreams WHERE id = ?\").run(existing.id);\n }\n copyWorkstreamRows(sourceDb, localDb, workstream, {\n includeMachineLocalRows: false,\n preserveLogSeq: false,\n includeSync: false,\n });\n const wsId = (\n localDb.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as\n | { id: number }\n | undefined\n )?.id;\n if (wsId === undefined) throw new Error(`importDb: failed to import workstream ${workstream}`);\n writeSyncState(localDb, wsId, sourceMachineId, sourceSeq);\n })();\n}\n\nfunction parkLocalWorkstream(db: Db, workstream: string): string {\n const dir = join(defaultStateDir(), \"divergence\");\n mkdirSync(dir, { recursive: true });\n const path = join(\n dir,\n `${workstream}-${new Date().toISOString()}-${randomUUID().slice(0, 8)}.db`,\n );\n const parkDb = openDb({ path });\n try {\n const identity = getMachineIdentity(db);\n if (identity) {\n parkDb\n .prepare(\n `UPDATE machine_identity\n SET machine_id = ?, hostname = ?, created_at = ?\n WHERE id = 1`,\n )\n .run(\n identity.machine_id,\n identity.hostname,\n identity.created_at ?? new Date().toISOString(),\n );\n }\n copyWorkstreamRows(db, parkDb, workstream, {\n includeMachineLocalRows: true,\n preserveLogSeq: true,\n includeSync: true,\n });\n } catch (err) {\n try {\n parkDb.close();\n } catch {\n // keep original error\n }\n try {\n if (existsSync(path)) unlinkSync(path);\n } catch {\n // keep original error\n }\n throw err;\n }\n parkDb.close();\n return path;\n}\n\nfunction copyWorkstreamRows(\n sourceDb: Db,\n targetDb: Db,\n workstream: string,\n opts: CopyWorkstreamOptions,\n): void {\n const sourceWs = sourceDb\n .prepare(\"SELECT id, name, created_at FROM workstreams WHERE name = ?\")\n .get(workstream) as WorkstreamRow | undefined;\n if (!sourceWs) throw new Error(`copyWorkstreamRows: no such workstream ${workstream}`);\n\n targetDb\n .prepare(\"INSERT INTO workstreams (name, created_at) VALUES (?, ?)\")\n .run(sourceWs.name, sourceWs.created_at);\n const targetWsId = (\n targetDb.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(workstream) as { id: number }\n ).id;\n\n if (opts.includeMachineLocalRows) copyAgents(sourceDb, targetDb, sourceWs.id, targetWsId);\n copyTasks(sourceDb, targetDb, sourceWs.id, targetWsId, opts.includeMachineLocalRows);\n copyEdges(sourceDb, targetDb, sourceWs.id, targetWsId);\n copyNotes(sourceDb, targetDb, sourceWs.id, targetWsId);\n copyLogs(sourceDb, targetDb, sourceWs.id, targetWsId, opts.preserveLogSeq);\n if (opts.includeMachineLocalRows) copyWorkspaces(sourceDb, targetDb, sourceWs.id, targetWsId);\n if (opts.includeSync) copySync(sourceDb, targetDb, sourceWs.id, targetWsId);\n}\n\nfunction copyAgents(sourceDb: Db, targetDb: Db, sourceWsId: number, targetWsId: number): void {\n const rows = sourceDb\n .prepare(\n `SELECT name, cli, pane_id, status, role, tab, created_at, updated_at\n FROM agents\n WHERE workstream_id = ?\n ORDER BY id`,\n )\n .all(sourceWsId) as AgentCopyRow[];\n const insert = targetDb.prepare(\n `INSERT INTO agents (workstream_id, name, cli, pane_id, status, role, tab, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n for (const row of rows) {\n insert.run(\n targetWsId,\n row.name,\n row.cli,\n row.pane_id,\n row.status,\n row.role,\n row.tab,\n row.created_at,\n row.updated_at,\n );\n }\n}\n\nfunction copyTasks(\n sourceDb: Db,\n targetDb: Db,\n sourceWsId: number,\n targetWsId: number,\n includeOwners: boolean,\n): void {\n const rows = sourceDb\n .prepare(\n `SELECT t.local_id, t.title, t.status, t.impact, t.effort_days, a.name AS owner_name,\n t.created_at, t.updated_at\n FROM tasks t\n LEFT JOIN agents a ON a.id = t.owner_id\n WHERE t.workstream_id = ?\n ORDER BY t.id`,\n )\n .all(sourceWsId) as TaskCopyRow[];\n const ownerLookup = targetDb.prepare(\n \"SELECT id FROM agents WHERE workstream_id = ? AND name = ?\",\n );\n const insert = targetDb.prepare(\n `INSERT INTO tasks (workstream_id, local_id, title, status, impact, effort_days, owner_id, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n for (const row of rows) {\n const ownerId =\n includeOwners && row.owner_name !== null\n ? ((ownerLookup.get(targetWsId, row.owner_name) as { id: number } | undefined)?.id ?? null)\n : null;\n insert.run(\n targetWsId,\n row.local_id,\n row.title,\n row.status,\n row.impact,\n row.effort_days,\n ownerId,\n row.created_at,\n row.updated_at,\n );\n }\n}\n\nfunction copyEdges(sourceDb: Db, targetDb: Db, sourceWsId: number, targetWsId: number): void {\n const rows = sourceDb\n .prepare(\n `SELECT f.local_id AS from_local_id, t.local_id AS to_local_id, e.created_at\n FROM task_edges e\n JOIN tasks f ON f.id = e.from_task_id\n JOIN tasks t ON t.id = e.to_task_id\n WHERE f.workstream_id = ? AND t.workstream_id = ?\n ORDER BY e.created_at, f.local_id, t.local_id`,\n )\n .all(sourceWsId, sourceWsId) as EdgeCopyRow[];\n const insert = targetDb.prepare(\n `INSERT OR IGNORE INTO task_edges (from_task_id, to_task_id, created_at)\n SELECT f.id, t.id, ?\n FROM tasks f, tasks t\n WHERE f.workstream_id = ? AND f.local_id = ?\n AND t.workstream_id = ? AND t.local_id = ?`,\n );\n for (const row of rows) {\n insert.run(row.created_at, targetWsId, row.from_local_id, targetWsId, row.to_local_id);\n }\n}\n\nfunction copyNotes(sourceDb: Db, targetDb: Db, sourceWsId: number, targetWsId: number): void {\n const rows = sourceDb\n .prepare(\n `SELECT t.local_id AS task_local_id, n.author, n.content, n.created_at\n FROM task_notes n\n JOIN tasks t ON t.id = n.task_id\n WHERE t.workstream_id = ?\n ORDER BY n.id`,\n )\n .all(sourceWsId) as NoteCopyRow[];\n const insert = targetDb.prepare(\n `INSERT INTO task_notes (task_id, author, content, created_at)\n SELECT id, ?, ?, ? FROM tasks WHERE workstream_id = ? AND local_id = ?`,\n );\n for (const row of rows) {\n insert.run(row.author, row.content, row.created_at, targetWsId, row.task_local_id);\n }\n}\n\nfunction copyLogs(\n sourceDb: Db,\n targetDb: Db,\n sourceWsId: number,\n targetWsId: number,\n preserveSeq: boolean,\n): void {\n const rows = sourceDb\n .prepare(\n `SELECT seq, source, kind, payload, created_at\n FROM agent_logs\n WHERE workstream_id = ?\n ORDER BY seq`,\n )\n .all(sourceWsId) as LogCopyRow[];\n const insertPreserve = targetDb.prepare(\n \"INSERT INTO agent_logs (seq, workstream_id, source, kind, payload, created_at) VALUES (?, ?, ?, ?, ?, ?)\",\n );\n const insertRenumber = targetDb.prepare(\n \"INSERT INTO agent_logs (workstream_id, source, kind, payload, created_at) VALUES (?, ?, ?, ?, ?)\",\n );\n for (const row of rows) {\n if (preserveSeq) {\n insertPreserve.run(row.seq, targetWsId, row.source, row.kind, row.payload, row.created_at);\n } else {\n insertRenumber.run(targetWsId, row.source, row.kind, row.payload, row.created_at);\n }\n }\n}\n\nfunction copyWorkspaces(sourceDb: Db, targetDb: Db, sourceWsId: number, targetWsId: number): void {\n const rows = sourceDb\n .prepare(\n `SELECT a.name AS agent_name, v.backend, v.path, v.parent_ref, v.created_at\n FROM vcs_workspaces v\n JOIN agents a ON a.id = v.agent_id\n WHERE v.workstream_id = ?\n ORDER BY v.id`,\n )\n .all(sourceWsId) as WorkspaceCopyRow[];\n const agentLookup = targetDb.prepare(\n \"SELECT id FROM agents WHERE workstream_id = ? AND name = ?\",\n );\n const insert = targetDb.prepare(\n `INSERT INTO vcs_workspaces (agent_id, workstream_id, backend, path, parent_ref, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n );\n for (const row of rows) {\n const agentId = (agentLookup.get(targetWsId, row.agent_name) as { id: number } | undefined)?.id;\n if (agentId === undefined) continue;\n insert.run(agentId, targetWsId, row.backend, row.path, row.parent_ref, row.created_at);\n }\n}\n\nfunction copySync(sourceDb: Db, targetDb: Db, sourceWsId: number, targetWsId: number): void {\n const row = sourceDb\n .prepare(\"SELECT last_known_peer_seqs FROM workstream_sync WHERE workstream_id = ?\")\n .get(sourceWsId) as { last_known_peer_seqs: string } | undefined;\n if (!row) return;\n targetDb\n .prepare(\"INSERT INTO workstream_sync (workstream_id, last_known_peer_seqs) VALUES (?, ?)\")\n .run(targetWsId, row.last_known_peer_seqs);\n}\n\nfunction writeSyncState(\n db: Db,\n workstreamId: number,\n sourceMachineId: string,\n sourceSeq: number,\n): void {\n const localSeq = latestSeq(db, workstreamId);\n const peers: Record<string, number> = {\n [sourceMachineId]: sourceSeq,\n [localSeqKey(sourceMachineId)]: localSeq,\n };\n db.prepare(\n `INSERT OR REPLACE INTO workstream_sync (workstream_id, last_known_peer_seqs)\n VALUES (?, ?)`,\n ).run(workstreamId, JSON.stringify(peers));\n}\n\nfunction lastKnownPeerSync(\n db: Db,\n workstreamId: number,\n machineId: string,\n): { sourceSeq: number; localSeq: number } {\n const row = db\n .prepare(\"SELECT last_known_peer_seqs FROM workstream_sync WHERE workstream_id = ?\")\n .get(workstreamId) as { last_known_peer_seqs: string } | undefined;\n if (!row) return { sourceSeq: 0, localSeq: 0 };\n const parsed = parsePeerSeqs(row.last_known_peer_seqs);\n const sourceSeq = parsed[machineId] ?? 0;\n return { sourceSeq, localSeq: parsed[localSeqKey(machineId)] ?? sourceSeq };\n}\n\nfunction localSeqKey(machineId: string): string {\n return `${machineId}:local`;\n}\n\nfunction parsePeerSeqs(raw: string): Record<string, number> {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return {};\n const result: Record<string, number> = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (typeof value === \"number\" && Number.isFinite(value)) result[key] = value;\n }\n return result;\n } catch {\n return {};\n }\n}\n\nfunction readImportManifest(file: string): DbExportManifest {\n const manifestPath = `${file}.manifest.json`;\n if (!existsSync(manifestPath)) throw new DbImportManifestMissingError(manifestPath);\n return JSON.parse(readFileSync(manifestPath, \"utf8\")) as DbExportManifest;\n}\n\nfunction assertImportSchemaCompatible(sourceVersion: number): void {\n if (sourceVersion < CURRENT_SCHEMA_VERSION) throw new DbImportSchemaTooOldError(sourceVersion);\n if (sourceVersion > CURRENT_SCHEMA_VERSION) throw new DbImportSchemaTooNewError(sourceVersion);\n}\n\nfunction buildExportManifest(db: Db): DbExportManifest {\n const identity = getMachineIdentity(db);\n const schemaRow = db.prepare(\"SELECT version FROM schema_version WHERE id = 1\").get() as\n | { version: number }\n | undefined;\n const workstreams = listLocalWorkstreams(db);\n\n return {\n muVersion: readPackageVersion(),\n schemaVersion: schemaRow?.version ?? CURRENT_SCHEMA_VERSION,\n machineId: identity?.machine_id ?? \"\",\n hostname: identity?.hostname ?? hostname(),\n exportedAt: new Date().toISOString(),\n workstreams: workstreams.map((ws) => ({\n name: ws.name,\n tasks: count(db, \"SELECT COUNT(*) AS n FROM tasks WHERE workstream_id = ?\", ws.id),\n edges: count(\n db,\n `SELECT COUNT(*) AS n\n FROM task_edges e\n JOIN tasks f ON f.id = e.from_task_id\n JOIN tasks t ON t.id = e.to_task_id\n WHERE f.workstream_id = ? AND t.workstream_id = ?`,\n ws.id,\n ws.id,\n ),\n notes: count(\n db,\n `SELECT COUNT(*) AS n\n FROM task_notes n\n JOIN tasks t ON t.id = n.task_id\n WHERE t.workstream_id = ?`,\n ws.id,\n ),\n latestSeq: latestSeq(db, ws.id),\n })),\n };\n}\n\nfunction listLocalWorkstreams(db: Db): WorkstreamIdRow[] {\n return db.prepare(\"SELECT id, name FROM workstreams ORDER BY name\").all() as WorkstreamIdRow[];\n}\n\nfunction getMachineIdentity(db: Db): MachineIdentityRow | undefined {\n return db\n .prepare(\"SELECT machine_id, hostname, created_at FROM machine_identity WHERE id = 1\")\n .get() as MachineIdentityRow | undefined;\n}\n\nfunction countWorkstream(db: Db, wsId: number): Record<string, number> {\n return {\n tasks: count(db, \"SELECT COUNT(*) AS n FROM tasks WHERE workstream_id = ?\", wsId),\n edges: count(\n db,\n `SELECT COUNT(*) AS n\n FROM task_edges e\n JOIN tasks f ON f.id = e.from_task_id\n JOIN tasks t ON t.id = e.to_task_id\n WHERE f.workstream_id = ? AND t.workstream_id = ?`,\n wsId,\n wsId,\n ),\n notes: count(\n db,\n `SELECT COUNT(*) AS n\n FROM task_notes n\n JOIN tasks t ON t.id = n.task_id\n WHERE t.workstream_id = ?`,\n wsId,\n ),\n };\n}\n\nfunction countsFromManifest(ws: DbExportManifestWorkstream): Record<string, number> {\n return { tasks: ws.tasks, edges: ws.edges, notes: ws.notes };\n}\n\nfunction normaliseOnlyWorkstreams(input: readonly string[] | undefined): Set<string> {\n if (!input || input.length === 0) return new Set();\n return new Set(\n input\n .flatMap((v) => v.split(\",\"))\n .map((v) => v.trim())\n .filter((v) => v.length > 0),\n );\n}\n\nfunction count(db: Db, sql: string, ...params: unknown[]): number {\n const row = db.prepare(sql).get(...params) as { n: number } | undefined;\n return row?.n ?? 0;\n}\n\nfunction quoteSqlString(s: string): string {\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nfunction readPackageVersion(): string {\n try {\n const here = dirname(fileURLToPath(import.meta.url));\n const raw = readFileSync(join(here, \"..\", \"package.json\"), \"utf8\");\n const parsed = JSON.parse(raw) as { version?: unknown };\n return typeof parsed.version === \"string\" ? parsed.version : \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\"'\"'`)}'`;\n}\n\nexport {\n DbReplayLocalIdConflictError,\n DbReplayWorkstreamMissingError,\n buildReplayPlan,\n replayDb,\n type DbReplayEdgeItem,\n type DbReplayNoteItem,\n type DbReplayPlan,\n type DbReplayResult,\n type DbReplayTaskConflict,\n type DbReplayTaskItem,\n type ReplayDbOptions,\n} from \"./db-sync-replay.js\";\n","// mu — manual replay of divergence sidecars parked by `mu db import --force-source`.\n\nimport { createHash } from \"node:crypto\";\nimport { type Db, openDb } from \"./db.js\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\nimport { captureSnapshot } from \"./snapshots.js\";\n\nexport class DbReplayWorkstreamMissingError extends Error implements HasNextSteps {\n override readonly name = \"DbReplayWorkstreamMissingError\";\n constructor(public readonly workstream: string) {\n super(\n `replay sidecar is for workstream \"${workstream}\", which does not exist locally; restore it first via mu db import or mu archive restore`,\n );\n }\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Restore this workstream from a DB export\",\n command: \"mu db import <file> --apply\",\n },\n {\n intent: \"Or restore it from an archive\",\n command: `mu archive restore <label> --as ${this.workstream}`,\n },\n ];\n }\n}\n\nexport interface DbReplayTaskConflict {\n localId: string;\n local: { title: string; status: string };\n sidecar: { title: string; status: string };\n}\n\nexport class DbReplayLocalIdConflictError extends Error implements HasNextSteps {\n override readonly name = \"DbReplayLocalIdConflictError\";\n constructor(\n public readonly workstream: string,\n public readonly conflicts: readonly DbReplayTaskConflict[],\n ) {\n super(\n `sidecar task id collides with different local content in ${workstream}: ${conflicts\n .map(\n (c) =>\n `${c.localId} (local: ${c.local.status} ${JSON.stringify(c.local.title)}; sidecar: ${c.sidecar.status} ${JSON.stringify(c.sidecar.title)})`,\n )\n .join(\", \")}`,\n );\n }\n errorNextSteps(): NextStep[] {\n const first = this.conflicts[0];\n return [\n {\n intent: \"Create a renamed local task manually, then replay notes if desired\",\n command: first\n ? `mu task add ${first.localId}-replay -w ${this.workstream} -t ${shellQuote(first.sidecar.title)} -i <impact> -e <effort>`\n : `mu task add <renamed-id> -w ${this.workstream} -t <title> -i <impact> -e <effort>`,\n },\n {\n intent: \"Skip the colliding id and replay another task\",\n command: \"mu db replay <sidecar> --task <other-id> --apply\",\n },\n ];\n }\n}\n\nexport interface ReplayDbOptions {\n apply?: boolean;\n tasks?: readonly string[];\n notes?: readonly string[];\n all?: boolean;\n}\n\nexport interface DbReplayTaskItem {\n localId: string;\n title: string;\n status: string;\n impact: number;\n effortDays: number;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface DbReplayNoteItem {\n taskLocalId: string;\n author: string | null;\n content: string;\n createdAt: string;\n hash: string;\n}\n\nexport interface DbReplayEdgeItem {\n fromLocalId: string;\n toLocalId: string;\n createdAt: string;\n}\n\nexport interface DbReplayPlan {\n sourceFile: string;\n workstream: string;\n tasks: DbReplayTaskItem[];\n notes: DbReplayNoteItem[];\n edges: DbReplayEdgeItem[];\n conflicts: DbReplayTaskConflict[];\n}\n\nexport interface DbReplayResult extends DbReplayPlan {\n dryRun: boolean;\n applied: boolean;\n snapshotId?: number;\n added: { tasks: number; notes: number; edges: number };\n warnings: string[];\n}\n\ninterface WorkstreamIdRow {\n id: number;\n name: string;\n}\n\ninterface ReplayTaskRow {\n local_id: string;\n title: string;\n status: string;\n impact: number;\n effort_days: number;\n created_at: string;\n updated_at: string;\n}\n\nexport function replayDb(db: Db, file: string, opts: ReplayDbOptions = {}): DbReplayResult {\n const sidecarDb = openDb({ path: file, readonly: true });\n try {\n const plan = buildReplayPlan(db, sidecarDb, file);\n const taskFilter = new Set(opts.tasks ?? []);\n const noteFilter = new Set(opts.notes ?? []);\n const selectedTaskIds =\n opts.all === true ? new Set(plan.tasks.map((t) => t.localId)) : taskFilter;\n const selectedNoteIds =\n opts.all === true ? new Set(plan.notes.map((n) => n.taskLocalId)) : noteFilter;\n const hasSelectors = opts.all === true || selectedTaskIds.size > 0 || selectedNoteIds.size > 0;\n const noteTaskIds = new Set([...selectedNoteIds, ...selectedTaskIds]);\n const hasWrites =\n plan.tasks.some((t) => selectedTaskIds.has(t.localId)) ||\n plan.notes.some((n) => noteTaskIds.has(n.taskLocalId)) ||\n plan.edges.some(\n (e) =>\n opts.all === true ||\n selectedTaskIds.has(e.fromLocalId) ||\n selectedTaskIds.has(e.toLocalId),\n );\n const relevantConflicts =\n opts.all === true\n ? plan.conflicts\n : plan.conflicts.filter((c) => selectedTaskIds.has(c.localId));\n if (relevantConflicts.length > 0) {\n throw new DbReplayLocalIdConflictError(plan.workstream, relevantConflicts);\n }\n if (opts.apply !== true || !hasSelectors) return replayResult(plan, true, false);\n if (!hasWrites) return replayResult(plan, false, true);\n\n const snapshot = captureSnapshot(db, `db replay ${file}`, null);\n const applied = applyReplayPlan(db, plan, selectedTaskIds, selectedNoteIds, opts.all === true);\n return { ...replayResult(plan, false, true), snapshotId: snapshot.id, ...applied };\n } finally {\n sidecarDb.close();\n }\n}\n\nexport function buildReplayPlan(localDb: Db, sidecarDb: Db, sourceFile: string): DbReplayPlan {\n const sidecarWorkstreams = listLocalWorkstreams(sidecarDb);\n const sidecarWs = sidecarWorkstreams[0];\n if (sidecarWorkstreams.length !== 1 || !sidecarWs) {\n throw new Error(\n `replay sidecar must contain exactly one workstream; found ${sidecarWorkstreams.length}`,\n );\n }\n const localWs = listLocalWorkstreams(localDb).find((w) => w.name === sidecarWs.name);\n if (!localWs) throw new DbReplayWorkstreamMissingError(sidecarWs.name);\n\n const localTasks = new Map(\n (\n localDb\n .prepare(\"SELECT local_id, title, status FROM tasks WHERE workstream_id = ?\")\n .all(localWs.id) as {\n local_id: string;\n title: string;\n status: string;\n }[]\n ).map((t) => [t.local_id, t]),\n );\n const tasks: DbReplayTaskItem[] = [];\n const conflicts: DbReplayTaskConflict[] = [];\n for (const task of listReplayTasks(sidecarDb, sidecarWs.id)) {\n const local = localTasks.get(task.localId);\n if (!local) tasks.push(task);\n else if (local.title !== task.title || local.status !== task.status) {\n conflicts.push({\n localId: task.localId,\n local: { title: local.title, status: local.status },\n sidecar: { title: task.title, status: task.status },\n });\n }\n }\n\n const localNoteHashes = new Set(listReplayNotes(localDb, localWs.id).map((n) => n.hash));\n const localEdges = new Set(listReplayEdges(localDb, localWs.id).map(edgeKey));\n return {\n sourceFile,\n workstream: sidecarWs.name,\n tasks,\n notes: listReplayNotes(sidecarDb, sidecarWs.id).filter((n) => !localNoteHashes.has(n.hash)),\n edges: listReplayEdges(sidecarDb, sidecarWs.id).filter((e) => !localEdges.has(edgeKey(e))),\n conflicts,\n };\n}\n\nfunction applyReplayPlan(\n db: Db,\n plan: DbReplayPlan,\n selectedTaskIds: ReadonlySet<string>,\n selectedNoteIds: ReadonlySet<string>,\n replayAllEdges: boolean,\n): { added: { tasks: number; notes: number; edges: number }; warnings: string[] } {\n const warnings: string[] = [];\n const added = db.transaction(() => {\n const wsId = (\n db.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(plan.workstream) as\n | WorkstreamIdRow\n | undefined\n )?.id;\n if (wsId === undefined) throw new DbReplayWorkstreamMissingError(plan.workstream);\n const taskIds = new Set(selectedTaskIds);\n const noteTaskIds = new Set([...selectedNoteIds, ...taskIds]);\n let tasks = 0;\n let notes = 0;\n let edges = 0;\n\n const insertTask = db.prepare(\n `INSERT OR IGNORE INTO tasks (workstream_id, local_id, title, status, impact, effort_days, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n for (const task of plan.tasks) {\n if (!taskIds.has(task.localId)) continue;\n const result = insertTask.run(\n wsId,\n task.localId,\n task.title,\n task.status,\n task.impact,\n task.effortDays,\n task.createdAt,\n task.updatedAt,\n );\n if (result.changes > 0) tasks += 1;\n }\n\n const existingNoteHashes = new Set(listReplayNotes(db, wsId).map((n) => n.hash));\n const insertNote = db.prepare(\n `INSERT INTO task_notes (task_id, author, content, created_at)\n SELECT id, ?, ?, ? FROM tasks WHERE workstream_id = ? AND local_id = ?`,\n );\n for (const note of plan.notes) {\n if (!noteTaskIds.has(note.taskLocalId) || existingNoteHashes.has(note.hash)) continue;\n const result = insertNote.run(\n note.author,\n note.content,\n note.createdAt,\n wsId,\n note.taskLocalId,\n );\n if (result.changes > 0) {\n notes += 1;\n existingNoteHashes.add(note.hash);\n }\n }\n\n const insertEdge = db.prepare(\n `INSERT OR IGNORE INTO task_edges (from_task_id, to_task_id, created_at)\n SELECT f.id, t.id, ?\n FROM tasks f, tasks t\n WHERE f.workstream_id = ? AND f.local_id = ?\n AND t.workstream_id = ? AND t.local_id = ?`,\n );\n for (const edge of plan.edges) {\n if (!replayAllEdges && !taskIds.has(edge.fromLocalId) && !taskIds.has(edge.toLocalId)) {\n continue;\n }\n if (!hasTask(db, wsId, edge.fromLocalId) || !hasTask(db, wsId, edge.toLocalId)) {\n warnings.push(\n `skipped edge ${edge.fromLocalId} -> ${edge.toLocalId}: one endpoint is missing locally`,\n );\n continue;\n }\n const result = insertEdge.run(edge.createdAt, wsId, edge.fromLocalId, wsId, edge.toLocalId);\n if (result.changes > 0) edges += 1;\n }\n return { tasks, notes, edges };\n })();\n return { added, warnings };\n}\n\nfunction replayResult(plan: DbReplayPlan, dryRun: boolean, applied: boolean): DbReplayResult {\n return { ...plan, dryRun, applied, added: { tasks: 0, notes: 0, edges: 0 }, warnings: [] };\n}\n\nfunction listLocalWorkstreams(db: Db): WorkstreamIdRow[] {\n return db.prepare(\"SELECT id, name FROM workstreams ORDER BY name\").all() as WorkstreamIdRow[];\n}\n\nfunction listReplayTasks(db: Db, wsId: number): DbReplayTaskItem[] {\n return (\n db\n .prepare(\n `SELECT local_id, title, status, impact, effort_days, created_at, updated_at\n FROM tasks\n WHERE workstream_id = ?\n ORDER BY local_id`,\n )\n .all(wsId) as ReplayTaskRow[]\n ).map((row) => ({\n localId: row.local_id,\n title: row.title,\n status: row.status,\n impact: row.impact,\n effortDays: row.effort_days,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }));\n}\n\nfunction listReplayNotes(db: Db, wsId: number): DbReplayNoteItem[] {\n const rows = db\n .prepare(\n `SELECT t.local_id AS taskLocalId, n.author, n.content, n.created_at AS createdAt\n FROM task_notes n\n JOIN tasks t ON t.id = n.task_id\n WHERE t.workstream_id = ?\n ORDER BY n.created_at, n.id`,\n )\n .all(wsId) as Omit<DbReplayNoteItem, \"hash\">[];\n return rows.map((row) => ({ ...row, hash: noteHash(row) }));\n}\n\nfunction listReplayEdges(db: Db, wsId: number): DbReplayEdgeItem[] {\n return db\n .prepare(\n `SELECT f.local_id AS fromLocalId, t.local_id AS toLocalId, e.created_at AS createdAt\n FROM task_edges e\n JOIN tasks f ON f.id = e.from_task_id\n JOIN tasks t ON t.id = e.to_task_id\n WHERE f.workstream_id = ? AND t.workstream_id = ?\n ORDER BY f.local_id, t.local_id`,\n )\n .all(wsId, wsId) as DbReplayEdgeItem[];\n}\n\nfunction noteHash(note: Omit<DbReplayNoteItem, \"hash\">): string {\n return createHash(\"sha256\")\n .update(`${note.taskLocalId}\\0${note.content}\\0${note.createdAt}`)\n .digest(\"hex\");\n}\n\nfunction edgeKey(edge: DbReplayEdgeItem): string {\n return `${edge.fromLocalId}\\0${edge.toLocalId}`;\n}\n\nfunction hasTask(db: Db, wsId: number, localId: string): boolean {\n return (\n (db\n .prepare(\"SELECT 1 FROM tasks WHERE workstream_id = ? AND local_id = ?\")\n .get(wsId, localId) as { \"1\": number } | undefined) !== undefined\n );\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\"'\"'`)}'`;\n}\n","// mu — parallel-track detection via union-find with diamond merge.\n//\n// Port of a parallel-tracks union-find algorithm cribbed from a\n// prior internal multi-agent runtime. The killer feature: when two goals share a prerequisite, their subgraphs\n// overlap and they collapse into ONE track, so two agents are never\n// assigned tasks that share a dependency.\n//\n// goal_a goal_b goal_a goal_b\n// \\ / \\ /\n// shared → shared (1 track)\n// | |\n// leaf leaf\n//\n// Algorithm:\n// 1. Get all open goals (tasks with no outgoing edges, not CLOSED).\n// 2. For each goal, compute its prerequisite subgraph\n// (everything transitively reachable via reverse edges).\n// 3. Build union-find: merge any two goals whose subgraphs intersect.\n// 4. Each connected component is one Track.\n\nimport type { Db } from \"./db.js\";\nimport {\n STATUSES_TERMINAL_OR_PARKED,\n type TaskRow,\n getPrerequisites,\n listGoals,\n listReady,\n} from \"./tasks.js\";\n\nexport interface Track {\n /** Goal tasks (no outgoing edges) belonging to this track. */\n roots: TaskRow[];\n /** Every task id reachable as a prerequisite of any root in this track. */\n taskIds: ReadonlySet<string>;\n /** Number of READY tasks (per the SQL view) within this track's subgraph. */\n readyCount: number;\n}\n\n/**\n * Identify independent task subtrees suitable for parallel assignment\n * within a workstream. Open goals only; CLOSED goals are excluded as\n * they no longer represent work to schedule.\n *\n * Scoping: only goals belonging to `workstream` are considered.\n * Cross-workstream edges are forbidden by addTask, so a goal's\n * prerequisite subgraph is naturally workstream-internal.\n */\nexport function getParallelTracks(db: Db, workstream: string): Track[] {\n // listGoals already filters via the SQL view (NOT IN CLOSED/REJECTED/\n // DEFERRED), but defence-in-depth: a stale db snapshot or future view\n // tweak shouldn't let parked/terminal goals leak into track count.\n const goals = listGoals(db, workstream).filter(\n (g) => !STATUSES_TERMINAL_OR_PARKED.includes(g.status),\n );\n if (goals.length === 0) return [];\n\n // 2. Compute prerequisite subgraph for each goal.\n const subgraphs = new Map<string, Set<string>>();\n for (const goal of goals) {\n subgraphs.set(goal.name, getPrerequisites(db, goal.name, workstream));\n }\n\n // 3. Union-find: merge goals whose subgraphs overlap.\n const uf = new UnionFind(goals.map((g) => g.name));\n for (let i = 0; i < goals.length; i++) {\n const a = goals[i];\n if (!a) continue;\n for (let j = i + 1; j < goals.length; j++) {\n const b = goals[j];\n if (!b) continue;\n const subA = subgraphs.get(a.name);\n const subB = subgraphs.get(b.name);\n if (subA && subB && overlaps(subA, subB)) {\n uf.union(a.name, b.name);\n }\n }\n }\n\n // 4. Group goals + subgraph task ids by union-find root.\n const componentTaskIds = new Map<string, Set<string>>();\n const componentRoots = new Map<string, TaskRow[]>();\n for (const goal of goals) {\n const root = uf.find(goal.name);\n let bucket = componentTaskIds.get(root);\n if (!bucket) {\n bucket = new Set<string>();\n componentTaskIds.set(root, bucket);\n componentRoots.set(root, []);\n }\n componentRoots.get(root)?.push(goal);\n const sub = subgraphs.get(goal.name);\n if (sub) {\n for (const id of sub) bucket.add(id);\n }\n }\n\n // 5. Compute ready counts per track.\n const readyIds = new Set(listReady(db, workstream).map((t) => t.name));\n const tracks: Track[] = [];\n for (const [root, taskIds] of componentTaskIds) {\n const trackRoots = componentRoots.get(root) ?? [];\n let readyCount = 0;\n for (const id of taskIds) if (readyIds.has(id)) readyCount++;\n tracks.push({ roots: trackRoots, taskIds, readyCount });\n }\n\n // Stable order: by primary root's localId so output is deterministic.\n tracks.sort((a, b) => {\n const an = a.roots[0]?.name ?? \"\";\n const bn = b.roots[0]?.name ?? \"\";\n return an.localeCompare(bn);\n });\n return tracks;\n}\n\nfunction overlaps(a: Set<string>, b: Set<string>): boolean {\n // Iterate the smaller set for O(min(|a|, |b|)) lookups.\n const [small, large] = a.size <= b.size ? [a, b] : [b, a];\n for (const x of small) if (large.has(x)) return true;\n return false;\n}\n\nclass UnionFind {\n private readonly parent = new Map<string, string>();\n private readonly rank = new Map<string, number>();\n\n constructor(items: readonly string[]) {\n for (const item of items) {\n this.parent.set(item, item);\n this.rank.set(item, 0);\n }\n }\n\n find(x: string): string {\n let root = x;\n while (true) {\n const next = this.parent.get(root);\n if (next === undefined || next === root) break;\n root = next;\n }\n // Path compression.\n let curr = x;\n while (curr !== root) {\n const next = this.parent.get(curr);\n if (next === undefined) break;\n this.parent.set(curr, root);\n curr = next;\n }\n return root;\n }\n\n union(a: string, b: string): void {\n const rootA = this.find(a);\n const rootB = this.find(b);\n if (rootA === rootB) return;\n const rankA = this.rank.get(rootA) ?? 0;\n const rankB = this.rank.get(rootB) ?? 0;\n if (rankA < rankB) {\n this.parent.set(rootA, rootB);\n } else if (rankA > rankB) {\n this.parent.set(rootB, rootA);\n } else {\n this.parent.set(rootB, rootA);\n this.rank.set(rootA, rankA + 1);\n }\n }\n}\n","// Doctor summary — a TUI-friendly slice of `mu doctor`'s checks.\n//\n// The full `mu doctor` verb (src/cli/doctor.ts) renders a textual card\n// with: tmux/env presence, DB schema integrity, schema_version,\n// journal_mode, foreign_keys, per-workstream agent/task/log counts,\n// and reconcile drift (would-be-pruned ghosts + orphan panes). The\n// TUI's slot-9 Doctor card needs the same SIGNAL but at poll-tick\n// cost: an array of checks the card can render row-by-row.\n//\n// Per feat_card_9_doctor (workstream `tui-impl`), promoted from the\n// last reserved slot in design_global_keymap.\n//\n// CHEAPNESS RULES (so this is safe to run on every snapshot tick):\n// - Synchronous DB pragmas + COUNT(*)-shape SELECTs only.\n// - Reads `view.report.prunedGhosts` and `view.orphans` straight\n// out of the WorkstreamSnapshot — `loadWorkstreamSnapshot`\n// already runs `listLiveAgents(...)` once per tick, which populates\n// `report.prunedGhosts`.\n// - Reads `workspaceOrphans` straight out of the snapshot too.\n// - NO subprocess shellouts. The `tmux -V` check from the textual\n// `mu doctor` is intentionally OMITTED here — the TUI is\n// already running inside a terminal so tmux's binary presence\n// is implicit, and adding a per-tick subprocess fork to a\n// polled dashboard is the wrong tradeoff. If a future task\n// wants tmux-presence on the dashboard, add it as a\n// once-per-session probe in <App> (mirrors probeClipboardBackend),\n// not as a per-tick check here.\n//\n// CHECK SHAPE (matches the textual doctor's per-row vocabulary):\n// { name, status: \"ok\" | \"warn\" | \"fail\", detail }\n//\n// The card filters to non-OK rows for its body; subtitle counts\n// warn+fail. When everything is OK the card renders a quiet\n// \"all healthy\" line so the operator's eye learns to read the\n// presence of rows as \"something needs attention.\"\n\nimport { CURRENT_SCHEMA_VERSION, type Db, EXPECTED_TABLES } from \"./db.js\";\nimport type { WorkstreamSnapshot } from \"./state.js\";\n\nexport type DoctorStatus = \"ok\" | \"warn\" | \"fail\";\n\nexport interface DoctorCheck {\n /** Short, stable identifier — used as the row label. Lowercase\n * one-word tokens so the column-aligned card layout looks tidy. */\n name: string;\n status: DoctorStatus;\n /** Free-form prose for the row's right-hand column. Kept short so\n * the card's CLIP column doesn't truncate it on common widths. */\n detail: string;\n}\n\nexport interface DoctorSummary {\n /** Every check that ran, in stable display order. The card filters\n * to non-OK rows for its body but keeps the OK rows so the popup\n * (when it ships under feat_more_cards_umbrella) can render the\n * full list. */\n checks: readonly DoctorCheck[];\n /** Convenience: how many rows are warn or fail. Card subtitle\n * reads this directly. Pure derivation from `checks`. */\n problemCount: number;\n}\n\n/**\n * Compute the doctor summary for a workstream. Pure-ish: runs cheap\n * synchronous DB queries + reads from the supplied snapshot. Callers\n * that don't want to compute a snapshot first (or are running inside\n * `loadWorkstreamSnapshot` mid-build) can omit `snapshot` — the\n * snapshot-derived checks (ghosts / orphans / workspace-orphans) are\n * skipped in that case.\n */\nexport function loadDoctorSummary(db: Db, snapshot: WorkstreamSnapshot | null): DoctorSummary {\n const checks: DoctorCheck[] = [];\n\n // ─ schema (table presence) ──────────────────────────────────────\n try {\n const tables = (\n db\n .prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name\",\n )\n .all() as { name: string }[]\n ).map((r) => r.name);\n const missing = EXPECTED_TABLES.filter((t) => !tables.includes(t));\n if (missing.length === 0) {\n checks.push({\n name: \"schema\",\n status: \"ok\",\n detail: `${EXPECTED_TABLES.length} tables`,\n });\n } else {\n checks.push({\n name: \"schema\",\n status: \"fail\",\n detail: `missing: ${missing.join(\", \")}`,\n });\n }\n } catch (err) {\n checks.push({\n name: \"schema\",\n status: \"fail\",\n detail: err instanceof Error ? err.message : String(err),\n });\n }\n\n // ─ schema_version ────────────────────────────────────────────────\n try {\n const row = db.prepare(\"SELECT version FROM schema_version WHERE id = 1\").get() as\n | { version: number }\n | undefined;\n const v = row?.version;\n if (v === undefined) {\n checks.push({\n name: \"schema_version\",\n status: \"fail\",\n detail: `missing row (expected ${CURRENT_SCHEMA_VERSION})`,\n });\n } else if (v === CURRENT_SCHEMA_VERSION) {\n checks.push({ name: \"schema_version\", status: \"ok\", detail: String(v) });\n } else if (v < CURRENT_SCHEMA_VERSION) {\n checks.push({\n name: \"schema_version\",\n status: \"warn\",\n detail: `${v} (code expects ${CURRENT_SCHEMA_VERSION})`,\n });\n } else {\n checks.push({\n name: \"schema_version\",\n status: \"fail\",\n detail: `${v} (code expects ${CURRENT_SCHEMA_VERSION}; downgrade?)`,\n });\n }\n } catch {\n checks.push({\n name: \"schema_version\",\n status: \"fail\",\n detail: \"unreadable\",\n });\n }\n\n // ─ journal_mode ─────────────────────────────────────────────────\n try {\n const journal = db.pragma(\"journal_mode\", { simple: true });\n if (journal === \"wal\") {\n checks.push({ name: \"journal_mode\", status: \"ok\", detail: String(journal) });\n } else {\n checks.push({\n name: \"journal_mode\",\n status: \"warn\",\n detail: `${journal} (expected wal)`,\n });\n }\n } catch (err) {\n checks.push({\n name: \"journal_mode\",\n status: \"warn\",\n detail: err instanceof Error ? err.message : String(err),\n });\n }\n\n // ─ foreign_keys ─────────────────────────────────────────────────\n try {\n const fk = db.pragma(\"foreign_keys\", { simple: true });\n if (fk === 1) {\n checks.push({ name: \"foreign_keys\", status: \"ok\", detail: \"on\" });\n } else {\n checks.push({\n name: \"foreign_keys\",\n status: \"fail\",\n detail: `off (${fk})`,\n });\n }\n } catch (err) {\n checks.push({\n name: \"foreign_keys\",\n status: \"fail\",\n detail: err instanceof Error ? err.message : String(err),\n });\n }\n\n // ─ snapshot-derived checks (ghosts / orphans) ───────────────────\n if (snapshot !== null) {\n const ghosts = snapshot.view.report.prunedGhosts;\n if (ghosts === 0) {\n checks.push({ name: \"agents\", status: \"ok\", detail: \"no ghost panes\" });\n } else {\n checks.push({\n name: \"agents\",\n status: \"warn\",\n detail: `${ghosts} ghost pane${ghosts === 1 ? \"\" : \"s\"}; run \\`mu state\\` or \\`mu agent list\\` to reap`,\n });\n }\n const orphanPanes = snapshot.view.orphans.length;\n if (orphanPanes === 0) {\n checks.push({ name: \"panes\", status: \"ok\", detail: \"no orphan panes\" });\n } else {\n checks.push({\n name: \"panes\",\n status: \"warn\",\n detail: `${orphanPanes} orphan pane${orphanPanes === 1 ? \"\" : \"s\"}; run \\`mu agent adopt\\``,\n });\n }\n const orphanWorkspaces = snapshot.workspaceOrphans.length;\n if (orphanWorkspaces === 0) {\n checks.push({ name: \"workspaces\", status: \"ok\", detail: \"no orphan dirs\" });\n } else {\n checks.push({\n name: \"workspaces\",\n status: \"warn\",\n detail: `${orphanWorkspaces} orphan dir${orphanWorkspaces === 1 ? \"\" : \"s\"}; run \\`mu workspace orphans\\``,\n });\n }\n }\n\n return { checks, problemCount: countProblems(checks) };\n}\n\n/** Count of warn + fail rows. Pure; exported for unit tests. */\nexport function countProblems(checks: readonly DoctorCheck[]): number {\n let n = 0;\n for (const c of checks) if (c.status !== \"ok\") n++;\n return n;\n}\n\n/**\n * Return the full check array (OK + warn + fail) in stable display\n * order. Used by the TUI's slot-9 Doctor popup\n * (feat_popup_9_doctor, workstream `tui-impl`) which renders every\n * row — not just the non-OK subset Card 9 surfaces.\n *\n * Thin wrapper over `loadDoctorSummary` so the SDK seam stays\n * single: `loadDoctorSummary` is the source of truth for the\n * check vocabulary, and the popup's `loadDoctorChecks` view is\n * just `.checks`. Pure-ish (same cheap synchronous DB reads as\n * `loadDoctorSummary`).\n */\nexport function loadDoctorChecks(\n db: Db,\n snapshot: WorkstreamSnapshot | null,\n): readonly DoctorCheck[] {\n return loadDoctorSummary(db, snapshot).checks;\n}\n\n// ─── pure helpers (per-check remediation hints) ────────────────────\n//\n// These map a `DoctorCheck.name` to either (a) a single-line\n// informational shell command suitable for yank/paste, or (b) a\n// short multi-line paragraph explaining the failure shape. They\n// originally lived next to the slot-9 Doctor popup\n// (src/cli/tui/popups/doctor.tsx) but moved here per\n// review_tui_doctor_remediation_lives_in_popup: neither function\n// has any rendering concern (both return plain strings) and the\n// popup's `renderDrillBody` was the only render-layer caller.\n// Living here keeps every per-check fact in one file so adding a\n// new check is a single touchpoint, and lets future SDK consumers\n// (e.g. a `mu doctor --remediation` flag) reach the same data\n// without depending on the TUI render layer.\n//\n// READ-ONLY by construction. Even when a check is `fail`, the yank\n// recipe is the diagnostic verb the operator should RUN MANUALLY,\n// never a mutating fix. Schema-shape checks (no actionable\n// mutation) yank a `# ...` comment line so the muscle-memory of\n// `y` still gives feedback.\n\n/**\n * Map a check row to the most useful informational command the\n * operator might paste. Read-only by construction: `mu agent list`,\n * `mu workspace orphans`, `mu doctor` are all SELECT-shape verbs;\n * `# ...` lines are visibly inert. Per the slot-9 popup spec KEY\n * MAP block this is INFORMATIONAL, never a mutating recipe — so\n * even when the check is `fail`, we yank the diagnostic verb the\n * operator should RUN MANUALLY, not a fix command.\n *\n * Pure; exported for unit tests + SDK reuse.\n */\nexport function yankCommandForCheck(check: Pick<DoctorCheck, \"name\" | \"status\">): string {\n switch (check.name) {\n case \"agents\":\n // Diagnostic/reaping read: `mu state` and `mu agent list` both\n // reconcile missing panes. Prefer the canonical state card; the\n // detailed agent list remains in the doctor detail text.\n return \"mu state\";\n case \"panes\":\n // Orphan tmux panes — the standard adoption recipe.\n return \"mu agent adopt\";\n case \"workspaces\":\n // Diagnostic: list orphan workspace dirs. Operator decides\n // whether to `mu workspace free` from the list output.\n return \"mu workspace orphans\";\n case \"schema\":\n case \"schema_version\":\n case \"journal_mode\":\n case \"foreign_keys\":\n // No actionable mutation an operator should yank for\n // schema-shape checks (the migration runner is the SDK seam,\n // not a CLI verb). Yank a no-op comment so the muscle memory\n // of `y` still gives feedback. When fail, the textual\n // `mu doctor` carries the full diagnostic.\n return `# ${check.name}: see \\`mu doctor\\` for full diagnostic`;\n default:\n // Forward-compat: any future check name falls back to the\n // textual `mu doctor` verb. Read-only.\n return \"mu doctor\";\n }\n}\n\n/**\n * A short paragraph (one paragraph per check name) explaining the\n * shape of the failure / warning. Returned as a `readonly string[]`\n * so the popup's drill body can interleave the lines with other\n * content; CLI consumers can `.join(\"\\n\")` themselves.\n *\n * Pure; exported for unit tests + SDK reuse.\n */\nexport function remediationParagraph(check: DoctorCheck): readonly string[] {\n switch (check.name) {\n case \"agents\":\n return [\n \"A 'ghost pane' is a registered agent whose tmux pane is gone.\",\n \"Doctor only reports the count. Run `mu state` or `mu agent list`\",\n \"to reap ghost agents and return their IN_PROGRESS tasks to OPEN.\",\n \"The TUI is read-only, but its slow tick uses the same state reap.\",\n ];\n case \"panes\":\n return [\n \"An 'orphan pane' is a live tmux pane in the workstream's\",\n \"session that mu doesn't know about. Adopt it via\",\n \"`mu agent adopt <pane-id>` to register it as a managed agent,\",\n \"or kill it manually if it's not yours.\",\n ];\n case \"workspaces\":\n return [\n \"An 'orphan workspace dir' is a per-agent VCS workspace under\",\n \"the workstream that has no matching mu agent row. Run\",\n \"`mu workspace orphans -w <ws>` to list them, then\",\n \"`mu workspace free <agent>` to release each one.\",\n ];\n case \"schema\":\n return [\n \"Missing tables typically mean an older mu binary opened the\",\n \"DB without running migrations. Rebuild mu (npm run build)\",\n \"and re-open; openDb runs the migration block on every\",\n \"process start.\",\n ];\n case \"schema_version\":\n return [\n \"Schema version mismatch means the DB was opened by a\",\n \"different mu binary than the one running now. If `<` the\",\n \"expected version, openDb should have migrated — check the\",\n \"build. If `>` the expected, you may have a downgrade in\",\n \"progress; restore from a snapshot rather than continuing.\",\n ];\n case \"journal_mode\":\n return [\n \"WAL is the default journal mode for SQLite under mu. A\",\n \"different mode (e.g. `delete`) means an external tool\",\n \"rewrote the DB pragma. Re-open the DB with mu to restore.\",\n ];\n case \"foreign_keys\":\n return [\n \"Foreign keys must be ON for mu's CASCADE deletes to work.\",\n \"An OFF value usually means an external SQLite client opened\",\n \"the DB without `PRAGMA foreign_keys = ON`. Re-open with mu.\",\n ];\n default:\n return [\"See `mu doctor` for the canonical textual diagnostic.\"];\n }\n}\n","// SDK seam for `mu state` (static) and the interactive TUI.\n//\n// Both renderers (the legacy cli-table3-based static fallback in\n// src/cli/state.ts and the new ink-based TUI in src/cli/tui/) consume\n// the same WorkstreamSnapshot. Pure data + a few small derivation\n// helpers — no rendering. See design_sdk_seam in workstream `tui` for\n// the rationale (`mu task notes design_sdk_seam -w tui`).\n\nimport { type AgentRow, type AgentStatus, type LiveAgentsView, listLiveAgents } from \"./agents.js\";\nimport type { Db } from \"./db.js\";\nimport { type DoctorSummary, loadDoctorSummary } from \"./doctor-summary.js\";\nimport { type LogRow, listLogs } from \"./logs.js\";\nimport {\n type TaskRow,\n listBlocked,\n listInProgress,\n listReady,\n listRecentClosed,\n listTasks,\n listTasksByOwner,\n} from \"./tasks.js\";\nimport { type Track, getParallelTracks } from \"./tracks.js\";\nimport { type CommitSummary, type VcsBackendName, detectBackend } from \"./vcs.js\";\nimport {\n type WorkspaceOrphan,\n type WorkspaceRow,\n decorateWithDirty,\n decorateWithStaleness,\n listWorkspaceOrphans,\n listWorkspaces,\n} from \"./workspace.js\";\n\n// ─── WorkstreamSnapshot ───────────────────────────────────────────\n\nexport interface WorkstreamSnapshot {\n workstreamName: string;\n view: LiveAgentsView;\n tracks: Track[];\n ready: TaskRow[];\n inProgress: TaskRow[];\n blocked: TaskRow[];\n recentClosed: TaskRow[];\n /** Populated only when callers explicitly pass `withAllTasks: true`.\n * The TUI dashboard fast tick leaves this empty and the all-tasks\n * popup reads its exhaustive list directly from SQLite while open. */\n allTasks: TaskRow[];\n workspaces: WorkspaceRow[];\n workspaceOrphans: WorkspaceOrphan[];\n recent: LogRow[];\n /** Last N commits from the project root (process.cwd()), populated\n * when `loadWorkstreamSnapshot` is called with withRecentCommits.\n * This is intentionally NOT a per-agent workspace log. */\n recentCommits: CommitSummary[];\n /** Backend that produced recentCommits. Null when recent commits were\n * not requested or no VCS backend was detected. */\n commitsBackend?: VcsBackendName | null;\n /** Populated when `loadWorkstreamSnapshot` is called with\n * `withDoctor: true`. Used by the TUI's slot-9 Doctor card to\n * render a glanceable health badge on the dashboard\n * (feat_card_9_doctor, workstream `tui-impl`). The static `mu\n * state` card and `mu doctor` itself don't consume it — they\n * read the textual doctor card directly. Null when not requested. */\n doctor: DoctorSummary | null;\n}\n\nexport interface LoadWorkstreamSnapshotOptions {\n /** Recent-events cap (default 200). */\n eventLimit?: number;\n /** When true, slow snapshot loading also populates `WorkspaceRow.dirty`\n * via decorateWithDirty (one `git status --porcelain` shellout per row,\n * capped at DECORATE_CONCURRENCY). The TUI caches this slow-tier value\n * and merges it into every fast SQL tick. */\n withDirty?: boolean;\n /** When true, slow snapshot loading also populates\n * `WorkstreamSnapshot.doctor` via `loadDoctorSummary`. The summary is\n * cheap SQL, but it reports tmux/workspace drift from slow-tier fields,\n * so the TUI refreshes it with the subprocess tier. */\n withDoctor?: boolean;\n /** Optional full task list for the TUI all-tasks popup. */\n withAllTasks?: true;\n /** Optional recent-project-commits slice for the TUI Commits card /\n * popup. Uses process.cwd() as the project root on purpose: the TUI\n * is launched from the project checkout, while worker workspaces live\n * elsewhere under the mu state dir. */\n withRecentCommits?: { limit: number };\n}\n\nexport interface WorkstreamSnapshotSlowFields {\n view: LiveAgentsView;\n /** Workspace rows decorated with slow-tier VCS observations\n * (`commitsBehindMain`, and `dirty` when requested). */\n workspaces: WorkspaceRow[];\n recentCommits: CommitSummary[];\n commitsBackend?: VcsBackendName | null;\n doctor: DoctorSummary | null;\n}\n\n/**\n * Fast TUI/state snapshot tier: pure SQLite reads only. Subprocess-backed\n * fields are intentionally empty placeholders so callers can merge the last\n * slow-tier values without blocking a 1s render tick on tmux or VCS probes.\n */\nexport async function loadWorkstreamSnapshotFast(\n db: Db,\n workstream: string,\n opts: LoadWorkstreamSnapshotOptions = {},\n): Promise<WorkstreamSnapshot> {\n const eventLimit = opts.eventLimit ?? 200;\n return {\n workstreamName: workstream,\n view: emptyLiveAgentsView(),\n tracks: getParallelTracks(db, workstream),\n ready: listReady(db, workstream).sort(byRoiDesc),\n inProgress: listInProgress(db, workstream),\n blocked: listBlocked(db, workstream),\n recentClosed: listRecentClosed(db, workstream),\n allTasks: opts.withAllTasks === true ? listTasks(db, workstream) : [],\n workspaces: listWorkspaces(db, workstream),\n workspaceOrphans: listWorkspaceOrphans(db, workstream),\n recent: listLogs(db, { workstream, kind: \"event\", limit: eventLimit }),\n recentCommits: [],\n commitsBackend: null,\n doctor: null,\n };\n}\n\n/**\n * Slow snapshot tier: fields backed by tmux / VCS subprocess probes (plus\n * doctor, which reports over those slow-tier observations). Returns only the\n * fields the fast snapshot deliberately leaves empty or undecorated.\n *\n * The slow snapshot tier runs full reconciliation: missing panes are\n * reaped, while mid-spawn placeholders remain protected by the prune\n * loop's pending-pane guard.\n */\nexport async function loadWorkstreamSnapshotSlow(\n db: Db,\n workstream: string,\n opts: LoadWorkstreamSnapshotOptions = {},\n baseSnapshot?: WorkstreamSnapshot,\n): Promise<WorkstreamSnapshotSlowFields> {\n const view = await listLiveAgents(db, { workstream });\n let workspaces = listWorkspaces(db, workstream);\n if (opts.withDirty === true) workspaces = await decorateWithDirty(workspaces);\n const commits = await loadRecentCommits(opts.withRecentCommits);\n const slow: WorkstreamSnapshotSlowFields = {\n view,\n workspaces,\n recentCommits: commits.items,\n commitsBackend: commits.backend,\n doctor: null,\n };\n if (opts.withDoctor === true) {\n slow.doctor = loadDoctorSummary(\n db,\n mergeSnapshotFastSlow(baseSnapshot ?? minimalSnapshot(workstream), slow),\n );\n }\n return slow;\n}\n\n/** Merge the latest slow-tier subprocess observations into a fresh fast tier. */\nexport function mergeSnapshotFastSlow(\n fast: WorkstreamSnapshot,\n slow: WorkstreamSnapshotSlowFields | null,\n): WorkstreamSnapshot {\n if (slow === null) return fast;\n return {\n ...fast,\n view: slow.view,\n workspaces: mergeWorkspaceSlowFields(fast.workspaces, slow.workspaces),\n recentCommits: slow.recentCommits,\n commitsBackend: slow.commitsBackend ?? null,\n doctor: slow.doctor,\n };\n}\n\n/**\n * Back-compat wrapper for non-TUI callers: return the historical union shape\n * by composing the new fast SQL tier with one slow subprocess tier.\n */\nexport async function loadWorkstreamSnapshot(\n db: Db,\n workstream: string,\n opts: LoadWorkstreamSnapshotOptions = {},\n): Promise<WorkstreamSnapshot> {\n const fast = await loadWorkstreamSnapshotFast(db, workstream, opts);\n const fastWithStaleness: WorkstreamSnapshot = {\n ...fast,\n workspaces: await decorateWithStaleness(fast.workspaces),\n };\n const slow = await loadWorkstreamSnapshotSlow(db, workstream, opts, fastWithStaleness);\n return mergeSnapshotFastSlow(fastWithStaleness, slow);\n}\n\nfunction emptyLiveAgentsView(): LiveAgentsView {\n return {\n agents: [],\n orphans: [],\n report: { prunedGhosts: 0, statusChanges: 0, orphans: [], mode: \"full\" },\n };\n}\n\nfunction minimalSnapshot(workstream: string): WorkstreamSnapshot {\n return {\n workstreamName: workstream,\n view: emptyLiveAgentsView(),\n tracks: [],\n ready: [],\n inProgress: [],\n blocked: [],\n recentClosed: [],\n allTasks: [],\n workspaces: [],\n workspaceOrphans: [],\n recent: [],\n recentCommits: [],\n commitsBackend: null,\n doctor: null,\n };\n}\n\nfunction mergeWorkspaceSlowFields(\n fastRows: readonly WorkspaceRow[],\n slowRows: readonly WorkspaceRow[],\n): WorkspaceRow[] {\n const slowByAgent = new Map(slowRows.map((row) => [row.agentName, row]));\n return fastRows.map((fast) => {\n const slow = slowByAgent.get(fast.agentName);\n if (slow === undefined) return fast;\n return {\n ...fast,\n commitsBehindMain: slow.commitsBehindMain ?? fast.commitsBehindMain,\n dirty: slow.dirty,\n };\n });\n}\n\nasync function loadRecentCommits(\n opt: LoadWorkstreamSnapshotOptions[\"withRecentCommits\"],\n): Promise<{ backend: VcsBackendName | null; items: CommitSummary[] }> {\n if (opt === undefined) return { backend: null, items: [] };\n const projectRoot = process.cwd();\n const backend = await detectBackend(projectRoot);\n if (backend.name === \"none\") return { backend: null, items: [] };\n return { backend: backend.name, items: await backend.recentCommits(projectRoot, opt.limit) };\n}\n\n// ─── ROI helpers ───────────────────────────────────────────────────\n\n/**\n * ROI tiers used to colour task rows. Pure: returns the bucket name; the\n * consumer maps bucket → picocolors function (or ink text colour).\n * Magic numbers (≥100 high, ≥50 mid) lifted from the previous HUD impl.\n */\nexport type RoiBucket = \"high\" | \"mid\" | \"low\" | \"infinite\";\n\nexport function roiBucket(impact: number, effortDays: number): RoiBucket {\n const r = effortDays > 0 ? impact / effortDays : Number.POSITIVE_INFINITY;\n if (!Number.isFinite(r)) return \"infinite\";\n if (r >= 100) return \"high\";\n if (r >= 50) return \"mid\";\n return \"low\";\n}\n\n/** ROI sort comparator (descending). Used by loadWorkstreamSnapshot.ready. */\nfunction byRoiDesc(a: TaskRow, b: TaskRow): number {\n const ra = a.effortDays > 0 ? a.impact / a.effortDays : Number.POSITIVE_INFINITY;\n const rb = b.effortDays > 0 ? b.impact / b.effortDays : Number.POSITIVE_INFINITY;\n if (rb !== ra) return rb - ra;\n if (a.effortDays !== b.effortDays) return a.effortDays - b.effortDays;\n return a.name.localeCompare(b.name);\n}\n\n// ─── Agent helpers ─────────────────────────────────────────────────\n\n/** Histogram of agents by status. Pure derivation (no colour render). */\nexport function agentStatusHistogram(\n agents: readonly AgentRow[],\n): ReadonlyMap<AgentStatus, number> {\n const out = new Map<AgentStatus, number>();\n for (const a of agents) {\n out.set(a.status, (out.get(a.status) ?? 0) + 1);\n }\n return out;\n}\n\n// ─── Task helpers ──────────────────────────────────────────────────\n\nexport interface OwnedTasksSummary {\n /** Display token: \"—\" (none) | \"<task_id>\" (one) | \"⊕<N>\" (many). */\n bit: string;\n /** Underlying count for callers that want their own format. */\n count: number;\n /** The single owned task's local id, when count===1. */\n onlyTaskId?: string;\n}\n\n/**\n * Per-agent task summary: condensed display token + raw count. Used by\n * both the static Agents table and the ink Agents card. Pure on the\n * input rows — caller (e.g. loadWorkstreamSnapshot consumer) does the\n * listTasksByOwner query upstream and feeds the rows in.\n */\nexport function summarizeOwnedTasks(owned: readonly TaskRow[]): OwnedTasksSummary {\n const count = owned.length;\n if (count === 0) return { bit: \"—\", count: 0 };\n if (count === 1) {\n const only = owned[0];\n if (!only) return { bit: \"—\", count: 0 };\n return { bit: only.name, count: 1, onlyTaskId: only.name };\n }\n return { bit: `⊕${count}`, count };\n}\n\n// Re-export for convenience: callers wanting to combine listTasksByOwner\n// with summarizeOwnedTasks in one import.\nexport { listTasksByOwner };\n"],"mappings":"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","names":["resolve","out","panes","mkdirSync","join","dirname","join","rowFromDb","join","dirname","mkdirSync","join","rowFromDb","existsSync","statSync","unlinkSync","existsSync","unlinkSync","rowFromDb","statSync","existsSync","unlinkSync","Database","existsSync","unlinkSync","Database","rowFromDb","join","rowFromDb","workspacePath","join","existsSync","rmSync","homedir","resolve","workspacePath","existsSync","resolve","execFile","existsSync","dirname","promisify","workspacePath","promisify","execFile","dirname","existsSync","existsSync","workspacePath","resolve","existsSync","existsSync","workspacePath","existsSync","existsSync","workspacePath","existsSync","existsSync","workspacePath","resolve","homedir","existsSync","rmSync","rowFromDb","rows","join","join","rowFromDb","sql","rowFromDb","existsSync","readdirSync","join","resolve","existsSync","mkdirSync","statSync","dirname","join","existsSync","dirname","join","statSync","mkdirSync","result","join","existsSync","readdirSync","resolve","row","resolve","snap","realExecutor","execa","currentExecutor","rowFromDb","rows","pc","pc","randomUUID","existsSync","mkdirSync","readFileSync","unlinkSync","writeFileSync","hostname","dirname","join","fileURLToPath","createHash","shellQuote","createHash","shellQuote","existsSync","mkdirSync","dirname","unlinkSync","quoteSqlString","writeFileSync","listLocalWorkstreams","join","randomUUID","readFileSync","hostname","fileURLToPath","count"]}
|