@mu-crew/mu 3.13.2 → 4.0.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.
Files changed (119) hide show
  1. package/AGENTS.md +73 -35
  2. package/README.md +71 -57
  3. package/dist/chunk-5HECDBK3.js +4482 -0
  4. package/dist/chunk-5HECDBK3.js.map +1 -0
  5. package/dist/chunk-AW2NONYU.js +301 -0
  6. package/dist/chunk-AW2NONYU.js.map +1 -0
  7. package/dist/chunk-GQNHIPJN.js +4060 -0
  8. package/dist/chunk-GQNHIPJN.js.map +1 -0
  9. package/dist/{chunk-3BARPJLY.js → chunk-PZ5GOCWH.js} +16 -2
  10. package/dist/chunk-PZ5GOCWH.js.map +1 -0
  11. package/dist/chunk-S424D5EP.js +13766 -0
  12. package/dist/chunk-S424D5EP.js.map +1 -0
  13. package/dist/{cli-CKOMHBXV.js → cli-GB2CAVVW.js} +6776 -4350
  14. package/dist/cli-GB2CAVVW.js.map +1 -0
  15. package/dist/cli.js +2 -3
  16. package/dist/cli.js.map +1 -1
  17. package/dist/errors-KHOYKD7D.js +65 -0
  18. package/dist/extension/mu-pi.js +1617 -836
  19. package/dist/extension/mu-pi.js.map +1 -1
  20. package/dist/index.d.ts +4015 -2561
  21. package/dist/index.js +614 -297
  22. package/dist/index.js.map +1 -1
  23. package/dist/tui-ALPHYTA2.js +1773 -0
  24. package/dist/tui-ALPHYTA2.js.map +1 -0
  25. package/docs/ARCHITECTURE.md +117 -66
  26. package/docs/VOCABULARY.md +112 -49
  27. package/docs/guide/README.md +3 -3
  28. package/docs/guide/backends.md +8 -4
  29. package/docs/guide/cleanup.md +61 -33
  30. package/docs/guide/delegate.md +141 -66
  31. package/docs/guide/dispatch.md +148 -53
  32. package/docs/guide/getting-started.md +37 -44
  33. package/docs/guide/other-agent-clis.md +102 -0
  34. package/docs/guide/recovery.md +31 -50
  35. package/docs/guide/remote.md +115 -45
  36. package/docs/guide/sql.md +104 -24
  37. package/docs/guide/stop-a-worker.md +30 -37
  38. package/docs/guide/sync.md +11 -3
  39. package/docs/guide/upgrade.md +93 -43
  40. package/docs/reference/env.md +23 -32
  41. package/docs/reference/naming.md +12 -7
  42. package/docs/reference/surface-contract.md +153 -0
  43. package/package.json +33 -11
  44. package/prompts/mu-debug.md +3 -1
  45. package/prompts/mu-refute.md +2 -1
  46. package/prompts/mu-research.md +3 -1
  47. package/prompts/mu-review.md +2 -1
  48. package/prompts/mu-rules-audit.md +5 -4
  49. package/prompts/mu-sweep.md +2 -1
  50. package/prompts/mu-tournament.md +4 -2
  51. package/prompts/mu-until.md +3 -1
  52. package/prompts/ultrathink.md +1 -1
  53. package/skills/mu/SKILL.md +60 -291
  54. package/skills/mu/recipes/adversarial-review.md +12 -103
  55. package/skills/mu/recipes/brief.md +14 -61
  56. package/skills/mu/recipes/course-correct.md +23 -0
  57. package/skills/mu/recipes/findings.md +17 -111
  58. package/skills/mu/recipes/models.md +17 -92
  59. package/skills/mu/recipes/move.md +14 -0
  60. package/skills/mu/recipes/orchestrator-loop.md +20 -119
  61. package/skills/mu/recipes/recovery.md +8 -29
  62. package/skills/mu/recipes/remote-workers.md +16 -318
  63. package/skills/mu/recipes/tasks-or-calls.md +15 -78
  64. package/skills/mu/recipes/test-health.md +19 -69
  65. package/skills/mu/recipes/worker.md +17 -24
  66. package/dist/chunk-45KQLMEL.js +0 -2908
  67. package/dist/chunk-45KQLMEL.js.map +0 -1
  68. package/dist/chunk-G434JKV2.js +0 -641
  69. package/dist/chunk-G434JKV2.js.map +0 -1
  70. package/dist/chunk-KUGUFBWH.js +0 -12527
  71. package/dist/chunk-KUGUFBWH.js.map +0 -1
  72. package/dist/chunk-QITLNZEM.js +0 -87
  73. package/dist/chunk-QITLNZEM.js.map +0 -1
  74. package/dist/chunk-VIEYQMQA.js +0 -393
  75. package/dist/chunk-VIEYQMQA.js.map +0 -1
  76. package/dist/chunk-WI6AWCMK.js +0 -19
  77. package/dist/chunk-WI6AWCMK.js.map +0 -1
  78. package/dist/cli-CKOMHBXV.js.map +0 -1
  79. package/dist/devtools-LS4ZEPWM.js +0 -3772
  80. package/dist/devtools-LS4ZEPWM.js.map +0 -1
  81. package/dist/tui-LFJ5P4Y2.js +0 -40956
  82. package/dist/tui-LFJ5P4Y2.js.map +0 -1
  83. package/dist/tui-sync-worker.js +0 -17
  84. package/dist/tui-sync-worker.js.map +0 -1
  85. package/dist/yank-XDOONQSL.js +0 -13
  86. package/dist/yank-XDOONQSL.js.map +0 -1
  87. package/docs/ROADMAP.md +0 -123
  88. package/docs/VISION.md +0 -298
  89. package/docs/architecture/control-socket.md +0 -142
  90. package/docs/architecture/dag.md +0 -117
  91. package/docs/architecture/mux.md +0 -167
  92. package/docs/architecture/ops-log.md +0 -160
  93. package/docs/architecture/sdk.md +0 -187
  94. package/docs/architecture/sync.md +0 -159
  95. package/docs/architecture/tui.md +0 -165
  96. package/docs/guide/tui.md +0 -98
  97. package/docs/history/CHANGELOG-pre-3.md +0 -4866
  98. package/docs/img/tui-dashboard.png +0 -0
  99. package/docs/specs/2026-09-28-agent-state-from-murmur.md +0 -333
  100. package/docs/specs/2026-09-29-task-substates.md +0 -247
  101. package/docs/specs/2026-10-03-keep-driving-nudge.md +0 -163
  102. package/docs/specs/2026-10-21-fast-and-slow-gates.md +0 -229
  103. package/skills/mu/recipes/backlog-triage.md +0 -45
  104. package/skills/mu/recipes/codemode-driver.md +0 -118
  105. package/skills/mu/recipes/deep-research.md +0 -69
  106. package/skills/mu/recipes/drift-audit.md +0 -75
  107. package/skills/mu/recipes/fan-out.md +0 -42
  108. package/skills/mu/recipes/hypothesis-panel.md +0 -48
  109. package/skills/mu/recipes/long-run.md +0 -32
  110. package/skills/mu/recipes/loop-until-done.md +0 -45
  111. package/skills/mu/recipes/plan.md +0 -67
  112. package/skills/mu/recipes/refute.md +0 -55
  113. package/skills/mu/recipes/review-panel.md +0 -65
  114. package/skills/mu/recipes/rules-audit.md +0 -89
  115. package/skills/mu/recipes/tournament.md +0 -44
  116. package/skills/mu/recipes/ultrathink.md +0 -86
  117. package/skills/mu/recipes/watcher.md +0 -28
  118. package/skills/mu/recipes/waves.md +0 -21
  119. /package/dist/{chunk-3BARPJLY.js.map → errors-KHOYKD7D.js.map} +0 -0
