@ionivetech/mugiwara 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/.opencode/commands/mugiwara-continue.md +16 -0
  7. package/.opencode/commands/mugiwara-execute.md +6 -4
  8. package/.opencode/commands/mugiwara-heal.md +6 -4
  9. package/.opencode/commands/mugiwara-plan.md +6 -4
  10. package/.opencode/commands/mugiwara-review.md +6 -4
  11. package/.opencode/commands/mugiwara-security.md +6 -4
  12. package/.opencode/commands/mugiwara-ship.md +6 -4
  13. package/.opencode/commands/mugiwara.md +4 -2
  14. package/.opencode/commands/using-mugiwara.md +7 -13
  15. package/.opencode/plugins/mugiwara.mjs +18 -6
  16. package/AGENTS.md +1 -1
  17. package/GEMINI.md +1 -1
  18. package/README.md +251 -120
  19. package/content/agents/brook-healing.md +17 -3
  20. package/content/agents/chopper-checkpoint.md +17 -3
  21. package/content/agents/eval-runner.md +17 -2
  22. package/content/agents/franky-gates.md +19 -5
  23. package/content/agents/jinbe-security.md +19 -3
  24. package/content/agents/luffy-orchestrator.md +6 -3
  25. package/content/agents/memory-keeper.md +15 -0
  26. package/content/agents/nami-planner.md +21 -6
  27. package/content/agents/onboarding-guide.md +145 -0
  28. package/content/agents/resume-coordinator.md +21 -5
  29. package/content/agents/robin-reviewer.md +18 -3
  30. package/content/agents/sanji-quality.md +19 -4
  31. package/content/agents/skeptic-verifier.md +18 -3
  32. package/content/agents/usopp-brainstorm.md +17 -3
  33. package/content/agents/zoro-execution.md +18 -3
  34. package/content/skills/mugiwara-backend/SKILL.md +1 -1
  35. package/content/skills/mugiwara-brainstorm/SKILL.md +5 -1
  36. package/content/skills/mugiwara-checkpoint/SKILL.md +2 -2
  37. package/content/skills/mugiwara-contract-first/SKILL.md +2 -36
  38. package/content/skills/mugiwara-contract-first/references/process.md +37 -0
  39. package/content/skills/mugiwara-execution/SKILL.md +30 -13
  40. package/content/skills/mugiwara-execution/references/resume-batching.md +32 -0
  41. package/content/skills/mugiwara-frontend/SKILL.md +1 -1
  42. package/content/skills/mugiwara-gates/SKILL.md +32 -30
  43. package/content/skills/mugiwara-healing/SKILL.md +1 -1
  44. package/content/skills/mugiwara-orchestration/SKILL.md +41 -25
  45. package/content/skills/mugiwara-orchestration/references/delegation-pillars.md +40 -0
  46. package/content/skills/mugiwara-orchestration/references/triage-escalation.md +96 -0
  47. package/content/skills/mugiwara-planning/SKILL.md +23 -27
  48. package/content/skills/mugiwara-planning/references/plan-template.md +42 -0
  49. package/content/skills/mugiwara-pr/SKILL.md +2 -2
  50. package/content/skills/mugiwara-quality/SKILL.md +10 -6
  51. package/content/skills/mugiwara-resume/SKILL.md +7 -2
  52. package/content/skills/mugiwara-review/SKILL.md +20 -1
  53. package/content/skills/mugiwara-review/references/code-attributes.md +38 -0
  54. package/content/skills/mugiwara-root-cause/SKILL.md +3 -30
  55. package/content/skills/mugiwara-root-cause/references/process.md +35 -0
  56. package/content/skills/mugiwara-security/SKILL.md +17 -32
  57. package/content/skills/mugiwara-security/references/owasp-top10.md +12 -0
  58. package/content/skills/mugiwara-security/references/rationalizations.md +8 -0
  59. package/content/skills/mugiwara-ship/SKILL.md +5 -4
  60. package/content/skills/mugiwara-workflow/SKILL.md +74 -77
  61. package/content/skills/mugiwara-workflow/references/workspace-layout.md +50 -0
  62. package/content/skills/using-mugiwara/SKILL.md +8 -19
  63. package/dist/mugiwara.js +107 -41
  64. package/gemini-extension.json +1 -1
  65. package/hooks/session-start.ts +1 -1
  66. package/package.json +11 -4
  67. package/plugin.json +1 -1
  68. package/references/multi-actor.md +1 -1
  69. package/scripts/evidence.sh +12 -5
  70. package/scripts/gate-selftest.ts +266 -0
  71. package/scripts/initiative.ts +248 -0
  72. package/scripts/lane.sh +15 -0
  73. package/scripts/mission-report.sh +90 -12
  74. package/scripts/onboard.ts +292 -0
  75. package/scripts/release-notes.ts +42 -13
  76. package/scripts/savepoint.sh +97 -50
  77. package/scripts/validate-content.ts +77 -0
  78. package/scripts/verify-install.ts +72 -0
  79. package/src/installer.ts +34 -7
  80. package/src/mission.ts +7 -5
  81. package/src/targets/claude.ts +20 -3
  82. package/src/targets/generic.ts +1 -1
  83. package/src/targets/opencode.ts +31 -10
  84. package/docs/concepts/agents.md +0 -53
  85. package/docs/concepts/audit-trail.md +0 -65
  86. package/docs/concepts/comparison.md +0 -58
  87. package/docs/concepts/config.md +0 -55
  88. package/docs/concepts/cost.md +0 -45
  89. package/docs/concepts/execution-model.md +0 -92
  90. package/docs/concepts/git-strategy.md +0 -62
  91. package/docs/concepts/lanes.md +0 -82
  92. package/docs/concepts/modes.md +0 -73
  93. package/docs/concepts/pr-summary.md +0 -54
  94. package/docs/concepts/skills.md +0 -55
  95. package/docs/concepts/workflow.md +0 -89
  96. package/docs/getting-started.md +0 -158
  97. package/docs/index.md +0 -56
  98. package/docs/install/antigravity.md +0 -45
  99. package/docs/install/claude.md +0 -77
  100. package/docs/install/cli.md +0 -115
  101. package/docs/install/codex.md +0 -44
  102. package/docs/install/copilot.md +0 -45
  103. package/docs/install/cursor.md +0 -45
  104. package/docs/install/gemini.md +0 -44
  105. package/docs/install/index.md +0 -53
  106. package/docs/install/kimi.md +0 -45
  107. package/docs/install/opencode.md +0 -143
  108. package/docs/install/pi.md +0 -46
  109. package/docs/reference/adoption-guide.md +0 -72
  110. package/docs/reference/agent-anatomy.md +0 -72
  111. package/docs/reference/compliance-matrix.md +0 -81
  112. package/docs/reference/developer-onboarding.md +0 -89
  113. package/docs/reference/enforcement.md +0 -35
  114. package/docs/reference/harness-matrix.md +0 -41
  115. package/docs/reference/skill-anatomy.md +0 -71
  116. package/docs/troubleshooting.md +0 -91
  117. package/evals/cases/_no-skill.json +0 -16
  118. package/evals/cases/adversarial-pressure-fake-pass.json +0 -25
  119. package/evals/cases/adversarial-pressure-skip-review.json +0 -25
  120. package/evals/cases/lane-exploratory-vague.json +0 -24
  121. package/evals/cases/lane-sensitivity-payment.json +0 -24
  122. package/evals/cases/positive-refactor-existing-tests.json +0 -25
  123. package/evals/cases/positive-resume-mid-mission.json +0 -24
  124. package/evals/cases/routing-agent-security.json +0 -25
  125. package/evals/cases/routing-auth-feature.json +0 -26
  126. package/evals/cases/routing-backend.json +0 -25
  127. package/evals/cases/routing-bug-one-file.json +0 -26
  128. package/evals/cases/routing-claim-audit.json +0 -25
  129. package/evals/cases/routing-context-budget.json +0 -25
  130. package/evals/cases/routing-contract-first.json +0 -25
  131. package/evals/cases/routing-execution.json +0 -25
  132. package/evals/cases/routing-frontend.json +0 -26
  133. package/evals/cases/routing-gates.json +0 -25
  134. package/evals/cases/routing-git.json +0 -25
  135. package/evals/cases/routing-healing.json +0 -25
  136. package/evals/cases/routing-lessons.json +0 -25
  137. package/evals/cases/routing-orchestration.json +0 -25
  138. package/evals/cases/routing-planning.json +0 -26
  139. package/evals/cases/routing-pr.json +0 -25
  140. package/evals/cases/routing-quality.json +0 -25
  141. package/evals/cases/routing-ship.json +0 -26
  142. package/evals/cases/routing-sunset.json +0 -25
  143. package/evals/cases/routing-using-mugiwara.json +0 -25
  144. package/evals/cases/routing-workflow.json +0 -25
  145. package/evals/floor.json +0 -6
