@ionivetech/mugiwara 0.6.1 → 0.6.3
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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.kimi-plugin/plugin.json +1 -1
- package/.opencode/commands/mugiwara-continue.md +42 -10
- package/.opencode/commands/mugiwara-execute.md +1 -1
- package/.opencode/commands/mugiwara-heal.md +1 -1
- package/.opencode/commands/mugiwara-onboard.md +15 -0
- 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/mugiwara-helpers.mjs +0 -1
- package/.opencode/plugins/mugiwara.mjs +33 -22
- package/AGENTS.md +16 -5
- package/README.md +80 -27
- package/content/agents/brook-healing.md +1 -1
- package/content/agents/chopper-checkpoint.md +1 -1
- package/content/agents/eval-runner.md +1 -1
- package/content/agents/franky-gates.md +1 -1
- package/content/agents/jinbe-security.md +1 -1
- package/content/agents/luffy-orchestrator.md +14 -4
- package/content/agents/memory-keeper.md +1 -1
- package/content/agents/nami-planner.md +2 -2
- package/content/agents/onboarding-guide.md +24 -45
- package/content/agents/resume-coordinator.md +11 -11
- package/content/agents/robin-reviewer.md +1 -1
- package/content/agents/sanji-quality.md +1 -1
- package/content/agents/skeptic-verifier.md +1 -1
- package/content/agents/usopp-brainstorm.md +2 -2
- package/content/agents/zoro-execution.md +6 -2
- package/content/skills/mugiwara-agent-security/SKILL.md +1 -18
- package/content/skills/mugiwara-agent-security/references/checklist.md +20 -0
- package/content/skills/mugiwara-backend/SKILL.md +1 -1
- package/content/skills/mugiwara-brainstorm/SKILL.md +7 -1
- package/content/skills/mugiwara-checkpoint/SKILL.md +1 -1
- package/content/skills/mugiwara-claim-audit/SKILL.md +1 -1
- package/content/skills/mugiwara-execution/SKILL.md +22 -22
- package/content/skills/mugiwara-execution/references/dispatch.md +4 -3
- package/content/skills/mugiwara-execution/references/resume-batching.md +4 -4
- package/content/skills/mugiwara-frontend/SKILL.md +1 -1
- package/content/skills/mugiwara-healing/SKILL.md +3 -38
- package/content/skills/mugiwara-healing/references/workers.md +38 -0
- package/content/skills/mugiwara-orchestration/SKILL.md +30 -33
- package/content/skills/mugiwara-orchestration/references/check-ins.md +42 -0
- package/content/skills/mugiwara-orchestration/references/closure.md +1 -1
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +9 -12
- package/content/skills/mugiwara-planning/SKILL.md +7 -11
- package/content/skills/mugiwara-planning/references/plan-template.md +4 -4
- package/content/skills/mugiwara-pr/SKILL.md +11 -9
- package/content/skills/mugiwara-resume/SKILL.md +37 -22
- package/content/skills/mugiwara-ship/SKILL.md +2 -24
- package/content/skills/mugiwara-ship/references/cleanup.md +25 -0
- package/content/skills/mugiwara-sunset/SKILL.md +1 -1
- package/content/skills/mugiwara-workflow/SKILL.md +4 -2
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +16 -7
- package/content/skills/using-mugiwara/SKILL.md +5 -1
- package/dist/mugiwara.js +107 -37
- package/gemini-extension.json +1 -1
- package/hooks/session-start.ts +113 -2
- package/package.json +3 -3
- package/plugin.json +1 -1
- package/references/multi-actor.md +39 -14
- package/references/skill-versioning.md +3 -3
- package/references/token-budget.md +1 -1
- package/scripts/evidence.sh +9 -0
- package/scripts/gate-selftest.ts +123 -0
- package/scripts/lane-base.ts +114 -0
- package/scripts/lane.sh +4 -2
- package/scripts/lib/lane-base.sh +19 -0
- package/scripts/lib/patterns.sh +15 -0
- package/scripts/mission-report.sh +21 -5
- package/scripts/onboard.ts +3 -29
- package/scripts/release-notes.ts +152 -75
- package/scripts/savepoint.sh +178 -56
- package/scripts/setup-fixtures.ts +108 -0
- package/scripts/validate-content.ts +61 -0
- package/src/cli.ts +5 -1
- package/src/installer.ts +70 -8
- package/src/mission.ts +39 -24
- package/src/targets/claude.ts +5 -3
- package/src/targets/opencode.ts +12 -8
package/scripts/gate-selftest.ts
CHANGED
|
@@ -89,6 +89,129 @@ if (!existsSync(join(root, 'test', 'savepoint.test.ts'))) {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// --- D1 mutation: break LANE_PREV resolve (require-style) → lane-integrity red ---
|
|
93
|
+
console.log('\nD1 — LANE_PREV resolve mutation');
|
|
94
|
+
const savepointFile = join(root, 'scripts', 'savepoint.sh');
|
|
95
|
+
if (!existsSync(savepointFile)) {
|
|
96
|
+
console.log(' ⚠ savepoint.sh not found, skipping');
|
|
97
|
+
} else {
|
|
98
|
+
const original = readFileSync(savepointFile, 'utf8');
|
|
99
|
+
try {
|
|
100
|
+
// reintroduce the D1 defect: read lane_prev with require() of a relative path
|
|
101
|
+
const broken = original.replace(
|
|
102
|
+
/PREV_JSON=\$\(node -e "try\{const fs=require\('fs'\);const s=JSON\.parse\(fs\.readFileSync\(process\.argv\[1\],'utf8'\)\);process\.stdout\.write\(JSON\.stringify\(\{mission:s\.mission\|\|'',lane:s\.lane\|\|'',peak:s\.lane_peak\|\|''\}\)\)\}catch\(e\)\{process\.stdout\.write\('\{\}'\)\}" "\$STATE_FILE" 2>\/dev\/null \|\| true\)/,
|
|
103
|
+
"PREV_JSON=$(node -e \"try{const s=require(process.argv[1]);process.stdout.write(JSON.stringify({mission:s.mission||'',lane:s.lane||'',peak:s.lane_peak||''}))}catch(e){process.stdout.write('{}')}\" \"$STATE_FILE\" 2>/dev/null || true)"
|
|
104
|
+
);
|
|
105
|
+
if (broken === original) {
|
|
106
|
+
console.log(' ⚠ D1 mutation pattern not found — skipping');
|
|
107
|
+
} else {
|
|
108
|
+
writeFileSync(savepointFile, broken);
|
|
109
|
+
assert('broken LANE_PREV resolve → lane-integrity fails', false, () => run('D1', 'bun run test -- lane-integrity -t "lane_prev"'));
|
|
110
|
+
}
|
|
111
|
+
} finally {
|
|
112
|
+
writeFileSync(savepointFile, original);
|
|
113
|
+
assert('restored → lane-integrity passes', true, () => run('D1', 'bun run test -- lane-integrity -t "lane_prev"'));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- D2 mutation: break monotonic clamp (drop lane_rank) → red ---
|
|
118
|
+
console.log('\nD2 — monotonic clamp mutation');
|
|
119
|
+
if (!existsSync(savepointFile)) {
|
|
120
|
+
console.log(' ⚠ savepoint.sh not found, skipping');
|
|
121
|
+
} else {
|
|
122
|
+
const original = readFileSync(savepointFile, 'utf8');
|
|
123
|
+
try {
|
|
124
|
+
// neuter the clamp: make lane_rank always return 0 so a drop never holds
|
|
125
|
+
const broken = original.replace(
|
|
126
|
+
/lane_rank\(\) \{\n case "\$1" in\n direct\) echo 0 ;;[\s\S]*?\n esac\n\}/,
|
|
127
|
+
'lane_rank() {\n echo 0\n}'
|
|
128
|
+
);
|
|
129
|
+
if (broken === original) {
|
|
130
|
+
console.log(' ⚠ D2 mutation pattern not found — skipping');
|
|
131
|
+
} else {
|
|
132
|
+
writeFileSync(savepointFile, broken);
|
|
133
|
+
assert('broken clamp → lane-integrity fails', false, () => run('D2', 'bun run test -- lane-integrity -t "clamp"'));
|
|
134
|
+
}
|
|
135
|
+
} finally {
|
|
136
|
+
writeFileSync(savepointFile, original);
|
|
137
|
+
assert('restored → lane-integrity passes', true, () => run('D2', 'bun run test -- lane-integrity -t "clamp"'));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- D3 mutation: empty SENSITIVE_PATS → lane-integrity red ---
|
|
142
|
+
console.log('\nD3 — sensitive patterns mutation');
|
|
143
|
+
const patternsFile = join(root, 'scripts', 'lib', 'patterns.sh');
|
|
144
|
+
if (!existsSync(patternsFile)) {
|
|
145
|
+
console.log(' ⚠ patterns.sh not found, skipping');
|
|
146
|
+
} else {
|
|
147
|
+
const original = readFileSync(patternsFile, 'utf8');
|
|
148
|
+
try {
|
|
149
|
+
// reintroduce the D3 defect: singular-only list (no payments/, migrations/)
|
|
150
|
+
const broken = original.replace(
|
|
151
|
+
/SENSITIVE_PATS=.*/,
|
|
152
|
+
'SENSITIVE_PATS="auth/|payment/|billing/|crypto/|secrets/|\\.env$|config/.*key|migration/|\\.sql$|schema\\.|\\.prisma$|\\.terraform|\\.tf$"'
|
|
153
|
+
);
|
|
154
|
+
if (broken === original) {
|
|
155
|
+
console.log(' ⚠ D3 mutation pattern not found — skipping');
|
|
156
|
+
} else {
|
|
157
|
+
writeFileSync(patternsFile, broken);
|
|
158
|
+
assert('singular sensitive patterns → lane-integrity fails', false, () => run('D3', 'bun run test -- lane-integrity -t "payments"'));
|
|
159
|
+
}
|
|
160
|
+
} finally {
|
|
161
|
+
writeFileSync(patternsFile, original);
|
|
162
|
+
assert('restored → lane-integrity passes', true, () => run('D3', 'bun run test -- lane-integrity -t "payments"'));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- D4 mutation: zero LOC_TOKENS → lane-integrity red ---
|
|
167
|
+
console.log('\nD4 — churn token mutation');
|
|
168
|
+
if (!existsSync(savepointFile)) {
|
|
169
|
+
console.log(' ⚠ savepoint.sh not found, skipping');
|
|
170
|
+
} else {
|
|
171
|
+
const original = readFileSync(savepointFile, 'utf8');
|
|
172
|
+
try {
|
|
173
|
+
// revert to delta-based (0 on deletions/refactors)
|
|
174
|
+
const broken = original.replace(
|
|
175
|
+
/LOC_TOKENS=\$\(\( LOC_CHURN \* 12 \)\)/,
|
|
176
|
+
'LOC_TOKENS=$(( LOC_DELTA > 0 ? LOC_DELTA * 12 : 0 ))'
|
|
177
|
+
);
|
|
178
|
+
if (broken === original) {
|
|
179
|
+
console.log(' ⚠ D4 mutation pattern not found — skipping');
|
|
180
|
+
} else {
|
|
181
|
+
writeFileSync(savepointFile, broken);
|
|
182
|
+
assert('zero churn tokens → lane-integrity fails', false, () => run('D4', 'bun run test -- lane-integrity -t "churn"'));
|
|
183
|
+
}
|
|
184
|
+
} finally {
|
|
185
|
+
writeFileSync(savepointFile, original);
|
|
186
|
+
assert('restored → lane-integrity passes', true, () => run('D4', 'bun run test -- lane-integrity -t "churn"'));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// --- D10 mutation: break continue writer → savepoint test red ---
|
|
191
|
+
console.log('\nD10 — continue writer mutation');
|
|
192
|
+
if (!existsSync(savepointFile)) {
|
|
193
|
+
console.log(' ⚠ savepoint.sh not found, skipping');
|
|
194
|
+
} else {
|
|
195
|
+
const original = readFileSync(savepointFile, 'utf8');
|
|
196
|
+
try {
|
|
197
|
+
// silently drop the continue writer block (make it a no-op). Anchor on the
|
|
198
|
+
// D10 header comment so the regex hits the writer, not the STATE_FILE if.
|
|
199
|
+
const broken = original.replace(
|
|
200
|
+
/# --- continue\/<mission>\/<member>\.json \(D10\): machine-written resume point ---[\s\S]*?\nfi\n\n/,
|
|
201
|
+
'# --- continue writer disabled (D10) ---\n\n'
|
|
202
|
+
);
|
|
203
|
+
if (broken === original) {
|
|
204
|
+
console.log(' ⚠ D10 mutation pattern not found — skipping');
|
|
205
|
+
} else {
|
|
206
|
+
writeFileSync(savepointFile, broken);
|
|
207
|
+
assert('broken continue writer → savepoint fails', false, () => run('D10', 'bun run test -- savepoint -t "D10"'));
|
|
208
|
+
}
|
|
209
|
+
} finally {
|
|
210
|
+
writeFileSync(savepointFile, original);
|
|
211
|
+
assert('restored → savepoint passes', true, () => run('D10', 'bun run test -- savepoint -t "D10"'));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
92
215
|
// --- Retrieval eval ratchet ---
|
|
93
216
|
console.log('\nRetrieval eval ratchet');
|
|
94
217
|
if (!existsSync(join(root, 'scripts', 'retrieval-eval.ts'))) {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// scripts/lane-base.ts — G5: validate LANE_BASE constants against measured
|
|
3
|
+
// content instruction load. Each lane loads a fixed set of skills + agents
|
|
4
|
+
// (wave owners from docs/concepts/workflow.md). The honest base is the sum of
|
|
5
|
+
// their body word-sums × 1.35 tokens/word. A constant that drifts more than
|
|
6
|
+
// the tolerance from that load fails — content growth must be reflected in
|
|
7
|
+
// lane-base.sh, or the estimate is a lie (D5).
|
|
8
|
+
//
|
|
9
|
+
// Run: bun scripts/lane-base.ts — validate (CI gate)
|
|
10
|
+
// bun scripts/lane-base.ts --show — print computed load per lane
|
|
11
|
+
|
|
12
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
|
|
15
|
+
const root = join(import.meta.dirname, '..');
|
|
16
|
+
const skills = join(root, 'content', 'skills');
|
|
17
|
+
const agents = join(root, 'content', 'agents');
|
|
18
|
+
|
|
19
|
+
// wave → owning skill + agent (workflow.md rows)
|
|
20
|
+
const WAVE_OWNER: Record<string, [string, string]> = {
|
|
21
|
+
'0': ['mugiwara-orchestration', 'luffy-orchestrator'],
|
|
22
|
+
'1': ['mugiwara-brainstorm', 'usopp-brainstorm'],
|
|
23
|
+
'2': ['mugiwara-planning', 'nami-planner'],
|
|
24
|
+
'3': ['mugiwara-execution', 'zoro-execution'],
|
|
25
|
+
'4': ['mugiwara-checkpoint', 'chopper-checkpoint'],
|
|
26
|
+
'4.5': ['mugiwara-claim-audit', 'skeptic-verifier'],
|
|
27
|
+
'5': ['mugiwara-quality', 'sanji-quality'],
|
|
28
|
+
'6': ['mugiwara-gates', 'franky-gates'],
|
|
29
|
+
'7': ['mugiwara-review', 'robin-reviewer'],
|
|
30
|
+
'8': ['mugiwara-healing', 'brook-healing'],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// skills always loaded regardless of lane: none — resume/lessons only load
|
|
34
|
+
// mid-mission on interruption, too cheap to count as a lane base.
|
|
35
|
+
const ALWAYS: Array<[string, string]> = [];
|
|
36
|
+
|
|
37
|
+
const LANE_WAVES: Record<string, string[]> = {
|
|
38
|
+
direct: [],
|
|
39
|
+
lean: ['0', '3', '5'],
|
|
40
|
+
standard: ['0', '2', '3', '4', '7'],
|
|
41
|
+
full: ['0', '1', '2', '3', '4', '4.5', '5', '6', '7', '8'],
|
|
42
|
+
spike: ['0', '1'],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const TOKENS_PER_WORD = 1.35;
|
|
46
|
+
const TOLERANCE = 0.2; // ±20% — a real drift, not rounding
|
|
47
|
+
|
|
48
|
+
function wordCount(file: string): number {
|
|
49
|
+
if (!existsSync(file)) return 0;
|
|
50
|
+
return readFileSync(file, 'utf8').split(/\s+/).filter(Boolean).length;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function laneLoad(lane: string): number {
|
|
54
|
+
const waves = LANE_WAVES[lane] ?? [];
|
|
55
|
+
const owners: Array<[string, string]> = [...ALWAYS];
|
|
56
|
+
for (const w of waves) {
|
|
57
|
+
if (WAVE_OWNER[w]) owners.push(WAVE_OWNER[w]);
|
|
58
|
+
}
|
|
59
|
+
let words = 0;
|
|
60
|
+
for (const [skill, agent] of owners) {
|
|
61
|
+
words += wordCount(join(skills, skill, 'SKILL.md'));
|
|
62
|
+
words += wordCount(join(agents, agent + '.md'));
|
|
63
|
+
}
|
|
64
|
+
return Math.round(words * TOKENS_PER_WORD);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// constants live in scripts/lib/lane-base.sh — parse them, not hardcode
|
|
68
|
+
function parseConstants(): Record<string, { base: number; budget: number }> {
|
|
69
|
+
const text = readFileSync(join(root, 'scripts', 'lib', 'lane-base.sh'), 'utf8');
|
|
70
|
+
const out: Record<string, { base: number; budget: number }> = {};
|
|
71
|
+
for (const lane of ['lean', 'standard', 'full', 'spike']) {
|
|
72
|
+
const base = text.match(new RegExp(`LANE_BASE_${lane}=(\\d+)`));
|
|
73
|
+
const budget = text.match(new RegExp(`BUDGET_${lane}=(\\d+)`));
|
|
74
|
+
out[lane] = {
|
|
75
|
+
base: base ? parseInt(base[1], 10) : 0,
|
|
76
|
+
budget: budget ? parseInt(budget[1], 10) : 0,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const SHOW = process.argv.includes('--show');
|
|
83
|
+
const lanes = ['direct', 'lean', 'standard', 'full', 'spike'];
|
|
84
|
+
let failures = 0;
|
|
85
|
+
|
|
86
|
+
const constants = parseConstants();
|
|
87
|
+
for (const lane of lanes) {
|
|
88
|
+
const computed = laneLoad(lane);
|
|
89
|
+
const declared = constants[lane]?.base ?? 0;
|
|
90
|
+
if (SHOW) {
|
|
91
|
+
console.log(`${lane.padEnd(9)} computed=${computed.toString().padEnd(6)} declared=${declared} budget=${constants[lane]?.budget ?? 0}`);
|
|
92
|
+
}
|
|
93
|
+
// direct runs no pipeline — base 0 is correct by design, skip
|
|
94
|
+
if (lane === 'direct') continue;
|
|
95
|
+
// spike is a deliberate floor (resize lane, brainstorm only) — not
|
|
96
|
+
// content-derived, never validated
|
|
97
|
+
if (lane === 'spike') continue;
|
|
98
|
+
if (declared === 0) {
|
|
99
|
+
console.log(` ✗ ${lane}: LANE_BASE is 0 — missing constant in lane-base.sh`);
|
|
100
|
+
failures++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const drift = Math.abs(computed - declared) / declared;
|
|
104
|
+
if (drift > TOLERANCE) {
|
|
105
|
+
console.log(` ✗ ${lane}: LANE_BASE ${declared} drifts ${(drift * 100).toFixed(0)}% from computed load ${computed} — update lane-base.sh`);
|
|
106
|
+
failures++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (failures > 0) {
|
|
111
|
+
console.log(`\nlane-base: ${failures} constant(s) drifted from content load`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
console.log('\nlane-base: constants match content load');
|
package/scripts/lane.sh
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
# Usage: lane.sh [base-ref] [--json]
|
|
4
4
|
set -u
|
|
5
5
|
|
|
6
|
+
# shared path patterns — single source of truth (D3)
|
|
7
|
+
# shellcheck source=scripts/lib/patterns.sh
|
|
8
|
+
source "$(dirname "$0")/lib/patterns.sh"
|
|
9
|
+
|
|
6
10
|
BASE="${1:-main}"
|
|
7
11
|
JSON_OUT=0
|
|
8
12
|
[ "${2:-}" = "--json" ] && JSON_OUT=1
|
|
@@ -19,7 +23,6 @@ CHANGED=$(git diff --name-only "$BASE"..HEAD 2>/dev/null || git diff --name-only
|
|
|
19
23
|
FILE_COUNT=0
|
|
20
24
|
[ -n "$CHANGED" ] && FILE_COUNT=$(echo "$CHANGED" | wc -l | tr -d ' ')
|
|
21
25
|
|
|
22
|
-
SENSITIVE_PATS="auth/|payment/|billing/|crypto/|secrets/|\.env$|config/.*key|migration/|\.sql$|schema\.|\.prisma$|\.terraform|\.tf$"
|
|
23
26
|
SENSITIVE=$(echo "$CHANGED" | grep -E "$SENSITIVE_PATS" 2>/dev/null | head -5 | tr '\n' ',' | sed 's/,$//' || true)
|
|
24
27
|
HAS_SENSITIVE=0
|
|
25
28
|
[ -n "$SENSITIVE" ] && HAS_SENSITIVE=1
|
|
@@ -64,7 +67,6 @@ fi
|
|
|
64
67
|
# code surface actually touched. Sensitive-path escalation above still wins.
|
|
65
68
|
# Product surface for mugiwara: content/, src/, scripts/, test/, hooks/,
|
|
66
69
|
# .opencode/, .claude/, evals/ — everything else is docs/config/asset.
|
|
67
|
-
PRODUCT_PAT="^content/|^src/|^scripts/|^test/|^hooks/|^\.opencode/|^\.claude/|^evals/"
|
|
68
70
|
if [ "$LANE" = "full" ] && [ "$HAS_SENSITIVE" -eq 0 ] && [ -n "$CHANGED" ]; then
|
|
69
71
|
CODE_COUNT=$(echo "$CHANGED" | grep -E "$PRODUCT_PAT" 2>/dev/null | grep -c . || true)
|
|
70
72
|
if [ -z "$CODE_COUNT" ] || [ "$CODE_COUNT" -eq 0 ] 2>/dev/null; then
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/lib/lane-base.sh — LANE_BASE + BUDGET constants, single source of
|
|
3
|
+
# truth. Validated by scripts/lane-base.ts against measured content word-sums.
|
|
4
|
+
# Change a constant here → run `bun scripts/lane-base.ts`; the gate fails if
|
|
5
|
+
# the constant drifts from the honest instruction load (D5).
|
|
6
|
+
|
|
7
|
+
# LANE_BASE: token estimate for skills/agents loaded in this lane. Derived
|
|
8
|
+
# from content word-sums × 1.35 (see scripts/lane-base.ts). spike is a
|
|
9
|
+
# resize, not a rise — small base, tiny budget.
|
|
10
|
+
LANE_BASE_lean=7000
|
|
11
|
+
LANE_BASE_standard=13000
|
|
12
|
+
LANE_BASE_full=23000
|
|
13
|
+
LANE_BASE_spike=1000
|
|
14
|
+
|
|
15
|
+
# BUDGET: warn at 1.5×, stop at 3×.
|
|
16
|
+
BUDGET_lean=12000
|
|
17
|
+
BUDGET_standard=25000
|
|
18
|
+
BUDGET_full=50000
|
|
19
|
+
BUDGET_spike=3000
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/lib/patterns.sh — single source of truth for lane path patterns.
|
|
3
|
+
# Sourced by lane.sh and savepoint.sh. Extend here, never in the consumers.
|
|
4
|
+
|
|
5
|
+
# Sensitive-path escalation patterns. A changed file matching any of these
|
|
6
|
+
# always escalates to Lane 3 (Full), regardless of file count. Plural forms
|
|
7
|
+
# included (payments/, migrations/) — the singular-only list missed them (D3).
|
|
8
|
+
# Deliberately NOT matched: package.json (dependency churn is policy-as-code,
|
|
9
|
+
# not a sensitive lane trigger), authors/ (contains "auth" but never auth/).
|
|
10
|
+
SENSITIVE_PATS="auth/|payment/|payments/|billing/|crypto/|secrets/|\.env$|config/.*key|migration/|migrations/|\.sql$|schema\.|\.prisma$|\.terraform|\.tf$"
|
|
11
|
+
|
|
12
|
+
# Product surface: paths that count toward file-based lane sizing. Changes
|
|
13
|
+
# outside this surface are docs/config/asset and never escalate to full on
|
|
14
|
+
# count alone (path-weighted sizing).
|
|
15
|
+
PRODUCT_PAT="^content/|^src/|^scripts/|^test/|^hooks/|^\.opencode/|^\.claude/|^evals/"
|
|
@@ -12,25 +12,38 @@ MISSION="${1:-}"
|
|
|
12
12
|
case "$MISSION" in
|
|
13
13
|
*[!a-zA-Z0-9._-]*) echo "mission-report: invalid mission name \"$MISSION\" (allowlist: [a-zA-Z0-9._-])" >&2; exit 1 ;;
|
|
14
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
|
|
15
18
|
|
|
16
19
|
MUGIWARA_DIR="${MUGIWARA_DIR:-.mugiwara}"
|
|
17
|
-
|
|
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
|
|
18
31
|
REPORT_DIR="$MUGIWARA_DIR/reports"
|
|
19
32
|
RESULTS_DIR="$MUGIWARA_DIR/results/$MISSION"
|
|
20
33
|
REVIEW_DIR="$MUGIWARA_DIR/review"
|
|
21
34
|
ISSUES_DIR="$MUGIWARA_DIR/issues"
|
|
22
35
|
|
|
23
|
-
export STATE_FILE REPORT_DIR RESULTS_DIR REVIEW_DIR ISSUES_DIR MISSION
|
|
36
|
+
export STATE_FILE REPORT_DIR RESULTS_DIR REVIEW_DIR ISSUES_DIR MISSION MUGIWARA_DIR
|
|
24
37
|
node << 'NODE'
|
|
25
38
|
const fs = require('fs');
|
|
26
39
|
const path = require('path');
|
|
27
40
|
|
|
28
|
-
const stateFile = process.env.STATE_FILE || ".mugiwara/state.json";
|
|
41
|
+
const stateFile = process.env.STATE_FILE || ".mugiwara/state/unknown/state.json";
|
|
29
42
|
const reportDir = process.env.REPORT_DIR || ".mugiwara/reports";
|
|
30
43
|
const resultsDir = process.env.RESULTS_DIR || ".mugiwara/results/unknown";
|
|
31
44
|
const reviewDir = process.env.REVIEW_DIR || ".mugiwara/review";
|
|
32
45
|
const issuesDir = process.env.ISSUES_DIR || ".mugiwara/issues";
|
|
33
|
-
const mugiDir =
|
|
46
|
+
const mugiDir = process.env.MUGIWARA_DIR || ".mugiwara";
|
|
34
47
|
const mission = process.env.MISSION || "unknown";
|
|
35
48
|
|
|
36
49
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
@@ -56,6 +69,7 @@ const val = (obj, key, dflt) => {
|
|
|
56
69
|
const now = new Date().toISOString().slice(0, 10);
|
|
57
70
|
const reportFile = path.join(reportDir, now + "-" + mission + ".md");
|
|
58
71
|
const lane = val(s, 'lane', 'n/a');
|
|
72
|
+
const lanePeak = val(s, 'lane_peak', 'n/a');
|
|
59
73
|
const laneReason = val(s, 'lane_reason', 'n/a');
|
|
60
74
|
const mode = val(s, 'mode', 'n/a');
|
|
61
75
|
const actor = val(s, 'actor', 'n/a');
|
|
@@ -63,6 +77,7 @@ const branch = val(s, 'branch', 'n/a');
|
|
|
63
77
|
const wave = val(s, 'wave', 'n/a');
|
|
64
78
|
const filesTouched = val(s, 'files_touched', 0);
|
|
65
79
|
const locDelta = val(s, 'loc_delta', 0);
|
|
80
|
+
const locChurn = val(s, 'loc_churn', 0);
|
|
66
81
|
const sensitive = s ? (s.sensitive_paths || []) : [];
|
|
67
82
|
const tasks = s ? (s.tasks || {}) : {};
|
|
68
83
|
const blockers = val(s, 'blockers_open', 0);
|
|
@@ -213,6 +228,7 @@ report += "## Mission header\n\n| Field | Value |\n|-------|-------|\n";
|
|
|
213
228
|
report += "| Mission | " + mission + " |\n";
|
|
214
229
|
report += "| Branch | " + branch + " |\n";
|
|
215
230
|
report += "| Lane | " + lane + " |\n";
|
|
231
|
+
report += "| Lane peak | " + lanePeak + " |\n";
|
|
216
232
|
report += "| Lane reason | " + laneReason + " |\n";
|
|
217
233
|
report += "| Mode | " + mode + " |\n";
|
|
218
234
|
report += "| Wave | " + wave + " |\n";
|
|
@@ -232,7 +248,7 @@ report += "| Total | " + tasksTotal + " |\n";
|
|
|
232
248
|
report += "| Source | " + tasksSource + " |\n\n";
|
|
233
249
|
|
|
234
250
|
report += "## What changed\n\n";
|
|
235
|
-
report += filesTouched + " files, +" + locDelta + " LOC";
|
|
251
|
+
report += filesTouched + " files, +" + locDelta + " LOC (" + locChurn + " churn)";
|
|
236
252
|
if (sensitive.length) report += "\nSensitive paths: " + sensitive.join(", ");
|
|
237
253
|
report += "\n\n";
|
|
238
254
|
|
package/scripts/onboard.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { createInterface } from "node:readline";
|
|
|
7
7
|
const root = join(import.meta.dirname, "..");
|
|
8
8
|
const mugiwaraDir = join(root, ".mugiwara");
|
|
9
9
|
const configPath = join(mugiwaraDir, "config");
|
|
10
|
-
const onboardPath = join(mugiwaraDir, "onboard.json");
|
|
11
10
|
|
|
12
11
|
function ask(rl: import("node:readline").Interface, prompt: string): Promise<string> {
|
|
13
12
|
return new Promise((resolve) => {
|
|
@@ -38,7 +37,7 @@ async function main() {
|
|
|
38
37
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
39
38
|
console.log("Usage: bun scripts/onboard.ts");
|
|
40
39
|
console.log("Runs the Mugiwara onboarding wizard (10 fixed questions).");
|
|
41
|
-
console.log("Writes .mugiwara/config
|
|
40
|
+
console.log("Writes .mugiwara/config.");
|
|
42
41
|
process.exit(0);
|
|
43
42
|
}
|
|
44
43
|
|
|
@@ -64,10 +63,6 @@ async function main() {
|
|
|
64
63
|
console.log("");
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
const answers: Record<string, unknown> = {
|
|
68
|
-
started_at: new Date().toISOString(),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
66
|
// ---- Phase 1: Project Context ----
|
|
72
67
|
console.log("── Phase 1: Project Context ──");
|
|
73
68
|
console.log("");
|
|
@@ -80,7 +75,6 @@ async function main() {
|
|
|
80
75
|
"Backend service / API",
|
|
81
76
|
"Other",
|
|
82
77
|
]);
|
|
83
|
-
answers.project_type = q1;
|
|
84
78
|
console.log("");
|
|
85
79
|
|
|
86
80
|
const q2 = await pick(rl, "Q2 — Primary language:\n [1] TypeScript\n [2] JavaScript\n [3] Python\n [4] Go\n [5] Rust\n [6] Java\n [7] Other\n > ", [
|
|
@@ -92,7 +86,6 @@ async function main() {
|
|
|
92
86
|
"Java",
|
|
93
87
|
"Other",
|
|
94
88
|
]);
|
|
95
|
-
answers.primary_language = q2;
|
|
96
89
|
console.log("");
|
|
97
90
|
|
|
98
91
|
const q3 = await pick(rl, "Q3 — Team size:\n [1] Solo\n [2] 2–5\n [3] 6–15\n [4] 16+\n > ", [
|
|
@@ -101,7 +94,6 @@ async function main() {
|
|
|
101
94
|
"6–15",
|
|
102
95
|
"16+",
|
|
103
96
|
]);
|
|
104
|
-
answers.team_size = q3;
|
|
105
97
|
console.log("");
|
|
106
98
|
|
|
107
99
|
const q4 = await pick(rl, "Q4 — Git workflow:\n [1] Trunk-based (feature/{type}-{issue}-{slug})\n [2] GitFlow (feature/{slug})\n [3] GitHub Flow (feat/{slug})\n [4] Other (feature/{slug})\n > ", [
|
|
@@ -110,7 +102,6 @@ async function main() {
|
|
|
110
102
|
"github-flow",
|
|
111
103
|
"other",
|
|
112
104
|
]);
|
|
113
|
-
answers.git_workflow = q4;
|
|
114
105
|
console.log("");
|
|
115
106
|
|
|
116
107
|
const q5 = await pick(rl, "Q5 — CI/CD platform:\n [1] GitHub Actions\n [2] GitLab CI\n [3] CircleCI\n [4] Jenkins\n [5] None / manual\n [6] Other\n > ", [
|
|
@@ -121,7 +112,6 @@ async function main() {
|
|
|
121
112
|
"None / manual",
|
|
122
113
|
"Other",
|
|
123
114
|
]);
|
|
124
|
-
answers.ci_cd = q5;
|
|
125
115
|
console.log("");
|
|
126
116
|
|
|
127
117
|
// ---- Phase 2: Mugiwara Preferences ----
|
|
@@ -133,27 +123,20 @@ async function main() {
|
|
|
133
123
|
"semi",
|
|
134
124
|
"auto",
|
|
135
125
|
]);
|
|
136
|
-
answers.autonomy_mode = q6;
|
|
137
|
-
console.log("");
|
|
138
|
-
|
|
139
|
-
const q7 = await ask(rl, "Q7 — Agents to enable (comma-separated or 'all'):\n Available: brainstorm, plan, execute, checkpoint, quality, gates, review, security, healing\n Default: all\n > ");
|
|
140
|
-
answers.enabled_agents = q7 || "all";
|
|
141
126
|
console.log("");
|
|
142
127
|
|
|
143
|
-
const q8a = await pick(rl, "
|
|
128
|
+
const q8a = await pick(rl, "Q7 — Code review depth:\n [1] full — breaking-change map, five-axis review, ≤3 cycles\n [2] standard — five-axis review, 1 cycle\n [3] quick — diff-only, no caller-map\n > ", [
|
|
144
129
|
"full",
|
|
145
130
|
"standard",
|
|
146
131
|
"quick",
|
|
147
132
|
]);
|
|
148
|
-
answers.review_depth = q8a;
|
|
149
133
|
console.log("");
|
|
150
134
|
|
|
151
|
-
const q8b = await pick(rl, "
|
|
135
|
+
const q8b = await pick(rl, "Q8 — Quality check depth:\n [1] full — format, lint, typecheck, test, build\n [2] standard — lint, typecheck, test\n [3] quick — test only\n > ", [
|
|
152
136
|
"full",
|
|
153
137
|
"standard",
|
|
154
138
|
"quick",
|
|
155
139
|
]);
|
|
156
|
-
answers.quality_depth = q8b;
|
|
157
140
|
console.log("");
|
|
158
141
|
|
|
159
142
|
console.log("Q9 — Test coverage threshold:");
|
|
@@ -180,9 +163,6 @@ async function main() {
|
|
|
180
163
|
coverageNew = 0;
|
|
181
164
|
coverageModified = 0;
|
|
182
165
|
}
|
|
183
|
-
answers.coverage_threshold = q9;
|
|
184
|
-
answers.coverage_new = coverageNew;
|
|
185
|
-
answers.coverage_modified = coverageModified;
|
|
186
166
|
console.log("");
|
|
187
167
|
|
|
188
168
|
const q10 = await pick(rl, "Q10 — Commit style:\n [1] Conventional Commits (feat:, fix:, chore:, docs:)\n [2] Semantic (type(scope): message)\n [3] Free-form\n > ", [
|
|
@@ -190,8 +170,6 @@ async function main() {
|
|
|
190
170
|
"semantic",
|
|
191
171
|
"free-form",
|
|
192
172
|
]);
|
|
193
|
-
answers.commit_style = q10;
|
|
194
|
-
answers.completed_at = new Date().toISOString();
|
|
195
173
|
console.log("");
|
|
196
174
|
|
|
197
175
|
// ---- Branch name format ----
|
|
@@ -230,11 +208,9 @@ async function main() {
|
|
|
230
208
|
commit,
|
|
231
209
|
review_depth: reviewDepth,
|
|
232
210
|
quality_depth: qualityDepth,
|
|
233
|
-
enabled_agents: answers.enabled_agents,
|
|
234
211
|
};
|
|
235
212
|
|
|
236
213
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
237
|
-
writeFileSync(onboardPath, JSON.stringify(answers, null, 2) + "\n");
|
|
238
214
|
|
|
239
215
|
// ---- Summary ----
|
|
240
216
|
const qLabels: Record<string, Record<number, string>> = {
|
|
@@ -275,14 +251,12 @@ async function main() {
|
|
|
275
251
|
console.log(" Git workflow: ", qLabels.git_workflow[q4]);
|
|
276
252
|
console.log(" CI/CD: ", qLabels.ci_cd[q5]);
|
|
277
253
|
console.log(" Autonomy mode: ", qLabels.autonomy_mode[q6]);
|
|
278
|
-
console.log(" Enabled agents: ", answers.enabled_agents);
|
|
279
254
|
console.log(" Review depth: ", qLabels.review_depth[q8a]);
|
|
280
255
|
console.log(" Quality depth: ", qLabels.quality_depth[q8b]);
|
|
281
256
|
console.log(" Coverage: ", q9 === 3 ? `${coverageNew}/${coverageModified}` : qLabels.coverage_threshold[q9]);
|
|
282
257
|
console.log(" Commit style: ", qLabels.commit_style[q10]);
|
|
283
258
|
console.log("");
|
|
284
259
|
console.log(` Config written: ${configPath}`);
|
|
285
|
-
console.log(` Audit trail: ${onboardPath}`);
|
|
286
260
|
console.log("");
|
|
287
261
|
} finally {
|
|
288
262
|
rl.close();
|