@ionivetech/mugiwara 0.6.3 → 0.6.4
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/mugiwara-helpers.mjs +1 -0
- package/.opencode/plugins/mugiwara.mjs +39 -7
- package/README.md +1 -0
- package/content/agents/zoro-execution.md +1 -1
- package/content/skills/mugiwara-execution/SKILL.md +12 -11
- package/content/skills/mugiwara-orchestration/SKILL.md +6 -3
- package/content/skills/mugiwara-orchestration/references/check-ins.md +15 -4
- package/content/skills/mugiwara-orchestration/references/closure.md +9 -0
- package/content/skills/mugiwara-pr/SKILL.md +11 -18
- package/content/skills/mugiwara-pr/references/verdict-format.md +31 -0
- package/content/skills/mugiwara-workflow/SKILL.md +5 -1
- package/dist/mugiwara.js +40 -21
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/references/wave-banners.md +75 -0
- package/scripts/gate-selftest.ts +31 -0
- package/scripts/lane.sh +1 -1
- package/scripts/lib/patterns.sh +8 -2
- package/src/targets/opencode.ts +34 -6
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "The Straw Hat crew for AI agents",
|
|
8
|
-
"version": "0.6.
|
|
8
|
+
"version": "0.6.4"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "mugiwara",
|
|
13
13
|
"description": "The Straw Hat crew of AI agents and skills: brainstorm, plan, execute, checkpoint, quality, gates, review, security, healing.",
|
|
14
|
-
"version": "0.6.
|
|
14
|
+
"version": "0.6.4",
|
|
15
15
|
"source": "./"
|
|
16
16
|
}
|
|
17
17
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mugiwara",
|
|
3
3
|
"displayName": "Mugiwara",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.4",
|
|
5
5
|
"description": "The Straw Hat crew of AI agents and skills: brainstorm, plan, execute, checkpoint, quality, gates, review, security, healing.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ionivetech"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "mugiwara",
|
|
3
3
|
"displayName": "Mugiwara",
|
|
4
4
|
"description": "The Straw Hat crew of AI agents and skills: brainstorm, plan, execute, checkpoint, quality, gates, review, security, healing.",
|
|
5
|
-
"version": "0.6.
|
|
5
|
+
"version": "0.6.4",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ionivetech"
|
|
8
8
|
},
|
package/.kimi-plugin/plugin.json
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
// single export because OpenCode's legacy loader calls every exported function
|
|
7
7
|
// as a plugin (same constraint ponytail documents).
|
|
8
8
|
//
|
|
9
|
+
// Crew COLORS come from references/wave-banners.md (single source, shared
|
|
10
|
+
// references dir); the CREW map below is the cold-path
|
|
11
|
+
// fallback only. Temperature/steps stay here (runtime tuning, not banner
|
|
12
|
+
// material).
|
|
13
|
+
//
|
|
9
14
|
// Install: add to opencode.json
|
|
10
15
|
// { "plugin": ["@ionivetech/mugiwara"] }
|
|
11
16
|
// or from the git repo:
|
|
@@ -22,26 +27,47 @@ const contentDir = join(__dirname, '..', '..', 'content');
|
|
|
22
27
|
const skillsDir = join(contentDir, 'skills');
|
|
23
28
|
const agentsDir = join(contentDir, 'agents');
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
"Mugiwara crew available. The workflow auto-activates for non-trivial requests — no need to call `/using-mugiwara` at session start (it is an optional router). Run the crew pipeline inline in the main conversation: embody ONE crew role at a time using its skill, wait for its report, then move to the next. Never Task-dispatch a crew member — the crew runs in the main thread; subagents only for [PARALLEL] task batches, concurrent review/security, and independent re-run checks. Progress shows as checkpoint reports at wave/stage boundaries, pausing on failure or risk. Switch mode with `/mugiwara` (guided|semi|auto). See skills/mugiwara-workflow.";
|
|
27
|
-
|
|
30
|
+
// Colors are fallbacks — the wave-banners table is the source of truth.
|
|
28
31
|
const CREW = {
|
|
29
32
|
'luffy-orchestrator': { color: '#ef4444', temperature: 0.2, steps: 30 },
|
|
30
|
-
'usopp-brainstorm': { color: '#
|
|
33
|
+
'usopp-brainstorm': { color: '#b45309', temperature: 0.6, steps: 30 },
|
|
31
34
|
'nami-planner': { color: '#f97316', temperature: 0.2, steps: 30 },
|
|
32
35
|
'zoro-execution': { color: '#22c55e', temperature: 0.1, steps: 50 },
|
|
33
|
-
'chopper-checkpoint': { color: '#
|
|
34
|
-
'sanji-quality': { color: '#
|
|
36
|
+
'chopper-checkpoint': { color: '#60a5fa', temperature: 0.1, steps: 30 },
|
|
37
|
+
'sanji-quality': { color: '#facc15', temperature: 0.1, steps: 40 },
|
|
35
38
|
'franky-gates': { color: '#06b6d4', temperature: 0.1, steps: 40 },
|
|
36
39
|
'robin-reviewer': { color: '#8b5cf6', temperature: 0.2, steps: 30 },
|
|
37
40
|
'jinbe-security': { color: '#6366f1', temperature: 0.2, steps: 30 },
|
|
38
|
-
'brook-healing': { color: '#
|
|
41
|
+
'brook-healing': { color: '#2dd4bf', temperature: 0.1, steps: 30 },
|
|
39
42
|
'skeptic-verifier': { color: '#64748b', temperature: 0.1, steps: 30 },
|
|
40
43
|
'eval-runner': { color: '#14b8a6', temperature: 0.2, steps: 30 },
|
|
41
44
|
'resume-coordinator': { color: '#d97706', temperature: 0.2, steps: 30 },
|
|
42
45
|
'memory-keeper': { color: '#d946ef', temperature: 0.2, steps: 30 },
|
|
43
46
|
};
|
|
44
47
|
|
|
48
|
+
// Read the crew color table (single source of truth, shared references/).
|
|
49
|
+
// Returns {} on any failure — callers fall back to the CREW map. The regex
|
|
50
|
+
// anchors the exact table shape: | agent-id | role | hex | ansi-256 | emoji |
|
|
51
|
+
function readBannerColors() {
|
|
52
|
+
try {
|
|
53
|
+
const path = join(__dirname, '..', '..', 'references', 'wave-banners.md');
|
|
54
|
+
if (!existsSync(path)) return {};
|
|
55
|
+
const text = readFileSync(path, 'utf8');
|
|
56
|
+
// null-prototype: agent ids are trusted repo content, but a future
|
|
57
|
+
// `__proto__` id must never write the object's prototype
|
|
58
|
+
const colors = Object.create(null);
|
|
59
|
+
for (const m of text.matchAll(/^\| ([\w-]+) \| [^|]+ \| (#[0-9a-f]{6}) \| (\d+) \| (\S+) \|\r?$/gm)) {
|
|
60
|
+
colors[m[1]] = m[2];
|
|
61
|
+
}
|
|
62
|
+
return colors;
|
|
63
|
+
} catch {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ANNOUNCE =
|
|
69
|
+
"Mugiwara crew available. The workflow auto-activates for non-trivial requests — no need to call `/using-mugiwara` at session start (it is an optional router). Run the crew pipeline inline in the main conversation: embody ONE crew role at a time using its skill, wait for its report, then move to the next. Never Task-dispatch a crew member — the crew runs in the main thread; subagents only for [PARALLEL] task batches, concurrent review/security, and independent re-run checks. Progress shows as checkpoint reports at wave/stage boundaries, pausing on failure or risk. Switch mode with `/mugiwara` (guided|semi|auto). See skills/mugiwara-workflow.";
|
|
70
|
+
|
|
45
71
|
function parseFrontmatter(text) {
|
|
46
72
|
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
47
73
|
if (!m) throw new Error('Missing frontmatter fence (---)');
|
|
@@ -68,6 +94,7 @@ function permissionFromScope(scope) {
|
|
|
68
94
|
|
|
69
95
|
function readAgents(stepsEnabled = true) {
|
|
70
96
|
const agents = {};
|
|
97
|
+
const bannerColors = readBannerColors();
|
|
71
98
|
let files;
|
|
72
99
|
try {
|
|
73
100
|
files = readdirSync(agentsDir).filter((f) => f.endsWith('.md'));
|
|
@@ -99,6 +126,11 @@ function readAgents(stepsEnabled = true) {
|
|
|
99
126
|
agents[name] = { ...agents[name], ...rest };
|
|
100
127
|
if (stepsEnabled) agents[name] = { ...agents[name], steps };
|
|
101
128
|
}
|
|
129
|
+
// color comes from the wave-banners table (single source); CREW fallback
|
|
130
|
+
// covers agents the table does not list (and vice versa)
|
|
131
|
+
if (bannerColors[name] || CREW[name]) {
|
|
132
|
+
agents[name] = { ...agents[name], color: bannerColors[name] ?? CREW[name].color };
|
|
133
|
+
}
|
|
102
134
|
const perm = permissionFromScope(parsed.data['write-scope']);
|
|
103
135
|
if (perm && agents[name].mode === 'subagent') agents[name].permission = perm;
|
|
104
136
|
}
|
package/README.md
CHANGED
|
@@ -245,6 +245,7 @@ Switch mode any time: `/mugiwara guided | semi | auto`. Or edit `.mugiwara/confi
|
|
|
245
245
|
| `mode` | guided | guided / semi / auto |
|
|
246
246
|
| `branch` | `feature/{type}-{issue}-{slug}` | Branch naming |
|
|
247
247
|
| `commit` | conventional | conventional / gitmoji / plain |
|
|
248
|
+
| `auto_commit` | on | on / off — off disables commit+push in guided/semi |
|
|
248
249
|
| `coverage_new` | 90 | Coverage threshold for new files (%) |
|
|
249
250
|
| `coverage_modified` | 80 | Coverage threshold for modified files (%) |
|
|
250
251
|
| `review_depth` | full | full / standard / quick — Robin's review depth |
|
|
@@ -31,7 +31,7 @@ Wave 3 of `mugiwara-workflow`, with the plan doc path.
|
|
|
31
31
|
## Rules
|
|
32
32
|
|
|
33
33
|
1. Follow `mugiwara-execution` exactly (ingestion, dispatch rules, per-task discipline).
|
|
34
|
-
2. Before touching code, follow the mode's branch/commit rule (per mode config): `guided` ASKS THE USER (auto branch for the mission or current branch; auto commit per task or user-controlled checkpoints); `semi
|
|
34
|
+
2. Before touching code, follow the mode's branch/commit rule (per mode config): `guided` ASKS THE USER (auto branch for the mission or current branch; auto commit per task or user-controlled checkpoints); `semi` auto-creates the mission branch per the config `branch` key and auto-commits per task in the config `commit` style — no ask; `auto` same but commits ALWAYS. `auto_commit=off` (config, default on): guided and semi leave all changes uncommitted for the user — no commits, no push; auto mode ignores it. Record the mode + branch + commit style + auto_commit in the decision log (`.mugiwara/logs/`) and todos. State-mutating consent still applies in every mode.
|
|
35
35
|
3. Sequential tasks and chains run INLINE in the main thread — no subagent round-trips for ordered work. Only `[PARALLEL]` task batches dispatch WORKER subagents (one task per worker); never another crew member; return your execution report inline to the conversation, which routes to Chopper.
|
|
36
36
|
4. Every task done = evidence attached (command output / file inspection); run acceptance criteria, do not assert them.
|
|
37
37
|
5. Apply `mugiwara-git` as you go: atomic commits per LOGICAL task (when auto-commit is on) — a task is a meaningful unit of work, not a micro-step; adjacent trivial changes fold into the neighboring task's commit. Save-points before risky work, commit style matched to the repo history.
|
|
@@ -14,8 +14,10 @@ Execute the plan exactly. No silent reordering, no skipping steps, no "close eno
|
|
|
14
14
|
|
|
15
15
|
## Ask before working
|
|
16
16
|
|
|
17
|
-
- `guided`: before touching any code, ASK THE USER — auto branch (dedicated mission branch, recommended, keeps `main` clean) or work on the current branch; auto commit per task or commit at user-controlled checkpoints.
|
|
18
|
-
- `semi
|
|
17
|
+
- `guided`: before touching any code, ASK THE USER — auto branch (dedicated mission branch, recommended, keeps `main` clean) or work on the current branch; auto commit per task or commit at user-controlled checkpoints. With `auto_commit=off`: the branch ask stays, the commit question is skipped — changes stay uncommitted.
|
|
18
|
+
- `semi`: auto-create the mission branch per the config `branch` key; auto-commit per task in the config `commit` style ONLY when `auto_commit=on` (default). Off → leave every task's changes uncommitted; the user commits manually.
|
|
19
|
+
- `auto`: auto-create the branch and auto-commit per task ALWAYS — `auto_commit=off` has no effect in auto mode.
|
|
20
|
+
Record mode + branch + commit style + `auto_commit` in the decision log (`.mugiwara/logs/YYYY-MM-DD-<mission>.md`) and in `.mugiwara/results/<mission>/todos.md` — every mode.
|
|
19
21
|
|
|
20
22
|
Code to the installed version's docs, not memory: `_shared/references/source-grounding.md`.
|
|
21
23
|
|
|
@@ -28,9 +30,10 @@ Before touching code:
|
|
|
28
30
|
1. Create `.mugiwara/results/<mission>/todos.md` — one checkbox per task, derived from the plan.
|
|
29
31
|
2. Check each box off only when the task completes, WITH its evidence link (`[path](relative/path)`, clickable).
|
|
30
32
|
3. Re-check the whole list after each task and after each batch; unmarked boxes mean the mission is not done.
|
|
31
|
-
4. Mirror
|
|
32
|
-
opencode; `TaskUpdate` on Claude Code; none on tier 2/3 — plan doc only)
|
|
33
|
-
|
|
33
|
+
4. Mirror EVERY transition into the host's native todo tool (`todowrite` on
|
|
34
|
+
opencode; `TaskUpdate` on Claude Code; none on tier 2/3 — plan doc only) in
|
|
35
|
+
the SAME response the task's evidence lands — one transition per call,
|
|
36
|
+
never batched at wave end. Per-host table: `docs/reference/harness-matrix.md`.
|
|
34
37
|
|
|
35
38
|
## Wave execution
|
|
36
39
|
|
|
@@ -63,12 +66,10 @@ Real worker dispatch exists only where the harness has subagents — tier 1
|
|
|
63
66
|
(Claude Code, opencode) plus Copilot. Gate the context-pressure trigger on
|
|
64
67
|
that capability: if the harness cannot dispatch, do not promise fresh workers.
|
|
65
68
|
|
|
66
|
-
Where workers are unavailable and context pressure crosses the threshold
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
`⚠ context 62% — no worker dispatch on this harness; savepoint written,
|
|
71
|
-
resume in a fresh session (plan order unchanged).`
|
|
69
|
+
Where workers are unavailable and context pressure crosses the threshold:
|
|
70
|
+
write a savepoint, run the checkpoint, and suggest a fresh session via
|
|
71
|
+
`resume`. Announce: `⚠ context 62% — no worker dispatch on this harness;
|
|
72
|
+
savepoint written, resume in a fresh session (plan order unchanged).`
|
|
72
73
|
|
|
73
74
|
## Batch resume
|
|
74
75
|
|
|
@@ -35,7 +35,7 @@ In `auto` mode the AI decides everything; any requirement that stays unclear aft
|
|
|
35
35
|
|
|
36
36
|
## Mode read (Wave 0)
|
|
37
37
|
|
|
38
|
-
Read the runtime mode via mode config at Wave 0: `.mugiwara/config` (project) then `~/.mugiwara/config` (global); a key missing from both = `guided`. Record the active mode in the decision log. Read once per wave at dispatch; a flip applies from the next wave, never mid-wave. Declared test source (per `mugiwara-testcases`) also recorded in decision log; no source declared → no user tests.
|
|
38
|
+
Read the runtime mode via mode config at Wave 0: `.mugiwara/config` (project) then `~/.mugiwara/config` (global); a key missing from both = `guided`. Record the active mode AND `auto_commit` (default on) in the decision log. Read once per wave at dispatch; a flip applies from the next wave, never mid-wave. Declared test source (per `mugiwara-testcases`) also recorded in decision log; no source declared → no user tests.
|
|
39
39
|
|
|
40
40
|
## Request classifier (Wave 0) — 8 classes
|
|
41
41
|
|
|
@@ -80,7 +80,10 @@ Shortcuts ("skip X", "just do it") reroute work inside the pipeline — never ou
|
|
|
80
80
|
|
|
81
81
|
## Wave transitions (visibility)
|
|
82
82
|
|
|
83
|
-
Banner
|
|
83
|
+
Banner in the owning agent's color opens every wave — terminal equals line
|
|
84
|
+
`===== WAVE 3 — ZORO (EXECUTION) =====`, markdown UI emoji heading
|
|
85
|
+
`## ⚔️ WAVE 3 — ZORO (EXECUTION)`. Spec + colors: `_shared/references/wave-banners.md`.
|
|
86
|
+
A skip is recorded, never silent.
|
|
84
87
|
|
|
85
88
|
## Work splitting
|
|
86
89
|
|
|
@@ -96,7 +99,7 @@ Recognize the in-session phrase `mugiwara mode <guided|semi|auto>`: write the pr
|
|
|
96
99
|
|
|
97
100
|
## Closure (Wave 9)
|
|
98
101
|
|
|
99
|
-
Gate — every task's acceptance criteria verified, every gate passed, findings resolved or deferred with an owner, blocker ledger reviewed. Step results `results/<mission>/01..05` are evidence — kept, never deleted; only consumed cross-artifacts (`logs/`, `spec/`, `review/`, `issues/`) are removed. Run `scripts/savepoint.sh <mission>` to write final state, then `scripts/mission-report.sh <mission>` to generate the aggregate mission report at `.mugiwara/reports/YYYY-MM-DD-<mission>.md`. Write the closure summary to `.mugiwara/results/<mission>/06-closure.md`. The plan doc stays untouched. Full detail: `references/closure.md`.
|
|
102
|
+
Gate — every task's acceptance criteria verified, every gate passed, findings resolved or deferred with an owner, blocker ledger reviewed. Step results `results/<mission>/01..05` are evidence — kept, never deleted; only consumed cross-artifacts (`logs/`, `spec/`, `review/`, `issues/`) are removed. Run `scripts/savepoint.sh <mission>` to write final state, then `scripts/mission-report.sh <mission>` to generate the aggregate mission report at `.mugiwara/reports/YYYY-MM-DD-<mission>.md`. Write the closure summary to `.mugiwara/results/<mission>/06-closure.md`. The plan doc stays untouched. Full detail: `references/closure.md`. With `auto_commit=off` (guided/semi): skip the save-point commit and push — hand the uncommitted tree + verdict to the user; auto always pushes.
|
|
100
103
|
|
|
101
104
|
## Spirit vs letter
|
|
102
105
|
|
|
@@ -27,15 +27,26 @@ After every wave AND at the end of each execution batch, verify:
|
|
|
27
27
|
never verbatim instructions.
|
|
28
28
|
7. **Host todo synced** — the main thread mirrors the plan doc's task list into the host's native todo mechanism
|
|
29
29
|
(opencode `todowrite`; Claude Code `TaskCreate`/`TaskUpdate`/`TaskList` — `TodoWrite` is deprecated since
|
|
30
|
-
v2.1.142; tier 2/3 hosts have no native tool — plan doc only)
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
v2.1.142; tier 2/3 hosts have no native tool — plan doc only). Seed it at Wave 2 (tasks + wave list 4-9);
|
|
31
|
+
update it in the SAME response each task's evidence lands — one transition per call, never deferred to
|
|
32
|
+
batch or wave end; flip each wave to in_progress when its banner opens. The host todo is a mirror; the plan
|
|
33
|
+
doc stays the source of truth. A task done in the plan doc but not yet in the host tool is incomplete.
|
|
34
|
+
Per-host table: `docs/reference/harness-matrix.md`.
|
|
33
35
|
|
|
34
36
|
By mode (per mode config): `guided` checks in with the user as today; `semi`/`auto` write the check-in verdicts to the decision log without pausing the pipeline.
|
|
35
37
|
|
|
36
38
|
## Wave transitions (visibility)
|
|
37
39
|
|
|
38
|
-
Every wave opens with a
|
|
40
|
+
Every wave opens with a colored banner in the owning agent's color and closes
|
|
41
|
+
with the handoff line `→ Wave N+1 — <crew>` (Wave 9: `→ closure`). Terminal:
|
|
42
|
+
equals line `===== WAVE 3 — ZORO (EXECUTION) =====` wrapped in ANSI truecolor
|
|
43
|
+
`\x1b[38;2;R;G;Bm...\x1b[0m` (256 fallback `38;5;N`); markdown UIs: emoji
|
|
44
|
+
heading `## ⚔️ WAVE 3 — ZORO (EXECUTION)`, no ANSI. The literal `WAVE N —`
|
|
45
|
+
text must stay exact (savepoint's heal counter greps `wave 8`). Colors, emoji,
|
|
46
|
+
and the full spec: `_shared/references/wave-banners.md`. No wave starts without its banner. A wave intentionally
|
|
47
|
+
omitted is never silent — record wave, owner, and reason in the decision log
|
|
48
|
+
before moving on. The user must always see which crew runs now and who takes
|
|
49
|
+
over next.
|
|
39
50
|
|
|
40
51
|
## On drift
|
|
41
52
|
|
|
@@ -22,6 +22,15 @@ Save-point commit → push branch with plain `git push -u origin <branch>` → w
|
|
|
22
22
|
verdict to user. Crew never creates PR, never merges, never deploys. On push
|
|
23
23
|
failure, fall back to local closure report.
|
|
24
24
|
|
|
25
|
+
**`auto_commit=off` (guided/semi only).** No save-point commit, no push — the
|
|
26
|
+
working tree stays uncommitted. Write the verdict file exactly as usual, then
|
|
27
|
+
hand the user: the branch name, the exact commands to commit and push
|
|
28
|
+
(`git status` first, then `git add` of the mission's files only — never bare
|
|
29
|
+
`git add -A`, which would stage unrelated or secret files — then
|
|
30
|
+
`git commit -m "<suggested message>" && git push -u origin <branch>`), and the
|
|
31
|
+
verdict pointer. In `auto` mode `auto_commit` is ignored — the terminal step
|
|
32
|
+
runs unchanged.
|
|
33
|
+
|
|
25
34
|
When this mission is a sub-mission of a team initiative, after closure run
|
|
26
35
|
`bun scripts/initiative.ts set-status <initiative-plan> --id <sub-id> --status done`.
|
|
27
36
|
When all sub-missions show `[x]`, present initiative-level closure summary.
|
|
@@ -14,32 +14,25 @@ Mugiwara's evidence lands where the team reviews. At terminal, push the mission
|
|
|
14
14
|
|
|
15
15
|
## Verdict file
|
|
16
16
|
|
|
17
|
-
Write `.mugiwara/results/<mission>/07-pr-verdict.md
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
2. Per-wave evidence table — wave, task, status, evidence link (`[path](relative/path)`).
|
|
23
|
-
3. Gates — quality, gates, review + security dispositions, each verdict with evidence.
|
|
24
|
-
4. Review & security — findings with dispositions.
|
|
25
|
-
5. User tests — when declared, the ATDD oracle result (per `mugiwara-testcases`), from real runs, never asserted.
|
|
26
|
-
6. Closure report link — `[06-closure.md](.mugiwara/results/<mission>/06-closure.md)`.
|
|
27
|
-
7. Verdict — PASS / FAIL with the single blocking reason, if any.
|
|
17
|
+
Write `.mugiwara/results/<mission>/07-pr-verdict.md` — ONE document that IS
|
|
18
|
+
the ready PR material (no separate report + PR-body copy). Exact order —
|
|
19
|
+
Title → Summary (key-point bullets) → What changed (compact file inventory
|
|
20
|
+
paragraph) → Per-wave evidence → Tests → Checks → Verdict. Full spec:
|
|
21
|
+
`references/verdict-format.md`.
|
|
28
22
|
|
|
29
23
|
## PR summary
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- Validate every interpolated value against the safe charset and quote it.
|
|
25
|
+
The verdict file IS the PR summary. No second block: the user pastes the file
|
|
26
|
+
— title line into the PR title, the rest into the body. Order mirrors the
|
|
27
|
+
verdict file (title → summary → what changed → per-wave evidence → tests →
|
|
28
|
+
checks → verdict). Validate every interpolated value against the safe charset
|
|
29
|
+
and quote it.
|
|
37
30
|
|
|
38
31
|
The summary is material, never posted — the crew stops at push.
|
|
39
32
|
|
|
40
33
|
## Handoff rule
|
|
41
34
|
|
|
42
|
-
Push the branch + write the verdict file at terminal, after every wave passes (never a draft state — the user opens the PR when they choose). The verdict is delivered as a file, not posted; the user pastes it into their PR. Never per-wave (reviewer noise).
|
|
35
|
+
Push the branch + write the verdict file at terminal, after every wave passes (never a draft state — the user opens the PR when they choose). The verdict is delivered as a file, not posted; the user pastes it into their PR. Never per-wave (reviewer noise). With `auto_commit=off` (guided/semi only): nothing to push — write the verdict file, hand the UNCOMMITTED working tree to the user with the exact commit + push commands; `auto` mode always pushes.
|
|
43
36
|
|
|
44
37
|
## Push adapter (plain git, no gh)
|
|
45
38
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Verdict file format
|
|
2
|
+
|
|
3
|
+
`.mugiwara/results/<mission>/07-pr-verdict.md` — ONE document that IS the
|
|
4
|
+
ready PR material (the user copies the title line and the body as-is). No
|
|
5
|
+
separate report section plus a PR-body copy: one flow, in this exact order:
|
|
6
|
+
|
|
7
|
+
1. **Title** — `# {type}: {Title Case summary}` — mandatory Title case, e.g.
|
|
8
|
+
`# Feat: Add Evidence Links To Mugiwara Reports`.
|
|
9
|
+
2. **Summary** — goal, mode, waves, task count, branch/stacking note,
|
|
10
|
+
closure report link (`[06-closure.md](.mugiwara/results/<mission>/06-closure.md)`);
|
|
11
|
+
then the mission's key points as compact bullets (what each defect/feature
|
|
12
|
+
does — never a file list).
|
|
13
|
+
3. **What changed** — ONE compact paragraph, file inventory only: `<N> files:
|
|
14
|
+
<comma-separated paths>, <grouped counts>, docs (dir or file list), README.`
|
|
15
|
+
Feature detail lives in Summary, not here.
|
|
16
|
+
4. **Per-wave evidence** — wave, task, status, evidence link
|
|
17
|
+
(`[path](relative/path)`). Gates, review, security, and heal rows live here
|
|
18
|
+
with their dispositions.
|
|
19
|
+
5. **Tests** — captured test counts (never asserted); the ATDD oracle result
|
|
20
|
+
when user tests were declared (per `mugiwara-testcases`).
|
|
21
|
+
6. **Checks** — one `- [x]` checkbox bullet per gate: typecheck, tests
|
|
22
|
+
(`N/N`), build, content/manifest/doc-integrity, lane-base, npm pack,
|
|
23
|
+
evals + retrieval, verify-install (`N/N` pointers), gate-selftest
|
|
24
|
+
(`N/N` mutations prove red).
|
|
25
|
+
7. **Verdict** — PASS / FAIL with the single blocking reason, if any.
|
|
26
|
+
|
|
27
|
+
The file IS the PR summary. No second block: the user pastes the file — title
|
|
28
|
+
line into the PR title, the rest into the body. Order mirrors the verdict file
|
|
29
|
+
(title → summary → what changed → per-wave evidence → tests → checks →
|
|
30
|
+
verdict). Validate every interpolated value against the safe charset and quote
|
|
31
|
+
it.
|
|
@@ -46,7 +46,10 @@ Waves are phases, not files. The plan doc defines them. The harness runs inline.
|
|
|
46
46
|
|
|
47
47
|
**One role at a time.** The main thread embodies ONE crew role per response — completes that role's report, then moves to the next. Never role-bleeds two personas into one response; never starts the next role before the current one returns its output.
|
|
48
48
|
|
|
49
|
-
**Banners.** Every wave opens with a
|
|
49
|
+
**Banners.** Every wave opens with a banner in the owning agent's color and
|
|
50
|
+
closes with a handoff line — terminal ANSI equals line, markdown-UIs emoji
|
|
51
|
+
heading; handoff `→ Wave 4 — Chopper (Checkpoint)`. Keep literal `WAVE N —`
|
|
52
|
+
(savepoint's heal counter greps it). Spec + colors: `_shared/references/wave-banners.md`.
|
|
50
53
|
|
|
51
54
|
**Subagents only for parallelism.** `[PARALLEL]` task batches, parallel review, parallel heal workers. Crew members never dispatch crew members.
|
|
52
55
|
|
|
@@ -103,6 +106,7 @@ Archive, never delete: run `mugiwara archive <mission>` — folds `logs/`, `spec
|
|
|
103
106
|
5. Plan doc is source of truth from Wave 2.
|
|
104
107
|
6. Resume via `resume-coordinator` before any wave — never restart.
|
|
105
108
|
7. Push branch + hand verdict to user; crew never merges or deploys.
|
|
109
|
+
8. Host todo mirrors the plan doc every task + wave — same response as evidence.
|
|
106
110
|
|
|
107
111
|
## Iron Law
|
|
108
112
|
|
package/dist/mugiwara.js
CHANGED
|
@@ -188,27 +188,43 @@ var target = {
|
|
|
188
188
|
};
|
|
189
189
|
|
|
190
190
|
// src/targets/opencode.ts
|
|
191
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2, copyFileSync as copyFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
191
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, copyFileSync as copyFileSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
192
192
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
193
193
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
194
194
|
var here2 = dirname2(fileURLToPath2(import.meta.url));
|
|
195
195
|
var COMMANDS_SRC2 = join2(here2, "..", "..", ".opencode", "commands");
|
|
196
|
+
var BANNER_TABLE = join2(here2, "..", "..", "references", "wave-banners.md");
|
|
196
197
|
var CREW = {
|
|
197
198
|
"luffy-orchestrator": { color: "#ef4444", temperature: 0.2, steps: 15 },
|
|
198
|
-
"usopp-brainstorm": { color: "#
|
|
199
|
+
"usopp-brainstorm": { color: "#b45309", temperature: 0.6, steps: 15 },
|
|
199
200
|
"nami-planner": { color: "#f97316", temperature: 0.2, steps: 15 },
|
|
200
201
|
"zoro-execution": { color: "#22c55e", temperature: 0.1, steps: 30 },
|
|
201
|
-
"chopper-checkpoint": { color: "#
|
|
202
|
-
"sanji-quality": { color: "#
|
|
202
|
+
"chopper-checkpoint": { color: "#60a5fa", temperature: 0.1, steps: 15 },
|
|
203
|
+
"sanji-quality": { color: "#facc15", temperature: 0.1, steps: 10 },
|
|
203
204
|
"franky-gates": { color: "#06b6d4", temperature: 0.1, steps: 10 },
|
|
204
205
|
"robin-reviewer": { color: "#8b5cf6", temperature: 0.2, steps: 15 },
|
|
205
206
|
"jinbe-security": { color: "#6366f1", temperature: 0.2, steps: 15 },
|
|
206
|
-
"brook-healing": { color: "#
|
|
207
|
+
"brook-healing": { color: "#2dd4bf", temperature: 0.1, steps: 20 },
|
|
207
208
|
"skeptic-verifier": { color: "#64748b", temperature: 0.1, steps: 12 },
|
|
208
209
|
"eval-runner": { color: "#14b8a6", temperature: 0.2, steps: 15 },
|
|
209
210
|
"resume-coordinator": { color: "#d97706", temperature: 0.2, steps: 10 },
|
|
210
211
|
"memory-keeper": { color: "#d946ef", temperature: 0.2, steps: 8 }
|
|
211
212
|
};
|
|
213
|
+
function readBannerColors() {
|
|
214
|
+
try {
|
|
215
|
+
if (!existsSync2(BANNER_TABLE))
|
|
216
|
+
return {};
|
|
217
|
+
const text = readFileSync2(BANNER_TABLE, "utf8");
|
|
218
|
+
const colors = Object.create(null);
|
|
219
|
+
for (const m of text.matchAll(/^\| ([\w-]+) \| [^|]+ \| (#[0-9a-f]{6}) \| (\d+) \| (\S+) \|\r?$/gm)) {
|
|
220
|
+
colors[m[1]] = m[2];
|
|
221
|
+
}
|
|
222
|
+
return colors;
|
|
223
|
+
} catch {
|
|
224
|
+
return {};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
var BANNER_COLORS = readBannerColors();
|
|
212
228
|
function permissionFromScope(scope) {
|
|
213
229
|
if (scope === "source")
|
|
214
230
|
return { edit: "allow" };
|
|
@@ -219,8 +235,11 @@ function permissionFromScope(scope) {
|
|
|
219
235
|
function agentFrontmatter(name, description, internal, writeScope) {
|
|
220
236
|
const crew = CREW[name];
|
|
221
237
|
const lines = [`description: ${description}`, `mode: all`];
|
|
238
|
+
const color = BANNER_COLORS[name] ?? crew?.color;
|
|
239
|
+
if (color)
|
|
240
|
+
lines.push(`color: '${color}'`);
|
|
222
241
|
if (crew) {
|
|
223
|
-
lines.push(`
|
|
242
|
+
lines.push(`temperature: ${crew.temperature}`, `steps: ${crew.steps}`);
|
|
224
243
|
const perm = internal ? permissionFromScope(writeScope) : undefined;
|
|
225
244
|
if (perm) {
|
|
226
245
|
lines.push("permission:");
|
|
@@ -477,21 +496,21 @@ var targets = { claude: target, opencode: target2, copilot: target3, gemini: tar
|
|
|
477
496
|
var TARGET_IDS = Object.keys(targets);
|
|
478
497
|
|
|
479
498
|
// src/installer.ts
|
|
480
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as
|
|
499
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync2, copyFileSync as copyFileSync3, rmSync, lstatSync } from "node:fs";
|
|
481
500
|
import { dirname as dirname3, join as join5 } from "node:path";
|
|
482
501
|
import { homedir } from "node:os";
|
|
483
502
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
484
503
|
var CONTENT_DIR = join5(dirname3(fileURLToPath3(import.meta.url)), "..", "content");
|
|
485
|
-
var pkg = JSON.parse(
|
|
504
|
+
var pkg = JSON.parse(readFileSync3(join5(dirname3(fileURLToPath3(import.meta.url)), "..", "package.json"), "utf8"));
|
|
486
505
|
var VERSION = pkg.version;
|
|
487
506
|
function collectContent() {
|
|
488
507
|
const skillNames = readdirSync3(join5(CONTENT_DIR, "skills"), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
489
508
|
const skills = skillNames.map((name) => {
|
|
490
|
-
const { data, body } = parseFrontmatter(
|
|
509
|
+
const { data, body } = parseFrontmatter(readFileSync3(join5(CONTENT_DIR, "skills", name, "SKILL.md"), "utf8"));
|
|
491
510
|
return { name, data, body, refs: collectRefs(join5(CONTENT_DIR, "skills", name)) };
|
|
492
511
|
});
|
|
493
512
|
const agents = readdirSync3(join5(CONTENT_DIR, "agents")).filter((f) => f.endsWith(".md")).map((f) => {
|
|
494
|
-
const { data, body } = parseFrontmatter(
|
|
513
|
+
const { data, body } = parseFrontmatter(readFileSync3(join5(CONTENT_DIR, "agents", f), "utf8"));
|
|
495
514
|
return { name: f.replace(/\.md$/, ""), data, body, refs: [], internal: data.internal === "true" };
|
|
496
515
|
});
|
|
497
516
|
const sharedRefsDir = join5(dirname3(CONTENT_DIR), "references");
|
|
@@ -500,7 +519,7 @@ function collectContent() {
|
|
|
500
519
|
for (const f of readdirSync3(sharedRefsDir)) {
|
|
501
520
|
if (!f.endsWith(".md"))
|
|
502
521
|
continue;
|
|
503
|
-
sharedRefs.push({ relPath: f, text:
|
|
522
|
+
sharedRefs.push({ relPath: f, text: readFileSync3(join5(sharedRefsDir, f), "utf8") });
|
|
504
523
|
}
|
|
505
524
|
}
|
|
506
525
|
return { skills, agents, sharedRefs };
|
|
@@ -509,7 +528,7 @@ function collectRefs(skillDir) {
|
|
|
509
528
|
const refsDir = join5(skillDir, "references");
|
|
510
529
|
if (!existsSync4(refsDir))
|
|
511
530
|
return [];
|
|
512
|
-
return readdirSync3(refsDir, { recursive: true }).map((f) => String(f)).filter((f) => f.endsWith(".md")).map((rel) => ({ relPath: rel, text:
|
|
531
|
+
return readdirSync3(refsDir, { recursive: true }).map((f) => String(f)).filter((f) => f.endsWith(".md")).map((rel) => ({ relPath: rel, text: readFileSync3(join5(refsDir, rel), "utf8") }));
|
|
513
532
|
}
|
|
514
533
|
function installTo(target10, opts) {
|
|
515
534
|
const { scope, projectDir, dryRun = false, force = false } = opts;
|
|
@@ -520,7 +539,7 @@ function installTo(target10, opts) {
|
|
|
520
539
|
const result = { written: [], skipped: [], backedUp: [], notes: [] };
|
|
521
540
|
const writeOne = (absPath, text) => {
|
|
522
541
|
if (existsSync4(absPath)) {
|
|
523
|
-
if (
|
|
542
|
+
if (readFileSync3(absPath, "utf8") === text) {
|
|
524
543
|
result.skipped.push(absPath);
|
|
525
544
|
return;
|
|
526
545
|
}
|
|
@@ -641,7 +660,7 @@ function ensureProjectGitignore(projectDir, opts = {}) {
|
|
|
641
660
|
const { dryRun = false } = opts;
|
|
642
661
|
const path = join5(projectDir, ".gitignore");
|
|
643
662
|
assertNotSymlink(path);
|
|
644
|
-
let existing = existsSync4(path) ?
|
|
663
|
+
let existing = existsSync4(path) ? readFileSync3(path, "utf8") : "";
|
|
645
664
|
if (existing) {
|
|
646
665
|
if (existing.includes(GITIGNORE_BLOCK_START) && existing.includes(".mugiwara/state/")) {
|
|
647
666
|
return { appended: false, notes: [] };
|
|
@@ -649,7 +668,7 @@ function ensureProjectGitignore(projectDir, opts = {}) {
|
|
|
649
668
|
const hadOld = existing.includes(GITIGNORE_MARKER) || existing.includes(GITIGNORE_BLOCK_START);
|
|
650
669
|
if (hadOld) {
|
|
651
670
|
const clean = removeProjectGitignore(projectDir, { dryRun });
|
|
652
|
-
existing = clean.removed ? existsSync4(path) ?
|
|
671
|
+
existing = clean.removed ? existsSync4(path) ? readFileSync3(path, "utf8") : "" : existing;
|
|
653
672
|
}
|
|
654
673
|
}
|
|
655
674
|
const separator = existing.length && !existing.endsWith(`
|
|
@@ -666,7 +685,7 @@ function removeProjectGitignore(projectDir, { dryRun = false } = {}) {
|
|
|
666
685
|
assertNotSymlink(path);
|
|
667
686
|
if (!existsSync4(path))
|
|
668
687
|
return { removed: false, notes: [] };
|
|
669
|
-
const current =
|
|
688
|
+
const current = readFileSync3(path, "utf8");
|
|
670
689
|
const delimited = current.includes(GITIGNORE_BLOCK_START);
|
|
671
690
|
let cleaned;
|
|
672
691
|
if (delimited) {
|
|
@@ -709,13 +728,13 @@ function removeProjectGitignore(projectDir, { dryRun = false } = {}) {
|
|
|
709
728
|
}
|
|
710
729
|
|
|
711
730
|
// src/manifest.ts
|
|
712
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as
|
|
731
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
713
732
|
import { dirname as dirname4, join as join6 } from "node:path";
|
|
714
733
|
function manifestPath({ scope, projectDir, home }) {
|
|
715
734
|
return scope === "global" ? join6(home, ".mugiwara", "manifest.json") : join6(projectDir, ".mugiwara", "manifest.json");
|
|
716
735
|
}
|
|
717
736
|
function readManifest(file) {
|
|
718
|
-
return existsSync5(file) ? JSON.parse(
|
|
737
|
+
return existsSync5(file) ? JSON.parse(readFileSync4(file, "utf8")) : null;
|
|
719
738
|
}
|
|
720
739
|
function writeManifest(file, data) {
|
|
721
740
|
mkdirSync4(dirname4(file), { recursive: true });
|
|
@@ -724,7 +743,7 @@ function writeManifest(file, data) {
|
|
|
724
743
|
}
|
|
725
744
|
|
|
726
745
|
// src/mission.ts
|
|
727
|
-
import { existsSync as existsSync6, rmSync as rmSync2, readFileSync as
|
|
746
|
+
import { existsSync as existsSync6, rmSync as rmSync2, readFileSync as readFileSync5, readdirSync as readdirSync4, mkdirSync as mkdirSync5, appendFileSync } from "node:fs";
|
|
728
747
|
import { join as join7 } from "node:path";
|
|
729
748
|
function activeActor(projectDir) {
|
|
730
749
|
const stateDir = join7(projectDir, ".mugiwara", "state");
|
|
@@ -736,7 +755,7 @@ function activeActor(projectDir) {
|
|
|
736
755
|
const d = join7(stateDir, mission);
|
|
737
756
|
for (const f of readdirSync4(d).filter((f2) => f2.endsWith(".json"))) {
|
|
738
757
|
try {
|
|
739
|
-
const s = JSON.parse(
|
|
758
|
+
const s = JSON.parse(readFileSync5(join7(d, f), "utf8"));
|
|
740
759
|
const t = Date.parse(s.updated_at || "") || 0;
|
|
741
760
|
if (!latest || t > latest.updated)
|
|
742
761
|
latest = { actor: s.actor || null, updated: t };
|
|
@@ -881,7 +900,7 @@ function archiveMission(projectDir, mission, opts = {}) {
|
|
|
881
900
|
`;
|
|
882
901
|
if (!dryRun) {
|
|
883
902
|
mkdirSync5(join7(root, "reports"), { recursive: true });
|
|
884
|
-
const existing = existsSync6(indexFile) ?
|
|
903
|
+
const existing = existsSync6(indexFile) ? readFileSync5(indexFile, "utf8") : "";
|
|
885
904
|
if (!existing.split(/\r?\n/).some((l) => l.startsWith(`- ${mission} —`))) {
|
|
886
905
|
const header = existing ? "" : `# Mission index
|
|
887
906
|
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ionivetech/mugiwara",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "The Straw Hat crew of AI agents and skills: brainstorm, plan, execute, checkpoint, quality, gates, review, security, self-healing. Installs into Claude Code, opencode, Copilot, Gemini, Codex, Cursor, Kimi, pi, Windsurf, Cline, Kilo, Antigravity.",
|
|
5
5
|
"homepage": "https://github.com/ionivetech/mugiwara#readme",
|
|
6
6
|
"repository": {
|
package/plugin.json
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Wave Banners — formats + crew colors
|
|
2
|
+
|
|
3
|
+
The single source for wave-banner rendering and crew colors. The opencode
|
|
4
|
+
plugin and the opencode-target generator derive agent UI colors from the table
|
|
5
|
+
below — change a color HERE, never in code. This file is machine-parsed:
|
|
6
|
+
keep the table format exact (one row per agent, pipes, no extra columns).
|
|
7
|
+
|
|
8
|
+
## Terminal format (ANSI truecolor)
|
|
9
|
+
|
|
10
|
+
Every wave opens with the banner line in the owning agent's color, and closes
|
|
11
|
+
with a handoff line. Wrap the whole line in the ANSI truecolor of the agent:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
\x1b[38;2;R;G;Bm==================== WAVE 3 — ZORO (EXECUTION) ====================\x1b[0m
|
|
15
|
+
→ Wave 4 — Chopper (Checkpoint)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- RGB values come from the `hex` column below; R/G/B are the hex channels in
|
|
19
|
+
decimal (truecolor `38;2;R;G;B`). If the terminal lacks truecolor, use the
|
|
20
|
+
`ansi-256` index: `\x1b[38;5;Nm`.
|
|
21
|
+
- Wave 9's handoff: `→ closure`.
|
|
22
|
+
- The literal `WAVE N —` text must stay exact: `scripts/savepoint.sh` counts
|
|
23
|
+
heal cycles by grepping `wave 8` (case-insensitive) in the trace. A banner
|
|
24
|
+
that drops the literal silently resets the heal loop.
|
|
25
|
+
- Never convey the wave by color alone — the crew name and emoji always
|
|
26
|
+
accompany the color.
|
|
27
|
+
|
|
28
|
+
## UI format (markdown UIs)
|
|
29
|
+
|
|
30
|
+
Rich UIs (Claude Code UI, VSCode, Codex) strip or garble ANSI escapes. Emit
|
|
31
|
+
the emoji-heading form instead — no ANSI, no equals line:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
## ⚔️ WAVE 3 — ZORO (EXECUTION)
|
|
35
|
+
→ Wave 4 — Chopper (Checkpoint)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Same literal `WAVE N —` rule applies.
|
|
39
|
+
|
|
40
|
+
## Crew colors
|
|
41
|
+
|
|
42
|
+
| agent-id | role | hex | ansi-256 | emoji |
|
|
43
|
+
|----------|------|-----|----------|-------|
|
|
44
|
+
| luffy-orchestrator | Luffy | #ef4444 | 196 | 🏴☠️ |
|
|
45
|
+
| usopp-brainstorm | Usopp | #b45309 | 130 | 🎯 |
|
|
46
|
+
| nami-planner | Nami | #f97316 | 208 | 🧭 |
|
|
47
|
+
| zoro-execution | Zoro | #22c55e | 34 | ⚔️ |
|
|
48
|
+
| chopper-checkpoint | Chopper | #60a5fa | 75 | 🩺 |
|
|
49
|
+
| sanji-quality | Sanji | #facc15 | 220 | 🍳 |
|
|
50
|
+
| franky-gates | Franky | #06b6d4 | 45 | 🔧 |
|
|
51
|
+
| robin-reviewer | Robin | #8b5cf6 | 99 | 📚 |
|
|
52
|
+
| jinbe-security | Jinbe | #6366f1 | 63 | 🌊 |
|
|
53
|
+
| brook-healing | Brook | #2dd4bf | 43 | 🎻 |
|
|
54
|
+
| skeptic-verifier | Skeptic | #64748b | 245 | 🔍 |
|
|
55
|
+
| eval-runner | EvalRunner | #14b8a6 | 37 | 🧪 |
|
|
56
|
+
| resume-coordinator | Resume | #d97706 | 172 | 🔄 |
|
|
57
|
+
| memory-keeper | MemoryKeeper | #d946ef | 200 | 🧠 |
|
|
58
|
+
| onboarding-guide | Guide | #0ea5e9 | 75 | 🚀 |
|
|
59
|
+
|
|
60
|
+
Skeptic, EvalRunner, Resume, MemoryKeeper and Guide are internal/dispatch or
|
|
61
|
+
wizard agents — their banners appear only when a wave or worker names them.
|
|
62
|
+
|
|
63
|
+
## Rules
|
|
64
|
+
|
|
65
|
+
1. Banner before EVERY wave; handoff after it. No wave starts without its
|
|
66
|
+
banner (orchestration red flag).
|
|
67
|
+
2. The color comes from this table only — never invent a hex mid-mission.
|
|
68
|
+
3. Terminal variant when the session looks like a terminal; UI variant for
|
|
69
|
+
markdown-rendering UIs. When unsure, the UI variant is safe everywhere.
|
|
70
|
+
4. Only the crew table's colors and the two SGR forms above (truecolor,
|
|
71
|
+
256-index) may appear in a banner — never other escape families (OSC,
|
|
72
|
+
title, cursor, other SGR codes). The banner is a fixed template, not a
|
|
73
|
+
formatting playground.
|
|
74
|
+
5. Adding a crew member? Add the row here first; the plugin and target
|
|
75
|
+
generator pick it up automatically.
|
package/scripts/gate-selftest.ts
CHANGED
|
@@ -163,6 +163,37 @@ if (!existsSync(patternsFile)) {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
// --- D3b mutation: strip the NEW pattern categories → lane-integrity red ---
|
|
167
|
+
console.log('\nD3b — new category strip mutation');
|
|
168
|
+
if (!existsSync(patternsFile)) {
|
|
169
|
+
console.log(' ⚠ patterns.sh not found, skipping');
|
|
170
|
+
} else {
|
|
171
|
+
const original = readFileSync(patternsFile, 'utf8');
|
|
172
|
+
try {
|
|
173
|
+
// derive the broken list from the LIVE source, stripping the exact tokens
|
|
174
|
+
// of the v0.6.4 D3 families — a hard-coded baseline rots the moment a
|
|
175
|
+
// category is added (G3). New family tokens must be added here too.
|
|
176
|
+
const D3B_FAMILY_TOKENS = new Set([
|
|
177
|
+
// raw regex tokens as they appear in live SENSITIVE_PATS (backslashes included)
|
|
178
|
+
'oauth2?/', 'credential', 'sessions?/', 'tokens?/', 'rbac', 'permissions?/', 'acls?/', 'iam/',
|
|
179
|
+
'\\.p12$', '\\.key$', '\\.pem$', 'migrate/', 'Dockerfile', 'docker-compose', '\\.github/workflows/',
|
|
180
|
+
'webhooks?/', 'secret/', 'secrets?\\.ya?ml$', '\\.tfvars$', '\\.env$', '\\.env\\.',
|
|
181
|
+
]);
|
|
182
|
+
const live = original.match(/SENSITIVE_PATS="([^"]+)"/)?.[1] ?? '';
|
|
183
|
+
const broken = live.split('|').filter(t => !D3B_FAMILY_TOKENS.has(t)).join('|');
|
|
184
|
+
const brokenLine = `SENSITIVE_PATS="${broken}"`;
|
|
185
|
+
if (broken === live || !live) {
|
|
186
|
+
console.log(' ⚠ D3b: no D3 family tokens found in live SENSITIVE_PATS — skipping');
|
|
187
|
+
} else {
|
|
188
|
+
writeFileSync(patternsFile, original.replace(/SENSITIVE_PATS="[^"]*"/, brokenLine));
|
|
189
|
+
assert('missing new categories → lane-integrity fails', false, () => run('D3b', 'bun run test -- lane-integrity -t "sensitive-paths"'));
|
|
190
|
+
}
|
|
191
|
+
} finally {
|
|
192
|
+
writeFileSync(patternsFile, original);
|
|
193
|
+
assert('restored → lane-integrity passes', true, () => run('D3b', 'bun run test -- lane-integrity -t "sensitive-paths"'));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
166
197
|
// --- D4 mutation: zero LOC_TOKENS → lane-integrity red ---
|
|
167
198
|
console.log('\nD4 — churn token mutation');
|
|
168
199
|
if (!existsSync(savepointFile)) {
|
package/scripts/lane.sh
CHANGED
|
@@ -23,7 +23,7 @@ CHANGED=$(git diff --name-only "$BASE"..HEAD 2>/dev/null || git diff --name-only
|
|
|
23
23
|
FILE_COUNT=0
|
|
24
24
|
[ -n "$CHANGED" ] && FILE_COUNT=$(echo "$CHANGED" | wc -l | tr -d ' ')
|
|
25
25
|
|
|
26
|
-
SENSITIVE=$(echo "$CHANGED" | grep -E "$SENSITIVE_PATS" 2>/dev/null |
|
|
26
|
+
SENSITIVE=$(echo "$CHANGED" | grep -E "$SENSITIVE_PATS" 2>/dev/null | tr '\n' ',' | sed 's/,$//' || true)
|
|
27
27
|
HAS_SENSITIVE=0
|
|
28
28
|
[ -n "$SENSITIVE" ] && HAS_SENSITIVE=1
|
|
29
29
|
|
package/scripts/lib/patterns.sh
CHANGED
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
# Sensitive-path escalation patterns. A changed file matching any of these
|
|
6
6
|
# always escalates to Lane 3 (Full), regardless of file count. Plural forms
|
|
7
|
-
# included (payments/, migrations/)
|
|
7
|
+
# included (payments/, migrations/) and, since v0.6.4, the D3 categories:
|
|
8
|
+
# oauth, credential(s), session(s)/, token(s)/, rbac, permission(s), acl(s)/,
|
|
9
|
+
# iam/, cert keys (.pem/.key/.p12), migrate/, Dockerfile, docker-compose,
|
|
10
|
+
# .github/workflows/, webhooks?/, secret yaml, .tfvars, and .env variants
|
|
11
|
+
# (.env.local, .env.production — .env$ alone missed them, D3 follow-up).
|
|
12
|
+
# Dir-anchored (not bare): oauth2?/, permissions?/, tokens?/, sessions?/,
|
|
13
|
+
# acls?/ — bare forms over-matched docs (oauth-guide.md, permissionless.ts).
|
|
8
14
|
# Deliberately NOT matched: package.json (dependency churn is policy-as-code,
|
|
9
15
|
# not a sensitive lane trigger), authors/ (contains "auth" but never auth/).
|
|
10
|
-
SENSITIVE_PATS="auth/|payment/|payments/|billing/|crypto/|secrets/|\.env
|
|
16
|
+
SENSITIVE_PATS="auth/|oauth2?/|payment/|payments/|billing/|crypto/|secrets/|credential|sessions?/|tokens?/|rbac|permissions?/|acls?/|iam/|\.env$|\.env\.|config/.*key|\.p12$|\.key$|\.pem$|migration/|migrations/|migrate/|\.sql$|schema\.|\.prisma$|\.terraform|\.tf$|Dockerfile|docker-compose|\.github/workflows/|webhooks?/|secret/|secrets?\.ya?ml$|\.tfvars$"
|
|
11
17
|
|
|
12
18
|
# Product surface: paths that count toward file-based lane sizing. Changes
|
|
13
19
|
# outside this surface are docs/config/asset and never escalate to full on
|
package/src/targets/opencode.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/targets/opencode.ts
|
|
2
|
-
import { existsSync, readdirSync, copyFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { existsSync, readdirSync, copyFileSync, mkdirSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { stringifyFrontmatter, type FrontmatterData } from '../frontmatter.ts';
|
|
@@ -7,6 +7,7 @@ import type { Target } from '../installer.ts';
|
|
|
7
7
|
|
|
8
8
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const COMMANDS_SRC = join(here, '..', '..', '.opencode', 'commands');
|
|
10
|
+
const BANNER_TABLE = join(here, '..', '..', 'references', 'wave-banners.md');
|
|
10
11
|
|
|
11
12
|
type CrewConfig = {
|
|
12
13
|
color: string;
|
|
@@ -15,23 +16,46 @@ type CrewConfig = {
|
|
|
15
16
|
permission?: Record<string, string>;
|
|
16
17
|
};
|
|
17
18
|
|
|
19
|
+
// Colors are fallbacks — the wave-banners table is the source of truth.
|
|
20
|
+
// Temperature/steps stay here (runtime tuning, not banner material).
|
|
18
21
|
const CREW: Record<string, CrewConfig> = {
|
|
19
22
|
'luffy-orchestrator': { color: '#ef4444', temperature: 0.2, steps: 15 },
|
|
20
|
-
'usopp-brainstorm': { color: '#
|
|
23
|
+
'usopp-brainstorm': { color: '#b45309', temperature: 0.6, steps: 15 },
|
|
21
24
|
'nami-planner': { color: '#f97316', temperature: 0.2, steps: 15 },
|
|
22
25
|
'zoro-execution': { color: '#22c55e', temperature: 0.1, steps: 30 },
|
|
23
|
-
'chopper-checkpoint': { color: '#
|
|
24
|
-
'sanji-quality': { color: '#
|
|
26
|
+
'chopper-checkpoint': { color: '#60a5fa', temperature: 0.1, steps: 15 },
|
|
27
|
+
'sanji-quality': { color: '#facc15', temperature: 0.1, steps: 10 },
|
|
25
28
|
'franky-gates': { color: '#06b6d4', temperature: 0.1, steps: 10 },
|
|
26
29
|
'robin-reviewer': { color: '#8b5cf6', temperature: 0.2, steps: 15 },
|
|
27
30
|
'jinbe-security': { color: '#6366f1', temperature: 0.2, steps: 15 },
|
|
28
|
-
'brook-healing': { color: '#
|
|
31
|
+
'brook-healing': { color: '#2dd4bf', temperature: 0.1, steps: 20 },
|
|
29
32
|
'skeptic-verifier': { color: '#64748b', temperature: 0.1, steps: 12 },
|
|
30
33
|
'eval-runner': { color: '#14b8a6', temperature: 0.2, steps: 15 },
|
|
31
34
|
'resume-coordinator': { color: '#d97706', temperature: 0.2, steps: 10 },
|
|
32
35
|
'memory-keeper': { color: '#d946ef', temperature: 0.2, steps: 8 },
|
|
33
36
|
};
|
|
34
37
|
|
|
38
|
+
// Crew colors from the wave-banners table (single source of truth). Returns
|
|
39
|
+
// {} on any failure — callers fall back to the CREW map. Same table shape as
|
|
40
|
+
// the opencode plugin parses: | agent-id | role | hex | ansi-256 | emoji |
|
|
41
|
+
function readBannerColors(): Record<string, string> {
|
|
42
|
+
try {
|
|
43
|
+
if (!existsSync(BANNER_TABLE)) return {};
|
|
44
|
+
const text = readFileSync(BANNER_TABLE, 'utf8');
|
|
45
|
+
// null-prototype + CRLF-tolerant: agent ids are trusted repo content, but
|
|
46
|
+
// a future `__proto__` id must never write the object's prototype
|
|
47
|
+
const colors: Record<string, string> = Object.create(null);
|
|
48
|
+
for (const m of text.matchAll(/^\| ([\w-]+) \| [^|]+ \| (#[0-9a-f]{6}) \| (\d+) \| (\S+) \|\r?$/gm)) {
|
|
49
|
+
colors[m[1]] = m[2];
|
|
50
|
+
}
|
|
51
|
+
return colors;
|
|
52
|
+
} catch {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const BANNER_COLORS = readBannerColors();
|
|
58
|
+
|
|
35
59
|
// write-scope is the single source of truth (content/agents/*.md frontmatter),
|
|
36
60
|
// but it is a RULE for user-facing crew agents: they run inline in the main
|
|
37
61
|
// thread, so a runtime permission bound to the active-agent identity would
|
|
@@ -50,8 +74,12 @@ function permissionFromScope(scope: string | undefined): Record<string, string |
|
|
|
50
74
|
function agentFrontmatter(name: string, description: string, internal: boolean, writeScope?: string) {
|
|
51
75
|
const crew = CREW[name];
|
|
52
76
|
const lines = [`description: ${description}`, `mode: all`];
|
|
77
|
+
// color: wave-banners table wins; CREW fallback for agents the table lacks.
|
|
78
|
+
// temperature/steps only exist for CREW members (runtime tuning).
|
|
79
|
+
const color = BANNER_COLORS[name] ?? crew?.color;
|
|
80
|
+
if (color) lines.push(`color: '${color}'`);
|
|
53
81
|
if (crew) {
|
|
54
|
-
lines.push(`
|
|
82
|
+
lines.push(`temperature: ${crew.temperature}`, `steps: ${crew.steps}`);
|
|
55
83
|
const perm = internal ? permissionFromScope(writeScope) : undefined;
|
|
56
84
|
if (perm) {
|
|
57
85
|
lines.push('permission:');
|