@ionivetech/mugiwara 0.5.3 → 0.5.5
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 +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +2 -2
- package/.cursor-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +2 -2
- package/.opencode/plugins/mugiwara.mjs +2 -1
- package/AGENTS.md +1 -1
- package/README.md +155 -263
- package/content/agents/zoro-execution.md +1 -2
- package/content/skills/using-mugiwara/SKILL.md +1 -1
- package/dist/mugiwara.js +113 -60
- package/docs/{modes.md → concepts/modes.md} +1 -2
- package/docs/{skills.md → concepts/skills.md} +1 -1
- package/docs/getting-started.md +8 -8
- package/docs/index.md +22 -22
- package/docs/{install-antigravity.md → install/antigravity.md} +1 -1
- package/docs/{install-claude.md → install/claude.md} +4 -2
- package/docs/{install-cli.md → install/cli.md} +19 -7
- package/docs/{install-codex.md → install/codex.md} +1 -1
- package/docs/{install-copilot.md → install/copilot.md} +1 -1
- package/docs/{install-cursor.md → install/cursor.md} +1 -1
- package/docs/{install-gemini.md → install/gemini.md} +1 -1
- package/docs/{install.md → install/index.md} +11 -11
- package/docs/{install-kimi.md → install/kimi.md} +1 -1
- package/docs/{install-opencode.md → install/opencode.md} +20 -6
- package/docs/{install-pi.md → install/pi.md} +1 -1
- package/docs/{adoption-guide.md → reference/adoption-guide.md} +2 -2
- package/docs/{harness-matrix.md → reference/harness-matrix.md} +2 -2
- package/gemini-extension.json +1 -1
- package/package.json +2 -2
- package/plugin.json +2 -2
- package/scripts/evidence.sh +1 -1
- package/scripts/mission-report.sh +60 -73
- package/scripts/retrieval-eval.ts +1 -2
- package/scripts/run-evals.ts +1 -1
- package/scripts/savepoint.sh +1 -1
- package/scripts/validate-content.ts +7 -7
- package/src/args.ts +1 -1
- package/src/cli.ts +49 -22
- package/src/frontmatter.ts +3 -3
- package/src/installer.ts +10 -8
- package/src/targets/opencode.ts +22 -1
- package/docs/claude-setup.md +0 -43
- package/docs/codex-setup.md +0 -26
- package/docs/copilot-setup.md +0 -29
- package/docs/cursor-setup.md +0 -25
- package/docs/gemini-setup.md +0 -27
- package/docs/opencode-setup.md +0 -54
- package/docs/rule-based-setup.md +0 -31
- package/docs/windsurf-setup.md +0 -18
- /package/.opencode/{plugins/mugiwara-helpers.mjs → mugiwara-helpers.mjs} +0 -0
- /package/docs/{agents.md → concepts/agents.md} +0 -0
- /package/docs/{audit-trail.md → concepts/audit-trail.md} +0 -0
- /package/docs/{comparison.md → concepts/comparison.md} +0 -0
- /package/docs/{config.md → concepts/config.md} +0 -0
- /package/docs/{cost.md → concepts/cost.md} +0 -0
- /package/docs/{execution-model.md → concepts/execution-model.md} +0 -0
- /package/docs/{git-strategy.md → concepts/git-strategy.md} +0 -0
- /package/docs/{lanes.md → concepts/lanes.md} +0 -0
- /package/docs/{pr-summary.md → concepts/pr-summary.md} +0 -0
- /package/docs/{workflow.md → concepts/workflow.md} +0 -0
- /package/docs/{agent-anatomy.md → reference/agent-anatomy.md} +0 -0
- /package/docs/{compliance-matrix.md → reference/compliance-matrix.md} +0 -0
- /package/docs/{developer-onboarding.md → reference/developer-onboarding.md} +0 -0
- /package/docs/{enforcement.md → reference/enforcement.md} +0 -0
- /package/docs/{skill-anatomy.md → reference/skill-anatomy.md} +0 -0
|
@@ -29,7 +29,7 @@ Restart OpenCode.
|
|
|
29
29
|
|
|
30
30
|
## How it works
|
|
31
31
|
|
|
32
|
-
The plugin (`mugiwara.mjs`) does
|
|
32
|
+
The plugin (`mugiwara.mjs`) does four things:
|
|
33
33
|
|
|
34
34
|
1. **Config hook** — registers `content/skills/` as a skills path and
|
|
35
35
|
`content/agents/` as agents so OpenCode discovers the full crew
|
|
@@ -38,12 +38,27 @@ The plugin (`mugiwara.mjs`) does three things:
|
|
|
38
38
|
2. **System transform hook** — injects the crew announce header and active
|
|
39
39
|
autonomy mode into every session's system prompt.
|
|
40
40
|
|
|
41
|
-
3. **Chat message hook** — intercepts `/mugiwara
|
|
41
|
+
3. **Chat message hook** — intercepts `/mugiwara` commands and
|
|
42
42
|
natural-language mode switches, writing `.mugiwara/config`.
|
|
43
43
|
|
|
44
|
+
4. **Commands** — the file-based installer copies slash commands
|
|
45
|
+
(`/mugiwara`, `/using-mugiwara`, `/mugiwara-plan`, etc.) to
|
|
46
|
+
`.opencode/commands/`.
|
|
47
|
+
|
|
44
48
|
Skills are loaded on-demand via OpenCode's native `skill` tool. Agents appear
|
|
45
49
|
as subagents with full config (color, temperature, permissions, step limits).
|
|
46
50
|
|
|
51
|
+
## Mode switching
|
|
52
|
+
|
|
53
|
+
Switch autonomy mode at runtime:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
/mugiwara guided # human decides every GO
|
|
57
|
+
/mugiwara semi # auto branch + commit, plan needs GO
|
|
58
|
+
/mugiwara auto # hands-off except high-risk
|
|
59
|
+
/mugiwara # show current mode
|
|
60
|
+
```
|
|
61
|
+
|
|
47
62
|
## Verify
|
|
48
63
|
|
|
49
64
|
Ask your agent:
|
|
@@ -77,9 +92,10 @@ then restart OpenCode.
|
|
|
77
92
|
|
|
78
93
|
### Server error on startup
|
|
79
94
|
|
|
80
|
-
|
|
95
|
+
Clear stale plugin cache and reinstall:
|
|
81
96
|
|
|
82
97
|
```bash
|
|
98
|
+
rm -rf ~/.cache/opencode/packages/@ionivetech
|
|
83
99
|
npm update @ionivetech/mugiwara
|
|
84
100
|
```
|
|
85
101
|
|
|
@@ -124,6 +140,4 @@ Mugiwara skills reference general actions. On OpenCode these map to:
|
|
|
124
140
|
## Configuration
|
|
125
141
|
|
|
126
142
|
After install, configure mugiwara in `.mugiwara/config` (project) or
|
|
127
|
-
`~/.mugiwara/config` (global). See [
|
|
128
|
-
|
|
129
|
-
Switch autonomy mode at runtime: `/mugiwara-mode guided|semi|auto`
|
|
143
|
+
`~/.mugiwara/config` (global). See [config.md](../concepts/config.md).
|
|
@@ -56,7 +56,7 @@ mid-wave.
|
|
|
56
56
|
|
|
57
57
|
The crew runs **inline in your main conversation**; subagents are used only for
|
|
58
58
|
independent `[PARALLEL]` task batches and background checks. You see every wave
|
|
59
|
-
as it happens. See [execution-model.md](execution-model.md).
|
|
59
|
+
as it happens. See [execution-model.md](../concepts/execution-model.md).
|
|
60
60
|
|
|
61
61
|
## What the crew will never do
|
|
62
62
|
|
|
@@ -69,4 +69,4 @@ as it happens. See [execution-model.md](execution-model.md).
|
|
|
69
69
|
## Going further
|
|
70
70
|
|
|
71
71
|
- [Developer onboarding](developer-onboarding.md) if you want to contribute.
|
|
72
|
-
- [Comparison](comparison.md) if you're weighing mugiwara against alternatives.
|
|
72
|
+
- [Comparison](../concepts/comparison.md) if you're weighing mugiwara against alternatives.
|
|
@@ -35,7 +35,7 @@ What actually differs per harness tier. Every skill and agent file ships to ever
|
|
|
35
35
|
|
|
36
36
|
## What's the same everywhere
|
|
37
37
|
|
|
38
|
-
- All
|
|
39
|
-
- All
|
|
38
|
+
- All 26 skill directories ship to every harness.
|
|
39
|
+
- All 14 agent markdown files ship to every harness.
|
|
40
40
|
- `references/` files are always copied.
|
|
41
41
|
- The workflow, lane sizing, and evidence discipline are identical — the difference is in how the model loads them.
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ionivetech/mugiwara",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "The Straw Hat crew of AI agents and skills: brainstorm, plan, execute, checkpoint, quality, gates, review, security, self-healing. Installs into Claude Code, opencode, Copilot, Gemini, Codex, Cursor, Kimi, pi, Windsurf, Cline, Kilo, Antigravity.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"retrieval-eval": "bun scripts/retrieval-eval.ts",
|
|
54
54
|
"sync-version": "bun scripts/sync-version.ts",
|
|
55
55
|
"gate": "bun run typecheck && bun run test && bun run build && bun scripts/validate-content.ts --check-manifest --check-docs && bun run verify-pack && bun scripts/run-evals.ts && bun scripts/retrieval-eval.ts",
|
|
56
|
-
"verify-pack": "npm pack --dry-run 2>&1 |
|
|
56
|
+
"verify-pack": "npm pack --dry-run 2>&1 | node -e \"let s='';process.stdin.on('data',d=>s+=d);process.stdin.on('end',()=>{if(!s.includes('ionivetech-mugiwara')){console.error('npm pack failed');process.exit(1)};console.log('npm package clean')})\"",
|
|
57
57
|
"prepack": "bun run build && bun run sync-version"
|
|
58
58
|
},
|
|
59
59
|
"keywords": [
|
package/plugin.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mugiwara",
|
|
3
3
|
"description": "The Straw Hat crew of AI agents and skills: brainstorm, plan, execute, checkpoint, quality, gates, review, security, healing.",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.5",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "
|
|
6
|
+
"name": "ionivetech"
|
|
7
7
|
},
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": "https://github.com/ionivetech/mugiwara",
|
package/scripts/evidence.sh
CHANGED
|
@@ -15,7 +15,7 @@ RESULTS_DIR="$MUGIWARA_DIR/results"
|
|
|
15
15
|
mkdir -p "$RESULTS_DIR"
|
|
16
16
|
|
|
17
17
|
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
|
18
|
-
HASH=$(echo "${LABEL}-${TIMESTAMP}-$$-${RANDOM}" | shasum -a 256 | cut -c1-12 2>/dev/null || echo "${TIMESTAMP}")
|
|
18
|
+
HASH=$(echo "${LABEL}-${TIMESTAMP}-$$-${RANDOM}" | (sha256sum 2>/dev/null || shasum -a 256 2>/dev/null || openssl sha256) | cut -c1-12 2>/dev/null || echo "${TIMESTAMP}")
|
|
19
19
|
EVIDENCE_FILE="$RESULTS_DIR/${LABEL}-${HASH}.log"
|
|
20
20
|
|
|
21
21
|
{
|
|
@@ -13,76 +13,63 @@ REPORT_FILE="$REPORT_DIR/${MISSION}.md"
|
|
|
13
13
|
|
|
14
14
|
[ -f "$STATE_FILE" ] || { echo "mission-report: $STATE_FILE not found" >&2; exit 1; }
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
report +=
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
report +=
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
| Heal cycles | {heal_cycle} |
|
|
77
|
-
| Tokens used | {tokens:,} / {budget:,} |
|
|
78
|
-
|
|
79
|
-
## Updated
|
|
80
|
-
|
|
81
|
-
{updated}
|
|
82
|
-
"""
|
|
83
|
-
|
|
84
|
-
with open(report_file, 'w') as f:
|
|
85
|
-
f.write(report)
|
|
86
|
-
|
|
87
|
-
print(f"✓ mission report: {report_file}")
|
|
88
|
-
PY
|
|
16
|
+
export STATE_FILE REPORT_DIR MISSION
|
|
17
|
+
node << 'NODE'
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
const stateFile = process.env.STATE_FILE || ".mugiwara/state.json";
|
|
22
|
+
const reportDir = process.env.REPORT_DIR || ".mugiwara/reports";
|
|
23
|
+
const mission = process.env.MISSION || "unknown";
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(stateFile)) {
|
|
26
|
+
console.error("mission-report: " + stateFile + " not found");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
31
|
+
const s = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
32
|
+
|
|
33
|
+
const reportFile = path.join(reportDir, mission + ".md");
|
|
34
|
+
const now = new Date().toISOString().slice(0, 10);
|
|
35
|
+
const lane = s.lane || "unknown";
|
|
36
|
+
const mode = s.mode || "guided";
|
|
37
|
+
const actor = s.actor || "unknown";
|
|
38
|
+
const branch = s.branch || "unknown";
|
|
39
|
+
const wave = s.wave || 0;
|
|
40
|
+
const filesTouched = s.files_touched || 0;
|
|
41
|
+
const locDelta = s.loc_delta || 0;
|
|
42
|
+
const sensitive = s.sensitive_paths || [];
|
|
43
|
+
const tasks = s.tasks || {};
|
|
44
|
+
const blockers = s.blockers_open || 0;
|
|
45
|
+
const healCycle = s.heal_cycle || 1;
|
|
46
|
+
const tokens = s.tokens_est || 0;
|
|
47
|
+
const budget = s.budget || 0;
|
|
48
|
+
const evidence = s.evidence || [];
|
|
49
|
+
const updated = s.updated_at || new Date().toISOString();
|
|
50
|
+
|
|
51
|
+
let report = "# Mission: " + mission + " . " + now + "\n\n";
|
|
52
|
+
report += "**Lane** " + lane + " . **Mode** " + mode + " . **Actor** " + actor + " . **Branch** " + branch + "\n\n";
|
|
53
|
+
report += "## What changed\n\n";
|
|
54
|
+
report += filesTouched + " files, +" + locDelta + " LOC";
|
|
55
|
+
|
|
56
|
+
if (sensitive.length) {
|
|
57
|
+
report += "\nSensitive paths: " + sensitive.join(", ");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
report += "\n\n## Gates\n\n| Gate | Verdict | Evidence |\n|------|---------|----------|";
|
|
61
|
+
for (const e of evidence) {
|
|
62
|
+
report += "\n| \u2014 | PASS | " + e + " |";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
report += "\n\n## State\n\n| Field | Value |\n|-------|-------|\n";
|
|
66
|
+
report += "| Wave | " + wave + " |\n";
|
|
67
|
+
report += "| Tasks | " + (tasks.done || 0) + "/" + (tasks.total || 0) + " done |\n";
|
|
68
|
+
report += "| Blockers open | " + blockers + " |\n";
|
|
69
|
+
report += "| Heal cycles | " + healCycle + " |\n";
|
|
70
|
+
report += "| Tokens used | " + tokens.toLocaleString() + " / " + budget.toLocaleString() + " |\n\n";
|
|
71
|
+
report += "## Updated\n\n" + updated + "\n";
|
|
72
|
+
|
|
73
|
+
fs.writeFileSync(reportFile, report);
|
|
74
|
+
console.log("\u2713 mission report: " + reportFile);
|
|
75
|
+
NODE
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Builds TF-IDF index over skill descriptions, scores prompts, reports rank-1 + top_k.
|
|
4
4
|
// No model needed — pure string matching. Outputs JSON.
|
|
5
5
|
|
|
6
|
-
import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
|
|
6
|
+
import { readdirSync, readFileSync, existsSync, statSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { parseFrontmatter } from '../src/frontmatter.ts';
|
|
9
9
|
|
|
@@ -256,6 +256,5 @@ if (ciArg !== -1) {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
function writeJson(path: string, obj: object) {
|
|
259
|
-
const { writeFileSync } = require('node:fs');
|
|
260
259
|
writeFileSync(path, JSON.stringify(obj, null, 2) + '\n');
|
|
261
260
|
}
|
package/scripts/run-evals.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// keyword match, then reports pass/fail per case and a rank-1 style score.
|
|
10
10
|
// The command defaults to "claude -p" — override with MUGIWARA_EVAL_CMD.
|
|
11
11
|
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
12
|
+
import { execFileSync } from 'node:child_process';
|
|
12
13
|
import { join } from 'node:path';
|
|
13
14
|
|
|
14
15
|
const root = join(import.meta.dirname, '..');
|
|
@@ -136,7 +137,6 @@ async function runCases(env: { execFileSync: typeof import('node:child_process')
|
|
|
136
137
|
|
|
137
138
|
const runArg = process.argv.indexOf('--run');
|
|
138
139
|
if (runArg !== -1) {
|
|
139
|
-
const { execFileSync } = require('node:child_process');
|
|
140
140
|
const cmd = process.env.MUGIWARA_EVAL_CMD ?? 'claude -p';
|
|
141
141
|
const [bin, ...pre] = cmd.split(' ');
|
|
142
142
|
runCases({ execFileSync, bin, pre });
|
package/scripts/savepoint.sh
CHANGED
|
@@ -100,7 +100,7 @@ EVIDENCE=$(ls "$MUGIWARA_DIR/results/" 2>/dev/null | grep "$MISSION" | sed 's|^|
|
|
|
100
100
|
SKILL_VERSION="1"
|
|
101
101
|
if [ -f package.json ]; then
|
|
102
102
|
PKG_JSON="$MUGIWARA_DIR/../package.json"
|
|
103
|
-
[ -f "$PKG_JSON" ] && SKILL_VERSION=$(
|
|
103
|
+
[ -f "$PKG_JSON" ] && SKILL_VERSION=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('$PKG_JSON','utf8')).version.split('.')[0])}catch(e){console.log('1')}" 2>/dev/null || echo "1")
|
|
104
104
|
fi
|
|
105
105
|
|
|
106
106
|
# tokens from env var (harness exports estimated tokens consumed)
|
|
@@ -14,7 +14,7 @@ function checkFile(file: string, wantName: string, kind: 'skill' | 'agent'): Rec
|
|
|
14
14
|
const { data, body } = parsed;
|
|
15
15
|
if (data.name !== wantName) errors.push(`${kind} ${file}: name "${data.name}" != "${wantName}"`);
|
|
16
16
|
const d = data.description ?? '';
|
|
17
|
-
if (kind === 'skill' && (d.length < 20 || d.length >
|
|
17
|
+
if (kind === 'skill' && (d.length < 20 || d.length > 220)) errors.push(`skill ${file}: description must be 20-220 chars (got ${d.length})`);
|
|
18
18
|
if (kind === 'agent' && d.length < 20) errors.push(`agent ${file}: description too short`);
|
|
19
19
|
if (kind === 'skill' && body.replace(/\r?\n$/, '').split(/\r?\n/).length > 120) errors.push(`skill ${file}: body exceeds 120 lines`);
|
|
20
20
|
if (kind === 'skill' && !body.includes('## Skip when')) errors.push(`skill ${file}: missing required "## Skip when" block (≤4 lines, numeric threshold)`);
|
|
@@ -188,20 +188,20 @@ if (totalDescChars > INDEX_BUDGET) {
|
|
|
188
188
|
const docsArg = process.argv.indexOf('--check-docs');
|
|
189
189
|
if (docsArg !== -1) {
|
|
190
190
|
const docsDir = join(import.meta.dirname, '..', 'docs');
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
const skillsDoc = join(docsDir, 'concepts', 'skills.md');
|
|
192
|
+
const agentsDoc = join(docsDir, 'concepts', 'agents.md');
|
|
193
193
|
let docErrors = 0;
|
|
194
194
|
|
|
195
195
|
if (existsSync(skillsDoc)) {
|
|
196
196
|
const content = readFileSync(skillsDoc, 'utf8');
|
|
197
197
|
for (const dir of skillDirs) {
|
|
198
198
|
if (!content.includes(dir)) {
|
|
199
|
-
errors.push(`docs/skills.md: missing skill "${dir}"`);
|
|
199
|
+
errors.push(`docs/concepts/skills.md: missing skill "${dir}"`);
|
|
200
200
|
docErrors++;
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
} else {
|
|
204
|
-
errors.push('docs/skills.md: file not found');
|
|
204
|
+
errors.push('docs/concepts/skills.md: file not found');
|
|
205
205
|
docErrors++;
|
|
206
206
|
}
|
|
207
207
|
|
|
@@ -210,12 +210,12 @@ if (docsArg !== -1) {
|
|
|
210
210
|
for (const f of agentFiles) {
|
|
211
211
|
const name = f.replace(/\.md$/, '');
|
|
212
212
|
if (!content.includes(name)) {
|
|
213
|
-
errors.push(`docs/agents.md: missing agent "${name}"`);
|
|
213
|
+
errors.push(`docs/concepts/agents.md: missing agent "${name}"`);
|
|
214
214
|
docErrors++;
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
} else {
|
|
218
|
-
errors.push('docs/agents.md: file not found');
|
|
218
|
+
errors.push('docs/concepts/agents.md: file not found');
|
|
219
219
|
docErrors++;
|
|
220
220
|
}
|
|
221
221
|
|
package/src/args.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type Args = {
|
|
|
9
9
|
const VALUE_FLAGS: Record<string, string> = { '--project': 'project', '--target': 'target' };
|
|
10
10
|
const BOOL_FLAGS: Record<string, string> = {
|
|
11
11
|
'--global': 'global', '--yes': 'yes', '-y': 'yes', '--force': 'force',
|
|
12
|
-
'--dry-run': 'dryRun', '--keep-logs': 'keepLogs',
|
|
12
|
+
'--dry-run': 'dryRun', '--keep-logs': 'keepLogs', '--check': 'check',
|
|
13
13
|
'--help': 'help', '-h': 'help', '--version': 'version', '-v': 'version',
|
|
14
14
|
};
|
|
15
15
|
|
package/src/cli.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// src/cli.ts
|
|
3
|
-
import { existsSync, readFileSync,
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
-
import { resolve } from 'node:path';
|
|
6
|
-
import {
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { parseArgs, type FlagValue, type Args } from './args.ts';
|
|
8
8
|
import { createRl, choose, multiChoose, confirm } from './prompt.ts';
|
|
9
9
|
import { targets, TARGET_IDS } from './targets/index.ts';
|
|
@@ -43,12 +43,15 @@ function resetCmd(flags: Args['flags']): void {
|
|
|
43
43
|
|
|
44
44
|
async function resolveOptions(flags: Args['flags']): Promise<{ scope: Scope; projectDir: string; targetIds: string[] }> {
|
|
45
45
|
const interactive = !flag(flags.yes);
|
|
46
|
+
if (interactive && !process.stdin.isTTY) {
|
|
47
|
+
throw new Error('Not a terminal. Run with --yes and --global or --project <dir> --target <ids|all>');
|
|
48
|
+
}
|
|
46
49
|
const rl = interactive ? createRl() : null;
|
|
47
50
|
try {
|
|
48
51
|
let scope: Scope | null = flag(flags.global) ? 'global' : str(flags.project) ? 'project' : null;
|
|
49
52
|
if (!scope) {
|
|
50
|
-
if (!interactive)
|
|
51
|
-
scope = (await choose(rl!, 'Install scope?', ['global (user-wide)', 'project (this repo)'])) === 0 ? 'global' : 'project';
|
|
53
|
+
if (!interactive) { scope = 'project'; }
|
|
54
|
+
else scope = (await choose(rl!, 'Install scope?', ['global (user-wide)', 'project (this repo)'])) === 0 ? 'global' : 'project';
|
|
52
55
|
}
|
|
53
56
|
const projectDir = resolve(str(flags.project) ?? process.cwd());
|
|
54
57
|
if (scope === 'project' && !existsSync(projectDir)) throw new Error(`Project dir not found: ${projectDir}`);
|
|
@@ -56,9 +59,11 @@ async function resolveOptions(flags: Args['flags']): Promise<{ scope: Scope; pro
|
|
|
56
59
|
let targetIds = str(flags.target)?.split(',').map(s => s.trim()) ?? null;
|
|
57
60
|
if (targetIds && targetIds.includes('all')) targetIds = [...TARGET_IDS];
|
|
58
61
|
if (!targetIds) {
|
|
59
|
-
if (!interactive)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
if (!interactive) { targetIds = [...TARGET_IDS]; }
|
|
63
|
+
else {
|
|
64
|
+
const idx = await multiChoose(rl!, 'Target AI agents?', ['all targets', ...TARGET_IDS]);
|
|
65
|
+
targetIds = idx.includes(0) ? [...TARGET_IDS] : idx.map(i => TARGET_IDS[i - 1]);
|
|
66
|
+
}
|
|
62
67
|
}
|
|
63
68
|
for (const id of targetIds) {
|
|
64
69
|
if (!targets[id]) throw new Error(`Unknown target: ${id} (valid: ${TARGET_IDS.join(', ')}, all)`);
|
|
@@ -110,7 +115,17 @@ async function uninstall(flags: Args['flags']): Promise<void> {
|
|
|
110
115
|
const home = homedir();
|
|
111
116
|
const file = manifestPath({ scope, projectDir, home });
|
|
112
117
|
const manifest = readManifest(file);
|
|
113
|
-
if (!manifest) {
|
|
118
|
+
if (!manifest) {
|
|
119
|
+
if (scope === 'project') {
|
|
120
|
+
const globalFile = manifestPath({ scope: 'global', projectDir, home });
|
|
121
|
+
if (existsSync(globalFile)) {
|
|
122
|
+
console.log('No project manifest found, but a global install exists. Try:\n mugiwara uninstall --global');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
console.log('Nothing installed (no manifest found).');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
114
129
|
console.log(`Will remove ${manifest.files.length} files (targets: ${manifest.targets.join(', ')}).`);
|
|
115
130
|
if (!flag(flags.yes)) {
|
|
116
131
|
const rl = createRl();
|
|
@@ -119,7 +134,20 @@ async function uninstall(flags: Args['flags']): Promise<void> {
|
|
|
119
134
|
if (!ok) { console.log('Aborted.'); return; }
|
|
120
135
|
}
|
|
121
136
|
const removed = removeInstalled(manifest, { dryRun: flag(flags.dryRun) });
|
|
122
|
-
if (!flag(flags.dryRun))
|
|
137
|
+
if (!flag(flags.dryRun)) {
|
|
138
|
+
rmSync(file);
|
|
139
|
+
const mugiDir = join(scope === 'global' ? home : projectDir, '.mugiwara');
|
|
140
|
+
if (existsSync(mugiDir) && readdirSync(mugiDir).length === 0) {
|
|
141
|
+
rmSync(mugiDir, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
if (manifest.targets.includes('opencode')) {
|
|
144
|
+
const opencodeCache = join(home, '.cache', 'opencode', 'packages', '@ionivetech');
|
|
145
|
+
if (existsSync(opencodeCache)) {
|
|
146
|
+
rmSync(opencodeCache, { recursive: true, force: true });
|
|
147
|
+
console.log(' cleared opencode npm cache (stale plugin versions)');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
123
151
|
console.log(`OK removed ${removed.length} files`);
|
|
124
152
|
}
|
|
125
153
|
|
|
@@ -134,7 +162,12 @@ function list(flags: Args['flags']): void {
|
|
|
134
162
|
const m = readManifest(file);
|
|
135
163
|
if (!m) continue;
|
|
136
164
|
found = true;
|
|
137
|
-
|
|
165
|
+
if (flag(flags.check)) {
|
|
166
|
+
const missing = m.files.filter(f => !existsSync(f));
|
|
167
|
+
console.log(`${label}: v${m.version} targets=${m.targets.join(',')} files=${m.files.length} missing=${missing.length} installed=${m.installedAt}`);
|
|
168
|
+
} else {
|
|
169
|
+
console.log(`${label}: v${m.version} targets=${m.targets.join(',')} files=${m.files.length} installed=${m.installedAt}`);
|
|
170
|
+
}
|
|
138
171
|
}
|
|
139
172
|
if (!found) console.log('No mugiwara installation found.');
|
|
140
173
|
}
|
|
@@ -143,10 +176,11 @@ function help(): void {
|
|
|
143
176
|
console.log(`mugiwara ${VERSION} — the Straw Hat crew for AI agents
|
|
144
177
|
|
|
145
178
|
Usage:
|
|
146
|
-
mugiwara [install] install the crew (
|
|
179
|
+
mugiwara [install] install the crew (wizard; with --yes uses project + all)
|
|
147
180
|
mugiwara update replace existing files (backs up differences first)
|
|
148
181
|
mugiwara uninstall remove installed files via manifest
|
|
149
182
|
mugiwara list show installations
|
|
183
|
+
mugiwara list --check health check: show installations + missing files
|
|
150
184
|
mugiwara reset wipe mission state (spec/plans/results/review/issues[/logs])
|
|
151
185
|
mugiwara --help this help
|
|
152
186
|
mugiwara --version print version
|
|
@@ -155,21 +189,14 @@ Flags:
|
|
|
155
189
|
--global user-wide install
|
|
156
190
|
--project <dir> project install (default: cwd)
|
|
157
191
|
--target <ids|all> comma-separated: ${TARGET_IDS.join(', ')}
|
|
158
|
-
--yes, -y non-interactive (
|
|
192
|
+
--yes, -y non-interactive (defaults: project, all targets)
|
|
159
193
|
--force overwrite differing files (with backup)
|
|
160
194
|
--dry-run print actions without writing
|
|
195
|
+
--check with list: report missing files (health check)
|
|
161
196
|
--keep-logs with reset: keep .mugiwara/logs (lessons ledger survives)`);
|
|
162
197
|
}
|
|
163
198
|
|
|
164
|
-
|
|
165
|
-
if (entry !== undefined) {
|
|
166
|
-
try {
|
|
167
|
-
entry = realpathSync(entry);
|
|
168
|
-
} catch {
|
|
169
|
-
// ponytail: realpath can throw on a missing/odd path — fall back to the raw resolve
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const isMain = entry !== undefined && import.meta.url === pathToFileURL(entry).href;
|
|
199
|
+
const isMain = process.argv[1] !== undefined && fileURLToPath(import.meta.url) === resolve(process.argv[1]);
|
|
173
200
|
if (isMain) {
|
|
174
201
|
run(process.argv.slice(2)).catch(err => {
|
|
175
202
|
console.error(`mugiwara: ${err.message}`);
|
package/src/frontmatter.ts
CHANGED
|
@@ -7,9 +7,9 @@ export function parseFrontmatter(text: string): { data: FrontmatterData; body: s
|
|
|
7
7
|
const data: FrontmatterData = {};
|
|
8
8
|
for (const line of m[1].split(/\r?\n/)) {
|
|
9
9
|
if (!line.trim()) continue;
|
|
10
|
-
const i = line.indexOf(':');
|
|
11
|
-
if (i === -1) throw new Error(`Bad frontmatter line: ${line}`);
|
|
12
|
-
data[line.slice(0, i).trim()] = line.slice(i +
|
|
10
|
+
const i = line.indexOf(': ');
|
|
11
|
+
if (i === -1) throw new Error(`Bad frontmatter line (missing ': ' separator): ${line}`);
|
|
12
|
+
data[line.slice(0, i).trim()] = line.slice(i + 2).trim();
|
|
13
13
|
}
|
|
14
14
|
return { data, body: text.slice(m[0].length) };
|
|
15
15
|
}
|
package/src/installer.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, copyFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { parseFrontmatter, type FrontmatterData } from './frontmatter.ts';
|
|
6
7
|
import type { Scope } from './manifest.ts';
|
|
7
8
|
|
|
@@ -43,8 +44,8 @@ export interface Target {
|
|
|
43
44
|
postInstall?(opts: { scope: Scope; projectDir: string; home: string; dryRun: boolean; files: string[] }): { written: string[]; notes: string[] };
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
export const CONTENT_DIR = join(import.meta.
|
|
47
|
-
const pkg = JSON.parse(readFileSync(join(import.meta.
|
|
47
|
+
export const CONTENT_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'content');
|
|
48
|
+
const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8')) as { version: string };
|
|
48
49
|
export const VERSION = pkg.version;
|
|
49
50
|
|
|
50
51
|
export function collectContent(): { skills: ContentItem[]; agents: ContentItem[] } {
|
|
@@ -87,8 +88,10 @@ export function installTo(target: Target, opts: InstallOptions): InstallResult {
|
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
89
90
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
90
|
-
const
|
|
91
|
-
|
|
91
|
+
const fileName = absPath.replace(/[^a-zA-Z0-9]+/g, '_');
|
|
92
|
+
const backupDir = join(backupRoot, 'backup', `${ts}-${target.id}`);
|
|
93
|
+
const backupFile = join(backupDir, fileName);
|
|
94
|
+
if (!dryRun) { mkdirSync(backupDir, { recursive: true }); copyFileSync(absPath, backupFile); }
|
|
92
95
|
result.backedUp.push(absPath);
|
|
93
96
|
}
|
|
94
97
|
if (!dryRun) { mkdirSync(dirname(absPath), { recursive: true }); writeFileSync(absPath, text); }
|
|
@@ -133,10 +136,9 @@ export function removeInstalled(manifest: { files: string[] }, { dryRun = false
|
|
|
133
136
|
for (const f of manifest.files) {
|
|
134
137
|
let d = dirname(f);
|
|
135
138
|
while (existsSync(d) && readdirSync(d).length === 0) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
d = parent;
|
|
139
|
+
if (d.endsWith('/.mugiwara') || d === dirname(d)) break;
|
|
140
|
+
rmSync(d, { recursive: true, force: true });
|
|
141
|
+
d = dirname(d);
|
|
140
142
|
}
|
|
141
143
|
}
|
|
142
144
|
}
|
package/src/targets/opencode.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
// src/targets/opencode.ts
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readdirSync, copyFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { stringifyFrontmatter, type FrontmatterData } from '../frontmatter.ts';
|
|
4
6
|
import type { Target } from '../installer.ts';
|
|
5
7
|
|
|
8
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const COMMANDS_SRC = join(here, '..', '..', '.opencode', 'commands');
|
|
10
|
+
|
|
6
11
|
type CrewConfig = {
|
|
7
12
|
color: string;
|
|
8
13
|
temperature: number;
|
|
@@ -62,4 +67,20 @@ export const target: Target = {
|
|
|
62
67
|
const root = scope === 'global' ? join(home, '.config', 'opencode') : join(projectDir, '.opencode');
|
|
63
68
|
return join(root, 'skills', skillName, 'references');
|
|
64
69
|
},
|
|
70
|
+
postInstall({ scope, projectDir, home, dryRun }) {
|
|
71
|
+
const root = scope === 'global' ? join(home, '.config', 'opencode') : join(projectDir, '.opencode');
|
|
72
|
+
const dstDir = join(root, 'commands');
|
|
73
|
+
if (dryRun) return { written: [], notes: [] };
|
|
74
|
+
if (!existsSync(COMMANDS_SRC)) return { written: [], notes: [] };
|
|
75
|
+
mkdirSync(dstDir, { recursive: true });
|
|
76
|
+
const written: string[] = [];
|
|
77
|
+
for (const f of readdirSync(COMMANDS_SRC)) {
|
|
78
|
+
if (!f.endsWith('.md')) continue;
|
|
79
|
+
const src = join(COMMANDS_SRC, f);
|
|
80
|
+
const dst = join(dstDir, f);
|
|
81
|
+
copyFileSync(src, dst);
|
|
82
|
+
written.push(dst);
|
|
83
|
+
}
|
|
84
|
+
return { written, notes: [] };
|
|
85
|
+
},
|
|
65
86
|
};
|