@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,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. initiative.ts ships too: `mugiwara initiative` runs it.
44
+ if ((!f.endsWith('.sh') && f !== 'initiative.ts') || 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
- if (!f.endsWith('.ts')) continue;
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
  };
@@ -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({ description: data.description, applyTo: '**/*' }, body),
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) {
@@ -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
- return join(projectDir, '.mugiwara', 'refs');
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: [] };
@@ -1,124 +0,0 @@
1
- ---
2
- name: onboarding-guide
3
- description: Persona for using-mugiwara. Onboarding wizard: host-native question flow, writes config only. No network.
4
- skills: using-mugiwara, mugiwara-orchestration
5
- write-scope: artifacts
6
- permissions: read-only
7
- ---
8
- # Onboarding Guide
9
- ## Role
10
-
11
- Runs the onboarding wizard via host-native question tool (opencode `question`,
12
- Claude Code `AskUserQuestion`, Copilot `askQuestion` when present), or plain
13
- conversation otherwise. Writes `.mugiwara/config` only. No network. First-run
14
- specialist; knows the full config surface, explains each option inline.
15
- Dispatched by `/mugiwara onboard`, first-run detection, or re-onboard reset.
16
-
17
- ## Rules
18
-
19
- 1. Never modify the questions — fixed and validated (9 questions).
20
- 2. Never skip a question; all 9 answered before writing config.
21
- 3. Host question tool when present (opencode `question`, Claude Code `AskUserQuestion`,
22
- Copilot `askQuestion`): one per call, options + free-type, next-next until done.
23
- No tool → conversation: numbered choices + "type your own answer".
24
- 4. Write `.mugiwara/config` only after all 9 answers. Never write
25
- `.mugiwara/onboard.json`; delete a stale copy if one exists.
26
- 5. Print a config summary after completion so user can verify.
27
- 6. All prompts static — no network, no LLM-generated questions.
28
- 7. CLI users: point to `bun scripts/onboard.ts` (terminal wizard for non-interactive hosts).
29
-
30
- ## The 9 Questions
31
-
32
- ### Phase 1: Project Context
33
-
34
- **Q1 — Project type:**
35
- ```
36
- [1] Web application
37
- [2] Mobile application
38
- [3] CLI tool
39
- [4] Library/SDK
40
- [5] Backend service / API
41
- [6] Other
42
- ```
43
-
44
- **Q2 — Primary language:**
45
- ```
46
- [1] TypeScript
47
- [2] JavaScript
48
- [3] Python
49
- [4] Go
50
- [5] Rust
51
- [6] Java
52
- [7] Other
53
- ```
54
-
55
- **Q3 — Team size:**
56
- ```
57
- [1] Solo
58
- [2] 2–5
59
- [3] 6–15
60
- [4] 16+
61
- ```
62
-
63
- **Q4 — Git workflow:**
64
- ```
65
- [1] Trunk-based (feature/{type}-{issue}-{slug})
66
- [2] GitFlow (feature/{slug})
67
- [3] GitHub Flow (feat/{slug})
68
- [4] Other (feature/{slug})
69
- ```
70
-
71
- **Q5 — CI/CD platform:**
72
- ```
73
- [1] GitHub Actions
74
- [2] GitLab CI
75
- [3] CircleCI
76
- [4] Jenkins
77
- [5] None / manual
78
- [6] Other
79
- ```
80
-
81
- ### Phase 2: Mugiwara Preferences
82
-
83
- **Q6 — Autonomy mode:**
84
- ```
85
- [1] guided — ask before every wave transition
86
- [2] semi — auto-advance through waves, pause on failures
87
- [3] auto — full auto-pilot
88
- ```
89
-
90
- **Q7 — Code review depth:**
91
- ```
92
- [1] full — breaking-change map, five-axis review, ≤3 cycles
93
- [2] standard — five-axis review, 1 cycle
94
- [3] quick — diff-only, no caller-map
95
- ```
96
-
97
- **Q8 — Quality check depth:**
98
- ```
99
- [1] full — format, lint, typecheck, test, build
100
- [2] standard — lint, typecheck, test
101
- [3] quick — test only
102
- ```
103
-
104
- **Q9 — Test coverage threshold:**
105
- ```
106
- [1] 90/80 — new code 90%, modified 80%
107
- [2] 80/70 — new code 80%, modified 70%
108
- [3] custom — enter your own values
109
- [4] none — 0/0, no coverage enforcement
110
- ```
111
- ## Output
112
-
113
- After all 9 answers: write `.mugiwara/config` (mode, branch, coverage,
114
- review_depth, quality_depth). Commit style defaults to `conventional`; CLI
115
- wizard (`bun scripts/onboard.ts`) also writes `commit` and can set a
116
- custom style. Print a summary of chosen values before exit.
117
-
118
- ## Before you start
119
-
120
- Dispatched by Luffy only; check `.mugiwara/logs/` routing log, existing config, confirm project root with Luffy.
121
-
122
- ## Return to Luffy
123
-
124
- Report: config written with timestamp, all 9 answers, warnings (custom coverage). Luffy uses this for wave 0 routing.
@@ -1,30 +0,0 @@
1
- # OWASP Top 10 Mapping
2
-
3
- Required when the project handles payments, health data, or PII.
4
-
5
- | Code | Category | Review area |
6
- |------|----------|-------------|
7
- | A01 | Broken access control | authz gaps, IDOR, missing server-side checks |
8
- | A02 | Cryptographic failures | PII in transit/at rest, weak crypto, exposed secrets |
9
- | A03 | Injection | SQL/NoSQL/OS/template injection, unsanitized input to exec/render |
10
- | A04 | Insecure design | missing threat model, trust-boundary failures |
11
- | A05 | Misconfiguration | default creds, verbose errors, permissive headers, debug on |
12
- | A06 | Vulnerable components | dependency audit, known-vuln check, outdated libs |
13
- | A07 | Authn failures | broken sessions, brute-forceable login, credential reuse |
14
- | A08 | Integrity | insecure deserialization, supply-chain tamper |
15
- | A09 | Logging/monitoring | PII in logs, missing audit trail, silent failures |
16
- | A10 | SSRF | server-side requests to attacker-controlled targets, URL validation |
17
-
18
- ## How to map
19
-
20
- For each security check run, record which OWASP category it covers. A handled
21
- category with no mapping row = documentation gap in the security report.
22
-
23
- ```
24
- | OWASP | Check | Verdict |
25
- |-------|-------|---------|
26
- | A01 | Authz on /api/admin routes | PASS |
27
- | A02 | TLS enforced, no hardcoded secrets | PASS |
28
- | A03 | SQL params in user query handler | PASS |
29
- | A06 | npm audit: 0 critical/high | PASS |
30
- ```
@@ -1,266 +0,0 @@
1
- #!/usr/bin/env bun
2
- // scripts/onboard.ts — Mugiwara onboarding wizard (10 fixed questions, no network)
3
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { createInterface } from "node:readline";
6
-
7
- const root = join(import.meta.dirname, "..");
8
- const mugiwaraDir = join(root, ".mugiwara");
9
- const configPath = join(mugiwaraDir, "config");
10
-
11
- function ask(rl: import("node:readline").Interface, prompt: string): Promise<string> {
12
- return new Promise((resolve) => {
13
- rl.question(prompt, (answer) => resolve(answer.trim()));
14
- });
15
- }
16
-
17
- async function confirm(rl: import("node:readline").Interface, prompt: string): Promise<boolean> {
18
- const a = await ask(rl, prompt);
19
- return a.toLowerCase() === "y" || a.toLowerCase() === "yes";
20
- }
21
-
22
- async function pick(
23
- rl: import("node:readline").Interface,
24
- prompt: string,
25
- options: string[],
26
- ): Promise<number> {
27
- while (true) {
28
- const a = await ask(rl, prompt);
29
- const n = Number.parseInt(a, 10);
30
- if (!Number.isNaN(n) && n >= 1 && n <= options.length) return n;
31
- console.log(` Enter a number 1–${options.length}`);
32
- }
33
- }
34
-
35
- async function main() {
36
- // --help / no-tty guard
37
- if (process.argv.includes("--help") || process.argv.includes("-h")) {
38
- console.log("Usage: bun scripts/onboard.ts");
39
- console.log("Runs the Mugiwara onboarding wizard (10 fixed questions).");
40
- console.log("Writes .mugiwara/config.");
41
- process.exit(0);
42
- }
43
-
44
- const rl = createInterface({ input: process.stdin, output: process.stdout });
45
-
46
- try {
47
- console.log("");
48
- console.log("╔══════════════════════════════════════╗");
49
- console.log("║ Mugiwara Onboarding Wizard ║");
50
- console.log("╚══════════════════════════════════════╝");
51
- console.log("");
52
-
53
- // Check for existing config
54
- if (existsSync(configPath)) {
55
- const redo = await confirm(
56
- rl,
57
- "Existing .mugiwara/config found. Re-onboard? (y/n): ",
58
- );
59
- if (!redo) {
60
- console.log("Onboarding cancelled. Config preserved.");
61
- process.exit(0);
62
- }
63
- console.log("");
64
- }
65
-
66
- // ---- Phase 1: Project Context ----
67
- console.log("── Phase 1: Project Context ──");
68
- console.log("");
69
-
70
- const q1 = await pick(rl, "Q1 — Project type:\n [1] Web application\n [2] Mobile application\n [3] CLI tool\n [4] Library/SDK\n [5] Backend service / API\n [6] Other\n > ", [
71
- "Web application",
72
- "Mobile application",
73
- "CLI tool",
74
- "Library/SDK",
75
- "Backend service / API",
76
- "Other",
77
- ]);
78
- console.log("");
79
-
80
- const q2 = await pick(rl, "Q2 — Primary language:\n [1] TypeScript\n [2] JavaScript\n [3] Python\n [4] Go\n [5] Rust\n [6] Java\n [7] Other\n > ", [
81
- "TypeScript",
82
- "JavaScript",
83
- "Python",
84
- "Go",
85
- "Rust",
86
- "Java",
87
- "Other",
88
- ]);
89
- console.log("");
90
-
91
- const q3 = await pick(rl, "Q3 — Team size:\n [1] Solo\n [2] 2–5\n [3] 6–15\n [4] 16+\n > ", [
92
- "Solo",
93
- "2–5",
94
- "6–15",
95
- "16+",
96
- ]);
97
- console.log("");
98
-
99
- const q4 = await pick(rl, "Q4 — Git workflow:\n [1] Trunk-based (feature/{type}-{issue}-{slug})\n [2] GitFlow (feature/{slug})\n [3] GitHub Flow (feat/{slug})\n [4] Other (feature/{slug})\n > ", [
100
- "trunk-based",
101
- "gitflow",
102
- "github-flow",
103
- "other",
104
- ]);
105
- console.log("");
106
-
107
- const q5 = await pick(rl, "Q5 — CI/CD platform:\n [1] GitHub Actions\n [2] GitLab CI\n [3] CircleCI\n [4] Jenkins\n [5] None / manual\n [6] Other\n > ", [
108
- "GitHub Actions",
109
- "GitLab CI",
110
- "CircleCI",
111
- "Jenkins",
112
- "None / manual",
113
- "Other",
114
- ]);
115
- console.log("");
116
-
117
- // ---- Phase 2: Mugiwara Preferences ----
118
- console.log("── Phase 2: Mugiwara Preferences ──");
119
- console.log("");
120
-
121
- const q6 = await pick(rl, "Q6 — Autonomy mode:\n [1] guided — ask before every wave transition\n [2] semi — auto-advance through waves, pause on failures\n [3] auto — full auto-pilot\n > ", [
122
- "guided",
123
- "semi",
124
- "auto",
125
- ]);
126
- console.log("");
127
-
128
- const q8a = await pick(rl, "Q7 — Code review depth:\n [1] full — breaking-change map, five-axis review, ≤3 cycles\n [2] standard — five-axis review, 1 cycle\n [3] quick — diff-only, no caller-map\n > ", [
129
- "full",
130
- "standard",
131
- "quick",
132
- ]);
133
- console.log("");
134
-
135
- const q8b = await pick(rl, "Q8 — Quality check depth:\n [1] full — format, lint, typecheck, test, build\n [2] standard — lint, typecheck, test\n [3] quick — test only\n > ", [
136
- "full",
137
- "standard",
138
- "quick",
139
- ]);
140
- console.log("");
141
-
142
- console.log("Q9 — Test coverage threshold:");
143
- const q9 = await pick(rl, " [1] 90/80 — new code 90%, modified 80%\n [2] 80/70 — new code 80%, modified 70%\n [3] custom — enter your own values\n [4] none — 0/0, no coverage enforcement\n > ", [
144
- "90/80",
145
- "80/70",
146
- "custom",
147
- "none",
148
- ]);
149
- let coverageNew = 90;
150
- let coverageModified = 80;
151
- if (q9 === 1) {
152
- coverageNew = 90;
153
- coverageModified = 80;
154
- } else if (q9 === 2) {
155
- coverageNew = 80;
156
- coverageModified = 70;
157
- } else if (q9 === 3) {
158
- const cNew = await ask(rl, " Enter coverage threshold for new code (%): ");
159
- const cMod = await ask(rl, " Enter coverage threshold for modified code (%): ");
160
- coverageNew = Number.parseInt(cNew, 10) || 0;
161
- coverageModified = Number.parseInt(cMod, 10) || 0;
162
- } else {
163
- coverageNew = 0;
164
- coverageModified = 0;
165
- }
166
- console.log("");
167
-
168
- const q10 = await pick(rl, "Q10 — Commit style:\n [1] Conventional Commits (feat:, fix:, chore:, docs:)\n [2] Semantic (type(scope): message)\n [3] Free-form\n > ", [
169
- "conventional",
170
- "semantic",
171
- "free-form",
172
- ]);
173
- console.log("");
174
-
175
- // ---- Branch name format ----
176
- const branchFormats: Record<number, string> = {
177
- 1: "feature/{type}-{issue}-{slug}",
178
- 2: "feature/{slug}",
179
- 3: "feat/{slug}",
180
- 4: "feature/{slug}",
181
- };
182
- const branch = branchFormats[q4] || "feature/{slug}";
183
-
184
- // ---- Autonomy mode ----
185
- const modes: Record<number, string> = { 1: "guided", 2: "semi", 3: "auto" };
186
- const mode = modes[q6] || "semi";
187
-
188
- // ---- Commit style ----
189
- const commitStyles: Record<number, string> = { 1: "conventional", 2: "semantic", 3: "free-form" };
190
- const commit = commitStyles[q10] || "conventional";
191
-
192
- // ---- Review depth ----
193
- const reviewDepths: Record<number, string> = { 1: "full", 2: "standard", 3: "quick" };
194
- const reviewDepth = reviewDepths[q8a] || "standard";
195
-
196
- // ---- Quality depth ----
197
- const qualityDepths: Record<number, string> = { 1: "full", 2: "standard", 3: "quick" };
198
- const qualityDepth = qualityDepths[q8b] || "standard";
199
-
200
- // ---- Write config ----
201
- if (!existsSync(mugiwaraDir)) mkdirSync(mugiwaraDir, { recursive: true });
202
-
203
- const config = {
204
- mode,
205
- branch,
206
- coverage_new: coverageNew,
207
- coverage_modified: coverageModified,
208
- commit,
209
- review_depth: reviewDepth,
210
- quality_depth: qualityDepth,
211
- };
212
-
213
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
214
-
215
- // ---- Summary ----
216
- const qLabels: Record<string, Record<number, string>> = {
217
- project_type: {
218
- 1: "Web application",
219
- 2: "Mobile application",
220
- 3: "CLI tool",
221
- 4: "Library/SDK",
222
- 5: "Backend service / API",
223
- 6: "Other",
224
- },
225
- primary_language: {
226
- 1: "TypeScript",
227
- 2: "JavaScript",
228
- 3: "Python",
229
- 4: "Go",
230
- 5: "Rust",
231
- 6: "Java",
232
- 7: "Other",
233
- },
234
- team_size: { 1: "Solo", 2: "2–5", 3: "6–15", 4: "16+" },
235
- git_workflow: { 1: "Trunk-based", 2: "GitFlow", 3: "GitHub Flow", 4: "Other" },
236
- ci_cd: { 1: "GitHub Actions", 2: "GitLab CI", 3: "CircleCI", 4: "Jenkins", 5: "None", 6: "Other" },
237
- autonomy_mode: { 1: "guided", 2: "semi", 3: "auto" },
238
- review_depth: { 1: "full", 2: "standard", 3: "quick" },
239
- quality_depth: { 1: "full", 2: "standard", 3: "quick" },
240
- coverage_threshold: { 1: "90/80", 2: "80/70", 3: "custom", 4: "none" },
241
- commit_style: { 1: "Conventional", 2: "Semantic", 3: "Free-form" },
242
- };
243
-
244
- console.log("╔══════════════════════════════════════╗");
245
- console.log("║ Onboarding Complete ║");
246
- console.log("╚══════════════════════════════════════╝");
247
- console.log("");
248
- console.log(" Project type: ", qLabels.project_type[q1]);
249
- console.log(" Primary language: ", qLabels.primary_language[q2]);
250
- console.log(" Team size: ", qLabels.team_size[q3]);
251
- console.log(" Git workflow: ", qLabels.git_workflow[q4]);
252
- console.log(" CI/CD: ", qLabels.ci_cd[q5]);
253
- console.log(" Autonomy mode: ", qLabels.autonomy_mode[q6]);
254
- console.log(" Review depth: ", qLabels.review_depth[q8a]);
255
- console.log(" Quality depth: ", qLabels.quality_depth[q8b]);
256
- console.log(" Coverage: ", q9 === 3 ? `${coverageNew}/${coverageModified}` : qLabels.coverage_threshold[q9]);
257
- console.log(" Commit style: ", qLabels.commit_style[q10]);
258
- console.log("");
259
- console.log(` Config written: ${configPath}`);
260
- console.log("");
261
- } finally {
262
- rl.close();
263
- }
264
- }
265
-
266
- main();