@ionivetech/mugiwara 0.6.4 → 0.6.6

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 (115) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.codex-plugin/plugin.json +48 -2
  4. package/.cursor-plugin/plugin.json +48 -2
  5. package/.kimi-plugin/plugin.json +48 -2
  6. package/.opencode/commands/mugiwara-continue.md +44 -31
  7. package/.opencode/commands/mugiwara-execute.md +1 -1
  8. package/.opencode/commands/mugiwara-heal.md +1 -1
  9. package/.opencode/commands/mugiwara-onboard.md +8 -9
  10. package/.opencode/commands/mugiwara-plan.md +1 -1
  11. package/.opencode/commands/mugiwara-review.md +1 -1
  12. package/.opencode/commands/mugiwara-security.md +1 -1
  13. package/.opencode/commands/mugiwara-ship.md +1 -1
  14. package/.opencode/commands/mugiwara.md +2 -2
  15. package/.opencode/mugiwara-helpers.mjs +3 -0
  16. package/.opencode/plugins/mugiwara.mjs +1 -1
  17. package/AGENTS.md +12 -2
  18. package/README.md +53 -32
  19. package/content/agents/brook-healing.md +5 -8
  20. package/content/agents/chopper-checkpoint.md +8 -11
  21. package/content/agents/eval-runner.md +2 -5
  22. package/content/agents/franky-gates.md +4 -7
  23. package/content/agents/jinbe-security.md +4 -7
  24. package/content/agents/luffy-orchestrator.md +13 -13
  25. package/content/agents/memory-keeper.md +5 -8
  26. package/content/agents/nami-planner.md +4 -7
  27. package/content/agents/resume-coordinator.md +7 -10
  28. package/content/agents/robin-reviewer.md +6 -9
  29. package/content/agents/sanji-quality.md +7 -10
  30. package/content/agents/skeptic-verifier.md +5 -8
  31. package/content/agents/usopp-brainstorm.md +3 -6
  32. package/content/agents/zoro-execution.md +7 -10
  33. package/content/skills/mugiwara-backend/SKILL.md +2 -0
  34. package/content/skills/mugiwara-checkpoint/SKILL.md +14 -11
  35. package/content/skills/mugiwara-checkpoint/references/ledger-format.md +4 -4
  36. package/content/skills/mugiwara-context-budget/SKILL.md +5 -3
  37. package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
  38. package/content/skills/mugiwara-execution/SKILL.md +27 -27
  39. package/content/skills/mugiwara-execution/references/dispatch.md +2 -2
  40. package/content/skills/mugiwara-execution/references/resume-batching.md +1 -1
  41. package/content/skills/mugiwara-frontend/SKILL.md +2 -4
  42. package/content/skills/mugiwara-gates/SKILL.md +9 -7
  43. package/content/skills/mugiwara-git/SKILL.md +7 -2
  44. package/content/skills/mugiwara-healing/SKILL.md +6 -4
  45. package/content/skills/mugiwara-healing/references/workers.md +2 -2
  46. package/content/skills/mugiwara-lessons/SKILL.md +1 -0
  47. package/content/skills/mugiwara-orchestration/SKILL.md +34 -35
  48. package/content/skills/mugiwara-orchestration/references/check-ins.md +17 -16
  49. package/content/skills/mugiwara-orchestration/references/closure.md +5 -5
  50. package/content/skills/mugiwara-orchestration/references/output-contract.md +77 -0
  51. package/content/skills/mugiwara-orchestration/references/triage-escalation.md +22 -22
  52. package/content/skills/mugiwara-planning/SKILL.md +3 -1
  53. package/content/skills/mugiwara-planning/references/plan-template.md +2 -2
  54. package/content/skills/mugiwara-pr/SKILL.md +6 -6
  55. package/content/skills/mugiwara-pr/references/verdict-format.md +3 -3
  56. package/content/skills/mugiwara-quality/SKILL.md +8 -7
  57. package/content/skills/mugiwara-resume/SKILL.md +19 -20
  58. package/content/skills/mugiwara-review/SKILL.md +6 -1
  59. package/content/skills/mugiwara-review/references/five-axis-worksheet.md +1 -1
  60. package/content/skills/mugiwara-root-cause/SKILL.md +2 -0
  61. package/content/skills/mugiwara-security/SKILL.md +5 -1
  62. package/content/skills/mugiwara-ship/SKILL.md +1 -1
  63. package/content/skills/mugiwara-ship/references/cleanup.md +1 -1
  64. package/content/skills/mugiwara-testcases/SKILL.md +5 -3
  65. package/content/skills/mugiwara-workflow/SKILL.md +38 -39
  66. package/content/skills/mugiwara-workflow/references/workspace-layout.md +15 -15
  67. package/content/skills/using-mugiwara/SKILL.md +5 -5
  68. package/dist/mugiwara.js +807 -31
  69. package/gemini-extension.json +1 -1
  70. package/hooks/auto-savepoint.js +106 -0
  71. package/hooks/auto-savepoint.ts +102 -0
  72. package/hooks/engagement-marker.js +59 -0
  73. package/hooks/engagement-marker.ts +97 -0
  74. package/hooks/hooks.json +41 -2
  75. package/hooks/mugiwara-mode-tracker.js +79 -0
  76. package/hooks/pipeline-guard.js +182 -0
  77. package/hooks/pipeline-guard.ts +267 -0
  78. package/hooks/session-start.js +106 -0
  79. package/hooks/session-start.ts +44 -32
  80. package/package.json +7 -3
  81. package/plugin.json +1 -1
  82. package/references/agent-protocol.md +15 -0
  83. package/references/complexity.md +54 -0
  84. package/references/definition-of-done.md +1 -1
  85. package/references/multi-actor.md +5 -5
  86. package/references/skill-versioning.md +1 -1
  87. package/references/token-budget.md +30 -8
  88. package/references/wave-banners.md +21 -31
  89. package/scripts/build-hooks.ts +56 -0
  90. package/scripts/conformance.ts +232 -0
  91. package/scripts/coverage-gate.ts +179 -0
  92. package/scripts/evidence.sh +17 -4
  93. package/scripts/gate-selftest.ts +14 -0
  94. package/scripts/initiative.ts +51 -17
  95. package/scripts/install.ps1 +5 -4
  96. package/scripts/install.sh +4 -4
  97. package/scripts/lane.sh +6 -2
  98. package/scripts/lib/patterns.sh +84 -0
  99. package/scripts/mission-report.sh +12 -11
  100. package/scripts/savepoint.sh +152 -37
  101. package/scripts/validate-content.ts +22 -2
  102. package/scripts/verify-install.ts +154 -44
  103. package/src/args.ts +1 -1
  104. package/src/cli.ts +217 -0
  105. package/src/continue.ts +243 -0
  106. package/src/installer.ts +39 -0
  107. package/src/onboard.ts +207 -0
  108. package/src/run.ts +82 -0
  109. package/src/targets/claude.ts +140 -2
  110. package/src/targets/copilot.ts +20 -1
  111. package/src/targets/generic.ts +9 -4
  112. package/content/agents/onboarding-guide.md +0 -124
  113. package/content/skills/mugiwara-security/references/owasp-mapping.md +0 -30
  114. package/scripts/onboard.ts +0 -266
  115. package/scripts/probe.ts +0 -40
package/dist/mugiwara.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { existsSync as existsSync7, readdirSync as readdirSync5, rmSync as rmSync3 } from "node:fs";
4
+ import { existsSync as existsSync10, readFileSync as readFileSync8, readdirSync as readdirSync7, rmSync as rmSync3 } from "node:fs";
5
+ import { spawnSync as spawnSync2 } from "node:child_process";
5
6
  import { homedir as homedir2 } from "node:os";
