@mu-crew/mu 3.13.3 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +73 -35
- package/README.md +71 -57
- package/dist/chunk-4ULY3ZKO.js +4482 -0
- package/dist/chunk-4ULY3ZKO.js.map +1 -0
- package/dist/chunk-7M634JMD.js +375 -0
- package/dist/chunk-7M634JMD.js.map +1 -0
- package/dist/{chunk-3BARPJLY.js → chunk-PZ5GOCWH.js} +16 -2
- package/dist/chunk-PZ5GOCWH.js.map +1 -0
- package/dist/chunk-SS7NQSSZ.js +3660 -0
- package/dist/chunk-SS7NQSSZ.js.map +1 -0
- package/dist/chunk-UCZPJX7K.js +14253 -0
- package/dist/chunk-UCZPJX7K.js.map +1 -0
- package/dist/{cli-CKOMHBXV.js → cli-TV3N47AZ.js} +7317 -4593
- package/dist/cli-TV3N47AZ.js.map +1 -0
- package/dist/cli.js +2 -3
- package/dist/cli.js.map +1 -1
- package/dist/errors-GRONKWFS.js +65 -0
- package/dist/extension/mu-pi.js +1632 -837
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +4067 -2561
- package/dist/index.js +614 -297
- package/dist/index.js.map +1 -1
- package/dist/tui-FUBQ326D.js +1848 -0
- package/dist/tui-FUBQ326D.js.map +1 -0
- package/docs/ARCHITECTURE.md +118 -66
- package/docs/VOCABULARY.md +113 -49
- package/docs/guide/README.md +3 -3
- package/docs/guide/backends.md +8 -4
- package/docs/guide/cleanup.md +69 -33
- package/docs/guide/delegate.md +149 -66
- package/docs/guide/dispatch.md +148 -53
- package/docs/guide/getting-started.md +37 -44
- package/docs/guide/other-agent-clis.md +102 -0
- package/docs/guide/recovery.md +31 -50
- package/docs/guide/remote.md +122 -45
- package/docs/guide/sql.md +104 -24
- package/docs/guide/stop-a-worker.md +30 -37
- package/docs/guide/sync.md +11 -3
- package/docs/guide/upgrade.md +93 -43
- package/docs/reference/env.md +24 -32
- package/docs/reference/naming.md +14 -7
- package/docs/reference/surface-contract.md +153 -0
- package/package.json +33 -11
- package/prompts/mu-debug.md +3 -1
- package/prompts/mu-refute.md +2 -1
- package/prompts/mu-research.md +3 -1
- package/prompts/mu-review.md +2 -1
- package/prompts/mu-rules-audit.md +5 -4
- package/prompts/mu-sweep.md +2 -1
- package/prompts/mu-tournament.md +4 -2
- package/prompts/mu-until.md +3 -1
- package/prompts/ultrathink.md +1 -1
- package/skills/mu/SKILL.md +60 -291
- package/skills/mu/recipes/adversarial-review.md +12 -103
- package/skills/mu/recipes/brief.md +14 -61
- package/skills/mu/recipes/course-correct.md +23 -0
- package/skills/mu/recipes/findings.md +17 -111
- package/skills/mu/recipes/models.md +17 -92
- package/skills/mu/recipes/move.md +14 -0
- package/skills/mu/recipes/orchestrator-loop.md +20 -119
- package/skills/mu/recipes/recovery.md +8 -29
- package/skills/mu/recipes/remote-workers.md +17 -318
- package/skills/mu/recipes/tasks-or-calls.md +15 -78
- package/skills/mu/recipes/test-health.md +19 -77
- package/skills/mu/recipes/worker.md +17 -24
- package/dist/chunk-45KQLMEL.js +0 -2908
- package/dist/chunk-45KQLMEL.js.map +0 -1
- package/dist/chunk-G434JKV2.js +0 -641
- package/dist/chunk-G434JKV2.js.map +0 -1
- package/dist/chunk-KUGUFBWH.js +0 -12527
- package/dist/chunk-KUGUFBWH.js.map +0 -1
- package/dist/chunk-QITLNZEM.js +0 -87
- package/dist/chunk-QITLNZEM.js.map +0 -1
- package/dist/chunk-VIEYQMQA.js +0 -393
- package/dist/chunk-VIEYQMQA.js.map +0 -1
- package/dist/chunk-WI6AWCMK.js +0 -19
- package/dist/chunk-WI6AWCMK.js.map +0 -1
- package/dist/cli-CKOMHBXV.js.map +0 -1
- package/dist/devtools-LS4ZEPWM.js +0 -3772
- package/dist/devtools-LS4ZEPWM.js.map +0 -1
- package/dist/tui-LFJ5P4Y2.js +0 -40956
- package/dist/tui-LFJ5P4Y2.js.map +0 -1
- package/dist/tui-sync-worker.js +0 -17
- package/dist/tui-sync-worker.js.map +0 -1
- package/dist/yank-XDOONQSL.js +0 -13
- package/dist/yank-XDOONQSL.js.map +0 -1
- package/docs/ROADMAP.md +0 -123
- package/docs/VISION.md +0 -298
- package/docs/architecture/control-socket.md +0 -142
- package/docs/architecture/dag.md +0 -117
- package/docs/architecture/mux.md +0 -167
- package/docs/architecture/ops-log.md +0 -160
- package/docs/architecture/sdk.md +0 -187
- package/docs/architecture/sync.md +0 -159
- package/docs/architecture/tui.md +0 -165
- package/docs/guide/tui.md +0 -98
- package/docs/history/CHANGELOG-pre-3.md +0 -4866
- package/docs/img/tui-dashboard.png +0 -0
- package/docs/specs/2026-09-28-agent-state-from-murmur.md +0 -333
- package/docs/specs/2026-09-29-task-substates.md +0 -247
- package/docs/specs/2026-10-03-keep-driving-nudge.md +0 -163
- package/docs/specs/2026-10-21-fast-and-slow-gates.md +0 -229
- package/skills/mu/recipes/backlog-triage.md +0 -45
- package/skills/mu/recipes/codemode-driver.md +0 -118
- package/skills/mu/recipes/deep-research.md +0 -69
- package/skills/mu/recipes/drift-audit.md +0 -75
- package/skills/mu/recipes/fan-out.md +0 -42
- package/skills/mu/recipes/hypothesis-panel.md +0 -48
- package/skills/mu/recipes/long-run.md +0 -32
- package/skills/mu/recipes/loop-until-done.md +0 -45
- package/skills/mu/recipes/plan.md +0 -67
- package/skills/mu/recipes/refute.md +0 -55
- package/skills/mu/recipes/review-panel.md +0 -65
- package/skills/mu/recipes/rules-audit.md +0 -89
- package/skills/mu/recipes/tournament.md +0 -44
- package/skills/mu/recipes/ultrathink.md +0 -86
- package/skills/mu/recipes/watcher.md +0 -28
- package/skills/mu/recipes/waves.md +0 -21
- /package/dist/{chunk-3BARPJLY.js.map → errors-GRONKWFS.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/undo.ts"],"sourcesContent":["// mu — undo: emit INVERSE OPS for one group.\n//\n// THE CHANGE IN SEMANTICS FROM THE OLD UNDO\n// ------------------------------\n// The old `mu undo --yes` swapped the whole DB file back to a snapshot. That\n// reverts your OTHER workstreams too — on the real dogfood DB, 812 tasks\n// across three workstreams — so it was a blunt instrument you hesitated\n// to reach for. It also did not sync, so a peer kept the mistake.\n//\n// `mu undo` emits inverse ops for one `group_id`:\n//\n// * GRANULAR — touches only the rows that action touched.\n// * COMPOSABLE — it is itself an ordinary op, in its own group, so it\n// syncs to peers and is ITSELF UNDOABLE. There is no\n// separate redo mechanism and no asymmetry between undo\n// and redo: undoing an undo is just undoing a group.\n// * NO FILES — no snapshots, no swap, nothing to prune.\n//\n// HOW AN INVERSE IS COMPUTED\n// --------------------------\n// Per op in the group, oldest-first:\n//\n// put that CREATED the row -> inverse is `del`\n// put that CHANGED fields -> inverse is a put restoring the PRIOR\n// value of exactly those fields\n// del -> inverse is a put restoring the row\n//\n// A field the group wrote more than once is restored once, from the value\n// before its FIRST write in the group: later writes' \"prior\" values are\n// the group's own intermediate states.\n//\n// \"Did this put create the row\" is answered by provenance, not by a flag:\n// a put created the row iff no op for that key precedes it. \"What was\n// this field's prior value\" is the newest op before this one that named\n// that field — the same shape of query src/apply.ts uses for per-field\n// LWW (its private `fieldHlc`), pointed backwards. `priorFieldValue`\n// below is the one place undo answers \"what was this field before\";\n// keep its predicates (json_type, the legacy-intent exclusion) in step\n// with apply's, or undo will quietly restore wrong values.\n//\n// WHY INVERSES GO THROUGH THE NORMAL WRITE PATH\n// ---------------------------------------------\n// This module MUTATES THE TABLES inside a `withOpContext` scope and lets\n// the capture triggers record the result. It does NOT hand-write rows\n// into `ops`, and it does NOT use applyOp (which is capture-SUPPRESSED,\n// being the ingest path). Consequences, all of them the point:\n//\n// * The inverse gets a fresh HLC from the same clock as any other edit,\n// so it is newest and therefore wins per-field LWW.\n// * It is captured like any other change, so it appears in `mu log`,\n// flushes to this machine's segment, and is visible to the drift\n// check. `mu doctor --deep` stays clean after an undo, which is the\n// strongest end-to-end proof available that undo did not corrupt the\n// projection.\n// * It lands in its own group, which is what makes undoing an undo work\n// with no extra machinery.\n//\n// THE SUPERSESSION DECISION\n// -------------------------\n// Undoing a group whose rows were changed AGAIN by a later group is the\n// hard case. Because the inverse gets a fresh (newest) HLC, it would WIN\n// per-field LWW and silently clobber that newer work. Three options:\n//\n// (a) clobber silently — destroys work the operator did after\n// the mistake. Unacceptable.\n// (b) skip superseded fields — \"undo\" that partially does nothing,\n// silently. Equally unacceptable: the\n// operator believes the action is undone.\n// (c) REFUSE, name the conflict, require an explicit flag.\n//\n// (c) is implemented. `planUndo` detects, per field, whether a LATER op\n// (from a different group) has written that field since, and reports it\n// as a `supersededBy` conflict. `updated_at` is the exception: almost\n// every write touches it (a `task note` touches its task), so a later\n// write to it is not a conflict; the undo just leaves the newer\n// timestamp. `undoGroup` refuses unless\n// `opts.force === true`. So the default is safe and loud, and the\n// override exists and says what it will destroy. Fail safe, never fail\n// silent — the same rule capture follows.\n//\n// FK ORDERING\n// -----------\n// A group can span entities: a cascade close writes N task ops, and a\n// workstream teardown writes tombstones for the whole tree. Restoring a\n// task before its workstream violates the FK, so inverses are applied in\n// DEPENDENCY ORDER (workstream -> task -> note/edge), not merely in\n// reverse emission order. See ENTITY_RESTORE_ORDER.\n\nimport { repairTaskPair } from \"./apply.js\";\nimport { type Db, resolveWorkstreamId } from \"./db.js\";\nimport { LEGACY_LOG_ONLY_SQL_EXCLUSION } from \"./legacy-ops.js\";\nimport { groupIdFromPrefix } from \"./logs.js\";\nimport { withOpContext } from \"./op-context.js\";\nimport type { HasNextSteps, NextStep } from \"./output.js\";\nimport { localMachineId } from \"./segments.js\";\nimport { mapLegacyStatus, resolvePair } from \"./tasks/status.js\";\n\n/** One op as stored, with the provenance we need to invert it. */\ninterface GroupOpRow {\n seq: number;\n hlc: string;\n group_id: string;\n machine_id: string;\n intent: string | null;\n actor: string | null;\n entity: string;\n key: string;\n op: string;\n payload: string;\n /** Ops for this key with a strictly older HLC. */\n prior: number;\n /** Kind of the NEWEST op preceding this one for the same key, or null\n * when there is none. 'del' means this put RESURRECTED the row, which\n * is a creation for undo purposes even though prior > 0. */\n priorKind: string | null;\n}\n\n/** What undoing one op will do. */\nexport interface InverseOp {\n /** Entity of the row being restored/removed. */\n entity: string;\n /** Natural key of that row. */\n key: string;\n /** The inverse action. */\n op: \"put\" | \"del\";\n /** Field -> prior value, for a `put`. Empty for a `del`. */\n fields: Record<string, string | number | null>;\n /** Human summary, for the dry-run listing. */\n summary: string;\n /** Fields that a LATER group has written since, with the group that\n * did. Non-empty means applying this inverse would clobber newer\n * work. */\n supersededBy: Array<{ field: string; groupId: string; intent: string | null }>;\n}\n\nexport interface UndoPlan {\n /** The group being undone. */\n groupId: string;\n /** The intent(s) of the ops in the group — what the operator did. */\n intents: readonly string[];\n /** When the group was written (ISO, from the oldest op). */\n when: string;\n /** Inverse ops, in the order they will be applied (FK-safe). */\n inverses: readonly InverseOp[];\n /** True iff any inverse would clobber a newer edit. */\n superseded: boolean;\n /** Ops in the group that need no inverse (already reverted, or a\n * no-op), for an honest count. */\n skipped: number;\n}\n\nexport interface UndoResult {\n plan: UndoPlan;\n /** Group id of the ops the UNDO itself wrote — pass this to\n * `mu undo` to redo. Null when no inverse changed a row: the undo\n * wrote no ops, so there is no group to redo. */\n undoGroupId: string | null;\n /** Inverse ops that actually changed a row. */\n applied: number;\n}\n\n/** Raised when the requested group does not exist. */\nexport class UndoGroupNotFoundError extends Error implements HasNextSteps {\n constructor(readonly groupId: string) {\n super(`no ops found for group ${JSON.stringify(groupId)}`);\n this.name = \"UndoGroupNotFoundError\";\n }\n\n errorNextSteps(): NextStep[] {\n return [\n { intent: \"List recent undoable groups\", command: \"mu undo\" },\n { intent: \"Inspect a group's ops\", command: \"mu log --group <id>\" },\n ];\n }\n}\n\n/** Raised when there is nothing to undo at all. */\nexport class NothingToUndoError extends Error implements HasNextSteps {\n constructor() {\n super(\"the ops log is empty; nothing to undo\");\n this.name = \"NothingToUndoError\";\n }\n\n errorNextSteps(): NextStep[] {\n return [{ intent: \"Make a change first\", command: \"mu task add <id> -t <title>\" }];\n }\n}\n\n/** Raised when the group's rows were changed by a later group, so\n * undoing would clobber that newer work. */\nexport class UndoSupersededError extends Error implements HasNextSteps {\n constructor(\n readonly groupId: string,\n readonly conflicts: readonly { key: string; field: string; groupId: string }[],\n ) {\n const shown = conflicts\n .slice(0, 5)\n .map((c) => `${c.key}.${c.field} (changed by ${c.groupId.slice(0, 8)})`)\n .join(\", \");\n super(\n `group ${groupId.slice(0, 8)} has been superseded: ${conflicts.length} field(s) were changed ` +\n `by a later action — ${shown}. Undoing would discard that newer work.`,\n );\n this.name = \"UndoSupersededError\";\n }\n\n errorNextSteps(): NextStep[] {\n return [\n {\n intent: \"Inspect what changed since\",\n command: `mu log --group ${this.groupId.slice(0, 8)}`,\n },\n {\n intent: \"Undo anyway, discarding the newer edits\",\n command: `mu undo ${this.groupId.slice(0, 8)} --yes --force`,\n },\n ];\n }\n}\n\n// ─── entity ordering ──────────────────────────────────────────────────\n\n/**\n * Dependency order for RESTORING rows. A workstream must exist before its\n * tasks, and a task before its notes and edges, or the FK rejects the\n * insert.\n *\n * Not simply \"reverse of emission order\": a teardown emits the workstream\n * tombstone FIRST (it is the row the operator deleted; the rest are FK\n * cascade victims), so reversing would try to restore notes before their\n * task. Sorting by entity depth is correct regardless of how the group\n * happened to be emitted.\n */\nconst ENTITY_RESTORE_ORDER: Record<string, number> = {\n workstream: 0,\n task: 1,\n note: 2,\n edge: 2,\n};\n\n/** Entity depth for ordering inverses. Restores sort by ascending rank\n * (parents first); deletes sort by descending rank, the mirror image\n * (children first, so the workstream goes last). */\nfunction restoreRank(entity: string): number {\n return ENTITY_RESTORE_ORDER[entity] ?? 3;\n}\n\n// ─── provenance ───────────────────────────────────────────────────────\n\n/**\n * The value `field` held for `key` immediately BEFORE `hlc`.\n *\n * This is the provenance query src/apply.ts uses for per-field LWW,\n * pointed backwards: the newest op strictly older than `hlc` that NAMED\n * this field. `json_type(...) IS NOT NULL` rather than\n * `json_extract(...) IS NOT NULL` for the same reason apply does it —\n * json_extract returns SQL NULL both for an absent key and for a\n * present-but-null one, so a set-to-NULL would look absent and we would\n * restore the wrong (older) value.\n *\n * Returns `{ found: false }` when no earlier op named the field, which\n * means the field had no value before this op — so there is nothing to\n * restore and the op must have been part of the row's creation.\n *\n * `machineId` limits the lookup to one machine's ops. Undo passes it for\n * `owner_id`, whose live value only this machine's ops ever wrote (apply\n * strips it from peers' ops).\n */\nexport function priorFieldValue(\n db: Db,\n entity: string,\n key: string,\n hlc: string,\n field: string,\n machineId?: string,\n): { found: true; value: string | number | null } | { found: false } {\n const row = db\n .prepare(\n `SELECT json_extract(payload, '$.' || @field) AS value\n FROM ops\n WHERE entity = @entity\n AND key = @key\n AND op = 'put'\n AND hlc < @hlc\n AND (@machineId IS NULL OR machine_id = @machineId)\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}\n AND json_type(payload, '$.' || @field) IS NOT NULL\n ORDER BY hlc DESC\n LIMIT 1`,\n )\n .get({ entity, key, hlc, field, machineId: machineId ?? null }) as\n | { value: string | number | null }\n | undefined;\n if (row === undefined) return { found: false };\n return { found: true, value: row.value };\n}\n\n/** Groups that wrote `field` of `key` AFTER `hlc`. Non-empty means the\n * field has been superseded since the group we are undoing. `machineId`\n * as in `priorFieldValue`. */\nfunction laterWriters(\n db: Db,\n entity: string,\n key: string,\n hlc: string,\n field: string,\n excludeGroup: string,\n machineId?: string,\n): Array<{ groupId: string; intent: string | null }> {\n return db\n .prepare(\n `SELECT DISTINCT group_id AS groupId, intent\n FROM ops\n WHERE entity = @entity\n AND key = @key\n AND op = 'put'\n AND hlc > @hlc\n AND group_id <> @excludeGroup\n AND (@machineId IS NULL OR machine_id = @machineId)\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}\n AND json_type(payload, '$.' || @field) IS NOT NULL`,\n )\n .all({ entity, key, hlc, field, excludeGroup, machineId: machineId ?? null }) as Array<{\n groupId: string;\n intent: string | null;\n }>;\n}\n\n/** Groups that wrote ANY field of `key` AFTER `hlc`, regardless of which\n * field. Used for the two whole-row inverses (restore-from-tombstone,\n * delete-the-row-this-group-created) where the question is \"did a later\n * group touch this key at all\", not \"did it set one specific field\". */\nfunction laterRowWriters(\n db: Db,\n entity: string,\n key: string,\n hlc: string,\n excludeGroup: string,\n): Array<{ groupId: string; intent: string | null }> {\n return db\n .prepare(\n `SELECT DISTINCT group_id AS groupId, intent\n FROM ops\n WHERE entity = @entity\n AND key = @key\n AND op = 'put'\n AND hlc > @hlc\n AND group_id <> @excludeGroup\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}`,\n )\n .all({ entity, key, hlc, excludeGroup }) as Array<{\n groupId: string;\n intent: string | null;\n }>;\n}\n\n/** True iff the newest op for `key` is a tombstone written by a group\n * other than `groupId`, i.e. the row is gone and not by this group's\n * own hand. */\nfunction isDeletedByLaterGroup(db: Db, entity: string, key: string, groupId: string): boolean {\n const row = db\n .prepare(\n `SELECT op, group_id AS groupId FROM ops\n WHERE entity = @entity AND key = @key\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}\n ORDER BY hlc DESC LIMIT 1`,\n )\n .get({ entity, key }) as { op: string; groupId: string } | undefined;\n return row?.op === \"del\" && row.groupId !== groupId;\n}\n\n/** True iff a later group DELETED this key after `hlc`. Undoing a change\n * to a row that has since been deleted would resurrect it. */\nfunction laterDeleters(\n db: Db,\n entity: string,\n key: string,\n hlc: string,\n excludeGroup: string,\n): Array<{ groupId: string; intent: string | null }> {\n return db\n .prepare(\n `SELECT DISTINCT group_id AS groupId, intent\n FROM ops\n WHERE entity = @entity\n AND key = @key\n AND op = 'del'\n AND hlc > @hlc\n AND group_id <> @excludeGroup`,\n )\n .all({ entity, key, hlc, excludeGroup }) as Array<{ groupId: string; intent: string | null }>;\n}\n\n// ─── group discovery ──────────────────────────────────────────────────\n\nexport interface GroupSummary {\n groupId: string;\n /** Distinct intents in the group, in first-seen order. */\n intents: readonly string[];\n actor: string | null;\n /** Ops in the group. */\n ops: number;\n /** ISO timestamp of the group's oldest op. */\n when: string;\n /** Newest HLC in the group, for ordering. */\n hlc: string;\n}\n\n/**\n * Recent groups, newest first. This is how group ids become DISCOVERABLE:\n * `mu undo` with no argument lists these, so the operator never has to\n * know a uuid to use the verb.\n *\n * Only groups that touched a portable table are listed, because those are\n * the only ones with anything to invert.\n */\nexport function listRecentGroups(db: Db, limit = 10): GroupSummary[] {\n const rows = db\n .prepare(\n `SELECT group_id AS groupId,\n MAX(hlc) AS hlc,\n MIN(created_at) AS when_,\n COUNT(*) AS ops,\n MAX(actor) AS actor\n FROM ops\n WHERE entity IN ('workstream','task','note','edge')\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}\n GROUP BY group_id\n ORDER BY MAX(hlc) DESC\n LIMIT @limit`,\n )\n .all({ limit }) as Array<{\n groupId: string;\n hlc: string;\n when_: string;\n ops: number;\n actor: string | null;\n }>;\n\n return rows.map((row) => {\n const intents = (\n db\n .prepare(\n `SELECT DISTINCT intent FROM ops\n WHERE group_id = @groupId AND intent IS NOT NULL\n ORDER BY seq`,\n )\n .all({ groupId: row.groupId }) as { intent: string }[]\n ).map((r) => r.intent);\n return {\n groupId: row.groupId,\n intents,\n actor: row.actor,\n ops: row.ops,\n when: row.when_,\n hlc: row.hlc,\n };\n });\n}\n\n/** The most recent undoable group, or null when there is none. */\nexport function mostRecentGroup(db: Db): GroupSummary | null {\n return listRecentGroups(db, 1)[0] ?? null;\n}\n\n/**\n * Resolve a possibly-abbreviated group id to a full one, or raise.\n *\n * Delegates to `groupIdFromPrefix` (src/logs.ts) so `mu undo` and\n * `mu log --group` accept EXACTLY the same identifiers. They used to\n * disagree: undo resolved prefixes, `mu log --group` compared the column\n * literally and silently returned nothing\n * (bug_group_id_prefix_asymmetry). One rule, two verbs.\n *\n * Ambiguity surfaces as `GroupIdAmbiguousError` (exit 4, a conflict the\n * operator resolves) rather than being folded into not-found.\n */\nexport function resolveGroupId(db: Db, prefix: string): string {\n const resolved = groupIdFromPrefix(db, prefix);\n if (resolved === null) throw new UndoGroupNotFoundError(prefix);\n return resolved;\n}\n\n// ─── planning ─────────────────────────────────────────────────────────\n\nconst CAPTURED_TABLE_FOR: Record<string, string> = {\n workstream: \"workstreams\",\n task: \"tasks\",\n note: \"task_notes\",\n edge: \"task_edges\",\n};\n\n/** Fields never restored by an undo: surrogate ids and the columns\n * encoded in the natural key (`local_id` / `name`). */\nconst NEVER_RESTORE = new Set([\"id\", \"local_id\", \"name\", \"workstream_id\", \"task_id\"]);\n\n/** `tasks.owner_id` is an FK into machine-local `agents`. Undoing a\n * claim or release restores it with the status/substate pair it was\n * written with, but only from THIS machine's ops (a peer's value names\n * a peer's agent) and only when that agent still exists. No local op\n * before it means the row came from a peer and the owner was NULL. A whole-row\n * restore from a tombstone leaves it NULL, as apply does. */\nconst OWNER_FIELD = \"owner_id\";\n\n/** Excluded from whole-row reconstruction (tombstone restores). */\nconst NEVER_RECONSTRUCT = new Set([...NEVER_RESTORE, OWNER_FIELD]);\n\n/** Bookkeeping timestamp written by nearly every task change, including\n * the parent touch of `mu task note`. A later write to it alone is not\n * a supersession: undo then leaves the newer timestamp in place. */\nconst TOUCH_FIELD = \"updated_at\";\n\n/**\n * Compute what undoing `groupId` would do, WITHOUT doing it.\n *\n * Pure with respect to the DB: reads only. `mu undo <group>` calls this\n * for its dry run and `undoGroup` calls it again before applying, so the\n * preview and the action can never diverge.\n */\nexport function planUndo(db: Db, groupId: string): UndoPlan {\n const rows = db\n .prepare(\n `SELECT o.seq, o.hlc, o.group_id, o.machine_id, o.intent, o.actor, o.entity, o.key, o.op, o.payload,\n (SELECT COUNT(*) FROM ops p\n WHERE p.entity = o.entity AND p.key = o.key AND p.hlc < o.hlc\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}) AS prior,\n (SELECT p.op FROM ops p\n WHERE p.entity = o.entity AND p.key = o.key AND p.hlc < o.hlc\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}\n ORDER BY p.hlc DESC LIMIT 1) AS priorKind\n FROM ops o\n WHERE o.group_id = @groupId\n AND o.entity IN ('workstream','task','note','edge')\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}\n ORDER BY o.hlc`,\n )\n .all({ groupId }) as GroupOpRow[];\n\n if (rows.length === 0) throw new UndoGroupNotFoundError(groupId);\n\n const inverses: InverseOp[] = [];\n let skipped = 0;\n /** Task keys already handled by the legacy-note recovery below, which\n * works per TASK rather than per tombstone (see there). */\n const seenLegacyNoteTasks = new Set<string>();\n /** Per row key, the fields an EARLIER op of this group already\n * restores. A group can write one field twice (`task.park` writes\n * updated_at in two statements); only the first write's prior value\n * is the pre-group state, so later writes add no inverse for it. */\n const restoredFields = new Map<string, Set<string>>();\n let machine: string | undefined;\n const localMachine = (): string => {\n machine ??= localMachineId(db);\n return machine;\n };\n\n for (const row of rows) {\n if (row.op === \"del\") {\n // Inverse of a delete: restore the row from the state the log says\n // it had. Reconstruct it by replaying every put for the key that\n // preceded the tombstone, so a partial-update history still yields\n // a complete row.\n //\n // When that fold comes back empty, fall back to the tombstone's\n // OWN payload. Note tombstones are self-describing (see\n // fullOldPayload in src/capture.ts) precisely because a note's key\n // embeds a non-portable rowid: after a reprojection the puts sit\n // under the old key and the fold finds nothing, which used to make\n // the restore a silent no-op (drift-641).\n let fields = reconstructRow(db, row.entity, row.key, row.hlc);\n if (Object.keys(fields).length === 0) fields = payloadFields(row.payload);\n // Third tier, for LEGACY note tombstones only. A note key embeds a\n // non-portable rowid, so after a reprojection the puts sit under a\n // different key than the later del (drift-641). Tombstones written\n // since that fix carry the row, so tier 2 resolves them — but ones\n // written BEFORE it carry '{}', and then both tiers come back\n // empty and the note is silently not restored. Recover from the\n // puts for the same TASK instead of the same key.\n //\n // Ordinal position cannot be used to pair them: notes are a\n // grow-only SET, and before 3.1.1 it was keyed on (task, content),\n // so identical prose collapsed to one row. Observed 5 puts against 3 dels for one\n // task, the surplus being repeated '[reaper]' notes — the Nth put\n // is therefore not the Nth del. So this restores the DISTINCT\n // contents the log holds for the task and lets the set semantics\n // settle the count, which reaches the same end state the dedupe\n // produced. Consequence: the notes come back, but which tombstone\n // restored which note is not reconstructible, so this runs once\n // per task rather than once per tombstone.\n if (Object.keys(fields).length === 0 && row.entity === \"note\") {\n const recovered = recoverLegacyNoteContents(db, row.key, row.hlc, seenLegacyNoteTasks);\n for (const legacy of recovered) {\n inverses.push({\n entity: \"note\",\n key: legacy.key,\n op: \"put\",\n fields: legacy.fields,\n summary: `note ${legacy.key} (recovered from legacy history)`,\n supersededBy: [],\n });\n }\n skipped += 1;\n continue;\n }\n // History predating v10 carries retired lifecycle values that the\n // task_substates FK rejects, so a restore maps them onto the pair\n // they now mean, exactly as the apply path does. Without this,\n // undoing a pre-v10 `workstream teardown` aborted the whole\n // transaction on the first DEFERRED task.\n const status = fields.status;\n const legacy = typeof status === \"string\" ? mapLegacyStatus(status) : null;\n if (legacy) {\n fields.status = legacy.status;\n fields.substate = legacy.substate;\n }\n const conflicts = laterRowWriters(db, row.entity, row.key, row.hlc, groupId).map((w) => ({\n field: \"<row>\",\n groupId: w.groupId,\n intent: w.intent,\n }));\n inverses.push({\n entity: row.entity,\n key: row.key,\n op: \"put\",\n fields,\n summary: `${row.entity} ${row.key} (from tombstone)`,\n supersededBy: conflicts,\n });\n continue;\n }\n\n // A put. Did it CREATE the row, or CHANGE fields?\n //\n // Creation is \"no prior op at all\" OR \"the newest prior op was a\n // tombstone\" — the latter is a RESURRECTION, which creates the row\n // just as much as a first insert does. Testing only `prior === 0`\n // gets this wrong for the very common case of undoing an undo of a\n // delete: the undo's restoring put has prior ops (the original add\n // and the delete), so it would be treated as a field change and its\n // inverse would restore fields instead of deleting the row.\n if (row.prior === 0 || row.priorKind === \"del\") {\n // Created it: the inverse is a delete. If the newest op for the\n // key is a delete by another group (an earlier undo of this one,\n // or a later delete), the row is gone and there is nothing to\n // revert.\n if (isDeletedByLaterGroup(db, row.entity, row.key, groupId)) {\n skipped += 1;\n continue;\n }\n // A later group that wrote this key is a supersession — deleting\n // would discard that work.\n const later = [...laterRowWriters(db, row.entity, row.key, row.hlc, groupId)];\n inverses.push({\n entity: row.entity,\n key: row.key,\n op: \"del\",\n fields: {},\n summary: `${row.entity} ${row.key} (it was created by this group)`,\n supersededBy: later.map((w) => ({ field: \"<row>\", groupId: w.groupId, intent: w.intent })),\n });\n continue;\n }\n\n // Changed fields: restore the prior value of exactly those fields.\n const payload = JSON.parse(row.payload) as Record<string, unknown>;\n const fields: Record<string, string | number | null> = {};\n const supersededBy: InverseOp[\"supersededBy\"] = [];\n const rowKey = `${row.entity}\\u0000${row.key}`;\n const restored = restoredFields.get(rowKey) ?? new Set<string>();\n restoredFields.set(rowKey, restored);\n for (const field of Object.keys(payload)) {\n if (NEVER_RESTORE.has(field) || restored.has(field)) continue;\n restored.add(field);\n let machineId: string | undefined;\n if (field === OWNER_FIELD) {\n // A peer's owner_id was never applied here, so there is nothing\n // of it to undo.\n if (row.machine_id !== localMachine()) continue;\n machineId = localMachine();\n }\n const prior =\n field === OWNER_FIELD\n ? priorOwner(db, row.key, row.hlc, localMachine())\n : priorFieldValue(db, row.entity, row.key, row.hlc, field, machineId);\n if (!prior.found) {\n // No earlier op named this field, so it had no prior value to go\n // back to. Nothing to restore for it.\n continue;\n }\n // The prior owner's agent row is gone (ON DELETE SET NULL): its id\n // would violate the FK, so the owner stays as it is.\n if (field === OWNER_FIELD && prior.value !== null && !agentExists(db, prior.value)) continue;\n const writers = laterWriters(db, row.entity, row.key, row.hlc, field, groupId, machineId);\n if (field === TOUCH_FIELD && writers.length > 0) continue;\n fields[field] = prior.value;\n for (const writer of writers) {\n supersededBy.push({ field, groupId: writer.groupId, intent: writer.intent });\n }\n }\n // A later DELETE also supersedes: restoring fields on a row that has\n // since been removed would resurrect it.\n for (const deleter of laterDeleters(db, row.entity, row.key, row.hlc, groupId)) {\n supersededBy.push({ field: \"<row>\", groupId: deleter.groupId, intent: deleter.intent });\n }\n\n if (Object.keys(fields).length === 0) {\n skipped += 1;\n continue;\n }\n inverses.push({\n entity: row.entity,\n key: row.key,\n op: \"put\",\n fields,\n summary: `${row.entity} ${row.key} ${Object.entries(fields)\n .map(([f, v]) => `${f}=${v === null ? \"NULL\" : String(v)}`)\n .join(\" \")}`,\n supersededBy,\n });\n }\n\n // FK-safe ordering. Restores must go parents-first; deletes\n // children-first. Sorting by entity depth is correct regardless of the\n // order the group happened to be emitted in.\n const ordered = [...inverses].sort((a, b) => {\n if (a.op !== b.op) return a.op === \"put\" ? -1 : 1; // restores before deletes\n const rank =\n a.op === \"put\"\n ? restoreRank(a.entity) - restoreRank(b.entity)\n : restoreRank(b.entity) - restoreRank(a.entity);\n return rank;\n });\n\n const intents = [...new Set(rows.map((r) => r.intent).filter((i): i is string => i !== null))];\n\n return {\n groupId,\n intents,\n when: groupWhen(db, groupId),\n inverses: ordered,\n superseded: ordered.some((i) => i.supersededBy.length > 0),\n skipped,\n };\n}\n\n/**\n * The owner_id a task had just before `hlc`, from this machine's ops.\n *\n * Always found: a task row only ever starts unowned. Apply strips a\n * peer's owner_id and inserts the row with owner_id NULL without\n * capturing, so a task created (or recreated after a tombstone) by a\n * peer has no local op naming owner_id. A local op older than a\n * tombstone that won described a row that no longer exists, so it does\n * not count either. A tombstone lost (and deleted nothing) when a newer\n * put had already arrived, i.e. one with a higher hlc and a lower seq:\n * the same check applyDel makes.\n */\nfunction priorOwner(\n db: Db,\n key: string,\n hlc: string,\n machineId: string,\n): { found: true; value: string | number | null } {\n const local = priorFieldValue(db, \"task\", key, hlc, OWNER_FIELD, machineId);\n if (!local.found) return { found: true, value: null };\n const tomb = db\n .prepare(\n `SELECT 1 FROM ops d\n WHERE d.entity = 'task' AND d.key = @key AND d.op = 'del' AND d.hlc < @hlc\n AND d.hlc > (SELECT MAX(hlc) FROM ops\n WHERE entity = 'task' AND key = @key AND op = 'put'\n AND hlc < @hlc AND machine_id = @machineId\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}\n AND json_type(payload, '$.owner_id') IS NOT NULL)\n AND NOT EXISTS (SELECT 1 FROM ops p\n WHERE p.entity = 'task' AND p.key = @key AND p.op = 'put'\n AND p.hlc > d.hlc AND p.seq < d.seq)\n LIMIT 1`,\n )\n .get({ key, hlc, machineId });\n return tomb === undefined ? local : { found: true, value: null };\n}\n\nfunction agentExists(db: Db, id: string | number): boolean {\n return db.prepare(\"SELECT 1 FROM agents WHERE id = ?\").get(id) !== undefined;\n}\n\nfunction groupWhen(db: Db, groupId: string): string {\n const row = db\n .prepare(\"SELECT MIN(created_at) AS when_ FROM ops WHERE group_id = ?\")\n .get(groupId) as { when_: string | null } | undefined;\n return row?.when_ ?? \"\";\n}\n\n/**\n * Note contents the log holds for a task, for tombstones no other tier\n * can resolve (see the call site for why this is per-task).\n *\n * Returns one entry per DISTINCT content, skipping any whose content is\n * already live, and returns nothing at all the second time it is asked\n * about a task — `seen` makes the per-task work happen once even though\n * the caller iterates per tombstone.\n */\nfunction recoverLegacyNoteContents(\n db: Db,\n tombstoneKey: string,\n beforeHlc: string,\n seen: Set<string>,\n): Array<{ key: string; fields: Record<string, string | number | null> }> {\n const parsed = parseNoteKey(tombstoneKey);\n if (parsed === null) return [];\n if (seen.has(parsed.taskKey)) return [];\n seen.add(parsed.taskKey);\n\n const rows = db\n .prepare(\n `SELECT key, payload FROM ops\n WHERE entity = 'note'\n AND op = 'put'\n AND hlc < @hlc\n AND key LIKE @prefix\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}\n ORDER BY hlc`,\n )\n .all({ hlc: beforeHlc, prefix: `${parsed.taskKey}#%` }) as Array<{\n key: string;\n payload: string;\n }>;\n\n const out: Array<{ key: string; fields: Record<string, string | number | null> }> = [];\n const contents = new Set<string>();\n for (const row of rows) {\n const fields = payloadFields(row.payload);\n const content = fields.content;\n if (typeof content !== \"string\" || content === \"\") continue;\n if (contents.has(content)) continue;\n contents.add(content);\n out.push({ key: row.key, fields });\n }\n return out;\n}\n\n/** The restorable fields carried by one op's payload.\n *\n * Shares `NEVER_RECONSTRUCT` and the scalar-only guard with\n * `reconstructRow`, so a fallback can never reintroduce a surrogate id\n * or an FK the fold would have dropped. */\nfunction payloadFields(payload: string): Record<string, string | number | null> {\n const fields: Record<string, string | number | null> = {};\n const parsed = JSON.parse(payload) as Record<string, unknown>;\n for (const [field, value] of Object.entries(parsed)) {\n if (NEVER_RECONSTRUCT.has(field)) continue;\n if (value === null || typeof value === \"string\" || typeof value === \"number\") {\n fields[field] = value;\n }\n }\n return fields;\n}\n\n/**\n * Rebuild the full field set a row had just before `beforeHlc`, by\n * folding every put for that key in HLC order.\n *\n * Necessary because ops are SEMANTIC PARTIAL UPDATES: most tombstones\n * carry no payload and the creating put may have been amended by later\n * partial puts, so no single op holds the whole row. Folding is the only\n * correct reconstruction, and it is the same fold the rebuild path does —\n * just bounded to one key and one point in time.\n */\nfunction reconstructRow(\n db: Db,\n entity: string,\n key: string,\n beforeHlc: string,\n): Record<string, string | number | null> {\n const puts = db\n .prepare(\n `SELECT payload FROM ops\n WHERE entity = @entity AND key = @key AND op = 'put' AND hlc < @hlc\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}\n ORDER BY hlc`,\n )\n .all({ entity, key, hlc: beforeHlc }) as { payload: string }[];\n const fields: Record<string, string | number | null> = {};\n for (const put of puts) {\n const parsed = JSON.parse(put.payload) as Record<string, unknown>;\n for (const [field, value] of Object.entries(parsed)) {\n if (NEVER_RECONSTRUCT.has(field)) continue;\n if (value === null || typeof value === \"string\" || typeof value === \"number\") {\n fields[field] = value;\n }\n }\n }\n return fields;\n}\n\n// ─── applying ─────────────────────────────────────────────────────────\n\nexport interface UndoOptions {\n /** Apply even when the group has been superseded, discarding the\n * newer edits to those fields. */\n force?: boolean;\n /** Actor recorded on the undo's own ops. */\n actor?: string | undefined;\n}\n\n/**\n * Apply the inverse of `groupId`.\n *\n * Everything happens inside ONE `withOpContext` scope with\n * `group: \"new\"`, so:\n * * every inverse write lands in a single new group (making the undo\n * itself one undoable unit), and\n * * the capture triggers record each write with a fresh HLC, so the\n * undo is an ordinary op that syncs and shows up in `mu log`.\n *\n * Wrapped in one transaction: a half-applied undo is worse than none,\n * because the operator would not know which half.\n */\nexport function undoGroup(db: Db, groupId: string, opts: UndoOptions = {}): UndoResult {\n const plan = planUndo(db, groupId);\n\n if (plan.superseded && opts.force !== true) {\n const conflicts = plan.inverses.flatMap((inv) =>\n inv.supersededBy.map((s) => ({ key: inv.key, field: s.field, groupId: s.groupId })),\n );\n throw new UndoSupersededError(groupId, conflicts);\n }\n\n let applied = 0;\n let undoGroupId = \"\";\n\n const run = db.transaction(() => {\n withOpContext(\n db,\n { intent: \"undo\", group: \"new\", ...(opts.actor !== undefined ? { actor: opts.actor } : {}) },\n () => {\n const ctx = db.prepare(\"SELECT group_id FROM _op_ctx\").get() as\n | { group_id: string | null }\n | undefined;\n undoGroupId = ctx?.group_id ?? \"\";\n\n for (const inverse of plan.inverses) {\n if (applyInverse(db, inverse)) applied += 1;\n }\n },\n );\n });\n run.immediate();\n\n return { plan, undoGroupId: applied > 0 && undoGroupId !== \"\" ? undoGroupId : null, applied };\n}\n\n/** Apply one inverse by MUTATING THE TABLE, letting capture record it.\n * Returns true iff a row changed. */\nfunction applyInverse(db: Db, inverse: InverseOp): boolean {\n const table = CAPTURED_TABLE_FOR[inverse.entity];\n if (table === undefined) return false;\n\n if (inverse.op === \"del\") return deleteRow(db, inverse);\n return restoreRow(db, inverse, table);\n}\n\nfunction deleteRow(db: Db, inverse: InverseOp): boolean {\n switch (inverse.entity) {\n case \"workstream\":\n return db.prepare(\"DELETE FROM workstreams WHERE name = ?\").run(inverse.key).changes > 0;\n case \"task\": {\n const id = taskIdForKey(db, inverse.key);\n if (id === null) return false;\n return db.prepare(\"DELETE FROM tasks WHERE id = ?\").run(id).changes > 0;\n }\n case \"note\": {\n const parsed = parseNoteKey(inverse.key);\n if (parsed === null) return false;\n const taskId = taskIdForKey(db, parsed.taskKey);\n if (taskId === null) return false;\n // A note's identity is (task, author, content, created_at), as in\n // applyNotePut, so delete exactly the note the log says this was,\n // not every repeat of its text.\n const note = db\n .prepare(\n `SELECT json_extract(payload, '$.content') AS content,\n json_extract(payload, '$.author') AS author,\n json_extract(payload, '$.created_at') AS created_at FROM ops\n WHERE entity = 'note' AND key = ? AND op = 'put'\n AND ${LEGACY_LOG_ONLY_SQL_EXCLUSION}\n ORDER BY hlc DESC LIMIT 1`,\n )\n .get(inverse.key) as\n | { content: string | null; author: string | null; created_at: string | null }\n | undefined;\n if (note?.content === undefined || note.content === null) return false;\n return (\n db\n .prepare(\n `DELETE FROM task_notes\n WHERE task_id = @taskId AND content = @content\n AND COALESCE(author, '') = COALESCE(@author, '')\n AND (@createdAt IS NULL OR created_at = @createdAt)`,\n )\n .run({\n taskId,\n content: note.content,\n author: note.author,\n createdAt: note.created_at,\n }).changes > 0\n );\n }\n case \"edge\": {\n const parsed = parseEdgeKey(inverse.key);\n if (parsed === null) return false;\n const from = taskIdForKey(db, parsed.blocker);\n const to = taskIdForKey(db, parsed.blocked);\n if (from === null || to === null) return false;\n return (\n db.prepare(\"DELETE FROM task_edges WHERE from_task_id = ? AND to_task_id = ?\").run(from, to)\n .changes > 0\n );\n }\n default:\n return false;\n }\n}\n\nfunction restoreRow(db: Db, inverse: InverseOp, table: string): boolean {\n switch (inverse.entity) {\n case \"workstream\": {\n const exists = db.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(inverse.key) as\n | { id: number }\n | undefined;\n if (!exists) {\n db.prepare(\"INSERT INTO workstreams (name, created_at) VALUES (?, ?)\").run(\n inverse.key,\n String(inverse.fields.created_at ?? new Date().toISOString()),\n );\n return true;\n }\n return updateFields(db, \"workstreams\", \"id\", exists.id, inverse.fields);\n }\n case \"task\": {\n const parsed = parseTaskKey(inverse.key);\n if (parsed === null) return false;\n const id = taskIdForKey(db, inverse.key);\n if (id === null) {\n // The task row is gone, so this is a restore-from-tombstone. The\n // parent workstream must exist first; it will have been restored\n // already by the entity ordering, but create it if the group\n // only deleted the task.\n const wsId = ensureWorkstream(db, parsed.workstream);\n const now = new Date().toISOString();\n // Insert a legal pair: the FK checks it at commit, and an\n // unknown status or substate must not abort the whole undo.\n const pair = resolvePair(\n String(inverse.fields.status ?? \"OPEN\"),\n inverse.fields.substate,\n ) ?? {\n status: \"OPEN\",\n substate: \"todo\",\n };\n db.prepare(\n `INSERT INTO tasks (workstream_id, local_id, title, status, substate, impact,\n effort_days, owner_id, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)`,\n ).run(\n wsId,\n parsed.localId,\n String(inverse.fields.title ?? parsed.localId),\n pair.status,\n pair.substate,\n Number(inverse.fields.impact ?? 50),\n Number(inverse.fields.effort_days ?? 1),\n String(inverse.fields.created_at ?? now),\n String(inverse.fields.updated_at ?? now),\n );\n return true;\n }\n // A partial restore (e.g. status only, from history that predates\n // substates) can leave an illegal pair; repair it before commit.\n const updated = updateFields(db, \"tasks\", \"id\", id, inverse.fields);\n return repairTaskPair(db, id) || updated;\n }\n case \"note\": {\n const parsed = parseNoteKey(inverse.key);\n if (parsed === null) return false;\n const taskId = taskIdForKey(db, parsed.taskKey);\n if (taskId === null) return false;\n const content = inverse.fields.content;\n if (content === undefined || content === null) return false;\n const author = inverse.fields.author;\n const createdAt = inverse.fields.created_at;\n // Same identity as applyNotePut, so restoring one of two repeats of\n // a text does not find the other and stop.\n const exists = db\n .prepare(\n `SELECT id FROM task_notes\n WHERE task_id = @taskId AND content = @content\n AND COALESCE(author, '') = COALESCE(@author, '')\n AND (@createdAt IS NULL OR created_at = @createdAt)`,\n )\n .get({\n taskId,\n content: String(content),\n author: author === undefined || author === null ? null : String(author),\n createdAt: createdAt === undefined || createdAt === null ? null : String(createdAt),\n }) as { id: number } | undefined;\n if (exists) return false; // grow-only: already there\n db.prepare(\n \"INSERT INTO task_notes (task_id, author, content, created_at) VALUES (?, ?, ?, ?)\",\n ).run(\n taskId,\n inverse.fields.author === undefined || inverse.fields.author === null\n ? null\n : String(inverse.fields.author),\n String(content),\n String(inverse.fields.created_at ?? new Date().toISOString()),\n );\n return true;\n }\n case \"edge\": {\n const parsed = parseEdgeKey(inverse.key);\n if (parsed === null) return false;\n const from = taskIdForKey(db, parsed.blocker);\n const to = taskIdForKey(db, parsed.blocked);\n if (from === null || to === null) return false;\n const exists = db\n .prepare(\"SELECT 1 AS x FROM task_edges WHERE from_task_id = ? AND to_task_id = ?\")\n .get(from, to) as { x: number } | undefined;\n if (exists) return false;\n db.prepare(\n \"INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)\",\n ).run(from, to, String(inverse.fields.created_at ?? new Date().toISOString()));\n return true;\n }\n default:\n void table;\n return false;\n }\n}\n\n/**\n * Restore the named fields in ONE UPDATE.\n *\n * One statement, not one per field, and that matters: each UPDATE fires\n * the capture trigger once, so a per-field loop would emit N ops for a\n * single logical inverse. They would share the undo's group so undo/redo\n * still worked, but `mu log` would show one action as several and the\n * op count would misrepresent what happened. One statement, one op.\n *\n * Fields already holding the target value are dropped from the statement\n * first, so a fully-redundant inverse issues no UPDATE at all and\n * therefore produces no op (matching the capture WHEN guard). Returns\n * true iff a row actually changed, so `applied` never overstates the work.\n */\nfunction updateFields(\n db: Db,\n table: string,\n idColumn: string,\n id: number,\n fields: Record<string, string | number | null>,\n): boolean {\n const names = Object.keys(fields).filter((f) => !NEVER_RESTORE.has(f));\n if (names.length === 0) return false;\n\n const current = db.prepare(`SELECT * FROM ${table} WHERE ${idColumn} = ?`).get(id) as\n | Record<string, string | number | null>\n | undefined;\n\n // Only fields whose value actually differs. Comparing here rather than\n // relying on the trigger keeps `changed` honest for the caller.\n const pending = names.filter((name) => {\n const target = fields[name] ?? null;\n if (current === undefined) return true;\n return current[name] !== target;\n });\n if (pending.length === 0) return false;\n\n const setClause = pending.map((name) => `${name} = @${name}`).join(\", \");\n const params: Record<string, string | number | null> = { id };\n for (const name of pending) params[name] = fields[name] ?? null;\n db.prepare(`UPDATE ${table} SET ${setClause} WHERE ${idColumn} = @id`).run(params);\n return true;\n}\n\nfunction ensureWorkstream(db: Db, name: string): number {\n const existing = db.prepare(\"SELECT id FROM workstreams WHERE name = ?\").get(name) as\n | { id: number }\n | undefined;\n if (existing) return existing.id;\n db.prepare(\"INSERT INTO workstreams (name, created_at) VALUES (?, ?)\").run(\n name,\n new Date().toISOString(),\n );\n return resolveWorkstreamId(db, name);\n}\n\n// ─── key parsing (mirrors src/apply.ts) ───────────────────────────────\n\nfunction parseTaskKey(key: string): { workstream: string; localId: string } | null {\n const slash = key.indexOf(\"/\");\n if (slash <= 0 || slash === key.length - 1) return null;\n return { workstream: key.slice(0, slash), localId: key.slice(slash + 1) };\n}\n\nfunction parseNoteKey(key: string): { taskKey: string } | null {\n const hash = key.lastIndexOf(\"#\");\n if (hash <= 0) return null;\n return { taskKey: key.slice(0, hash) };\n}\n\nfunction parseEdgeKey(key: string): { blocker: string; blocked: string } | null {\n const arrow = key.indexOf(\"->\");\n if (arrow <= 0) return null;\n return { blocker: key.slice(0, arrow), blocked: key.slice(arrow + 2) };\n}\n\nfunction taskIdForKey(db: Db, key: string): number | null {\n const parsed = parseTaskKey(key);\n if (parsed === null) return null;\n const row = db\n .prepare(\n `SELECT t.id AS id FROM tasks t\n JOIN workstreams w ON w.id = t.workstream_id\n WHERE w.name = ? AND t.local_id = ?`,\n )\n .get(parsed.workstream, parsed.localId) as { id: number } | undefined;\n return row?.id ?? null;\n}\n"],"mappings":"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","names":["fields","legacy"]}
|