@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.
Files changed (115) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.codex-plugin/plugin.json +48 -2
  4. package/.cursor-plugin/plugin.json +48 -2
  5. package/.kimi-plugin/plugin.json +48 -2
  6. package/.opencode/commands/mugiwara-continue.md +44 -31
  7. package/.opencode/commands/mugiwara-execute.md +1 -1
  8. package/.opencode/commands/mugiwara-heal.md +1 -1
  9. package/.opencode/commands/mugiwara-onboard.md +8 -9
  10. package/.opencode/commands/mugiwara-plan.md +1 -1
  11. package/.opencode/commands/mugiwara-review.md +1 -1
  12. package/.opencode/commands/mugiwara-security.md +1 -1
  13. package/.opencode/commands/mugiwara-ship.md +1 -1
  14. package/.opencode/commands/mugiwara.md +2 -2
  15. package/.opencode/mugiwara-helpers.mjs +3 -0
  16. package/.opencode/plugins/mugiwara.mjs +1 -1
  17. package/AGENTS.md +12 -2
  18. package/README.md +53 -32
  19. package/content/agents/brook-healing.md +5 -8
  20. package/content/agents/chopper-checkpoint.md +8 -11
  21. package/content/agents/eval-runner.md +2 -5
  22. package/content/agents/franky-gates.md +4 -7
  23. package/content/agents/jinbe-security.md +4 -7
  24. package/content/agents/luffy-orchestrator.md +13 -13
  25. package/content/agents/memory-keeper.md +5 -8
  26. package/content/agents/nami-planner.md +4 -7
  27. package/content/agents/resume-coordinator.md +7 -10
  28. package/content/agents/robin-reviewer.md +6 -9
  29. package/content/agents/sanji-quality.md +7 -10
  30. package/content/agents/skeptic-verifier.md +5 -8
  31. package/content/agents/usopp-brainstorm.md +3 -6
  32. package/content/agents/zoro-execution.md +7 -10
  33. package/content/skills/mugiwara-backend/SKILL.md +2 -0
  34. package/content/skills/mugiwara-checkpoint/SKILL.md +14 -11
  35. package/content/skills/mugiwara-checkpoint/references/ledger-format.md +4 -4
  36. package/content/skills/mugiwara-context-budget/SKILL.md +5 -3
  37. package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
  38. package/content/skills/mugiwara-execution/SKILL.md +27 -27
  39. package/content/skills/mugiwara-execution/references/dispatch.md +2 -2
  40. package/content/skills/mugiwara-execution/references/resume-batching.md +1 -1
  41. package/content/skills/mugiwara-frontend/SKILL.md +2 -4
  42. package/content/skills/mugiwara-gates/SKILL.md +9 -7
  43. package/content/skills/mugiwara-git/SKILL.md +7 -2
  44. package/content/skills/mugiwara-healing/SKILL.md +6 -4
  45. package/content/skills/mugiwara-healing/references/workers.md +2 -2
  46. package/content/skills/mugiwara-lessons/SKILL.md +1 -0
  47. package/content/skills/mugiwara-orchestration/SKILL.md +34 -35
  48. package/content/skills/mugiwara-orchestration/references/check-ins.md +17 -16
  49. package/content/skills/mugiwara-orchestration/references/closure.md +5 -5
  50. package/content/skills/mugiwara-orchestration/references/output-contract.md +77 -0
  51. package/content/skills/mugiwara-orchestration/references/triage-escalation.md +22 -22
  52. package/content/skills/mugiwara-planning/SKILL.md +3 -1
  53. package/content/skills/mugiwara-planning/references/plan-template.md +2 -2
  54. package/content/skills/mugiwara-pr/SKILL.md +6 -6
  55. package/content/skills/mugiwara-pr/references/verdict-format.md +3 -3
  56. package/content/skills/mugiwara-quality/SKILL.md +8 -7
  57. package/content/skills/mugiwara-resume/SKILL.md +19 -20
  58. package/content/skills/mugiwara-review/SKILL.md +6 -1
  59. package/content/skills/mugiwara-review/references/five-axis-worksheet.md +1 -1
  60. package/content/skills/mugiwara-root-cause/SKILL.md +2 -0
  61. package/content/skills/mugiwara-security/SKILL.md +5 -1
  62. package/content/skills/mugiwara-ship/SKILL.md +1 -1
  63. package/content/skills/mugiwara-ship/references/cleanup.md +1 -1
  64. package/content/skills/mugiwara-testcases/SKILL.md +5 -3
  65. package/content/skills/mugiwara-workflow/SKILL.md +38 -39
  66. package/content/skills/mugiwara-workflow/references/workspace-layout.md +15 -15
  67. package/content/skills/using-mugiwara/SKILL.md +5 -5
  68. package/dist/mugiwara.js +807 -31
  69. package/gemini-extension.json +1 -1
  70. package/hooks/auto-savepoint.js +106 -0
  71. package/hooks/auto-savepoint.ts +102 -0
  72. package/hooks/engagement-marker.js +59 -0
  73. package/hooks/engagement-marker.ts +97 -0
  74. package/hooks/hooks.json +41 -2
  75. package/hooks/mugiwara-mode-tracker.js +79 -0
  76. package/hooks/pipeline-guard.js +182 -0
  77. package/hooks/pipeline-guard.ts +267 -0
  78. package/hooks/session-start.js +106 -0
  79. package/hooks/session-start.ts +44 -32
  80. package/package.json +7 -3
  81. package/plugin.json +1 -1
  82. package/references/agent-protocol.md +15 -0
  83. package/references/complexity.md +54 -0
  84. package/references/definition-of-done.md +1 -1
  85. package/references/multi-actor.md +5 -5
  86. package/references/skill-versioning.md +1 -1
  87. package/references/token-budget.md +30 -8
  88. package/references/wave-banners.md +21 -31
  89. package/scripts/build-hooks.ts +56 -0
  90. package/scripts/conformance.ts +232 -0
  91. package/scripts/coverage-gate.ts +179 -0
  92. package/scripts/evidence.sh +17 -4
  93. package/scripts/gate-selftest.ts +14 -0
  94. package/scripts/initiative.ts +51 -17
  95. package/scripts/install.ps1 +5 -4
  96. package/scripts/install.sh +4 -4
  97. package/scripts/lane.sh +6 -2
  98. package/scripts/lib/patterns.sh +84 -0
  99. package/scripts/mission-report.sh +12 -11
  100. package/scripts/savepoint.sh +152 -37
  101. package/scripts/validate-content.ts +22 -2
  102. package/scripts/verify-install.ts +154 -44
  103. package/src/args.ts +1 -1
  104. package/src/cli.ts +217 -0
  105. package/src/continue.ts +243 -0
  106. package/src/installer.ts +39 -0
  107. package/src/onboard.ts +207 -0
  108. package/src/run.ts +82 -0
  109. package/src/targets/claude.ts +140 -2
  110. package/src/targets/copilot.ts +20 -1
  111. package/src/targets/generic.ts +9 -4
  112. package/content/agents/onboarding-guide.md +0 -124
  113. package/content/skills/mugiwara-security/references/owasp-mapping.md +0 -30
  114. package/scripts/onboard.ts +0 -266
  115. package/scripts/probe.ts +0 -40
