@ionivetech/mugiwara 0.6.5 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -9
  3. package/.codex-plugin/plugin.json +2 -8
  4. package/.cursor-plugin/plugin.json +2 -8
  5. package/.kimi-plugin/plugin.json +2 -8
  6. package/.opencode/commands/mugiwara-continue.md +44 -31
  7. package/.opencode/commands/mugiwara-review.md +1 -1
  8. package/.opencode/commands/mugiwara-security.md +1 -1
  9. package/.opencode/commands/mugiwara.md +2 -2
  10. package/.opencode/plugins/mugiwara.mjs +3 -19
  11. package/AGENTS.md +6 -6
  12. package/GEMINI.md +3 -3
  13. package/README.md +82 -74
  14. package/content/agents/brook-healing.md +12 -11
  15. package/content/agents/chopper-checkpoint.md +14 -13
  16. package/content/agents/eval-runner.md +6 -9
  17. package/content/agents/franky-gates.md +7 -10
  18. package/content/agents/jinbe-security.md +10 -14
  19. package/content/agents/luffy-orchestrator.md +20 -22
  20. package/content/agents/memory-keeper.md +9 -12
  21. package/content/agents/nami-planner.md +7 -10
  22. package/content/agents/resume-coordinator.md +12 -15
  23. package/content/agents/robin-reviewer.md +13 -12
  24. package/content/agents/sanji-quality.md +9 -12
  25. package/content/agents/skeptic-verifier.md +7 -10
  26. package/content/agents/usopp-brainstorm.md +6 -9
  27. package/content/agents/zoro-execution.md +9 -12
  28. package/content/skills/mugiwara-backend/SKILL.md +4 -0
  29. package/content/skills/mugiwara-backend/references/database.md +61 -0
  30. package/content/skills/mugiwara-brainstorm/SKILL.md +1 -1
  31. package/content/skills/mugiwara-checkpoint/SKILL.md +17 -14
  32. package/content/skills/mugiwara-checkpoint/references/ledger-format.md +5 -5
  33. package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
  34. package/content/skills/mugiwara-execution/SKILL.md +24 -24
  35. package/content/skills/mugiwara-execution/references/dispatch.md +3 -3
  36. package/content/skills/mugiwara-execution/references/resume-batching.md +2 -2
  37. package/content/skills/mugiwara-frontend/SKILL.md +5 -6
  38. package/content/skills/mugiwara-frontend/references/checklist.md +14 -0
  39. package/content/skills/mugiwara-gates/SKILL.md +14 -10
  40. package/content/skills/mugiwara-git/SKILL.md +7 -2
  41. package/content/skills/mugiwara-healing/SKILL.md +7 -5
  42. package/content/skills/mugiwara-healing/references/workers.md +2 -2
  43. package/content/skills/mugiwara-lessons/SKILL.md +2 -2
  44. package/content/skills/mugiwara-orchestration/SKILL.md +28 -26
  45. package/content/skills/mugiwara-orchestration/references/check-ins.md +17 -16
  46. package/content/skills/mugiwara-orchestration/references/closure.md +18 -13
  47. package/content/skills/mugiwara-orchestration/references/output-contract.md +9 -9
  48. package/content/skills/mugiwara-orchestration/references/triage-escalation.md +32 -22
  49. package/content/skills/mugiwara-planning/SKILL.md +5 -18
  50. package/content/skills/mugiwara-planning/references/plan-template.md +3 -3
  51. package/content/skills/mugiwara-quality/SKILL.md +10 -7
  52. package/content/skills/mugiwara-resume/SKILL.md +20 -22
  53. package/content/skills/mugiwara-review/SKILL.md +7 -2
  54. package/content/skills/mugiwara-review/references/five-axis-worksheet.md +1 -1
  55. package/content/skills/mugiwara-root-cause/SKILL.md +2 -0
  56. package/content/skills/mugiwara-security/SKILL.md +6 -2
  57. package/content/skills/mugiwara-ship/SKILL.md +2 -2
  58. package/content/skills/mugiwara-ship/references/cleanup.md +17 -16
  59. package/content/skills/mugiwara-testcases/SKILL.md +5 -3
  60. package/content/skills/mugiwara-workflow/SKILL.md +32 -35
  61. package/content/skills/mugiwara-workflow/references/workspace-layout.md +41 -40
  62. package/dist/mugiwara.js +1370 -153
  63. package/gemini-extension.json +1 -1
  64. package/hooks/auto-savepoint.js +114 -0
  65. package/hooks/auto-savepoint.ts +102 -0
  66. package/hooks/engagement-marker.js +59 -0
  67. package/hooks/engagement-marker.ts +97 -0
  68. package/hooks/hooks.json +41 -2
  69. package/hooks/mugiwara-mode-tracker.js +79 -0
  70. package/hooks/pipeline-guard.js +186 -0
  71. package/hooks/pipeline-guard.ts +270 -0
  72. package/hooks/session-start.js +109 -0
  73. package/hooks/session-start.ts +62 -45
  74. package/package.json +7 -3
  75. package/plugin.json +1 -1
  76. package/references/agent-protocol.md +15 -0
  77. package/references/complexity.md +77 -0
  78. package/references/definition-of-done.md +1 -1
  79. package/references/multi-actor.md +4 -7
  80. package/references/prose-style.md +54 -0
  81. package/references/skill-versioning.md +1 -1
  82. package/references/wave-banners.md +16 -16
  83. package/scripts/build-hooks.ts +56 -0
  84. package/scripts/check-doc-links.ts +40 -0
  85. package/scripts/conformance.ts +23 -16
  86. package/scripts/coverage-gate.ts +193 -0
  87. package/scripts/gate-selftest.ts +118 -7
  88. package/scripts/install.ps1 +5 -4
  89. package/scripts/install.sh +4 -4
  90. package/scripts/lane.sh +56 -6
  91. package/scripts/lib/patterns.sh +89 -0
  92. package/scripts/policy-force.ts +22 -0
  93. package/scripts/retrieval-eval.ts +1 -1
  94. package/scripts/savepoint.sh +249 -67
  95. package/scripts/validate-content.ts +22 -9
  96. package/scripts/verify-install.ts +156 -44
  97. package/src/args.ts +2 -2
  98. package/src/budget.ts +56 -0
  99. package/src/cli.ts +272 -4
  100. package/src/continue.ts +262 -0
  101. package/src/installer.ts +59 -15
  102. package/src/integrity.ts +158 -0
  103. package/src/mission.ts +228 -95
  104. package/src/policy.ts +156 -0
  105. package/src/provenance.ts +116 -0
  106. package/src/rollback.ts +95 -0
  107. package/src/routing.ts +69 -0
  108. package/src/run.ts +82 -0
  109. package/src/sign.ts +61 -0
  110. package/src/targets/claude.ts +140 -2
  111. package/src/targets/copilot.ts +20 -1
  112. package/src/targets/generic.ts +9 -4
  113. package/src/targets/opencode.ts +0 -1
  114. package/.opencode/commands/mugiwara-execute.md +0 -13
  115. package/.opencode/commands/mugiwara-heal.md +0 -13
  116. package/.opencode/commands/mugiwara-onboard.md +0 -15
  117. package/.opencode/commands/mugiwara-plan.md +0 -17
  118. package/.opencode/commands/mugiwara-ship.md +0 -13
  119. package/.opencode/commands/using-mugiwara.md +0 -20
  120. package/content/agents/onboarding-guide.md +0 -124
  121. package/content/skills/mugiwara-agent-security/SKILL.md +0 -58
  122. package/content/skills/mugiwara-agent-security/references/checklist.md +0 -20
  123. package/content/skills/mugiwara-context-budget/SKILL.md +0 -64
  124. package/content/skills/mugiwara-context-budget/references/context-budget.md +0 -37
  125. package/content/skills/mugiwara-pr/SKILL.md +0 -69
  126. package/content/skills/mugiwara-pr/references/verdict-format.md +0 -31
  127. package/content/skills/mugiwara-security/references/owasp-mapping.md +0 -30
  128. package/content/skills/mugiwara-sunset/SKILL.md +0 -82
  129. package/content/skills/using-mugiwara/SKILL.md +0 -51
  130. package/references/token-budget.md +0 -34
  131. package/scripts/evidence.sh +0 -81
  132. package/scripts/initiative.ts +0 -296
  133. package/scripts/mission-report.sh +0 -292
  134. package/scripts/onboard.ts +0 -266
  135. package/scripts/probe.ts +0 -40
