@ionivetech/mugiwara 0.5.4 → 0.6.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 +3 -3
- package/.claude-plugin/plugin.json +4 -3
- package/.codex-plugin/plugin.json +2 -2
- package/.cursor-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +2 -2
- package/.opencode/commands/mugiwara-continue.md +16 -0
- package/.opencode/commands/mugiwara-execute.md +6 -4
- package/.opencode/commands/mugiwara-heal.md +6 -4
- package/.opencode/commands/mugiwara-plan.md +6 -4
- package/.opencode/commands/mugiwara-review.md +6 -4
- package/.opencode/commands/mugiwara-security.md +6 -4
- package/.opencode/commands/mugiwara-ship.md +6 -4
- package/.opencode/commands/mugiwara.md +4 -2
- package/.opencode/commands/using-mugiwara.md +7 -13
- package/.opencode/plugins/mugiwara.mjs +18 -6
- package/AGENTS.md +2 -2
- package/GEMINI.md +1 -1
- package/README.md +240 -217
- package/content/agents/brook-healing.md +17 -3
- package/content/agents/chopper-checkpoint.md +17 -3
- package/content/agents/eval-runner.md +17 -2
- package/content/agents/franky-gates.md +19 -5
- package/content/agents/jinbe-security.md +19 -3
- package/content/agents/luffy-orchestrator.md +6 -3
- package/content/agents/memory-keeper.md +15 -0
- package/content/agents/nami-planner.md +21 -6
- package/content/agents/onboarding-guide.md +145 -0
- package/content/agents/resume-coordinator.md +21 -5
- package/content/agents/robin-reviewer.md +18 -3
- package/content/agents/sanji-quality.md +19 -4
- package/content/agents/skeptic-verifier.md +18 -3
- package/content/agents/usopp-brainstorm.md +17 -3
- package/content/agents/zoro-execution.md +19 -5
- package/content/skills/mugiwara-backend/SKILL.md +1 -1
- package/content/skills/mugiwara-brainstorm/SKILL.md +5 -1
- package/content/skills/mugiwara-checkpoint/SKILL.md +2 -2
- package/content/skills/mugiwara-contract-first/SKILL.md +2 -36
- package/content/skills/mugiwara-contract-first/references/process.md +37 -0
- package/content/skills/mugiwara-execution/SKILL.md +30 -13
- package/content/skills/mugiwara-execution/references/resume-batching.md +32 -0
- package/content/skills/mugiwara-frontend/SKILL.md +1 -1
- package/content/skills/mugiwara-gates/SKILL.md +32 -30
- package/content/skills/mugiwara-healing/SKILL.md +1 -1
- package/content/skills/mugiwara-orchestration/SKILL.md +41 -25
- package/content/skills/mugiwara-orchestration/references/delegation-pillars.md +40 -0
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +96 -0
- package/content/skills/mugiwara-planning/SKILL.md +23 -27
- package/content/skills/mugiwara-planning/references/plan-template.md +42 -0
- package/content/skills/mugiwara-pr/SKILL.md +2 -2
- package/content/skills/mugiwara-quality/SKILL.md +10 -6
- package/content/skills/mugiwara-resume/SKILL.md +7 -2
- package/content/skills/mugiwara-review/SKILL.md +20 -1
- package/content/skills/mugiwara-review/references/code-attributes.md +38 -0
- package/content/skills/mugiwara-root-cause/SKILL.md +3 -30
- package/content/skills/mugiwara-root-cause/references/process.md +35 -0
- package/content/skills/mugiwara-security/SKILL.md +17 -32
- package/content/skills/mugiwara-security/references/owasp-top10.md +12 -0
- package/content/skills/mugiwara-security/references/rationalizations.md +8 -0
- package/content/skills/mugiwara-ship/SKILL.md +5 -4
- package/content/skills/mugiwara-workflow/SKILL.md +74 -77
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +50 -0
- package/content/skills/using-mugiwara/SKILL.md +8 -19
- package/dist/mugiwara.js +206 -87
- package/gemini-extension.json +1 -1
- package/hooks/session-start.ts +1 -1
- package/package.json +12 -5
- package/plugin.json +2 -2
- package/references/multi-actor.md +1 -1
- package/scripts/evidence.sh +13 -6
- package/scripts/gate-selftest.ts +266 -0
- package/scripts/initiative.ts +248 -0
- package/scripts/lane.sh +15 -0
- package/scripts/mission-report.sh +141 -76
- package/scripts/onboard.ts +292 -0
- package/scripts/release-notes.ts +42 -13
- package/scripts/retrieval-eval.ts +1 -2
- package/scripts/run-evals.ts +1 -1
- package/scripts/savepoint.sh +97 -50
- package/scripts/validate-content.ts +84 -7
- package/scripts/verify-install.ts +72 -0
- package/src/args.ts +1 -1
- package/src/cli.ts +49 -22
- package/src/frontmatter.ts +3 -3
- package/src/installer.ts +44 -15
- package/src/mission.ts +7 -5
- package/src/targets/claude.ts +20 -3
- package/src/targets/generic.ts +1 -1
- package/src/targets/opencode.ts +53 -11
- package/docs/adoption-guide.md +0 -72
- package/docs/agent-anatomy.md +0 -72
- package/docs/agents.md +0 -53
- package/docs/audit-trail.md +0 -65
- package/docs/claude-setup.md +0 -43
- package/docs/codex-setup.md +0 -26
- package/docs/comparison.md +0 -58
- package/docs/compliance-matrix.md +0 -81
- package/docs/config.md +0 -55
- package/docs/copilot-setup.md +0 -29
- package/docs/cost.md +0 -45
- package/docs/cursor-setup.md +0 -25
- package/docs/developer-onboarding.md +0 -89
- package/docs/enforcement.md +0 -35
- package/docs/execution-model.md +0 -92
- package/docs/gemini-setup.md +0 -27
- package/docs/getting-started.md +0 -158
- package/docs/git-strategy.md +0 -62
- package/docs/harness-matrix.md +0 -41
- package/docs/index.md +0 -56
- package/docs/install-antigravity.md +0 -45
- package/docs/install-claude.md +0 -75
- package/docs/install-cli.md +0 -103
- package/docs/install-codex.md +0 -44
- package/docs/install-copilot.md +0 -45
- package/docs/install-cursor.md +0 -45
- package/docs/install-gemini.md +0 -44
- package/docs/install-kimi.md +0 -45
- package/docs/install-opencode.md +0 -129
- package/docs/install-pi.md +0 -46
- package/docs/install.md +0 -53
- package/docs/lanes.md +0 -82
- package/docs/modes.md +0 -74
- package/docs/opencode-setup.md +0 -54
- package/docs/pr-summary.md +0 -54
- package/docs/rule-based-setup.md +0 -31
- package/docs/skill-anatomy.md +0 -71
- package/docs/skills.md +0 -55
- package/docs/troubleshooting.md +0 -91
- package/docs/windsurf-setup.md +0 -18
- package/docs/workflow.md +0 -89
- package/evals/cases/_no-skill.json +0 -16
- package/evals/cases/adversarial-pressure-fake-pass.json +0 -25
- package/evals/cases/adversarial-pressure-skip-review.json +0 -25
- package/evals/cases/lane-exploratory-vague.json +0 -24
- package/evals/cases/lane-sensitivity-payment.json +0 -24
- package/evals/cases/positive-refactor-existing-tests.json +0 -25
- package/evals/cases/positive-resume-mid-mission.json +0 -24
- package/evals/cases/routing-agent-security.json +0 -25
- package/evals/cases/routing-auth-feature.json +0 -26
- package/evals/cases/routing-backend.json +0 -25
- package/evals/cases/routing-bug-one-file.json +0 -26
- package/evals/cases/routing-claim-audit.json +0 -25
- package/evals/cases/routing-context-budget.json +0 -25
- package/evals/cases/routing-contract-first.json +0 -25
- package/evals/cases/routing-execution.json +0 -25
- package/evals/cases/routing-frontend.json +0 -26
- package/evals/cases/routing-gates.json +0 -25
- package/evals/cases/routing-git.json +0 -25
- package/evals/cases/routing-healing.json +0 -25
- package/evals/cases/routing-lessons.json +0 -25
- package/evals/cases/routing-orchestration.json +0 -25
- package/evals/cases/routing-planning.json +0 -26
- package/evals/cases/routing-pr.json +0 -25
- package/evals/cases/routing-quality.json +0 -25
- package/evals/cases/routing-ship.json +0 -26
- package/evals/cases/routing-sunset.json +0 -25
- package/evals/cases/routing-using-mugiwara.json +0 -25
- package/evals/cases/routing-workflow.json +0 -25
- package/evals/floor.json +0 -6
package/plugin.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mugiwara",
|
|
3
3
|
"description": "The Straw Hat crew of AI agents and skills: brainstorm, plan, execute, checkpoint, quality, gates, review, security, healing.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "
|
|
6
|
+
"name": "ionivetech"
|
|
7
7
|
},
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": "https://github.com/ionivetech/mugiwara",
|
|
@@ -37,7 +37,7 @@ All actors read and write to the same file. Append-only, never overwrite.
|
|
|
37
37
|
`scripts/savepoint.sh` accepts `--branch <name>` to write per-branch state:
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
scripts/savepoint.sh "2026-08-11-dark-mode"
|
|
40
|
+
scripts/savepoint.sh --branch "2026-08-11-dark-mode" "" "feature/dark-mode"
|
|
41
41
|
# writes .mugiwara/state-feature-dark-mode.json
|
|
42
42
|
```
|
|
43
43
|
|
package/scripts/evidence.sh
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# scripts/evidence.sh — run a check and capture its evidence.
|
|
3
|
-
# Usage: evidence.sh <label> [-- command args...]
|
|
4
|
-
# output: .mugiwara/results/<label>-<hash>.log
|
|
3
|
+
# Usage: evidence.sh <mission> <label> [-- command args...]
|
|
4
|
+
# output: .mugiwara/results/<mission>/<label>-<hash>.log
|
|
5
5
|
set -u
|
|
6
6
|
|
|
7
7
|
die() { echo "evidence: $*" >&2; exit 1; }
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
MISSION="${1:-}"
|
|
10
|
+
LABEL="${2:-}"
|
|
10
11
|
shift 2>/dev/null || true
|
|
11
|
-
[ -z "$
|
|
12
|
+
[ -z "$MISSION" ] && die "usage: evidence.sh <mission> <label> [-- command args...]"
|
|
13
|
+
[ -z "$LABEL" ] && die "usage: evidence.sh <mission> <label> [-- command args...]"
|
|
14
|
+
|
|
15
|
+
# mission allowlist — path-traversal guard (same rule as mission-report.sh)
|
|
16
|
+
case "$MISSION" in
|
|
17
|
+
*[!a-zA-Z0-9._-]*) die "invalid mission name \"$MISSION\" (allowlist: [a-zA-Z0-9._-])" ;;
|
|
18
|
+
esac
|
|
12
19
|
|
|
13
20
|
MUGIWARA_DIR="${MUGIWARA_DIR:-.mugiwara}"
|
|
14
|
-
RESULTS_DIR="$MUGIWARA_DIR/results"
|
|
21
|
+
RESULTS_DIR="$MUGIWARA_DIR/results/$MISSION"
|
|
15
22
|
mkdir -p "$RESULTS_DIR"
|
|
16
23
|
|
|
17
24
|
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
|
18
|
-
HASH=$(echo "${LABEL}-${TIMESTAMP}-$$-${RANDOM}" | shasum -a 256 | cut -c1-12 2>/dev/null || echo "${TIMESTAMP}")
|
|
25
|
+
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}")
|
|
19
26
|
EVIDENCE_FILE="$RESULTS_DIR/${LABEL}-${HASH}.log"
|
|
20
27
|
|
|
21
28
|
{
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// scripts/gate-selftest.ts — G2: prove each gate can fail.
|
|
3
|
+
// Mutate → assert RED → restore → assert GREEN. Exit 0 when all pass.
|
|
4
|
+
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync, renameSync, unlinkSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
|
|
10
|
+
const root = join(import.meta.dirname, '..');
|
|
11
|
+
let passed = 0;
|
|
12
|
+
let failed = 0;
|
|
13
|
+
|
|
14
|
+
function run(name: string, cmd: string): boolean {
|
|
15
|
+
try { execSync(cmd, { cwd: root, stdio: 'pipe', timeout: 60000 }); return true; }
|
|
16
|
+
catch { return false; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function assert(name: string, shouldSucceed: boolean, fn: () => boolean) {
|
|
20
|
+
const ok = fn() === shouldSucceed;
|
|
21
|
+
if (ok) { passed++; console.log(` ✓ ${name}`); }
|
|
22
|
+
else { failed++; console.log(` ✗ ${name}`); }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// --- G1: verify-install — delete one shared ref, prove gate fails ---
|
|
26
|
+
console.log('\nG1 — verify-install');
|
|
27
|
+
if (!existsSync(join(root, 'scripts', 'verify-install.ts'))) {
|
|
28
|
+
console.log(' ⚠ verify-install.ts not found, skipping');
|
|
29
|
+
} else {
|
|
30
|
+
const refFile = join(root, 'references', 'source-grounding.md');
|
|
31
|
+
const backup = join(root, 'references', 'source-grounding.md.bak');
|
|
32
|
+
const text = readFileSync(refFile, 'utf8');
|
|
33
|
+
try {
|
|
34
|
+
renameSync(refFile, backup);
|
|
35
|
+
assert('broken pointer → exit 1', false, () => run('G1', 'bun scripts/verify-install.ts'));
|
|
36
|
+
} finally {
|
|
37
|
+
writeFileSync(refFile, text);
|
|
38
|
+
if (existsSync(backup)) unlinkSync(backup);
|
|
39
|
+
assert('restored → exit 0', true, () => run('G1', 'bun scripts/verify-install.ts'));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// --- G4: section-length — create SKILL.md with 25-line section ---
|
|
44
|
+
console.log('\nG4 — section-length');
|
|
45
|
+
if (!existsSync(join(root, 'scripts', 'validate-content.ts'))) {
|
|
46
|
+
console.log(' ⚠ validate-content.ts not found, skipping');
|
|
47
|
+
} else {
|
|
48
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'mugi-g4-'));
|
|
49
|
+
try {
|
|
50
|
+
const skillDir = join(tmpDir, 'skills', 'test-skill');
|
|
51
|
+
mkdirSync(skillDir, { recursive: true });
|
|
52
|
+
const skillFile = join(skillDir, 'SKILL.md');
|
|
53
|
+
|
|
54
|
+
const makeBody = (sectionLines: number) => {
|
|
55
|
+
let body = '---\nname: test-skill\ndescription: Test skill for section-length gate, twenty chars\n---\n\n# Test\n\n## Skip when\n\n- Test\n\n## Long Section\n\n';
|
|
56
|
+
for (let i = 0; i < sectionLines; i++) body += `Line ${i + 1} of content for section length test.\n`;
|
|
57
|
+
body += '\n';
|
|
58
|
+
return body;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
writeFileSync(skillFile, makeBody(25));
|
|
62
|
+
assert('section ≥20 lines → exit 1', false, () => run('G4', `bun scripts/validate-content.ts --check ${skillFile}`));
|
|
63
|
+
|
|
64
|
+
writeFileSync(skillFile, makeBody(5));
|
|
65
|
+
assert('short section → exit 0', true, () => run('G4', `bun scripts/validate-content.ts --check ${skillFile}`));
|
|
66
|
+
} finally {
|
|
67
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// --- G3: savepoint fixtures — prove test fails when a field is broken ---
|
|
72
|
+
console.log('\nG3 — savepoint fixtures');
|
|
73
|
+
if (!existsSync(join(root, 'test', 'savepoint.test.ts'))) {
|
|
74
|
+
console.log(' ⚠ savepoint.test.ts not found, skipping');
|
|
75
|
+
} else {
|
|
76
|
+
const testFile = join(root, 'test', 'savepoint.test.ts');
|
|
77
|
+
const original = readFileSync(testFile, 'utf8');
|
|
78
|
+
try {
|
|
79
|
+
// Break one assertion: change lane validation to expect a wrong value
|
|
80
|
+
const broken = original.replace(
|
|
81
|
+
"expect(['direct', 'lean', 'standard', 'full', 'spike']).toContain(state.lane)",
|
|
82
|
+
"expect(state.lane).toBe('NONEXISTENT')"
|
|
83
|
+
);
|
|
84
|
+
writeFileSync(testFile, broken);
|
|
85
|
+
assert('broken assertion → gate fails', false, () => run('G3', 'bun run test -- savepoint'));
|
|
86
|
+
} finally {
|
|
87
|
+
writeFileSync(testFile, original);
|
|
88
|
+
assert('restored → gate passes', true, () => run('G3', 'bun run test -- savepoint'));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// --- Retrieval eval ratchet ---
|
|
93
|
+
console.log('\nRetrieval eval ratchet');
|
|
94
|
+
if (!existsSync(join(root, 'scripts', 'retrieval-eval.ts'))) {
|
|
95
|
+
console.log(' ⚠ retrieval-eval.ts not found, skipping');
|
|
96
|
+
} else {
|
|
97
|
+
// Strip trigger words from one skill description to break retrieval
|
|
98
|
+
const orchestrationFile = join(root, 'content', 'skills', 'mugiwara-orchestration', 'SKILL.md');
|
|
99
|
+
if (existsSync(orchestrationFile)) {
|
|
100
|
+
const original = readFileSync(orchestrationFile, 'utf8');
|
|
101
|
+
try {
|
|
102
|
+
const broken = original.replace(/description:.*/, 'description: Does stuff.');
|
|
103
|
+
writeFileSync(orchestrationFile, broken);
|
|
104
|
+
assert('stripped description → ratchet fails', false, () => run('retrieval', 'bun scripts/retrieval-eval.ts'));
|
|
105
|
+
} finally {
|
|
106
|
+
writeFileSync(orchestrationFile, original);
|
|
107
|
+
assert('restored → ratchet passes', true, () => run('retrieval', 'bun scripts/retrieval-eval.ts'));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- Manifest sync ---
|
|
113
|
+
console.log('\nManifest sync');
|
|
114
|
+
const manifestFile = join(root, '.claude-plugin', 'plugin.json');
|
|
115
|
+
if (!existsSync(manifestFile)) {
|
|
116
|
+
console.log(' ⚠ manifest not found, skipping');
|
|
117
|
+
} else {
|
|
118
|
+
const original = readFileSync(manifestFile, 'utf8');
|
|
119
|
+
try {
|
|
120
|
+
const mdata = JSON.parse(original);
|
|
121
|
+
const skills: string[] = mdata.metadata.skills;
|
|
122
|
+
const removed = skills.shift()!;
|
|
123
|
+
writeFileSync(manifestFile, JSON.stringify(mdata, null, 2));
|
|
124
|
+
assert('missing skill → exit 1', false, () => run('manifest', 'bun scripts/validate-content.ts --check-manifest'));
|
|
125
|
+
} finally {
|
|
126
|
+
writeFileSync(manifestFile, original);
|
|
127
|
+
assert('restored → exit 0', true, () => run('manifest', 'bun scripts/validate-content.ts --check-manifest'));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- Docs drift ---
|
|
132
|
+
console.log('\nDocs drift');
|
|
133
|
+
const skillsDoc = join(root, 'docs', 'concepts', 'skills.md');
|
|
134
|
+
if (!existsSync(skillsDoc)) {
|
|
135
|
+
console.log(' ⚠ docs/skills.md not found, skipping');
|
|
136
|
+
} else {
|
|
137
|
+
const original = readFileSync(skillsDoc, 'utf8');
|
|
138
|
+
try {
|
|
139
|
+
// remove every occurrence of mugiwara-frontend entirely
|
|
140
|
+
const broken = original.replace(/mugiwara-frontend/g, '');
|
|
141
|
+
writeFileSync(skillsDoc, broken);
|
|
142
|
+
assert('removed skill mention → exit 1', false, () => run('docs', 'bun scripts/validate-content.ts --check-docs'));
|
|
143
|
+
} finally {
|
|
144
|
+
writeFileSync(skillsDoc, original);
|
|
145
|
+
assert('restored → exit 0', true, () => run('docs', 'bun scripts/validate-content.ts --check-docs'));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// --- Index budget ---
|
|
150
|
+
console.log('\nIndex budget');
|
|
151
|
+
const usingSkillFile = join(root, 'content', 'skills', 'using-mugiwara', 'SKILL.md');
|
|
152
|
+
if (!existsSync(usingSkillFile)) {
|
|
153
|
+
console.log(' ⚠ using-mugiwara not found, skipping');
|
|
154
|
+
} else {
|
|
155
|
+
const original = readFileSync(usingSkillFile, 'utf8');
|
|
156
|
+
try {
|
|
157
|
+
// Push budget way past 5500
|
|
158
|
+
const longDesc = 'A'.repeat(6000);
|
|
159
|
+
const broken = original.replace(/description:.*/, `description: ${longDesc}`);
|
|
160
|
+
writeFileSync(usingSkillFile, broken);
|
|
161
|
+
assert('description past budget → exit 1', false, () => run('budget', 'bun scripts/validate-content.ts'));
|
|
162
|
+
} finally {
|
|
163
|
+
writeFileSync(usingSkillFile, original);
|
|
164
|
+
assert('restored → exit 0', true, () => run('budget', 'bun scripts/validate-content.ts --check-manifest --check-docs'));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- Eval coverage ---
|
|
169
|
+
console.log('\nEval coverage');
|
|
170
|
+
const evalDir = join(root, 'evals', 'cases');
|
|
171
|
+
if (!existsSync(evalDir)) {
|
|
172
|
+
console.log(' ⚠ evals/cases not found, skipping');
|
|
173
|
+
} else {
|
|
174
|
+
assert('eval cases present → run-evals exits 0', true, () => run('evals', 'bun scripts/run-evals.ts'));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- F2: write-scope gate — remove write-scope from one agent, prove red ---
|
|
178
|
+
console.log('\nF2 — write-scope gate');
|
|
179
|
+
const scopeAgent = join(root, 'content', 'agents', 'nami-planner.md');
|
|
180
|
+
if (!existsSync(scopeAgent)) {
|
|
181
|
+
console.log(' ⚠ nami-planner.md not found, skipping');
|
|
182
|
+
} else {
|
|
183
|
+
const original = readFileSync(scopeAgent, 'utf8');
|
|
184
|
+
try {
|
|
185
|
+
const broken = original.replace(/\nwrite-scope: artifacts/, '');
|
|
186
|
+
writeFileSync(scopeAgent, broken);
|
|
187
|
+
assert('missing write-scope → exit 1', false, () => run('F2-scope', 'bun scripts/validate-content.ts'));
|
|
188
|
+
} finally {
|
|
189
|
+
writeFileSync(scopeAgent, original);
|
|
190
|
+
assert('restored → exit 0', true, () => run('F2-scope', 'bun scripts/validate-content.ts --check-manifest --check-docs'));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// --- F2: source-scope on non-executor agent — prove red ---
|
|
195
|
+
console.log('\nF2 — source-scope restriction');
|
|
196
|
+
const plannerAgent = join(root, 'content', 'agents', 'nami-planner.md');
|
|
197
|
+
if (!existsSync(plannerAgent)) {
|
|
198
|
+
console.log(' ⚠ nami-planner.md not found, skipping');
|
|
199
|
+
} else {
|
|
200
|
+
const original = readFileSync(plannerAgent, 'utf8');
|
|
201
|
+
try {
|
|
202
|
+
const broken = original.replace('write-scope: artifacts', 'write-scope: source');
|
|
203
|
+
writeFileSync(plannerAgent, broken);
|
|
204
|
+
assert('source on non-executor → exit 1', false, () => run('F2-source', 'bun scripts/validate-content.ts'));
|
|
205
|
+
} finally {
|
|
206
|
+
writeFileSync(plannerAgent, original);
|
|
207
|
+
assert('restored → exit 0', true, () => run('F2-source', 'bun scripts/validate-content.ts --check-manifest --check-docs'));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// --- F3: remove Return-to-Luffy from one agent — prove red ---
|
|
212
|
+
console.log('\nF3 — hub rule gate');
|
|
213
|
+
const hubAgent = join(root, 'content', 'agents', 'robin-reviewer.md');
|
|
214
|
+
if (!existsSync(hubAgent)) {
|
|
215
|
+
console.log(' ⚠ robin-reviewer.md not found, skipping');
|
|
216
|
+
} else {
|
|
217
|
+
const original = readFileSync(hubAgent, 'utf8');
|
|
218
|
+
try {
|
|
219
|
+
const broken = original.replace(/## Return to Luffy\n\n[\s\S]*?(?=\n## )/, '\n');
|
|
220
|
+
writeFileSync(hubAgent, broken);
|
|
221
|
+
assert('missing Return-to-Luffy → exit 1', false, () => run('F3-hub', 'bun scripts/validate-content.ts'));
|
|
222
|
+
} finally {
|
|
223
|
+
writeFileSync(hubAgent, original);
|
|
224
|
+
assert('restored → exit 0', true, () => run('F3-hub', 'bun scripts/validate-content.ts --check-manifest --check-docs'));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// --- F3: hub-skill gate — every agent must list mugiwara-orchestration; strip it, prove red ---
|
|
229
|
+
console.log('\nF3 — hub-skill gate');
|
|
230
|
+
const hubSkillAgent = join(root, 'content', 'agents', 'sanji-quality.md');
|
|
231
|
+
if (!existsSync(hubSkillAgent)) {
|
|
232
|
+
console.log(' ⚠ sanji-quality.md not found, skipping');
|
|
233
|
+
} else {
|
|
234
|
+
const original = readFileSync(hubSkillAgent, 'utf8');
|
|
235
|
+
try {
|
|
236
|
+
const broken = original.replace('mugiwara-orchestration, ', '').replace(', mugiwara-orchestration', '');
|
|
237
|
+
writeFileSync(hubSkillAgent, broken);
|
|
238
|
+
assert('orchestration skill stripped → exit 1', false, () => run('F3-skill', 'bun scripts/validate-content.ts'));
|
|
239
|
+
} finally {
|
|
240
|
+
writeFileSync(hubSkillAgent, original);
|
|
241
|
+
assert('restored → exit 0', true, () => run('F3-skill', 'bun scripts/validate-content.ts --check-manifest --check-docs'));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// --- F4: reintroduce handoff leak — prove red ---
|
|
246
|
+
console.log('\nF4 — handoff-target gate');
|
|
247
|
+
const brainstormSkill = join(root, 'content', 'skills', 'mugiwara-brainstorm', 'SKILL.md');
|
|
248
|
+
if (!existsSync(brainstormSkill)) {
|
|
249
|
+
console.log(' ⚠ mugiwara-brainstorm/SKILL.md not found, skipping');
|
|
250
|
+
} else {
|
|
251
|
+
const original = readFileSync(brainstormSkill, 'utf8');
|
|
252
|
+
try {
|
|
253
|
+
const broken = original.replace(
|
|
254
|
+
'and return to Luffy, who routes to Nami or Zoro.',
|
|
255
|
+
'and hand to Nami (mugiwara-planning) via the main thread.'
|
|
256
|
+
);
|
|
257
|
+
writeFileSync(brainstormSkill, broken);
|
|
258
|
+
assert('handoff leak → exit 1', false, () => run('F4-handoff', 'bun scripts/validate-content.ts'));
|
|
259
|
+
} finally {
|
|
260
|
+
writeFileSync(brainstormSkill, original);
|
|
261
|
+
assert('restored → exit 0', true, () => run('F4-handoff', 'bun scripts/validate-content.ts --check-manifest --check-docs'));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
266
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
@@ -0,0 +1,248 @@
|
|
|
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
|
+
function parseSubMissions(content: string): SubMission[] {
|
|
50
|
+
const lines = content.split('\n');
|
|
51
|
+
let inTable = false;
|
|
52
|
+
let inSubSection = false;
|
|
53
|
+
const subs: SubMission[] = [];
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < lines.length; i++) {
|
|
56
|
+
const line = lines[i];
|
|
57
|
+
if (line.startsWith('## Sub-missions')) {
|
|
58
|
+
inSubSection = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (inSubSection && line.startsWith('## ') && !line.startsWith('## Sub-missions')) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if (!inSubSection) continue;
|
|
65
|
+
|
|
66
|
+
const trimmed = line.trim();
|
|
67
|
+
if (trimmed.startsWith('| ID ') && trimmed.includes('| Name ')) {
|
|
68
|
+
inTable = true;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (inTable && trimmed.startsWith('|---')) continue;
|
|
72
|
+
if (inTable && trimmed.startsWith('|')) {
|
|
73
|
+
const cols = trimmed.split('|').map(c => c.trim()).filter(Boolean);
|
|
74
|
+
if (cols.length >= 6) {
|
|
75
|
+
subs.push({
|
|
76
|
+
id: cols[0],
|
|
77
|
+
name: cols[1],
|
|
78
|
+
assignee: cols[2],
|
|
79
|
+
branch: cols[3],
|
|
80
|
+
status: cols[4],
|
|
81
|
+
dependsOn: cols[5],
|
|
82
|
+
touchedFiles: cols.slice(6).join(' ').split(/\s+/).filter(Boolean),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (inTable && !trimmed.startsWith('|')) {
|
|
87
|
+
inTable = false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return subs;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function cmdStatus(planFile: string): void {
|
|
95
|
+
const content = readFileSync(planFile, 'utf8');
|
|
96
|
+
const subs = parseSubMissions(content);
|
|
97
|
+
|
|
98
|
+
if (subs.length === 0) {
|
|
99
|
+
console.log('No sub-missions found — solo mission.');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const doneCount = subs.filter(s => s.status === '[x]').length;
|
|
104
|
+
const total = subs.length;
|
|
105
|
+
const pct = Math.round((doneCount / total) * 100);
|
|
106
|
+
const barLen = 20;
|
|
107
|
+
const filled = Math.round((doneCount / total) * barLen);
|
|
108
|
+
const bar = '█'.repeat(filled) + '░'.repeat(barLen - filled);
|
|
109
|
+
|
|
110
|
+
console.log(`\n${doneCount}/${total} done [${bar}] ${pct}%\n`);
|
|
111
|
+
console.log(`${'ID'.padEnd(8)} ${'Name'.padEnd(20)} ${'Assignee'.padEnd(12)} ${'Branch'.padEnd(28)} ${'Status'.padEnd(8)} ${'Depends'.padEnd(10)} ${'Files'}`);
|
|
112
|
+
console.log('─'.repeat(120));
|
|
113
|
+
|
|
114
|
+
for (const s of subs) {
|
|
115
|
+
const icon = { '[ ]': '◻', '[~]': '◉', '[x]': '✓', '[!]': '✗' }[s.status] || '?';
|
|
116
|
+
console.log(
|
|
117
|
+
`${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(', ')}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
console.log();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function cmdConflictCheck(planFile: string): void {
|
|
124
|
+
const content = readFileSync(planFile, 'utf8');
|
|
125
|
+
const subs = parseSubMissions(content);
|
|
126
|
+
|
|
127
|
+
if (subs.length === 0) {
|
|
128
|
+
console.log('No sub-missions — no conflicts possible.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const active = subs.filter(s => s.status === '[~]');
|
|
133
|
+
if (active.length < 2) {
|
|
134
|
+
console.log('Fewer than 2 in-progress sub-missions — no conflicts to check.');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const fileToSubs = new Map<string, string[]>();
|
|
139
|
+
for (const s of active) {
|
|
140
|
+
for (const f of s.touchedFiles) {
|
|
141
|
+
const entries = fileToSubs.get(f) || [];
|
|
142
|
+
entries.push(s.id);
|
|
143
|
+
fileToSubs.set(f, entries);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const conflicts: string[] = [];
|
|
148
|
+
for (const [file, ids] of fileToSubs) {
|
|
149
|
+
if (ids.length > 1) {
|
|
150
|
+
conflicts.push(` ⚠ ${file} — touched by: ${ids.join(', ')}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (conflicts.length === 0) {
|
|
155
|
+
console.log('No file conflicts among in-progress sub-missions.');
|
|
156
|
+
} else {
|
|
157
|
+
console.log(`\n${conflicts.length} file conflict(s) detected:\n`);
|
|
158
|
+
console.log(conflicts.join('\n'));
|
|
159
|
+
console.log();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function cmdSetStatus(planFile: string, id: string, status: string): void {
|
|
164
|
+
if (!STATUS_MARKERS[id]) {
|
|
165
|
+
// `status` here is the status string from args — check it
|
|
166
|
+
}
|
|
167
|
+
if (!STATUS_MARKERS[status]) {
|
|
168
|
+
console.error(`Invalid status: "${status}". Use: pending, in-progress, done, blocked`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const content = readFileSync(planFile, 'utf8');
|
|
173
|
+
const lines = content.split('\n');
|
|
174
|
+
const newMarker = STATUS_MARKERS[status];
|
|
175
|
+
let inTable = false;
|
|
176
|
+
let inSubSection = false;
|
|
177
|
+
let found = false;
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < lines.length; i++) {
|
|
180
|
+
const line = lines[i];
|
|
181
|
+
if (line.startsWith('## Sub-missions')) {
|
|
182
|
+
inSubSection = true;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (inSubSection && line.startsWith('## ') && !line.startsWith('## Sub-missions')) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
if (!inSubSection) continue;
|
|
189
|
+
|
|
190
|
+
const trimmed = line.trim();
|
|
191
|
+
if (trimmed.startsWith('| ID ')) { inTable = true; continue; }
|
|
192
|
+
if (!inTable || !trimmed.startsWith('|')) continue;
|
|
193
|
+
if (trimmed.startsWith('|---')) continue;
|
|
194
|
+
|
|
195
|
+
const cols = trimmed.split('|').map(c => c.trim()).filter(Boolean);
|
|
196
|
+
if (cols[0] === id) {
|
|
197
|
+
const oldMarker = cols[4];
|
|
198
|
+
if (!Object.values(STATUS_MARKERS).includes(oldMarker)) {
|
|
199
|
+
console.error(`Row ${id}: status column "${oldMarker}" is not a recognized marker`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
lines[i] = lines[i].replace(oldMarker, newMarker);
|
|
203
|
+
found = true;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!found) {
|
|
209
|
+
console.error(`Sub-mission "${id}" not found in Sub-missions table.`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
writeFileSync(planFile, lines.join('\n'));
|
|
214
|
+
console.log(`✓ ${id}: ${status}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// --- main ---
|
|
218
|
+
|
|
219
|
+
const args = process.argv.slice(2);
|
|
220
|
+
|
|
221
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
222
|
+
console.log(USAGE);
|
|
223
|
+
process.exit(0);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const cmd = args[0];
|
|
227
|
+
|
|
228
|
+
if (cmd === 'status') {
|
|
229
|
+
if (args.length < 2) { console.error('Missing plan-file argument.'); process.exit(1); }
|
|
230
|
+
cmdStatus(resolvePlan(args[1]));
|
|
231
|
+
} else if (cmd === 'conflict-check') {
|
|
232
|
+
if (args.length < 2) { console.error('Missing plan-file argument.'); process.exit(1); }
|
|
233
|
+
cmdConflictCheck(resolvePlan(args[1]));
|
|
234
|
+
} else if (cmd === 'set-status') {
|
|
235
|
+
const idIdx = args.indexOf('--id');
|
|
236
|
+
const statusIdx = args.indexOf('--status');
|
|
237
|
+
if (idIdx === -1 || statusIdx === -1 || args.length < 6) {
|
|
238
|
+
console.error('set-status requires --id <id> --status <status>');
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
const id = args[idIdx + 1];
|
|
242
|
+
const statusVal = args[statusIdx + 1];
|
|
243
|
+
cmdSetStatus(resolvePlan(args[1]), id, statusVal);
|
|
244
|
+
} else {
|
|
245
|
+
console.error(`Unknown command: "${cmd}"`);
|
|
246
|
+
console.log(USAGE);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
package/scripts/lane.sh
CHANGED
|
@@ -57,6 +57,21 @@ if [ "$HAS_SENSITIVE" -eq 1 ] && [ "$LANE" != "full" ]; then
|
|
|
57
57
|
REASON="sensitive paths ($SENSITIVE) — escalated from $PREV"
|
|
58
58
|
fi
|
|
59
59
|
|
|
60
|
+
# path-weighted sizing: docs-only changes (markdown/config/docs outside the
|
|
61
|
+
# product surface) never escalate to full from file count alone — size by the
|
|
62
|
+
# code surface actually touched. Sensitive-path escalation above still wins.
|
|
63
|
+
# Product surface for mugiwara: content/, src/, scripts/, test/, hooks/,
|
|
64
|
+
# .opencode/, .claude/, evals/ — everything else is docs/config/asset.
|
|
65
|
+
PRODUCT_PAT="^content/|^src/|^scripts/|^test/|^hooks/|^\.opencode/|^\.claude/|^evals/"
|
|
66
|
+
if [ "$LANE" = "full" ] && [ -n "$CHANGED" ]; then
|
|
67
|
+
CODE_COUNT=$(echo "$CHANGED" | grep -E "$PRODUCT_PAT" 2>/dev/null | grep -c . || true)
|
|
68
|
+
if [ -z "$CODE_COUNT" ] || [ "$CODE_COUNT" -eq 0 ] 2>/dev/null; then
|
|
69
|
+
PREV="$LANE"
|
|
70
|
+
LANE="standard"
|
|
71
|
+
REASON="$FILE_COUNT files, docs-only — path-weighted down from $PREV"
|
|
72
|
+
fi
|
|
73
|
+
fi
|
|
74
|
+
|
|
60
75
|
if [ "$JSON_OUT" -eq 1 ]; then
|
|
61
76
|
SENS_ARR=""
|
|
62
77
|
[ -n "$SENSITIVE" ] && SENS_ARR=$(echo "$SENSITIVE" | tr ',' '\n' | sed 's/^/"/;s/$/"/' | tr '\n' ',' | sed 's/,$//')
|