@@ -1,61 +1,72 @@
1
1
  #!/usr/bin/env bun
2
- // scripts/verify-install.ts — G1: verify all references/*.md pointers resolve after install.
3
- // Installs to temp dir for one target per tier, then checks every pointer.
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 tierTargets = [
12
- { id: 'claude', tier: 1 },
13
- { id: 'codex', tier: 2 },
14
- { id: 'kilo', tier: 3 },
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
- let total = 0;
18
- let broken = 0;
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 tt of tierTargets) {
21
- const target = targets[tt.id];
22
- if (!target) { console.log(`⚠ target ${tt.id} not found, skipping`); continue; }
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
- function findSkillFiles(root: string): string[] {
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
- const body = readFileSync(file, 'utf8');
46
- const refs = [...body.matchAll(/`([^`]*references\/[^`]+\.md)`/g)];
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 refs) {
49
- total++;
57
+ for (const m of readFileSync(file, 'utf8').matchAll(/`([^`]*references\/[^`]+\.md)`/g)) {
58
+ pointers++;
50
59
  const pointer = m[1];
51
- const localResolve = join(dirname(file), pointer);
52
- const mugiwaraResolve = join(mugiwaraRefsDir, pointer.replace(/^(?:_shared\/)?references\//, ''));
53
- const resolved = [localResolve, mugiwaraResolve].find(p => existsSync(p));
54
-
55
- if (!resolved) {
56
- broken++;
57
- const relFile = file.replace(dir + '/', '');
58
- console.log(`✗ ${relFile}: \`${pointer}\` → not found`);
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
- if (broken > 0) {
68
- console.log(`\n✗ ${broken}/${total} pointers cannot resolve after install`);
69
- process.exit(1);
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
- console.log(`✓ ${total} pointers checked across ${tierTargets.length} targets — all resolve`);
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