@@ -1,25 +1,34 @@
1
1
  #!/usr/bin/env bash
2
- # scripts/mission-report.sh — generate human-readable mission report from state.json
3
- # Usage: mission-report.sh <mission>
2
+ # scripts/mission-report.sh — generate aggregate mission report from state.json
3
+ # + per-mission wave artifacts. Usage: mission-report.sh <mission>
4
+ # Output: .mugiwara/reports/YYYY-MM-DD-<mission>.md — one-file summary of all waves.
4
5
  set -u
5
6
 
6
7
  MISSION="${1:-}"
7
8
  [ -z "$MISSION" ] && { echo "usage: mission-report.sh <mission>" >&2; exit 1; }
9
+ case "$MISSION" in
10
+ *[!a-zA-Z0-9._-]*) echo "mission-report: invalid mission name \"$MISSION\" (allowlist: [a-zA-Z0-9._-])" >&2; exit 1 ;;
11
+ esac
8
12
 
9
13
  MUGIWARA_DIR="${MUGIWARA_DIR:-.mugiwara}"
10
14
  STATE_FILE="$MUGIWARA_DIR/state.json"
11
15
  REPORT_DIR="$MUGIWARA_DIR/reports"
12
- REPORT_FILE="$REPORT_DIR/${MISSION}.md"
16
+ RESULTS_DIR="$MUGIWARA_DIR/results/$MISSION"
17
+ REVIEW_DIR="$MUGIWARA_DIR/review"
18
+ ISSUES_DIR="$MUGIWARA_DIR/issues"
13
19
 
