@ionivetech/mugiwara 0.5.5 → 0.6.1

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 (151) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/.opencode/commands/mugiwara-continue.md +16 -0
  7. package/.opencode/commands/mugiwara-execute.md +6 -4
  8. package/.opencode/commands/mugiwara-heal.md +6 -4
  9. package/.opencode/commands/mugiwara-plan.md +6 -4
  10. package/.opencode/commands/mugiwara-review.md +6 -4
  11. package/.opencode/commands/mugiwara-security.md +6 -4
  12. package/.opencode/commands/mugiwara-ship.md +6 -4
  13. package/.opencode/commands/mugiwara.md +4 -2
  14. package/.opencode/commands/using-mugiwara.md +7 -13
  15. package/.opencode/mugiwara-helpers.mjs +24 -0
  16. package/.opencode/plugins/mugiwara.mjs +28 -8
  17. package/AGENTS.md +1 -1
  18. package/GEMINI.md +1 -1
  19. package/README.md +311 -127
  20. package/content/agents/brook-healing.md +17 -3
  21. package/content/agents/chopper-checkpoint.md +17 -3
  22. package/content/agents/eval-runner.md +17 -2
  23. package/content/agents/franky-gates.md +19 -5
  24. package/content/agents/jinbe-security.md +19 -3
  25. package/content/agents/luffy-orchestrator.md +6 -3
  26. package/content/agents/memory-keeper.md +15 -0
  27. package/content/agents/nami-planner.md +21 -6
  28. package/content/agents/onboarding-guide.md +145 -0
  29. package/content/agents/resume-coordinator.md +21 -5
  30. package/content/agents/robin-reviewer.md +18 -3
  31. package/content/agents/sanji-quality.md +19 -4
  32. package/content/agents/skeptic-verifier.md +18 -3
  33. package/content/agents/usopp-brainstorm.md +17 -3
  34. package/content/agents/zoro-execution.md +18 -3
  35. package/content/skills/mugiwara-backend/SKILL.md +1 -1
  36. package/content/skills/mugiwara-brainstorm/SKILL.md +12 -1
  37. package/content/skills/mugiwara-checkpoint/SKILL.md +2 -2
  38. package/content/skills/mugiwara-contract-first/SKILL.md +2 -36
  39. package/content/skills/mugiwara-contract-first/references/process.md +37 -0
  40. package/content/skills/mugiwara-execution/SKILL.md +51 -34
  41. package/content/skills/mugiwara-execution/references/dispatch.md +41 -0
  42. package/content/skills/mugiwara-execution/references/resume-batching.md +32 -0
  43. package/content/skills/mugiwara-frontend/SKILL.md +1 -1
  44. package/content/skills/mugiwara-gates/SKILL.md +32 -30
  45. package/content/skills/mugiwara-healing/SKILL.md +1 -1
  46. package/content/skills/mugiwara-orchestration/SKILL.md +59 -39
  47. package/content/skills/mugiwara-orchestration/references/closure.md +34 -0
  48. package/content/skills/mugiwara-orchestration/references/delegation-pillars.md +40 -0
  49. package/content/skills/mugiwara-orchestration/references/triage-escalation.md +97 -0
  50. package/content/skills/mugiwara-planning/SKILL.md +29 -27
  51. package/content/skills/mugiwara-planning/references/plan-template.md +42 -0
  52. package/content/skills/mugiwara-pr/SKILL.md +9 -2
  53. package/content/skills/mugiwara-quality/SKILL.md +17 -6
  54. package/content/skills/mugiwara-resume/SKILL.md +7 -2
  55. package/content/skills/mugiwara-review/SKILL.md +20 -1
  56. package/content/skills/mugiwara-review/references/code-attributes.md +38 -0
  57. package/content/skills/mugiwara-root-cause/SKILL.md +3 -30
  58. package/content/skills/mugiwara-root-cause/references/process.md +35 -0
  59. package/content/skills/mugiwara-security/SKILL.md +17 -32
  60. package/content/skills/mugiwara-security/references/owasp-top10.md +12 -0
  61. package/content/skills/mugiwara-security/references/rationalizations.md +8 -0
  62. package/content/skills/mugiwara-ship/SKILL.md +13 -10
  63. package/content/skills/mugiwara-testcases/SKILL.md +7 -0
  64. package/content/skills/mugiwara-workflow/SKILL.md +74 -77
  65. package/content/skills/mugiwara-workflow/references/workspace-layout.md +50 -0
  66. package/content/skills/using-mugiwara/SKILL.md +13 -18
  67. package/dist/mugiwara.js +293 -47
  68. package/gemini-extension.json +1 -1
  69. package/hooks/mugiwara-mode-tracker.ts +0 -0
  70. package/hooks/session-start.ts +1 -1
  71. package/package.json +11 -4
  72. package/plugin.json +1 -1
  73. package/references/multi-actor.md +1 -1
  74. package/scripts/evidence.sh +28 -6
  75. package/scripts/gate-selftest.ts +317 -0
  76. package/scripts/initiative.ts +262 -0
  77. package/scripts/lane.sh +18 -1
  78. package/scripts/mission-report.sh +237 -36
  79. package/scripts/onboard.ts +292 -0
  80. package/scripts/release-notes.ts +42 -13
  81. package/scripts/savepoint.sh +151 -60
  82. package/scripts/validate-content.ts +97 -0
  83. package/scripts/verify-install.ts +72 -0
  84. package/src/cli.ts +20 -3
  85. package/src/installer.ts +71 -8
  86. package/src/mission.ts +117 -5
  87. package/src/targets/claude.ts +45 -9
  88. package/src/targets/generic.ts +1 -1
  89. package/src/targets/opencode.ts +31 -10
  90. package/docs/concepts/agents.md +0 -53
  91. package/docs/concepts/audit-trail.md +0 -65
  92. package/docs/concepts/comparison.md +0 -58
  93. package/docs/concepts/config.md +0 -55
  94. package/docs/concepts/cost.md +0 -45
  95. package/docs/concepts/execution-model.md +0 -92
  96. package/docs/concepts/git-strategy.md +0 -62
  97. package/docs/concepts/lanes.md +0 -82
  98. package/docs/concepts/modes.md +0 -73
  99. package/docs/concepts/pr-summary.md +0 -54
  100. package/docs/concepts/skills.md +0 -55
  101. package/docs/concepts/workflow.md +0 -89
  102. package/docs/getting-started.md +0 -158
  103. package/docs/index.md +0 -56
  104. package/docs/install/antigravity.md +0 -45
  105. package/docs/install/claude.md +0 -77
  106. package/docs/install/cli.md +0 -115
  107. package/docs/install/codex.md +0 -44
  108. package/docs/install/copilot.md +0 -45
  109. package/docs/install/cursor.md +0 -45
  110. package/docs/install/gemini.md +0 -44
  111. package/docs/install/index.md +0 -53
  112. package/docs/install/kimi.md +0 -45
  113. package/docs/install/opencode.md +0 -143
  114. package/docs/install/pi.md +0 -46
  115. package/docs/reference/adoption-guide.md +0 -72
  116. package/docs/reference/agent-anatomy.md +0 -72
  117. package/docs/reference/compliance-matrix.md +0 -81
  118. package/docs/reference/developer-onboarding.md +0 -89
  119. package/docs/reference/enforcement.md +0 -35
  120. package/docs/reference/harness-matrix.md +0 -41
  121. package/docs/reference/skill-anatomy.md +0 -71
  122. package/docs/troubleshooting.md +0 -91
  123. package/evals/cases/_no-skill.json +0 -16
  124. package/evals/cases/adversarial-pressure-fake-pass.json +0 -25
  125. package/evals/cases/adversarial-pressure-skip-review.json +0 -25
  126. package/evals/cases/lane-exploratory-vague.json +0 -24
  127. package/evals/cases/lane-sensitivity-payment.json +0 -24
  128. package/evals/cases/positive-refactor-existing-tests.json +0 -25
  129. package/evals/cases/positive-resume-mid-mission.json +0 -24
  130. package/evals/cases/routing-agent-security.json +0 -25
  131. package/evals/cases/routing-auth-feature.json +0 -26
  132. package/evals/cases/routing-backend.json +0 -25
  133. package/evals/cases/routing-bug-one-file.json +0 -26
  134. package/evals/cases/routing-claim-audit.json +0 -25
  135. package/evals/cases/routing-context-budget.json +0 -25
  136. package/evals/cases/routing-contract-first.json +0 -25
  137. package/evals/cases/routing-execution.json +0 -25
  138. package/evals/cases/routing-frontend.json +0 -26
  139. package/evals/cases/routing-gates.json +0 -25
  140. package/evals/cases/routing-git.json +0 -25
  141. package/evals/cases/routing-healing.json +0 -25
  142. package/evals/cases/routing-lessons.json +0 -25
  143. package/evals/cases/routing-orchestration.json +0 -25
  144. package/evals/cases/routing-planning.json +0 -26
  145. package/evals/cases/routing-pr.json +0 -25
  146. package/evals/cases/routing-quality.json +0 -25
  147. package/evals/cases/routing-ship.json +0 -26
  148. package/evals/cases/routing-sunset.json +0 -25
  149. package/evals/cases/routing-using-mugiwara.json +0 -25
  150. package/evals/cases/routing-workflow.json +0 -25
  151. package/evals/floor.json +0 -6
