@ionivetech/mugiwara 0.6.5 → 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 (110) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.codex-plugin/plugin.json +1 -2
  4. package/.cursor-plugin/plugin.json +1 -2
  5. package/.kimi-plugin/plugin.json +1 -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/plugins/mugiwara.mjs +1 -2
  16. package/AGENTS.md +1 -2
  17. package/README.md +33 -31
  18. package/content/agents/brook-healing.md +5 -8
  19. package/content/agents/chopper-checkpoint.md +8 -11
  20. package/content/agents/eval-runner.md +2 -5
  21. package/content/agents/franky-gates.md +4 -7
  22. package/content/agents/jinbe-security.md +4 -7
  23. package/content/agents/luffy-orchestrator.md +13 -13
  24. package/content/agents/memory-keeper.md +5 -8
  25. package/content/agents/nami-planner.md +4 -7
  26. package/content/agents/resume-coordinator.md +7 -10
  27. package/content/agents/robin-reviewer.md +6 -9
  28. package/content/agents/sanji-quality.md +7 -10
  29. package/content/agents/skeptic-verifier.md +5 -8
  30. package/content/agents/usopp-brainstorm.md +3 -6
  31. package/content/agents/zoro-execution.md +7 -10
  32. package/content/skills/mugiwara-backend/SKILL.md +2 -0
  33. package/content/skills/mugiwara-checkpoint/SKILL.md +13 -11
  34. package/content/skills/mugiwara-checkpoint/references/ledger-format.md +4 -4
  35. package/content/skills/mugiwara-context-budget/SKILL.md +5 -3
  36. package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
  37. package/content/skills/mugiwara-execution/SKILL.md +20 -20
  38. package/content/skills/mugiwara-execution/references/dispatch.md +2 -2
  39. package/content/skills/mugiwara-execution/references/resume-batching.md +1 -1
  40. package/content/skills/mugiwara-frontend/SKILL.md +2 -4
  41. package/content/skills/mugiwara-gates/SKILL.md +8 -7
  42. package/content/skills/mugiwara-git/SKILL.md +7 -2
  43. package/content/skills/mugiwara-healing/SKILL.md +6 -4
  44. package/content/skills/mugiwara-healing/references/workers.md +2 -2
  45. package/content/skills/mugiwara-orchestration/SKILL.md +26 -24
  46. package/content/skills/mugiwara-orchestration/references/check-ins.md +16 -15
  47. package/content/skills/mugiwara-orchestration/references/closure.md +5 -5
  48. package/content/skills/mugiwara-orchestration/references/output-contract.md +9 -9
  49. package/content/skills/mugiwara-orchestration/references/triage-escalation.md +22 -22
  50. package/content/skills/mugiwara-planning/SKILL.md +3 -1
  51. package/content/skills/mugiwara-planning/references/plan-template.md +2 -2
  52. package/content/skills/mugiwara-pr/SKILL.md +6 -6
  53. package/content/skills/mugiwara-pr/references/verdict-format.md +3 -3
  54. package/content/skills/mugiwara-quality/SKILL.md +7 -7
  55. package/content/skills/mugiwara-resume/SKILL.md +18 -20
  56. package/content/skills/mugiwara-review/SKILL.md +5 -1
  57. package/content/skills/mugiwara-review/references/five-axis-worksheet.md +1 -1
  58. package/content/skills/mugiwara-root-cause/SKILL.md +2 -0
  59. package/content/skills/mugiwara-security/SKILL.md +5 -1
  60. package/content/skills/mugiwara-ship/SKILL.md +1 -1
  61. package/content/skills/mugiwara-ship/references/cleanup.md +1 -1
  62. package/content/skills/mugiwara-testcases/SKILL.md +5 -3
  63. package/content/skills/mugiwara-workflow/SKILL.md +27 -27
  64. package/content/skills/mugiwara-workflow/references/workspace-layout.md +15 -15
  65. package/content/skills/using-mugiwara/SKILL.md +5 -5
  66. package/dist/mugiwara.js +808 -33
  67. package/gemini-extension.json +1 -1
  68. package/hooks/auto-savepoint.js +106 -0
  69. package/hooks/auto-savepoint.ts +102 -0
  70. package/hooks/engagement-marker.js +59 -0
  71. package/hooks/engagement-marker.ts +97 -0
  72. package/hooks/hooks.json +41 -2
  73. package/hooks/mugiwara-mode-tracker.js +79 -0
  74. package/hooks/pipeline-guard.js +182 -0
  75. package/hooks/pipeline-guard.ts +267 -0
  76. package/hooks/session-start.js +106 -0
  77. package/hooks/session-start.ts +44 -32
  78. package/package.json +7 -3
  79. package/plugin.json +1 -1
  80. package/references/agent-protocol.md +15 -0
  81. package/references/complexity.md +54 -0
  82. package/references/definition-of-done.md +1 -1
  83. package/references/multi-actor.md +5 -5
  84. package/references/skill-versioning.md +1 -1
  85. package/references/token-budget.md +30 -8
  86. package/references/wave-banners.md +16 -16
  87. package/scripts/build-hooks.ts +56 -0
  88. package/scripts/conformance.ts +17 -0
  89. package/scripts/coverage-gate.ts +179 -0
  90. package/scripts/install.ps1 +5 -4
  91. package/scripts/install.sh +4 -4
  92. package/scripts/lane.sh +6 -2
  93. package/scripts/lib/patterns.sh +84 -0
  94. package/scripts/mission-report.sh +12 -11
  95. package/scripts/savepoint.sh +141 -37
  96. package/scripts/verify-install.ts +154 -44
  97. package/src/args.ts +1 -1
  98. package/src/cli.ts +217 -0
  99. package/src/continue.ts +243 -0
  100. package/src/installer.ts +39 -0
  101. package/src/onboard.ts +207 -0
  102. package/src/run.ts +82 -0
  103. package/src/targets/claude.ts +140 -2
  104. package/src/targets/copilot.ts +20 -1
  105. package/src/targets/generic.ts +9 -4
  106. package/src/targets/opencode.ts +0 -1
  107. package/content/agents/onboarding-guide.md +0 -124
  108. package/content/skills/mugiwara-security/references/owasp-mapping.md +0 -30
  109. package/scripts/onboard.ts +0 -266
  110. 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
 