@@ -1,81 +0,0 @@
1
- #!/usr/bin/env bash
2
- # scripts/evidence.sh — run a check and capture its evidence.
3
- # Usage: evidence.sh <mission> <label> [-- command args...]
4
- # output: .mugiwara/results/<mission>/<label>-<hash>.log
5
- set -u
6
-
7
- die() { echo "evidence: $*" >&2; exit 1; }
8
-
9
- MISSION="${1:-}"
10
- LABEL="${2:-}"
11
- shift 2 2>/dev/null || true
12
-
13
- # strip an optional "--" separator between <label> and the command
14
- [ "${1:-}" = "--" ] && shift
15
-
16
- [ -z "$MISSION" ] && die "usage: evidence.sh <mission> <label> [-- command args...]"
17
- [ -z "$LABEL" ] && die "usage: evidence.sh <mission> <label> [-- command args...]"
18
-
19
- # mission allowlist — path-traversal guard (same rule as mission-report.sh)
20
- case "$MISSION" in
21
- *[!a-zA-Z0-9._-]*) die "invalid mission name \"$MISSION\" (allowlist: [a-zA-Z0-9._-])" ;;
22
- esac
23
-
24
- # label allowlist — LABEL feeds the output filename; traversal or shell
25
- # metacharacters must not reach the filesystem (same rule as MISSION)
26
- case "$LABEL" in
27
- *[!a-zA-Z0-9._-]*) die "invalid label \"$LABEL\" (allowlist: [a-zA-Z0-9._-])" ;;
28
- esac
29
- # dot-only labels (".", "..", "...") pass the char allowlist but escape the
30
- # results dir — reject them before any filename is built from LABEL.
31
- if [[ "$LABEL" =~ ^\.+$ ]]; then
32
- die "invalid label \"$LABEL\" (allowlist: [a-zA-Z0-9._-], not a dot-path)"
33
- fi
34
-
35
- MUGIWARA_DIR="${MUGIWARA_DIR:-.mugiwara}"
36
- RESULTS_DIR="$MUGIWARA_DIR/results/$MISSION"
37
- mkdir -p "$RESULTS_DIR"
38
-
39
- TIMESTAMP=$(date +%Y%m%d-%H%M%S)
40
- HASH=$(echo "${LABEL}-${TIMESTAMP}-$$-${RANDOM}" | (sha256sum 2>/dev/null || shasum -a 256 2>/dev/null || openssl sha256) | cut -c1-12 2>/dev/null || echo "${TIMESTAMP}")
41
- EVIDENCE_FILE="$RESULTS_DIR/${LABEL}-${HASH}.log"
42
-
43
- # Neutralize forged verdict/exit header lines in captured output. The agent
44
- # trusts the real trailer only (# Exit:/# Verdict: appended after the block);
45
- # attacker output may try to impersonate it under any spelling — leading
46
- # whitespace, ANSI prefix, no space, CRLF. Idempotent: already-neutralized
47
- # #-Verdict: / #-Exit: lines pass through unchanged.
48
- SANITIZE='s/^[[:space:]]*(\x1b\[[0-9;]*m)*#[[:space:]]*(Verdict|Exit):/#-\2:/'
49
-
50
- # command line echoed in the header — collapse embedded newlines so an arg
51
- # cannot forge a header line inside "# Command:" (the header block is written
52
- # verbatim, outside the sanitizer).
53
- COMMAND_LINE=$(printf '%s ' "$@" 2>/dev/null | tr '\n\r' ' ' | sed 's/ *$//')
54
-
55
- {
56
- echo "# Evidence: $LABEL"
57
- echo "# At: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
58
- echo "# Command: ${COMMAND_LINE:-<stdin pipeline>}"
59
- echo "# ---"
60
- echo
61
-
62
- if [ $# -gt 0 ]; then
63
- "$@" 2>&1 | sed -E "$SANITIZE"
64
- EXIT_CODE=${PIPESTATUS[0]}
65
- else
66
- sed -E "$SANITIZE"
67
- EXIT_CODE=0
68
- fi
69
- } > "$EVIDENCE_FILE"
70
-
71
- # trailer: exit code + verdict (D6). The verdict is PASS/FAIL derived from the
72
- # exit code — the check's own outcome, not the harness's opinion.
73
- if [ "$EXIT_CODE" -eq 0 ]; then
74
- VERDICT="PASS"
75
- else
76
- VERDICT="FAIL"
77
- fi
78
- printf '# Exit: %s\n# Verdict: %s\n' "$EXIT_CODE" "$VERDICT" >> "$EVIDENCE_FILE"
79
-
80
- echo "$EVIDENCE_FILE"
81
- exit $EXIT_CODE
@@ -1,296 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { readFileSync, writeFileSync, existsSync } from 'node:fs';
3
- import { join, resolve } from 'node:path';
4
-
5
- const USAGE = `initiative — team sub-mission coordination for mugiwara plans
6
-
7
- Usage:
8
- bun run scripts/initiative.ts status <plan-file>
9
- bun run scripts/initiative.ts conflict-check <plan-file>
10
- bun run scripts/initiative.ts set-status <plan-file> --id <id> --status <pending|in-progress|done|blocked>
11
-
12
- Commands:
13
- status Parse Sub-missions table, render ASCII dashboard
14
- conflict-check Detect overlapping Touched Files across in-progress sub-missions
15
- set-status Update status marker for a sub-mission row
16
- `;
17
-
18
- interface SubMission {
19
- id: string;
20
- name: string;
21
- assignee: string;
22
- branch: string;
23
- status: string;
24
- dependsOn: string;
25
- touchedFiles: string[];
26
- }
27
-
28
- const STATUS_MARKERS: Record<string, string> = {
29
- pending: '[ ]',
30
- 'in-progress': '[~]',
31
- done: '[x]',
32
- blocked: '[!]',
33
- };
34
-
35
- function resolvePlan(planFile: string): string {
36
- const resolved = resolve(process.cwd(), planFile);
37
- if (existsSync(resolved)) return resolved;
38
-
39
- const plansDir = join(process.cwd(), '.mugiwara', 'plans');
40
- const plansPath = join(plansDir, planFile.endsWith('.md') ? planFile : `${planFile}.md`);
41
- if (existsSync(plansPath)) return plansPath;
42
- if (!planFile.endsWith('.md') && existsSync(join(plansDir, planFile))) return join(plansDir, planFile);
43
-
44
- const alt = join(plansDir, `${planFile}.md`);
45
- const err = `Plan not found: "${planFile}" (tried: ${resolved}, ${plansPath}, ${alt})`;
46
- throw new Error(err);
47
- }
48
-
49
- // A plan is a markdown document. Garbage (no markdown structure) is not a
50
- // "solo mission" — it is a malformed plan. Heading check keeps the
51
- // no-sub-missions-but-valid-markdown case working as solo mission.
52
- function assertPlanContent(content: string, planFile: string): void {
53
- if (!/^#{1,6}\s/m.test(content)) {
54
- throw new Error(`Not a valid plan file: "${planFile}" — expected markdown (no heading found)`);
55
- }
56
- }
57
-
58
- function parseSubMissions(content: string): { subs: SubMission[]; hasSection: boolean } {
59
- const lines = content.split('\n');
60
- let inTable = false;
61
- let inSubSection = false;
62
- let hasSection = false;
63
- const subs: SubMission[] = [];
64
-
65
- for (let i = 0; i < lines.length; i++) {
66
- const line = lines[i];
67
- if (line.trim().toLowerCase().startsWith('## sub-missions')) {
68
- inSubSection = true;
69
- hasSection = true;
70
- continue;
71
- }
72
- if (inSubSection && line.startsWith('## ') && !line.trim().toLowerCase().startsWith('## sub-missions')) {
73
- break;
74
- }
75
- if (!inSubSection) continue;
76
-
77
- const trimmed = line.trim();
78
- const lower = trimmed.toLowerCase();
79
- if (lower.startsWith('| id ') && lower.includes('| name ')) {
80
- inTable = true;
81
- continue;
82
- }
83
- if (inTable && trimmed.startsWith('|---')) continue;
84
- if (inTable && trimmed.startsWith('|')) {
85
- // Keep interior empty cells (e.g. empty "depends on"): filter(Boolean)
86
- // would shift columns and drop the touched-files tail.
87
- const cols = trimmed.split('|').map(c => c.trim());
88
- while (cols.length && cols[0] === '') cols.shift();
89
- while (cols.length && cols[cols.length - 1] === '') cols.pop();
90
- if (cols.length >= 6) {
91
- subs.push({
92
- id: cols[0],
93
- name: cols[1],
94
- assignee: cols[2],
95
- branch: cols[3],
96
- status: cols[4].replace('[X]', '[x]'),
97
- dependsOn: cols[5],
98
- touchedFiles: cols.slice(6).join(' ').split(/[,\s]+/).map(s => s.trim()).filter(Boolean),
99
- });
100
- }
101
- }
102
- if (inTable && !trimmed.startsWith('|')) {
103
- inTable = false;
104
- }
105
- }
106
-
107
- return { subs, hasSection };
108
- }
109
-
110
- function assertRowsParsed(planFile: string, content: string): { subs: SubMission[]; hasSection: boolean } {
111
- const { subs, hasSection } = parseSubMissions(content);
112
- if (hasSection && subs.length === 0) {
113
- console.error('initiative: "## Sub-missions" section found but no rows parsed.');
114
- console.error(' Expected header: | ID | Name | Assignee | Branch | Status | Depends On | Touched Files |');
115
- process.exit(1);
116
- }
117
- return { subs, hasSection };
118
- }
119
-
120
- function cmdStatus(planFile: string): void {
121
- const content = readFileSync(planFile, 'utf8');
122
- assertPlanContent(content, planFile);
123
- const { subs } = assertRowsParsed(planFile, content);
124
-
125
- if (subs.length === 0) {
126
- console.log('No sub-missions found — solo mission.');
127
- return;
128
- }
129
-
130
- const doneCount = subs.filter(s => s.status === '[x]').length;
131
- const total = subs.length;
132
- const pct = Math.round((doneCount / total) * 100);
133
- const barLen = 20;
134
- const filled = Math.round((doneCount / total) * barLen);
135
- const bar = '█'.repeat(filled) + '░'.repeat(barLen - filled);
136
-
137
- // dependency blocking: a sub whose dependency is not done is blocked
138
- const statusById = new Map(subs.map(s => [s.id, s.status]));
139
- const blockedBy = new Map<string, string>();
140
- for (const s of subs) {
141
- if (s.dependsOn && statusById.has(s.dependsOn) && statusById.get(s.dependsOn) !== '[x]') {
142
- blockedBy.set(s.id, s.dependsOn);
143
- }
144
- }
145
-
146
- console.log(`\n${doneCount}/${total} done [${bar}] ${pct}%\n`);
147
- console.log(`${'ID'.padEnd(8)} ${'Name'.padEnd(20)} ${'Assignee'.padEnd(12)} ${'Branch'.padEnd(28)} ${'Status'.padEnd(8)} ${'Depends'.padEnd(10)} ${'Files'}`);
148
- console.log('─'.repeat(120));
149
-
150
- for (const s of subs) {
151
- const icon = { '[ ]': '◻', '[~]': '◉', '[x]': '✓', '[!]': '✗' }[s.status] || '?';
152
- const blocked = blockedBy.has(s.id) ? ` ⛔ blocked-by ${blockedBy.get(s.id)}` : '';
153
- console.log(
154
- `${s.id.padEnd(8)} ${s.name.padEnd(20)} ${s.assignee.padEnd(12)} ${s.branch.padEnd(28)} ${icon} ${s.status.padEnd(3)} ${s.dependsOn.padEnd(10)} ${s.touchedFiles.join(', ')}${blocked}`
155
- );
156
- }
157
- console.log();
158
- }
159
-
160
- function cmdConflictCheck(planFile: string): void {
161
- const content = readFileSync(planFile, 'utf8');
162
- assertPlanContent(content, planFile);
163
- const { subs } = assertRowsParsed(planFile, content);
164
-
165
- if (subs.length === 0) {
166
- console.log('No sub-missions — no conflicts possible.');
167
- return;
168
- }
169
-
170
- const active = subs.filter(s => s.status === '[~]');
171
- if (active.length < 2) {
172
- console.log('Fewer than 2 in-progress sub-missions — no conflicts to check.');
173
- return;
174
- }
175
-
176
- const fileToSubs = new Map<string, string[]>();
177
- for (const s of active) {
178
- for (const f of s.touchedFiles) {
179
- const entries = fileToSubs.get(f) || [];
180
- entries.push(s.id);
181
- fileToSubs.set(f, entries);
182
- }
183
- }
184
-
185
- const conflicts: string[] = [];
186
- for (const [file, ids] of fileToSubs) {
187
- if (ids.length > 1) {
188
- conflicts.push(` ⚠ ${file} — touched by: ${ids.join(', ')}`);
189
- }
190
- }
191
-
192
- if (conflicts.length === 0) {
193
- console.log('No file conflicts among in-progress sub-missions.');
194
- } else {
195
- console.log(`\n${conflicts.length} file conflict(s) detected:\n`);
196
- console.log(conflicts.join('\n'));
197
- console.log();
198
- process.exit(1);
199
- }
200
- }
201
-
202
- function cmdSetStatus(planFile: string, id: string, status: string): void {
203
- if (!STATUS_MARKERS[status]) {
204
- console.error(`Invalid status: "${status}". Use: pending, in-progress, done, blocked`);
205
- process.exit(1);
206
- }
207
-
208
- const content = readFileSync(planFile, 'utf8');
209
- assertPlanContent(content, planFile);
210
- const lines = content.split('\n');
211
- const newMarker = STATUS_MARKERS[status];
212
- let inTable = false;
213
- let inSubSection = false;
214
- let found = false;
215
-
216
- for (let i = 0; i < lines.length; i++) {
217
- const line = lines[i];
218
- if (line.trim().toLowerCase().startsWith('## sub-missions')) {
219
- inSubSection = true;
220
- continue;
221
- }
222
- if (inSubSection && line.startsWith('## ') && !line.trim().toLowerCase().startsWith('## sub-missions')) {
223
- break;
224
- }
225
- if (!inSubSection) continue;
226
-
227
- const trimmed = line.trim();
228
- if (trimmed.toLowerCase().startsWith('| id ')) { inTable = true; continue; }
229
- if (!inTable || !trimmed.startsWith('|')) continue;
230
- if (trimmed.startsWith('|---')) continue;
231
-
232
- // same cell handling as parseSubMissions: interior empty cells preserved
233
- const cols = trimmed.split('|').map(c => c.trim());
234
- while (cols.length && cols[0] === '') cols.shift();
235
- while (cols.length && cols[cols.length - 1] === '') cols.pop();
236
- if (cols[0] === id) {
237
- let oldMarker = cols[4];
238
- if (oldMarker === '[X]') oldMarker = '[x]';
239
- if (!Object.values(STATUS_MARKERS).includes(oldMarker)) {
240
- console.error(`Row ${id}: status column "${oldMarker}" is not a recognized marker`);
241
- process.exit(1);
242
- }
243
- // rewrite only the status cell — a marker string elsewhere in the row
244
- // (name, touched files) must not be clobbered by a row-wide replace
245
- lines[i] = `| ${cols.slice(0, 4).join(' | ')} | ${newMarker} | ${cols.slice(5).join(' | ')} |`;
246
- found = true;
247
- break;
248
- }
249
- }
250
-
251
- if (!found) {
252
- console.error(`Sub-mission "${id}" not found in Sub-missions table.`);
253
- process.exit(1);
254
- }
255
-
256
- writeFileSync(planFile, lines.join('\n'));
257
- console.log(`✓ ${id}: ${status}`);
258
- }
259
-
260
- // --- main ---
261
-
262
- const args = process.argv.slice(2);
263
-
264
- if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
265
- console.log(USAGE);
266
- process.exit(0);
267
- }
268
-
269
- const cmd = args[0];
270
-
271
- try {
272
- if (cmd === 'status') {
273
- if (args.length < 2) { console.error('Missing plan-file argument.'); process.exit(1); }
274
- cmdStatus(resolvePlan(args[1]));
275
- } else if (cmd === 'conflict-check') {
276
- if (args.length < 2) { console.error('Missing plan-file argument.'); process.exit(1); }
277
- cmdConflictCheck(resolvePlan(args[1]));
278
- } else if (cmd === 'set-status') {
279
- const idIdx = args.indexOf('--id');
280
- const statusIdx = args.indexOf('--status');
281
- if (idIdx === -1 || statusIdx === -1 || args.length < 6) {
282
- console.error('set-status requires --id <id> --status <status>');
283
- process.exit(1);
284
- }
285
- const id = args[idIdx + 1];
286
- const statusVal = args[statusIdx + 1];
287
- cmdSetStatus(resolvePlan(args[1]), id, statusVal);
288
- } else {
289
- console.error(`Unknown command: "${cmd}"`);
290
- console.log(USAGE);
291
- process.exit(1);
292
- }
293
- } catch (err) {
294
- console.error(err instanceof Error ? err.message : String(err));
295
- process.exit(1);
296
- }
@@ -1,292 +0,0 @@
1
- #!/usr/bin/env bash
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.
5
- # Enriched: mission header, token budget (WARN/STOP flag), tasks (state or plan
6
- # doc fallback), gate/quality excerpt from 06-closure.md, evidence file list.
7
- # Every section degrades to n/a when state.json or results/ artifacts are absent.
8
- set -u
9
-
10
- MISSION="${1:-}"
11
- [ -z "$MISSION" ] && { echo "usage: mission-report.sh <mission>" >&2; exit 1; }
12
- case "$MISSION" in
13
- *[!a-zA-Z0-9._-]*) echo "mission-report: invalid mission name \"$MISSION\" (allowlist: [a-zA-Z0-9._-])" >&2; exit 1 ;;
14
- esac
15
- if [[ "$MISSION" =~ ^\.+$ ]]; then
16
- echo "mission-report: invalid mission name \"$MISSION\" (allowlist: [a-zA-Z0-9._-], not a dot-path)" >&2; exit 1
17
- fi
18
-
19
- MUGIWARA_DIR="${MUGIWARA_DIR:-.mugiwara}"
20
- # state lives per (mission, member): solo = state/<mission>/state.json;
21
- # MEMBER env selects a team member's state, else the solo state.
22
- MEMBER="${MEMBER:-}"
23
- case "$MEMBER" in
24
- *[!a-zA-Z0-9._-]*) echo "mission-report: invalid member \"$MEMBER\" (allowlist: [a-zA-Z0-9._-])" >&2; exit 1 ;;
25
- esac
26
- if [ -n "$MEMBER" ]; then
27
- STATE_FILE="$MUGIWARA_DIR/state/$MISSION/$MEMBER.json"
28
- else
29
- STATE_FILE="$MUGIWARA_DIR/state/$MISSION/state.json"
30
- fi
31
- REPORT_DIR="$MUGIWARA_DIR/reports"
32
- RESULTS_DIR="$MUGIWARA_DIR/results/$MISSION"
33
- REVIEW_DIR="$MUGIWARA_DIR/review"
34
- ISSUES_DIR="$MUGIWARA_DIR/issues"
35
-
36
- export STATE_FILE REPORT_DIR RESULTS_DIR REVIEW_DIR ISSUES_DIR MISSION MUGIWARA_DIR
37
- node << 'NODE'
38
- const fs = require('fs');
39
- const path = require('path');
40
-
41
- const stateFile = process.env.STATE_FILE || ".mugiwara/state/unknown/state.json";
42
- const reportDir = process.env.REPORT_DIR || ".mugiwara/reports";
43
- const resultsDir = process.env.RESULTS_DIR || ".mugiwara/results/unknown";
44
- const reviewDir = process.env.REVIEW_DIR || ".mugiwara/review";
45
- const issuesDir = process.env.ISSUES_DIR || ".mugiwara/issues";
46
- const mugiDir = process.env.MUGIWARA_DIR || ".mugiwara";
47
- const mission = process.env.MISSION || "unknown";
48
-
49
- fs.mkdirSync(reportDir, { recursive: true });
50
-
51
- // state.json is optional — degrade gracefully, never hard-exit
52
- let s = null;
53
- if (fs.existsSync(stateFile)) {
54
- try {
55
- s = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
56
- } catch (e) {
57
- console.error("mission-report: warning: " + stateFile + " unreadable — degraded report");
58
- }
59
- } else {
60
- console.error("mission-report: warning: " + stateFile + " not found — degraded report");
61
- }
62
-
63
- const val = (obj, key, dflt) => {
64
- if (!s || !obj) return dflt;
65
- const v = obj[key];
66
- return (v === undefined || v === null || v === "") ? dflt : v;
67
- };
68
-
69
- const now = new Date().toISOString().slice(0, 10);
70
- const reportFile = path.join(reportDir, now + "-" + mission + ".md");
71
- const lane = val(s, 'lane', 'n/a');
72
- const lanePeak = val(s, 'lane_peak', 'n/a');
73
- const laneReason = val(s, 'lane_reason', 'n/a');
74
- const mode = val(s, 'mode', 'n/a');
75
- const actor = val(s, 'actor', 'n/a');
76
- const branch = val(s, 'branch', 'n/a');
77
- const wave = val(s, 'wave', 'n/a');
78
- const filesTouched = val(s, 'files_touched', 0);
79
- const locDelta = val(s, 'loc_delta', 0);
80
- const locChurn = val(s, 'loc_churn', 0);
81
- const sensitive = s ? (s.sensitive_paths || []) : [];
82
- const tasks = s ? (s.tasks || {}) : {};
83
- const blockers = val(s, 'blockers_open', 0);
84
- const healCycle = val(s, 'heal_cycle', 1);
85
- const tokens = Number(val(s, 'tokens_est', 0)) || 0;
86
- const tokensSource = val(s, 'tokens_source', 'n/a');
87
- const budget = Number(val(s, 'budget', 0)) || 0;
88
- const budgetStatus = val(s, 'budget_status', 'n/a');
89
- const evidence = s ? (s.evidence || []) : [];
90
- const updated = val(s, 'updated_at', now);
91
-
92
- // --- wave artifact scan: results/<mission>/NN-*.md with verdict sniffing ---
93
- const WAVE_LABEL = {
94
- "01-execution": "Execute (Wave 3)",
95
- "02-audit": "Checkpoint (Wave 4)",
96
- "03-quality": "Quality (Wave 5)",
97
- "04-gates": "Gates (Wave 6)",
98
- "05-healing": "Healing (Wave 8)",
99
- "06-closure": "Closure (Wave 9)",
100
- "07-pr-verdict": "PR material",
101
- };
102
-
103
- const WAVE_FILES = new Set(Object.keys(WAVE_LABEL));
104
-
105
- function sniffVerdict(file) {
106
- const text = fs.readFileSync(file, 'utf8');
107
- const lines = text.split(/\r?\n/).filter(l => /pass|fail|✅|❌|verdict|go|no-go/i.test(l));
108
- for (const l of lines) {
109
- const m = l.match(/(NO-GO|GO|PASS|FAIL|✅|❌)/i);
110
- if (m) return m[1].toUpperCase();
111
- }
112
- return "?";
113
- }
114
-
115
- const waveRows = [];
116
- if (fs.existsSync(resultsDir)) {
117
- const files = fs.readdirSync(resultsDir).filter(f => f.endsWith('.md')).sort();
118
- for (const f of files) {
119
- const base = f.replace(/\.md$/, '');
120
- if (!WAVE_FILES.has(base)) continue; // support files (todos.md, resume.md, eval.md, evidence logs) not in waves table
121
- const label = WAVE_LABEL[base];
122
- const verdict = sniffVerdict(path.join(resultsDir, f));
123
- waveRows.push({ label, file: f, verdict });
124
- }
125
- }
126
-
127
- // --- review + issues scan: aggregate findings count + open blockers ---
128
- let reviewFiles = [];
129
- let reviewFindings = 0;
130
- if (fs.existsSync(reviewDir)) {
131
- reviewFiles = fs.readdirSync(reviewDir).filter(f => (f.startsWith(mission + "-") || f.startsWith(mission + "/")) && f.endsWith('.md'));
132
- for (const f of reviewFiles) {
133
- const text = fs.readFileSync(path.join(reviewDir, f), 'utf8');
134
- reviewFindings += (text.match(/^\s*(?:#+\s*)?(?:🔴|🟠|🟡|⚪)(?:\s*\/\s*(?:🔴|🟠|🟡|⚪))?\s+.*/gm) || []).length;
135
- }
136
- }
137
-
138
- let issueRows = [];
139
- if (fs.existsSync(issuesDir)) {
140
- const issueFiles = fs.readdirSync(issuesDir).filter(f => (f.startsWith(mission + "-") || f.startsWith(mission + "/")) && f.endsWith('.md'));
141
- for (const f of issueFiles) {
142
- const text = fs.readFileSync(path.join(issuesDir, f), 'utf8');
143
- for (const line of text.split(/\r?\n/)) {
144
- if (line.trim().startsWith('|') && line.includes('|') && !line.includes('---')) {
145
- const cells = line.split('|').map(c => c.trim()).filter(Boolean);
146
- if (cells.length >= 5 && !/^wave$/i.test(cells[0])) issueRows.push(cells.join(" | "));
147
- }
148
- }
149
- }
150
- }
151
-
152
- // --- tasks: state.json counts, plan-doc fallback (savepoint-style counting) ---
153
- function findPlan(mDir, m) {
154
- const plansDir = path.join(mDir, 'plans');
155
- if (!fs.existsSync(plansDir)) return null;
156
- const direct = path.join(plansDir, m + '.md');
157
- if (fs.existsSync(direct)) return direct;
158
- const matches = fs.readdirSync(plansDir).filter(f => f.endsWith('-' + m + '.md'));
159
- return matches.length ? path.join(plansDir, matches.sort()[0]) : null;
160
- }
161
-
162
- let tasksDone = tasks.done || 0;
163
- let tasksTotal = tasks.total || 0;
164
- let tasksSource = "state.json";
165
- if (tasksTotal === 0) {
166
- const plan = findPlan(mugiDir, mission);
167
- if (plan) {
168
- const lines = fs.readFileSync(plan, 'utf8').split(/\r?\n/);
169
- const done = lines.filter(l => l.includes('[x]')).length;
170
- const total = done + lines.filter(l => l.includes('[ ]')).length;
171
- if (total > 0) {
172
- tasksDone = done;
173
- tasksTotal = total;
174
- tasksSource = "plan doc";
175
- }
176
- }
177
- }
178
- if (tasksDone === 0 && tasksTotal === 0) { tasksDone = 'n/a'; tasksTotal = 'n/a'; }
179
-
180
- // --- token budget flag: WARN/STOP line when status is not ok ---
181
- let budgetFlag = "";
182
- if (budgetStatus === "warn" || budgetStatus === "stop") {
183
- const warnAt = Math.floor(budget * 3 / 2);
184
- const stopAt = budget * 3;
185
- if (budgetStatus === "stop") {
186
- budgetFlag = "🛑 STOP: tokens " + tokens.toLocaleString() + " ≥ 3× budget " + budget.toLocaleString() + " (stop at " + stopAt.toLocaleString() + ") — halt; escalate to Luffy";
187
- } else {
188
- budgetFlag = "⚠ WARN: tokens " + tokens.toLocaleString() + " ≥ 1.5× budget " + budget.toLocaleString() + " (warn at " + warnAt.toLocaleString() + ") — checkpoint before continuing";
189
- }
190
- }
191
-
192
- // --- gate/quality excerpt: headings + body from 06-closure.md, as-is ---
193
- function sectionExcerpt(file, heading) {
194
- if (!fs.existsSync(file)) return null;
195
- const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
196
- let collecting = false;
197
- let content = 0;
198
- const out = [];
199
- for (const l of lines) {
200
- if (/^##\s/.test(l)) {
201
- if (collecting) break;
202
- if (l.trim() === heading) { collecting = true; out.push(l); continue; }
203
- } else if (collecting) {
204
- if (l.trim()) {
205
- out.push(l);
206
- content++;
207
- if (content >= 20) break; // cap excerpt
208
- }
209
- }
210
- }
211
- return collecting ? out : null;
212
- }
213
-
214
- const closureFile = path.join(resultsDir, "06-closure.md");
215
- const gateSec = sectionExcerpt(closureFile, "## Gate verdicts");
216
- const testsSec = sectionExcerpt(closureFile, "## Tests");
217
-
218
- // --- evidence file list: all .md artifacts in results/<mission>/ ---
219
- let evidenceFiles = [];
220
- if (fs.existsSync(resultsDir)) {
221
- evidenceFiles = fs.readdirSync(resultsDir).filter(f => f.endsWith('.md')).sort();
222
- }
223
-
224
- // --- assemble report ---
225
- let report = "# Mission: " + mission + " . " + now + "\n\n";
226
-
227
- report += "## Mission header\n\n| Field | Value |\n|-------|-------|\n";
228
- report += "| Mission | " + mission + " |\n";
229
- report += "| Branch | " + branch + " |\n";
230
- report += "| Lane | " + lane + " |\n";
231
- report += "| Lane peak | " + lanePeak + " |\n";
232
- report += "| Lane reason | " + laneReason + " |\n";
233
- report += "| Mode | " + mode + " |\n";
234
- report += "| Wave | " + wave + " |\n";
235
- report += "| Actor | " + actor + " |\n\n";
236
-
237
- report += "## Token budget\n\n| Field | Value |\n|-------|-------|\n";
238
- report += "| Tokens (est) | " + tokens.toLocaleString() + " |\n";
239
- report += "| Tokens source | " + tokensSource + " |\n";
240
- report += "| Budget | " + budget.toLocaleString() + " |\n";
241
- report += "| Budget status | " + budgetStatus + " |\n";
242
- if (budgetFlag) report += "\n" + budgetFlag;
243
- report += "\n\n";
244
-
245
- report += "## Tasks\n\n| Field | Value |\n|-------|-------|\n";
246
- report += "| Done | " + tasksDone + " |\n";
247
- report += "| Total | " + tasksTotal + " |\n";
248
- report += "| Source | " + tasksSource + " |\n\n";
249
-
250
- report += "## What changed\n\n";
251
- report += filesTouched + " files, +" + locDelta + " LOC (" + locChurn + " churn)";
252
- if (sensitive.length) report += "\nSensitive paths: " + sensitive.join(", ");
253
- report += "\n\n";
254
-
255
- report += "## Waves\n\n| Wave | Artifact | Verdict |\n|------|----------|---------|";
256
- if (waveRows.length) {
257
- for (const r of waveRows) report += "\n| " + r.label + " | `" + r.file + "` | " + r.verdict + " |";
258
- } else {
259
- report += "\n| (no wave artifacts found in `" + resultsDir + "`) | | |";
260
- }
261
- report += "\n\n";
262
-
263
- report += "## Gate & quality\n\n";
264
- if (gateSec) report += gateSec.join("\n") + "\n";
265
- if (testsSec) report += (gateSec ? "\n" : "") + testsSec.join("\n") + "\n";
266
- if (!gateSec && !testsSec) report += "n/a\n";
267
- report += "\n";
268
-
269
- report += "## Evidence files\n\n";
270
- report += evidenceFiles.length ? evidenceFiles.map(f => "- " + f).join("\n") : "n/a";
271
- report += "\n\n";
272
-
273
- report += "## Review & blockers\n\n";
274
- report += "Review + security files: " + (reviewFiles.length ? reviewFiles.join(", ") : "none") + "\n";
275
- report += "Findings: " + reviewFindings + "\n";
276
- report += "Blocker ledger rows: " + issueRows.length + (issueRows.length ? "\n" + issueRows.map(r => "- " + r).join("\n") : "");
277
- report += "\n\n";
278
-
279
- report += "## State\n\n| Field | Value |\n|-------|-------|\n";
280
- report += "| Wave | " + wave + " |\n";
281
- report += "| Tasks | " + (tasks.done || 0) + "/" + (tasks.total || 0) + " done |\n";
282
- report += "| Blockers open | " + blockers + " |\n";
283
- report += "| Heal cycles | " + healCycle + " |\n";
284
- report += "| Tokens used | " + tokens.toLocaleString() + " / " + budget.toLocaleString() + " |\n\n";
285
-
286
- report += "## Evidence\n\n";
287
- report += evidence.length ? evidence.map(e => "- " + e).join("\n") : "n/a";
288
- report += "\n\n## Updated\n\n" + updated + "\n";
289
-
290
- fs.writeFileSync(reportFile, report);
291
- console.log("\u2713 mission report: " + reportFile);
292
- NODE