@ionivetech/mugiwara 0.9.0 → 0.9.1

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 (46) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/.opencode/mugiwara-helpers.mjs +1 -1
  7. package/.opencode/plugins/mugiwara.mjs +173 -1
  8. package/README.md +4 -4
  9. package/content/agents/luffy-orchestrator.md +15 -1
  10. package/content/agents/zoro-execution.md +1 -1
  11. package/content/skills/mugiwara-checkpoint/SKILL.md +1 -0
  12. package/content/skills/mugiwara-execution/SKILL.md +5 -5
  13. package/content/skills/mugiwara-gates/SKILL.md +1 -0
  14. package/content/skills/mugiwara-healing/SKILL.md +1 -0
  15. package/content/skills/mugiwara-orchestration/SKILL.md +7 -3
  16. package/content/skills/mugiwara-orchestration/references/check-ins.md +4 -5
  17. package/content/skills/mugiwara-orchestration/references/output-contract.md +2 -2
  18. package/content/skills/mugiwara-planning/SKILL.md +1 -1
  19. package/content/skills/mugiwara-planning/references/sub-missions.md +2 -2
  20. package/content/skills/mugiwara-quality/SKILL.md +1 -0
  21. package/content/skills/mugiwara-review/SKILL.md +2 -0
  22. package/content/skills/mugiwara-security/SKILL.md +2 -2
  23. package/content/skills/mugiwara-ship/SKILL.md +12 -0
  24. package/content/skills/mugiwara-workflow/SKILL.md +2 -2
  25. package/dist/mugiwara.js +235 -82
  26. package/gemini-extension.json +1 -1
  27. package/hooks/engagement-marker.js +9 -1
  28. package/hooks/engagement-marker.ts +9 -1
  29. package/hooks/hooks.json +12 -0
  30. package/hooks/pipeline-guard.js +137 -3
  31. package/hooks/pipeline-guard.ts +161 -3
  32. package/hooks/pretool-guard.js +84 -0
  33. package/hooks/pretool-guard.ts +60 -0
  34. package/package.json +1 -1
  35. package/plugin.json +1 -1
  36. package/references/wave-banners.md +22 -27
  37. package/scripts/build-hooks.ts +1 -1
  38. package/scripts/gate-selftest.ts +342 -0
  39. package/scripts/savepoint.sh +16 -4
  40. package/scripts/validate-content.ts +190 -15
  41. package/scripts/write-metrics.ts +25 -1
  42. package/src/cli.ts +15 -0
  43. package/src/config.ts +1 -1
  44. package/src/guards.ts +40 -0
  45. package/src/initiative.ts +174 -0
  46. package/src/targets/claude.ts +1 -0
package/dist/mugiwara.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { existsSync as existsSync16, readdirSync as readdirSync10, readFileSync as readFileSync15, rmSync as rmSync3, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, renameSync as renameSync2 } from "node:fs";
4
+ import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync16, rmSync as rmSync3, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, renameSync as renameSync2 } from "node:fs";
5
5
  import { execFileSync as execFileSync6 } from "node:child_process";
6
6
  import { homedir as homedir4 } from "node:os";
7
7
  import { dirname as dirname6, join as join21, resolve as resolve2 } from "node:path";
