@ionivetech/mugiwara 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -3
- package/.codex-plugin/plugin.json +48 -2
- package/.cursor-plugin/plugin.json +48 -2
- package/.kimi-plugin/plugin.json +48 -2
- package/.opencode/commands/mugiwara-continue.md +44 -31
- package/.opencode/commands/mugiwara-execute.md +1 -1
- package/.opencode/commands/mugiwara-heal.md +1 -1
- package/.opencode/commands/mugiwara-onboard.md +8 -9
- package/.opencode/commands/mugiwara-plan.md +1 -1
- package/.opencode/commands/mugiwara-review.md +1 -1
- package/.opencode/commands/mugiwara-security.md +1 -1
- package/.opencode/commands/mugiwara-ship.md +1 -1
- package/.opencode/commands/mugiwara.md +2 -2
- package/.opencode/mugiwara-helpers.mjs +3 -0
- package/.opencode/plugins/mugiwara.mjs +1 -1
- package/AGENTS.md +12 -2
- package/README.md +53 -32
- package/content/agents/brook-healing.md +5 -8
- package/content/agents/chopper-checkpoint.md +8 -11
- package/content/agents/eval-runner.md +2 -5
- package/content/agents/franky-gates.md +4 -7
- package/content/agents/jinbe-security.md +4 -7
- package/content/agents/luffy-orchestrator.md +13 -13
- package/content/agents/memory-keeper.md +5 -8
- package/content/agents/nami-planner.md +4 -7
- package/content/agents/resume-coordinator.md +7 -10
- package/content/agents/robin-reviewer.md +6 -9
- package/content/agents/sanji-quality.md +7 -10
- package/content/agents/skeptic-verifier.md +5 -8
- package/content/agents/usopp-brainstorm.md +3 -6
- package/content/agents/zoro-execution.md +7 -10
- package/content/skills/mugiwara-backend/SKILL.md +2 -0
- package/content/skills/mugiwara-checkpoint/SKILL.md +14 -11
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +4 -4
- package/content/skills/mugiwara-context-budget/SKILL.md +5 -3
- package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
- package/content/skills/mugiwara-execution/SKILL.md +27 -27
- package/content/skills/mugiwara-execution/references/dispatch.md +2 -2
- package/content/skills/mugiwara-execution/references/resume-batching.md +1 -1
- package/content/skills/mugiwara-frontend/SKILL.md +2 -4
- package/content/skills/mugiwara-gates/SKILL.md +9 -7
- package/content/skills/mugiwara-git/SKILL.md +7 -2
- package/content/skills/mugiwara-healing/SKILL.md +6 -4
- package/content/skills/mugiwara-healing/references/workers.md +2 -2
- package/content/skills/mugiwara-lessons/SKILL.md +1 -0
- package/content/skills/mugiwara-orchestration/SKILL.md +34 -35
- package/content/skills/mugiwara-orchestration/references/check-ins.md +17 -16
- package/content/skills/mugiwara-orchestration/references/closure.md +5 -5
- package/content/skills/mugiwara-orchestration/references/output-contract.md +77 -0
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +22 -22
- package/content/skills/mugiwara-planning/SKILL.md +3 -1
- package/content/skills/mugiwara-planning/references/plan-template.md +2 -2
- package/content/skills/mugiwara-pr/SKILL.md +6 -6
- package/content/skills/mugiwara-pr/references/verdict-format.md +3 -3
- package/content/skills/mugiwara-quality/SKILL.md +8 -7
- package/content/skills/mugiwara-resume/SKILL.md +19 -20
- package/content/skills/mugiwara-review/SKILL.md +6 -1
- 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 +5 -1
- package/content/skills/mugiwara-ship/SKILL.md +1 -1
- package/content/skills/mugiwara-ship/references/cleanup.md +1 -1
- package/content/skills/mugiwara-testcases/SKILL.md +5 -3
- package/content/skills/mugiwara-workflow/SKILL.md +38 -39
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +15 -15
- package/content/skills/using-mugiwara/SKILL.md +5 -5
- package/dist/mugiwara.js +807 -31
- package/gemini-extension.json +1 -1
- package/hooks/auto-savepoint.js +106 -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 +182 -0
- package/hooks/pipeline-guard.ts +267 -0
- package/hooks/session-start.js +106 -0
- package/hooks/session-start.ts +44 -32
- package/package.json +7 -3
- package/plugin.json +1 -1
- package/references/agent-protocol.md +15 -0
- package/references/complexity.md +54 -0
- package/references/definition-of-done.md +1 -1
- package/references/multi-actor.md +5 -5
- package/references/skill-versioning.md +1 -1
- package/references/token-budget.md +30 -8
- package/references/wave-banners.md +21 -31
- package/scripts/build-hooks.ts +56 -0
- package/scripts/conformance.ts +232 -0
- package/scripts/coverage-gate.ts +179 -0
- package/scripts/evidence.sh +17 -4
- package/scripts/gate-selftest.ts +14 -0
- package/scripts/initiative.ts +51 -17
- package/scripts/install.ps1 +5 -4
- package/scripts/install.sh +4 -4
- package/scripts/lane.sh +6 -2
- package/scripts/lib/patterns.sh +84 -0
- package/scripts/mission-report.sh +12 -11
- package/scripts/savepoint.sh +152 -37
- package/scripts/validate-content.ts +22 -2
- package/scripts/verify-install.ts +154 -44
- package/src/args.ts +1 -1
- package/src/cli.ts +217 -0
- package/src/continue.ts +243 -0
- package/src/installer.ts +39 -0
- package/src/onboard.ts +207 -0
- package/src/run.ts +82 -0
- package/src/targets/claude.ts +140 -2
- package/src/targets/copilot.ts +20 -1
- package/src/targets/generic.ts +9 -4
- package/content/agents/onboarding-guide.md +0 -124
- package/content/skills/mugiwara-security/references/owasp-mapping.md +0 -30
- package/scripts/onboard.ts +0 -266
- package/scripts/probe.ts +0 -40
|
@@ -1,61 +1,72 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
// scripts/verify-install.ts — G1:
|
|
3
|
-
//
|
|
2
|
+
// scripts/verify-install.ts — G1: prove the shipped prose does not lie.
|
|
3
|
+
//
|
|
4
|
+
// Three checks, all of which failed silently before:
|
|
5
|
+
// A. pointer resolution — every `references/*.md` pointer resolves after install
|
|
6
|
+
// B. prose path validity — every `.mugiwara/…`, `scripts/…`, `docs/…` path a
|
|
7
|
+
// skill or agent NAMES either exists in the repo or matches a declared
|
|
8
|
+
// runtime shape. Four BLOCKER/MAJOR findings shared one root cause:
|
|
9
|
+
// nothing verified that a path named in prose actually exists.
|
|
10
|
+
// C. reference reachability — every reference file has an inbound pointer.
|
|
11
|
+
// Unreachable files still install into every project.
|
|
4
12
|
|
|
5
13
|
import { existsSync, mkdtempSync, rmSync, readFileSync, readdirSync } from 'node:fs';
|
|
6
|
-
import { join, dirname } from 'node:path';
|
|
14
|
+
import { join, dirname, basename, relative } from 'node:path';
|
|
7
15
|
import { tmpdir } from 'node:os';
|
|
8
16
|
import { installTo } from '../src/installer.ts';
|
|
9
|
-
import { targets } from '../src/targets/index.ts';
|
|
17
|
+
import { targets, TARGET_IDS } from '../src/targets/index.ts';
|
|
10
18
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
const repoRoot = join(import.meta.dirname, '..');
|
|
20
|
+
const fail: string[] = [];
|
|
21
|
+
|
|
22
|
+
function findMd(root: string, out: string[] = []): string[] {
|
|
23
|
+
if (!existsSync(root)) return out;
|
|
24
|
+
for (const ent of readdirSync(root, { withFileTypes: true })) {
|
|
25
|
+
const p = join(root, ent.name);
|
|
26
|
+
if (ent.isDirectory()) findMd(p, out);
|
|
27
|
+
else if (ent.name.endsWith('.md')) out.push(p);
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
16
31
|
|
|
17
|
-
|
|
18
|
-
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// A. pointer resolution, across EVERY install target (was 3 of 9)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
let pointers = 0;
|
|
36
|
+
let brokenPointers = 0;
|
|
19
37
|
|
|
20
|
-
for (const
|
|
21
|
-
const target = targets[
|
|
22
|
-
|
|
23
|
-
const dir = mkdtempSync(join(tmpdir(), `mugi-verify-${tt.id}-`));
|
|
38
|
+
for (const id of TARGET_IDS) {
|
|
39
|
+
const target = targets[id];
|
|
40
|
+
const dir = mkdtempSync(join(tmpdir(), `mugi-verify-${id}-`));
|
|
24
41
|
try {
|
|
25
42
|
installTo(target, { scope: 'project', projectDir: dir, dryRun: false, force: true });
|
|
26
43
|
|
|
27
44
|
const skillsDir = target.paths({ scope: 'project', projectDir: dir, home: '' }).skillsDir;
|
|
28
45
|
const mugiwaraRefsDir = join(dir, '.mugiwara', 'refs');
|
|
29
46
|
|
|
30
|
-
|
|
31
|
-
const out: string[] = [];
|
|
32
|
-
if (!existsSync(root)) return out;
|
|
33
|
-
for (const ent of readdirSync(root, { withFileTypes: true })) {
|
|
34
|
-
const p = join(root, ent.name);
|
|
35
|
-
if (ent.isDirectory()) out.push(...findSkillFiles(p));
|
|
36
|
-
else if (ent.name.endsWith('.md')) out.push(p);
|
|
37
|
-
}
|
|
38
|
-
return out;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const skillFiles = findSkillFiles(skillsDir)
|
|
42
|
-
.filter(f => !f.replace(skillsDir, '').includes('/references/'));
|
|
47
|
+
const skillFiles = findMd(skillsDir).filter((f) => !f.replace(skillsDir, '').includes('/references/'));
|
|
43
48
|
|
|
44
49
|
for (const file of skillFiles) {
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
// Targets emit skills either as `<skillsDir>/<name>/SKILL.md` (claude) or
|
|
51
|
+
// as a flat `<skillsDir>/<name>.instructions.md` (copilot). Both forms
|
|
52
|
+
// must yield the skill name, because non-native targets store references
|
|
53
|
+
// per-skill under `.mugiwara/refs/<name>/`.
|
|
54
|
+
const relSkill = relative(skillsDir, file);
|
|
55
|
+
const skillName = relSkill.includes('/') ? relSkill.split('/')[0] : basename(relSkill).split('.')[0];
|
|
47
56
|
|
|
48
|
-
for (const m of
|
|
49
|
-
|
|
57
|
+
for (const m of readFileSync(file, 'utf8').matchAll(/`([^`]*references\/[^`]+\.md)`/g)) {
|
|
58
|
+
pointers++;
|
|
50
59
|
const pointer = m[1];
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
const candidates = [
|
|
61
|
+
join(dirname(file), pointer),
|
|
62
|
+
join(mugiwaraRefsDir, pointer.replace(/^(?:_shared\/)?references\//, '')),
|
|
63
|
+
...(target.refsDir
|
|
64
|
+
? [join(target.refsDir({ scope: 'project', projectDir: dir, home: '' }, skillName), basename(pointer))]
|
|
65
|
+
: []),
|
|
66
|
+
];
|
|
67
|
+
if (!candidates.some((p) => existsSync(p))) {
|
|
68
|
+
brokenPointers++;
|
|
69
|
+
fail.push(`✗ [${id}] ${file.replace(dir + '/', '')}: \`${pointer}\` → not found`);
|
|
59
70
|
}
|
|
60
71
|
}
|
|
61
72
|
}
|
|
@@ -64,9 +75,108 @@ for (const tt of tierTargets) {
|
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// B. prose path validity
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// `.mugiwara/**` paths are written at RUNTIME — they never exist in a clone
|
|
82
|
+
// (`.mugiwara/` is gitignored), so existence proves nothing. What can be
|
|
83
|
+
// verified is the SHAPE: prose must name the same filename template the
|
|
84
|
+
// scripts actually write. This is the check that would have caught
|
|
85
|
+
// savepoint.sh globbing `issues/<mission>-blockers.md` while 15 prose sites
|
|
86
|
+
// mandated the dated `issues/YYYY-MM-DD-<mission>-blockers.md`.
|
|
87
|
+
const RUNTIME_SHAPES: RegExp[] = [
|
|
88
|
+
/^\.mugiwara\/?(\*\*)?$/, // bare mention of the dir
|
|
89
|
+
/^~?\/?\.mugiwara\/config$/, // project + global config
|
|
90
|
+
/^\.mugiwara\/(state|continue)\/<mission>\/(\[member\]|state|<member>)\.json$/,
|
|
91
|
+
/^\.mugiwara\/issues\/(YYYY-MM-DD-<mission>-blockers\.md)?$/,
|
|
92
|
+
/^\.mugiwara\/logs\/(YYYY-MM-DD-<mission>\.md|lessons\.md)?$/,
|
|
93
|
+
/^\.mugiwara\/(plans|spec|reports)\/(YYYY-MM-DD-<mission>\.md)?$/,
|
|
94
|
+
/^\.mugiwara\/review\/(YYYY-MM-DD-<mission>-(review|security|verifier)\.md)?$/,
|
|
95
|
+
/^\.mugiwara\/results\/<mission>\/([0-9]{2}-[a-z-]+|eval|resume|todos)\.md$/,
|
|
96
|
+
/^\.mugiwara\/results\/?$/,
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
// Shape violations that exist today and are NOT fixed in this mission because
|
|
100
|
+
// another worker holds `content/**` prose this wave. Ratchet: a NEW violation
|
|
101
|
+
// fails the gate; these two are reported, then must be deleted from this list.
|
|
102
|
+
// Prose paths whose SHAPE is known-wrong but not yet fixed. Reported every run
|
|
103
|
+
// so they cannot rot quietly; emptied as each is repaired. Keep this empty —
|
|
104
|
+
// an entry here is a defect with a due date, never a permanent exemption.
|
|
105
|
+
const KNOWN_SHAPE_DRIFT = new Map<string, string>([]);
|
|
106
|
+
|
|
107
|
+
/** `[label](path)`, `bun scripts/x.ts --flag`, trailing punctuation → the bare path. */
|
|
108
|
+
function normalizePath(raw: string): string {
|
|
109
|
+
let s = raw.trim();
|
|
110
|
+
const link = s.match(/^\[[^\]]*\]\(([^)]+)\)$/);
|
|
111
|
+
if (link) s = link[1];
|
|
112
|
+
s = s.replace(/^(?:bun|bash|sh|npx|node)\s+/, '');
|
|
113
|
+
s = s.split(/\s/)[0];
|
|
114
|
+
return s.replace(/[.,;:]$/, '');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const PROSE_PATH = /`([^`\n]*?(?:\.mugiwara\/|scripts\/|docs\/)[^`\n]*?)`/g;
|
|
118
|
+
const proseFiles = [...findMd(join(repoRoot, 'content')), ...findMd(join(repoRoot, 'references'))];
|
|
119
|
+
|
|
120
|
+
let prosePaths = 0;
|
|
121
|
+
const drift: string[] = [];
|
|
122
|
+
for (const file of proseFiles) {
|
|
123
|
+
const rel = relative(repoRoot, file);
|
|
124
|
+
readFileSync(file, 'utf8').split('\n').forEach((line, i) => {
|
|
125
|
+
for (const m of line.matchAll(PROSE_PATH)) {
|
|
126
|
+
const p = normalizePath(m[1]);
|
|
127
|
+
if (!p.includes('/')) continue;
|
|
128
|
+
prosePaths++;
|
|
129
|
+
const at = `${rel}:${i + 1}`;
|
|
130
|
+
|
|
131
|
+
if (/(^|\/)\.mugiwara\//.test(p) || p === '.mugiwara/' || p === '.mugiwara/**') {
|
|
132
|
+
if (RUNTIME_SHAPES.some((re) => re.test(p))) continue;
|
|
133
|
+
if (KNOWN_SHAPE_DRIFT.has(p)) { drift.push(` ${at}: \`${p}\` — ${KNOWN_SHAPE_DRIFT.get(p)}`); continue; }
|
|
134
|
+
fail.push(`✗ ${at}: \`${p}\` → not a declared runtime path shape`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// scripts/ and docs/ are SHIPPED files — they must exist in the repo
|
|
138
|
+
if (!existsSync(join(repoRoot, p))) fail.push(`✗ ${at}: \`${p}\` → file does not exist`);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// C. reference reachability (file → pointer, the inverse of check A)
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Hard zero. The 18 orphans this ratchet was holding were all substantive —
|
|
147
|
+
// worksheets, rubrics and worked examples their own skill visibly needed — so
|
|
148
|
+
// every one was wired to its parent section rather than deleted. With the
|
|
149
|
+
// backlog at zero the ratchet becomes what it should always have been: a new
|
|
150
|
+
// reference file must be pointed at by the prose that needs it, or it is not
|
|
151
|
+
// a reference, it is dead weight shipped to every project.
|
|
152
|
+
const ORPHAN_BASELINE = 0;
|
|
153
|
+
|
|
154
|
+
const corpus = proseFiles.map((f) => ({ file: f, body: readFileSync(f, 'utf8') }));
|
|
155
|
+
const refFiles = proseFiles.filter((f) => f.includes('/references/') || relative(repoRoot, f).startsWith('references/'));
|
|
156
|
+
const orphans: string[] = [];
|
|
157
|
+
for (const rf of refFiles) {
|
|
158
|
+
const needle = `references/${basename(rf)}`;
|
|
159
|
+
const reachable = corpus.some((c) => c.file !== rf && c.body.includes(needle));
|
|
160
|
+
if (!reachable) orphans.push(relative(repoRoot, rf));
|
|
161
|
+
}
|
|
162
|
+
if (orphans.length > ORPHAN_BASELINE) {
|
|
163
|
+
fail.push(
|
|
164
|
+
`✗ ${orphans.length} unreachable reference files (baseline ${ORPHAN_BASELINE}) — a new orphan was added:\n` +
|
|
165
|
+
orphans.map((o) => ` ${o}`).join('\n'),
|
|
166
|
+
);
|
|
70
167
|
}
|
|
71
168
|
|
|
72
|
-
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
console.log(` ${pointers} pointers checked across ${TARGET_IDS.length} targets`);
|
|
171
|
+
console.log(` ${prosePaths} prose paths checked in ${proseFiles.length} files`);
|
|
172
|
+
console.log(` ${orphans.length}/${refFiles.length} reference files unreachable (baseline ${ORPHAN_BASELINE})`);
|
|
173
|
+
if (orphans.length) orphans.forEach((o) => console.log(` orphan: ${o}`));
|
|
174
|
+
if (drift.length) { console.log(' known prose-path drift (owned elsewhere, must be fixed):'); drift.forEach((d) => console.log(d)); }
|
|
175
|
+
|
|
176
|
+
if (fail.length) {
|
|
177
|
+
console.log('');
|
|
178
|
+
fail.forEach((f) => console.log(f));
|
|
179
|
+
console.log(`\n✗ verify-install: ${fail.length} problem(s) (${brokenPointers} broken pointers)`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
console.log('✓ verify-install: pointers resolve, prose paths valid, no new orphans');
|
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', '--check': 'check',
|
|
12
|
+
'--dry-run': 'dryRun', '--keep-logs': 'keepLogs', '--check': 'check', '--all': 'all',
|
|
13
13
|
'--help': 'help', '-h': 'help', '--version': 'version', '-v': 'version',
|
|
14
14
|
};
|
|
15
15
|
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// src/cli.ts
|
|
3
3
|
import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
4
5
|
import { homedir } from 'node:os';
|
|
5
6
|
import { dirname, join, resolve } from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
@@ -10,6 +11,9 @@ import { targets, TARGET_IDS } from './targets/index.ts';
|
|
|
10
11
|
import { installTo, removeInstalled, VERSION, ensureProjectGitignore, removeProjectGitignore } from './installer.ts';
|
|
11
12
|
import { manifestPath, readManifest, writeManifest, type Scope } from './manifest.ts';
|
|
12
13
|
import { resetMission, archiveMission } from './mission.ts';
|
|
14
|
+
import { runOnboard } from './onboard.ts';
|
|
15
|
+
import { runScript, RUNNABLE } from './run.ts';
|
|
16
|
+
import { readContinue, readState, resolveContinue, formatTable, formatResume, gitActor } from './continue.ts';
|
|
13
17
|
|
|
14
18
|
const str = (v: FlagValue): string | undefined => (typeof v === 'string' ? v : undefined);
|
|
15
19
|
const flag = (v: FlagValue): boolean => v === true;
|
|
@@ -25,6 +29,13 @@ export async function run(argv: string[]): Promise<void> {
|
|
|
25
29
|
case 'list': return list(flags);
|
|
26
30
|
case 'reset': return resetCmd(flags);
|
|
27
31
|
case 'archive': return archive(flags, _);
|
|
32
|
+
case 'onboard': return onboardCmd(flags);
|
|
33
|
+
case 'continue': return continueCmd(flags, _);
|
|
34
|
+
case 'status': return statusCmd(flags);
|
|
35
|
+
case 'run': return runCmd(flags, _);
|
|
36
|
+
case 'savepoint': return runCmd(flags, ['run', 'savepoint.sh', ..._.slice(1)]);
|
|
37
|
+
case 'initiative': return initiativeCmd(flags, _);
|
|
38
|
+
case 'start': return startCmd(flags, _);
|
|
28
39
|
default: throw new Error(`Unknown command: ${command}`);
|
|
29
40
|
}
|
|
30
41
|
}
|
|
@@ -53,6 +64,12 @@ function archive(flags: Args['flags'], positionals: string[]): void {
|
|
|
53
64
|
if (result.index) console.log(`index updated: ${result.index}`);
|
|
54
65
|
}
|
|
55
66
|
|
|
67
|
+
async function onboardCmd(flags: Args['flags']): Promise<void> {
|
|
68
|
+
const projectDir = resolve(str(flags.project) ?? process.cwd());
|
|
69
|
+
if (!existsSync(projectDir)) throw new Error(`Project dir not found: ${projectDir}`);
|
|
70
|
+
await runOnboard(projectDir);
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
async function resolveOptions(flags: Args['flags']): Promise<{ scope: Scope; projectDir: string; targetIds: string[] }> {
|
|
57
74
|
const interactive = !flag(flags.yes);
|
|
58
75
|
if (interactive && !process.stdin.isTTY) {
|
|
@@ -123,6 +140,22 @@ async function install(flags: Args['flags']): Promise<void> {
|
|
|
123
140
|
});
|
|
124
141
|
console.log(`\nOK mugiwara ${VERSION} installed (manifest: ${file})`);
|
|
125
142
|
if (allNotes.length) console.log(`${allNotes.length} note(s) above may need attention.`);
|
|
143
|
+
// TASK 8: name exactly one next step, and state the active mode + enforce
|
|
144
|
+
// value since both change behaviour.
|
|
145
|
+
if (scope === 'project') {
|
|
146
|
+
const cfg = join(projectDir, '.mugiwara', 'config');
|
|
147
|
+
let mode = 'guided', enforce = 'block';
|
|
148
|
+
if (existsSync(cfg)) {
|
|
149
|
+
for (const line of readFileSync(cfg, 'utf8').split(/\r?\n/)) {
|
|
150
|
+
const [k, v] = line.split('=').map((s) => s.trim());
|
|
151
|
+
if (k === 'mode') mode = v;
|
|
152
|
+
if (k === 'enforce') enforce = v;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
console.log(`\nNext: run \`mugiwara onboard\` to customise (mode=${mode}, enforce=${enforce} now).`);
|
|
156
|
+
} else {
|
|
157
|
+
console.log('\nNext: run `mugiwara onboard` in a project to write its .mugiwara/config.');
|
|
158
|
+
}
|
|
126
159
|
}
|
|
127
160
|
|
|
128
161
|
async function uninstall(flags: Args['flags']): Promise<void> {
|
|
@@ -150,6 +183,16 @@ async function uninstall(flags: Args['flags']): Promise<void> {
|
|
|
150
183
|
if (!ok) { console.log('Aborted.'); return; }
|
|
151
184
|
}
|
|
152
185
|
const removed = removeInstalled(manifest, { dryRun: flag(flags.dryRun) });
|
|
186
|
+
// Un-merge anything we injected into files the user owns. These are
|
|
187
|
+
// deliberately absent from the manifest — deleting them would destroy the
|
|
188
|
+
// user's own configuration alongside ours.
|
|
189
|
+
for (const id of manifest.targets) {
|
|
190
|
+
const t = targets[id];
|
|
191
|
+
if (!t?.postUninstall) continue;
|
|
192
|
+
const post = t.postUninstall({ scope, projectDir, home, dryRun: flag(flags.dryRun) });
|
|
193
|
+
for (const f of post.changed) console.log(` unwired mugiwara hooks from ${f}`);
|
|
194
|
+
for (const n of post.notes) console.log(` note: ${n}`);
|
|
195
|
+
}
|
|
153
196
|
if (!flag(flags.dryRun)) {
|
|
154
197
|
if (scope === 'project') {
|
|
155
198
|
const gi = removeProjectGitignore(projectDir);
|
|
@@ -192,6 +235,166 @@ function list(flags: Args['flags']): void {
|
|
|
192
235
|
if (!found) console.log('No mugiwara installation found.');
|
|
193
236
|
}
|
|
194
237
|
|
|
238
|
+
/**
|
|
239
|
+
* `mugiwara continue [mission] [member]` — the deterministic half of resume.
|
|
240
|
+
*
|
|
241
|
+
* Selecting which mission/member to resume is a directory scan, not a judgement
|
|
242
|
+
* call, so it runs here instead of costing the host model a reasoning turn.
|
|
243
|
+
* Only the last step (verifying next_action against the plan) needs a model,
|
|
244
|
+
* and that happens after this prints.
|
|
245
|
+
*
|
|
246
|
+
* Exit codes: 0 = a single resume point was printed; 2 = ambiguous or absent,
|
|
247
|
+
* the caller must stop and let the user pick.
|
|
248
|
+
*/
|
|
249
|
+
function continueCmd(flags: Args['flags'], positionals: string[]): void {
|
|
250
|
+
const projectDir = resolve(str(flags.project) ?? process.cwd());
|
|
251
|
+
const [mission, member] = positionals.slice(1);
|
|
252
|
+
let entries = readContinue(projectDir);
|
|
253
|
+
|
|
254
|
+
// default to this actor's work; --all crosses actors on a shared checkout
|
|
255
|
+
if (!flag(flags.all)) {
|
|
256
|
+
const actor = gitActor(projectDir);
|
|
257
|
+
const mine = entries.filter((e) => e.actor === actor);
|
|
258
|
+
// an actor-less savepoint (older file, or git identity unset) is still the
|
|
259
|
+
// only thing on disk — showing nothing would look like "no missions"
|
|
260
|
+
if (mine.length) entries = mine;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const r = resolveContinue(entries, mission, member);
|
|
264
|
+
if (r.kind === 'resume') { console.log(formatResume(r.entry)); return; }
|
|
265
|
+
|
|
266
|
+
if (r.kind === 'none') {
|
|
267
|
+
console.log('No mission in flight. Start one with Flow 0 triage (mugiwara-orchestration).');
|
|
268
|
+
} else if (r.kind === 'missions') {
|
|
269
|
+
console.log(`${new Set(r.entries.map((e) => e.mission)).size} missions in flight:\n`);
|
|
270
|
+
console.log(formatTable(r.entries));
|
|
271
|
+
console.log('\nPick one: mugiwara continue <mission> [member]');
|
|
272
|
+
} else if (r.kind === 'members') {
|
|
273
|
+
console.log(`Mission "${r.mission}" has ${r.entries.length} members in flight:\n`);
|
|
274
|
+
console.log(formatTable(r.entries));
|
|
275
|
+
console.log(`\nPick one: mugiwara continue ${r.mission} <member>`);
|
|
276
|
+
} else if (r.kind === 'unknown-mission') {
|
|
277
|
+
console.error(`No in-flight mission "${r.mission}". Known: ${r.known.join(', ') || '(none)'}`);
|
|
278
|
+
} else {
|
|
279
|
+
console.error(`Mission "${r.mission}" has no member "${r.member}". Known: ${r.known.join(', ')}`);
|
|
280
|
+
}
|
|
281
|
+
process.exit(2);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** `mugiwara status` — one screen of computed mission state, no model needed. */
|
|
285
|
+
function statusCmd(flags: Args['flags']): void {
|
|
286
|
+
const projectDir = resolve(str(flags.project) ?? process.cwd());
|
|
287
|
+
const states = readState(projectDir);
|
|
288
|
+
if (!states.length) { console.log('No mission state on disk.'); return; }
|
|
289
|
+
const actor = flag(flags.all) ? null : gitActor(projectDir);
|
|
290
|
+
const rows = actor ? (states.filter((s) => s.actor === actor).length ? states.filter((s) => s.actor === actor) : states) : states;
|
|
291
|
+
for (const s of rows) {
|
|
292
|
+
const scope = s.member ? ` [${s.member}]` : '';
|
|
293
|
+
console.log(`${s.mission}${scope}`);
|
|
294
|
+
console.log(` flow ${s.flow} · ${s.tasks_done}/${s.tasks_total} tasks · lane ${s.lane}${s.lane_rose ? ' ⬆ ROSE' : ''}${s.lane_reason ? ` (${s.lane_reason})` : ''} · mode ${s.mode}`);
|
|
295
|
+
console.log(` blockers ${s.blockers_open} · heal cycle ${s.heal_cycle}/${s.heal_max_cycles}${s.heal_halt ? ' — HALT' : ''} · files touched ${s.files_touched}`);
|
|
296
|
+
if (s.budget) console.log(` tokens ${s.tokens_est}/${s.budget} (${s.budget_status})${s.delegate_due ? ' · delegate due' : ''}`);
|
|
297
|
+
console.log(` branch ${s.branch} · updated ${s.updated_at}`);
|
|
298
|
+
if (s.evidence.length) console.log(` evidence: ${s.evidence.join(', ')}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** `mugiwara run <script.sh> [args]` — run a bundled harness script here. */
|
|
303
|
+
function runCmd(flags: Args['flags'], positionals: string[]): void {
|
|
304
|
+
const projectDir = resolve(str(flags.project) ?? process.cwd());
|
|
305
|
+
const name = positionals[1];
|
|
306
|
+
if (!name) {
|
|
307
|
+
console.error(`usage: mugiwara run <script> [args...]\n scripts: ${RUNNABLE.join(', ')}`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
const code = runScript(name, positionals.slice(2), projectDir);
|
|
311
|
+
if (code !== 0) process.exit(code);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** `mugiwara initiative <status|conflict-check|set-status> <plan> [args]` — team sub-mission coordination. */
|
|
315
|
+
function initiativeCmd(flags: Args['flags'], positionals: string[]): void {
|
|
316
|
+
const projectDir = resolve(str(flags.project) ?? process.cwd());
|
|
317
|
+
const sub = positionals[1];
|
|
318
|
+
if (!sub) {
|
|
319
|
+
console.error('usage: mugiwara initiative <status|conflict-check|set-status> <plan-file> [--id <id> --status <s>]');
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
const script = join(import.meta.dirname, '..', 'scripts', 'initiative.ts');
|
|
323
|
+
// initiative.ts is TypeScript — spawn `bun` (which strips types), not node,
|
|
324
|
+
// which only strips types on >=22.18. engines floor is >=20.11.
|
|
325
|
+
const bun = process.platform === 'win32' ? 'bun.exe' : 'bun';
|
|
326
|
+
const r = spawnSync(bun, [script, ...positionals.slice(1)], {
|
|
327
|
+
cwd: projectDir,
|
|
328
|
+
stdio: 'inherit',
|
|
329
|
+
env: process.env,
|
|
330
|
+
});
|
|
331
|
+
if (r.error) throw r.error;
|
|
332
|
+
if (r.status) process.exit(r.status);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** `mugiwara start <mission> [member]` — begin a mission from its plan. */
|
|
336
|
+
function startCmd(flags: Args['flags'], positionals: string[]): void {
|
|
337
|
+
const projectDir = resolve(str(flags.project) ?? process.cwd());
|
|
338
|
+
const mission = positionals[1];
|
|
339
|
+
if (!mission) {
|
|
340
|
+
console.error('usage: mugiwara start <mission> [member]');
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
// find the plan doc: plans/<mission>.md or plans/YYYY-MM-DD-<mission>.md
|
|
344
|
+
const plansDir = join(projectDir, '.mugiwara', 'plans');
|
|
345
|
+
let planFile = '';
|
|
346
|
+
if (existsSync(plansDir)) {
|
|
347
|
+
const bare = join(plansDir, `${mission}.md`);
|
|
348
|
+
if (existsSync(bare)) planFile = bare;
|
|
349
|
+
else {
|
|
350
|
+
const dated = readdirSync(plansDir).filter((f) => f.endsWith(`-${mission}.md`)).sort().pop();
|
|
351
|
+
if (dated) planFile = join(plansDir, dated);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!planFile) {
|
|
355
|
+
console.error(`mugiwara start: no plan found for "${mission}" in ${plansDir}`);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
const plan = readFileSync(planFile, 'utf8');
|
|
359
|
+
|
|
360
|
+
// team plan = has a ## Sub-missions table; solo = none.
|
|
361
|
+
const subMatch = plan.match(/##\s*Sub-missions/i);
|
|
362
|
+
let member = positionals[2] ?? '';
|
|
363
|
+
if (subMatch && !member) {
|
|
364
|
+
// team: find the row whose Assignee matches this git actor
|
|
365
|
+
const actor = gitActor(projectDir);
|
|
366
|
+
const name = actor.split('<')[0].trim();
|
|
367
|
+
const rowRe = /^\|\s*([\w.-]+)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|/gm;
|
|
368
|
+
let m: RegExpExecArray | null;
|
|
369
|
+
let found = '';
|
|
370
|
+
while ((m = rowRe.exec(plan))) {
|
|
371
|
+
const assignee = m[3].trim();
|
|
372
|
+
if (assignee === name || assignee === actor) { found = m[1].trim(); break; }
|
|
373
|
+
}
|
|
374
|
+
if (!found) {
|
|
375
|
+
console.error(`mugiwara start: no sub-mission assigned to "${name}" in ${planFile}. Pass a member explicitly: mugiwara start ${mission} <member>`);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
member = found;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// write the savepoint: mission [member] flow=0 mode=auto lane=full (start)
|
|
382
|
+
const code = runScript('savepoint.sh', [mission, member, '0', 'auto', 'full'], projectDir);
|
|
383
|
+
if (code !== 0) process.exit(code);
|
|
384
|
+
|
|
385
|
+
// team plan: mark the sub-mission in-progress in the plan (source of truth)
|
|
386
|
+
if (subMatch && member) {
|
|
387
|
+
const init = join(import.meta.dirname, '..', 'scripts', 'initiative.ts');
|
|
388
|
+
const bun = process.platform === 'win32' ? 'bun.exe' : 'bun';
|
|
389
|
+
const r = spawnSync(bun, [init, 'set-status', planFile, '--id', member, '--status', 'in-progress'], {
|
|
390
|
+
cwd: projectDir, stdio: 'inherit', env: process.env,
|
|
391
|
+
});
|
|
392
|
+
if (r.status) process.exit(r.status);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
console.log(`mugiwara start: ${mission}${member ? ` [${member}]` : ''} begun at Flow 0. Resume anytime with: mugiwara continue ${mission}${member ? ` ${member}` : ''}`);
|
|
396
|
+
}
|
|
397
|
+
|
|
195
398
|
function help(): void {
|
|
196
399
|
console.log(`mugiwara ${VERSION} — the Straw Hat crew for AI agents
|
|
197
400
|
|
|
@@ -203,6 +406,19 @@ Usage:
|
|
|
203
406
|
mugiwara list --check health check: show installations + missing files
|
|
204
407
|
mugiwara reset wipe mission state (spec/plans/results/review/issues[/logs])
|
|
205
408
|
mugiwara archive <m> fold a closed mission's evidence into its report, then remove loose files
|
|
409
|
+
mugiwara onboard run the zero-LLM onboarding wizard (writes .mugiwara/config)
|
|
410
|
+
mugiwara continue list in-flight missions (exit 2 = pick one, nothing resumed)
|
|
411
|
+
mugiwara continue <m> [member]
|
|
412
|
+
print the exact resume point for that mission/member
|
|
413
|
+
mugiwara status computed mission state: wave, tasks, lane, blockers, budget
|
|
414
|
+
mugiwara run <script> [args...]
|
|
415
|
+
run a bundled harness script here (${RUNNABLE.join(', ')})
|
|
416
|
+
mugiwara savepoint <mission> [member] [wave] [mode]
|
|
417
|
+
shorthand for: mugiwara run savepoint.sh ...
|
|
418
|
+
mugiwara initiative <status|conflict-check|set-status> <plan> [args]
|
|
419
|
+
team sub-mission coordination (status / conflict-check / set-status)
|
|
420
|
+
mugiwara start <mission> [member]
|
|
421
|
+
begin a mission from its plan (solo, or team sub-mission by git actor)
|
|
206
422
|
mugiwara --help this help
|
|
207
423
|
mugiwara --version print version
|
|
208
424
|
|
|
@@ -214,6 +430,7 @@ Flags:
|
|
|
214
430
|
--force overwrite differing files (with backup)
|
|
215
431
|
--dry-run print actions without writing
|
|
216
432
|
--check with list: report missing files (health check)
|
|
433
|
+
--all with continue/status: every actor, not just yours
|
|
217
434
|
--keep-logs with reset: keep .mugiwara/logs (lessons ledger survives)`);
|
|
218
435
|
}
|
|
219
436
|
|