@@ -1,87 +0,0 @@
1
- import { createRequire as __muCreateRequire } from "node:module"; const require = __muCreateRequire(import.meta.url);
2
-
3
- // src/cli/tui/yank.ts
4
- import { writeFile } from "fs/promises";
5
- async function probeClipboardBackend(env = {}) {
6
- const platform = env.platform ?? process.platform;
7
- const hasWayland = env.wayland ?? process.env.WAYLAND_DISPLAY !== void 0;
8
- const hasX = env.x11 ?? process.env.DISPLAY !== void 0;
9
- const wsl = env.isWsl ?? isWsl;
10
- const has = env.hasCommand ?? hasCommand;
11
- if (platform === "darwin" && await has("pbcopy")) {
12
- return { kind: "cli", cmd: "pbcopy", args: [] };
13
- }
14
- if (await wsl()) {
15
- if (await has("clip.exe")) {
16
- return { kind: "cli", cmd: "clip.exe", args: [] };
17
- }
18
- }
19
- if (platform === "linux") {
20
- if (hasWayland && await has("wl-copy")) {
21
- return { kind: "cli", cmd: "wl-copy", args: ["--type", "text/plain"] };
22
- }
23
- if (hasX) {
24
- if (await has("xclip")) {
25
- return {
26
- kind: "cli",
27
- cmd: "xclip",
28
- args: ["-selection", "clipboard", "-in", "-loops", "1", "-quiet"]
29
- };
30
- }
31
- if (await has("xsel")) {
32
- return { kind: "cli", cmd: "xsel", args: ["--clipboard", "--input"] };
33
- }
34
- }
35
- }
36
- return { kind: "osc52" };
37
- }
38
- function osc52Sequence(text) {
39
- return `\x1B]52;c;${Buffer.from(text).toString("base64")}\x07`;
40
- }
41
- async function hasCommand(cmd) {
42
- try {
43
- const { execa } = await import("execa");
44
- await execa("command", ["-v", cmd], { shell: true, stdio: "ignore" });
45
- return true;
46
- } catch {
47
- return false;
48
- }
49
- }
50
- async function isWsl() {
51
- try {
52
- const { readFile } = await import("fs/promises");
53
- const v = await readFile("/proc/version", "utf-8");
54
- return v.toLowerCase().includes("microsoft");
55
- } catch {
56
- return false;
57
- }
58
- }
59
- var defaultOsc52Writer = (data) => writeFile("/dev/tty", data);
60
- async function yank(text, backend, opts = {}) {
61
- if (backend === null) {
62
- return { copied: false, backend: null };
63
- }
64
- if (backend.kind === "cli") {
65
- try {
66
- const { execa } = await import("execa");
67
- await execa(backend.cmd, backend.args, { input: text });
68
- return { copied: true, backend: backend.cmd };
69
- } catch (err) {
70
- return { copied: false, backend: backend.cmd, error: String(err) };
71
- }
72
- }
73
- const writer = opts.osc52Writer ?? defaultOsc52Writer;
74
- try {
75
- await writer(osc52Sequence(text));
76
- return { copied: true, backend: "osc52" };
77
- } catch (err) {
78
- return { copied: false, backend: "osc52", error: String(err) };
79
- }
80
- }
81
-
82
- export {
83
- probeClipboardBackend,
84
- osc52Sequence,
85
- yank
86
- };
87
- //# sourceMappingURL=chunk-QITLNZEM.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/tui/yank.ts"],"sourcesContent":["// Clipboard probe + yank for the TUI's act-intent flow (R1: model\n// drives the CLI; the user yanks `mu …` commands rather than the TUI\n// executing them). See design_yank_flow + investigate_clipboard in\n// workstream `tui` for the matrix.\n//\n// Probe order:\n// darwin → pbcopy\n// linux + Wayland → wl-copy\n// linux + X11 → xclip → xsel\n// wsl → clip.exe\n// fallback → OSC-52 (works over SSH; needs terminal support)\n// final → null (UI shows \"[no clipboard]\" but the\n// command is still visible in the footer)\n//\n// The probe is sync-style (relies on which/command-v paths existing or\n// not) but execa returns Promises, so the API is async. Probe once per\n// TUI session and cache the result.\n\nimport { writeFile } from \"node:fs/promises\";\n\nexport type ClipboardBackend =\n | { kind: \"cli\"; cmd: string; args: string[] }\n | { kind: \"osc52\" }\n | null;\n\nexport interface YankResult {\n copied: boolean;\n backend: string | null;\n error?: string;\n}\n\n/** Injection seam for probeClipboardBackend tests — lets a test pin\n * process.platform / WAYLAND_DISPLAY / DISPLAY / WSL detection /\n * binary availability without monkey-patching globals. Defaults\n * exercise the real environment. */\nexport interface ProbeEnv {\n platform?: NodeJS.Platform;\n wayland?: boolean;\n x11?: boolean;\n isWsl?: () => Promise<boolean>;\n hasCommand?: (cmd: string) => Promise<boolean>;\n}\n\n/**\n * Probe the environment for an available clipboard backend. Returns\n * null when nothing works (the TUI falls back to displaying the\n * command in the footer for manual mouse-select).\n *\n * Caller is expected to memoise the result for the lifetime of the\n * TUI session.\n */\nexport async function probeClipboardBackend(env: ProbeEnv = {}): Promise<ClipboardBackend> {\n const platform = env.platform ?? process.platform;\n const hasWayland = env.wayland ?? process.env.WAYLAND_DISPLAY !== undefined;\n const hasX = env.x11 ?? process.env.DISPLAY !== undefined;\n const wsl = env.isWsl ?? isWsl;\n const has = env.hasCommand ?? hasCommand;\n\n // macOS\n if (platform === \"darwin\" && (await has(\"pbcopy\"))) {\n return { kind: \"cli\", cmd: \"pbcopy\", args: [] };\n }\n\n // WSL\n if (await wsl()) {\n if (await has(\"clip.exe\")) {\n return { kind: \"cli\", cmd: \"clip.exe\", args: [] };\n }\n }\n\n // Linux: prefer Wayland over X11 (XWayland sessions often have both).\n if (platform === \"linux\") {\n if (hasWayland && (await has(\"wl-copy\"))) {\n return { kind: \"cli\", cmd: \"wl-copy\", args: [\"--type\", \"text/plain\"] };\n }\n if (hasX) {\n if (await has(\"xclip\")) {\n return {\n kind: \"cli\",\n cmd: \"xclip\",\n args: [\"-selection\", \"clipboard\", \"-in\", \"-loops\", \"1\", \"-quiet\"],\n };\n }\n if (await has(\"xsel\")) {\n return { kind: \"cli\", cmd: \"xsel\", args: [\"--clipboard\", \"--input\"] };\n }\n }\n }\n\n // OSC-52 — best-effort over any terminal that supports it. Always\n // available as a fallback because we can write to /dev/tty without\n // any external dependency. The terminal may silently drop the\n // sequence (gnome-terminal, old tmux without `set-clipboard on`),\n // but we've done all we can — the footer line is the real fallback.\n return { kind: \"osc52\" };\n}\n\n/** Build the OSC-52 clipboard-set sequence for `text`. Exported for\n * test assertions: a regression that swaps the BEL terminator for\n * the (also-legal) ST `ESC \\` would still copy in some terminals\n * but break others; pinning the byte sequence catches both. */\nexport function osc52Sequence(text: string): string {\n return `\\x1b]52;c;${Buffer.from(text).toString(\"base64\")}\\x07`;\n}\n\n/** Check whether `cmd` exists on PATH. Returns false on any error. */\nasync function hasCommand(cmd: string): Promise<boolean> {\n try {\n const { execa } = await import(\"execa\");\n await execa(\"command\", [\"-v\", cmd], { shell: true, stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Detect WSL via /proc/version content. Returns false on any error. */\nasync function isWsl(): Promise<boolean> {\n try {\n const { readFile } = await import(\"node:fs/promises\");\n const v = await readFile(\"/proc/version\", \"utf-8\");\n return v.toLowerCase().includes(\"microsoft\");\n } catch {\n return false;\n }\n}\n\n/** Writer seam for the OSC-52 branch. Default writes to /dev/tty\n * (so the sequence reaches the controlling terminal even though\n * ink owns stdout). Tests inject a stub that captures the bytes. */\nexport type Osc52Writer = (data: string) => Promise<void>;\n\nconst defaultOsc52Writer: Osc52Writer = (data) => writeFile(\"/dev/tty\", data);\n\n/**\n * Copy `text` to the clipboard via the resolved backend. Returns a\n * structured result so the caller can render `[copied]` /\n * `[no clipboard]` in the footer/toast.\n */\nexport async function yank(\n text: string,\n backend: ClipboardBackend,\n opts: { osc52Writer?: Osc52Writer } = {},\n): Promise<YankResult> {\n if (backend === null) {\n return { copied: false, backend: null };\n }\n\n if (backend.kind === \"cli\") {\n try {\n const { execa } = await import(\"execa\");\n await execa(backend.cmd, backend.args, { input: text });\n return { copied: true, backend: backend.cmd };\n } catch (err) {\n return { copied: false, backend: backend.cmd, error: String(err) };\n }\n }\n\n // OSC-52: ESC ] 52 ; c ; <base64> BEL.\n const writer = opts.osc52Writer ?? defaultOsc52Writer;\n try {\n await writer(osc52Sequence(text));\n return { copied: true, backend: \"osc52\" };\n } catch (err) {\n return { copied: false, backend: \"osc52\", error: String(err) };\n }\n}\n"],"mappings":";;;AAkBA,SAAS,iBAAiB;AAiC1B,eAAsB,sBAAsB,MAAgB,CAAC,GAA8B;AACzF,QAAM,WAAW,IAAI,YAAY,QAAQ;AACzC,QAAM,aAAa,IAAI,WAAW,QAAQ,IAAI,oBAAoB;AAClE,QAAM,OAAO,IAAI,OAAO,QAAQ,IAAI,YAAY;AAChD,QAAM,MAAM,IAAI,SAAS;AACzB,QAAM,MAAM,IAAI,cAAc;AAG9B,MAAI,aAAa,YAAa,MAAM,IAAI,QAAQ,GAAI;AAClD,WAAO,EAAE,MAAM,OAAO,KAAK,UAAU,MAAM,CAAC,EAAE;AAAA,EAChD;AAGA,MAAI,MAAM,IAAI,GAAG;AACf,QAAI,MAAM,IAAI,UAAU,GAAG;AACzB,aAAO,EAAE,MAAM,OAAO,KAAK,YAAY,MAAM,CAAC,EAAE;AAAA,IAClD;AAAA,EACF;AAGA,MAAI,aAAa,SAAS;AACxB,QAAI,cAAe,MAAM,IAAI,SAAS,GAAI;AACxC,aAAO,EAAE,MAAM,OAAO,KAAK,WAAW,MAAM,CAAC,UAAU,YAAY,EAAE;AAAA,IACvE;AACA,QAAI,MAAM;AACR,UAAI,MAAM,IAAI,OAAO,GAAG;AACtB,eAAO;AAAA,UACL,MAAM;AAAA,UACN,KAAK;AAAA,UACL,MAAM,CAAC,cAAc,aAAa,OAAO,UAAU,KAAK,QAAQ;AAAA,QAClE;AAAA,MACF;AACA,UAAI,MAAM,IAAI,MAAM,GAAG;AACrB,eAAO,EAAE,MAAM,OAAO,KAAK,QAAQ,MAAM,CAAC,eAAe,SAAS,EAAE;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AAOA,SAAO,EAAE,MAAM,QAAQ;AACzB;AAMO,SAAS,cAAc,MAAsB;AAClD,SAAO,aAAa,OAAO,KAAK,IAAI,EAAE,SAAS,QAAQ,CAAC;AAC1D;AAGA,eAAe,WAAW,KAA+B;AACvD,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,OAAO,OAAO;AACtC,UAAM,MAAM,WAAW,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,MAAM,OAAO,SAAS,CAAC;AACpE,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAe,QAA0B;AACvC,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,MAAM,OAAO,aAAkB;AACpD,UAAM,IAAI,MAAM,SAAS,iBAAiB,OAAO;AACjD,WAAO,EAAE,YAAY,EAAE,SAAS,WAAW;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,IAAM,qBAAkC,CAAC,SAAS,UAAU,YAAY,IAAI;AAO5E,eAAsB,KACpB,MACA,SACA,OAAsC,CAAC,GAClB;AACrB,MAAI,YAAY,MAAM;AACpB,WAAO,EAAE,QAAQ,OAAO,SAAS,KAAK;AAAA,EACxC;AAEA,MAAI,QAAQ,SAAS,OAAO;AAC1B,QAAI;AACF,YAAM,EAAE,MAAM,IAAI,MAAM,OAAO,OAAO;AACtC,YAAM,MAAM,QAAQ,KAAK,QAAQ,MAAM,EAAE,OAAO,KAAK,CAAC;AACtD,aAAO,EAAE,QAAQ,MAAM,SAAS,QAAQ,IAAI;AAAA,IAC9C,SAAS,KAAK;AACZ,aAAO,EAAE,QAAQ,OAAO,SAAS,QAAQ,KAAK,OAAO,OAAO,GAAG,EAAE;AAAA,IACnE;AAAA,EACF;AAGA,QAAM,SAAS,KAAK,eAAe;AACnC,MAAI;AACF,UAAM,OAAO,cAAc,IAAI,CAAC;AAChC,WAAO,EAAE,QAAQ,MAAM,SAAS,QAAQ;AAAA,EAC1C,SAAS,KAAK;AACZ,WAAO,EAAE,QAAQ,OAAO,SAAS,SAAS,OAAO,OAAO,GAAG,EAAE;AAAA,EAC/D;AACF;","names":[]}
@@ -1,393 +0,0 @@
1
- import { createRequire as __muCreateRequire } from "node:module"; const require = __muCreateRequire(import.meta.url);
2
- import {
3
- GLYPH,
4
- UNKNOWN_REASON,
5
- agentStateGlyph,
6
- muTable,
7
- parseOpKey,
8
- pc,
9
- renderBoxTable,
10
- renderOp
11
- } from "./chunk-KUGUFBWH.js";
12
- import {
13
- compareStrings,
14
- formatPair
15
- } from "./chunk-45KQLMEL.js";
16
-
17
- // src/cli/format.ts
18
- var STATUS_COLORS = {
19
- busy: pc.cyan,
20
- needs_input: pc.dim,
21
- needs_permission: pc.magenta,
22
- unknown: pc.dim
23
- };
24
- function statusIcon(state) {
25
- return STATUS_COLORS[state](agentStateGlyph(state));
26
- }
27
- var IDLE_GLYPH = GLYPH.warn;
28
- function colorPair(pair) {
29
- const text = formatPair(pair);
30
- switch (pair.status) {
31
- case "OPEN":
32
- return pair.substate === "parked" || pair.substate === "triage" ? pc.dim(text) : pc.cyan(text);
33
- case "IN_PROGRESS":
34
- return pc.yellow(text);
35
- case "CLOSED":
36
- return pair.substate === "done" ? pc.green(text) : pc.red(text);
37
- }
38
- }
39
- function colorStatus(status) {
40
- switch (status) {
41
- case "OPEN":
42
- return pc.cyan(status);
43
- case "IN_PROGRESS":
44
- return pc.yellow(status);
45
- case "CLOSED":
46
- return pc.green(status);
47
- }
48
- }
49
- function inkColorForPair(pair) {
50
- switch (pair.status) {
51
- case "OPEN":
52
- return pair.substate === "parked" || pair.substate === "triage" ? "gray" : "cyan";
53
- case "IN_PROGRESS":
54
- return "yellow";
55
- case "CLOSED":
56
- return pair.substate === "done" ? "green" : "red";
57
- }
58
- }
59
- var DEFAULT_TERMINAL_WIDTH = 100;
60
- function terminalWidth() {
61
- return process.stdout.columns ?? DEFAULT_TERMINAL_WIDTH;
62
- }
63
- function truncate(s, max) {
64
- if (max <= 1) return s.slice(0, max);
65
- if (s.length <= max) return s;
66
- return `${s.slice(0, max - 1)}\u2026`;
67
- }
68
- function truncateFront(s, max) {
69
- if (max <= 1) return s.slice(-max);
70
- if (s.length <= max) return s;
71
- return `\u2026${s.slice(-(max - 1))}`;
72
- }
73
- function relTime(ms) {
74
- const sec = Math.max(0, Math.floor(ms / 1e3));
75
- if (sec < 60) return `${sec}s`;
76
- const min = Math.floor(sec / 60);
77
- if (min < 60) return `${min}m`;
78
- const hr = Math.floor(min / 60);
79
- if (hr < 24) return `${hr}h`;
80
- const day = Math.floor(hr / 24);
81
- if (day < 7) return `${day}d`;
82
- return `${Math.floor(day / 7)}w`;
83
- }
84
- function relTimeAgo(ms) {
85
- return `${relTime(ms)} ago`;
86
- }
87
- function formatAgentsTable(agents) {
88
- if (agents.length === 0) return pc.dim(" (no agents)");
89
- const table = muTable({
90
- head: [
91
- pc.bold(""),
92
- pc.bold("name"),
93
- pc.bold("cli"),
94
- pc.bold("state"),
95
- pc.bold("window"),
96
- pc.bold("role")
97
- ],
98
- colWidths: [null, null, null, null, 32, 14],
99
- style: { head: [] }
100
- });
101
- for (const a of agents) {
102
- const idle = a.idle === true;
103
- const glyphCell = idle ? `${pc.yellow(IDLE_GLYPH)} ${statusIcon(a.state)}` : statusIcon(a.state);
104
- const nameCell = idle ? pc.yellow(a.name) : a.name;
105
- table.push([
106
- glyphCell,
107
- nameCell,
108
- a.cli,
109
- (a.state === "unknown" || a.state === "needs_permission") && a.reason !== void 0 ? `${a.state}: ${a.reason}` : a.state,
110
- a.tab ?? a.name,
111
- a.role === "read-only" ? pc.yellow("read-only") : ""
112
- ]);
113
- }
114
- const needsMurmur = agents.some(
115
- (a) => a.state === "unknown" && (a.reason === UNKNOWN_REASON.murmurMissing || a.reason === UNKNOWN_REASON.extensionMissing)
116
- );
117
- return needsMurmur ? `${table.toString()}
118
- ${pc.dim("agent state needs murmur (see mu doctor)")}` : table.toString();
119
- }
120
- function formatReadyTable(tasks) {
121
- if (tasks.length === 0) return pc.dim(" (no ready tasks)");
122
- const sorted = [...tasks].sort((a, b) => b.impact / b.effortDays - a.impact / a.effortDays);
123
- let idW = "name".length;
124
- let impactW = "impact".length;
125
- let effortW = "effort".length;
126
- let roiW = "ROI".length;
127
- let ownerW = "owner".length;
128
- for (const t of sorted) {
129
- idW = Math.max(idW, t.name.length);
130
- impactW = Math.max(impactW, String(t.impact).length);
131
- effortW = Math.max(effortW, String(t.effortDays).length);
132
- const roi = (t.impact / t.effortDays).toFixed(1);
133
- roiW = Math.max(roiW, roi.length);
134
- ownerW = Math.max(ownerW, (t.ownerName ?? "").length);
135
- }
136
- const padding = 6 * 3 + 1;
137
- const titleBudget = Math.max(
138
- 20,
139
- terminalWidth() - (idW + impactW + effortW + roiW + ownerW) - padding
140
- );
141
- const table = muTable({
142
- head: [
143
- pc.bold("name"),
144
- pc.bold("title"),
145
- pc.bold("impact"),
146
- pc.bold("effort"),
147
- pc.bold("ROI"),
148
- pc.bold("owner")
149
- ],
150
- style: { head: [] }
151
- });
152
- for (const t of sorted) {
153
- const roi = (t.impact / t.effortDays).toFixed(1);
154
- table.push([
155
- t.name,
156
- truncate(t.title, titleBudget),
157
- String(t.impact),
158
- String(t.effortDays),
159
- roi,
160
- t.ownerName ?? ""
161
- ]);
162
- }
163
- return table.toString();
164
- }
165
- function formatTracks(tracks) {
166
- if (tracks.length === 0) return pc.dim(" (no open tracks)");
167
- const lines = [];
168
- tracks.forEach((track, i) => {
169
- const rootNames = track.roots.map((r) => r.name).join(", ");
170
- const verb = track.roots.length > 1 ? "merged" : "track";
171
- const parked = track.parked ? ` ${pc.dim("(parked)")}` : "";
172
- lines.push(
173
- ` Track ${i + 1}: ${pc.bold(rootNames)} ${pc.dim(`(${track.taskIds.size} tasks, ${track.readyCount} ready, ${verb})`)}${parked}`
174
- );
175
- });
176
- return lines.join("\n");
177
- }
178
- function formatWorkspacesTable(rows) {
179
- const PATH_BUDGET = 40;
180
- const table = muTable({
181
- head: ["agent", "workstream", "backend", "path", "parent_ref", "behind", "created"].map(
182
- (h) => pc.bold(h)
183
- ),
184
- colWidths: [null, null, null, PATH_BUDGET, null, null, null]
185
- });
186
- for (const r of rows) {
187
- table.push([
188
- r.agentName,
189
- r.workstreamName,
190
- r.backend,
191
- truncateFront(r.path, PATH_BUDGET - 2),
192
- r.parentRef ? pc.dim(r.parentRef.slice(0, 12)) : pc.dim("\u2014"),
193
- formatBehind(r.commitsBehindMain),
194
- pc.dim(r.createdAt)
195
- ]);
196
- }
197
- return table.toString();
198
- }
199
- function formatBehind(n) {
200
- if (n === void 0 || n === null) return pc.dim("\u2014");
201
- if (n <= 2) return pc.green(String(n));
202
- if (n <= 9) return pc.yellow(String(n));
203
- return pc.red(String(n));
204
- }
205
- function printLogRow(row) {
206
- const time = row.createdAt.replace("T", " ").replace(/\.\d+Z$/, "Z");
207
- const rendered = renderOp(row);
208
- const scope = parseOpKey(row.workstreamName).workstream;
209
- const where = scope === void 0 ? pc.dim("\u2014") : scope;
210
- if (rendered === null) {
211
- console.log(
212
- `${pc.dim(`#${row.seq}`)} ${pc.dim(time)} ${pc.bold(row.source)} ${pc.yellow(row.kind)} [${where}] ${row.payload}`
213
- );
214
- return;
215
- }
216
- const detail = rendered.detail === "" ? "" : ` ${pc.dim(rendered.detail)}`;
217
- console.log(
218
- `${pc.dim(`#${row.seq}`)} ${pc.dim(time)} ${pc.bold(row.source)} ${pc.cyan(rendered.verb)} ${rendered.subject}${detail} ${pc.dim(`[${where}]`)}`
219
- );
220
- }
221
- function formatWorkstreamsTable(rows) {
222
- const table = muTable({
223
- head: ["name", "mux", "agents", "tasks", "edges", "notes"].map((h) => pc.bold(h)),
224
- colWidths: [40, null, null, null, null, null]
225
- });
226
- for (const r of rows) {
227
- table.push([
228
- r.name,
229
- r.muxAlive ? pc.green("alive") : pc.dim("\u2014"),
230
- String(r.agentCount),
231
- String(r.taskCount),
232
- String(r.edgeCount),
233
- String(r.noteCount)
234
- ]);
235
- }
236
- return table.toString();
237
- }
238
- function formatTornDownWorkstreamsTable(rows) {
239
- const table = muTable({
240
- head: ["name", "group", "torn down", "tasks", "edges", "notes", ""].map((h) => pc.bold(h)),
241
- colWidths: [32, null, null, null, null, null, null]
242
- });
243
- for (const r of rows) {
244
- const at = Date.parse(r.at);
245
- table.push([
246
- r.name,
247
- r.group.slice(0, 8),
248
- Number.isNaN(at) ? pc.dim("\u2014") : relTimeAgo(Date.now() - at),
249
- String(r.tasks),
250
- String(r.edges),
251
- String(r.notes),
252
- r.recreated ? pc.dim("\u2190 recreated since") : ""
253
- ]);
254
- }
255
- return table.toString();
256
- }
257
- function formatTaskListTable(tasks, opts = {}) {
258
- if (tasks.length === 0) return pc.dim(" (no tasks)");
259
- const timeHeader = opts.relTimeBasis === "updatedAt" ? "updated" : opts.relTimeBasis === "createdAt" ? "created" : null;
260
- const baseHead = opts.withWorkstream ? ["name", "workstream", "status", "title", "impact", "effort", "ROI", "owner"] : ["name", "status", "title", "impact", "effort", "ROI", "owner"];
261
- const head = timeHeader === null ? baseHead : [...baseHead, timeHeader];
262
- const now = Date.now();
263
- const timeCells = [];
264
- if (opts.relTimeBasis !== void 0) {
265
- const basis = opts.relTimeBasis;
266
- for (const t of tasks) {
267
- const ts = basis === "updatedAt" ? t.updatedAt : t.createdAt;
268
- const ms = now - new Date(ts).getTime();
269
- timeCells.push(relTime(ms));
270
- }
271
- }
272
- const otherCols = opts.withWorkstream ? ["localId", "workstream", "status", "impact", "effortDays", "roi", "owner"] : ["localId", "status", "impact", "effortDays", "roi", "owner"];
273
- const widths = /* @__PURE__ */ new Map();
274
- for (const col of otherCols) widths.set(col, col.length);
275
- for (const t of tasks) {
276
- widths.set("localId", Math.max(widths.get("localId") ?? 0, t.name.length));
277
- if (opts.withWorkstream) {
278
- widths.set("workstream", Math.max(widths.get("workstream") ?? 0, t.workstreamName.length));
279
- }
280
- widths.set("status", Math.max(widths.get("status") ?? 0, formatPair(t).length));
281
- widths.set("impact", Math.max(widths.get("impact") ?? 0, String(t.impact).length));
282
- widths.set("effortDays", Math.max(widths.get("effortDays") ?? 0, String(t.effortDays).length));
283
- const roi = t.effortDays > 0 ? (t.impact / t.effortDays).toFixed(1) : "\u221E";
284
- widths.set("roi", Math.max(widths.get("roi") ?? 0, roi.length));
285
- widths.set("owner", Math.max(widths.get("owner") ?? 0, (t.ownerName ?? "\u2014").length));
286
- }
287
- let timeWidth = 0;
288
- if (timeHeader !== null) {
289
- timeWidth = timeHeader.length;
290
- for (const cell of timeCells) timeWidth = Math.max(timeWidth, cell.length);
291
- }
292
- const numCols = head.length;
293
- const otherTotal = otherCols.reduce((acc, c) => acc + (widths.get(c) ?? 0), 0) + timeWidth;
294
- const padding = numCols * 3 + 1;
295
- const titleBudget = Math.max(20, terminalWidth() - otherTotal - padding);
296
- const rows = [];
297
- for (let i = 0; i < tasks.length; i++) {
298
- const t = tasks[i];
299
- if (!t) continue;
300
- const roi = t.effortDays > 0 ? (t.impact / t.effortDays).toFixed(1) : "\u221E";
301
- const title = truncate(t.title, titleBudget);
302
- const baseRow = opts.withWorkstream ? [
303
- t.name,
304
- t.workstreamName,
305
- colorPair(t),
306
- title,
307
- String(t.impact),
308
- String(t.effortDays),
309
- roi,
310
- t.ownerName ?? pc.dim("\u2014")
311
- ] : [
312
- t.name,
313
- colorPair(t),
314
- title,
315
- String(t.impact),
316
- String(t.effortDays),
317
- roi,
318
- t.ownerName ?? pc.dim("\u2014")
319
- ];
320
- const row = timeHeader === null ? baseRow : [...baseRow, pc.dim(timeCells[i] ?? "")];
321
- rows.push(row);
322
- }
323
- return renderBoxTable(
324
- head.map((h) => pc.bold(h)),
325
- rows
326
- );
327
- }
328
-
329
- // src/tasks/sort.ts
330
- var TASK_SORT_KEYS = ["roi", "recency", "age", "id"];
331
- function isTaskSortKey(s) {
332
- return TASK_SORT_KEYS.includes(s);
333
- }
334
- function roiOf(t) {
335
- return t.effortDays > 0 ? t.impact / t.effortDays : Number.POSITIVE_INFINITY;
336
- }
337
- function byRoiDesc(a, b) {
338
- const ra = roiOf(a);
339
- const rb = roiOf(b);
340
- if (rb !== ra) return rb - ra;
341
- if (a.effortDays !== b.effortDays) return a.effortDays - b.effortDays;
342
- return compareStrings(a.name, b.name);
343
- }
344
- function sortTasks(tasks, key) {
345
- const out = tasks.slice();
346
- switch (key) {
347
- case "roi":
348
- return out.sort(byRoiDesc);
349
- case "recency":
350
- return out.sort((a, b) => {
351
- const byUpdated = compareStrings(b.updatedAt, a.updatedAt);
352
- return byUpdated !== 0 ? byUpdated : compareStrings(a.name, b.name);
353
- });
354
- case "age":
355
- return out.sort((a, b) => {
356
- const byCreated = compareStrings(a.createdAt, b.createdAt);
357
- return byCreated !== 0 ? byCreated : compareStrings(a.name, b.name);
358
- });
359
- case "id":
360
- return out.sort((a, b) => compareStrings(a.name, b.name));
361
- }
362
- }
363
- function relTimeBasisForSort(key) {
364
- if (key === "recency") return "updatedAt";
365
- if (key === "age") return "createdAt";
366
- return null;
367
- }
368
-
369
- export {
370
- statusIcon,
371
- IDLE_GLYPH,
372
- colorPair,
373
- colorStatus,
374
- inkColorForPair,
375
- truncate,
376
- truncateFront,
377
- relTime,
378
- relTimeAgo,
379
- formatAgentsTable,
380
- formatReadyTable,
381
- formatTracks,
382
- formatWorkspacesTable,
383
- printLogRow,
384
- formatWorkstreamsTable,
385
- formatTornDownWorkstreamsTable,
386
- formatTaskListTable,
387
- TASK_SORT_KEYS,
388
- isTaskSortKey,
389
- byRoiDesc,
390
- sortTasks,
391
- relTimeBasisForSort
392
- };
393
- //# sourceMappingURL=chunk-VIEYQMQA.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/format.ts","../src/tasks/sort.ts"],"sourcesContent":["// mu — pure rendering helpers used by every cli/*.ts verb wrapper.\n//\n// Extracted from src/cli.ts (review_cli_ts_past_refactor_signal): the\n// table renderers, status colourers, truncators, and relative-time\n// formatter were the bulk of cli.ts's bloat (~600 LOC over the 800-LOC\n// refactor signal). Co-locating them here keeps cli.ts focused on\n// argument parsing, workstream resolution, and program wiring.\n//\n// All helpers are pure (no I/O beyond returning a string) except\n// `printLogRow` which writes a single line to stdout — kept here so\n// `mu log` and the `recent events` section of `mu state` share one\n// renderer. cli.ts re-exports every symbol for back-compat with the\n// existing import surface (tests + cli/* importers).\n\nimport { type RuntimeState, UNKNOWN_REASON } from \"../agent-state.js\";\nimport type { LiveAgent } from \"../agents.js\";\nimport { agentStateGlyph, GLYPH } from \"../glyphs.js\";\nimport { parseOpKey, renderOp } from \"../log-render.js\";\nimport type { LogRow } from \"../logs.js\";\nimport { muTable, pc, renderBoxTable } from \"../output.js\";\nimport { formatPair, type TaskPair } from \"../tasks/status.js\";\nimport type { TaskRow } from \"../tasks.js\";\nimport type { Track } from \"../tracks.js\";\nimport type { WorkspaceRow } from \"../workspace.js\";\nimport type { TornDownWorkstream, WorkstreamSummary } from \"../workstream.js\";\n\n// ─── Status colours / icons ────────────────────────────────────────────\n\n/** Per-status colour for the table view. The glyph itself comes from\n * agentStateGlyph in src/glyphs.ts — single source of truth so the\n * CLI and TUI status surfaces never drift\n * (review_code_status_emoji_two_sources caught a 2-of-7 disagreement). */\nconst STATUS_COLORS: Record<RuntimeState, (s: string) => string> = {\n busy: pc.cyan,\n needs_input: pc.dim,\n needs_permission: pc.magenta,\n unknown: pc.dim,\n};\n\nexport function statusIcon(state: RuntimeState): string {\n return STATUS_COLORS[state](agentStateGlyph(state));\n}\n\n/**\n * Glyph used to flag the derived 'idle but assigned' state\n * (`AgentRow.idle === true`; idle_assigned_agent_detection). Same\n * warning glyph the doctor card paints for a `warn` check — one\n * symbol, one meaning, from src/glyphs.ts.\n */\nexport const IDLE_GLYPH = GLYPH.warn;\n\n/** Colour a task's (status, substate) pair, rendered by formatPair:\n * bare \"OPEN\" for a default substate, \"CLOSED/wontfix\" otherwise.\n * OPEN cyan, IN_PROGRESS yellow, CLOSED/done green, any other CLOSED\n * red (not done), OPEN/parked dim (out of the ready set). */\nexport function colorPair(pair: TaskPair): string {\n const text = formatPair(pair);\n switch (pair.status) {\n case \"OPEN\":\n return pair.substate === \"parked\" || pair.substate === \"triage\"\n ? pc.dim(text)\n : pc.cyan(text);\n case \"IN_PROGRESS\":\n return pc.yellow(text);\n case \"CLOSED\":\n return pair.substate === \"done\" ? pc.green(text) : pc.red(text);\n }\n}\n\n/** Colour a bare status. Only for callers with no substate in hand\n * (e.g. the TUI status-filter toggles); task rows use colorPair. */\nexport function colorStatus(status: TaskRow[\"status\"]): string {\n switch (status) {\n case \"OPEN\":\n return pc.cyan(status);\n case \"IN_PROGRESS\":\n return pc.yellow(status);\n case \"CLOSED\":\n return pc.green(status);\n }\n}\n\n/** Ink colour equivalent of colorPair(). The TUI must not embed\n * picocolors ANSI strings inside <Text>; rows pass this value to Ink's\n * color prop instead. */\nexport type InkColor = \"cyan\" | \"yellow\" | \"green\" | \"gray\" | \"red\";\n\n/** Ink colour for a task's pair; same mapping as colorPair(). */\nexport function inkColorForPair(pair: TaskPair): InkColor {\n switch (pair.status) {\n case \"OPEN\":\n return pair.substate === \"parked\" || pair.substate === \"triage\" ? \"gray\" : \"cyan\";\n case \"IN_PROGRESS\":\n return \"yellow\";\n case \"CLOSED\":\n return pair.substate === \"done\" ? \"green\" : \"red\";\n }\n}\n\n// ─── Width / truncation helpers ───────────────────────────────────────\n\n/**\n * Default fallback when stdout isn't a TTY (e.g. output is piped to\n * less/jq) and `process.stdout.columns` is undefined. 100 fits an 80-col\n * terminal with some breathing room; 100 is wide enough to keep most\n * rows on one line.\n */\nconst DEFAULT_TERMINAL_WIDTH = 100;\n\nexport function terminalWidth(): number {\n return process.stdout.columns ?? DEFAULT_TERMINAL_WIDTH;\n}\n\n/** Truncate `s` to fit `max` columns (counting display width as length;\n * good enough for ASCII titles, undercount for emoji/CJK — acceptable\n * trade-off given the terminal will visually clip anyway). Adds an\n * ellipsis when truncated. */\nexport function truncate(s: string, max: number): string {\n if (max <= 1) return s.slice(0, max);\n if (s.length <= max) return s;\n return `${s.slice(0, max - 1)}…`;\n}\n\n/** Front-truncate `s` to fit `max` columns, prepending an ellipsis when\n * truncated. Used for paths where the trailing `<workstream>/<agent>`\n * suffix is the only useful bit (the leading `~/.local/state/mu/...`\n * prefix is identical for every row). Surfaced live by `mu workspace\n * list` blowing the terminal width on the `path` column\n * (tables_truncate_long_cols_audit). */\nexport function truncateFront(s: string, max: number): string {\n if (max <= 1) return s.slice(-max);\n if (s.length <= max) return s;\n return `…${s.slice(-(max - 1))}`;\n}\n\n/** Format an elapsed duration (in ms) as a compact relative string:\n * '12s', '3m', '1h', '2d', '3w'. Used by the task list table when\n * `--sort recency`/`--sort age` is active so the timeframe the user\n * is sorting by is visible. Mirrors the helper in src/cli/state.ts\n * (TUI renderers); kept in sync (the TUI version omits weeks because it shows tighter\n * windows, but for task list a 5w-old item is real and worth a tag).\n */\nexport function relTime(ms: number): string {\n const sec = Math.max(0, Math.floor(ms / 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\n/** Like {@link relTime} but with a trailing \" ago\" suffix. The TUI's\n * Recent card / popup wants past-tense formatting (\"3m ago\") while\n * the In-progress card / popup wants the bare token (\"3m\"); both\n * buckets share the rest of the arithmetic. Hoisted out of the TUI\n * cluster (review_unify_format_when_since) so the two formatters\n * can't drift the way they were starting to. */\nexport function relTimeAgo(ms: number): string {\n return `${relTime(ms)} ago`;\n}\n\n// ─── Table renderers ──────────────────────────────────────────────────\n\nexport function formatAgentsTable(agents: readonly LiveAgent[]): string {\n if (agents.length === 0) return pc.dim(\" (no agents)\");\n // Cap the variable-width columns so a long tmux window name (or a\n // future free-text role) can't push the table past the terminal.\n // Other columns are bounded by their schemas (name 32-char cap,\n // cli is one of pi/codex/claude, status is one of OPEN/...).\n const table = muTable({\n head: [\n pc.bold(\"\"),\n pc.bold(\"name\"),\n pc.bold(\"cli\"),\n pc.bold(\"state\"),\n pc.bold(\"window\"),\n pc.bold(\"role\"),\n ],\n colWidths: [null, null, null, null, 32, 14],\n style: { head: [] },\n });\n for (const a of agents) {\n // Idle (alive + assigned + no recent progress): supplement the\n // status glyph with the yellow shared warning glyph, and yellow the\n // agent name itself so the row is visually obvious. The status column\n // stays the truth ('needs_input'); GLYPH.warn is the supplement.\n const idle = a.idle === true;\n const glyphCell = idle\n ? `${pc.yellow(IDLE_GLYPH)} ${statusIcon(a.state)}`\n : statusIcon(a.state);\n const nameCell = idle ? pc.yellow(a.name) : a.name;\n table.push([\n glyphCell,\n nameCell,\n a.cli,\n (a.state === \"unknown\" || a.state === \"needs_permission\") && a.reason !== undefined\n ? `${a.state}: ${a.reason}`\n : a.state,\n a.tab ?? a.name,\n a.role === \"read-only\" ? pc.yellow(\"read-only\") : \"\",\n ]);\n }\n const needsMurmur = agents.some(\n (a) =>\n a.state === \"unknown\" &&\n (a.reason === UNKNOWN_REASON.murmurMissing || a.reason === UNKNOWN_REASON.extensionMissing),\n );\n return needsMurmur\n ? `${table.toString()}\\n${pc.dim(\"agent state needs murmur (see mu doctor)\")}`\n : table.toString();\n}\n\nexport function formatReadyTable(tasks: readonly TaskRow[]): string {\n if (tasks.length === 0) return pc.dim(\" (no ready tasks)\");\n // Sort by ROI descending.\n const sorted = [...tasks].sort((a, b) => b.impact / b.effortDays - a.impact / a.effortDays);\n // Same title-truncation treatment as formatTaskListTable so the\n // ready-task table doesn't blow out terminal width.\n let idW = \"name\".length;\n let impactW = \"impact\".length;\n let effortW = \"effort\".length;\n let roiW = \"ROI\".length;\n let ownerW = \"owner\".length;\n for (const t of sorted) {\n idW = Math.max(idW, t.name.length);\n impactW = Math.max(impactW, String(t.impact).length);\n effortW = Math.max(effortW, String(t.effortDays).length);\n const roi = (t.impact / t.effortDays).toFixed(1);\n roiW = Math.max(roiW, roi.length);\n ownerW = Math.max(ownerW, (t.ownerName ?? \"\").length);\n }\n const padding = 6 * 3 + 1; // 6 cols\n const titleBudget = Math.max(\n 20,\n terminalWidth() - (idW + impactW + effortW + roiW + ownerW) - padding,\n );\n\n // Title is pre-truncated above; use muTable so wordWrap:false acts\n // as a safety belt for any other cell we don't pre-trim.\n const table = muTable({\n head: [\n pc.bold(\"name\"),\n pc.bold(\"title\"),\n pc.bold(\"impact\"),\n pc.bold(\"effort\"),\n pc.bold(\"ROI\"),\n pc.bold(\"owner\"),\n ],\n style: { head: [] },\n });\n for (const t of sorted) {\n const roi = (t.impact / t.effortDays).toFixed(1);\n table.push([\n t.name,\n truncate(t.title, titleBudget),\n String(t.impact),\n String(t.effortDays),\n roi,\n t.ownerName ?? \"\",\n ]);\n }\n return table.toString();\n}\n\nexport function formatTracks(tracks: readonly Track[]): string {\n if (tracks.length === 0) return pc.dim(\" (no open tracks)\");\n const lines: string[] = [];\n tracks.forEach((track, i) => {\n const rootNames = track.roots.map((r) => r.name).join(\", \");\n const verb = track.roots.length > 1 ? \"merged\" : \"track\";\n const parked = track.parked ? ` ${pc.dim(\"(parked)\")}` : \"\";\n lines.push(\n ` Track ${i + 1}: ${pc.bold(rootNames)} ${pc.dim(`(${track.taskIds.size} tasks, ${track.readyCount} ready, ${verb})`)}${parked}`,\n );\n });\n return lines.join(\"\\n\");\n}\n\n/** Workspaces table renderer. Used by `mu workspace list` and by\n * `mu state`'s Workspaces section — exported so cli/workspace.ts\n * can reuse it. The `behind` column shows how many commits the row's\n * parent_ref is behind the project's default branch HEAD; color-coded\n * green ≤2, yellow 3–9, red ≥10. Renders \"—\" when the count couldn't\n * be computed (no main resolvable, none-backend, missing parent_ref)\n * or wasn't asked for (no decorateWithStaleness call). Surfaced by\n * bug_workspace_stale_parent_silent_drift. */\nexport function formatWorkspacesTable(rows: readonly WorkspaceRow[]): string {\n // The path column is the one that bit operators today: an absolute\n // ~/.local/state/mu/workspaces/<ws>/worker-foo path runs ~70 chars\n // and pushed the table to ~200 cols. Front-truncate so the useful\n // trailing `<ws>/<agent>` suffix survives, then cap the column with\n // colWidths as a safety belt (tables_truncate_long_cols_audit).\n const PATH_BUDGET = 40;\n const table = muTable({\n head: [\"agent\", \"workstream\", \"backend\", \"path\", \"parent_ref\", \"behind\", \"created\"].map((h) =>\n pc.bold(h),\n ),\n colWidths: [null, null, null, PATH_BUDGET, null, null, null],\n });\n for (const r of rows) {\n table.push([\n r.agentName,\n r.workstreamName,\n r.backend,\n truncateFront(r.path, PATH_BUDGET - 2),\n r.parentRef ? pc.dim(r.parentRef.slice(0, 12)) : pc.dim(\"—\"),\n formatBehind(r.commitsBehindMain),\n pc.dim(r.createdAt),\n ]);\n }\n return table.toString();\n}\n\n/** Color-code the commits-behind-main count. Green ≤2 (fresh), yellow\n * 3–9 (drifting), red ≥10 (stale). Undefined / null renders as a dim\n * em-dash so the column stays well-typed even when staleness wasn't\n * computed. */\nfunction formatBehind(n: number | null | undefined): string {\n if (n === undefined || n === null) return pc.dim(\"—\");\n if (n <= 2) return pc.green(String(n));\n if (n <= 9) return pc.yellow(String(n));\n return pc.red(String(n));\n}\n\n/** One op, human-formatted. Used by `mu log` (read + tail) and by the\n * `recent events` section of `mu state`.\n *\n * All prose comes from `renderOp` (src/log-render.ts) — the ONE\n * formatter. This function only adds colour and column layout, so the\n * terminal and the TUI can never drift into two phrasings of the same\n * op. No raw JSON reaches the operator here. */\nexport function printLogRow(row: LogRow): void {\n const time = row.createdAt.replace(\"T\", \" \").replace(/\\.\\d+Z$/, \"Z\");\n const rendered = renderOp(row);\n const scope = parseOpKey(row.workstreamName).workstream;\n const where = scope === undefined ? pc.dim(\"—\") : scope;\n if (rendered === null) {\n // No intent: operator prose from `mu log \"<text>\"` or a `--kind`\n // ledger. Show it verbatim — inventing a verb for it would be\n // wrong, it is already exactly what the operator typed.\n console.log(\n `${pc.dim(`#${row.seq}`)} ${pc.dim(time)} ${pc.bold(row.source)} ${pc.yellow(row.kind)} [${where}] ${row.payload}`,\n );\n return;\n }\n const detail = rendered.detail === \"\" ? \"\" : ` ${pc.dim(rendered.detail)}`;\n console.log(\n `${pc.dim(`#${row.seq}`)} ${pc.dim(time)} ${pc.bold(row.source)} ${pc.cyan(rendered.verb)} ${rendered.subject}${detail} ${pc.dim(`[${where}]`)}`,\n );\n}\n\n/**\n * Workstreams summary table renderer. Used by `mu workstream list`\n * and the bare-`mu` empty-machine discovery fallback. Both\n * render the same shape; the helper lives here so cli/workstream.ts\n * and cli.ts can both import it without a lateral cli/* dependency.\n */\nexport function formatWorkstreamsTable(rows: WorkstreamSummary[]): string {\n // Workstream names are user-chosen free-form text; everything else\n // is a small int / fixed-shape token. Cap the name column so a long\n // workstream name doesn't push the row counts off-screen.\n const table = muTable({\n head: [\"name\", \"mux\", \"agents\", \"tasks\", \"edges\", \"notes\"].map((h) => pc.bold(h)),\n colWidths: [40, null, null, null, null, null],\n });\n for (const r of rows) {\n table.push([\n r.name,\n r.muxAlive ? pc.green(\"alive\") : pc.dim(\"—\"),\n String(r.agentCount),\n String(r.taskCount),\n String(r.edgeCount),\n String(r.noteCount),\n ]);\n }\n return table.toString();\n}\n\n/**\n * Past teardowns, newest first.\n *\n * `group` leads after the name because it is the only ACTIONABLE\n * column — the whole point of the list is to feed `mu undo <group>` —\n * and it is shown short (8 chars), which is what `mu undo` accepts.\n */\nexport function formatTornDownWorkstreamsTable(rows: readonly TornDownWorkstream[]): string {\n const table = muTable({\n head: [\"name\", \"group\", \"torn down\", \"tasks\", \"edges\", \"notes\", \"\"].map((h) => pc.bold(h)),\n colWidths: [32, null, null, null, null, null, null],\n });\n for (const r of rows) {\n // relTimeAgo takes an ELAPSED duration, not a timestamp.\n const at = Date.parse(r.at);\n table.push([\n r.name,\n r.group.slice(0, 8),\n Number.isNaN(at) ? pc.dim(\"—\") : relTimeAgo(Date.now() - at),\n String(r.tasks),\n String(r.edges),\n String(r.notes),\n r.recreated ? pc.dim(\"← recreated since\") : \"\",\n ]);\n }\n return table.toString();\n}\n\nexport function formatTaskListTable(\n tasks: readonly TaskRow[],\n opts: { withWorkstream?: boolean; relTimeBasis?: \"updatedAt\" | \"createdAt\" } = {},\n): string {\n if (tasks.length === 0) return pc.dim(\" (no tasks)\");\n // The optional relative-time column is appended at the end; header\n // mirrors the timestamp basis (\"updated\" / \"created\") so a glance\n // says which sort key is live.\n const timeHeader =\n opts.relTimeBasis === \"updatedAt\"\n ? \"updated\"\n : opts.relTimeBasis === \"createdAt\"\n ? \"created\"\n : null;\n const baseHead = opts.withWorkstream\n ? [\"name\", \"workstream\", \"status\", \"title\", \"impact\", \"effort\", \"ROI\", \"owner\"]\n : [\"name\", \"status\", \"title\", \"impact\", \"effort\", \"ROI\", \"owner\"];\n const head = timeHeader === null ? baseHead : [...baseHead, timeHeader];\n\n // Pre-compute the relative-time strings (relative to NOW) so column\n // width and row text agree.\n const now = Date.now();\n const timeCells: string[] = [];\n if (opts.relTimeBasis !== undefined) {\n const basis = opts.relTimeBasis;\n for (const t of tasks) {\n const ts = basis === \"updatedAt\" ? t.updatedAt : t.createdAt;\n const ms = now - new Date(ts).getTime();\n timeCells.push(relTime(ms));\n }\n }\n\n // Compute a budget for the title column so the table fits the terminal.\n // Other columns are mostly short fixed-shape values; figure out how\n // wide they actually are, sum them up, and give title the leftover.\n const otherCols = opts.withWorkstream\n ? ([\"localId\", \"workstream\", \"status\", \"impact\", \"effortDays\", \"roi\", \"owner\"] as const)\n : ([\"localId\", \"status\", \"impact\", \"effortDays\", \"roi\", \"owner\"] as const);\n const widths = new Map<string, number>();\n for (const col of otherCols) widths.set(col, col.length); // header is the floor\n for (const t of tasks) {\n widths.set(\"localId\", Math.max(widths.get(\"localId\") ?? 0, t.name.length));\n if (opts.withWorkstream) {\n widths.set(\"workstream\", Math.max(widths.get(\"workstream\") ?? 0, t.workstreamName.length));\n }\n widths.set(\"status\", Math.max(widths.get(\"status\") ?? 0, formatPair(t).length));\n widths.set(\"impact\", Math.max(widths.get(\"impact\") ?? 0, String(t.impact).length));\n widths.set(\"effortDays\", Math.max(widths.get(\"effortDays\") ?? 0, String(t.effortDays).length));\n const roi = t.effortDays > 0 ? (t.impact / t.effortDays).toFixed(1) : \"∞\";\n widths.set(\"roi\", Math.max(widths.get(\"roi\") ?? 0, roi.length));\n widths.set(\"owner\", Math.max(widths.get(\"owner\") ?? 0, (t.ownerName ?? \"—\").length));\n }\n let timeWidth = 0;\n if (timeHeader !== null) {\n timeWidth = timeHeader.length;\n for (const cell of timeCells) timeWidth = Math.max(timeWidth, cell.length);\n }\n // cli-table3 adds 2 chars of padding per cell + 1 char border per\n // column. Account for that to find the title budget.\n const numCols = head.length;\n const otherTotal = otherCols.reduce((acc, c) => acc + (widths.get(c) ?? 0), 0) + timeWidth;\n const padding = numCols * 3 + 1;\n const titleBudget = Math.max(20, terminalWidth() - otherTotal - padding);\n\n // Title is pre-truncated to titleBudget above. renderBoxTable, not\n // cli-table3: its layout pass is quadratic in rows\n // (f_tasklist_table_render).\n const rows: string[][] = [];\n for (let i = 0; i < tasks.length; i++) {\n const t = tasks[i];\n if (!t) continue; // noUncheckedIndexedAccess\n const roi = t.effortDays > 0 ? (t.impact / t.effortDays).toFixed(1) : \"∞\";\n const title = truncate(t.title, titleBudget);\n const baseRow = opts.withWorkstream\n ? [\n t.name,\n t.workstreamName,\n colorPair(t),\n title,\n String(t.impact),\n String(t.effortDays),\n roi,\n t.ownerName ?? pc.dim(\"—\"),\n ]\n : [\n t.name,\n colorPair(t),\n title,\n String(t.impact),\n String(t.effortDays),\n roi,\n t.ownerName ?? pc.dim(\"—\"),\n ];\n const row = timeHeader === null ? baseRow : [...baseRow, pc.dim(timeCells[i] ?? \"\")];\n rows.push(row);\n }\n return renderBoxTable(\n head.map((h) => pc.bold(h)),\n rows,\n );\n}\n","// Shared task sort keys and comparators.\n//\n// Used by the CLI task list/next verbs and by the TUI all-tasks popup.\n// Keep this below the CLI layer so TUI files never import from src/cli.ts\n// (that bundle cycle previously made `node dist/cli.js --help` exit\n// silently).\n\nimport { compareStrings } from \"../compare.js\";\nimport type { TaskRow } from \"./core.js\";\n\nexport const TASK_SORT_KEYS = [\"roi\", \"recency\", \"age\", \"id\"] as const;\nexport type TaskSortKey = (typeof TASK_SORT_KEYS)[number];\n\nexport function isTaskSortKey(s: string): s is TaskSortKey {\n return (TASK_SORT_KEYS as readonly string[]).includes(s);\n}\n\nfunction roiOf(t: TaskRow): number {\n return t.effortDays > 0 ? t.impact / t.effortDays : Number.POSITIVE_INFINITY;\n}\n\nexport function byRoiDesc(a: TaskRow, b: TaskRow): number {\n const ra = roiOf(a);\n const rb = roiOf(b);\n if (rb !== ra) return rb - ra;\n if (a.effortDays !== b.effortDays) return a.effortDays - b.effortDays;\n return compareStrings(a.name, b.name);\n}\n\n/** Sort a copy of `tasks` by `key`. Pure (does not mutate input). */\nexport function sortTasks(tasks: readonly TaskRow[], key: TaskSortKey): TaskRow[] {\n const out = tasks.slice();\n switch (key) {\n case \"roi\":\n return out.sort(byRoiDesc);\n case \"recency\":\n // updated_at DESC: most-recently-touched first.\n return out.sort((a, b) => {\n const byUpdated = compareStrings(b.updatedAt, a.updatedAt);\n return byUpdated !== 0 ? byUpdated : compareStrings(a.name, b.name);\n });\n case \"age\":\n // created_at ASC: oldest first (\"what's gone stale\").\n return out.sort((a, b) => {\n const byCreated = compareStrings(a.createdAt, b.createdAt);\n return byCreated !== 0 ? byCreated : compareStrings(a.name, b.name);\n });\n case \"id\":\n return out.sort((a, b) => compareStrings(a.name, b.name));\n }\n}\n\n/** Which timestamp basis the table's relative-time column should use\n * for the active sort, or `null` if no time column should be shown. */\nexport function relTimeBasisForSort(key: TaskSortKey): \"updatedAt\" | \"createdAt\" | null {\n if (key === \"recency\") return \"updatedAt\";\n if (key === \"age\") return \"createdAt\";\n return null;\n}\n"],"mappings":"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","names":[]}
@@ -1,19 +0,0 @@
1
- import { createRequire as __muCreateRequire } from "node:module"; const require = __muCreateRequire(import.meta.url);
2
-
3
- // src/state-dir.ts
4
- import { homedir } from "os";
5
- import { isAbsolute, join } from "path";
6
- function defaultStateDir() {
7
- if (process.env.MU_STATE_DIR) return process.env.MU_STATE_DIR;
8
- return join(xdgStateHome(homedir()), "mu");
9
- }
10
- function xdgStateHome(home) {
11
- const xdg = process.env.XDG_STATE_HOME;
12
- return xdg !== void 0 && isAbsolute(xdg) ? xdg : join(home, ".local", "state");
13
- }
14
-
15
- export {
16
- defaultStateDir,
17
- xdgStateHome
18
- };
19
- //# sourceMappingURL=chunk-WI6AWCMK.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/state-dir.ts"],"sourcesContent":["// The mu state directory, resolved from builtins only so the CLI\n// bootstrap (src/main.ts) can locate its compile cache before it loads\n// any of the CLI graph. db.ts re-exports defaultStateDir.\n\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join } from \"node:path\";\n\n/**\n * Resolve the canonical mu state directory:\n * MU_STATE_DIR > $XDG_STATE_HOME/mu (absolute only) > ~/.local/state/mu\n */\nexport function defaultStateDir(): string {\n if (process.env.MU_STATE_DIR) return process.env.MU_STATE_DIR;\n return join(xdgStateHome(homedir()), \"mu\");\n}\n\n/**\n * The mu cache directory, `$XDG_CACHE_HOME/mu` (absolute only) else\n * `~/.cache/mu`: files mu can lose at any time, such as copies of data\n * that lives elsewhere too.\n */\nexport function defaultCacheDir(): string {\n const xdg = process.env.XDG_CACHE_HOME;\n return join(xdg !== undefined && isAbsolute(xdg) ? xdg : join(homedir(), \".cache\"), \"mu\");\n}\n\n/** `$XDG_STATE_HOME`, or `<home>/.local/state` when it is unset, empty,\n * or relative: the XDG spec says such a value must be ignored, and a\n * relative one would scatter state across every cwd mu runs in. */\nexport function xdgStateHome(home: string): string {\n const xdg = process.env.XDG_STATE_HOME;\n return xdg !== undefined && isAbsolute(xdg) ? xdg : join(home, \".local\", \"state\");\n}\n"],"mappings":";;;AAIA,SAAS,eAAe;AACxB,SAAS,YAAY,YAAY;AAM1B,SAAS,kBAA0B;AACxC,MAAI,QAAQ,IAAI,aAAc,QAAO,QAAQ,IAAI;AACjD,SAAO,KAAK,aAAa,QAAQ,CAAC,GAAG,IAAI;AAC3C;AAeO,SAAS,aAAa,MAAsB;AACjD,QAAM,MAAM,QAAQ,IAAI;AACxB,SAAO,QAAQ,UAAa,WAAW,GAAG,IAAI,MAAM,KAAK,MAAM,UAAU,OAAO;AAClF;","names":[]}