@@ -152,6 +152,7 @@ function wireSettings(root, hooksDir) {
152
152
  const events = {
153
153
  SessionStart: { file: "session-start.js", timeout: 10 },
154
154
  UserPromptSubmit: { file: "mugiwara-mode-tracker.js", timeout: 5 },
155
+ PreToolUse: { file: "pretool-guard.js", timeout: 10, matcher: "Bash" },
155
156
  Stop: { file: "auto-savepoint.js", timeout: 20 },
156
157
  SubagentStop: { file: "pipeline-guard.js", timeout: 15 },
157
158
  PostToolUse: { file: "engagement-marker.js", timeout: 5, matcher: "Task|Skill" }
@@ -647,7 +648,7 @@ var DEFAULT_CONFIG = [
647
648
  "# -- Git --------------------------------------------------",
648
649
  "branch=feature/{type}-{issue}-{slug}",
649
650
  "commit=conventional",
650
- "auto_commit=on # on | off — off hands you an uncommitted tree in guided/semi",
651
+ "auto_commit=off # on | off — off hands you an uncommitted tree in guided/semi",
651
652
  "",
652
653
  "# -- Gates ------------------------------------------------",
653
654
  "coverage_new=85",
@@ -779,11 +780,11 @@ function collectRefs(skillDir) {
779
780
  return [];
780
781
  return readdirSync3(refsDir, { recursive: true }).map((f) => String(f)).filter((f) => f.endsWith(".md")).map((rel) => ({ relPath: rel, text: readFileSync4(join6(refsDir, rel), "utf8") }));
781
782
  }
782
- function installTo(target10, opts) {
783
+ function installTo(target, opts) {
783
784
  const { scope, projectDir, dryRun = false, force = false } = opts;
784
785
  const home = opts.home ?? homedir2();
785
786
  const { skills, agents, sharedRefs } = collectContent();
786
- const dirs = target10.paths({ scope, projectDir, home });
787
+ const dirs = target.paths({ scope, projectDir, home });
787
788
  const backupRoot = join6(scope === "global" ? home : projectDir, ".mugiwara");
788
789
  const result = { written: [], skipped: [], backedUp: [], notes: [] };
789
790
  const writeOne = (absPath, text, mode) => {
@@ -804,7 +805,7 @@ function installTo(target10, opts) {
804
805
  }
805
806
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
806
807
  const fileName = absPath.replace(/[^a-zA-Z0-9]+/g, "_");
807
- const backupDir = join6(backupRoot, "backup", `${ts}-${target10.id}`);
808
+ const backupDir = join6(backupRoot, "backup", `${ts}-${target.id}`);
808
809
  const backupFile = join6(backupDir, fileName);
809
810
  if (!dryRun) {
810
811
  mkdirSync4(backupDir, { recursive: true });
@@ -824,38 +825,38 @@ function installTo(target10, opts) {
824
825
  result.written.push(absPath);
825
826
  };
826
827
  for (const s of skills) {
827
- const out = target10.transformSkill(s.data, s.body);
828
+ const out = target.transformSkill(s.data, s.body);
828
829
  if (out) {
829
830
  let text = out.text;
830
- if (target10.refPointerPrefix !== undefined && target10.refPointerPrefix !== "") {
831
- text = text.replace(/`_shared\/references\//g, "`" + target10.refPointerPrefix + "_shared/references/");
831
+ if (target.refPointerPrefix !== undefined && target.refPointerPrefix !== "") {
832
+ text = text.replace(/`_shared\/references\//g, "`" + target.refPointerPrefix + "_shared/references/");
832
833
  }
833
834
  writeOne(join6(dirs.skillsDir, out.relPath), text);
834
835
  }
835
- if (target10.transformSkillFull) {
836
- const full = target10.transformSkillFull(s.data, s.body);
837
- if (full && target10.refsDir) {
836
+ if (target.transformSkillFull) {
837
+ const full = target.transformSkillFull(s.data, s.body);
838
+ if (full && target.refsDir) {
838
839
  let text = full.text;
839
- if (target10.refPointerPrefix !== undefined && target10.refPointerPrefix !== "") {
840
- text = text.replace(/`_shared\/references\//g, "`" + target10.refPointerPrefix + "_shared/references/");
840
+ if (target.refPointerPrefix !== undefined && target.refPointerPrefix !== "") {
841
+ text = text.replace(/`_shared\/references\//g, "`" + target.refPointerPrefix + "_shared/references/");
841
842
  }
842
- writeOne(join6(target10.refsDir({ scope, projectDir, home }, s.name), full.relPath), text);
843
+ writeOne(join6(target.refsDir({ scope, projectDir, home }, s.name), full.relPath), text);
843
844
  }
844
845
  }
845
- if (s.refs.length && target10.refsDir) {
846
- const refsRoot = target10.refsDir({ scope, projectDir, home }, s.name);
846
+ if (s.refs.length && target.refsDir) {
847
+ const refsRoot = target.refsDir({ scope, projectDir, home }, s.name);
847
848
  for (const r of s.refs)
848
849
  writeOne(join6(refsRoot, r.relPath), r.text);
849
850
  }
850
851
  }
851
852
  for (const a of agents) {
852
- const out = target10.transformAgent({ ...a.data, ...a.internal ? { "internal-agent": "true" } : {} }, a.body);
853
+ const out = target.transformAgent({ ...a.data, ...a.internal ? { "internal-agent": "true" } : {} }, a.body);
853
854
  if (out)
854
855
  writeOne(join6(dirs.agentsDir, out.relPath), out.text);
855
- if (target10.transformAgentFull) {
856
- const full = target10.transformAgentFull(a.data, a.body);
857
- if (full && target10.refsDir)
858
- writeOne(join6(target10.refsDir({ scope, projectDir, home }, a.name), full.relPath), full.text);
856
+ if (target.transformAgentFull) {
857
+ const full = target.transformAgentFull(a.data, a.body);
858
+ if (full && target.refsDir)
859
+ writeOne(join6(target.refsDir({ scope, projectDir, home }, a.name), full.relPath), full.text);
859
860
  }
860
861
  }
861
862
  if (sharedRefs.length) {
@@ -875,8 +876,8 @@ function installTo(target10, opts) {
875
876
  writeOne(dest, text, 493);
876
877
  }
877
878
  }
878
- if (target10.postInstall) {
879
- const post = target10.postInstall({ scope, projectDir, home, dryRun, files: result.written });
879
+ if (target.postInstall) {
880
+ const post = target.postInstall({ scope, projectDir, home, dryRun, files: result.written });
880
881
  result.written.push(...post.written);
881
882
  result.notes.push(...post.notes);
882
883
  }
@@ -1062,10 +1063,10 @@ function parsePolicyYaml(text) {
1062
1063
  stack.pop();
1063
1064
  const top = stack[stack.length - 1];
1064
1065
  if (top.pending && !line.startsWith("- ") && indent > top.pending.indent && !Array.isArray(top.obj[top.pending.key])) {
1065
- const { key: key2, indent: pIndent } = top.pending;
1066
+ const { key, indent: pIndent } = top.pending;
1066
1067
  delete top.pending;
1067
1068
  const created = {};
1068
- top.obj[key2] = created;
1069
+ top.obj[key] = created;
1069
1070
  stack.push({ keyIndent: pIndent, obj: created });
1070
1071
  return process2(i);
1071
1072
  }
@@ -1617,15 +1618,15 @@ function checkTrail(missionDir, projectRoot) {
1617
1618
  } catch {
1618
1619
  continue;
1619
1620
  }
1620
- for (const target10 of linkedPaths(body)) {
1621
- if (isAbsolute(target10))
1621
+ for (const target of linkedPaths(body)) {
1622
+ if (isAbsolute(target))
1622
1623
  continue;
1623
- const fromMission = join9(missionDir, target10);
1624
- const fromRoot = join9(projectRoot, target10);
1624
+ const fromMission = join9(missionDir, target);
1625
+ const fromRoot = join9(projectRoot, target);
1625
1626
  if (!existsSync8(fromMission) && !existsSync8(fromRoot)) {
1626
1627
  issues.push({
1627
1628
  kind: "dangling-path",
1628
- detail: `${relative(projectRoot, f)} links "${target10}" — no such file (mission dir or repo root)`
1629
+ detail: `${relative(projectRoot, f)} links "${target}" — no such file (mission dir or repo root)`
1629
1630
  });
1630
1631
  }
1631
1632
  }
@@ -1852,18 +1853,18 @@ function attachGitNote(projectDir, branch, note, baseSha) {
1852
1853
  console.warn(`attachGitNote: range ${shas.length} >200, falling back to head-only`);
1853
1854
  shas = [];
1854
1855
  }
1855
- let targets2 = shas;
1856
- if (!targets2.length) {
1856
+ let targets = shas;
1857
+ if (!targets.length) {
1857
1858
  try {
1858
- targets2 = [git2(projectDir, ["rev-parse", "--verify", branch])];
1859
+ targets = [git2(projectDir, ["rev-parse", "--verify", branch])];
1859
1860
  } catch {
1860
- targets2 = [git2(projectDir, ["rev-parse", "HEAD"])];
1861
+ targets = [git2(projectDir, ["rev-parse", "HEAD"])];
1861
1862
  }
1862
1863
  }
1863
- for (const sha of targets2) {
1864
+ for (const sha of targets) {
1864
1865
  git2(projectDir, ["notes", "--ref=mugiwara", "add", "-f", "-m", note, sha]);
1865
1866
  }
1866
- return { sha: targets2[0], count: targets2.length };
1867
+ return { sha: targets[0], count: targets.length };
1867
1868
  } catch {
1868
1869
  return null;
1869
1870
  }
@@ -3150,9 +3151,9 @@ Provider total: ${reportedTotal.toLocaleString()} tokens (provider-reported).
3150
3151
  } catch {}
3151
3152
  if (shouldCompress(budget, chars)) {
3152
3153
  try {
3153
- const flowsDir2 = join18(dir, "flows");
3154
+ const flowsDir = join18(dir, "flows");
3154
3155
  const wavesDir = join18(dir, "waves");
3155
- const targetDir = existsSync13(flowsDir2) ? flowsDir2 : existsSync13(wavesDir) ? wavesDir : null;
3156
+ const targetDir = existsSync13(flowsDir) ? flowsDir : existsSync13(wavesDir) ? wavesDir : null;
3156
3157
  if (targetDir && existsSync13(targetDir)) {
3157
3158
  const flowFiles = readdirSync7(targetDir).filter((f) => f.endsWith(".md"));
3158
3159
  if (flowFiles.length) {
@@ -3339,7 +3340,7 @@ ${costSection}
3339
3340
  rmSync2(join18(dir, PR_VERDICT_SRC), { force: true });
3340
3341
  removed.push(join18("missions", mission, PR_VERDICT_SRC));
3341
3342
  }
3342
- for (const f of files.filter((f2) => f2.endsWith(".json")))
3343
+ for (const f of files.filter((f) => f.endsWith(".json")))
3343
3344
  rmSync2(join18(dir, f), { force: true });
3344
3345
  if (existsSync13(artDir) && readdirSync7(artDir).length === 0)
3345
3346
  rmSync2(artDir, { recursive: true, force: true });
@@ -3502,15 +3503,15 @@ function gitActor(cwd) {
3502
3503
  const author = (process.env.GIT_AUTHOR_NAME ?? "").trim();
3503
3504
  if (author)
3504
3505
  return author;
3505
- const git3 = (args) => {
3506
+ const git = (args) => {
3506
3507
  try {
3507
3508
  return execFileSync5("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
3508
3509
  } catch {
3509
3510
  return "";
3510
3511
  }
3511
3512
  };
3512
- const name = git3(["config", "user.name"]);
3513
- const email = git3(["config", "user.email"]);
3513
+ const name = git(["config", "user.name"]);
3514
+ const email = git(["config", "user.email"]);
3514
3515
  if (name && email)
3515
3516
  return `${name} <${email}>`;
3516
3517
  return name || process.env.USER || process.env.USERNAME || "";
@@ -3665,6 +3666,144 @@ function formatResume(e) {
3665
3666
  return `Resumed: ${e.mission}${scope}, Flow ${e.flow}, ${e.tasks_done}/${e.tasks_total} tasks — next_action: ${e.next_action} — run: ${next}`;
3666
3667
  }
3667
3668
 
3669
+ // src/initiative.ts
3670
+ import { existsSync as existsSync16, readFileSync as readFileSync15 } from "node:fs";
3671
+ var SUB_MISSIONS_HEADER = "| ID | Name | Assignee | Branch | Status | Depends On | Touched Files |";
3672
+ function splitTouchedFiles(cell) {
3673
+ return cell.split(/[,\s]+/).map((s) => s.trim().replace(/,+$/, "")).filter(Boolean);
3674
+ }
3675
+ function splitRow(line) {
3676
+ let t = line.trim();
3677
+ if (t.startsWith("|"))
3678
+ t = t.slice(1);
3679
+ if (t.endsWith("|"))
3680
+ t = t.slice(0, -1);
3681
+ return t.split("|").map((c) => c.trim());
3682
+ }
3683
+ function parseSubMissions(planText) {
3684
+ const lines = planText.split(`
3685
+ `);
3686
+ const sectionIdx = lines.findIndex((l) => /^##\s+sub-missions\s*$/i.test(l.trim()));
3687
+ if (sectionIdx < 0)
3688
+ return { hasSection: false, rows: [] };
3689
+ const endIdx = lines.findIndex((l, i) => i > sectionIdx && /^##\s+\S/.test(l.trim()));
3690
+ const body = lines.slice(sectionIdx + 1, endIdx < 0 ? undefined : endIdx);
3691
+ const isTableLine = (l) => /^\s*\|.*\|\s*$/.test(l);
3692
+ const isSeparator = (l) => /^\s*\|?[\s:|-]+\|?[\s:|.-]*$/.test(l) && /-/.test(l);
3693
+ const table = body.filter((l) => isTableLine(l));
3694
+ if (!table.length)
3695
+ return { hasSection: true, rows: [] };
3696
+ const header = splitRow(table[0]).map((c) => c.toLowerCase());
3697
+ const idIdx = header.findIndex((c) => c === "id");
3698
+ const nameIdx = header.findIndex((c) => c === "name");
3699
+ if (idIdx < 0 || nameIdx < 0)
3700
+ return { hasSection: true, rows: [] };
3701
+ const col = (name, fallback) => {
3702
+ const i = header.findIndex((c) => c === name);
3703
+ return i < 0 ? fallback : i;
3704
+ };
3705
+ const touchedIdx = header.findIndex((c) => /touch/.test(c));
3706
+ const dataStart = table.length > 1 && isSeparator(table[1]) ? 2 : 1;
3707
+ const rows = [];
3708
+ for (const line of table.slice(dataStart)) {
3709
+ if (isSeparator(line))
3710
+ continue;
3711
+ const cells = splitRow(line);
3712
+ const id = cells[idIdx] ?? "";
3713
+ if (!id)
3714
+ continue;
3715
+ rows.push({
3716
+ id,
3717
+ name: cells[nameIdx] ?? "",
3718
+ assignee: cells[col("assignee", 2)] ?? "",
3719
+ branch: cells[col("branch", 3)] ?? "",
3720
+ status: cells[col("status", 4)] ?? "",
3721
+ dependsOn: cells[col("depends on", 5)] ?? "",
3722
+ touchedFiles: touchedIdx < 0 ? [] : splitTouchedFiles(cells[touchedIdx] ?? "")
3723
+ });
3724
+ }
3725
+ return { hasSection: true, rows };
3726
+ }
3727
+ var DONE = /(\[x\]|done|complete|merged|closed)/i;
3728
+ function blockedRows(rows) {
3729
+ const statusOf = new Map(rows.map((r) => [r.id.toLowerCase(), r.status]));
3730
+ const out = [];
3731
+ for (const r of rows) {
3732
+ const dep = r.dependsOn.trim();
3733
+ if (!dep || dep === "-")
3734
+ continue;
3735
+ for (const part of dep.split(/[,\s]+/).filter(Boolean)) {
3736
+ const st = statusOf.get(part.toLowerCase());
3737
+ if (st !== undefined && !DONE.test(st)) {
3738
+ out.push({ id: r.id, blockedBy: part });
3739
+ break;
3740
+ }
3741
+ if (st === undefined && !DONE.test(dep)) {
3742
+ out.push({ id: r.id, blockedBy: part });
3743
+ break;
3744
+ }
3745
+ }
3746
+ }
3747
+ return out;
3748
+ }
3749
+ function findConflicts(rows) {
3750
+ const owners = new Map;
3751
+ for (const r of rows) {
3752
+ for (const f of r.touchedFiles) {
3753
+ const list = owners.get(f) ?? [];
3754
+ if (!list.includes(r.id))
3755
+ list.push(r.id);
3756
+ owners.set(f, list);
3757
+ }
3758
+ }
3759
+ return [...owners.entries()].filter(([, ids]) => ids.length > 1).map(([file, ids]) => ({ file, ids }));
3760
+ }
3761
+ function runInitiative(sub, planPath) {
3762
+ if (!sub || sub !== "status" && sub !== "conflict-check") {
3763
+ return { code: 1, output: `usage: mugiwara initiative <status|conflict-check> <plan>
3764
+ ` };
3765
+ }
3766
+ if (!planPath) {
3767
+ return { code: 1, output: `usage: mugiwara initiative ${sub} <plan>
3768
+ ` };
3769
+ }
3770
+ if (!existsSync16(planPath)) {
3771
+ return { code: 1, output: `mugiwara: plan not found: ${planPath}
3772
+ ` };
3773
+ }
3774
+ const { hasSection, rows } = parseSubMissions(readFileSync15(planPath, "utf8"));
3775
+ if (!hasSection) {
3776
+ return { code: 0, output: `solo mission (no ## Sub-missions section)
3777
+ ` };
3778
+ }
3779
+ if (!rows.length) {
3780
+ return {
3781
+ code: 1,
3782
+ output: `mugiwara: ## Sub-missions section present but no rows parsed — expected header:
3783
+ ${SUB_MISSIONS_HEADER}
3784
+ `
3785
+ };
3786
+ }
3787
+ if (sub === "status") {
3788
+ const blocked = new Map(blockedRows(rows).map((b) => [b.id, b.blockedBy]));
3789
+ const lines = ["id | name | assignee | branch | status | blocked-by"];
3790
+ for (const r of rows) {
3791
+ lines.push(`${r.id} | ${r.name} | ${r.assignee} | ${r.branch} | ${r.status} | ${blocked.get(r.id) ?? "-"}`);
3792
+ }
3793
+ return { code: 0, output: lines.join(`
3794
+ `) + `
3795
+ ` };
3796
+ }
3797
+ const conflicts = findConflicts(rows);
3798
+ if (!conflicts.length)
3799
+ return { code: 0, output: `no conflicts: no file is touched by two sub-missions
3800
+ ` };
3801
+ const lines = conflicts.map((c) => `conflict: ${c.file} touched by ${c.ids.join(", ")}`);
3802
+ return { code: 1, output: lines.join(`
3803
+ `) + `
3804
+ ` };
3805
+ }
3806
+
3668
3807
  // src/cli.ts
3669
3808
  var str = (v) => typeof v === "string" ? v : undefined;
3670
3809
  var flag = (v) => v === true;
@@ -3738,6 +3877,8 @@ async function run(argv) {
3738
3877
  return migrateCmd(flags, _);
3739
3878
  case "lesson":
3740
3879
  return lessonCmd(flags, _);
3880
+ case "initiative":
3881
+ return initiativeCmd(flags, _);
3741
3882
  default:
3742
3883
  throw new Error(`Unknown command: ${command}`);
3743
3884
  }
@@ -3780,7 +3921,7 @@ function cleanCmd(flags) {
3780
3921
  const projectDir = resolveProjectDir(str(flags.project));
3781
3922
  const dryRun = flag(flags.dryRun);
3782
3923
  const root = join21(projectDir, ".mugiwara", "missions");
3783
- if (!existsSync16(root)) {
3924
+ if (!existsSync17(root)) {
3784
3925
  console.log("nothing to clean (.mugiwara/missions/ does not exist).");
3785
3926
  return;
3786
3927
  }
@@ -3801,7 +3942,7 @@ function cleanCmd(flags) {
3801
3942
  return false;
3802
3943
  for (const f of stateFiles(m)) {
3803
3944
  try {
3804
- const ts = Date.parse(JSON.parse(readFileSync15(join21(root, m, f), "utf8")).updated_at ?? "") || 0;
3945
+ const ts = Date.parse(JSON.parse(readFileSync16(join21(root, m, f), "utf8")).updated_at ?? "") || 0;
3805
3946
  if (ts === 0 || ts >= beforeMs)
3806
3947
  return false;
3807
3948
  } catch {
@@ -3811,7 +3952,7 @@ function cleanCmd(flags) {
3811
3952
  return true;
3812
3953
  };
3813
3954
  if (!flag(flags.all)) {
3814
- candidates = candidates.filter((m) => existsSync16(join21(root, m, "report.md")) && !hasLiveState(m) || staleBefore(m));
3955
+ candidates = candidates.filter((m) => existsSync17(join21(root, m, "report.md")) && !hasLiveState(m) || staleBefore(m));
3815
3956
  } else if (!flag(flags.force)) {
3816
3957
  const live = candidates.filter((m) => hasLiveState(m) && !staleBefore(m));
3817
3958
  if (live.length) {
@@ -3845,7 +3986,7 @@ async function resolveOptions(flags) {
3845
3986
  scope = await choose(rl, "Install scope?", ["global (user-wide)", "project (this repo)"]) === 0 ? "global" : "project";
3846
3987
  }
3847
3988
  const projectDir = resolveProjectDir(str(flags.project));
3848
- if (scope === "project" && !existsSync16(projectDir))
3989
+ if (scope === "project" && !existsSync17(projectDir))
3849
3990
  throw new Error(`Project dir not found: ${projectDir}`);
3850
3991
  let targetIds = str(flags.target)?.split(",").map((s) => s.trim()) ?? null;
3851
3992
  if (targetIds && targetIds.includes("all"))
@@ -3858,7 +3999,13 @@ async function resolveOptions(flags) {
3858
3999
  targetIds = idx.includes(0) ? [...TARGET_IDS] : idx.map((i) => TARGET_IDS[i - 1]);
3859
4000
  }
3860
4001
  }
4002
+ const MARKETPLACE = new Set(["cursor", "kimi", "pi"]);
3861
4003
  for (const id of targetIds) {
4004
+ if (MARKETPLACE.has(id)) {
4005
+ console.error(`mugiwara: ${id} installs through its marketplace manifest, not --target.`);
4006
+ console.error(" See docs/reference/harness-matrix.md — marketplace row.");
4007
+ process.exit(1);
4008
+ }
3862
4009
  if (!targets[id])
3863
4010
  throw new Error(`Unknown target: ${id} (valid: ${TARGET_IDS.join(", ")}, all)`);
3864
4011
  }
@@ -3926,7 +4073,7 @@ async function uninstall(flags) {
3926
4073
  if (!manifest) {
3927
4074
  if (scope === "project") {
3928
4075
  const globalFile = manifestPath({ scope: "global", projectDir, home });
3929
- if (existsSync16(globalFile)) {
4076
+ if (existsSync17(globalFile)) {
3930
4077
  console.log(`No project manifest found, but a global install exists. Try:
3931
4078
  mugiwara uninstall --global`);
3932
4079
  return;
@@ -3964,12 +4111,12 @@ async function uninstall(flags) {
3964
4111
  }
3965
4112
  rmSync3(file);
3966
4113
  const mugiDir = join21(scope === "global" ? home : projectDir, ".mugiwara");
3967
- if (existsSync16(mugiDir) && readdirSync10(mugiDir).length === 0) {
4114
+ if (existsSync17(mugiDir) && readdirSync10(mugiDir).length === 0) {
3968
4115
  rmSync3(mugiDir, { recursive: true, force: true });
3969
4116
  }
3970
4117
  if (manifest.targets.includes("opencode")) {
3971
4118
  const opencodeCache = join21(home, ".cache", "opencode", "packages", "@ionivetech");
3972
- if (existsSync16(opencodeCache)) {
4119
+ if (existsSync17(opencodeCache)) {
3973
4120
  rmSync3(opencodeCache, { recursive: true, force: true });
3974
4121
  console.log(" cleared opencode npm cache (stale plugin versions)");
3975
4122
  }
@@ -4006,7 +4153,7 @@ function list(flags) {
4006
4153
  continue;
4007
4154
  found = true;
4008
4155
  if (flag(flags.check)) {
4009
- const missing = m.files.filter((f) => !existsSync16(f));
4156
+ const missing = m.files.filter((f) => !existsSync17(f));
4010
4157
  console.log(`${label}: v${m.version} targets=${m.targets.join(",")} files=${m.files.length} missing=${missing.length} installed=${m.installedAt}`);
4011
4158
  } else {
4012
4159
  console.log(`${label}: v${m.version} targets=${m.targets.join(",")} files=${m.files.length} installed=${m.installedAt}`);
@@ -4024,9 +4171,9 @@ function continueCmd(flags, positionals) {
4024
4171
  if (mission) {
4025
4172
  readState(projectDir);
4026
4173
  const badState = unreadableStateFiles();
4027
- const target10 = member ? `${mission}/${member}.json` : `${mission}/state.json`;
4028
- if (badState.includes(target10)) {
4029
- console.error(`✗ mission "${mission}"${member ? ` member "${member}"` : ""} has unreadable state: ${target10}`);
4174
+ const target = member ? `${mission}/${member}.json` : `${mission}/state.json`;
4175
+ if (badState.includes(target)) {
4176
+ console.error(`✗ mission "${mission}"${member ? ` member "${member}"` : ""} has unreadable state: ${target}`);
4030
4177
  process.exit(1);
4031
4178
  }
4032
4179
  }
@@ -4097,10 +4244,10 @@ function statusCmd(flags) {
4097
4244
  function costCmd(flags, positionals) {
4098
4245
  const projectDir = resolveProjectDir(str(flags.project));
4099
4246
  const mission = str(flags.mission) ?? positionals[1] ?? (() => {
4100
- const states2 = readState(projectDir);
4101
- if (states2.length === 1)
4102
- return states2[0].mission;
4103
- if (states2.length > 1) {
4247
+ const states = readState(projectDir);
4248
+ if (states.length === 1)
4249
+ return states[0].mission;
4250
+ if (states.length > 1) {
4104
4251
  console.error("multiple missions in flight — specify --mission <id>");
4105
4252
  process.exit(1);
4106
4253
  }
@@ -4111,7 +4258,7 @@ function costCmd(flags, positionals) {
4111
4258
  process.exit(1);
4112
4259
  }
4113
4260
  const missionDir = join21(projectDir, ".mugiwara", "missions", mission);
4114
- if (!existsSync16(missionDir)) {
4261
+ if (!existsSync17(missionDir)) {
4115
4262
  console.error(`No cost ledger found for mission "${mission}"`);
4116
4263
  process.exit(1);
4117
4264
  }
@@ -4170,7 +4317,7 @@ function blameCmd(flags, positionals) {
4170
4317
  function stalenessLine(projectDir, baseSha) {
4171
4318
  if (!baseSha || baseSha === "unknown")
4172
4319
  return null;
4173
- const git3 = (args) => {
4320
+ const git = (args) => {
4174
4321
  try {
4175
4322
  return execFileSync6("git", args, { cwd: projectDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
4176
4323
  } catch {
@@ -4179,14 +4326,14 @@ function stalenessLine(projectDir, baseSha) {
4179
4326
  };
4180
4327
  let main = "";
4181
4328
  for (const ref of ["main", "master"]) {
4182
- main = git3(["rev-parse", "--verify", ref]);
4329
+ main = git(["rev-parse", "--verify", ref]);
4183
4330
  if (main)
4184
4331
  break;
4185
4332
  }
4186
4333
  if (!main)
4187
4334
  return null;
4188
4335
  try {
4189
- const behind = Number(git3(["rev-list", "--count", `${baseSha}..${main}`])) || 0;
4336
+ const behind = Number(git(["rev-list", "--count", `${baseSha}..${main}`])) || 0;
4190
4337
  return behind > 0 ? `⚠ stale base: main is ${behind} commit(s) ahead of this mission's base ${baseSha.slice(0, 7)} — rebase check before continuing` : null;
4191
4338
  } catch {
4192
4339
  return null;
@@ -4247,20 +4394,20 @@ written: ${out}`);
4247
4394
  }
4248
4395
  function lessonCmd(flags, positionals) {
4249
4396
  const projectDir = resolveProjectDir(str(flags.project));
4250
- const text2 = positionals.slice(1).join(" ").trim();
4251
- if (!text2) {
4397
+ const text = positionals.slice(1).join(" ").trim();
4398
+ if (!text) {
4252
4399
  console.error('usage: mugiwara lesson "<text>" [--project <dir>]');
4253
4400
  process.exit(1);
4254
4401
  }
4255
4402
  const file = join21(projectDir, ".mugiwara", "lessons.md");
4256
4403
  const date = new Date().toISOString().slice(0, 10);
4257
- const sanitized = text2.replace(/\|/g, "/").replace(/\r?\n/g, " ").trim();
4404
+ const sanitized = text.replace(/\|/g, "/").replace(/\r?\n/g, " ").trim();
4258
4405
  const line = `| ${date} | manual | general | ${sanitized} |`;
4259
4406
  const header = `| Date | Mission | Area | Lesson |
4260
4407
  |---|---|---|---|`;
4261
4408
  let existing = "";
4262
4409
  try {
4263
- existing = readFileSync15(file, "utf8");
4410
+ existing = readFileSync16(file, "utf8");
4264
4411
  } catch {}
4265
4412
  if (!existing) {
4266
4413
  mkdirSync10(join21(projectDir, ".mugiwara"), { recursive: true });
@@ -4276,6 +4423,12 @@ function lessonCmd(flags, positionals) {
4276
4423
  }
4277
4424
  console.log(`lesson appended: ${line}`);
4278
4425
  }
4426
+ function initiativeCmd(_flags, positionals) {
4427
+ const r = runInitiative(positionals[1], positionals[2]);
4428
+ process.stdout.write(r.output);
4429
+ if (r.code !== 0)
4430
+ process.exit(r.code);
4431
+ }
4279
4432
  function migrateCmd(flags, positionals = []) {
4280
4433
  const projectDir = resolveProjectDir(str(flags.project));
4281
4434
  const dryRun = flag(flags.dryRun);
@@ -4294,7 +4447,7 @@ function migrateCmd(flags, positionals = []) {
4294
4447
  const missionsRootInner = join21(projectDir, ".mugiwara", "missions");
4295
4448
  let mission = str(flags.mission) ?? (positionals[1] ? String(positionals[1]) : null);
4296
4449
  const inferMission = () => {
4297
- if (!existsSync16(missionsRootInner))
4450
+ if (!existsSync17(missionsRootInner))
4298
4451
  return null;
4299
4452
  const all = readdirSync10(missionsRootInner, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
4300
4453
  if (mission && all.includes(mission))
@@ -4302,7 +4455,7 @@ function migrateCmd(flags, positionals = []) {
4302
4455
  if (mission)
4303
4456
  return mission;
4304
4457
  if (toTeam) {
4305
- const candidates = all.filter((m) => existsSync16(join21(missionsRootInner, m, "state.json")));
4458
+ const candidates = all.filter((m) => existsSync17(join21(missionsRootInner, m, "state.json")));
4306
4459
  if (candidates.length === 1)
4307
4460
  return candidates[0];
4308
4461
  if (candidates.length === 0) {
@@ -4312,7 +4465,7 @@ function migrateCmd(flags, positionals = []) {
4312
4465
  console.error(`multiple solo missions: ${candidates.join(", ")} — specify --mission <id>`);
4313
4466
  process.exit(1);
4314
4467
  } else {
4315
- const candidates = all.filter((m) => existsSync16(join21(missionsRootInner, m, `${member}.json`)));
4468
+ const candidates = all.filter((m) => existsSync17(join21(missionsRootInner, m, `${member}.json`)));
4316
4469
  if (candidates.length === 1)
4317
4470
  return candidates[0];
4318
4471
  if (candidates.length === 0) {
@@ -4335,16 +4488,16 @@ function migrateCmd(flags, positionals = []) {
4335
4488
  const srcContinue = join21(dir, "continue.json");
4336
4489
  const destState = join21(dir, `${member}.json`);
4337
4490
  const destContinue = join21(dir, `continue-${member}.json`);
4338
- if (!existsSync16(srcState)) {
4491
+ if (!existsSync17(srcState)) {
4339
4492
  console.error(`mission "${targetMission}" has no state.json — already team or not found`);
4340
4493
  process.exit(1);
4341
4494
  }
4342
- if (existsSync16(destState)) {
4495
+ if (existsSync17(destState)) {
4343
4496
  console.error(`destination ${destState} already exists`);
4344
4497
  process.exit(1);
4345
4498
  }
4346
4499
  const toMove = [{ src: srcState, dest: destState }];
4347
- if (existsSync16(srcContinue))
4500
+ if (existsSync17(srcContinue))
4348
4501
  toMove.push({ src: srcContinue, dest: destContinue });
4349
4502
  for (const m of toMove) {
4350
4503
  console.log(`${dryRun ? "would migrate" : "migrated"} ${m.src} → ${m.dest}`);
@@ -4354,7 +4507,7 @@ function migrateCmd(flags, positionals = []) {
4354
4507
  renameSync2(m.src, m.dest);
4355
4508
  } catch {
4356
4509
  try {
4357
- writeFileSync10(m.dest, readFileSync15(m.src));
4510
+ writeFileSync10(m.dest, readFileSync16(m.src));
4358
4511
  rmSync3(m.src, { force: true });
4359
4512
  } catch {}
4360
4513
  }
@@ -4367,7 +4520,7 @@ function migrateCmd(flags, positionals = []) {
4367
4520
  const srcContinue = join21(dir, `continue-${member}.json`);
4368
4521
  const destState = join21(dir, "state.json");
4369
4522
  const destContinue = join21(dir, "continue.json");
4370
- if (!existsSync16(srcState)) {
4523
+ if (!existsSync17(srcState)) {
4371
4524
  console.error(`mission "${targetMission}" has no ${member}.json`);
4372
4525
  process.exit(1);
4373
4526
  }
@@ -4380,12 +4533,12 @@ function migrateCmd(flags, positionals = []) {
4380
4533
  console.error(`mission "${targetMission}" has ${members.length} members (${members.join(", ")}) — refusing --to-solo (would orphan)`);
4381
4534
  process.exit(1);
4382
4535
  }
4383
- if (existsSync16(destState)) {
4536
+ if (existsSync17(destState)) {
4384
4537
  console.error(`destination ${destState} already exists`);
4385
4538
  process.exit(1);
4386
4539
  }
4387
4540
  const toMove = [{ src: srcState, dest: destState }];
4388
- if (existsSync16(srcContinue))
4541
+ if (existsSync17(srcContinue))
4389
4542
  toMove.push({ src: srcContinue, dest: destContinue });
4390
4543
  for (const m of toMove) {
4391
4544
  console.log(`${dryRun ? "would migrate" : "migrated"} ${m.src} → ${m.dest}`);
@@ -4395,7 +4548,7 @@ function migrateCmd(flags, positionals = []) {
4395
4548
  renameSync2(m.src, m.dest);
4396
4549
  } catch {
4397
4550
  try {
4398
- writeFileSync10(m.dest, readFileSync15(m.src));
4551
+ writeFileSync10(m.dest, readFileSync16(m.src));
4399
4552
  rmSync3(m.src, { force: true });
4400
4553
  } catch {}
4401
4554
  }
@@ -4410,7 +4563,7 @@ function migrateCmd(flags, positionals = []) {
4410
4563
  const missionsRoot = join21(projectDir, ".mugiwara", "missions");
4411
4564
  const moves = [];
4412
4565
  const collect = (srcRoot, isContinue) => {
4413
- if (!existsSync16(srcRoot))
4566
+ if (!existsSync17(srcRoot))
4414
4567
  return;
4415
4568
  const walk = (dir) => {
4416
4569
  for (const e of readdirSync10(dir, { withFileTypes: true })) {
@@ -4443,7 +4596,7 @@ function migrateCmd(flags, positionals = []) {
4443
4596
  collect(legacyState, false);
4444
4597
  collect(legacyContinue, true);
4445
4598
  if (!moves.length) {
4446
- if (!existsSync16(legacyState) && !existsSync16(legacyContinue)) {
4599
+ if (!existsSync17(legacyState) && !existsSync17(legacyContinue)) {
4447
4600
  console.log("no legacy layout found (.mugiwara/state/ does not exist)");
4448
4601
  } else {
4449
4602
  console.log("no legacy state files to migrate");
@@ -4455,7 +4608,7 @@ function migrateCmd(flags, positionals = []) {
4455
4608
  if (!dryRun) {
4456
4609
  mkdirSync10(dirname6(m.dest), { recursive: true });
4457
4610
  try {
4458
- const raw = JSON.parse(readFileSync15(m.src, "utf8"));
4611
+ const raw = JSON.parse(readFileSync16(m.src, "utf8"));
4459
4612
  raw.schema_version = CURRENT_SCHEMA_VERSION;
4460
4613
  writeFileSync10(m.dest, JSON.stringify(raw, null, 2) + `
4461
4614
  `);
@@ -4469,7 +4622,7 @@ function migrateCmd(flags, positionals = []) {
4469
4622
  }
4470
4623
  if (!dryRun) {
4471
4624
  const prune = (root) => {
4472
- if (!existsSync16(root))
4625
+ if (!existsSync17(root))
4473
4626
  return;
4474
4627
  const walkPrune = (dir) => {
4475
4628
  for (const e of readdirSync10(dir, { withFileTypes: true })) {
@@ -4483,7 +4636,7 @@ function migrateCmd(flags, positionals = []) {
4483
4636
  };
4484
4637
  walkPrune(root);
4485
4638
  try {
4486
- if (existsSync16(root) && readdirSync10(root).length === 0)
4639
+ if (existsSync17(root) && readdirSync10(root).length === 0)
4487
4640
  rmSync3(root, { recursive: true, force: true });
4488
4641
  } catch {}
4489
4642
  };
@@ -4521,7 +4674,7 @@ function signCmd(flags, _) {
4521
4674
  process.exit(1);
4522
4675
  }
4523
4676
  const missionDir = join21(projectDir, ".mugiwara", "missions", mission);
4524
- if (!existsSync16(missionDir)) {
4677
+ if (!existsSync17(missionDir)) {
4525
4678
  console.error(`no mission dir: ${missionDir}`);
4526
4679
  process.exit(1);
4527
4680
  }