14
20
  [ -f "$STATE_FILE" ] || { echo "mission-report: $STATE_FILE not found" >&2; exit 1; }
15
21
 
16
- export STATE_FILE REPORT_DIR MISSION
22
+ export STATE_FILE REPORT_DIR RESULTS_DIR REVIEW_DIR ISSUES_DIR MISSION
17
23
  node << 'NODE'
18
24
  const fs = require('fs');
19
25
  const path = require('path');
20
26
 
21
27
  const stateFile = process.env.STATE_FILE || ".mugiwara/state.json";
22
28
  const reportDir = process.env.REPORT_DIR || ".mugiwara/reports";
29
+ const resultsDir = process.env.RESULTS_DIR || ".mugiwara/results/unknown";
30
+ const reviewDir = process.env.REVIEW_DIR || ".mugiwara/review";
31
+ const issuesDir = process.env.ISSUES_DIR || ".mugiwara/issues";
23
32
  const mission = process.env.MISSION || "unknown";
24
33
 
25
34
  if (!fs.existsSync(stateFile)) {
@@ -30,8 +39,8 @@ if (!fs.existsSync(stateFile)) {
30
39
  fs.mkdirSync(reportDir, { recursive: true });
31
40
  const s = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
32
41
 
33
- const reportFile = path.join(reportDir, mission + ".md");
34
42
  const now = new Date().toISOString().slice(0, 10);
43
+ const reportFile = path.join(reportDir, now + "-" + mission + ".md");
35
44
  const lane = s.lane || "unknown";
36
45
  const mode = s.mode || "guided";
37
46
  const actor = s.actor || "unknown";
@@ -48,19 +57,84 @@ const budget = s.budget || 0;
48
57
  const evidence = s.evidence || [];
49
58
  const updated = s.updated_at || new Date().toISOString();
50
59
 
60
+ // --- wave artifact scan: results/<mission>/NN-*.md with verdict sniffing ---
61
+ const WAVE_LABEL = {
62
+ "01-execution": "Execute (Wave 3)",
63
+ "02-audit": "Checkpoint (Wave 4)",
64
+ "03-quality": "Quality (Wave 5)",
65
+ "04-gates": "Gates (Wave 6)",
66
+ "05-healing": "Healing (Wave 8)",
67
+ "06-closure": "Closure (Wave 9)",
68
+ "07-pr-verdict": "PR material",
69
+ };
70
+
71
+ const WAVE_FILES = new Set(Object.keys(WAVE_LABEL));
72
+
73
+ function sniffVerdict(file) {
74
+ const text = fs.readFileSync(file, 'utf8');
75
+ const lines = text.split(/\r?\n/).filter(l => /pass|fail|✅|❌|verdict|go|no-go/i.test(l));
76
+ for (const l of lines) {
77
+ const m = l.match(/(NO-GO|GO|PASS|FAIL|✅|❌)/i);
78
+ if (m) return m[1].toUpperCase();
79
+ }
80
+ return "?";
81
+ }
82
+
83
+ const waveRows = [];
84
+ if (fs.existsSync(resultsDir)) {
85
+ const files = fs.readdirSync(resultsDir).filter(f => f.endsWith('.md')).sort();
86
+ for (const f of files) {
87
+ const base = f.replace(/\.md$/, '');
88
+ if (!WAVE_FILES.has(base)) continue; // support files (todos.md, resume.md, eval.md, evidence logs) not in waves table
89
+ const label = WAVE_LABEL[base];
90
+ const verdict = sniffVerdict(path.join(resultsDir, f));
91
+ waveRows.push({ label, file: f, verdict });
92
+ }
93
+ }
94
+
95
+ // --- review + issues scan: aggregate findings count + open blockers ---
96
+ let reviewFiles = [];
97
+ let reviewFindings = 0;
98
+ if (fs.existsSync(reviewDir)) {
99
+ reviewFiles = fs.readdirSync(reviewDir).filter(f => (f.startsWith(mission + "-") || f.startsWith(mission + "/")) && f.endsWith('.md'));
100
+ for (const f of reviewFiles) {
101
+ const text = fs.readFileSync(path.join(reviewDir, f), 'utf8');
102
+ reviewFindings += (text.match(/^\s*(?:#+\s*)?(?:🔴|🟠|🟡|⚪)(?:\s*\/\s*(?:🔴|🟠|🟡|⚪))?\s+.*/gm) || []).length;
103
+ }
104
+ }
105
+
106
+ let issueRows = [];
107
+ if (fs.existsSync(issuesDir)) {
108
+ const issueFiles = fs.readdirSync(issuesDir).filter(f => (f.startsWith(mission + "-") || f.startsWith(mission + "/")) && f.endsWith('.md'));
109
+ for (const f of issueFiles) {
110
+ const text = fs.readFileSync(path.join(issuesDir, f), 'utf8');
111
+ for (const line of text.split(/\r?\n/)) {
112
+ if (line.trim().startsWith('|') && line.includes('|') && !line.includes('---')) {
113
+ const cells = line.split('|').map(c => c.trim()).filter(Boolean);
114
+ if (cells.length >= 5 && !/^wave$/i.test(cells[0])) issueRows.push(cells.join(" | "));
115
+ }
116
+ }
117
+ }
118
+ }
119
+
51
120
  let report = "# Mission: " + mission + " . " + now + "\n\n";
52
121
  report += "**Lane** " + lane + " . **Mode** " + mode + " . **Actor** " + actor + " . **Branch** " + branch + "\n\n";
122
+
53
123
  report += "## What changed\n\n";
54
124
  report += filesTouched + " files, +" + locDelta + " LOC";
125
+ if (sensitive.length) report += "\nSensitive paths: " + sensitive.join(", ");
55
126
 
56
- if (sensitive.length) {
57
- report += "\nSensitive paths: " + sensitive.join(", ");
127
+ report += "\n\n## Waves\n\n| Wave | Artifact | Verdict |\n|------|----------|---------|";
128
+ if (waveRows.length) {
129
+ for (const r of waveRows) report += "\n| " + r.label + " | `" + r.file + "` | " + r.verdict + " |";
130
+ } else {
131
+ report += "\n| (no wave artifacts found in `" + resultsDir + "`) | | |";
58
132
  }
59
133
 
60
- report += "\n\n## Gates\n\n| Gate | Verdict | Evidence |\n|------|---------|----------|";
61
- for (const e of evidence) {
62
- report += "\n| \u2014 | PASS | " + e + " |";
63
- }
134
+ report += "\n\n## Review & blockers\n\n";
135
+ report += "Review + security files: " + (reviewFiles.length ? reviewFiles.join(", ") : "none") + "\n";
136
+ report += "Findings: " + reviewFindings + "\n";
137
+ report += "Blocker ledger rows: " + issueRows.length + (issueRows.length ? "\n" + issueRows.map(r => "- " + r).join("\n") : "");
64
138
 
65
139
  report += "\n\n## State\n\n| Field | Value |\n|-------|-------|\n";
66
140
  report += "| Wave | " + wave + " |\n";
@@ -68,7 +142,11 @@ report += "| Tasks | " + (tasks.done || 0) + "/" + (tasks.total || 0) + " done |
68
142
  report += "| Blockers open | " + blockers + " |\n";
69
143
  report += "| Heal cycles | " + healCycle + " |\n";
70
144
  report += "| Tokens used | " + tokens.toLocaleString() + " / " + budget.toLocaleString() + " |\n\n";
71
- report += "## Updated\n\n" + updated + "\n";
145
+
146
+ report += "## Evidence\n\n";
147
+ for (const e of evidence) report += "- " + e + "\n";
148
+
149
+ report += "\n## Updated\n\n" + updated + "\n";
72
150
 
73
151
  fs.writeFileSync(reportFile, report);
74
152
  console.log("\u2713 mission report: " + reportFile);
@@ -0,0 +1,292 @@
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
+ const onboardPath = join(mugiwaraDir, "onboard.json");
11
+
12
+ function ask(rl: import("node:readline").Interface, prompt: string): Promise<string> {
13
+ return new Promise((resolve) => {
14
+ rl.question(prompt, (answer) => resolve(answer.trim()));
15
+ });
16
+ }
17
+
18
+ async function confirm(rl: import("node:readline").Interface, prompt: string): Promise<boolean> {
19
+ const a = await ask(rl, prompt);
20
+ return a.toLowerCase() === "y" || a.toLowerCase() === "yes";
21
+ }
22
+
23
+ async function pick(
24
+ rl: import("node:readline").Interface,
25
+ prompt: string,
26
+ options: string[],
27
+ ): Promise<number> {
28
+ while (true) {
29
+ const a = await ask(rl, prompt);
30
+ const n = Number.parseInt(a, 10);
31
+ if (!Number.isNaN(n) && n >= 1 && n <= options.length) return n;
32
+ console.log(` Enter a number 1–${options.length}`);
33
+ }
34
+ }
35
+
36
+ async function main() {
37
+ // --help / no-tty guard
38
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
39
+ console.log("Usage: bun scripts/onboard.ts");
40
+ console.log("Runs the Mugiwara onboarding wizard (10 fixed questions).");
41
+ console.log("Writes .mugiwara/config and .mugiwara/onboard.json.");
42
+ process.exit(0);
43
+ }
44
+
45
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
46
+
47
+ try {
48
+ console.log("");
49
+ console.log("╔══════════════════════════════════════╗");
50
+ console.log("║ Mugiwara Onboarding Wizard ║");
51
+ console.log("╚══════════════════════════════════════╝");
52
+ console.log("");
53
+
54
+ // Check for existing config
55
+ if (existsSync(configPath)) {
56
+ const redo = await confirm(
57
+ rl,
58
+ "Existing .mugiwara/config found. Re-onboard? (y/n): ",
59
+ );
60
+ if (!redo) {
61
+ console.log("Onboarding cancelled. Config preserved.");
62
+ process.exit(0);
63
+ }
64
+ console.log("");
65
+ }
66
+
67
+ const answers: Record<string, unknown> = {
68
+ started_at: new Date().toISOString(),
69
+ };
70
+
71
+ // ---- Phase 1: Project Context ----
72
+ console.log("── Phase 1: Project Context ──");
73
+ console.log("");
74
+
75
+ 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 > ", [
76
+ "Web application",
77
+ "Mobile application",
78
+ "CLI tool",
79
+ "Library/SDK",
80
+ "Backend service / API",
81
+ "Other",
82
+ ]);
83
+ answers.project_type = q1;
84
+ console.log("");
85
+
86
+ 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 > ", [
87
+ "TypeScript",
88
+ "JavaScript",
89
+ "Python",
90
+ "Go",
91
+ "Rust",
92
+ "Java",
93
+ "Other",
94
+ ]);
95
+ answers.primary_language = q2;
96
+ console.log("");
97
+
98
+ const q3 = await pick(rl, "Q3 — Team size:\n [1] Solo\n [2] 2–5\n [3] 6–15\n [4] 16+\n > ", [
99
+ "Solo",
100
+ "2–5",
101
+ "6–15",
102
+ "16+",
103
+ ]);
104
+ answers.team_size = q3;
105
+ console.log("");
106
+
107
+ 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 > ", [
108
+ "trunk-based",
109
+ "gitflow",
110
+ "github-flow",
111
+ "other",
112
+ ]);
113
+ answers.git_workflow = q4;
114
+ console.log("");
115
+
116
+ 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 > ", [
117
+ "GitHub Actions",
118
+ "GitLab CI",
119
+ "CircleCI",
120
+ "Jenkins",
121
+ "None / manual",
122
+ "Other",
123
+ ]);
124
+ answers.ci_cd = q5;
125
+ console.log("");
126
+
127
+ // ---- Phase 2: Mugiwara Preferences ----
128
+ console.log("── Phase 2: Mugiwara Preferences ──");
129
+ console.log("");
130
+
131
+ 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 > ", [
132
+ "guided",
133
+ "semi",
134
+ "auto",
135
+ ]);
136
+ answers.autonomy_mode = q6;
137
+ console.log("");
138
+
139
+ const q7 = await ask(rl, "Q7 — Agents to enable (comma-separated or 'all'):\n Available: brainstorm, plan, execute, checkpoint, quality, gates, review, security, healing\n Default: all\n > ");
140
+ answers.enabled_agents = q7 || "all";
141
+ console.log("");
142
+
143
+ const q8a = await pick(rl, "Q8a — 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 > ", [
144
+ "full",
145
+ "standard",
146
+ "quick",
147
+ ]);
148
+ answers.review_depth = q8a;
149
+ console.log("");
150
+
151
+ const q8b = await pick(rl, "Q8b — Quality check depth:\n [1] full — format, lint, typecheck, test, build\n [2] standard — lint, typecheck, test\n [3] quick — test only\n > ", [
152
+ "full",
153
+ "standard",
154
+ "quick",
155
+ ]);
156
+ answers.quality_depth = q8b;
157
+ console.log("");
158
+
159
+ console.log("Q9 — Test coverage threshold:");
160
+ 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 > ", [
161
+ "90/80",
162
+ "80/70",
163
+ "custom",
164
+ "none",
165
+ ]);
166
+ let coverageNew = 90;
167
+ let coverageModified = 80;
168
+ if (q9 === 1) {
169
+ coverageNew = 90;
170
+ coverageModified = 80;
171
+ } else if (q9 === 2) {
172
+ coverageNew = 80;
173
+ coverageModified = 70;
174
+ } else if (q9 === 3) {
175
+ const cNew = await ask(rl, " Enter coverage threshold for new code (%): ");
176
+ const cMod = await ask(rl, " Enter coverage threshold for modified code (%): ");
177
+ coverageNew = Number.parseInt(cNew, 10) || 0;
178
+ coverageModified = Number.parseInt(cMod, 10) || 0;
179
+ } else {
180
+ coverageNew = 0;
181
+ coverageModified = 0;
182
+ }
183
+ answers.coverage_threshold = q9;
184
+ answers.coverage_new = coverageNew;
185
+ answers.coverage_modified = coverageModified;
186
+ console.log("");
187
+
188
+ 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 > ", [
189
+ "conventional",
190
+ "semantic",
191
+ "free-form",
192
+ ]);
193
+ answers.commit_style = q10;
194
+ answers.completed_at = new Date().toISOString();
195
+ console.log("");
196
+
197
+ // ---- Branch name format ----
198
+ const branchFormats: Record<number, string> = {
199
+ 1: "feature/{type}-{issue}-{slug}",
200
+ 2: "feature/{slug}",
201
+ 3: "feat/{slug}",
202
+ 4: "feature/{slug}",
203
+ };
204
+ const branch = branchFormats[q4] || "feature/{slug}";
205
+
206
+ // ---- Autonomy mode ----
207
+ const modes: Record<number, string> = { 1: "guided", 2: "semi", 3: "auto" };
208
+ const mode = modes[q6] || "semi";
209
+
210
+ // ---- Commit style ----
211
+ const commitStyles: Record<number, string> = { 1: "conventional", 2: "semantic", 3: "free-form" };
212
+ const commit = commitStyles[q10] || "conventional";
213
+
214
+ // ---- Review depth ----
215
+ const reviewDepths: Record<number, string> = { 1: "full", 2: "standard", 3: "quick" };
216
+ const reviewDepth = reviewDepths[q8a] || "standard";
217
+
218
+ // ---- Quality depth ----
219
+ const qualityDepths: Record<number, string> = { 1: "full", 2: "standard", 3: "quick" };
220
+ const qualityDepth = qualityDepths[q8b] || "standard";
221
+
222
+ // ---- Write config ----
223
+ if (!existsSync(mugiwaraDir)) mkdirSync(mugiwaraDir, { recursive: true });
224
+
225
+ const config = {
226
+ mode,
227
+ branch,
228
+ coverage_new: coverageNew,
229
+ coverage_modified: coverageModified,
230
+ commit,
231
+ review_depth: reviewDepth,
232
+ quality_depth: qualityDepth,
233
+ enabled_agents: answers.enabled_agents,
234
+ };
235
+
236
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
237
+ writeFileSync(onboardPath, JSON.stringify(answers, null, 2) + "\n");
238
+
239
+ // ---- Summary ----
240
+ const qLabels: Record<string, Record<number, string>> = {
241
+ project_type: {
242
+ 1: "Web application",
243
+ 2: "Mobile application",
244
+ 3: "CLI tool",
245
+ 4: "Library/SDK",
246
+ 5: "Backend service / API",
247
+ 6: "Other",
248
+ },
249
+ primary_language: {
250
+ 1: "TypeScript",
251
+ 2: "JavaScript",
252
+ 3: "Python",
253
+ 4: "Go",
254
+ 5: "Rust",
255
+ 6: "Java",
256
+ 7: "Other",
257
+ },
258
+ team_size: { 1: "Solo", 2: "2–5", 3: "6–15", 4: "16+" },
259
+ git_workflow: { 1: "Trunk-based", 2: "GitFlow", 3: "GitHub Flow", 4: "Other" },
260
+ ci_cd: { 1: "GitHub Actions", 2: "GitLab CI", 3: "CircleCI", 4: "Jenkins", 5: "None", 6: "Other" },
261
+ autonomy_mode: { 1: "guided", 2: "semi", 3: "auto" },
262
+ review_depth: { 1: "full", 2: "standard", 3: "quick" },
263
+ quality_depth: { 1: "full", 2: "standard", 3: "quick" },
264
+ coverage_threshold: { 1: "90/80", 2: "80/70", 3: "custom", 4: "none" },
265
+ commit_style: { 1: "Conventional", 2: "Semantic", 3: "Free-form" },
266
+ };
267
+
268
+ console.log("╔══════════════════════════════════════╗");
269
+ console.log("║ Onboarding Complete ║");
270
+ console.log("╚══════════════════════════════════════╝");
271
+ console.log("");
272
+ console.log(" Project type: ", qLabels.project_type[q1]);
273
+ console.log(" Primary language: ", qLabels.primary_language[q2]);
274
+ console.log(" Team size: ", qLabels.team_size[q3]);
275
+ console.log(" Git workflow: ", qLabels.git_workflow[q4]);
276
+ console.log(" CI/CD: ", qLabels.ci_cd[q5]);
277
+ console.log(" Autonomy mode: ", qLabels.autonomy_mode[q6]);
278
+ console.log(" Enabled agents: ", answers.enabled_agents);
279
+ console.log(" Review depth: ", qLabels.review_depth[q8a]);
280
+ console.log(" Quality depth: ", qLabels.quality_depth[q8b]);
281
+ console.log(" Coverage: ", q9 === 3 ? `${coverageNew}/${coverageModified}` : qLabels.coverage_threshold[q9]);
282
+ console.log(" Commit style: ", qLabels.commit_style[q10]);
283
+ console.log("");
284
+ console.log(` Config written: ${configPath}`);
285
+ console.log(` Audit trail: ${onboardPath}`);
286
+ console.log("");
287
+ } finally {
288
+ rl.close();
289
+ }
290
+ }
291
+
292
+ main();
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env bun
2
- // scripts/release-notes.ts — generates a human-readable release description
3
- // from git history between the previous tag and HEAD. Grouped by change type,
4
- // conventional-commit aware, no gh dependency.
2
+ // scripts/release-notes.ts — generates a detailed GitHub Release description
3
+ // from git history between the previous tag and HEAD. Pulls the full commit
4
+ // message (subject + body), groups by conventional-commit type, flags breaking
5
+ // changes, and lists the affected scopes. No gh dependency.
5
6
  //