@@ -1,17 +1,39 @@
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
- LABEL="${1:-}"
10
- shift 2>/dev/null || true
11
- [ -z "$LABEL" ] && die "usage: evidence.sh <label> [-- command args...]"
9
+ MISSION="${1:-}"
10
+ LABEL="${2:-}"
11
+ shift 2 2>/dev/null || true
12
+
13
+ # strip an optional "--" separator between <label> and the command
14
+ [ "${1:-}" = "--" ] && shift
15
+
16
+ [ -z "$MISSION" ] && die "usage: evidence.sh <mission> <label> [-- command args...]"
17
+ [ -z "$LABEL" ] && die "usage: evidence.sh <mission> <label> [-- command args...]"
18
+
19
+ # mission allowlist — path-traversal guard (same rule as mission-report.sh)
20
+ case "$MISSION" in
21
+ *[!a-zA-Z0-9._-]*) die "invalid mission name \"$MISSION\" (allowlist: [a-zA-Z0-9._-])" ;;
22
+ esac
23
+
24
+ # label allowlist — LABEL feeds the output filename; traversal or shell
25
+ # metacharacters must not reach the filesystem (same rule as MISSION)
26
+ case "$LABEL" in
27
+ *[!a-zA-Z0-9._-]*) die "invalid label \"$LABEL\" (allowlist: [a-zA-Z0-9._-])" ;;
28
+ esac
29
+ # dot-only labels (".", "..", "...") pass the char allowlist but escape the
30
+ # results dir — reject them before any filename is built from LABEL.
31
+ if [[ "$LABEL" =~ ^\.+$ ]]; then
32
+ die "invalid label \"$LABEL\" (allowlist: [a-zA-Z0-9._-], not a dot-path)"
33
+ fi
12
34
 
