@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +3 -9
- package/.codex-plugin/plugin.json +2 -8
- package/.cursor-plugin/plugin.json +2 -8
- package/.kimi-plugin/plugin.json +2 -8
- package/.opencode/commands/mugiwara-continue.md +44 -31
- package/.opencode/commands/mugiwara-review.md +1 -1
- package/.opencode/commands/mugiwara-security.md +1 -1
- package/.opencode/commands/mugiwara.md +2 -2
- package/.opencode/plugins/mugiwara.mjs +3 -19
- package/AGENTS.md +6 -6
- package/GEMINI.md +3 -3
- package/README.md +82 -74
- package/content/agents/brook-healing.md +12 -11
- package/content/agents/chopper-checkpoint.md +14 -13
- package/content/agents/eval-runner.md +6 -9
- package/content/agents/franky-gates.md +7 -10
- package/content/agents/jinbe-security.md +10 -14
- package/content/agents/luffy-orchestrator.md +20 -22
- package/content/agents/memory-keeper.md +9 -12
- package/content/agents/nami-planner.md +7 -10
- package/content/agents/resume-coordinator.md +12 -15
- package/content/agents/robin-reviewer.md +13 -12
- package/content/agents/sanji-quality.md +9 -12
- package/content/agents/skeptic-verifier.md +7 -10
- package/content/agents/usopp-brainstorm.md +6 -9
- package/content/agents/zoro-execution.md +9 -12
- package/content/skills/mugiwara-backend/SKILL.md +4 -0
- 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 +17 -14
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +5 -5
- package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
- package/content/skills/mugiwara-execution/SKILL.md +24 -24
- package/content/skills/mugiwara-execution/references/dispatch.md +3 -3
- package/content/skills/mugiwara-execution/references/resume-batching.md +2 -2
- package/content/skills/mugiwara-frontend/SKILL.md +5 -6
- package/content/skills/mugiwara-frontend/references/checklist.md +14 -0
- package/content/skills/mugiwara-gates/SKILL.md +14 -10
- package/content/skills/mugiwara-git/SKILL.md +7 -2
- package/content/skills/mugiwara-healing/SKILL.md +7 -5
- package/content/skills/mugiwara-healing/references/workers.md +2 -2
- package/content/skills/mugiwara-lessons/SKILL.md +2 -2
- package/content/skills/mugiwara-orchestration/SKILL.md +28 -26
- package/content/skills/mugiwara-orchestration/references/check-ins.md +17 -16
- package/content/skills/mugiwara-orchestration/references/closure.md +18 -13
- package/content/skills/mugiwara-orchestration/references/output-contract.md +9 -9
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +32 -22
- package/content/skills/mugiwara-planning/SKILL.md +5 -18
- package/content/skills/mugiwara-planning/references/plan-template.md +3 -3
- package/content/skills/mugiwara-quality/SKILL.md +10 -7
- package/content/skills/mugiwara-resume/SKILL.md +20 -22
- package/content/skills/mugiwara-review/SKILL.md +7 -2
- package/content/skills/mugiwara-review/references/five-axis-worksheet.md +1 -1
- package/content/skills/mugiwara-root-cause/SKILL.md +2 -0
- package/content/skills/mugiwara-security/SKILL.md +6 -2
- package/content/skills/mugiwara-ship/SKILL.md +2 -2
- package/content/skills/mugiwara-ship/references/cleanup.md +17 -16
- package/content/skills/mugiwara-testcases/SKILL.md +5 -3
- package/content/skills/mugiwara-workflow/SKILL.md +32 -35
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +41 -40
- package/dist/mugiwara.js +1370 -153
- package/gemini-extension.json +1 -1
- package/hooks/auto-savepoint.js +114 -0
- package/hooks/auto-savepoint.ts +102 -0
- package/hooks/engagement-marker.js +59 -0
- package/hooks/engagement-marker.ts +97 -0
- package/hooks/hooks.json +41 -2
- package/hooks/mugiwara-mode-tracker.js +79 -0
- package/hooks/pipeline-guard.js +186 -0
- package/hooks/pipeline-guard.ts +270 -0
- package/hooks/session-start.js +109 -0
- package/hooks/session-start.ts +62 -45
- package/package.json +7 -3
- package/plugin.json +1 -1
- package/references/agent-protocol.md +15 -0
- package/references/complexity.md +77 -0
- package/references/definition-of-done.md +1 -1
- package/references/multi-actor.md +4 -7
- package/references/prose-style.md +54 -0
- package/references/skill-versioning.md +1 -1
- package/references/wave-banners.md +16 -16
- package/scripts/build-hooks.ts +56 -0
- package/scripts/check-doc-links.ts +40 -0
- package/scripts/conformance.ts +23 -16
- package/scripts/coverage-gate.ts +193 -0
- package/scripts/gate-selftest.ts +118 -7
- package/scripts/install.ps1 +5 -4
- package/scripts/install.sh +4 -4
- package/scripts/lane.sh +56 -6
- package/scripts/lib/patterns.sh +89 -0
- package/scripts/policy-force.ts +22 -0
- package/scripts/retrieval-eval.ts +1 -1
- package/scripts/savepoint.sh +249 -67
- package/scripts/validate-content.ts +22 -9
- package/scripts/verify-install.ts +156 -44
- package/src/args.ts +2 -2
- package/src/budget.ts +56 -0
- package/src/cli.ts +272 -4
- package/src/continue.ts +262 -0
- package/src/installer.ts +59 -15
- package/src/integrity.ts +158 -0
- package/src/mission.ts +228 -95
- package/src/policy.ts +156 -0
- package/src/provenance.ts +116 -0
- package/src/rollback.ts +95 -0
- package/src/routing.ts +69 -0
- package/src/run.ts +82 -0
- package/src/sign.ts +61 -0
- package/src/targets/claude.ts +140 -2
- package/src/targets/copilot.ts +20 -1
- package/src/targets/generic.ts +9 -4
- package/src/targets/opencode.ts +0 -1
- package/.opencode/commands/mugiwara-execute.md +0 -13
- package/.opencode/commands/mugiwara-heal.md +0 -13
- package/.opencode/commands/mugiwara-onboard.md +0 -15
- 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/agents/onboarding-guide.md +0 -124
- 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 -64
- 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-security/references/owasp-mapping.md +0 -30
- package/content/skills/mugiwara-sunset/SKILL.md +0 -82
- package/content/skills/using-mugiwara/SKILL.md +0 -51
- package/references/token-budget.md +0 -34
- package/scripts/evidence.sh +0 -81
- package/scripts/initiative.ts +0 -296
- package/scripts/mission-report.sh +0 -292
- package/scripts/onboard.ts +0 -266
- package/scripts/probe.ts +0 -40
package/scripts/evidence.sh
DELETED
|
@@ -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
|
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,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
|