@mu-crew/mu 3.2.1 → 3.3.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 (50) hide show
  1. package/AGENTS.md +10 -5
  2. package/README.md +49 -2
  3. package/dist/cli.js +108 -78
  4. package/dist/cli.js.map +1 -1
  5. package/dist/extension/mu-pi.js +244 -12
  6. package/dist/extension/mu-pi.js.map +1 -1
  7. package/dist/index.d.ts +36 -10
  8. package/dist/index.js +89 -67
  9. package/dist/index.js.map +1 -1
  10. package/docs/ARCHITECTURE.md +1 -0
  11. package/docs/VOCABULARY.md +2 -0
  12. package/docs/architecture/control-socket.md +17 -3
  13. package/docs/architecture/mux.md +3 -4
  14. package/docs/guide/README.md +5 -0
  15. package/docs/guide/dispatch.md +11 -4
  16. package/docs/guide/getting-started.md +3 -0
  17. package/docs/guide/recovery.md +3 -1
  18. package/docs/guide/remote.md +1 -1
  19. package/docs/history/CHANGELOG-pre-3.md +1 -1
  20. package/docs/reference/env.md +1 -1
  21. package/docs/specs/2026-10-03-keep-driving-nudge.md +142 -0
  22. package/package.json +2 -1
  23. package/prompts/mu-refute.md +8 -0
  24. package/prompts/mu-research.md +8 -0
  25. package/prompts/mu-review.md +8 -0
  26. package/prompts/mu-rules-audit.md +8 -0
  27. package/prompts/mu-tournament.md +9 -0
  28. package/prompts/ultrathink.md +9 -0
  29. package/skills/mu/SKILL.md +71 -131
  30. package/skills/mu/recipes/adversarial-review.md +83 -0
  31. package/skills/mu/recipes/brief.md +53 -0
  32. package/skills/mu/recipes/codemode-driver.md +54 -0
  33. package/skills/mu/recipes/deep-research.md +52 -0
  34. package/skills/mu/recipes/fan-out.md +36 -0
  35. package/skills/mu/recipes/hypothesis-panel.md +40 -0
  36. package/skills/mu/recipes/long-run.md +20 -0
  37. package/skills/mu/recipes/loop-until-done.md +38 -0
  38. package/skills/mu/recipes/orchestrator-loop.md +87 -0
  39. package/skills/mu/recipes/plan.md +54 -0
  40. package/skills/mu/recipes/recovery.md +26 -0
  41. package/skills/mu/recipes/refute.md +49 -0
  42. package/skills/mu/recipes/review-panel.md +57 -0
  43. package/skills/mu/recipes/rules-audit.md +70 -0
  44. package/skills/mu/recipes/tournament.md +37 -0
  45. package/skills/mu/recipes/triage.md +38 -0
  46. package/skills/mu/recipes/ultrathink.md +73 -0
  47. package/skills/mu/recipes/watcher.md +16 -0
  48. package/skills/mu/recipes/waves.md +19 -0
  49. package/skills/mu/recipes/worker.md +24 -0
  50. /package/skills/mu/{REMOTE_WORKERS.md → recipes/remote-workers.md} +0 -0
@@ -1,26 +1,30 @@
1
1
  // extension/mu-pi.ts
2
2
  import {
3
3
  chmodSync,
4
- existsSync as existsSync2,
4
+ existsSync as existsSync3,
5
5
  linkSync,
6
6
  mkdirSync,
7
- readFileSync,
7
+ readdirSync,
8
+ readFileSync as readFileSync2,
8
9
  realpathSync,
9
10
  rmSync,
10
11
  statSync as statSync2
11
12
  } from "fs";
12
13
  import { connect, createServer } from "net";
13
- import { dirname, join as join2 } from "path";
14
+ import { dirname, join as join3 } from "path";
15
+ import { fileURLToPath as fileURLToPath2 } from "url";
14
16
 
15
17
  // src/ctl/protocol.ts
16
18
  var CTL_PROTOCOL_VERSION = 1;