13
35
  MUGIWARA_DIR="${MUGIWARA_DIR:-.mugiwara}"
14
- RESULTS_DIR="$MUGIWARA_DIR/results"
36
+ RESULTS_DIR="$MUGIWARA_DIR/results/$MISSION"
15
37
  mkdir -p "$RESULTS_DIR"
16
38
 
17
39
  TIMESTAMP=$(date +%Y%m%d-%H%M%S)
@@ -0,0 +1,317 @@
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## Red flags\n\n- Test flag\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
+ // --- F2: red-flags gate — strip the heading from one skill, prove red ---
266
+ console.log('\nF2 — red-flags gate');
267
+ const redSkill = join(root, 'content', 'skills', 'mugiwara-workflow', 'SKILL.md');
268
+ if (!existsSync(redSkill)) {
269
+ console.log(' ⚠ mugiwara-workflow/SKILL.md not found, skipping');
270
+ } else {
271
+ const original = readFileSync(redSkill, 'utf8');
272
+ try {
273
+ const broken = original.replace('## Red flags', '## Disabled');
274
+ writeFileSync(redSkill, broken);
275
+ assert('missing Red flags → exit 1', false, () => run('F2-redflags', 'bun scripts/validate-content.ts'));
276
+ } finally {
277
+ writeFileSync(redSkill, original);
278
+ assert('restored → exit 0', true, () => run('F2-redflags', 'bun scripts/validate-content.ts --check-manifest --check-docs'));
279
+ }
280
+ }
281
+
282
+ // --- W1: write-boundary gate — strip the section, prove red ---
283
+ console.log('\nW1 — write-boundary gate');
284
+ const wbSkill = join(root, 'content', 'skills', 'mugiwara-orchestration', 'SKILL.md');
285
+ if (!existsSync(wbSkill)) {
286
+ console.log(' ⚠ mugiwara-orchestration/SKILL.md not found, skipping');
287
+ } else {
288
+ const original = readFileSync(wbSkill, 'utf8');
289
+ try {
290
+ const broken = original.replace('## Write boundary', '## Boundary');
291
+ writeFileSync(wbSkill, broken);
292
+ assert('missing write boundary → exit 1', false, () => run('W1-wb', 'bun scripts/validate-content.ts'));
293
+ } finally {
294
+ writeFileSync(wbSkill, original);
295
+ assert('restored → exit 0', true, () => run('W1-wb', 'bun scripts/validate-content.ts --check-manifest --check-docs'));
296
+ }
297
+ }
298
+
299
+ // --- F14: agent-count gate — flip an internal agent to user-facing, prove red ---
300
+ console.log('\nF14 — agent-count gate');
301
+ const countAgent = join(root, 'content', 'agents', 'eval-runner.md');
302
+ if (!existsSync(countAgent)) {
303
+ console.log(' ⚠ eval-runner.md not found, skipping');
304
+ } else {
305
+ const original = readFileSync(countAgent, 'utf8');
306
+ try {
307
+ const broken = original.replace('internal: true', 'internal: false');
308
+ writeFileSync(countAgent, broken);
309
+ assert('count drift → exit 1', false, () => run('F14-count', 'bun scripts/validate-content.ts'));
310
+ } finally {
311
+ writeFileSync(countAgent, original);
312
+ assert('restored → exit 0', true, () => run('F14-count', 'bun scripts/validate-content.ts --check-manifest --check-docs'));
313
+ }
314
+ }
315
+
316
+ console.log(`\n${passed} passed, ${failed} failed`);
317
+ process.exit(failed > 0 ? 1 : 0);
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env bun
2
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+
5
+ const USAGE = `initiative — team sub-mission coordination for mugiwara plans
6
+
7
+ Usage:
8
+ bun run scripts/initiative.ts status <plan-file>
9
+ bun run scripts/initiative.ts conflict-check <plan-file>
10
+ bun run scripts/initiative.ts set-status <plan-file> --id <id> --status <pending|in-progress|done|blocked>
11
+
12
+ Commands:
13
+ status Parse Sub-missions table, render ASCII dashboard
14
+ conflict-check Detect overlapping Touched Files across in-progress sub-missions
15
+ set-status Update status marker for a sub-mission row
16
+ `;
17
+
18
+ interface SubMission {
19
+ id: string;
20
+ name: string;
21
+ assignee: string;
22
+ branch: string;
23
+ status: string;
24
+ dependsOn: string;
25
+ touchedFiles: string[];
26
+ }
27
+
28
+ const STATUS_MARKERS: Record<string, string> = {
29
+ pending: '[ ]',
30
+ 'in-progress': '[~]',
31
+ done: '[x]',
32
+ blocked: '[!]',
33
+ };
34
+
35
+ function resolvePlan(planFile: string): string {
36
+ const resolved = resolve(process.cwd(), planFile);
37
+ if (existsSync(resolved)) return resolved;
38
+
39
+ const plansDir = join(process.cwd(), '.mugiwara', 'plans');
40
+ const plansPath = join(plansDir, planFile.endsWith('.md') ? planFile : `${planFile}.md`);
41
+ if (existsSync(plansPath)) return plansPath;
42
+ if (!planFile.endsWith('.md') && existsSync(join(plansDir, planFile))) return join(plansDir, planFile);
43
+
44
+ const alt = join(plansDir, `${planFile}.md`);
45
+ const err = `Plan not found: "${planFile}" (tried: ${resolved}, ${plansPath}, ${alt})`;
46
+ throw new Error(err);
47
+ }
48
+
49
+ // A plan is a markdown document. Garbage (no markdown structure) is not a
50
+ // "solo mission" — it is a malformed plan. Heading check keeps the
51
+ // no-sub-missions-but-valid-markdown case working as solo mission.
52
+ function assertPlanContent(content: string, planFile: string): void {
53
+ if (!/^#{1,6}\s/m.test(content)) {
54
+ throw new Error(`Not a valid plan file: "${planFile}" — expected markdown (no heading found)`);
55
+ }
56
+ }
57
+
58
+ function parseSubMissions(content: string): SubMission[] {
59
+ const lines = content.split('\n');
60
+ let inTable = false;
61
+ let inSubSection = false;
62
+ const subs: SubMission[] = [];
63
+
64
+ for (let i = 0; i < lines.length; i++) {
65
+ const line = lines[i];
66
+ if (line.startsWith('## Sub-missions')) {
67
+ inSubSection = true;
68
+ continue;
69
+ }
70
+ if (inSubSection && line.startsWith('## ') && !line.startsWith('## Sub-missions')) {
71
+ break;
72
+ }
73
+ if (!inSubSection) continue;
74
+
75
+ const trimmed = line.trim();
76
+ if (trimmed.startsWith('| ID ') && trimmed.includes('| Name ')) {
77
+ inTable = true;
78
+ continue;
79
+ }
80
+ if (inTable && trimmed.startsWith('|---')) continue;
81
+ if (inTable && trimmed.startsWith('|')) {
82
+ const cols = trimmed.split('|').map(c => c.trim()).filter(Boolean);
83
+ if (cols.length >= 6) {
84
+ subs.push({
85
+ id: cols[0],
86
+ name: cols[1],
87
+ assignee: cols[2],
88
+ branch: cols[3],
89
+ status: cols[4],
90
+ dependsOn: cols[5],
91
+ touchedFiles: cols.slice(6).join(' ').split(/\s+/).filter(Boolean),
92
+ });
93
+ }
94
+ }
95
+ if (inTable && !trimmed.startsWith('|')) {
96
+ inTable = false;
97
+ }
98
+ }
99
+
100
+ return subs;
101
+ }
102
+
103
+ function cmdStatus(planFile: string): void {
104
+ const content = readFileSync(planFile, 'utf8');
105
+ assertPlanContent(content, planFile);
106
+ const subs = parseSubMissions(content);
107
+
108
+ if (subs.length === 0) {
109
+ console.log('No sub-missions found — solo mission.');
110
+ return;
111
+ }
112
+
113
+ const doneCount = subs.filter(s => s.status === '[x]').length;
114
+ const total = subs.length;
115
+ const pct = Math.round((doneCount / total) * 100);
116
+ const barLen = 20;
117
+ const filled = Math.round((doneCount / total) * barLen);
118
+ const bar = '█'.repeat(filled) + '░'.repeat(barLen - filled);
119
+
120
+ console.log(`\n${doneCount}/${total} done [${bar}] ${pct}%\n`);
121
+ console.log(`${'ID'.padEnd(8)} ${'Name'.padEnd(20)} ${'Assignee'.padEnd(12)} ${'Branch'.padEnd(28)} ${'Status'.padEnd(8)} ${'Depends'.padEnd(10)} ${'Files'}`);
122
+ console.log('─'.repeat(120));
123
+
124
+ for (const s of subs) {
125
+ const icon = { '[ ]': '◻', '[~]': '◉', '[x]': '✓', '[!]': '✗' }[s.status] || '?';
126
+ console.log(
127
+ `${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(', ')}`
128
+ );
129
+ }
130
+ console.log();
131
+ }
132
+
133
+ function cmdConflictCheck(planFile: string): void {
134
+ const content = readFileSync(planFile, 'utf8');
135
+ assertPlanContent(content, planFile);
136
+ const subs = parseSubMissions(content);
137
+
138
+ if (subs.length === 0) {
139
+ console.log('No sub-missions — no conflicts possible.');
140
+ return;
141
+ }
142
+
143
+ const active = subs.filter(s => s.status === '[~]');
144
+ if (active.length < 2) {
145
+ console.log('Fewer than 2 in-progress sub-missions — no conflicts to check.');
146
+ return;
147
+ }
148
+
149
+ const fileToSubs = new Map<string, string[]>();
150
+ for (const s of active) {
151
+ for (const f of s.touchedFiles) {
152
+ const entries = fileToSubs.get(f) || [];
153
+ entries.push(s.id);
154
+ fileToSubs.set(f, entries);
155
+ }
156
+ }
157
+
158
+ const conflicts: string[] = [];
159
+ for (const [file, ids] of fileToSubs) {
160
+ if (ids.length > 1) {
161
+ conflicts.push(` ⚠ ${file} — touched by: ${ids.join(', ')}`);
162
+ }
163
+ }
164
+
165
+ if (conflicts.length === 0) {
166
+ console.log('No file conflicts among in-progress sub-missions.');
167
+ } else {
168
+ console.log(`\n${conflicts.length} file conflict(s) detected:\n`);
169
+ console.log(conflicts.join('\n'));
170
+ console.log();
171
+ }
172
+ }
173
+
174
+ function cmdSetStatus(planFile: string, id: string, status: string): void {
175
+ if (!STATUS_MARKERS[status]) {
176
+ console.error(`Invalid status: "${status}". Use: pending, in-progress, done, blocked`);
177
+ process.exit(1);
178
+ }
179
+
180
+ const content = readFileSync(planFile, 'utf8');
181
+ assertPlanContent(content, planFile);
182
+ const lines = content.split('\n');
183
+ const newMarker = STATUS_MARKERS[status];
184
+ let inTable = false;
185
+ let inSubSection = false;
186
+ let found = false;
187
+
188
+ for (let i = 0; i < lines.length; i++) {
189
+ const line = lines[i];
190
+ if (line.startsWith('## Sub-missions')) {
191
+ inSubSection = true;
192
+ continue;
193
+ }
194
+ if (inSubSection && line.startsWith('## ') && !line.startsWith('## Sub-missions')) {
195
+ break;
196
+ }
197
+ if (!inSubSection) continue;
198
+
199
+ const trimmed = line.trim();
200
+ if (trimmed.startsWith('| ID ')) { inTable = true; continue; }
201
+ if (!inTable || !trimmed.startsWith('|')) continue;
202
+ if (trimmed.startsWith('|---')) continue;
203
+
204
+ const cols = trimmed.split('|').map(c => c.trim()).filter(Boolean);
205
+ if (cols[0] === id) {
206
+ const oldMarker = cols[4];
207
+ if (!Object.values(STATUS_MARKERS).includes(oldMarker)) {
208
+ console.error(`Row ${id}: status column "${oldMarker}" is not a recognized marker`);
209
+ process.exit(1);
210
+ }
211
+ lines[i] = lines[i].replace(oldMarker, newMarker);
212
+ found = true;
213
+ break;
214
+ }
215
+ }
216
+
217
+ if (!found) {
218
+ console.error(`Sub-mission "${id}" not found in Sub-missions table.`);
219
+ process.exit(1);
220
+ }
221
+
222
+ writeFileSync(planFile, lines.join('\n'));
223
+ console.log(`✓ ${id}: ${status}`);
224
+ }
225
+
226
+ // --- main ---
227
+
228
+ const args = process.argv.slice(2);
229
+
230
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
231
+ console.log(USAGE);
232
+ process.exit(0);
233
+ }
234
+
235
+ const cmd = args[0];
236
+
237
+ try {
238
+ if (cmd === 'status') {
239
+ if (args.length < 2) { console.error('Missing plan-file argument.'); process.exit(1); }
240
+ cmdStatus(resolvePlan(args[1]));
241
+ } else if (cmd === 'conflict-check') {
242
+ if (args.length < 2) { console.error('Missing plan-file argument.'); process.exit(1); }
243
+ cmdConflictCheck(resolvePlan(args[1]));
244
+ } else if (cmd === 'set-status') {
245
+ const idIdx = args.indexOf('--id');
246
+ const statusIdx = args.indexOf('--status');
247
+ if (idIdx === -1 || statusIdx === -1 || args.length < 6) {
248
+ console.error('set-status requires --id <id> --status <status>');
249
+ process.exit(1);
250
+ }
251
+ const id = args[idIdx + 1];
252
+ const statusVal = args[statusIdx + 1];
253
+ cmdSetStatus(resolvePlan(args[1]), id, statusVal);
254
+ } else {
255
+ console.error(`Unknown command: "${cmd}"`);
256
+ console.log(USAGE);
257
+ process.exit(1);
258
+ }
259
+ } catch (err) {
260
+ console.error(err instanceof Error ? err.message : String(err));
261
+ process.exit(1);
262
+ }
package/scripts/lane.sh CHANGED
@@ -51,12 +51,29 @@ else
51
51
  REASON="$FILE_COUNT files"