@@ -208,8 +321,7 @@ var CREW = {
208
321
  "skeptic-verifier": { color: "#64748b", temperature: 0.1, steps: 12 },
209
322
  "eval-runner": { color: "#14b8a6", temperature: 0.2, steps: 15 },
210
323
  "resume-coordinator": { color: "#d97706", temperature: 0.2, steps: 10 },
211
- "memory-keeper": { color: "#d946ef", temperature: 0.2, steps: 8 },
212
- "onboarding-guide": { color: "#0ea5e9", temperature: 0.3, steps: 15 }
324
+ "memory-keeper": { color: "#d946ef", temperature: 0.2, steps: 8 }
213
325
  };
214
326
  function readBannerColors() {
215
327
  try {
@@ -319,7 +431,17 @@ var target3 = {
319
431
  transformSkill(data, body) {
320
432
  return {
321
433
  relPath: `${data.name}.instructions.md`,
322
- 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)
323
445
  };
324
446
  },
325
447
  transformAgent(data, body) {
@@ -334,7 +456,7 @@ var target3 = {
334
456
  };
335
457
 
336
458
  // src/targets/generic.ts
337
- import { existsSync as existsSync3, writeFileSync } from "node:fs";
459
+ import { existsSync as existsSync3, writeFileSync as writeFileSync2 } from "node:fs";
338
460
  import { join as join4 } from "node:path";
339
461
  function makeGeneric(opts) {
340
462
  const { id, label, rulesDir, bootstrapFile, bootstrapPointer, tier = bootstrapPointer ? 2 : 3 } = opts;
@@ -360,7 +482,7 @@ function makeGeneric(opts) {
360
482
 
361
483
  ## Skip when
362
484
 
363
- 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.`
364
486
  };
365
487
  }
366
488
  return { relPath: `${data.name}.md`, text: `# ${data.name}
@@ -386,7 +508,7 @@ ${body}` };
386
508
 
387
509
  > ${data.description}
388
510
 
389
- 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.
390
512
 
391
513
  Only zoro-execution and brook-healing may modify source code.
392
514
  Return your output to luffy-orchestrator; do not choose the next step.`
@@ -411,8 +533,8 @@ Skills used: ${data.skills ?? ""}
411
533
 
412
534
  ${body}` };
413
535
  },
414
- refsDir({ projectDir }) {
415
- return join4(projectDir, ".mugiwara", "refs");
536
+ refsDir({ projectDir }, skillName) {
537
+ return join4(projectDir, ".mugiwara", "refs", skillName);
416
538
  },
417
539
  postInstall({ projectDir, dryRun }) {
418
540
  if (!bootstrapFile)
@@ -422,7 +544,7 @@ ${body}` };
422
544
  const file = join4(projectDir, bootstrapFile);
423
545
  if (!existsSync3(file)) {
424
546
  if (!dryRun)
425
- writeFileSync(file, `${bootstrapPointer}
547
+ writeFileSync2(file, `${bootstrapPointer}
426
548
  `);
427
549
  written.push(file);
428
550
  } else {
@@ -497,7 +619,7 @@ var targets = { claude: target, opencode: target2, copilot: target3, gemini: tar
497
619
  var TARGET_IDS = Object.keys(targets);
498
620
 
499
621
  // src/installer.ts
500
- 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";
501
623
  import { dirname as dirname3, join as join5 } from "node:path";
502
624
  import { homedir } from "node:os";
503
625
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -561,7 +683,7 @@ function installTo(target10, opts) {
561
683
  }
562
684
  if (!dryRun) {
563
685
  mkdirSync3(dirname3(absPath), { recursive: true });
564
- writeFileSync2(absPath, text);
686
+ writeFileSync3(absPath, text);
565
687
  }
566
688
  result.written.push(absPath);
567
689
  };
@@ -610,6 +732,38 @@ function installTo(target10, opts) {
610
732
  result.written.push(...post.written);
611
733
  result.notes.push(...post.notes);
612
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
+ }
613
767
  return result;
614
768
  }
615
769
  function removeInstalled(manifest, { dryRun = false } = {}) {
@@ -677,7 +831,7 @@ function ensureProjectGitignore(projectDir, opts = {}) {
677
831
  ` : "";
678
832
  if (!dryRun) {
679
833
  mkdirSync3(dirname3(path), { recursive: true });
680
- writeFileSync2(path, existing + separator + GITIGNORE_BLOCK);
834
+ writeFileSync3(path, existing + separator + GITIGNORE_BLOCK);
681
835
  }
682
836
  return { appended: true, notes: [`.gitignore ${dryRun ? "would upgrade+append" : "upgraded"} mugiwara audit-trail block`] };
683
837
  }
@@ -724,12 +878,12 @@ function removeProjectGitignore(projectDir, { dryRun = false } = {}) {
724
878
  if (cleaned === current)
725
879
  return { removed: false, notes: [] };
726
880
  if (!dryRun)
727
- writeFileSync2(path, cleaned);
881
+ writeFileSync3(path, cleaned);
728
882
  return { removed: true, notes: [`.gitignore ${dryRun ? "would strip" : "stripped"} mugiwara block`] };
729
883
  }
730
884
 
731
885
  // src/manifest.ts
732
- 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";
733
887
  import { dirname as dirname4, join as join6 } from "node:path";
734
888
  function manifestPath({ scope, projectDir, home }) {
735
889
  return scope === "global" ? join6(home, ".mugiwara", "manifest.json") : join6(projectDir, ".mugiwara", "manifest.json");
@@ -739,7 +893,7 @@ function readManifest(file) {
739
893
  }
740
894
  function writeManifest(file, data) {
741
895
  mkdirSync4(dirname4(file), { recursive: true });
742
- writeFileSync3(file, JSON.stringify(data, null, 2) + `
896
+ writeFileSync4(file, JSON.stringify(data, null, 2) + `
743
897
  `);
744
898
  }
745
899
 
@@ -913,6 +1067,417 @@ function archiveMission(projectDir, mission, opts = {}) {
913
1067
  return { report, removed, kept, index };
914
1068
  }
915
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
+
916
1481
  // src/cli.ts
917
1482
  var str = (v) => typeof v === "string" ? v : undefined;
918
1483
  var flag = (v) => v === true;
@@ -937,12 +1502,26 @@ async function run(argv) {
937
1502
  return resetCmd(flags);
938
1503
  case "archive":
939
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, _);
940
1519
  default:
941
1520
  throw new Error(`Unknown command: ${command}`);
942
1521
  }
943
1522
  }
944
1523
  function resetCmd(flags) {
945
- const projectDir = resolve(str(flags.project) ?? process.cwd());
1524
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
946
1525
  const force = flag(flags.force);
947
1526
  const result = resetMission(projectDir, flag(flags.keepLogs), force);
948
1527
  if (result.blocked) {
@@ -957,7 +1536,7 @@ function resetCmd(flags) {
957
1536
  console.log(`kept: ${result.kept.join(", ")}`);
958
1537
  }
959
1538
  function archive(flags, positionals) {
960
- const projectDir = resolve(str(flags.project) ?? process.cwd());
1539
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
961
1540
  const mission = positionals[1];
962
1541
  if (!mission) {
963
1542
  console.error("usage: mugiwara archive <mission> [--project <dir>] [--dry-run]");
@@ -973,6 +1552,12 @@ function archive(flags, positionals) {
973
1552
  if (result.index)
974
1553
  console.log(`index updated: ${result.index}`);
975
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
+ }
976
1561
  async function resolveOptions(flags) {
977
1562
  const interactive = !flag(flags.yes);
978
1563
  if (interactive && !process.stdin.isTTY) {
@@ -987,8 +1572,8 @@ async function resolveOptions(flags) {
987
1572
  } else
988
1573
  scope = await choose(rl, "Install scope?", ["global (user-wide)", "project (this repo)"]) === 0 ? "global" : "project";
989
1574
  }
990
- const projectDir = resolve(str(flags.project) ?? process.cwd());
991
- if (scope === "project" && !existsSync7(projectDir))
1575
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1576
+ if (scope === "project" && !existsSync10(projectDir))
992
1577
  throw new Error(`Project dir not found: ${projectDir}`);
993
1578
  let targetIds = str(flags.target)?.split(",").map((s) => s.trim()) ?? null;
994
1579
  if (targetIds && targetIds.includes("all"))
@@ -1055,17 +1640,34 @@ Dry run — nothing written.`);
1055
1640
  OK mugiwara ${VERSION} installed (manifest: ${file})`);
1056
1641
  if (allNotes.length)
1057
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
+ }
1058
1660
  }
1059
1661
  async function uninstall(flags) {
1060
1662
  const scope = flag(flags.global) ? "global" : "project";
1061
- const projectDir = resolve(str(flags.project) ?? process.cwd());
1663
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1062
1664
  const home = homedir2();
1063
1665
  const file = manifestPath({ scope, projectDir, home });
1064
1666
  const manifest = readManifest(file);
1065
1667
  if (!manifest) {
1066
1668
  if (scope === "project") {
1067
1669
  const globalFile = manifestPath({ scope: "global", projectDir, home });
1068
- if (existsSync7(globalFile)) {
1670
+ if (existsSync10(globalFile)) {
1069
1671
  console.log(`No project manifest found, but a global install exists. Try:
1070
1672
  mugiwara uninstall --global`);
1071
1673
  return;
@@ -1085,6 +1687,16 @@ async function uninstall(flags) {
1085
1687
  }
1086
1688
  }
1087
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
+ }
1088
1700
  if (!flag(flags.dryRun)) {
1089
1701
  if (scope === "project") {
1090
1702
  const gi = removeProjectGitignore(projectDir);
@@ -1092,13 +1704,13 @@ async function uninstall(flags) {
1092
1704
  console.log(` note: ${n}`);
1093
1705
  }
1094
1706
  rmSync3(file);
1095
- const mugiDir = join8(scope === "global" ? home : projectDir, ".mugiwara");
1096
- if (existsSync7(mugiDir) && readdirSync5(mugiDir).length === 0) {
1707
+ const mugiDir = join11(scope === "global" ? home : projectDir, ".mugiwara");
1708
+ if (existsSync10(mugiDir) && readdirSync7(mugiDir).length === 0) {
1097
1709
  rmSync3(mugiDir, { recursive: true, force: true });
1098
1710
  }
1099
1711
  if (manifest.targets.includes("opencode")) {
1100
- const opencodeCache = join8(home, ".cache", "opencode", "packages", "@ionivetech");
1101
- if (existsSync7(opencodeCache)) {
1712
+ const opencodeCache = join11(home, ".cache", "opencode", "packages", "@ionivetech");
1713
+ if (existsSync10(opencodeCache)) {
1102
1714
  rmSync3(opencodeCache, { recursive: true, force: true });
1103
1715
  console.log(" cleared opencode npm cache (stale plugin versions)");
1104
1716
  }
@@ -1108,7 +1720,7 @@ async function uninstall(flags) {
1108
1720
  }
1109
1721
  function list(flags) {
1110
1722
  const home = homedir2();
1111
- const projectDir = resolve(str(flags.project) ?? process.cwd());
1723
+ const projectDir = resolve2(str(flags.project) ?? process.cwd());
1112
1724
  let found = false;
1113
1725
  for (const [label, file] of [
1114
1726
  ["project", manifestPath({ scope: "project", projectDir, home })],
@@ -1119,7 +1731,7 @@ function list(flags) {
1119
1731
  continue;
1120
1732
  found = true;
1121
1733
  if (flag(flags.check)) {
1122
- const missing = m.files.filter((f) => !existsSync7(f));
1734
+ const missing = m.files.filter((f) => !existsSync10(f));
1123
1735
  console.log(`${label}: v${m.version} targets=${m.targets.join(",")} files=${m.files.length} missing=${missing.length} installed=${m.installedAt}`);
1124
1736
  } else {
1125
1737
  console.log(`${label}: v${m.version} targets=${m.targets.join(",")} files=${m.files.length} installed=${m.installedAt}`);
@@ -1128,6 +1740,155 @@ function list(flags) {
1128
1740
  if (!found)
1129
1741
  console.log("No mugiwara installation found.");
1130
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
+ }
1131
1892
  function help() {
1132
1893
  console.log(`mugiwara ${VERSION} — the Straw Hat crew for AI agents
1133
1894
 
@@ -1139,6 +1900,19 @@ Usage:
1139
1900
  mugiwara list --check health check: show installations + missing files
1140
1901
  mugiwara reset wipe mission state (spec/plans/results/review/issues[/logs])
1141
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)
1142
1916
  mugiwara --help this help
1143
1917
  mugiwara --version print version
1144
1918
 
@@ -1150,9 +1924,10 @@ Flags:
1150
1924
  --force overwrite differing files (with backup)
1151
1925
  --dry-run print actions without writing
1152
1926
  --check with list: report missing files (health check)
1927
+ --all with continue/status: every actor, not just yours
1153
1928
  --keep-logs with reset: keep .mugiwara/logs (lessons ledger survives)`);
1154
1929
  }
1155
- 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]);
1156
1931
  if (isMain) {
1157
1932
  run(process.argv.slice(2)).catch((err) => {
1158
1933
  console.error(`mugiwara: ${err.message}`);