6
- import { join as join8, resolve } from "node:path";
7
- import { fileURLToPath as fileURLToPath4 } from "node:url";
7
+ import { join as join11, resolve as resolve2 } from "node:path";
8
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
8
9
 
9
10
  // src/args.ts
10
11
  var VALUE_FLAGS = { "--project": "project", "--target": "target" };
@@ -16,6 +17,7 @@ var BOOL_FLAGS = {
16
17
  "--dry-run": "dryRun",
17
18
  "--keep-logs": "keepLogs",
18
19
  "--check": "check",
20
+ "--all": "all",
19
21
  "--help": "help",
20
22
  "-h": "help",
21
23
  "--version": "version",
@@ -81,7 +83,7 @@ async function confirm(rl, question) {
81
83
  }
82
84
 
83
85
  // src/targets/claude.ts
84
- import { existsSync, mkdirSync, copyFileSync, chmodSync, readdirSync } from "node:fs";
86
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, chmodSync, readdirSync } from "node:fs";
85
87
  import { dirname, join } from "node:path";
86
88
  import { fileURLToPath } from "node:url";
87
89
 
@@ -119,6 +121,107 @@ function toolsFromScope(scope) {
119
121
  return "Read, Grep, Glob, Write, Bash, WebFetch, WebSearch";
120
122
  return;
121
123
  }