19
+ var CTL_COMMANDS = ["new", "reload", "compact"];
17
20
  var CTL_OPS = [
18
21
  "hello",
19
22
  "status",
20
23
  "send",
21
24
  "wait",
22
25
  "abort",
23
- "fresh"
26
+ "fresh",
27
+ "command"
24
28
  ];
25
29
  var UNKNOWN_OP_PREFIX = "unknown op: ";
26
30
  function encode(msg) {
@@ -427,10 +431,143 @@ ${task}` : task, "-w", W]);
427
431
  });
428
432
  }
429
433
 
434
+ // extension/nudge.ts
435
+ import { existsSync as existsSync2, readFileSync } from "fs";
436
+ import { join as join2 } from "path";
437
+ var NUDGE_MESSAGE_TYPE = "mu-keep-driving";
438
+ var NUDGE_LOG_KIND = "nudge";
439
+ var MARK_START = "<!-- mu:keep-driving -->";
440
+ var MARK_END = "<!-- /mu:keep-driving -->";
441
+ var WAIT_HINT_MAX = 8;
442
+ function nudgeEnabled(env = process.env) {
443
+ return env.MU_NUDGE !== "0";
444
+ }
445
+ function keepDrivingRule(skillMd) {
446
+ const i = skillMd.indexOf(MARK_START);
447
+ const j = skillMd.indexOf(MARK_END);
448
+ if (i < 0 || j < i) return void 0;
449
+ const text = skillMd.slice(i + MARK_START.length, j).trim();
450
+ return text === "" ? void 0 : text;
451
+ }
452
+ function readSkill() {
453
+ for (const up of ["..", "../.."]) {
454
+ const p = join2(import.meta.dirname ?? "", up, "skills", "mu", "SKILL.md");
455
+ if (existsSync2(p)) return readFileSync(p, "utf8");
456
+ }
457
+ return void 0;
458
+ }
459
+ function dispatchedWorkstreams(command) {
460
+ const found = /* @__PURE__ */ new Set();
461
+ for (const seg of command.split(/&&|\|\||;|\n|\|/)) {
462
+ const words = seg.trim().split(/\s+/);
463
+ const at = words.findIndex((w) => w === "mu" || w.endsWith("/mu"));
464
+ if (at < 0) continue;
465
+ const [noun, verb] = [words[at + 1], words[at + 2]];
466
+ const isSendSpawn = noun === "agent" && (verb === "send" || verb === "spawn");
467
+ const isClaimFor = noun === "task" && verb === "claim" && words.includes("--for");
468
+ if (!isSendSpawn && !isClaimFor) continue;
469
+ let ws = "";
470
+ for (let k = at + 3; k < words.length; k++) {
471
+ const w = words[k] ?? "";
472
+ if (w === "-w" || w === "--workstream") ws = unquote(words[k + 1] ?? "");
473
+ else if (w.startsWith("--workstream=")) ws = unquote(w.slice("--workstream=".length));
474
+ }
475
+ if (ws === "" && isClaimFor) {
476
+ const ref = words[at + 3] ?? "";
477
+ if (ref.includes("/")) ws = unquote(ref.split("/")[0] ?? "");
478
+ }
479
+ if (ws !== "scratch") found.add(ws);
480
+ }
481
+ return found.size > 0 ? [...found] : void 0;
482
+ }
483
+ function unquote(s) {
484
+ return s.replace(/^['"]|['"]$/g, "");
485
+ }
486
+ async function inProgress(run, workstreams) {
487
+ const named = workstreams.filter((w) => w !== "");
488
+ const args = ["state", "--json", "--events", "0"];
489
+ for (const w of named) args.push("-w", w);
490
+ const r = await run(args);
491
+ if (r.code !== 0) return void 0;
492
+ let json;
493
+ try {
494
+ json = JSON.parse(r.stdout);
495
+ } catch {
496
+ return void 0;
497
+ }
498
+ const cards = json.workstreams ?? [json];
499
+ const refs = [];
500
+ for (const ws of cards) {
501
+ for (const t of ws.inProgress ?? []) {
502
+ if (t.name) refs.push(`${ws.workstreamName ?? ""}/${t.name}`);
503
+ }
504
+ }
505
+ return refs;
506
+ }
507
+ function nudgeText(rule, refs) {
508
+ const shown = refs.slice(0, WAIT_HINT_MAX).join(" ");
509
+ const more = refs.length > WAIT_HINT_MAX ? ` (+${refs.length - WAIT_HINT_MAX} more)` : "";
510
+ return [
511
+ `[mu] ${rule}`,
512
+ "",
513
+ `${refs.length} task(s) IN_PROGRESS. Next: mu task wait ${shown} --first --on-stall exit --json${more}`
514
+ ].join("\n");
515
+ }
516
+ function outcomeOf(event) {
517
+ return typeof event === "object" && event !== null ? event.outcome : void 0;
518
+ }
519
+ function bashCommand(event) {
520
+ if (typeof event !== "object" || event === null) return void 0;
521
+ const e = event;
522
+ return e.toolName === "bash" && typeof e.input?.command === "string" ? e.input.command : void 0;
523
+ }
524
+ function registerNudge(pi, run = defaultRunner(), skillMd = readSkill()) {
525
+ if (!nudgeEnabled()) return;
526
+ const rule = skillMd === void 0 ? void 0 : keepDrivingRule(skillMd);
527
+ if (rule === void 0) return;
528
+ const armed = /* @__PURE__ */ new Set();
529
+ let fired = false;
530
+ pi.on("input", () => {
531
+ armed.clear();
532
+ fired = false;
533
+ });
534
+ pi.on("tool_call", (event) => {
535
+ const cmd = bashCommand(event);
536
+ if (cmd === void 0) return;
537
+ for (const ws of dispatchedWorkstreams(cmd) ?? []) armed.add(ws);
538
+ });
539
+ pi.on("agent_before_settle", async (event) => {
540
+ if (fired || armed.size === 0) return;
541
+ if (outcomeOf(event) !== "completed") return;
542
+ fired = true;
543
+ const refs = await inProgress(run, [...armed]);
544
+ if (refs === void 0 || refs.length === 0) return;
545
+ const text = nudgeText(rule, refs);
546
+ const logWs = [...armed].find((w) => w !== "");
547
+ await run([
548
+ "log",
549
+ ...logWs ? ["-w", logWs] : [],
550
+ "--kind",
551
+ NUDGE_LOG_KIND,
552
+ `keep-driving: ${refs.length} in progress`
553
+ ]);
554
+ return {
555
+ entries: [
556
+ { type: "custom_message", customType: NUDGE_MESSAGE_TYPE, content: text, display: true }
557
+ ],
558
+ continue: true
559
+ };
560
+ });
561
+ }
562
+
430
563
  // extension/mu-pi.ts
431
- var EXT_VERSION = true ? "3.2.1" : void 0;
564
+ var EXT_VERSION = true ? "3.3.0" : void 0;
432
565
  var FRESH_COMMAND = "mu-fresh";
433
566
  var FRESH_TIMEOUT_MS = 3e4;
567
+ function commandName(name) {
568
+ return `mu-${name}`;
569
+ }
570
+ var COMMAND_TIMEOUT_MS = 3e4;
434
571
  var LAST_TEXT_MAX_BYTES = 64 * 1024;
435
572
  var LAST_TEXT_TRUNCATED = "[truncated, see pane]";
436
573
  var LIVE_PROBE_MS = 500;
@@ -444,9 +581,9 @@ function detectPiVersion() {
444
581
  if (!entry) return void 0;
445
582
  let dir = dirname(realpathSync(entry));
446
583
  for (let i = 0; i < 6; i++) {
447
- const pkgPath = join2(dir, "package.json");
448
- if (existsSync2(pkgPath)) {
449
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
584
+ const pkgPath = join3(dir, "package.json");
585
+ if (existsSync3(pkgPath)) {
586
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
450
587
  if (typeof pkg === "object" && pkg !== null) {
451
588
  const { name, version } = pkg;
452
589
  if (typeof name === "string" && /pi/.test(name) && typeof version === "string") {
@@ -529,8 +666,31 @@ function reasonOf(event) {
529
666
  const r = typeof event === "object" && event !== null ? event.reason : void 0;
530
667
  return typeof r === "string" ? r : void 0;
531
668
  }
669
+ function promptsDir(from = import.meta.url) {
670
+ let here;
671
+ try {
672
+ here = dirname(fileURLToPath2(from));
673
+ } catch {
674
+ return void 0;
675
+ }
676
+ for (const up of ["..", "../.."]) {
677
+ const d = join3(here, up, "prompts");
678
+ if (existsSync3(join3(d, "ultrathink.md"))) return d;
679
+ }
680
+ return void 0;
681
+ }
682
+ function registerPrompts(pi) {
683
+ pi.on("resources_discover", () => {
684
+ const d = promptsDir();
685
+ if (!d) return {};
686
+ const paths = readdirSync(d).filter((f) => f.endsWith(".md")).sort().map((f) => join3(d, f));
687
+ return { promptPaths: paths };
688
+ });
689
+ }
532
690
  function muPi(pi) {
533
691
  registerDelegate(pi);
692
+ registerPrompts(pi);
693
+ registerNudge(pi);
534
694
  serveCtl(pi);
535
695
  }
536
696
  function serveCtl(pi) {
@@ -565,6 +725,12 @@ function serveCtl(pi) {
565
725
  g.fresh = void 0;
566
726
  f.settle(r);
567
727
  };
728
+ const settleCommand = (r) => {
729
+ const c = g.command;
730
+ if (!c) return;
731
+ g.command = void 0;
732
+ c.settle(r);
733
+ };
568
734
  pi.on("agent_start", (_e, c) => {
569
735
  g.ctx = c;
570
736
  g.state = "busy";
@@ -603,10 +769,69 @@ function serveCtl(pi) {
603
769
  settleFresh({ v: V, ok: true, ...status() });
604
770
  }
605
771
  });
772
+ pi.on("session_before_compact", () => {
773
+ if (g.command?.name === "compact") settleCommand({ v: V, ok: true, ...status() });
774
+ });
775
+ async function runCommand(name, ctx) {
776
+ const c = g.command;
777
+ if (c?.name !== name) return;
778
+ const ok = () => settleCommand({ v: V, ok: true, ...status() });
779
+ const failWith = (e) => settleCommand(fail(e instanceof Error ? e.message : String(e)));
780
+ try {
781
+ if (name === "new") {
782
+ const r = await ctx.newSession();
783
+ if (r.cancelled) settleCommand(fail("new session was cancelled"));
784
+ else ok();
785
+ } else if (name === "reload") {
786
+ await ctx.reload();
787
+ if (c.reloaded) ok();
788
+ else settleCommand(fail("pi did not reload (busy or compacting)"));
789
+ } else {
790
+ ctx.compact({
791
+ ...c.instructions !== void 0 ? { customInstructions: c.instructions } : {},
792
+ onComplete: ok,
793
+ onError: failWith
794
+ });
795
+ }
796
+ } catch (e) {
797
+ failWith(e);
798
+ }
799
+ }
800
+ for (const name of CTL_COMMANDS) {
801
+ pi.registerCommand(commandName(name), {
802
+ description: `mu internal: /${name} requested over the control socket (mu agent send '/${name}')`,
803
+ handler: (_args, ctx) => runCommand(name, ctx)
804
+ });
805
+ }
806
+ function command(req) {
807
+ const name = CTL_COMMANDS.find((n) => n === req.name);
808
+ if (name === void 0) {
809
+ return fail(`command must be one of ${CTL_COMMANDS.join(", ")}`);
810
+ }
811
+ if (g.fresh || g.command) return fail("a fresh send or command is already in progress");
812
+ const busy = !(g.ctx?.isIdle() ?? g.state === "idle");
813
+ if (busy && req.force !== true) return fail("busy");
814
+ const instructions = str(req, "instructions");
815
+ return new Promise((resolve) => {
816
+ const timer = setTimeout(() => settleCommand(fail(`/${name} timed out`)), COMMAND_TIMEOUT_MS);
817
+ g.command = {
818
+ name,
819
+ ...instructions !== void 0 ? { instructions } : {},
820
+ reloaded: false,
821
+ settle: (r) => {
822
+ clearTimeout(timer);
823
+ resolve(r);
824
+ }
825
+ };
826
+ Promise.resolve(
827
+ g.pi.sendUserMessage(`/${commandName(name)}`, { expandPromptTemplates: true })
828
+ ).catch((e) => settleCommand(fail(e instanceof Error ? e.message : String(e))));
829
+ });
830
+ }
606
831
  function fresh(req) {
607
832
  const text = str(req, "text");
608
833
  if (text === void 0) return fail("fresh needs a string text");
609
- if (g.fresh) return fail("a fresh send is already in progress");
834
+ if (g.fresh || g.command) return fail("a fresh send or command is already in progress");
610
835
  const busy = !(g.ctx?.isIdle() ?? g.state === "idle");
611
836
  if (busy && req.force !== true) return fail("busy");
612
837
  return new Promise((resolve) => {
@@ -648,6 +873,8 @@ function serveCtl(pi) {
648
873
  }
649
874
  case "fresh":
650
875
  return fresh(req);
876
+ case "command":
877
+ return command(req);
651
878
  case "wait":
652
879
  return wait(num(req, "afterRuns"), num(req, "timeoutMs"), conn);
653
880
  case "abort":
@@ -720,7 +947,7 @@ function serveCtl(pi) {
720
947
  return;
721
948
  }
722
949
  rmSync(sockPath, { force: true });
723
- const tmp = join2(dirname(sockPath), `.${process.pid.toString(36)}${(++bindSeq).toString(36)}`);
950
+ const tmp = join3(dirname(sockPath), `.${process.pid.toString(36)}${(++bindSeq).toString(36)}`);
724
951
  rmSync(tmp, { force: true });
725
952
  const s = createServer(onConnection);
726
953
  await new Promise((resolve, reject) => {
@@ -745,8 +972,9 @@ function serveCtl(pi) {
745
972
  g.server = s;
746
973
  g.ino = inodeOf(sockPath);
747
974
  }
748
- pi.on("session_start", async (_e, c) => {
975
+ pi.on("session_start", async (e, c) => {
749
976
  g.ctx = c;
977
+ if (reasonOf(e) === "reload" && g.command?.name === "reload") g.command.reloaded = true;
750
978
  const s = g.server;
751
979
  if (s) {
752
980
  if (g.ino !== void 0 && inodeOf(sockPath) === g.ino) return;
@@ -769,18 +997,22 @@ function serveCtl(pi) {
769
997
  if (!s) return;
770
998
  g.waiters.clear();
771
999
  settleFresh(fail("pi is shutting down"));
1000
+ settleCommand(fail("pi is shutting down"));
772
1001
  for (const c of g.conns) c.destroy();
773
1002
  await new Promise((resolve) => s.close(() => resolve()));
774
1003
  if (ino !== void 0 && inodeOf(sockPath) === ino) rmSync(sockPath, { force: true });
775
1004
  });
776
1005
  }
777
1006
  export {
1007
+ COMMAND_TIMEOUT_MS,
778
1008
  FRESH_COMMAND,
779
1009
  FRESH_TIMEOUT_MS,
780
1010
  LAST_TEXT_MAX_BYTES,
781
1011
  LAST_TEXT_TRUNCATED,
782
1012
  LIVE_PROBE_MS,
1013
+ commandName,
783
1014
  muPi as default,
784
- lastAssistantText
1015
+ lastAssistantText,
1016
+ promptsDir
785
1017
  };
786
1018
  //# sourceMappingURL=mu-pi.js.map