@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.kimi-plugin/plugin.json +1 -1
- package/.opencode/mugiwara-helpers.mjs +1 -1
- package/.opencode/plugins/mugiwara.mjs +173 -1
- package/README.md +4 -4
- package/content/agents/luffy-orchestrator.md +15 -1
- package/content/agents/zoro-execution.md +1 -1
- package/content/skills/mugiwara-checkpoint/SKILL.md +1 -0
- package/content/skills/mugiwara-execution/SKILL.md +5 -5
- package/content/skills/mugiwara-gates/SKILL.md +1 -0
- package/content/skills/mugiwara-healing/SKILL.md +1 -0
- package/content/skills/mugiwara-orchestration/SKILL.md +7 -3
- package/content/skills/mugiwara-orchestration/references/check-ins.md +4 -5
- package/content/skills/mugiwara-orchestration/references/output-contract.md +2 -2
- package/content/skills/mugiwara-planning/SKILL.md +1 -1
- package/content/skills/mugiwara-planning/references/sub-missions.md +2 -2
- package/content/skills/mugiwara-quality/SKILL.md +1 -0
- package/content/skills/mugiwara-review/SKILL.md +2 -0
- package/content/skills/mugiwara-security/SKILL.md +2 -2
- package/content/skills/mugiwara-ship/SKILL.md +12 -0
- package/content/skills/mugiwara-workflow/SKILL.md +2 -2
- package/dist/mugiwara.js +235 -82
- package/gemini-extension.json +1 -1
- package/hooks/engagement-marker.js +9 -1
- package/hooks/engagement-marker.ts +9 -1
- package/hooks/hooks.json +12 -0
- package/hooks/pipeline-guard.js +137 -3
- package/hooks/pipeline-guard.ts +161 -3
- package/hooks/pretool-guard.js +84 -0
- package/hooks/pretool-guard.ts +60 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/references/wave-banners.md +22 -27
- package/scripts/build-hooks.ts +1 -1
- package/scripts/gate-selftest.ts +342 -0
- package/scripts/savepoint.sh +16 -4
- package/scripts/validate-content.ts +190 -15
- package/scripts/write-metrics.ts +25 -1
- package/src/cli.ts +15 -0
- package/src/config.ts +1 -1
- package/src/guards.ts +40 -0
- package/src/initiative.ts +174 -0
- 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
|
|
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=
|
|
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(
|
|
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 =
|
|
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}-${
|
|
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 =
|
|
828
|
+
const out = target.transformSkill(s.data, s.body);
|
|
828
829
|
if (out) {
|
|
829
830
|
let text = out.text;
|
|
830
|
-
if (
|
|
831
|
-
text = text.replace(/`_shared\/references\//g, "`" +
|
|
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 (
|
|
836
|
-
const full =
|
|
837
|
-
if (full &&
|
|
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 (
|
|
840
|
-
text = text.replace(/`_shared\/references\//g, "`" +
|
|
840
|
+
if (target.refPointerPrefix !== undefined && target.refPointerPrefix !== "") {
|
|
841
|
+
text = text.replace(/`_shared\/references\//g, "`" + target.refPointerPrefix + "_shared/references/");
|
|
841
842
|
}
|
|
842
|
-
writeOne(join6(
|
|
843
|
+
writeOne(join6(target.refsDir({ scope, projectDir, home }, s.name), full.relPath), text);
|
|
843
844
|
}
|
|
844
845
|
}
|
|
845
|
-
if (s.refs.length &&
|
|
846
|
-
const refsRoot =
|
|
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 =
|
|
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 (
|
|
856
|
-
const full =
|
|
857
|
-
if (full &&
|
|
858
|
-
writeOne(join6(
|
|
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 (
|
|
879
|
-
const post =
|
|
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
|
|
1066
|
+
const { key, indent: pIndent } = top.pending;
|
|
1066
1067
|
delete top.pending;
|
|
1067
1068
|
const created = {};
|
|
1068
|
-
top.obj[
|
|
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
|
|
1621
|
-
if (isAbsolute(
|
|
1621
|
+
for (const target of linkedPaths(body)) {
|
|
1622
|
+
if (isAbsolute(target))
|
|
1622
1623
|
continue;
|
|
1623
|
-
const fromMission = join9(missionDir,
|
|
1624
|
-
const fromRoot = join9(projectRoot,
|
|
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 "${
|
|
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
|
|
1856
|
-
if (!
|
|
1856
|
+
let targets = shas;
|
|
1857
|
+
if (!targets.length) {
|
|
1857
1858
|
try {
|
|
1858
|
-
|
|
1859
|
+
targets = [git2(projectDir, ["rev-parse", "--verify", branch])];
|
|
1859
1860
|
} catch {
|
|
1860
|
-
|
|
1861
|
+
targets = [git2(projectDir, ["rev-parse", "HEAD"])];
|
|
1861
1862
|
}
|
|
1862
1863
|
}
|
|
1863
|
-
for (const sha of
|
|
1864
|
+
for (const sha of targets) {
|
|
1864
1865
|
git2(projectDir, ["notes", "--ref=mugiwara", "add", "-f", "-m", note, sha]);
|
|
1865
1866
|
}
|
|
1866
|
-
return { sha:
|
|
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
|
|
3154
|
+
const flowsDir = join18(dir, "flows");
|
|
3154
3155
|
const wavesDir = join18(dir, "waves");
|
|
3155
|
-
const targetDir = existsSync13(
|
|
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((
|
|
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
|
|
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 =
|
|
3513
|
-
const 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 (!
|
|
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(
|
|
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) =>
|
|
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" && !
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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) => !
|
|
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
|
|
4028
|
-
if (badState.includes(
|
|
4029
|
-
console.error(`✗ mission "${mission}"${member ? ` member "${member}"` : ""} has unreadable state: ${
|
|
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
|
|
4101
|
-
if (
|
|
4102
|
-
return
|
|
4103
|
-
if (
|
|
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 (!
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
4251
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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) =>
|
|
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) =>
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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,
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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,
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
4677
|
+
if (!existsSync17(missionDir)) {
|
|
4525
4678
|
console.error(`no mission dir: ${missionDir}`);
|
|
4526
4679
|
process.exit(1);
|
|
4527
4680
|
}
|