6
- // bun scripts/release-notes.ts notes since the last tag → stdout
7
+ // bun scripts/release-notes.ts notes since the last tag → stdout
7
8
  // bun scripts/release-notes.ts --since v0.3.0 notes from that tag → stdout
8
9
  import { execFileSync } from 'node:child_process';
9
10
 
@@ -22,15 +23,22 @@ if (!since) {
22
23
  }
23
24
 
24
25
  const range = since ? `${since}..HEAD` : '';
25
- const raw = execFileSync('git', ['log', '--pretty=%s', ...(range ? [range] : [])], { encoding: 'utf8' });
26
- const commits = raw.split(/\r?\n/).filter(Boolean);
26
+ const raw = execFileSync('git', ['log', '--pretty=%H%n%s%n%n%b%n__END__', ...(range ? [range] : [])], { encoding: 'utf8' });
27
+ const commits = raw
28
+ .split('__END__\n')
29
+ .map(c => c.trim())
30
+ .filter(Boolean)
31
+ .map(c => {
32
+ const [sha, subject, ...rest] = c.split('\n');
33
+ return { sha: sha.slice(0, 7), subject: subject ?? '', body: rest.join('\n').trim() };
34
+ });
27
35
 
28
36
  const groups: Record<string, { label: string; items: string[] }> = {
29
37
  feat: { label: 'New', items: [] },
30
38
  fix: { label: 'Fixed', items: [] },
31
- docs: { label: 'Docs', items: [] },
32
39
  refactor: { label: 'Refactored', items: [] },
33
40
  perf: { label: 'Performance', items: [] },
41
+ docs: { label: 'Docs', items: [] },
34
42
  chore: { label: 'Housekeeping', items: [] },
35
43
  test: { label: 'Housekeeping', items: [] },
36
44
  ci: { label: 'Housekeeping', items: [] },
@@ -38,12 +46,33 @@ const groups: Record<string, { label: string; items: string[] }> = {
38
46
 
39
47
  const order = ['feat', 'fix', 'refactor', 'perf', 'docs', 'chore'];
40
48
 
41
- for (const c of commits) {
42
- const m = /^(\w+)(?:\(.*\))?!?: (.*)/.exec(c);
49
+ // strip signature trailers (Co-authored-by, Signed-off-by, review notes)
50
+ const TRAILER = /^(Co-authored-by|Signed-off-by|Reviewed-by|Helped-by|Reported-by|Tested-by|Acked-by):/i;
51
+
52
+ for (const commit of commits) {
53
+ const m = /^(\w+)(?:\((.*?)\))?!?: (.*)/.exec(commit.subject);
43
54
  const type = m ? m[1] : 'chore';
44
- const text = m ? m[2] : c;
55
+ const scope = m?.[2] || '';
56
+ const text = m ? m[3] : commit.subject;
57
+ const breaking = Boolean(m?.[0]?.includes('!')) || /^BREAKING CHANGE:/m.test(commit.body);
58
+
59
+ const title = text.charAt(0).toUpperCase() + text.slice(1);
60
+ const scopeTag = scope ? ` \`${scope}\`` : '';
61
+ const breakingTag = breaking ? ' ⚠️ **BREAKING**' : '';
62
+
63
+ const bodyLines = commit.body
64
+ .split(/\r?\n/)
65
+ .map(l => l.trim())
66
+ .filter(l => l && !TRAILER.test(l) && l !== '---')
67
+ // the release-version marker commit repeats the subject as body — drop it
68
+ .filter(l => l !== text);
69
+
70
+ let entry = `- ${title}${scopeTag}${breakingTag} \`${commit.sha}\``;
71
+ if (bodyLines.length) {
72
+ entry += '\n' + bodyLines.map(l => ` - ${l}`).join('\n');
73
+ }
45
74
  const g = groups[type] ?? groups.chore;
46
- g.items.push(text.charAt(0).toUpperCase() + text.slice(1));
75
+ g.items.push(entry);
47
76
  }
48
77
 
49
78
  const lines: string[] = [];
@@ -52,11 +81,11 @@ for (const t of order) {
52
81
  if (!g.items.length) continue;
53
82
  lines.push(`## ${g.label}`);
54
83
  lines.push('');
55
- for (const item of g.items) lines.push(`- ${item}`);
84
+ for (const item of g.items) lines.push(item);
56
85
  lines.push('');
57
86
  }
58
87
 
59
- const bumpIdx = commits.findIndex(c => /chore: release|chore: bump/.test(c));
88
+ const bumpIdx = commits.findIndex(c => /chore: release|chore: bump/.test(c.subject));
60
89
  const body = commits.slice(0, bumpIdx === -1 ? commits.length : bumpIdx);
61
90
  const count = body.length || commits.length;
62
91