@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.
Files changed (110) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.codex-plugin/plugin.json +1 -2
  4. package/.cursor-plugin/plugin.json +1 -2
  5. package/.kimi-plugin/plugin.json +1 -2
  6. package/.opencode/commands/mugiwara-continue.md +44 -31
  7. package/.opencode/commands/mugiwara-execute.md +1 -1
  8. package/.opencode/commands/mugiwara-heal.md +1 -1
  9. package/.opencode/commands/mugiwara-onboard.md +8 -9
  10. package/.opencode/commands/mugiwara-plan.md +1 -1
  11. package/.opencode/commands/mugiwara-review.md +1 -1
  12. package/.opencode/commands/mugiwara-security.md +1 -1
  13. package/.opencode/commands/mugiwara-ship.md +1 -1
  14. package/.opencode/commands/mugiwara.md +2 -2
  15. package/.opencode/plugins/mugiwara.mjs +1 -2
  16. package/AGENTS.md +1 -2
  17. package/README.md +33 -31
  18. package/content/agents/brook-healing.md +5 -8
  19. package/content/agents/chopper-checkpoint.md +8 -11
  20. package/content/agents/eval-runner.md +2 -5
  21. package/content/agents/franky-gates.md +4 -7
  22. package/content/agents/jinbe-security.md +4 -7
  23. package/content/agents/luffy-orchestrator.md +13 -13
  24. package/content/agents/memory-keeper.md +5 -8
  25. package/content/agents/nami-planner.md +4 -7
  26. package/content/agents/resume-coordinator.md +7 -10
  27. package/content/agents/robin-reviewer.md +6 -9
  28. package/content/agents/sanji-quality.md +7 -10
  29. package/content/agents/skeptic-verifier.md +5 -8
  30. package/content/agents/usopp-brainstorm.md +3 -6
  31. package/content/agents/zoro-execution.md +7 -10
  32. package/content/skills/mugiwara-backend/SKILL.md +2 -0
  33. package/content/skills/mugiwara-checkpoint/SKILL.md +13 -11
  34. package/content/skills/mugiwara-checkpoint/references/ledger-format.md +4 -4
  35. package/content/skills/mugiwara-context-budget/SKILL.md +5 -3
  36. package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
  37. package/content/skills/mugiwara-execution/SKILL.md +20 -20
  38. package/content/skills/mugiwara-execution/references/dispatch.md +2 -2
  39. package/content/skills/mugiwara-execution/references/resume-batching.md +1 -1
  40. package/content/skills/mugiwara-frontend/SKILL.md +2 -4
  41. package/content/skills/mugiwara-gates/SKILL.md +8 -7
  42. package/content/skills/mugiwara-git/SKILL.md +7 -2
  43. package/content/skills/mugiwara-healing/SKILL.md +6 -4
  44. package/content/skills/mugiwara-healing/references/workers.md +2 -2
  45. package/content/skills/mugiwara-orchestration/SKILL.md +26 -24
  46. package/content/skills/mugiwara-orchestration/references/check-ins.md +16 -15
  47. package/content/skills/mugiwara-orchestration/references/closure.md +5 -5
  48. package/content/skills/mugiwara-orchestration/references/output-contract.md +9 -9
  49. package/content/skills/mugiwara-orchestration/references/triage-escalation.md +22 -22
  50. package/content/skills/mugiwara-planning/SKILL.md +3 -1
  51. package/content/skills/mugiwara-planning/references/plan-template.md +2 -2
  52. package/content/skills/mugiwara-pr/SKILL.md +6 -6
  53. package/content/skills/mugiwara-pr/references/verdict-format.md +3 -3
  54. package/content/skills/mugiwara-quality/SKILL.md +7 -7
  55. package/content/skills/mugiwara-resume/SKILL.md +18 -20
  56. package/content/skills/mugiwara-review/SKILL.md +5 -1
  57. package/content/skills/mugiwara-review/references/five-axis-worksheet.md +1 -1
  58. package/content/skills/mugiwara-root-cause/SKILL.md +2 -0
  59. package/content/skills/mugiwara-security/SKILL.md +5 -1
  60. package/content/skills/mugiwara-ship/SKILL.md +1 -1
  61. package/content/skills/mugiwara-ship/references/cleanup.md +1 -1
  62. package/content/skills/mugiwara-testcases/SKILL.md +5 -3
  63. package/content/skills/mugiwara-workflow/SKILL.md +27 -27
  64. package/content/skills/mugiwara-workflow/references/workspace-layout.md +15 -15
  65. package/content/skills/using-mugiwara/SKILL.md +5 -5
  66. package/dist/mugiwara.js +808 -33
  67. package/gemini-extension.json +1 -1
  68. package/hooks/auto-savepoint.js +106 -0
  69. package/hooks/auto-savepoint.ts +102 -0
  70. package/hooks/engagement-marker.js +59 -0
  71. package/hooks/engagement-marker.ts +97 -0
  72. package/hooks/hooks.json +41 -2
  73. package/hooks/mugiwara-mode-tracker.js +79 -0
  74. package/hooks/pipeline-guard.js +182 -0
  75. package/hooks/pipeline-guard.ts +267 -0
  76. package/hooks/session-start.js +106 -0
  77. package/hooks/session-start.ts +44 -32
  78. package/package.json +7 -3
  79. package/plugin.json +1 -1
  80. package/references/agent-protocol.md +15 -0
  81. package/references/complexity.md +54 -0
  82. package/references/definition-of-done.md +1 -1
  83. package/references/multi-actor.md +5 -5
  84. package/references/skill-versioning.md +1 -1
  85. package/references/token-budget.md +30 -8
  86. package/references/wave-banners.md +16 -16
  87. package/scripts/build-hooks.ts +56 -0
  88. package/scripts/conformance.ts +17 -0
  89. package/scripts/coverage-gate.ts +179 -0
  90. package/scripts/install.ps1 +5 -4
  91. package/scripts/install.sh +4 -4
  92. package/scripts/lane.sh +6 -2
  93. package/scripts/lib/patterns.sh +84 -0
  94. package/scripts/mission-report.sh +12 -11
  95. package/scripts/savepoint.sh +141 -37
  96. package/scripts/verify-install.ts +154 -44
  97. package/src/args.ts +1 -1
  98. package/src/cli.ts +217 -0
  99. package/src/continue.ts +243 -0
  100. package/src/installer.ts +39 -0
  101. package/src/onboard.ts +207 -0
  102. package/src/run.ts +82 -0
  103. package/src/targets/claude.ts +140 -2
  104. package/src/targets/copilot.ts +20 -1
  105. package/src/targets/generic.ts +9 -4
  106. package/src/targets/opencode.ts +0 -1
  107. package/content/agents/onboarding-guide.md +0 -124
  108. package/content/skills/mugiwara-security/references/owasp-mapping.md +0 -30
  109. package/scripts/onboard.ts +0 -266
  110. 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');
@@ -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
- $major = [int](node -p 'process.versions.node.split(".")[0]')
9
- if ($major -lt 20) {
10
- Write-Error "mugiwara requires Node.js >= 20 (found $(node --version))."
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
@@ -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
- MAJOR="$(node -p 'process.versions.node.split(".")[0]')"
12
- if [ "$MAJOR" -lt 20 ]; then
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
- CHANGED=$(git diff --name-only "$BASE"..HEAD 2>/dev/null || git diff --name-only --cached 2>/dev/null || true)
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=$(git diff --numstat "$BASE"..HEAD 2>/dev/null | awk '{s+=$1} END {print s+0}' || echo 0)
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"
@@ -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 (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)",
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 += "| Wave | " + wave + " |\n";
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 += "## Waves\n\n| Wave | Artifact | Verdict |\n|------|----------|---------|";
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 += "| Wave | " + wave + " |\n";
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";