124
+ var SCRIPTS_SRC = join(here, "..", "..", "scripts");
125
+ function copyScripts(dstDir) {
126
+ if (!existsSync(SCRIPTS_SRC))
127
+ return [];
128
+ const written = [];
129
+ const copyDir = (src, dst) => {
130
+ if (!existsSync(src))
131
+ return;
132
+ for (const f of readdirSync(src)) {
133
+ if (!f.endsWith(".sh") && f !== "initiative.ts" || f === "install.sh")
134
+ continue;
135
+ const to = join(dst, f);
136
+ mkdirSync(dst, { recursive: true });
137
+ copyFileSync(join(src, f), to);
138
+ chmodSync(to, 493);
139
+ written.push(to);
140
+ }
141
+ };
142
+ copyDir(SCRIPTS_SRC, dstDir);
143
+ copyDir(join(SCRIPTS_SRC, "lib"), join(dstDir, "lib"));
144
+ return written;
145
+ }
146
+ function wireSettings(root, hooksDir) {
147
+ const file = join(root, "settings.json");
148
+ const events = {
149
+ SessionStart: { file: "session-start.js", timeout: 10 },
150
+ UserPromptSubmit: { file: "mugiwara-mode-tracker.js", timeout: 5 },
151
+ Stop: { file: "auto-savepoint.js", timeout: 20 },
152
+ SubagentStop: { file: "pipeline-guard.js", timeout: 15 },
153
+ PostToolUse: { file: "engagement-marker.js", timeout: 5, matcher: "Task|Skill" }
154
+ };
155
+ let settings = {};
156
+ if (existsSync(file)) {
157
+ try {
158
+ settings = JSON.parse(readFileSync(file, "utf8"));
159
+ } catch {
160
+ return { written: [], notes: [`${file} is not valid JSON — add the mugiwara hooks manually`] };
161
+ }
162
+ }
163
+ const hooks = settings.hooks ?? {};
164
+ let changed = false;
165
+ for (const [event, spec] of Object.entries(events)) {
166
+ const command = join(hooksDir, spec.file);
167
+ if (!existsSync(command))
168
+ continue;
169
+ const groups = Array.isArray(hooks[event]) ? hooks[event] : [];
170
+ const already = groups.some((g) => g.hooks?.some((h) => h.command?.includes(spec.file)));
171
+ if (already)
172
+ continue;
173
+ const group = { hooks: [{ type: "command", command: JSON.stringify(command), timeout: spec.timeout }] };
174
+ if (spec.matcher)
175
+ group.matcher = spec.matcher;
176
+ groups.push(group);
177
+ hooks[event] = groups;
178
+ changed = true;
179
+ }
180
+ if (!changed)
181
+ return { written: [], notes: [] };
182
+ settings.hooks = hooks;
183
+ writeFileSync(file, JSON.stringify(settings, null, 2) + `
184
+ `);
185
+ return { written: [], notes: [`hooks registered in ${file}`] };
186
+ }
187
+ function unwireSettings(root) {
188
+ const file = join(root, "settings.json");
189
+ if (!existsSync(file))
190
+ return { changed: [], notes: [] };
191
+ let settings;
192
+ try {
193
+ settings = JSON.parse(readFileSync(file, "utf8"));
194
+ } catch {
195
+ return { changed: [], notes: [`${file} is not valid JSON — left untouched`] };
196
+ }
197
+ const hooks = settings.hooks;
198
+ if (!hooks)
199
+ return { changed: [], notes: [] };
200
+ const ours = new Set(["session-start.js", "mugiwara-mode-tracker.js", "auto-savepoint.js", "pipeline-guard.js", "engagement-marker.js"]);
201
+ const isOurs = (h) => [...ours].some((f) => h.command?.includes(join("hooks", f)) || h.command?.includes(`hooks/${f}`));
202
+ let changed = false;
203
+ for (const [event, groups] of Object.entries(hooks)) {
204
+ if (!Array.isArray(groups))
205
+ continue;
206
+ const kept = groups.map((g) => ({ ...g, hooks: (g.hooks ?? []).filter((h) => !isOurs(h)) })).filter((g) => g.hooks.length > 0);
207
+ if (kept.length !== groups.length || kept.some((g, i) => g.hooks.length !== groups[i]?.hooks?.length)) {
208
+ changed = true;
209
+ }
210
+ if (kept.length)
211
+ hooks[event] = kept;
212
+ else {
213
+ delete hooks[event];
214
+ changed = true;
215
+ }
216
+ }
217
+ if (!changed)
218
+ return { changed: [], notes: [] };
219
+ if (!Object.keys(hooks).length)
220
+ delete settings.hooks;
221
+ writeFileSync(file, JSON.stringify(settings, null, 2) + `
222
+ `);
223
+ return { changed: [file], notes: [] };
224
+ }
122
225
  var target = {
123
226
  id: "claude",
124
227
  label: "Claude Code",
@@ -157,7 +260,7 @@ var target = {
157
260
  return { written: [], notes: [] };
158
261
  if (existsSync(HOOKS_SRC)) {
159
262
  for (const f of readdirSync(HOOKS_SRC)) {
160
- if (!f.endsWith(".ts"))
263
+ if (!/\.(ts|js|json)$/.test(f))
161
264
  continue;
162
265
  const dst = join(root, "hooks", f);
163
266
  if (!existsSync(dst)) {
@@ -167,6 +270,10 @@ var target = {
167
270
  written.push(dst);
168
271
  }
169
272
  }
273
+ written.push(...copyScripts(join(root, "scripts")));
274
+ const wired = wireSettings(root, join(root, "hooks"));
275
+ written.push(...wired.written);
276
+ notes.push(...wired.notes);
170
277
  }
171
278
  if (existsSync(COMMANDS_SRC)) {
172
279
  const dstDir = join(root, "commands");
@@ -184,6 +291,12 @@ var target = {
184
291
  }
185
292
  }
186
293
  return { written, notes };
294
+ },
295
+ postUninstall({ scope, projectDir, home, dryRun }) {
296
+ if (dryRun)
297
+ return { changed: [], notes: [] };
298
+ const root = scope === "global" ? join(home, ".claude") : join(projectDir, ".claude");
299
+ return unwireSettings(root);
187
300
  }
188
301
  };
189
302
 
@@ -318,7 +431,17 @@ var target3 = {
318
431
  transformSkill(data, body) {
319
432
  return {
320
433
  relPath: `${data.name}.instructions.md`,
321
- text: stringifyFrontmatter({ description: data.description, applyTo: "**/*" }, body)
434
+ text: stringifyFrontmatter({ description: data.description, applyTo: "**/*" }, `# ${data.name}
435
+
436
+ > ${data.description}
437
+
438
+ Full skill: read \`.mugiwara/refs/${data.name}/${data.name}.md\` when the crew invokes this role.`)
439
+ };
440
+ },
441
+ transformSkillFull(data, body) {
442
+ return {
443
+ relPath: `${data.name}.md`,
444
+ text: stringifyFrontmatter({ description: data.description }, body)
322
445
  };
323
446
  },
324
447
  transformAgent(data, body) {
@@ -333,7 +456,7 @@ var target3 = {
333
456
  };
334
457
 
335
458
  // src/targets/generic.ts
336
- import { existsSync as existsSync3, writeFileSync } from "node:fs";
459
+ import { existsSync as existsSync3, writeFileSync as writeFileSync2 } from "node:fs";
337
460
  import { join as join4 } from "node:path";
338
461
  function makeGeneric(opts) {
339
462
  const { id, label, rulesDir, bootstrapFile, bootstrapPointer, tier = bootstrapPointer ? 2 : 3 } = opts;
@@ -359,7 +482,7 @@ function makeGeneric(opts) {
359
482
 
360
483
  ## Skip when
361
484
 
362
- Full skill: \`${data.name}\` — read \`.mugiwara/refs/${data.name}.md\` when the crew invokes this role.`
485
+ Full skill: \`${data.name}\` — read \`.mugiwara/refs/${data.name}/${data.name}.md\` when the crew invokes this role.`
363
486
  };
364
487
  }
365
488
  return { relPath: `${data.name}.md`, text: `# ${data.name}
@@ -385,7 +508,7 @@ ${body}` };
385
508
 
386
509
  > ${data.description}
387
510
 
388
- Skills: ${data.skills ?? ""}. Read \`.mugiwara/refs/${data.name}.md\` when embodying this role.
511
+ Skills: ${data.skills ?? ""}. Read \`.mugiwara/refs/${data.name}/${data.name}.md\` when embodying this role.
389
512
 
390
513
  Only zoro-execution and brook-healing may modify source code.
391
514
  Return your output to luffy-orchestrator; do not choose the next step.`
@@ -410,8 +533,8 @@ Skills used: ${data.skills ?? ""}
410
533
 
411
534
  ${body}` };
412
535
  },
413
- refsDir({ projectDir }) {
414
- return join4(projectDir, ".mugiwara", "refs");
536
+ refsDir({ projectDir }, skillName) {
537
+ return join4(projectDir, ".mugiwara", "refs", skillName);
415
538
  },
416
539
  postInstall({ projectDir, dryRun }) {
417
540
  if (!bootstrapFile)
@@ -421,7 +544,7 @@ ${body}` };
421
544
  const file = join4(projectDir, bootstrapFile);
422
545
  if (!existsSync3(file)) {
423
546
  if (!dryRun)
424
- writeFileSync(file, `${bootstrapPointer}
547
+ writeFileSync2(file, `${bootstrapPointer}
425
548
  `);
426
549
  written.push(file);
427
550
  } else {
@@ -496,7 +619,7 @@ var targets = { claude: target, opencode: target2, copilot: target3, gemini: tar
496
619
  var TARGET_IDS = Object.keys(targets);
497
620
 
498
621
  // src/installer.ts
499
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync2, copyFileSync as copyFileSync3, rmSync, lstatSync } from "node:fs";
622
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync3, copyFileSync as copyFileSync3, rmSync, lstatSync } from "node:fs";
500
623
  import { dirname as dirname3, join as join5 } from "node:path";
501
624
  import { homedir } from "node:os";
502
625
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -560,7 +683,7 @@ function installTo(target10, opts) {
560
683
  }
561
684
  if (!dryRun) {
562
685
  mkdirSync3(dirname3(absPath), { recursive: true });
563
- writeFileSync2(absPath, text);
686
+ writeFileSync3(absPath, text);
564
687
  }
565
688
  result.written.push(absPath);
566
689
  };
@@ -609,6 +732,38 @@ function installTo(target10, opts) {
609
732
  result.written.push(...post.written);
610
733
  result.notes.push(...post.notes);
611
734
  }
735
+ if (scope === "project") {
736
+ const configPath = join5(projectDir, ".mugiwara", "config");
737
+ let configExists = false;
738
+ try {
739
+ configExists = lstatSync(configPath).isFile() || lstatSync(configPath).isSymbolicLink();
740
+ } catch {
741
+ configExists = false;
742
+ }
743
+ if (!configExists) {
744
+ const body = [
745
+ "mode=guided",
746
+ "branch=feature/{type}-{issue}-{slug}",
747
+ "commit=conventional",
748
+ "auto_commit=on",
749
+ "coverage_new=90",
750
+ "coverage_modified=80",
751
+ "review_depth=full",
752
+ "quality_depth=full",
753
+ "delegate_threshold=60",
754
+ "heal_max_cycles=3",
755
+ "verbosity=normal"
756
+ ].join(`
757
+ `) + `
758
+ `;
759
+ if (!dryRun) {
760
+ mkdirSync3(dirname3(configPath), { recursive: true });
761
+ writeFileSync3(configPath, body);
762
+ }
763
+ result.written.push(configPath);
764
+ result.notes.push(`default config written: ${configPath} (run \`mugiwara onboard\` to customise)`);
765
+ }
766
+ }
612
767
  return result;
613
768
  }
614
769
  function removeInstalled(manifest, { dryRun = false } = {}) {
@@ -676,7 +831,7 @@ function ensureProjectGitignore(projectDir, opts = {}) {
676
831
  ` : "";
677
832
  if (!dryRun) {
678
833
  mkdirSync3(dirname3(path), { recursive: true });
679
- writeFileSync2(path, existing + separator + GITIGNORE_BLOCK);
834
+ writeFileSync3(path, existing + separator + GITIGNORE_BLOCK);
680
835
  }
681
836
  return { appended: true, notes: [`.gitignore ${dryRun ? "would upgrade+append" : "upgraded"} mugiwara audit-trail block`] };
682
837
  }
@@ -723,12 +878,12 @@ function removeProjectGitignore(projectDir, { dryRun = false } = {}) {
723
878
  if (cleaned === current)
724
879
  return { removed: false, notes: [] };
725
880
  if (!dryRun)
726
- writeFileSync2(path, cleaned);
881
+ writeFileSync3(path, cleaned);
727
882
  return { removed: true, notes: [`.gitignore ${dryRun ? "would strip" : "stripped"} mugiwara block`] };
728
883
  }
729
884
 
730
885
  // src/manifest.ts
731
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
886
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
732
887
  import { dirname as dirname4, join as join6 } from "node:path";
733
888
  function manifestPath({ scope, projectDir, home }) {
734
889
  return scope === "global" ? join6(home, ".mugiwara", "manifest.json") : join6(projectDir, ".mugiwara", "manifest.json");
@@ -738,7 +893,7 @@ function readManifest(file) {
738
893
  }
739
894
  function writeManifest(file, data) {
740
895
  mkdirSync4(dirname4(file), { recursive: true });
741
- writeFileSync3(file, JSON.stringify(data, null, 2) + `
896
+ writeFileSync4(file, JSON.stringify(data, null, 2) + `
742
897
  `);
743
898
  }
744
899
 
@@ -912,6 +1067,417 @@ function archiveMission(projectDir, mission, opts = {}) {
912
1067
  return { report, removed, kept, index };
913
1068
  }
914
1069
 
1070
+ // src/onboard.ts
1071
+ import { existsSync as existsSync7, lstatSync as lstatSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
1072
+ import { join as join8 } from "node:path";
1073
+ var DEFAULT_ORDER = [
1074
+ "mode",
1075
+ "branch",
1076
+ "commit",
1077
+ "auto_commit",
1078
+ "coverage_new",
1079
+ "coverage_modified",
1080
+ "review_depth",
1081
+ "quality_depth",
1082
+ "delegate_threshold",
1083
+ "heal_max_cycles",
1084
+ "verbosity"
1085
+ ];
1086
+ var DEFAULTS = {
1087
+ auto_commit: "on",
1088
+ delegate_threshold: "60",
1089
+ heal_max_cycles: "3",
1090
+ verbosity: "normal"
1091
+ };
1092
+ var BRANCH_PRESETS = {
1093
+ 1: "feature/{type}-{issue}-{slug}",
1094
+ 2: "feature/{slug}",
1095
+ 3: "feat/{slug}"
1096
+ };
1097
+ var COMMIT_PRESETS = {
1098
+ 1: "conventional",
1099
+ 2: "gitmoji",
1100
+ 3: "plain"
1101
+ };
1102
+ function parseConfig(lines) {
1103
+ const map = new Map;
1104
+ for (const line of lines) {
1105
+ const t = line.trim();
1106
+ if (!t || t.startsWith("#"))
1107
+ continue;
1108
+ const eq = t.indexOf("=");
1109
+ if (eq === -1)
1110
+ continue;
1111
+ const k = t.slice(0, eq).trim();
1112
+ const v = t.slice(eq + 1).trim();
1113
+ if (!map.has(k))
1114
+ map.set(k, v);
1115
+ }
1116
+ return map;
1117
+ }
1118
+ function buildConfig(existing, updates) {
1119
+ const merged = new Map(existing);
1120
+ for (const [k, v] of Object.entries(updates))
1121
+ merged.set(k, v);
1122
+ const lines = [];
1123
+ for (const k of DEFAULT_ORDER) {
1124
+ const v = merged.get(k) ?? DEFAULTS[k];
1125
+ if (v === undefined)
1126
+ continue;
1127
+ lines.push(`${k}=${v}`);
1128
+ merged.delete(k);
1129
+ }
1130
+ for (const [k, v] of merged)
1131
+ lines.push(`${k}=${v}`);
1132
+ return lines.join(`
1133
+ `) + `
1134
+ `;
1135
+ }
1136
+ async function askFreeText(rl, prompt) {
1137
+ for (;; ) {
1138
+ const v = (await rl.question(prompt)).trim();
1139
+ if (v)
1140
+ return v;
1141
+ console.log(" Cannot be empty.");
1142
+ }
1143
+ }
1144
+ function assertNotSymlink2(file) {
1145
+ if (!existsSync7(file))
1146
+ return;
1147
+ try {
1148
+ if (lstatSync2(file).isSymbolicLink())
1149
+ throw new Error(`refusing to follow symlink: ${file}`);
1150
+ } catch (e) {
1151
+ if (e.code === "ENOENT")
1152
+ return;
1153
+ throw e;
1154
+ }
1155
+ }
1156
+ async function runOnboard(projectDir) {
1157
+ if (!process.stdin.isTTY) {
1158
+ throw new Error("Not a terminal. Run `mugiwara onboard` in a terminal, or edit .mugiwara/config directly.");
1159
+ }
1160
+ const mugiwaraDir = join8(projectDir, ".mugiwara");
1161
+ const configPath = join8(mugiwaraDir, "config");
1162
+ assertNotSymlink2(configPath);
1163
+ const existing = parseConfig(existsSync7(configPath) ? readFileSync6(configPath, "utf8").split(/\r?\n/) : []);
1164
+ const rl = createRl();
1165
+ try {
1166
+ console.log("");
1167
+ console.log("╔══════════════════════════════════════╗");
1168
+ console.log("║ Mugiwara Onboarding Wizard ║");
1169
+ console.log("╚══════════════════════════════════════╝");
1170
+ console.log("");
1171
+ if (existing.size > 0) {
1172
+ const redo = await confirm(rl, "Existing .mugiwara/config found. Re-onboard?");
1173
+ if (!redo) {
1174
+ console.log("Onboarding cancelled. Config preserved.");
1175
+ return;
1176
+ }
1177
+ console.log("");
1178
+ }
1179
+ const branchChoice = await choose(rl, "Q1 — Branch naming pattern:", [
1180
+ "Trunk-based — feature/{type}-{issue}-{slug}",
1181
+ "GitFlow — feature/{slug}",
1182
+ "GitHub Flow — feat/{slug}",
1183
+ "Custom — enter your own pattern"
1184
+ ]);
1185
+ let branch;
1186
+ if (branchChoice === 3) {
1187
+ branch = await askFreeText(rl, " Branch pattern (placeholders: {type} {issue} {slug}, e.g. {issue}-{slug}): ");
1188
+ } else {
1189
+ branch = BRANCH_PRESETS[branchChoice + 1];
1190
+ }
1191
+ const modeIdx = await choose(rl, "Q2 — Autonomy mode:", [
1192
+ "guided — ask before every wave transition",
1193
+ "semi — auto-advance through waves, pause on failures",
1194
+ "auto — full auto-pilot"
1195
+ ]);
1196
+ const mode = ["guided", "semi", "auto"][modeIdx];
1197
+ const reviewIdx = await choose(rl, "Q3 — Code review depth:", [
1198
+ "full — breaking-change map, five-axis review, <=3 cycles",
1199
+ "standard — five-axis review, 1 cycle",
1200
+ "quick — diff-only, no caller-map"
1201
+ ]);
1202
+ const reviewDepth = ["full", "standard", "quick"][reviewIdx];
1203
+ const qualityIdx = await choose(rl, "Q4 — Quality check depth:", [
1204
+ "full — format, lint, typecheck, test, build",
1205
+ "standard — lint, typecheck, test",
1206
+ "quick — test only"
1207
+ ]);
1208
+ const qualityDepth = ["full", "standard", "quick"][qualityIdx];
1209
+ const covIdx = await choose(rl, "Q5 — Test coverage threshold:", [
1210
+ "90/80 — new code 90%, modified 80%",
1211
+ "80/70 — new code 80%, modified 70%",
1212
+ "custom — enter your own values",
1213
+ "none — 0/0, no coverage enforcement"
1214
+ ]);
1215
+ let coverageNew = 90;
1216
+ let coverageModified = 80;
1217
+ if (covIdx === 1) {
1218
+ coverageNew = 80;
1219
+ coverageModified = 70;
1220
+ } else if (covIdx === 2) {
1221
+ coverageNew = Number.parseInt(await askFreeText(rl, " Coverage for new code (%): "), 10) || 0;
1222
+ coverageModified = Number.parseInt(await askFreeText(rl, " Coverage for modified code (%): "), 10) || 0;
1223
+ } else if (covIdx === 3) {
1224
+ coverageNew = 0;
1225
+ coverageModified = 0;
1226
+ }
1227
+ const commitChoice = await choose(rl, "Q6 — Commit style:", [
1228
+ "Conventional Commits — feat:, fix:, chore:, docs:",
1229
+ "Gitmoji — emoji-prefixed conventional",
1230
+ "Plain — no prefix, imperative sentence",
1231
+ "Custom template — enter your own (placeholders: {type} {issue} {title})"
1232
+ ]);
1233
+ let commit;
1234
+ if (commitChoice === 3) {
1235
+ commit = await askFreeText(rl, " Commit template (e.g. {issue}: {title}): ");
1236
+ } else {
1237
+ commit = COMMIT_PRESETS[commitChoice + 1];
1238
+ }
1239
+ if (!existsSync7(mugiwaraDir))
1240
+ mkdirSync6(mugiwaraDir, { recursive: true });
1241
+ const body = buildConfig(existing, {
1242
+ mode,
1243
+ branch,
1244
+ commit,
1245
+ coverage_new: String(coverageNew),
1246
+ coverage_modified: String(coverageModified),
1247
+ review_depth: reviewDepth,
1248
+ quality_depth: qualityDepth
1249
+ });
1250
+ writeFileSync5(configPath, `# .mugiwara/config — written by mugiwara onboard on ${new Date().toISOString().slice(0, 10)}
1251
+ ${body}`);
1252
+ console.log("╔══════════════════════════════════════╗");
1253
+ console.log("║ Onboarding Complete ║");
1254
+ console.log("╚══════════════════════════════════════╝");
1255
+ console.log("");
1256
+ console.log(` Branch pattern: ${branch}`);
1257
+ console.log(` Autonomy mode: ${mode}`);
1258
+ console.log(` Review depth: ${reviewDepth}`);
1259
+ console.log(` Quality depth: ${qualityDepth}`);
1260
+ console.log(` Coverage: ${coverageNew}/${coverageModified}`);
1261
+ console.log(` Commit style: ${commit}`);
1262
+ console.log("");
1263
+ console.log(` Config written: ${configPath}`);
1264
+ console.log("");
1265
+ } finally {
1266
+ rl.close();
1267
+ }
1268
+ }
1269
+
1270
+ // src/run.ts
1271
+ import { existsSync as existsSync8, readdirSync as readdirSync5 } from "node:fs";
1272
+ import { spawnSync } from "node:child_process";
1273
+ import { dirname as dirname5, join as join9, resolve } from "node:path";
1274
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
1275
+ var here3 = dirname5(fileURLToPath4(import.meta.url));
1276
+ var SCRIPTS_DIR = join9(here3, "..", "scripts");
1277
+ var RUNNABLE = ["savepoint.sh", "lane.sh", "evidence.sh", "mission-report.sh"];
1278
+ function findBash() {
1279
+ const explicit = process.env.MUGIWARA_BASH?.trim();
1280
+ if (explicit)
1281
+ return existsSync8(explicit) ? explicit : null;
1282
+ const candidates = process.platform === "win32" ? [
1283
+ "C:\\Program Files\\Git\\bin\\bash.exe",
1284
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
1285
+ join9(process.env.LOCALAPPDATA ?? "", "Programs", "Git", "bin", "bash.exe")
1286
+ ] : ["/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash", "/opt/homebrew/bin/bash"];
1287
+ for (const p of candidates) {
1288
+ if (p && existsSync8(p))
1289
+ return p;
1290
+ }
1291
+ const probe = spawnSync(process.platform === "win32" ? "where" : "which", ["bash"], {
1292
+ encoding: "utf8"
1293
+ });
1294
+ const first = probe.stdout?.split(/\r?\n/).find((l) => l.trim());
1295
+ return first?.trim() || null;
1296
+ }
1297
+ function noBashMessage() {
1298
+ const remedy = process.platform === "win32" ? "Install Git for Windows (which ships bash)" : "Install bash with your package manager (e.g. `apk add bash`, `apt install bash`)";
1299
+ return `no bash found. ${remedy}, or set MUGIWARA_BASH to a bash executable.`;
1300
+ }
1301
+ function runScript(name, args, projectDir) {
1302
+ if (!/^[a-z0-9-]+\.sh$/.test(name)) {
1303
+ throw new Error(`invalid script name "${name}" (expected one of: ${RUNNABLE.join(", ")})`);
1304
+ }
1305
+ const script = join9(SCRIPTS_DIR, name);
1306
+ if (!existsSync8(script)) {
1307
+ const have = existsSync8(SCRIPTS_DIR) ? readdirSync5(SCRIPTS_DIR).filter((f) => f.endsWith(".sh")) : [];
1308
+ throw new Error(`script not found: ${name}${have.length ? ` (available: ${have.join(", ")})` : ""}`);
1309
+ }
1310
+ const bash = findBash();
1311
+ if (!bash)
1312
+ throw new Error(noBashMessage());
1313
+ const r = spawnSync(bash, [script, ...args], {
1314
+ cwd: resolve(projectDir),
1315
+ stdio: "inherit",
1316
+ env: process.env
1317
+ });
1318
+ if (r.error)
1319
+ throw r.error;
1320
+ return r.status ?? 1;
1321
+ }
1322
+
1323
+ // src/continue.ts
1324
+ import { existsSync as existsSync9, readFileSync as readFileSync7, readdirSync as readdirSync6 } from "node:fs";
1325
+ import { execFileSync } from "node:child_process";
1326
+ import { join as join10 } from "node:path";
1327
+ var SAFE = /^[A-Za-z0-9._-]+$/;
1328
+ var isSafeKey = (s) => SAFE.test(s) && !/^\.+$/.test(s);
1329
+ var num = (v) => {
1330
+ const n = Number(v);
1331
+ return Number.isFinite(n) ? n : 0;
1332
+ };
1333
+ var text = (v) => typeof v === "string" ? v : "";
1334
+ function gitActor(cwd) {
1335
+ const env = (process.env.STATE_ACTOR ?? "").trim();
1336
+ if (env)
1337
+ return env;
1338
+ const author = (process.env.GIT_AUTHOR_NAME ?? "").trim();
1339
+ if (author)
1340
+ return author;
1341
+ const git = (args) => {
1342
+ try {
1343
+ return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
1344
+ } catch {
1345
+ return "";
1346
+ }
1347
+ };
1348
+ const name = git(["config", "user.name"]);
1349
+ const email = git(["config", "user.email"]);
1350
+ if (name && email)
1351
+ return `${name} <${email}>`;
1352
+ return name || process.env.USER || process.env.USERNAME || "";
1353
+ }
1354
+ function scan(projectDir, root, map) {
1355
+ const base = join10(projectDir, ".mugiwara", root);
1356
+ if (!existsSync9(base))
1357
+ return [];
1358
+ const out = [];
1359
+ const missions = readdirSync6(base, { withFileTypes: true }).filter((e) => e.isDirectory() && isSafeKey(e.name)).map((e) => e.name).sort();
1360
+ for (const mission of missions) {
1361
+ const dir = join10(base, mission);
1362
+ for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
1363
+ const stem = f.slice(0, -".json".length);
1364
+ const member = stem === "state" ? null : stem;
1365
+ if (member !== null && !isSafeKey(member))
1366
+ continue;
1367
+ try {
1368
+ const raw = JSON.parse(readFileSync7(join10(dir, f), "utf8"));
1369
+ if (text(raw.mission) !== mission)
1370
+ continue;
1371
+ out.push(map(raw, member));
1372
+ } catch {}
1373
+ }
1374
+ }
1375
+ return out;
1376
+ }
1377
+ function readContinue(projectDir) {
1378
+ return scan(projectDir, "continue", (r, member) => ({
1379
+ mission: text(r.mission),
1380
+ member,
1381
+ actor: text(r.actor),
1382
+ branch: text(r.branch),
1383
+ flow: num(r.flow ?? r.wave),
1384
+ mode: text(r.mode) || "guided",
1385
+ tasks_done: num(r.tasks_done),
1386
+ tasks_total: num(r.tasks_total),
1387
+ lane: text(r.lane) || "direct",
1388
+ next_action: text(r.next_action),
1389
+ next_session_prompt: text(r.next_session_prompt),
1390
+ updated_at: text(r.updated_at)
1391
+ }));
1392
+ }
1393
+ function readState(projectDir) {
1394
+ return scan(projectDir, "state", (r, member) => {
1395
+ const tasks = r.tasks ?? {};
1396
+ return {
1397
+ mission: text(r.mission),
1398
+ member,
1399
+ actor: text(r.actor),
1400
+ branch: text(r.branch),
1401
+ flow: num(r.flow ?? r.wave),
1402
+ mode: text(r.mode) || "guided",
1403
+ tasks_done: num(tasks.done),
1404
+ tasks_total: num(tasks.total),
1405
+ lane: text(r.lane) || "direct",
1406
+ lane_reason: text(r.lane_reason),
1407
+ lane_rose: r.lane_rose === true,
1408
+ lane_prev: text(r.lane_prev),
1409
+ lane_peak: text(r.lane_peak),
1410
+ next_action: "",
1411
+ next_session_prompt: "",
1412
+ updated_at: text(r.updated_at),
1413
+ blockers_open: num(r.blockers_open),
1414
+ heal_cycle: num(r.heal_cycle),
1415
+ heal_max_cycles: num(r.heal_max_cycles) || 3,
1416
+ heal_halt: r.heal_halt === true,
1417
+ delegate_threshold: num(r.delegate_threshold) || 60,
1418
+ delegate_due: r.delegate_due === true,
1419
+ tokens_est: num(r.tokens_est),
1420
+ budget: num(r.budget),
1421
+ budget_status: text(r.budget_status) || "ok",
1422
+ files_touched: num(r.files_touched),
1423
+ evidence: Array.isArray(r.evidence) ? r.evidence.map(text).filter(Boolean) : []
1424
+ };
1425
+ });
1426
+ }
1427
+ function resolveContinue(entries, mission, member) {
1428
+ if (!entries.length)
1429
+ return { kind: "none" };
1430
+ if (!mission) {
1431
+ const missions = [...new Set(entries.map((e) => e.mission))];
1432
+ if (missions.length === 1)
1433
+ return resolveContinue(entries, missions[0], member);
1434
+ return { kind: "missions", entries };
1435
+ }
1436
+ const inMission = entries.filter((e) => e.mission === mission);
1437
+ if (!inMission.length) {
1438
+ return { kind: "unknown-mission", mission, known: [...new Set(entries.map((e) => e.mission))] };
1439
+ }
1440
+ if (member) {
1441
+ const hit = inMission.find((e) => e.member === member);
1442
+ if (!hit) {
1443
+ return {
1444
+ kind: "unknown-member",
1445
+ mission,
1446
+ member,
1447
+ known: inMission.map((e) => e.member ?? "(solo)")
1448
+ };
1449
+ }
1450
+ return { kind: "resume", entry: hit };
1451
+ }
1452
+ const solo = inMission.find((e) => e.member === null);
1453
+ if (solo && inMission.length === 1)
1454
+ return { kind: "resume", entry: solo };
1455
+ return { kind: "members", mission, entries: inMission };
1456
+ }
1457
+ function pad(s, n) {
1458
+ return s.length >= n ? s : s + " ".repeat(n - s.length);
1459
+ }
1460
+ function formatTable(entries) {
1461
+ const rows = entries.map((e) => [
1462
+ e.mission,
1463
+ e.member ?? "—",
1464
+ String(e.flow),
1465
+ `${e.tasks_done}/${e.tasks_total}`,
1466
+ e.lane,
1467
+ e.mode
1468
+ ]);
1469
+ const head = ["MISSION", "MEMBER", "FLOW", "TASKS", "LANE", "MODE"];
1470
+ const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1471
+ const line = (cells) => " " + cells.map((c, i) => pad(c, widths[i])).join(" ").trimEnd();
1472
+ return [line(head), ...rows.map(line)].join(`
1473
+ `);
1474
+ }
1475
+ function formatResume(e) {
1476
+ const scope = e.member ? ` [${e.member}]` : "";
1477
+ const next = e.next_session_prompt || "(no next_session_prompt recorded)";
1478
+ return `Resumed: ${e.mission}${scope}, Flow ${e.flow}, ${e.tasks_done}/${e.tasks_total} tasks — next_action: ${e.next_action} — run: ${next}`;
1479
+ }
1480
+
915
1481
  // src/cli.ts
916
1482
  var str = (v) => typeof v === "string" ? v : undefined;
917
1483
  var flag = (v) => v === true;
@@ -936,12 +1502,26 @@ async function run(argv) {
936
1502
  return resetCmd(flags);
937
1503
  case "archive":
938
1504
  return archive(flags, _);
1505
+ case "onboard":
1506
+ return onboardCmd(flags);
1507
+ case "continue":
1508
+ return continueCmd(flags, _);
1509
+ case "status":
1510
+ return statusCmd(flags);
1511
+ case "run":
1512
+ return runCmd(flags, _);
1513
+ case "savepoint":
1514
+ return runCmd(flags, ["run", "savepoint.sh", ..._.slice(1)]);
1515
+ case "initiative":
1516
+ return initiativeCmd(flags, _);
1517
+ case "start":
1518
+ return startCmd(flags, _);
939
1519
  default:
940
1520
  throw new Error(`Unknown command: ${command}`);
941
1521
  }
942
1522
  }
943
1523
  function resetCmd(flags) {
944
- const projectDir = resolve(str(flags.project) ?? process.cwd());
1524
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
945
1525
  const force = flag(flags.force);
946
1526
  const result = resetMission(projectDir, flag(flags.keepLogs), force);
947
1527
  if (result.blocked) {
@@ -956,7 +1536,7 @@ function resetCmd(flags) {
956
1536
  console.log(`kept: ${result.kept.join(", ")}`);
957
1537
  }
958
1538
  function archive(flags, positionals) {
959
- const projectDir = resolve(str(flags.project) ?? process.cwd());
1539
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
960
1540
  const mission = positionals[1];
961
1541
  if (!mission) {
962
1542
  console.error("usage: mugiwara archive <mission> [--project <dir>] [--dry-run]");
@@ -972,6 +1552,12 @@ function archive(flags, positionals) {
972
1552
  if (result.index)
973
1553
  console.log(`index updated: ${result.index}`);
974
1554
  }
1555
+ async function onboardCmd(flags) {
1556
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1557
+ if (!existsSync10(projectDir))
1558
+ throw new Error(`Project dir not found: ${projectDir}`);
1559
+ await runOnboard(projectDir);
1560
+ }
975
1561
  async function resolveOptions(flags) {
976
1562
  const interactive = !flag(flags.yes);
977
1563
  if (interactive && !process.stdin.isTTY) {
@@ -986,8 +1572,8 @@ async function resolveOptions(flags) {
986
1572
  } else
987
1573
  scope = await choose(rl, "Install scope?", ["global (user-wide)", "project (this repo)"]) === 0 ? "global" : "project";
988
1574
  }
989
- const projectDir = resolve(str(flags.project) ?? process.cwd());
990
- if (scope === "project" && !existsSync7(projectDir))
1575
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1576
+ if (scope === "project" && !existsSync10(projectDir))
991
1577
  throw new Error(`Project dir not found: ${projectDir}`);
992
1578
  let targetIds = str(flags.target)?.split(",").map((s) => s.trim()) ?? null;
993
1579
  if (targetIds && targetIds.includes("all"))
@@ -1054,17 +1640,34 @@ Dry run — nothing written.`);
1054
1640
  OK mugiwara ${VERSION} installed (manifest: ${file})`);
1055
1641
  if (allNotes.length)
1056
1642
  console.log(`${allNotes.length} note(s) above may need attention.`);
1643
+ if (scope === "project") {
1644
+ const cfg = join11(projectDir, ".mugiwara", "config");
1645
+ let mode = "guided", enforce = "block";
1646
+ if (existsSync10(cfg)) {
1647
+ for (const line of readFileSync8(cfg, "utf8").split(/\r?\n/)) {
1648
+ const [k, v] = line.split("=").map((s) => s.trim());
1649
+ if (k === "mode")
1650
+ mode = v;
1651
+ if (k === "enforce")
1652
+ enforce = v;
1653
+ }
1654
+ }
1655
+ console.log(`
1656
+ Next: run \`mugiwara onboard\` to customise (mode=${mode}, enforce=${enforce} now).`);
1657
+ } else {
1658
+ console.log("\nNext: run `mugiwara onboard` in a project to write its .mugiwara/config.");
1659
+ }
1057
1660
  }
1058
1661
  async function uninstall(flags) {
1059
1662
  const scope = flag(flags.global) ? "global" : "project";
1060
- const projectDir = resolve(str(flags.project) ?? process.cwd());
1663
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1061
1664
  const home = homedir2();
1062
1665
  const file = manifestPath({ scope, projectDir, home });
1063
1666
  const manifest = readManifest(file);
1064
1667
  if (!manifest) {
1065
1668
  if (scope === "project") {
1066
1669
  const globalFile = manifestPath({ scope: "global", projectDir, home });
1067
- if (existsSync7(globalFile)) {
1670
+ if (existsSync10(globalFile)) {
1068
1671
  console.log(`No project manifest found, but a global install exists. Try:
1069
1672
  mugiwara uninstall --global`);
1070
1673
  return;
@@ -1084,6 +1687,16 @@ async function uninstall(flags) {
1084
1687
  }
1085
1688
  }
1086
1689
  const removed = removeInstalled(manifest, { dryRun: flag(flags.dryRun) });
1690
+ for (const id of manifest.targets) {
1691
+ const t = targets[id];
1692
+ if (!t?.postUninstall)
1693
+ continue;
1694
+ const post = t.postUninstall({ scope, projectDir, home, dryRun: flag(flags.dryRun) });
1695
+ for (const f of post.changed)
1696
+ console.log(` unwired mugiwara hooks from ${f}`);
1697
+ for (const n of post.notes)
1698
+ console.log(` note: ${n}`);
1699
+ }
1087
1700
  if (!flag(flags.dryRun)) {
1088
1701
  if (scope === "project") {
1089
1702
  const gi = removeProjectGitignore(projectDir);
@@ -1091,13 +1704,13 @@ async function uninstall(flags) {
1091
1704
  console.log(` note: ${n}`);
1092
1705
  }
1093
1706
  rmSync3(file);
1094
- const mugiDir = join8(scope === "global" ? home : projectDir, ".mugiwara");
1095
- if (existsSync7(mugiDir) && readdirSync5(mugiDir).length === 0) {
1707
+ const mugiDir = join11(scope === "global" ? home : projectDir, ".mugiwara");
1708
+ if (existsSync10(mugiDir) && readdirSync7(mugiDir).length === 0) {
1096
1709
  rmSync3(mugiDir, { recursive: true, force: true });
1097
1710
  }
1098
1711
  if (manifest.targets.includes("opencode")) {
1099
- const opencodeCache = join8(home, ".cache", "opencode", "packages", "@ionivetech");
1100
- if (existsSync7(opencodeCache)) {
1712
+ const opencodeCache = join11(home, ".cache", "opencode", "packages", "@ionivetech");
1713
+ if (existsSync10(opencodeCache)) {
1101
1714
  rmSync3(opencodeCache, { recursive: true, force: true });
1102
1715
  console.log(" cleared opencode npm cache (stale plugin versions)");
1103
1716
  }
@@ -1107,7 +1720,7 @@ async function uninstall(flags) {
1107
1720
  }
1108
1721
  function list(flags) {
1109
1722
  const home = homedir2();
1110
- const projectDir = resolve(str(flags.project) ?? process.cwd());
1723
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1111
1724
  let found = false;
1112
1725
  for (const [label, file] of [
1113
1726
  ["project", manifestPath({ scope: "project", projectDir, home })],
@@ -1118,7 +1731,7 @@ function list(flags) {
1118
1731
  continue;
1119
1732
  found = true;
1120
1733
  if (flag(flags.check)) {
1121
- const missing = m.files.filter((f) => !existsSync7(f));
1734
+ const missing = m.files.filter((f) => !existsSync10(f));
1122
1735
  console.log(`${label}: v${m.version} targets=${m.targets.join(",")} files=${m.files.length} missing=${missing.length} installed=${m.installedAt}`);
1123
1736
  } else {
1124
1737
  console.log(`${label}: v${m.version} targets=${m.targets.join(",")} files=${m.files.length} installed=${m.installedAt}`);
@@ -1127,6 +1740,155 @@ function list(flags) {
1127
1740
  if (!found)
1128
1741
  console.log("No mugiwara installation found.");
1129
1742
  }
1743
+ function continueCmd(flags, positionals) {
1744
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1745
+ const [mission, member] = positionals.slice(1);
1746
+ let entries = readContinue(projectDir);
1747
+ if (!flag(flags.all)) {
1748
+ const actor = gitActor(projectDir);
1749
+ const mine = entries.filter((e) => e.actor === actor);
1750
+ if (mine.length)
1751
+ entries = mine;
1752
+ }
1753
+ const r = resolveContinue(entries, mission, member);
1754
+ if (r.kind === "resume") {
1755
+ console.log(formatResume(r.entry));
1756
+ return;
1757
+ }
1758
+ if (r.kind === "none") {
1759
+ console.log("No mission in flight. Start one with Flow 0 triage (mugiwara-orchestration).");
1760
+ } else if (r.kind === "missions") {
1761
+ console.log(`${new Set(r.entries.map((e) => e.mission)).size} missions in flight:
1762
+ `);
1763
+ console.log(formatTable(r.entries));
1764
+ console.log(`
1765
+ Pick one: mugiwara continue <mission> [member]`);
1766
+ } else if (r.kind === "members") {
1767
+ console.log(`Mission "${r.mission}" has ${r.entries.length} members in flight:
1768
+ `);
1769
+ console.log(formatTable(r.entries));
1770
+ console.log(`
1771
+ Pick one: mugiwara continue ${r.mission} <member>`);
1772
+ } else if (r.kind === "unknown-mission") {
1773
+ console.error(`No in-flight mission "${r.mission}". Known: ${r.known.join(", ") || "(none)"}`);
1774
+ } else {
1775
+ console.error(`Mission "${r.mission}" has no member "${r.member}". Known: ${r.known.join(", ")}`);
1776
+ }
1777
+ process.exit(2);
1778
+ }
1779
+ function statusCmd(flags) {
1780
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1781
+ const states = readState(projectDir);
1782
+ if (!states.length) {
1783
+ console.log("No mission state on disk.");
1784
+ return;
1785
+ }
1786
+ const actor = flag(flags.all) ? null : gitActor(projectDir);
1787
+ const rows = actor ? states.filter((s) => s.actor === actor).length ? states.filter((s) => s.actor === actor) : states : states;
1788
+ for (const s of rows) {
1789
+ const scope = s.member ? ` [${s.member}]` : "";
1790
+ console.log(`${s.mission}${scope}`);
1791
+ console.log(` flow ${s.flow} · ${s.tasks_done}/${s.tasks_total} tasks · lane ${s.lane}${s.lane_rose ? " ⬆ ROSE" : ""}${s.lane_reason ? ` (${s.lane_reason})` : ""} · mode ${s.mode}`);
1792
+ console.log(` blockers ${s.blockers_open} · heal cycle ${s.heal_cycle}/${s.heal_max_cycles}${s.heal_halt ? " — HALT" : ""} · files touched ${s.files_touched}`);
1793
+ if (s.budget)
1794
+ console.log(` tokens ${s.tokens_est}/${s.budget} (${s.budget_status})${s.delegate_due ? " · delegate due" : ""}`);
1795
+ console.log(` branch ${s.branch} · updated ${s.updated_at}`);
1796
+ if (s.evidence.length)
1797
+ console.log(` evidence: ${s.evidence.join(", ")}`);
1798
+ }
1799
+ }
1800
+ function runCmd(flags, positionals) {
1801
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1802
+ const name = positionals[1];
1803
+ if (!name) {
1804
+ console.error(`usage: mugiwara run <script> [args...]
1805
+ scripts: ${RUNNABLE.join(", ")}`);
1806
+ process.exit(1);
1807
+ }
1808
+ const code = runScript(name, positionals.slice(2), projectDir);
1809
+ if (code !== 0)
1810
+ process.exit(code);
1811
+ }
1812
+ function initiativeCmd(flags, positionals) {
1813
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1814
+ const sub = positionals[1];
1815
+ if (!sub) {
1816
+ console.error("usage: mugiwara initiative <status|conflict-check|set-status> <plan-file> [--id <id> --status <s>]");
1817
+ process.exit(1);
1818
+ }
1819
+ const script = join11(import.meta.dirname, "..", "scripts", "initiative.ts");
1820
+ const bun = process.platform === "win32" ? "bun.exe" : "bun";
1821
+ const r = spawnSync2(bun, [script, ...positionals.slice(1)], {
1822
+ cwd: projectDir,
1823
+ stdio: "inherit",
1824
+ env: process.env
1825
+ });
1826
+ if (r.error)
1827
+ throw r.error;
1828
+ if (r.status)
1829
+ process.exit(r.status);
1830
+ }
1831
+ function startCmd(flags, positionals) {
1832
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1833
+ const mission = positionals[1];
1834
+ if (!mission) {
1835
+ console.error("usage: mugiwara start <mission> [member]");
1836
+ process.exit(1);
1837
+ }
1838
+ const plansDir = join11(projectDir, ".mugiwara", "plans");
1839
+ let planFile = "";
1840
+ if (existsSync10(plansDir)) {
1841
+ const bare = join11(plansDir, `${mission}.md`);
1842
+ if (existsSync10(bare))
1843
+ planFile = bare;
1844
+ else {
1845
+ const dated = readdirSync7(plansDir).filter((f) => f.endsWith(`-${mission}.md`)).sort().pop();
1846
+ if (dated)
1847
+ planFile = join11(plansDir, dated);
1848
+ }
1849
+ }
1850
+ if (!planFile) {
1851
+ console.error(`mugiwara start: no plan found for "${mission}" in ${plansDir}`);
1852
+ process.exit(1);
1853
+ }
1854
+ const plan = readFileSync8(planFile, "utf8");
1855
+ const subMatch = plan.match(/##\s*Sub-missions/i);
1856
+ let member = positionals[2] ?? "";
1857
+ if (subMatch && !member) {
1858
+ const actor = gitActor(projectDir);
1859
+ const name = actor.split("<")[0].trim();
1860
+ const rowRe = /^\|\s*([\w.-]+)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|/gm;
1861
+ let m;
1862
+ let found = "";
1863
+ while (m = rowRe.exec(plan)) {
1864
+ const assignee = m[3].trim();
1865
+ if (assignee === name || assignee === actor) {
1866
+ found = m[1].trim();
1867
+ break;
1868
+ }
1869
+ }
1870
+ if (!found) {
1871
+ console.error(`mugiwara start: no sub-mission assigned to "${name}" in ${planFile}. Pass a member explicitly: mugiwara start ${mission} <member>`);
1872
+ process.exit(1);
1873
+ }
1874
+ member = found;
1875
+ }
1876
+ const code = runScript("savepoint.sh", [mission, member, "0", "auto", "full"], projectDir);
1877
+ if (code !== 0)
1878
+ process.exit(code);
1879
+ if (subMatch && member) {
1880
+ const init = join11(import.meta.dirname, "..", "scripts", "initiative.ts");
1881
+ const bun = process.platform === "win32" ? "bun.exe" : "bun";
1882
+ const r = spawnSync2(bun, [init, "set-status", planFile, "--id", member, "--status", "in-progress"], {
1883
+ cwd: projectDir,
1884
+ stdio: "inherit",
1885
+ env: process.env
1886
+ });
1887
+ if (r.status)
1888
+ process.exit(r.status);
1889
+ }
1890
+ console.log(`mugiwara start: ${mission}${member ? ` [${member}]` : ""} begun at Flow 0. Resume anytime with: mugiwara continue ${mission}${member ? ` ${member}` : ""}`);
1891
+ }
1130
1892
  function help() {
1131
1893
  console.log(`mugiwara ${VERSION} — the Straw Hat crew for AI agents
1132
1894
 
@@ -1138,6 +1900,19 @@ Usage:
1138
1900
  mugiwara list --check health check: show installations + missing files
1139
1901
  mugiwara reset wipe mission state (spec/plans/results/review/issues[/logs])
1140
1902
  mugiwara archive <m> fold a closed mission's evidence into its report, then remove loose files
1903
+ mugiwara onboard run the zero-LLM onboarding wizard (writes .mugiwara/config)
1904
+ mugiwara continue list in-flight missions (exit 2 = pick one, nothing resumed)
1905
+ mugiwara continue <m> [member]
1906
+ print the exact resume point for that mission/member
1907
+ mugiwara status computed mission state: wave, tasks, lane, blockers, budget
1908
+ mugiwara run <script> [args...]
1909
+ run a bundled harness script here (${RUNNABLE.join(", ")})
1910
+ mugiwara savepoint <mission> [member] [wave] [mode]
1911
+ shorthand for: mugiwara run savepoint.sh ...
1912
+ mugiwara initiative <status|conflict-check|set-status> <plan> [args]
1913
+ team sub-mission coordination (status / conflict-check / set-status)
1914
+ mugiwara start <mission> [member]
1915
+ begin a mission from its plan (solo, or team sub-mission by git actor)
1141
1916
  mugiwara --help this help
1142
1917
  mugiwara --version print version
1143
1918
 
@@ -1149,9 +1924,10 @@ Flags:
1149
1924
  --force overwrite differing files (with backup)
1150
1925
  --dry-run print actions without writing
1151
1926
  --check with list: report missing files (health check)
1927
+ --all with continue/status: every actor, not just yours
1152
1928
  --keep-logs with reset: keep .mugiwara/logs (lessons ledger survives)`);
1153
1929
  }
1154
- var isMain = process.argv[1] !== undefined && fileURLToPath4(import.meta.url) === resolve(process.argv[1]);
1930
+ var isMain = process.argv[1] !== undefined && fileURLToPath5(import.meta.url) === resolve2(process.argv[1]);
1155
1931
  if (isMain) {
1156
1932
  run(process.argv.slice(2)).catch((err) => {
1157
1933
  console.error(`mugiwara: ${err.message}`);