@ionivetech/mugiwara 0.6.5 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -3
- package/.codex-plugin/plugin.json +1 -2
- package/.cursor-plugin/plugin.json +1 -2
- package/.kimi-plugin/plugin.json +1 -2
- package/.opencode/commands/mugiwara-continue.md +44 -31
- package/.opencode/commands/mugiwara-execute.md +1 -1
- package/.opencode/commands/mugiwara-heal.md +1 -1
- package/.opencode/commands/mugiwara-onboard.md +8 -9
- package/.opencode/commands/mugiwara-plan.md +1 -1
- package/.opencode/commands/mugiwara-review.md +1 -1
- package/.opencode/commands/mugiwara-security.md +1 -1
- package/.opencode/commands/mugiwara-ship.md +1 -1
- package/.opencode/commands/mugiwara.md +2 -2
- package/.opencode/plugins/mugiwara.mjs +1 -2
- package/AGENTS.md +1 -2
- package/README.md +33 -31
- package/content/agents/brook-healing.md +5 -8
- package/content/agents/chopper-checkpoint.md +8 -11
- package/content/agents/eval-runner.md +2 -5
- package/content/agents/franky-gates.md +4 -7
- package/content/agents/jinbe-security.md +4 -7
- package/content/agents/luffy-orchestrator.md +13 -13
- package/content/agents/memory-keeper.md +5 -8
- package/content/agents/nami-planner.md +4 -7
- package/content/agents/resume-coordinator.md +7 -10
- package/content/agents/robin-reviewer.md +6 -9
- package/content/agents/sanji-quality.md +7 -10
- package/content/agents/skeptic-verifier.md +5 -8
- package/content/agents/usopp-brainstorm.md +3 -6
- package/content/agents/zoro-execution.md +7 -10
- package/content/skills/mugiwara-backend/SKILL.md +2 -0
- package/content/skills/mugiwara-checkpoint/SKILL.md +13 -11
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +4 -4
- package/content/skills/mugiwara-context-budget/SKILL.md +5 -3
- package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
- package/content/skills/mugiwara-execution/SKILL.md +20 -20
- package/content/skills/mugiwara-execution/references/dispatch.md +2 -2
- package/content/skills/mugiwara-execution/references/resume-batching.md +1 -1
- package/content/skills/mugiwara-frontend/SKILL.md +2 -4
- package/content/skills/mugiwara-gates/SKILL.md +8 -7
- package/content/skills/mugiwara-git/SKILL.md +7 -2
- package/content/skills/mugiwara-healing/SKILL.md +6 -4
- package/content/skills/mugiwara-healing/references/workers.md +2 -2
- package/content/skills/mugiwara-orchestration/SKILL.md +26 -24
- package/content/skills/mugiwara-orchestration/references/check-ins.md +16 -15
- package/content/skills/mugiwara-orchestration/references/closure.md +5 -5
- package/content/skills/mugiwara-orchestration/references/output-contract.md +9 -9
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +22 -22
- package/content/skills/mugiwara-planning/SKILL.md +3 -1
- package/content/skills/mugiwara-planning/references/plan-template.md +2 -2
- package/content/skills/mugiwara-pr/SKILL.md +6 -6
- package/content/skills/mugiwara-pr/references/verdict-format.md +3 -3
- package/content/skills/mugiwara-quality/SKILL.md +7 -7
- package/content/skills/mugiwara-resume/SKILL.md +18 -20
- package/content/skills/mugiwara-review/SKILL.md +5 -1
- 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 +5 -1
- package/content/skills/mugiwara-ship/SKILL.md +1 -1
- package/content/skills/mugiwara-ship/references/cleanup.md +1 -1
- package/content/skills/mugiwara-testcases/SKILL.md +5 -3
- package/content/skills/mugiwara-workflow/SKILL.md +27 -27
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +15 -15
- package/content/skills/using-mugiwara/SKILL.md +5 -5
- package/dist/mugiwara.js +808 -33
- package/gemini-extension.json +1 -1
- package/hooks/auto-savepoint.js +106 -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 +182 -0
- package/hooks/pipeline-guard.ts +267 -0
- package/hooks/session-start.js +106 -0
- package/hooks/session-start.ts +44 -32
- package/package.json +7 -3
- package/plugin.json +1 -1
- package/references/agent-protocol.md +15 -0
- package/references/complexity.md +54 -0
- package/references/definition-of-done.md +1 -1
- package/references/multi-actor.md +5 -5
- package/references/skill-versioning.md +1 -1
- package/references/token-budget.md +30 -8
- package/references/wave-banners.md +16 -16
- package/scripts/build-hooks.ts +56 -0
- package/scripts/conformance.ts +17 -0
- package/scripts/coverage-gate.ts +179 -0
- package/scripts/install.ps1 +5 -4
- package/scripts/install.sh +4 -4
- package/scripts/lane.sh +6 -2
- package/scripts/lib/patterns.sh +84 -0
- package/scripts/mission-report.sh +12 -11
- package/scripts/savepoint.sh +141 -37
- package/scripts/verify-install.ts +154 -44
- package/src/args.ts +1 -1
- package/src/cli.ts +217 -0
- package/src/continue.ts +243 -0
- package/src/installer.ts +39 -0
- package/src/onboard.ts +207 -0
- package/src/run.ts +82 -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/content/agents/onboarding-guide.md +0 -124
- package/content/skills/mugiwara-security/references/owasp-mapping.md +0 -30
- package/scripts/onboard.ts +0 -266
- package/scripts/probe.ts +0 -40
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// scripts/coverage-gate.ts — enforce the `coverage_new` / `coverage_modified`
|
|
3
|
+
// thresholds declared in `.mugiwara/config` against the files THIS diff adds
|
|
4
|
+
// and changes, not against a global project number. Two keys exist precisely
|
|
5
|
+
// because the two populations are judged differently (mugiwara-gates SKILL.md
|
|
6
|
+
// §Coverage gate); a global percentage would collapse them into one and let an
|
|
7
|
+
// untested new file hide behind an old well-tested one.
|
|
8
|
+
//
|
|
9
|
+
// Run: bun scripts/coverage-gate.ts enforce (gate step)
|
|
10
|
+
// bun scripts/coverage-gate.ts --base X compare against ref X
|
|
11
|
+
// bun scripts/coverage-gate.ts --show print per-file numbers, never fail
|
|
12
|
+
//
|
|
13
|
+
// Exit 0 = pass or recorded SKIP. Exit 1 = a threshold was missed.
|
|
14
|
+
// A repo with no coverage tooling or no test suite records a SKIP with its
|
|
15
|
+
// reason — never a fake pass (decision log row 50).
|
|
16
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
17
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { join, relative, sep } from 'node:path';
|
|
20
|
+
|
|
21
|
+
const root = join(import.meta.dirname, '..');
|
|
22
|
+
const SHOW = process.argv.includes('--show');
|
|
23
|
+
|
|
24
|
+
/** `.mugiwara/config` → project, then `~`. Returns undefined if the key is set nowhere. */
|
|
25
|
+
function config(key: string): string | undefined {
|
|
26
|
+
for (const base of [root, homedir()]) {
|
|
27
|
+
const file = join(base, '.mugiwara', 'config');
|
|
28
|
+
if (!existsSync(file)) continue;
|
|
29
|
+
// \r?\n: a config written on Windows is still a config
|
|
30
|
+
for (const line of readFileSync(file, 'utf8').split(/\r?\n/)) {
|
|
31
|
+
const t = line.trim();
|
|
32
|
+
if (!t || t.startsWith('#')) continue;
|
|
33
|
+
const eq = t.indexOf('=');
|
|
34
|
+
if (eq === -1) continue;
|
|
35
|
+
if (t.slice(0, eq).trim() !== key) continue;
|
|
36
|
+
return t.slice(eq + 1).trim();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Documented defaults: new >= 90, modified >= 80. A key set to 0 = no threshold. */
|
|
43
|
+
function threshold(key: string, fallback: number): number {
|
|
44
|
+
const raw = config(key);
|
|
45
|
+
if (raw === undefined || raw === '') return fallback;
|
|
46
|
+
const n = Number(raw);
|
|
47
|
+
if (!Number.isFinite(n) || n < 0 || n > 100) {
|
|
48
|
+
console.log(`coverage-gate: ignoring unparseable ${key}="${raw}", using ${fallback}`);
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
return n;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function skip(reason: string): never {
|
|
55
|
+
console.log(`coverage-gate: SKIP — ${reason}`);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function git(args: string[]): string {
|
|
60
|
+
return execFileSync('git', args, { cwd: root, encoding: 'utf8' }).trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---- 1. is there anything to measure at all? -------------------------------
|
|
64
|
+
|
|
65
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
66
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
67
|
+
if (!deps['@vitest/coverage-v8'] && !deps['@vitest/coverage-istanbul'])
|
|
68
|
+
skip('no coverage tooling installed (no @vitest/coverage-* in package.json)');
|
|
69
|
+
if (!existsSync(join(root, 'test')))
|
|
70
|
+
skip('no test suite found (no test/ directory)');
|
|
71
|
+
|
|
72
|
+
// ---- 2. which files does this diff add and change? ------------------------
|
|
73
|
+
|
|
74
|
+
const baseArg = process.argv.indexOf('--base');
|
|
75
|
+
let base = baseArg !== -1 ? process.argv[baseArg + 1] : process.env.MUGIWARA_COVERAGE_BASE;
|
|
76
|
+
if (!base) {
|
|
77
|
+
// the mission's own recorded base_sha is the truthful diff origin
|
|
78
|
+
const stateDir = join(root, '.mugiwara', 'state');
|
|
79
|
+
if (existsSync(stateDir)) {
|
|
80
|
+
for (const m of readdirSync(stateDir)) {
|
|
81
|
+
const f = join(stateDir, m, 'state.json');
|
|
82
|
+
if (!existsSync(f)) continue;
|
|
83
|
+
const sha = JSON.parse(readFileSync(f, 'utf8')).base_sha;
|
|
84
|
+
if (sha) { base = sha; break; }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!base) {
|
|
89
|
+
for (const ref of ['origin/main', 'main', 'origin/master', 'master']) {
|
|
90
|
+
try { base = git(['merge-base', 'HEAD', ref]); break; } catch { /* next */ }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!base) skip('no diff base could be resolved (no state base_sha, no main/master ref)');
|
|
94
|
+
|
|
95
|
+
let changed: Array<{ status: string; path: string }>;
|
|
96
|
+
try {
|
|
97
|
+
changed = git(['diff', '--name-status', '--diff-filter=AMR', '-M', `${base}`, 'HEAD'])
|
|
98
|
+
.split(/\r?\n/)
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.map((l) => {
|
|
101
|
+
const parts = l.split('\t');
|
|
102
|
+
// rename rows are `R096\told\tnew` — the new path is the one to judge
|
|
103
|
+
return { status: parts[0][0], path: parts[parts.length - 1] };
|
|
104
|
+
});
|
|
105
|
+
} catch (e) {
|
|
106
|
+
skip(`git diff against ${base} failed (${(e as Error).message})`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!changed.length) skip(`no added or modified files between ${base.slice(0, 7)} and HEAD`);
|
|
110
|
+
|
|
111
|
+
// ---- 3. measure ------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
const summaryPath = join(root, 'coverage', 'coverage-summary.json');
|
|
114
|
+
const needsRun =
|
|
115
|
+
!existsSync(summaryPath) ||
|
|
116
|
+
statSync(summaryPath).mtimeMs < Math.max(...changed
|
|
117
|
+
.map((c) => join(root, c.path))
|
|
118
|
+
.filter(existsSync)
|
|
119
|
+
.map((p) => statSync(p).mtimeMs), 0);
|
|
120
|
+
|
|
121
|
+
if (needsRun) {
|
|
122
|
+
console.log('coverage-gate: measuring (vitest run --coverage)...');
|
|
123
|
+
const r = spawnSync('npx', ['vitest', 'run', '--coverage', '--silent'], {
|
|
124
|
+
cwd: root, stdio: 'inherit', shell: process.platform === 'win32',
|
|
125
|
+
});
|
|
126
|
+
if (r.status !== 0) {
|
|
127
|
+
console.log('coverage-gate: FAIL — test run did not pass, coverage is not measurable');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!existsSync(summaryPath))
|
|
132
|
+
skip('coverage run produced no coverage-summary.json (json-summary reporter not configured)');
|
|
133
|
+
|
|
134
|
+
const summary = JSON.parse(readFileSync(summaryPath, 'utf8'));
|
|
135
|
+
// keys are absolute OS paths; normalise to repo-relative posix to match git
|
|
136
|
+
const measured = new Map<string, number>();
|
|
137
|
+
for (const [k, v] of Object.entries<any>(summary)) {
|
|
138
|
+
if (k === 'total') continue;
|
|
139
|
+
measured.set(relative(root, k).split(sep).join('/'), v.lines?.pct ?? 0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- 4. apply the two thresholds ------------------------------------------
|
|
143
|
+
|
|
144
|
+
const NEW = threshold('coverage_new', 90);
|
|
145
|
+
const MOD = threshold('coverage_modified', 80);
|
|
146
|
+
|
|
147
|
+
const rows = changed.map((c) => {
|
|
148
|
+
const isNew = c.status === 'A';
|
|
149
|
+
return {
|
|
150
|
+
path: c.path,
|
|
151
|
+
kind: isNew ? 'new' : 'modified',
|
|
152
|
+
limit: isNew ? NEW : MOD,
|
|
153
|
+
pct: measured.get(c.path),
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const scoped = rows.filter((r) => r.pct !== undefined);
|
|
158
|
+
const unscoped = rows.filter((r) => r.pct === undefined);
|
|
159
|
+
|
|
160
|
+
console.log(`coverage-gate: base ${base.slice(0, 7)} · thresholds new>=${NEW || 'off'} modified>=${MOD || 'off'}`);
|
|
161
|
+
console.log(` ${changed.length} changed file(s), ${scoped.length} within coverage scope, ${unscoped.length} outside it`);
|
|
162
|
+
|
|
163
|
+
const failures = scoped.filter((r) => r.limit > 0 && (r.pct as number) < r.limit);
|
|
164
|
+
|
|
165
|
+
for (const r of scoped.sort((a, b) => (a.pct as number) - (b.pct as number))) {
|
|
166
|
+
const bad = r.limit > 0 && (r.pct as number) < r.limit;
|
|
167
|
+
if (SHOW || bad || (r.pct as number) < 100)
|
|
168
|
+
console.log(` ${bad ? '✗' : '✓'} ${r.path} — ${(r.pct as number).toFixed(2)}% ${r.kind} (limit ${r.limit || 'off'})`);
|
|
169
|
+
}
|
|
170
|
+
if (SHOW && unscoped.length)
|
|
171
|
+
console.log(unscoped.map((r) => ` · ${r.path} — outside coverage scope (not a measured source file)`).join('\n'));
|
|
172
|
+
|
|
173
|
+
if (SHOW) process.exit(0);
|
|
174
|
+
if (failures.length) {
|
|
175
|
+
console.log(`\ncoverage-gate: FAIL — ${failures.length} file(s) below their threshold`);
|
|
176
|
+
console.log('Do not lower the thresholds or exclude files to pass: add the missing tests.');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
console.log('\ncoverage-gate: PASS');
|
package/scripts/install.ps1
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
$ErrorActionPreference = 'Stop'
|
|
3
3
|
|
|
4
4
|
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
|
|
5
|
-
Write-Error 'mugiwara requires Node.js >= 20. Install it from https://nodejs.org first.'
|
|
5
|
+
Write-Error 'mugiwara requires Node.js >= 20.11. Install it from https://nodejs.org first.'
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
# floor must match package.json "engines.node" exactly, and install.sh
|
|
9
|
+
node -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>20||(a===20&&b>=11)?0:1)'
|
|
10
|
+
if ($LASTEXITCODE -ne 0) {
|
|
11
|
+
Write-Error "mugiwara requires Node.js >= 20.11 (found $(node --version))."
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
npx -y @ionivetech/mugiwara@latest @args
|
package/scripts/install.sh
CHANGED
|
@@ -4,13 +4,13 @@ set -euo pipefail
|
|
|
4
4
|
PKG="@ionivetech/mugiwara"
|
|
5
5
|
|
|
6
6
|
if ! command -v node >/dev/null 2>&1; then
|
|
7
|
-
echo "mugiwara requires Node.js >= 20. Install it first: https://nodejs.org" >&2
|
|
7
|
+
echo "mugiwara requires Node.js >= 20.11. Install it first: https://nodejs.org" >&2
|
|
8
8
|
exit 1
|
|
9
9
|
fi
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
if
|
|
13
|
-
echo "mugiwara requires Node.js >= 20 (found $(node --version))." >&2
|
|
11
|
+
# floor must match package.json "engines.node" exactly, and install.ps1
|
|
12
|
+
if ! node -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>20||(a===20&&b>=11)?0:1)'; then
|
|
13
|
+
echo "mugiwara requires Node.js >= 20.11 (found $(node --version))." >&2
|
|
14
14
|
exit 1
|
|
15
15
|
fi
|
|
16
16
|
|
package/scripts/lane.sh
CHANGED
|
@@ -19,9 +19,13 @@ if ! git rev-parse "$BASE" >/dev/null 2>&1; then
|
|
|
19
19
|
[ -n "$ALT" ] && BASE="$ALT" || BASE="HEAD~1"
|
|
20
20
|
fi
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
# union of committed + staged + unstaged + untracked (F) — see patterns.sh
|
|
23
|
+
CHANGED=$(changed_files "$BASE")
|
|
23
24
|
FILE_COUNT=0
|
|
24
25
|
[ -n "$CHANGED" ] && FILE_COUNT=$(echo "$CHANGED" | wc -l | tr -d ' ')
|
|
26
|
+
read -r ADDED_INS ADDED_DEL <<EOF
|
|
27
|
+
$(changed_loc "$BASE")
|
|
28
|
+
EOF
|
|
25
29
|
|
|
26
30
|
SENSITIVE=$(echo "$CHANGED" | grep -E "$SENSITIVE_PATS" 2>/dev/null | tr '\n' ',' | sed 's/,$//' || true)
|
|
27
31
|
HAS_SENSITIVE=0
|
|
@@ -35,7 +39,7 @@ if [ "$FILE_COUNT" -eq 0 ] 2>/dev/null; then
|
|
|
35
39
|
LANE="direct"
|
|
36
40
|
REASON="no changed files"
|
|
37
41
|
elif [ "$FILE_COUNT" -le 1 ] 2>/dev/null; then
|
|
38
|
-
ADDED
|
|
42
|
+
ADDED="${ADDED_INS:-0}"
|
|
39
43
|
if [ "$ADDED" -lt 20 ] 2>/dev/null; then
|
|
40
44
|
LANE="direct"
|
|
41
45
|
REASON="1 file, <20 LOC"
|
package/scripts/lib/patterns.sh
CHANGED
|
@@ -19,3 +19,87 @@ SENSITIVE_PATS="auth/|oauth2?/|payment/|payments/|billing/|crypto/|secrets/|cred
|
|
|
19
19
|
# outside this surface are docs/config/asset and never escalate to full on
|
|
20
20
|
# count alone (path-weighted sizing).
|
|
21
21
|
PRODUCT_PAT="^content/|^src/|^scripts/|^test/|^hooks/|^\.opencode/|^\.claude/|^evals/"
|
|
22
|
+
|
|
23
|
+
# Installed-harness rules dirs — what `mugiwara install` writes into a project
|
|
24
|
+
# (~50 files across the 9 targets). In a CONSUMER project these are installed
|
|
25
|
+
# config, no different from the crew's own .mugiwara/ bookkeeping: the first
|
|
26
|
+
# savepoint after an install counted them and sized every fresh mission Lane 3
|
|
27
|
+
# before a single line of the user's own work existed. In MUGIWARA'S OWN repo
|
|
28
|
+
# the same dirs are the product surface (they are in PRODUCT_PAT), so the
|
|
29
|
+
# exclusion is conditional on the repo identity, never unconditional.
|
|
30
|
+
# Unanchored on purpose — call sites anchor with ^ (name lists) or a tab
|
|
31
|
+
# (numstat rows).
|
|
32
|
+
HARNESS_PAT="\.claude/|\.opencode/|\.github/(instructions|agents)/|\.gemini/mugiwara/|\.codex/mugiwara/|\.devin/rules/|\.clinerules/|\.kilo/rules/|\.agents/rules/"
|
|
33
|
+
|
|
34
|
+
# harness_excluded — true (exit 0) when harness rules dirs must NOT count
|
|
35
|
+
# toward mission sizing, i.e. this repo is not mugiwara itself. Identity comes
|
|
36
|
+
# from the repo root package.json name ("mugiwara" or "@scope/mugiwara"), the
|
|
37
|
+
# same manifest savepoint already trusts for skill_version.
|
|
38
|
+
harness_excluded() {
|
|
39
|
+
local root
|
|
40
|
+
root=$(git rev-parse --show-toplevel 2>/dev/null || echo .)
|
|
41
|
+
! grep -qE '"name"[[:space:]]*:[[:space:]]*"(@[^"/]+/)?mugiwara"' "$root/package.json" 2>/dev/null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# drop_harness <anchor> — filter stdin, dropping harness paths in consumer
|
|
45
|
+
# repos and passing everything through in mugiwara's own.
|
|
46
|
+
drop_harness() {
|
|
47
|
+
if harness_excluded; then grep -vE "$1($HARNESS_PAT)" || true; else cat; fi
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# --- working-tree-aware change set (F) -------------------------------------
|
|
51
|
+
# `git diff BASE..HEAD` alone is blind to uncommitted work, and the old
|
|
52
|
+
# `|| git diff --cached` fallback was dead code (|| fires on non-zero exit,
|
|
53
|
+
# never on empty output). With auto_commit=off nothing is committed, so lane
|
|
54
|
+
# sizing and files_touched read 0 while the tree holds real changes.
|
|
55
|
+
# These two helpers are the single source of truth for BOTH lane.sh and
|
|
56
|
+
# savepoint.sh so the two can never drift (lane-integrity case 15).
|
|
57
|
+
|
|
58
|
+
# Path of the crew's bookkeeping dir, relative to the repo root. MUGIWARA_DIR
|
|
59
|
+
# may be absolute (the harness passes a full path); git reports repo-relative
|
|
60
|
+
# paths, so normalize before comparing.
|
|
61
|
+
mugiwara_rel() {
|
|
62
|
+
local d="${MUGIWARA_DIR:-.mugiwara}" root
|
|
63
|
+
case "$d" in
|
|
64
|
+
/*) root=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
|
65
|
+
[ -n "$root" ] && d="${d#"$root"/}"
|
|
66
|
+
# symlinked repo roots (/var vs /private/var on macOS) defeat the
|
|
67
|
+
# prefix strip; fall back to the leaf name, which is what git reports.
|
|
68
|
+
case "$d" in /*) d=$(basename "$d") ;; esac ;;
|
|
69
|
+
esac
|
|
70
|
+
printf '%s' "$d"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# changed_files <base-ref> — union of committed (BASE..HEAD), staged, unstaged
|
|
74
|
+
# and untracked files. .gitignore'd files are excluded (git status default),
|
|
75
|
+
# and so is the crew's own .mugiwara/ bookkeeping: savepoint writes state on
|
|
76
|
+
# every wave, and mission state must never size the mission it measures.
|
|
77
|
+
changed_files() {
|
|
78
|
+
local base="${1:-}"
|
|
79
|
+
{
|
|
80
|
+
if [ -n "$base" ] && [ "$base" != "unknown" ]; then
|
|
81
|
+
git diff --name-only "$base"..HEAD 2>/dev/null || true
|
|
82
|
+
fi
|
|
83
|
+
# porcelain covers staged + unstaged + untracked in one pass; strip the
|
|
84
|
+
# 2-char status + space, and keep the destination side of a rename.
|
|
85
|
+
git status --porcelain --untracked-files=all 2>/dev/null | sed 's/^...//; s/^.* -> //' || true
|
|
86
|
+
} | grep -v '^[[:space:]]*$' | grep -v "^$(mugiwara_rel)/" | drop_harness '^' | sort -u
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# changed_loc <base-ref> — echoes "<insertions> <deletions>" over the same
|
|
90
|
+
# union. Untracked files count every line as an insertion.
|
|
91
|
+
changed_loc() {
|
|
92
|
+
local base="${1:-}" MUGI_REL
|
|
93
|
+
MUGI_REL=$(mugiwara_rel)
|
|
94
|
+
{
|
|
95
|
+
if [ -n "$base" ] && [ "$base" != "unknown" ]; then
|
|
96
|
+
git diff --numstat "$base"..HEAD 2>/dev/null || true
|
|
97
|
+
fi
|
|
98
|
+
git diff --numstat HEAD 2>/dev/null || true
|
|
99
|
+
git ls-files --others --exclude-standard 2>/dev/null | while IFS= read -r f; do
|
|
100
|
+
[ -f "$f" ] || continue
|
|
101
|
+
case "$f" in "$MUGI_REL"/*) continue ;; esac
|
|
102
|
+
printf '%s\t0\t%s\n' "$(wc -l < "$f" 2>/dev/null | tr -d ' ' || echo 0)" "$f"
|
|
103
|
+
done
|
|
104
|
+
} | grep -v " $MUGI_REL/" | drop_harness ' ' | awk '{ if ($1 ~ /^[0-9]+$/) i+=$1; if ($2 ~ /^[0-9]+$/) d+=$2 } END { print (i+0)" "(d+0) }'
|
|
105
|
+
}
|
|
@@ -70,11 +70,12 @@ const now = new Date().toISOString().slice(0, 10);
|
|
|
70
70
|
const reportFile = path.join(reportDir, now + "-" + mission + ".md");
|
|
71
71
|
const lane = val(s, 'lane', 'n/a');
|
|
72
72
|
const lanePeak = val(s, 'lane_peak', 'n/a');
|
|
73
|
+
const laneRose = val(s, 'lane_rose', false);
|
|
73
74
|
const laneReason = val(s, 'lane_reason', 'n/a');
|
|
74
75
|
const mode = val(s, 'mode', 'n/a');
|
|
75
76
|
const actor = val(s, 'actor', 'n/a');
|
|
76
77
|
const branch = val(s, 'branch', 'n/a');
|
|
77
|
-
const wave = val(s, 'wave', 'n/a');
|
|
78
|
+
const wave = val(s, 'flow', val(s, 'wave', 'n/a'));
|
|
78
79
|
const filesTouched = val(s, 'files_touched', 0);
|
|
79
80
|
const locDelta = val(s, 'loc_delta', 0);
|
|
80
81
|
const locChurn = val(s, 'loc_churn', 0);
|
|
@@ -91,12 +92,12 @@ const updated = val(s, 'updated_at', now);
|
|
|
91
92
|
|
|
92
93
|
// --- wave artifact scan: results/<mission>/NN-*.md with verdict sniffing ---
|
|
93
94
|
const WAVE_LABEL = {
|
|
94
|
-
"01-execution": "Execute (
|
|
95
|
-
"02-audit": "Checkpoint (
|
|
96
|
-
"03-quality": "Quality (
|
|
97
|
-
"04-gates": "Gates (
|
|
98
|
-
"05-healing": "Healing (
|
|
99
|
-
"06-closure": "Closure (
|
|
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)",
|
|
100
101
|
"07-pr-verdict": "PR material",
|
|
101
102
|
};
|
|
102
103
|
|
|
@@ -227,11 +228,11 @@ let report = "# Mission: " + mission + " . " + now + "\n\n";
|
|
|
227
228
|
report += "## Mission header\n\n| Field | Value |\n|-------|-------|\n";
|
|
228
229
|
report += "| Mission | " + mission + " |\n";
|
|
229
230
|
report += "| Branch | " + branch + " |\n";
|
|
230
|
-
report += "| Lane | " + lane + " |\n";
|
|
231
|
+
report += "| Lane | " + lane + (laneRose ? " ⬆ ROSE" : "") + " |\n";
|
|
231
232
|
report += "| Lane peak | " + lanePeak + " |\n";
|
|
232
233
|
report += "| Lane reason | " + laneReason + " |\n";
|
|
233
234
|
report += "| Mode | " + mode + " |\n";
|
|
234
|
-
report += "|
|
|
235
|
+
report += "| Flow | " + wave + " |\n";
|
|
235
236
|
report += "| Actor | " + actor + " |\n\n";
|
|
236
237
|
|
|
237
238
|
report += "## Token budget\n\n| Field | Value |\n|-------|-------|\n";
|
|
@@ -252,7 +253,7 @@ report += filesTouched + " files, +" + locDelta + " LOC (" + locChurn + " churn)
|
|
|
252
253
|
if (sensitive.length) report += "\nSensitive paths: " + sensitive.join(", ");
|
|
253
254
|
report += "\n\n";
|
|
254
255
|
|
|
255
|
-
report += "##
|
|
256
|
+
report += "## Flow stages\n\n| Flow stage | Artifact | Verdict |\n|------|----------|---------|";
|
|
256
257
|
if (waveRows.length) {
|
|
257
258
|
for (const r of waveRows) report += "\n| " + r.label + " | `" + r.file + "` | " + r.verdict + " |";
|
|
258
259
|
} else {
|
|
@@ -277,7 +278,7 @@ report += "Blocker ledger rows: " + issueRows.length + (issueRows.length ? "\n"
|
|
|
277
278
|
report += "\n\n";
|
|
278
279
|
|
|
279
280
|
report += "## State\n\n| Field | Value |\n|-------|-------|\n";
|
|
280
|
-
report += "|
|
|
281
|
+
report += "| Flow | " + wave + " |\n";
|
|
281
282
|
report += "| Tasks | " + (tasks.done || 0) + "/" + (tasks.total || 0) + " done |\n";
|
|
282
283
|
report += "| Blockers open | " + blockers + " |\n";
|
|
283
284
|
report += "| Heal cycles | " + healCycle + " |\n";
|