@ionivetech/mugiwara 0.6.6 → 0.8.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -7
- package/.codex-plugin/plugin.json +2 -7
- package/.cursor-plugin/plugin.json +2 -7
- package/.kimi-plugin/plugin.json +2 -7
- package/.opencode/mugiwara-helpers.mjs +2 -2
- package/.opencode/plugins/mugiwara.mjs +3 -18
- package/AGENTS.md +5 -4
- package/GEMINI.md +3 -3
- package/README.md +203 -331
- package/content/agents/brook-healing.md +8 -4
- package/content/agents/chopper-checkpoint.md +7 -3
- package/content/agents/eval-runner.md +4 -4
- package/content/agents/franky-gates.md +4 -4
- package/content/agents/jinbe-security.md +6 -7
- package/content/agents/luffy-orchestrator.md +11 -13
- package/content/agents/memory-keeper.md +4 -4
- package/content/agents/nami-planner.md +4 -4
- package/content/agents/resume-coordinator.md +7 -7
- package/content/agents/robin-reviewer.md +7 -3
- package/content/agents/sanji-quality.md +2 -2
- package/content/agents/skeptic-verifier.md +2 -2
- package/content/agents/usopp-brainstorm.md +3 -3
- package/content/agents/zoro-execution.md +6 -6
- package/content/skills/mugiwara-backend/SKILL.md +54 -43
- package/content/skills/mugiwara-backend/references/database.md +61 -0
- package/content/skills/mugiwara-brainstorm/SKILL.md +1 -1
- package/content/skills/mugiwara-checkpoint/SKILL.md +23 -11
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +1 -1
- package/content/skills/mugiwara-contract-first/SKILL.md +46 -1
- package/content/skills/mugiwara-execution/SKILL.md +37 -37
- package/content/skills/mugiwara-execution/references/dispatch.md +1 -1
- package/content/skills/mugiwara-execution/references/execution-phase-flows.md +18 -0
- package/content/skills/mugiwara-execution/references/resume-batching.md +2 -2
- package/content/skills/mugiwara-frontend/SKILL.md +44 -43
- package/content/skills/mugiwara-frontend/references/checklist.md +14 -0
- package/content/skills/mugiwara-gates/SKILL.md +23 -14
- package/content/skills/mugiwara-healing/SKILL.md +26 -25
- package/content/skills/mugiwara-lessons/SKILL.md +2 -2
- package/content/skills/mugiwara-orchestration/SKILL.md +11 -11
- package/content/skills/mugiwara-orchestration/references/check-ins.md +2 -2
- package/content/skills/mugiwara-orchestration/references/closure.md +17 -12
- package/content/skills/mugiwara-orchestration/references/control-commands.md +14 -0
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +11 -1
- package/content/skills/mugiwara-planning/SKILL.md +26 -29
- package/content/skills/mugiwara-planning/references/large-campaign-subplan.md +41 -0
- package/content/skills/mugiwara-planning/references/plan-template.md +24 -2
- package/content/skills/mugiwara-quality/SKILL.md +21 -12
- package/content/skills/mugiwara-resume/SKILL.md +8 -3
- package/content/skills/mugiwara-review/SKILL.md +19 -13
- package/content/skills/mugiwara-security/SKILL.md +47 -36
- package/content/skills/mugiwara-ship/SKILL.md +1 -1
- package/content/skills/mugiwara-ship/references/cleanup.md +17 -16
- package/content/skills/mugiwara-workflow/SKILL.md +11 -17
- package/content/skills/mugiwara-workflow/references/adaptive-budget-governor.md +5 -0
- package/content/skills/mugiwara-workflow/references/benchmark-governor.md +53 -0
- package/content/skills/mugiwara-workflow/references/cognitive-output-governor.md +5 -0
- package/content/skills/mugiwara-workflow/references/large-campaign-subplan.md +29 -0
- package/content/skills/mugiwara-workflow/references/scope-code-governor.md +14 -0
- package/content/skills/mugiwara-workflow/references/stop-slop-governor.md +14 -0
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +42 -38
- package/dist/mugiwara.js +1642 -528
- package/gemini-extension.json +1 -1
- package/hooks/auto-savepoint.js +18 -10
- package/hooks/auto-savepoint.ts +23 -23
- package/hooks/engagement-marker.js +1 -1
- package/hooks/engagement-marker.ts +1 -1
- package/hooks/pipeline-guard.js +17 -13
- package/hooks/pipeline-guard.ts +24 -20
- package/hooks/session-start.js +13 -10
- package/hooks/session-start.ts +24 -19
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/references/complexity.md +27 -4
- package/references/definition-of-done.md +1 -1
- package/references/multi-actor.md +20 -2
- package/references/posture-routing.md +31 -0
- package/references/prose-style.md +54 -0
- package/scripts/benchmark-governor.ts +516 -0
- package/scripts/benchmark-thresholds.json +47 -0
- package/scripts/check-doc-links.ts +46 -0
- package/scripts/conformance.ts +6 -16
- package/scripts/coverage-gate.ts +20 -6
- package/scripts/gate-selftest.ts +138 -7
- package/scripts/lane.sh +54 -8
- package/scripts/lib/lane-base.sh +4 -4
- package/scripts/lib/patterns.sh +5 -0
- package/scripts/policy-force.ts +22 -0
- package/scripts/retrieval-eval.ts +10 -4
- package/scripts/savepoint.sh +155 -58
- package/scripts/validate-content.ts +43 -11
- package/scripts/verify-install.ts +9 -7
- package/src/adaptive-budget.ts +178 -0
- package/src/args.ts +4 -3
- package/src/budget.ts +47 -0
- package/src/check-artifacts.ts +45 -0
- package/src/cli.ts +257 -108
- package/src/cognition.ts +234 -0
- package/src/config.ts +107 -0
- package/src/context.ts +72 -0
- package/src/continue.ts +29 -10
- package/src/cost.ts +186 -0
- package/src/evidence.ts +160 -0
- package/src/installer.ts +25 -34
- package/src/integrity.ts +158 -0
- package/src/investigation.ts +72 -0
- package/src/mission.ts +341 -94
- package/src/policy.ts +156 -0
- package/src/posture.ts +86 -0
- package/src/provenance.ts +116 -0
- package/src/reporting.ts +225 -0
- package/src/rollback.ts +95 -0
- package/src/routing.ts +69 -0
- package/src/run.ts +2 -2
- package/src/scope.ts +321 -0
- package/src/sign.ts +235 -0
- package/src/slop.ts +306 -0
- package/src/targets/claude.ts +2 -2
- package/src/work.ts +273 -0
- package/.opencode/commands/mugiwara-execute.md +0 -13
- package/.opencode/commands/mugiwara-heal.md +0 -13
- package/.opencode/commands/mugiwara-onboard.md +0 -14
- package/.opencode/commands/mugiwara-plan.md +0 -17
- package/.opencode/commands/mugiwara-ship.md +0 -13
- package/.opencode/commands/using-mugiwara.md +0 -20
- package/content/skills/mugiwara-agent-security/SKILL.md +0 -58
- package/content/skills/mugiwara-agent-security/references/checklist.md +0 -20
- package/content/skills/mugiwara-context-budget/SKILL.md +0 -66
- package/content/skills/mugiwara-context-budget/references/context-budget.md +0 -37
- package/content/skills/mugiwara-pr/SKILL.md +0 -69
- package/content/skills/mugiwara-pr/references/verdict-format.md +0 -31
- package/content/skills/mugiwara-sunset/SKILL.md +0 -82
- package/content/skills/using-mugiwara/SKILL.md +0 -51
- package/references/token-budget.md +0 -56
- package/scripts/evidence.sh +0 -81
- package/scripts/initiative.ts +0 -296
- package/scripts/mission-report.sh +0 -293
- package/src/onboard.ts +0 -207
package/scripts/initiative.ts
DELETED
|
@@ -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,293 +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 laneRose = val(s, 'lane_rose', false);
|
|
74
|
-
const laneReason = val(s, 'lane_reason', 'n/a');
|
|
75
|
-
const mode = val(s, 'mode', 'n/a');
|
|
76
|
-
const actor = val(s, 'actor', 'n/a');
|
|
77
|
-
const branch = val(s, 'branch', 'n/a');
|
|
78
|
-
const wave = val(s, 'flow', val(s, 'wave', 'n/a'));
|
|
79
|
-
const filesTouched = val(s, 'files_touched', 0);
|
|
80
|
-
const locDelta = val(s, 'loc_delta', 0);
|
|
81
|
-
const locChurn = val(s, 'loc_churn', 0);
|
|
82
|
-
const sensitive = s ? (s.sensitive_paths || []) : [];
|
|
83
|
-
const tasks = s ? (s.tasks || {}) : {};
|
|
84
|
-
const blockers = val(s, 'blockers_open', 0);
|
|
85
|
-
const healCycle = val(s, 'heal_cycle', 1);
|
|
86
|
-
const tokens = Number(val(s, 'tokens_est', 0)) || 0;
|
|
87
|
-
const tokensSource = val(s, 'tokens_source', 'n/a');
|
|
88
|
-
const budget = Number(val(s, 'budget', 0)) || 0;
|
|
89
|
-
const budgetStatus = val(s, 'budget_status', 'n/a');
|
|
90
|
-
const evidence = s ? (s.evidence || []) : [];
|
|
91
|
-
const updated = val(s, 'updated_at', now);
|
|
92
|
-
|
|
93
|
-
// --- wave artifact scan: results/<mission>/NN-*.md with verdict sniffing ---
|
|
94
|
-
const WAVE_LABEL = {
|
|
95
|
-
"01-execution": "Execute (Flow 3)",
|
|
96
|
-
"02-audit": "Checkpoint (Flow 4)",
|
|
97
|
-
"03-quality": "Quality (Flow 5)",
|
|
98
|
-
"04-gates": "Gates (Flow 6)",
|
|
99
|
-
"05-healing": "Healing (Flow 8)",
|
|
100
|
-
"06-closure": "Closure (Flow 9)",
|
|
101
|
-
"07-pr-verdict": "PR material",
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const WAVE_FILES = new Set(Object.keys(WAVE_LABEL));
|
|
105
|
-
|
|
106
|
-
function sniffVerdict(file) {
|
|
107
|
-
const text = fs.readFileSync(file, 'utf8');
|
|
108
|
-
const lines = text.split(/\r?\n/).filter(l => /pass|fail|✅|❌|verdict|go|no-go/i.test(l));
|
|
109
|
-
for (const l of lines) {
|
|
110
|
-
const m = l.match(/(NO-GO|GO|PASS|FAIL|✅|❌)/i);
|
|
111
|
-
if (m) return m[1].toUpperCase();
|
|
112
|
-
}
|
|
113
|
-
return "?";
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const waveRows = [];
|
|
117
|
-
if (fs.existsSync(resultsDir)) {
|
|
118
|
-
const files = fs.readdirSync(resultsDir).filter(f => f.endsWith('.md')).sort();
|
|
119
|
-
for (const f of files) {
|
|
120
|
-
const base = f.replace(/\.md$/, '');
|
|
121
|
-
if (!WAVE_FILES.has(base)) continue; // support files (todos.md, resume.md, eval.md, evidence logs) not in waves table
|
|
122
|
-
const label = WAVE_LABEL[base];
|
|
123
|
-
const verdict = sniffVerdict(path.join(resultsDir, f));
|
|
124
|
-
waveRows.push({ label, file: f, verdict });
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// --- review + issues scan: aggregate findings count + open blockers ---
|
|
129
|
-
let reviewFiles = [];
|
|
130
|
-
let reviewFindings = 0;
|
|
131
|
-
if (fs.existsSync(reviewDir)) {
|
|
132
|
-
reviewFiles = fs.readdirSync(reviewDir).filter(f => (f.startsWith(mission + "-") || f.startsWith(mission + "/")) && f.endsWith('.md'));
|
|
133
|
-
for (const f of reviewFiles) {
|
|
134
|
-
const text = fs.readFileSync(path.join(reviewDir, f), 'utf8');
|
|
135
|
-
reviewFindings += (text.match(/^\s*(?:#+\s*)?(?:🔴|🟠|🟡|⚪)(?:\s*\/\s*(?:🔴|🟠|🟡|⚪))?\s+.*/gm) || []).length;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
let issueRows = [];
|
|
140
|
-
if (fs.existsSync(issuesDir)) {
|
|
141
|
-
const issueFiles = fs.readdirSync(issuesDir).filter(f => (f.startsWith(mission + "-") || f.startsWith(mission + "/")) && f.endsWith('.md'));
|
|
142
|
-
for (const f of issueFiles) {
|
|
143
|
-
const text = fs.readFileSync(path.join(issuesDir, f), 'utf8');
|
|
144
|
-
for (const line of text.split(/\r?\n/)) {
|
|
145
|
-
if (line.trim().startsWith('|') && line.includes('|') && !line.includes('---')) {
|
|
146
|
-
const cells = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
147
|
-
if (cells.length >= 5 && !/^wave$/i.test(cells[0])) issueRows.push(cells.join(" | "));
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// --- tasks: state.json counts, plan-doc fallback (savepoint-style counting) ---
|
|
154
|
-
function findPlan(mDir, m) {
|
|
155
|
-
const plansDir = path.join(mDir, 'plans');
|
|
156
|
-
if (!fs.existsSync(plansDir)) return null;
|
|
157
|
-
const direct = path.join(plansDir, m + '.md');
|
|
158
|
-
if (fs.existsSync(direct)) return direct;
|
|
159
|
-
const matches = fs.readdirSync(plansDir).filter(f => f.endsWith('-' + m + '.md'));
|
|
160
|
-
return matches.length ? path.join(plansDir, matches.sort()[0]) : null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
let tasksDone = tasks.done || 0;
|
|
164
|
-
let tasksTotal = tasks.total || 0;
|
|
165
|
-
let tasksSource = "state.json";
|
|
166
|
-
if (tasksTotal === 0) {
|
|
167
|
-
const plan = findPlan(mugiDir, mission);
|
|
168
|
-
if (plan) {
|
|
169
|
-
const lines = fs.readFileSync(plan, 'utf8').split(/\r?\n/);
|
|
170
|
-
const done = lines.filter(l => l.includes('[x]')).length;
|
|
171
|
-
const total = done + lines.filter(l => l.includes('[ ]')).length;
|
|
172
|
-
if (total > 0) {
|
|
173
|
-
tasksDone = done;
|
|
174
|
-
tasksTotal = total;
|
|
175
|
-
tasksSource = "plan doc";
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (tasksDone === 0 && tasksTotal === 0) { tasksDone = 'n/a'; tasksTotal = 'n/a'; }
|
|
180
|
-
|
|
181
|
-
// --- token budget flag: WARN/STOP line when status is not ok ---
|
|
182
|
-
let budgetFlag = "";
|
|
183
|
-
if (budgetStatus === "warn" || budgetStatus === "stop") {
|
|
184
|
-
const warnAt = Math.floor(budget * 3 / 2);
|
|
185
|
-
const stopAt = budget * 3;
|
|
186
|
-
if (budgetStatus === "stop") {
|
|
187
|
-
budgetFlag = "🛑 STOP: tokens " + tokens.toLocaleString() + " ≥ 3× budget " + budget.toLocaleString() + " (stop at " + stopAt.toLocaleString() + ") — halt; escalate to Luffy";
|
|
188
|
-
} else {
|
|
189
|
-
budgetFlag = "⚠ WARN: tokens " + tokens.toLocaleString() + " ≥ 1.5× budget " + budget.toLocaleString() + " (warn at " + warnAt.toLocaleString() + ") — checkpoint before continuing";
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// --- gate/quality excerpt: headings + body from 06-closure.md, as-is ---
|
|
194
|
-
function sectionExcerpt(file, heading) {
|
|
195
|
-
if (!fs.existsSync(file)) return null;
|
|
196
|
-
const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
|
|
197
|
-
let collecting = false;
|
|
198
|
-
let content = 0;
|
|
199
|
-
const out = [];
|
|
200
|
-
for (const l of lines) {
|
|
201
|
-
if (/^##\s/.test(l)) {
|
|
202
|
-
if (collecting) break;
|
|
203
|
-
if (l.trim() === heading) { collecting = true; out.push(l); continue; }
|
|
204
|
-
} else if (collecting) {
|
|
205
|
-
if (l.trim()) {
|
|
206
|
-
out.push(l);
|
|
207
|
-
content++;
|
|
208
|
-
if (content >= 20) break; // cap excerpt
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return collecting ? out : null;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const closureFile = path.join(resultsDir, "06-closure.md");
|
|
216
|
-
const gateSec = sectionExcerpt(closureFile, "## Gate verdicts");
|
|
217
|
-
const testsSec = sectionExcerpt(closureFile, "## Tests");
|
|
218
|
-
|
|
219
|
-
// --- evidence file list: all .md artifacts in results/<mission>/ ---
|
|
220
|
-
let evidenceFiles = [];
|
|
221
|
-
if (fs.existsSync(resultsDir)) {
|
|
222
|
-
evidenceFiles = fs.readdirSync(resultsDir).filter(f => f.endsWith('.md')).sort();
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// --- assemble report ---
|
|
226
|
-
let report = "# Mission: " + mission + " . " + now + "\n\n";
|
|
227
|
-
|
|
228
|
-
report += "## Mission header\n\n| Field | Value |\n|-------|-------|\n";
|
|
229
|
-
report += "| Mission | " + mission + " |\n";
|
|
230
|
-
report += "| Branch | " + branch + " |\n";
|
|
231
|
-
report += "| Lane | " + lane + (laneRose ? " ⬆ ROSE" : "") + " |\n";
|
|
232
|
-
report += "| Lane peak | " + lanePeak + " |\n";
|
|
233
|
-
report += "| Lane reason | " + laneReason + " |\n";
|
|
234
|
-
report += "| Mode | " + mode + " |\n";
|
|
235
|
-
report += "| Flow | " + wave + " |\n";
|
|
236
|
-
report += "| Actor | " + actor + " |\n\n";
|
|
237
|
-
|
|
238
|
-
report += "## Token budget\n\n| Field | Value |\n|-------|-------|\n";
|
|
239
|
-
report += "| Tokens (est) | " + tokens.toLocaleString() + " |\n";
|
|
240
|
-
report += "| Tokens source | " + tokensSource + " |\n";
|
|
241
|
-
report += "| Budget | " + budget.toLocaleString() + " |\n";
|
|
242
|
-
report += "| Budget status | " + budgetStatus + " |\n";
|
|
243
|
-
if (budgetFlag) report += "\n" + budgetFlag;
|
|
244
|
-
report += "\n\n";
|
|
245
|
-
|
|
246
|
-
report += "## Tasks\n\n| Field | Value |\n|-------|-------|\n";
|
|
247
|
-
report += "| Done | " + tasksDone + " |\n";
|
|
248
|
-
report += "| Total | " + tasksTotal + " |\n";
|
|
249
|
-
report += "| Source | " + tasksSource + " |\n\n";
|
|
250
|
-
|
|
251
|
-
report += "## What changed\n\n";
|
|
252
|
-
report += filesTouched + " files, +" + locDelta + " LOC (" + locChurn + " churn)";
|
|
253
|
-
if (sensitive.length) report += "\nSensitive paths: " + sensitive.join(", ");
|
|
254
|
-
report += "\n\n";
|
|
255
|
-
|
|
256
|
-
report += "## Flow stages\n\n| Flow stage | Artifact | Verdict |\n|------|----------|---------|";
|
|
257
|
-
if (waveRows.length) {
|
|
258
|
-
for (const r of waveRows) report += "\n| " + r.label + " | `" + r.file + "` | " + r.verdict + " |";
|
|
259
|
-
} else {
|
|
260
|
-
report += "\n| (no wave artifacts found in `" + resultsDir + "`) | | |";
|
|
261
|
-
}
|
|
262
|
-
report += "\n\n";
|
|
263
|
-
|
|
264
|
-
report += "## Gate & quality\n\n";
|
|
265
|
-
if (gateSec) report += gateSec.join("\n") + "\n";
|
|
266
|
-
if (testsSec) report += (gateSec ? "\n" : "") + testsSec.join("\n") + "\n";
|
|
267
|
-
if (!gateSec && !testsSec) report += "n/a\n";
|
|
268
|
-
report += "\n";
|
|
269
|
-
|
|
270
|
-
report += "## Evidence files\n\n";
|
|
271
|
-
report += evidenceFiles.length ? evidenceFiles.map(f => "- " + f).join("\n") : "n/a";
|
|
272
|
-
report += "\n\n";
|
|
273
|
-
|
|
274
|
-
report += "## Review & blockers\n\n";
|
|
275
|
-
report += "Review + security files: " + (reviewFiles.length ? reviewFiles.join(", ") : "none") + "\n";
|
|
276
|
-
report += "Findings: " + reviewFindings + "\n";
|
|
277
|
-
report += "Blocker ledger rows: " + issueRows.length + (issueRows.length ? "\n" + issueRows.map(r => "- " + r).join("\n") : "");
|
|
278
|
-
report += "\n\n";
|
|
279
|
-
|
|
280
|
-
report += "## State\n\n| Field | Value |\n|-------|-------|\n";
|
|
281
|
-
report += "| Flow | " + wave + " |\n";
|
|
282
|
-
report += "| Tasks | " + (tasks.done || 0) + "/" + (tasks.total || 0) + " done |\n";
|
|
283
|
-
report += "| Blockers open | " + blockers + " |\n";
|
|
284
|
-
report += "| Heal cycles | " + healCycle + " |\n";
|
|
285
|
-
report += "| Tokens used | " + tokens.toLocaleString() + " / " + budget.toLocaleString() + " |\n\n";
|
|
286
|
-
|
|
287
|
-
report += "## Evidence\n\n";
|
|
288
|
-
report += evidence.length ? evidence.map(e => "- " + e).join("\n") : "n/a";
|
|
289
|
-
report += "\n\n## Updated\n\n" + updated + "\n";
|
|
290
|
-
|
|
291
|
-
fs.writeFileSync(reportFile, report);
|
|
292
|
-
console.log("\u2713 mission report: " + reportFile);
|
|
293
|
-
NODE
|