@ionivetech/mugiwara 0.6.5 → 0.7.0
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 +3 -9
- package/.codex-plugin/plugin.json +2 -8
- package/.cursor-plugin/plugin.json +2 -8
- package/.kimi-plugin/plugin.json +2 -8
- package/.opencode/commands/mugiwara-continue.md +44 -31
- package/.opencode/commands/mugiwara-review.md +1 -1
- package/.opencode/commands/mugiwara-security.md +1 -1
- package/.opencode/commands/mugiwara.md +2 -2
- package/.opencode/plugins/mugiwara.mjs +3 -19
- package/AGENTS.md +6 -6
- package/GEMINI.md +3 -3
- package/README.md +82 -74
- package/content/agents/brook-healing.md +12 -11
- package/content/agents/chopper-checkpoint.md +14 -13
- package/content/agents/eval-runner.md +6 -9
- package/content/agents/franky-gates.md +7 -10
- package/content/agents/jinbe-security.md +10 -14
- package/content/agents/luffy-orchestrator.md +20 -22
- package/content/agents/memory-keeper.md +9 -12
- package/content/agents/nami-planner.md +7 -10
- package/content/agents/resume-coordinator.md +12 -15
- package/content/agents/robin-reviewer.md +13 -12
- package/content/agents/sanji-quality.md +9 -12
- package/content/agents/skeptic-verifier.md +7 -10
- package/content/agents/usopp-brainstorm.md +6 -9
- package/content/agents/zoro-execution.md +9 -12
- package/content/skills/mugiwara-backend/SKILL.md +4 -0
- package/content/skills/mugiwara-backend/references/database.md +61 -0
- package/content/skills/mugiwara-brainstorm/SKILL.md +1 -1
- package/content/skills/mugiwara-checkpoint/SKILL.md +17 -14
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +5 -5
- package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
- package/content/skills/mugiwara-execution/SKILL.md +24 -24
- package/content/skills/mugiwara-execution/references/dispatch.md +3 -3
- package/content/skills/mugiwara-execution/references/resume-batching.md +2 -2
- package/content/skills/mugiwara-frontend/SKILL.md +5 -6
- package/content/skills/mugiwara-frontend/references/checklist.md +14 -0
- package/content/skills/mugiwara-gates/SKILL.md +14 -10
- package/content/skills/mugiwara-git/SKILL.md +7 -2
- package/content/skills/mugiwara-healing/SKILL.md +7 -5
- package/content/skills/mugiwara-healing/references/workers.md +2 -2
- package/content/skills/mugiwara-lessons/SKILL.md +2 -2
- package/content/skills/mugiwara-orchestration/SKILL.md +28 -26
- package/content/skills/mugiwara-orchestration/references/check-ins.md +17 -16
- package/content/skills/mugiwara-orchestration/references/closure.md +18 -13
- package/content/skills/mugiwara-orchestration/references/output-contract.md +9 -9
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +32 -22
- package/content/skills/mugiwara-planning/SKILL.md +5 -18
- package/content/skills/mugiwara-planning/references/plan-template.md +3 -3
- package/content/skills/mugiwara-quality/SKILL.md +10 -7
- package/content/skills/mugiwara-resume/SKILL.md +20 -22
- package/content/skills/mugiwara-review/SKILL.md +7 -2
- package/content/skills/mugiwara-review/references/five-axis-worksheet.md +1 -1
- package/content/skills/mugiwara-root-cause/SKILL.md +2 -0
- package/content/skills/mugiwara-security/SKILL.md +6 -2
- package/content/skills/mugiwara-ship/SKILL.md +2 -2
- package/content/skills/mugiwara-ship/references/cleanup.md +17 -16
- package/content/skills/mugiwara-testcases/SKILL.md +5 -3
- package/content/skills/mugiwara-workflow/SKILL.md +32 -35
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +41 -40
- package/dist/mugiwara.js +1370 -153
- package/gemini-extension.json +1 -1
- package/hooks/auto-savepoint.js +114 -0
- package/hooks/auto-savepoint.ts +102 -0
- package/hooks/engagement-marker.js +59 -0
- package/hooks/engagement-marker.ts +97 -0
- package/hooks/hooks.json +41 -2
- package/hooks/mugiwara-mode-tracker.js +79 -0
- package/hooks/pipeline-guard.js +186 -0
- package/hooks/pipeline-guard.ts +270 -0
- package/hooks/session-start.js +109 -0
- package/hooks/session-start.ts +62 -45
- package/package.json +7 -3
- package/plugin.json +1 -1
- package/references/agent-protocol.md +15 -0
- package/references/complexity.md +77 -0
- package/references/definition-of-done.md +1 -1
- package/references/multi-actor.md +4 -7
- package/references/prose-style.md +54 -0
- package/references/skill-versioning.md +1 -1
- package/references/wave-banners.md +16 -16
- package/scripts/build-hooks.ts +56 -0
- package/scripts/check-doc-links.ts +40 -0
- package/scripts/conformance.ts +23 -16
- package/scripts/coverage-gate.ts +193 -0
- package/scripts/gate-selftest.ts +118 -7
- package/scripts/install.ps1 +5 -4
- package/scripts/install.sh +4 -4
- package/scripts/lane.sh +56 -6
- package/scripts/lib/patterns.sh +89 -0
- package/scripts/policy-force.ts +22 -0
- package/scripts/retrieval-eval.ts +1 -1
- package/scripts/savepoint.sh +249 -67
- package/scripts/validate-content.ts +22 -9
- package/scripts/verify-install.ts +156 -44
- package/src/args.ts +2 -2
- package/src/budget.ts +56 -0
- package/src/cli.ts +272 -4
- package/src/continue.ts +262 -0
- package/src/installer.ts +59 -15
- package/src/integrity.ts +158 -0
- package/src/mission.ts +228 -95
- package/src/policy.ts +156 -0
- package/src/provenance.ts +116 -0
- package/src/rollback.ts +95 -0
- package/src/routing.ts +69 -0
- package/src/run.ts +82 -0
- package/src/sign.ts +61 -0
- package/src/targets/claude.ts +140 -2
- package/src/targets/copilot.ts +20 -1
- package/src/targets/generic.ts +9 -4
- package/src/targets/opencode.ts +0 -1
- package/.opencode/commands/mugiwara-execute.md +0 -13
- package/.opencode/commands/mugiwara-heal.md +0 -13
- package/.opencode/commands/mugiwara-onboard.md +0 -15
- package/.opencode/commands/mugiwara-plan.md +0 -17
- package/.opencode/commands/mugiwara-ship.md +0 -13
- package/.opencode/commands/using-mugiwara.md +0 -20
- package/content/agents/onboarding-guide.md +0 -124
- package/content/skills/mugiwara-agent-security/SKILL.md +0 -58
- package/content/skills/mugiwara-agent-security/references/checklist.md +0 -20
- package/content/skills/mugiwara-context-budget/SKILL.md +0 -64
- package/content/skills/mugiwara-context-budget/references/context-budget.md +0 -37
- package/content/skills/mugiwara-pr/SKILL.md +0 -69
- package/content/skills/mugiwara-pr/references/verdict-format.md +0 -31
- package/content/skills/mugiwara-security/references/owasp-mapping.md +0 -30
- package/content/skills/mugiwara-sunset/SKILL.md +0 -82
- package/content/skills/using-mugiwara/SKILL.md +0 -51
- package/references/token-budget.md +0 -34
- package/scripts/evidence.sh +0 -81
- package/scripts/initiative.ts +0 -296
- package/scripts/mission-report.sh +0 -292
- package/scripts/onboard.ts +0 -266
- package/scripts/probe.ts +0 -40
package/src/rollback.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/rollback.ts
|
|
2
|
+
// Executable rollback map: recovery you can run, not prose.
|
|
3
|
+
// Generated from git + state at closure; the human runs it, mugiwara never does.
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
export type RollbackInput = {
|
|
9
|
+
mission: string;
|
|
10
|
+
branch: string;
|
|
11
|
+
baseSha: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function git(cwd: string, args: string[]): string {
|
|
15
|
+
return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build the rollback script body. Pure given the inputs — the shas are read
|
|
20
|
+
* from the repo by the caller so this stays unit-testable without git.
|
|
21
|
+
*/
|
|
22
|
+
export function buildRollback(input: RollbackInput, commitsNewestFirst: string[], filesTouched: string[]): string {
|
|
23
|
+
const lines = [
|
|
24
|
+
'#!/usr/bin/env bash',
|
|
25
|
+
`# Rollback map for mission "${input.mission}" — generated at closure.`,
|
|
26
|
+
'# Human-executed. Review before running; mugiwara never runs this.',
|
|
27
|
+
`# Branch: ${input.branch}`,
|
|
28
|
+
`# Base: ${input.baseSha}`,
|
|
29
|
+
'',
|
|
30
|
+
'set -euo pipefail',
|
|
31
|
+
'',
|
|
32
|
+
];
|
|
33
|
+
if (!commitsNewestFirst.length && !filesTouched.length) {
|
|
34
|
+
lines.push('# No commits between base and HEAD on this branch — nothing to revert.', '');
|
|
35
|
+
return lines.join('\n');
|
|
36
|
+
}
|
|
37
|
+
if (!commitsNewestFirst.length) {
|
|
38
|
+
// Squash-merge workflows: the branch's changes reached the base ref as one
|
|
39
|
+
// squashed commit, so `rev-list base..branch` can come back empty while
|
|
40
|
+
// `git diff base branch` is not. "Nothing to revert" would be a lie — the
|
|
41
|
+
// changes are live. Emit loud, human-executable guidance and exit 1 so a
|
|
42
|
+
// careless run fails instead of silently doing nothing.
|
|
43
|
+
lines.push(
|
|
44
|
+
'# UNRESOLVED: squash-merged state detected.',
|
|
45
|
+
`# git rev-list ${input.baseSha}..${input.branch} is empty, but the diff`,
|
|
46
|
+
`# ${input.baseSha}..${input.branch} touches ${filesTouched.length} file(s):`,
|
|
47
|
+
'# the mission\'s changes were collapsed into commit(s) already on the base',
|
|
48
|
+
'# ref. No per-commit revert list can be derived automatically.',
|
|
49
|
+
'# Locate the squash commit, review it, then revert it:',
|
|
50
|
+
`# git log --oneline ${input.baseSha}..HEAD --grep="${input.mission}"`,
|
|
51
|
+
'# git revert <squash-commit>',
|
|
52
|
+
'# Files carrying the squashed changes (verify each after reverting):',
|
|
53
|
+
...filesTouched.map((f) => `# ${f}`),
|
|
54
|
+
'',
|
|
55
|
+
'echo "ROLLBACK INCOMPLETE: squash-merged state — locate and revert the squash commit manually" >&2',
|
|
56
|
+
'exit 1',
|
|
57
|
+
'',
|
|
58
|
+
);
|
|
59
|
+
return lines.join('\n');
|
|
60
|
+
}
|
|
61
|
+
lines.push(
|
|
62
|
+
'# Revert newest-first so earlier reverts never conflict with later ones.',
|
|
63
|
+
'git revert --no-edit \\',
|
|
64
|
+
commitsNewestFirst.map((c) => ` ${c}`).join(' \\\n'),
|
|
65
|
+
'',
|
|
66
|
+
);
|
|
67
|
+
if (filesTouched.length) {
|
|
68
|
+
lines.push(
|
|
69
|
+
'# Files this mission touched (verify the working tree is clean afterwards):',
|
|
70
|
+
...filesTouched.map((f) => `# ${f}`),
|
|
71
|
+
'',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Read the repo for everything the script needs, then write rollback.sh. */
|
|
78
|
+
export function generateRollback(projectDir: string, missionDir: string, input: RollbackInput): { file: string; commits: number } | null {
|
|
79
|
+
try {
|
|
80
|
+
const range = `${input.baseSha}..${input.branch}`;
|
|
81
|
+
const revList = git(projectDir, ['rev-list', '--reverse', range]).split(/\r?\n/).filter(Boolean);
|
|
82
|
+
const commitsNewestFirst = [...revList].reverse();
|
|
83
|
+
const filesTouched = input.baseSha === 'unknown'
|
|
84
|
+
? []
|
|
85
|
+
: git(projectDir, ['diff', '--name-only', input.baseSha, input.branch]).split(/\r?\n/).filter(Boolean);
|
|
86
|
+
const body = buildRollback(input, commitsNewestFirst, filesTouched);
|
|
87
|
+
const file = join(missionDir, 'rollback.sh');
|
|
88
|
+
writeFileSync(file, body, { mode: 0o755 });
|
|
89
|
+
return { file: 'rollback.sh', commits: commitsNewestFirst.length };
|
|
90
|
+
} catch {
|
|
91
|
+
// No git, unknown base, or an empty branch — a rollback map cannot be
|
|
92
|
+
// derived. Absent file beats a wrong one.
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/routing.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// src/routing.ts
|
|
2
|
+
// Review routing: tell the reviewer where to look. A ranked
|
|
3
|
+
// reading order computed from what the mission touched — not a flat diff.
|
|
4
|
+
// Heuristic and labeled as such: the ranking decides reading ORDER, never
|
|
5
|
+
// whether a line is correct.
|
|
6
|
+
|
|
7
|
+
export type RankedFile = { path: string; score: number; reasons: string[] };
|
|
8
|
+
|
|
9
|
+
const DOC_PAT = /\.(md|txt|rst)$|^docs\/|^(CHANGELOG|LICENSE|README)/;
|
|
10
|
+
const TEST_PAT = /\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)(tests?|__tests__|specs?)\//;
|
|
11
|
+
|
|
12
|
+
export type RoutingSource = { evidence: string[]; sensitive_paths?: string[] };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Score one path. Sensitive paths dominate; production code next; tests and
|
|
16
|
+
* docs sink. Files the evidence trail never mentions get a bump — absence of
|
|
17
|
+
* evidence is exactly where review attention pays.
|
|
18
|
+
*/
|
|
19
|
+
export function scorePath(path: string, sensitivePaths: string[], evidenceJoined: string): { score: number; reasons: string[] } {
|
|
20
|
+
const reasons: string[] = [];
|
|
21
|
+
let score = 0;
|
|
22
|
+
const sensitiveHit = sensitivePaths.some((s) => s && (path === s || path.startsWith(s.replace(/\/?$/, '/')) || new RegExp(s.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*')).test(path)));
|
|
23
|
+
if (sensitiveHit) {
|
|
24
|
+
score += 100;
|
|
25
|
+
reasons.push('sensitive path');
|
|
26
|
+
}
|
|
27
|
+
if (!DOC_PAT.test(path) && !TEST_PAT.test(path)) {
|
|
28
|
+
score += 50;
|
|
29
|
+
reasons.push('production code');
|
|
30
|
+
} else if (TEST_PAT.test(path)) {
|
|
31
|
+
score += 10;
|
|
32
|
+
reasons.push('test scaffolding — skim unless behavior changed');
|
|
33
|
+
} else {
|
|
34
|
+
score += 5;
|
|
35
|
+
reasons.push('docs/config');
|
|
36
|
+
}
|
|
37
|
+
if (!evidenceJoined.includes(path)) {
|
|
38
|
+
score += 20;
|
|
39
|
+
reasons.push('not covered by recorded evidence');
|
|
40
|
+
}
|
|
41
|
+
return { score, reasons };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function rankFiles(paths: string[], state: RoutingSource): RankedFile[] {
|
|
45
|
+
const sensitive = state.sensitive_paths ?? [];
|
|
46
|
+
const ev = state.evidence.join(' ');
|
|
47
|
+
return paths
|
|
48
|
+
.map((path) => {
|
|
49
|
+
const { score, reasons } = scorePath(path, sensitive, ev);
|
|
50
|
+
return { path, score, reasons };
|
|
51
|
+
})
|
|
52
|
+
.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function renderRouting(ranked: RankedFile[], mission: string): string {
|
|
56
|
+
if (!ranked.length) return '';
|
|
57
|
+
const lines = [
|
|
58
|
+
'',
|
|
59
|
+
'## Review routing',
|
|
60
|
+
'',
|
|
61
|
+
`Ranked reading order for \`${mission}\` (heuristic ordering — it decides where to look first, never correctness):`,
|
|
62
|
+
'',
|
|
63
|
+
];
|
|
64
|
+
ranked.forEach((r, i) => {
|
|
65
|
+
lines.push(`${i + 1}. \`${r.path}\` — ${r.reasons.join('; ')}`);
|
|
66
|
+
});
|
|
67
|
+
lines.push('');
|
|
68
|
+
return lines.join('\n');
|
|
69
|
+
}
|
package/src/run.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/run.ts
|
|
2
|
+
// `mugiwara run <script> [args...]` — runs a bundled harness script against the
|
|
3
|
+
// current project.
|
|
4
|
+
//
|
|
5
|
+
// Why this exists: the skills tell the crew to run `scripts/savepoint.sh`, but
|
|
6
|
+
// the installer only ever copied `content/` and `references/` into a project.
|
|
7
|
+
// That path resolved against the project's cwd, where the file does not exist,
|
|
8
|
+
// so every savepoint / lane call silently did
|
|
9
|
+
// nothing. Resolving from the package root fixes it for every install target at
|
|
10
|
+
// once.
|
|
11
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
12
|
+
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import { dirname, join, resolve } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
// src/ during dev, dist/ once bundled — scripts/ sits beside both
|
|
18
|
+
export const SCRIPTS_DIR = join(here, '..', 'scripts');
|
|
19
|
+
|
|
20
|
+
/** Scripts a project is meant to call. Anything else stays internal tooling. */
|
|
21
|
+
export const RUNNABLE = ['savepoint.sh', 'lane.sh'] as const;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Locate a POSIX shell. Windows has none natively, but Git for Windows ships
|
|
25
|
+
* one and mugiwara is git-backed anyway — savepoint reads git to compute state,
|
|
26
|
+
* so a git-less machine cannot run a mission regardless.
|
|
27
|
+
*/
|
|
28
|
+
export function findBash(): string | null {
|
|
29
|
+
const explicit = process.env.MUGIWARA_BASH?.trim();
|
|
30
|
+
if (explicit) return existsSync(explicit) ? explicit : null;
|
|
31
|
+
const candidates =
|
|
32
|
+
process.platform === 'win32'
|
|
33
|
+
? [
|
|
34
|
+
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
35
|
+
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
36
|
+
join(process.env.LOCALAPPDATA ?? '', 'Programs', 'Git', 'bin', 'bash.exe'),
|
|
37
|
+
]
|
|
38
|
+
: // /bin/bash is not universal on Linux — Alpine and other busybox-based
|
|
39
|
+
// images ship /bin/sh only. Probing beats assuming: an assumed path
|
|
40
|
+
// spawns ENOENT, which surfaces as a stack trace instead of the
|
|
41
|
+
// actionable "no bash found" message below.
|
|
42
|
+
['/bin/bash', '/usr/bin/bash', '/usr/local/bin/bash', '/opt/homebrew/bin/bash'];
|
|
43
|
+
for (const p of candidates) {
|
|
44
|
+
if (p && existsSync(p)) return p;
|
|
45
|
+
}
|
|
46
|
+
// last resort: whatever is on PATH (Git Bash, WSL, MSYS, a nonstandard prefix)
|
|
47
|
+
const probe = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['bash'], {
|
|
48
|
+
encoding: 'utf8',
|
|
49
|
+
});
|
|
50
|
+
const first = probe.stdout?.split(/\r?\n/).find((l) => l.trim());
|
|
51
|
+
return first?.trim() || null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Platform-correct remedy for a missing bash — "install Git for Windows" is wrong advice on Linux. */
|
|
55
|
+
export function noBashMessage(): string {
|
|
56
|
+
const remedy =
|
|
57
|
+
process.platform === 'win32'
|
|
58
|
+
? 'Install Git for Windows (which ships bash)'
|
|
59
|
+
: 'Install bash with your package manager (e.g. `apk add bash`, `apt install bash`)';
|
|
60
|
+
return `no bash found. ${remedy}, or set MUGIWARA_BASH to a bash executable.`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function runScript(name: string, args: string[], projectDir: string): number {
|
|
64
|
+
// no path separators: `run` takes a script name, never an arbitrary path
|
|
65
|
+
if (!/^[a-z0-9-]+\.sh$/.test(name)) {
|
|
66
|
+
throw new Error(`invalid script name "${name}" (expected one of: ${RUNNABLE.join(', ')})`);
|
|
67
|
+
}
|
|
68
|
+
const script = join(SCRIPTS_DIR, name);
|
|
69
|
+
if (!existsSync(script)) {
|
|
70
|
+
const have = existsSync(SCRIPTS_DIR) ? readdirSync(SCRIPTS_DIR).filter((f) => f.endsWith('.sh')) : [];
|
|
71
|
+
throw new Error(`script not found: ${name}${have.length ? ` (available: ${have.join(', ')})` : ''}`);
|
|
72
|
+
}
|
|
73
|
+
const bash = findBash();
|
|
74
|
+
if (!bash) throw new Error(noBashMessage());
|
|
75
|
+
const r = spawnSync(bash, [script, ...args], {
|
|
76
|
+
cwd: resolve(projectDir),
|
|
77
|
+
stdio: 'inherit',
|
|
78
|
+
env: process.env,
|
|
79
|
+
});
|
|
80
|
+
if (r.error) throw r.error;
|
|
81
|
+
return r.status ?? 1;
|
|
82
|
+
}
|
package/src/sign.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/sign.ts
|
|
2
|
+
// Signed attestation: evidence that cannot be fabricated
|
|
3
|
+
// after the fact — optional, user-keyed, never a hard dependency.
|
|
4
|
+
//
|
|
5
|
+
// Uses minisign when it is installed and the user supplies keys. No minisign,
|
|
6
|
+
// no keys → today's behavior, stated plainly in the output. Detached
|
|
7
|
+
// signature lives beside the report (`report.md.minisig`).
|
|
8
|
+
import { execFileSync } from 'node:child_process';
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
export function signArgs(reportPath: string, secretKey: string): string[] {
|
|
14
|
+
return ['-Sm', reportPath, '-s', secretKey];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function verifyArgs(reportPath: string, pubKey: string | null): string[] {
|
|
18
|
+
return pubKey ? ['-Vm', reportPath, '-p', pubKey] : ['-Vm', reportPath];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function hasMinisign(): boolean {
|
|
22
|
+
try {
|
|
23
|
+
execFileSync('minisign', ['-v'], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function defaultKey(flag: 'secret' | 'public'): string {
|
|
31
|
+
return join(homedir(), '.mugiwara', flag === 'secret' ? 'minisign.key' : 'minisign.pub');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function signReport(projectDir: string, missionDir: string): { ok: boolean; message: string } {
|
|
35
|
+
const report = join(missionDir, 'report.md');
|
|
36
|
+
if (!existsSync(report)) return { ok: false, message: 'no report.md to sign — archive first' };
|
|
37
|
+
if (!hasMinisign()) {
|
|
38
|
+
return { ok: false, message: 'minisign not installed — signing skipped (install minisign to enable attestation)' };
|
|
39
|
+
}
|
|
40
|
+
const secretKey = process.env.MUGIWARA_SIGN_KEY?.trim() || defaultKey('secret');
|
|
41
|
+
try {
|
|
42
|
+
execFileSync('minisign', signArgs(report, secretKey), { cwd: projectDir, stdio: 'pipe', input: process.env.MUGIWARA_SIGN_PASSWORD ?? '' });
|
|
43
|
+
return { ok: true, message: `signed ${report}.minisig (key: ${secretKey})` };
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return { ok: false, message: `signing failed: ${(e as Error).message}` };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function verifyReport(projectDir: string, missionDir: string): { ok: boolean; message: string } {
|
|
50
|
+
const report = join(missionDir, 'report.md');
|
|
51
|
+
const sig = `${report}.minisig`;
|
|
52
|
+
if (!existsSync(sig)) return { ok: false, message: 'not signed (no .minisig beside report.md)' };
|
|
53
|
+
if (!hasMinisign()) return { ok: false, message: 'minisign not installed — cannot verify' };
|
|
54
|
+
const pubKey = existsSync(defaultKey('public')) ? defaultKey('public') : null;
|
|
55
|
+
try {
|
|
56
|
+
execFileSync('minisign', verifyArgs(report, pubKey), { cwd: projectDir, stdio: 'pipe' });
|
|
57
|
+
return { ok: true, message: 'signature verifies against report.md' };
|
|
58
|
+
} catch {
|
|
59
|
+
return { ok: false, message: 'SIGNATURE INVALID — report.md changed after signing' };
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/targets/claude.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/targets/claude.ts
|
|
2
|
-
import { existsSync, readFileSync, mkdirSync, copyFileSync, chmodSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, chmodSync, readdirSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { stringifyFrontmatter, type FrontmatterData } from '../frontmatter.ts';
|
|
@@ -19,6 +19,128 @@ function toolsFromScope(scope?: string): string | undefined {
|
|
|
19
19
|
return undefined;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Register the copied hooks in settings.json.
|
|
24
|
+
*
|
|
25
|
+
* Copying hook files into .claude/hooks/ is not enough — Claude Code only runs
|
|
26
|
+
* a hook that settings.json names. Without this the CLI install left three
|
|
27
|
+
* dead files on disk while the plugin install (which reads hooks/hooks.json
|
|
28
|
+
* itself) worked, so the two install paths silently disagreed.
|
|
29
|
+
*
|
|
30
|
+
* Idempotent: an entry whose command already points at our hook is left alone,
|
|
31
|
+
* and unrelated user hooks are never touched.
|
|
32
|
+
*/
|
|
33
|
+
const SCRIPTS_SRC = join(here, '..', '..', 'scripts');
|
|
34
|
+
|
|
35
|
+
/** Copy the harness shell scripts (and their shared lib) next to the hooks. */
|
|
36
|
+
function copyScripts(dstDir: string): string[] {
|
|
37
|
+
if (!existsSync(SCRIPTS_SRC)) return [];
|
|
38
|
+
const written: string[] = [];
|
|
39
|
+
const copyDir = (src: string, dst: string): void => {
|
|
40
|
+
if (!existsSync(src)) return;
|
|
41
|
+
for (const f of readdirSync(src)) {
|
|
42
|
+
// install.sh is the npx bootstrap, not a harness script — never shipped
|
|
43
|
+
// into a project.
|
|
44
|
+
if (!f.endsWith('.sh') || f === 'install.sh') continue;
|
|
45
|
+
const to = join(dst, f);
|
|
46
|
+
mkdirSync(dst, { recursive: true });
|
|
47
|
+
copyFileSync(join(src, f), to);
|
|
48
|
+
chmodSync(to, 0o755);
|
|
49
|
+
written.push(to);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
copyDir(SCRIPTS_SRC, dstDir);
|
|
53
|
+
copyDir(join(SCRIPTS_SRC, 'lib'), join(dstDir, 'lib'));
|
|
54
|
+
return written;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type HookEntry = { type: string; command: string; timeout?: number };
|
|
58
|
+
type HookGroup = { matcher?: string; hooks: HookEntry[] };
|
|
59
|
+
|
|
60
|
+
function wireSettings(root: string, hooksDir: string): { written: string[]; notes: string[] } {
|
|
61
|
+
const file = join(root, 'settings.json');
|
|
62
|
+
const events: Record<string, { file: string; timeout: number; matcher?: string }> = {
|
|
63
|
+
SessionStart: { file: 'session-start.js', timeout: 10 },
|
|
64
|
+
UserPromptSubmit: { file: 'mugiwara-mode-tracker.js', timeout: 5 },
|
|
65
|
+
Stop: { file: 'auto-savepoint.js', timeout: 20 },
|
|
66
|
+
SubagentStop: { file: 'pipeline-guard.js', timeout: 15 },
|
|
67
|
+
// matcher scopes the marker to crew invocations — without it the hook would
|
|
68
|
+
// fire on every tool call in the session for no benefit
|
|
69
|
+
PostToolUse: { file: 'engagement-marker.js', timeout: 5, matcher: 'Task|Skill' },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
let settings: Record<string, unknown> = {};
|
|
73
|
+
if (existsSync(file)) {
|
|
74
|
+
try {
|
|
75
|
+
settings = JSON.parse(readFileSync(file, 'utf8')) as Record<string, unknown>;
|
|
76
|
+
} catch {
|
|
77
|
+
// a hand-broken settings.json is the user's to fix — never overwrite it
|
|
78
|
+
return { written: [], notes: [`${file} is not valid JSON — add the mugiwara hooks manually`] };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const hooks = (settings.hooks ?? {}) as Record<string, HookGroup[]>;
|
|
82
|
+
let changed = false;
|
|
83
|
+
for (const [event, spec] of Object.entries(events)) {
|
|
84
|
+
const command = join(hooksDir, spec.file);
|
|
85
|
+
if (!existsSync(command)) continue;
|
|
86
|
+
const groups = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
87
|
+
const already = groups.some((g) => g.hooks?.some((h) => h.command?.includes(spec.file)));
|
|
88
|
+
if (already) continue;
|
|
89
|
+
const group: HookGroup = { hooks: [{ type: 'command', command: JSON.stringify(command), timeout: spec.timeout }] };
|
|
90
|
+
if (spec.matcher) group.matcher = spec.matcher;
|
|
91
|
+
groups.push(group);
|
|
92
|
+
hooks[event] = groups;
|
|
93
|
+
changed = true;
|
|
94
|
+
}
|
|
95
|
+
if (!changed) return { written: [], notes: [] };
|
|
96
|
+
settings.hooks = hooks;
|
|
97
|
+
writeFileSync(file, JSON.stringify(settings, null, 2) + '\n');
|
|
98
|
+
// NOT reported as `written`: the manifest is an uninstall delete-list, and
|
|
99
|
+
// settings.json belongs to the user — we only merged into it. It is un-merged
|
|
100
|
+
// by unwireSettings instead.
|
|
101
|
+
return { written: [], notes: [`hooks registered in ${file}`] };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Remove only the hook entries mugiwara added, leaving every other setting and
|
|
106
|
+
* any user-authored hook untouched. Identified by their command pointing into
|
|
107
|
+
* our hooks dir.
|
|
108
|
+
*/
|
|
109
|
+
function unwireSettings(root: string): { changed: string[]; notes: string[] } {
|
|
110
|
+
const file = join(root, 'settings.json');
|
|
111
|
+
if (!existsSync(file)) return { changed: [], notes: [] };
|
|
112
|
+
let settings: Record<string, unknown>;
|
|
113
|
+
try {
|
|
114
|
+
settings = JSON.parse(readFileSync(file, 'utf8')) as Record<string, unknown>;
|
|
115
|
+
} catch {
|
|
116
|
+
return { changed: [], notes: [`${file} is not valid JSON — left untouched`] };
|
|
117
|
+
}
|
|
118
|
+
const hooks = settings.hooks as Record<string, HookGroup[]> | undefined;
|
|
119
|
+
if (!hooks) return { changed: [], notes: [] };
|
|
120
|
+
|
|
121
|
+
const ours = new Set(['session-start.js', 'mugiwara-mode-tracker.js', 'auto-savepoint.js', 'pipeline-guard.js', 'engagement-marker.js']);
|
|
122
|
+
const isOurs = (h: HookEntry): boolean =>
|
|
123
|
+
[...ours].some((f) => h.command?.includes(join('hooks', f)) || h.command?.includes(`hooks/${f}`));
|
|
124
|
+
|
|
125
|
+
let changed = false;
|
|
126
|
+
for (const [event, groups] of Object.entries(hooks)) {
|
|
127
|
+
if (!Array.isArray(groups)) continue;
|
|
128
|
+
const kept = groups
|
|
129
|
+
.map((g) => ({ ...g, hooks: (g.hooks ?? []).filter((h) => !isOurs(h)) }))
|
|
130
|
+
.filter((g) => g.hooks.length > 0);
|
|
131
|
+
if (kept.length !== groups.length || kept.some((g, i) => g.hooks.length !== groups[i]?.hooks?.length)) {
|
|
132
|
+
changed = true;
|
|
133
|
+
}
|
|
134
|
+
if (kept.length) hooks[event] = kept;
|
|
135
|
+
else { delete hooks[event]; changed = true; }
|
|
136
|
+
}
|
|
137
|
+
if (!changed) return { changed: [], notes: [] };
|
|
138
|
+
if (!Object.keys(hooks).length) delete settings.hooks;
|
|
139
|
+
// an emptied settings.json still belongs to the user — rewrite, never remove
|
|
140
|
+
writeFileSync(file, JSON.stringify(settings, null, 2) + '\n');
|
|
141
|
+
return { changed: [file], notes: [] };
|
|
142
|
+
}
|
|
143
|
+
|
|
22
144
|
export const target: Target = {
|
|
23
145
|
id: 'claude',
|
|
24
146
|
label: 'Claude Code',
|
|
@@ -55,7 +177,9 @@ export const target: Target = {
|
|
|
55
177
|
if (dryRun) return { written: [], notes: [] };
|
|
56
178
|
if (existsSync(HOOKS_SRC)) {
|
|
57
179
|
for (const f of readdirSync(HOOKS_SRC)) {
|
|
58
|
-
|
|
180
|
+
// .js is what settings.json runs (node-only, no bun needed); .ts ships
|
|
181
|
+
// beside it as the readable source. hooks.json is for plugin installs.
|
|
182
|
+
if (!/\.(ts|js|json)$/.test(f)) continue;
|
|
59
183
|
const dst = join(root, 'hooks', f);
|
|
60
184
|
if (!existsSync(dst)) {
|
|
61
185
|
mkdirSync(dirname(dst), { recursive: true });
|
|
@@ -66,6 +190,15 @@ export const target: Target = {
|
|
|
66
190
|
written.push(dst);
|
|
67
191
|
}
|
|
68
192
|
}
|
|
193
|
+
// The Stop hook shells out to savepoint.sh, resolved beside its own
|
|
194
|
+
// directory. A plugin install already has <plugin>/scripts/; a CLI
|
|
195
|
+
// install needs the shell scripts copied next to the hooks or the hook
|
|
196
|
+
// finds nothing and mission state stays empty — the exact bug this
|
|
197
|
+
// whole path exists to fix.
|
|
198
|
+
written.push(...copyScripts(join(root, 'scripts')));
|
|
199
|
+
const wired = wireSettings(root, join(root, 'hooks'));
|
|
200
|
+
written.push(...wired.written);
|
|
201
|
+
notes.push(...wired.notes);
|
|
69
202
|
}
|
|
70
203
|
// Port the /mugiwara commands into the installed .claude dir.
|
|
71
204
|
if (existsSync(COMMANDS_SRC)) {
|
|
@@ -81,4 +214,9 @@ export const target: Target = {
|
|
|
81
214
|
}
|
|
82
215
|
return { written, notes };
|
|
83
216
|
},
|
|
217
|
+
postUninstall({ scope, projectDir, home, dryRun }) {
|
|
218
|
+
if (dryRun) return { changed: [], notes: [] };
|
|
219
|
+
const root = scope === 'global' ? join(home, '.claude') : join(projectDir, '.claude');
|
|
220
|
+
return unwireSettings(root);
|
|
221
|
+
},
|
|
84
222
|
};
|
package/src/targets/copilot.ts
CHANGED
|
@@ -12,10 +12,29 @@ export const target: Target = {
|
|
|
12
12
|
const root = scope === 'global' ? join(home, '.copilot') : join(projectDir, '.github');
|
|
13
13
|
return { skillsDir: join(root, 'instructions'), agentsDir: join(root, 'agents') };
|
|
14
14
|
},
|
|
15
|
+
// Copilot injects EVERY matching instruction file into EVERY request — so
|
|
16
|
+
// the full corpus (26 files, ~27k tokens) was per-request cost, not
|
|
17
|
+
// per-session. Ship a routing stub here and park the full body in the
|
|
18
|
+
// per-skill refs dir, which the agent reads on demand. applyTo stays '**/*'
|
|
19
|
+
// so the routing line is always present; it is a few lines, not a skill.
|
|
15
20
|
transformSkill(data: FrontmatterData, body: string) {
|
|
21
|
+
void body;
|
|
16
22
|
return {
|
|
17
23
|
relPath: `${data.name}.instructions.md`,
|
|
18
|
-
text: stringifyFrontmatter(
|
|
24
|
+
text: stringifyFrontmatter(
|
|
25
|
+
{ description: data.description, applyTo: '**/*' },
|
|
26
|
+
`# ${data.name}
|
|
27
|
+
|
|
28
|
+
> ${data.description}
|
|
29
|
+
|
|
30
|
+
Full skill: read \`.mugiwara/refs/${data.name}/${data.name}.md\` when the crew invokes this role.`,
|
|
31
|
+
),
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
transformSkillFull(data: FrontmatterData, body: string) {
|
|
35
|
+
return {
|
|
36
|
+
relPath: `${data.name}.md`,
|
|
37
|
+
text: stringifyFrontmatter({ description: data.description }, body),
|
|
19
38
|
};
|
|
20
39
|
},
|
|
21
40
|
transformAgent(data: FrontmatterData, body: string) {
|
package/src/targets/generic.ts
CHANGED
|
@@ -31,7 +31,7 @@ export function makeGeneric(opts: {
|
|
|
31
31
|
// tokens at session start. The agent reads the full file on demand.
|
|
32
32
|
return {
|
|
33
33
|
relPath: `${data.name}.md`,
|
|
34
|
-
text: `# ${data.name}\n\n> ${data.description}\n\n## Skip when\n\nFull skill: \`${data.name}\` — read \`.mugiwara/refs/${data.name}.md\` when the crew invokes this role.`,
|
|
34
|
+
text: `# ${data.name}\n\n> ${data.description}\n\n## Skip when\n\nFull skill: \`${data.name}\` — read \`.mugiwara/refs/${data.name}/${data.name}.md\` when the crew invokes this role.`,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
return { relPath: `${data.name}.md`, text: `# ${data.name}\n\n> ${data.description}\n\n${body}` };
|
|
@@ -44,7 +44,7 @@ export function makeGeneric(opts: {
|
|
|
44
44
|
if (stubOnly) {
|
|
45
45
|
return {
|
|
46
46
|
relPath: `agent-${data.name}.md`,
|
|
47
|
-
text: `# Agent: ${data.name}\n\n> ${data.description}\n\nSkills: ${data.skills ?? ''}. Read \`.mugiwara/refs/${data.name}.md\` when embodying this role.\n\nOnly zoro-execution and brook-healing may modify source code.\nReturn your output to luffy-orchestrator; do not choose the next step.`,
|
|
47
|
+
text: `# Agent: ${data.name}\n\n> ${data.description}\n\nSkills: ${data.skills ?? ''}. Read \`.mugiwara/refs/${data.name}/${data.name}.md\` when embodying this role.\n\nOnly zoro-execution and brook-healing may modify source code.\nReturn your output to luffy-orchestrator; do not choose the next step.`,
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
return { relPath: `agent-${data.name}.md`, text: `# Agent: ${data.name}\n\n> ${data.description}\n\nSkills used: ${data.skills ?? ''}\n\n${body}` };
|
|
@@ -53,10 +53,15 @@ export function makeGeneric(opts: {
|
|
|
53
53
|
if (!stubOnly) return null;
|
|
54
54
|
return { relPath: `${data.name}.md`, text: `# Agent: ${data.name}\n\n> ${data.description}\n\nSkills used: ${data.skills ?? ''}\n\n${body}` };
|
|
55
55
|
},
|
|
56
|
-
refsDir({ projectDir }: { scope: Scope; projectDir: string; home: string }) {
|
|
56
|
+
refsDir({ projectDir }: { scope: Scope; projectDir: string; home: string }, skillName: string) {
|
|
57
57
|
// Keep reference detail outside the rules dir so the harness never
|
|
58
58
|
// glob-loads it; the skill names the path and the agent reads on demand.
|
|
59
|
-
|
|
59
|
+
// Per-skill subdir is load-bearing, not tidiness: a flat refs dir let
|
|
60
|
+
// same-named references collide (agent-security/checklist.md vs
|
|
61
|
+
// frontend/checklist.md, contract-first/process.md vs
|
|
62
|
+
// root-cause/process.md) and first-writer-wins silently served the
|
|
63
|
+
// wrong document to the skill that asked for it.
|
|
64
|
+
return join(projectDir, '.mugiwara', 'refs', skillName);
|
|
60
65
|
},
|
|
61
66
|
postInstall({ projectDir, dryRun }) {
|
|
62
67
|
if (!bootstrapFile) return { written: [], notes: [] };
|
package/src/targets/opencode.ts
CHANGED
|
@@ -33,7 +33,6 @@ const CREW: Record<string, CrewConfig> = {
|
|
|
33
33
|
'eval-runner': { color: '#14b8a6', temperature: 0.2, steps: 15 },
|
|
34
34
|
'resume-coordinator': { color: '#d97706', temperature: 0.2, steps: 10 },
|
|
35
35
|
'memory-keeper': { color: '#d946ef', temperature: 0.2, steps: 8 },
|
|
36
|
-
'onboarding-guide': { color: '#0ea5e9', temperature: 0.3, steps: 15 },
|
|
37
36
|
};
|
|
38
37
|
|
|
39
38
|
// Crew colors from the wave-banners table (single source of truth). Returns
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Execute an approved mugiwara plan as Zoro (execution stage)
|
|
3
|
-
---
|
|
4
|
-
Execute the approved plan as Zoro, inline in the main conversation:
|
|
5
|
-
|
|
6
|
-
1. **Entry protocol first** — read the mission state (`.mugiwara/state/<mission>/[member].json`). No active mission → run Wave 0 triage before anything else. Foreign mission → stop, report owner. Base drift → stop, ask. Non-git → degrade to standard, say so once.
|
|
7
|
-
2. Load the skill: `mugiwara-execution`.
|
|
8
|
-
3. Read the existing plan from `.mugiwara/plans/` — that file is the bridge, never re-plan.
|
|
9
|
-
4. Open a todo list, run tasks sequentially inline; dispatch [PARALLEL] batches to worker subagents only.
|
|
10
|
-
5. Commit per logical task and verify every acceptance criterion with evidence in `.mugiwara/results/`.
|
|
11
|
-
6. **Return the result to Luffy — do not choose the next wave.**
|
|
12
|
-
|
|
13
|
-
See skills/mugiwara-execution for the wave-structured protocol.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Heal earlier-wave failures as Brook (healing stage, after review/security findings)
|
|
3
|
-
---
|
|
4
|
-
Heal failures as Brook, inline in the main conversation:
|
|
5
|
-
|
|
6
|
-
1. **Entry protocol first** — read the mission state (`.mugiwara/state/<mission>/[member].json`). No active mission → run Wave 0 triage before anything else. Foreign mission → stop, report owner. Base drift → stop, ask. Non-git → degrade to standard, say so once. Read `heal_cycle` — at 3, STOP and escalate to the user.
|
|
7
|
-
2. Load the skill: `mugiwara-healing`.
|
|
8
|
-
3. Read the `.mugiwara/issues` ledger first — stop-the-line triage per failure, never fix blind.
|
|
9
|
-
4. Prove-it before fixing: reproduce or verify the failure from `.mugiwara/results/` evidence.
|
|
10
|
-
5. Apply minimal root-cause fixes, update the ledger with evidence.
|
|
11
|
-
6. **Return the result to Luffy — do not choose the next wave.**
|
|
12
|
-
|
|
13
|
-
See skills/mugiwara-healing for the triage protocol.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Run the Mugiwara onboarding wizard — conversation via the onboarding-guide agent, or the terminal wizard (bun scripts/onboard.ts) for CLI users.
|
|
3
|
-
---
|
|
4
|
-
Mugiwara onboard: $ARGUMENTS
|
|
5
|
-
|
|
6
|
-
Run the onboarding wizard. Two paths:
|
|
7
|
-
|
|
8
|
-
1. Conversation (default): the onboarding-guide agent runs the 9-question
|
|
9
|
-
wizard through the host's native question tool (choices + free type),
|
|
10
|
-
then writes .mugiwara/config. No network.
|
|
11
|
-
2. Terminal (CLI users / non-interactive hosts):
|
|
12
|
-
`bun scripts/onboard.ts`
|
|
13
|
-
|
|
14
|
-
The wizard never writes .mugiwara/onboard.json. All 15 crew agents are always
|
|
15
|
-
active — no agent-selection step.
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Plan a mugiwara mission as Nami (planning stage)
|
|
3
|
-
---
|
|
4
|
-
Plan the mission as Nami, inline in the main conversation:
|
|
5
|
-
|
|
6
|
-
1. **Entry protocol first** — read the mission state (`.mugiwara/state/<mission>/[member].json`). No active mission → run Wave 0 triage before anything else. Foreign mission → stop, report owner. Base drift → stop, ask. Non-git → degrade to standard, say so once.
|
|
7
|
-
2. Load the skill: `mugiwara-planning`.
|
|
8
|
-
3. Classify mission size, interview first, scan full context, write the scaled Quick/Standard/Full plan.
|
|
9
|
-
4. Embody the Nami crew role inline — never Task-dispatch.
|
|
10
|
-
5. Persist the plan to `.mugiwara/plans/`; later stages read it from there as the bridge.
|
|
11
|
-
6. **Return the plan to Luffy — do not choose the next wave.**
|
|
12
|
-
|
|
13
|
-
Spec input: read `.mugiwara/spec/YYYY-MM-DD-<mission>.md`. If it is empty or
|
|
14
|
-
missing, write the spec bridge from the user's request first (goal, acceptance
|
|
15
|
-
criteria, constraints) before planning — never plan from an empty spec.
|
|
16
|
-
|
|
17
|
-
See skills/mugiwara-planning for the full template and wave structure.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Close the mission as Luffy (closure + ship stage, at mission end)
|
|
3
|
-
---
|
|
4
|
-
Close the mission as Luffy, inline in the main conversation:
|
|
5
|
-
|
|
6
|
-
1. **Entry protocol first** — read the mission state (`.mugiwara/state/<mission>/[member].json`). No active mission → run Wave 0 triage before anything else. Foreign mission → stop, report owner. Base drift → stop, ask. Non-git → degrade to standard, say so once.
|
|
7
|
-
2. Load the skills: `mugiwara-orchestration` + `mugiwara-ship`.
|
|
8
|
-
3. Run the ship gate: pre-launch checklist, feature flags, staged rollout, mandatory rollback plan — binary GO/NO-GO with evidence.
|
|
9
|
-
4. Bridge on `.mugiwara/plans/` (promise) vs `.mugiwara/results/` (evidence) for the Definition-of-Done verdict.
|
|
10
|
-
5. Close the mission: lessons ledger update, then the terminal step — save-point commit, push the branch, write the PR verdict, hand to the user.
|
|
11
|
-
6. **The crew never creates a PR, never merges, never deploys — hand the branch + verdict to the user.**
|
|
12
|
-
|
|
13
|
-
See skills/mugiwara-ship for the gate and skills/mugiwara-orchestration for closure.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Mugiwara crew reference — how it works, crew overview, pipeline summary. Documentation only.
|
|
3
|
-
---
|
|
4
|
-
Mugiwara Reference: $ARGUMENTS
|
|
5
|
-
|
|
6
|
-
`mugiwara-orchestration` auto-loads as gatekeeper for every task — no need to call this command.
|
|
7
|
-
|
|
8
|
-
## How it works
|
|
9
|
-
|
|
10
|
-
- 11 agents: Luffy (triage), Usopp (brainstorm), Nami (plan), Zoro (execute), Chopper (audit), Sanji (quality), Franky (gates), Robin (review), Jinbe (security), Brook (heal), Resume (+3 internal: Skeptic, Eval Runner, Memory)
|
|
11
|
-
- 26 skills — one per crew role + domain skills (frontend, backend, git, security)
|
|
12
|
-
- 9-wave pipeline runs inline in the main conversation
|
|
13
|
-
- Evidence over claims at every wave
|
|
14
|
-
- Autonomy modes: guided, semi, auto
|
|
15
|
-
|
|
16
|
-
## When to use this command
|
|
17
|
-
|
|
18
|
-
Use `/using-mugiwara` when you want the crew overview or pipeline summary. For task routing and classification, `mugiwara-orchestration` auto-loads as gatekeeper — you do not need to call this first.
|
|
19
|
-
|
|
20
|
-
See skills/mugiwara-workflow for the full pipeline.
|