52
52
  fi
53
53
 
54
- if [ "$HAS_SENSITIVE" -eq 1 ] && [ "$LANE" != "full" ]; then
54
+ # Sensitive-path escalation is unconditional it wins over any count-based
55
+ # lane AND over the docs-only downgrade below (bug C9).
56
+ if [ "$HAS_SENSITIVE" -eq 1 ]; then
55
57
  PREV="$LANE"
56
58
  LANE="full"
57
59
  REASON="sensitive paths ($SENSITIVE) — escalated from $PREV"
58
60
  fi
59
61
 
62
+ # path-weighted sizing: docs-only changes (markdown/config/docs outside the
63
+ # product surface) never escalate to full from file count alone — size by the
64
+ # code surface actually touched. Sensitive-path escalation above still wins.
65
+ # Product surface for mugiwara: content/, src/, scripts/, test/, hooks/,
66
+ # .opencode/, .claude/, evals/ — everything else is docs/config/asset.
67
+ PRODUCT_PAT="^content/|^src/|^scripts/|^test/|^hooks/|^\.opencode/|^\.claude/|^evals/"
68
+ if [ "$LANE" = "full" ] && [ "$HAS_SENSITIVE" -eq 0 ] && [ -n "$CHANGED" ]; then
69
+ CODE_COUNT=$(echo "$CHANGED" | grep -E "$PRODUCT_PAT" 2>/dev/null | grep -c . || true)
70
+ if [ -z "$CODE_COUNT" ] || [ "$CODE_COUNT" -eq 0 ] 2>/dev/null; then
71
+ PREV="$LANE"
72
+ LANE="standard"
73
+ REASON="$FILE_COUNT files, docs-only — path-weighted down from $PREV"
74
+ fi
75
+ fi
76
+
60
77
  if [ "$JSON_OUT" -eq 1 ]; then
61
78
  SENS_ARR=""
62
79
  [ -n "$SENSITIVE" ] && SENS_ARR=$(echo "$SENSITIVE" | tr ',' '\n' | sed 's/^/"/;s/$/"/' | tr '\n' ',' | sed 's/,$//')