@ionivetech/mugiwara 0.6.6 → 0.8.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 +2 -2
- package/.claude-plugin/plugin.json +2 -7
- package/.codex-plugin/plugin.json +2 -7
- package/.cursor-plugin/plugin.json +2 -7
- package/.kimi-plugin/plugin.json +2 -7
- package/.opencode/mugiwara-helpers.mjs +2 -2
- package/.opencode/plugins/mugiwara.mjs +3 -18
- package/AGENTS.md +5 -4
- package/GEMINI.md +3 -3
- package/README.md +203 -331
- package/content/agents/brook-healing.md +8 -4
- package/content/agents/chopper-checkpoint.md +7 -3
- package/content/agents/eval-runner.md +4 -4
- package/content/agents/franky-gates.md +4 -4
- package/content/agents/jinbe-security.md +6 -7
- package/content/agents/luffy-orchestrator.md +11 -13
- package/content/agents/memory-keeper.md +4 -4
- package/content/agents/nami-planner.md +4 -4
- package/content/agents/resume-coordinator.md +7 -7
- package/content/agents/robin-reviewer.md +7 -3
- package/content/agents/sanji-quality.md +2 -2
- package/content/agents/skeptic-verifier.md +2 -2
- package/content/agents/usopp-brainstorm.md +3 -3
- package/content/agents/zoro-execution.md +6 -6
- package/content/skills/mugiwara-backend/SKILL.md +54 -43
- package/content/skills/mugiwara-backend/references/database.md +61 -0
- package/content/skills/mugiwara-brainstorm/SKILL.md +1 -1
- package/content/skills/mugiwara-checkpoint/SKILL.md +23 -11
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +1 -1
- package/content/skills/mugiwara-contract-first/SKILL.md +46 -1
- package/content/skills/mugiwara-execution/SKILL.md +37 -37
- package/content/skills/mugiwara-execution/references/dispatch.md +1 -1
- package/content/skills/mugiwara-execution/references/execution-phase-flows.md +18 -0
- package/content/skills/mugiwara-execution/references/resume-batching.md +2 -2
- package/content/skills/mugiwara-frontend/SKILL.md +44 -43
- package/content/skills/mugiwara-frontend/references/checklist.md +14 -0
- package/content/skills/mugiwara-gates/SKILL.md +23 -14
- package/content/skills/mugiwara-healing/SKILL.md +26 -25
- package/content/skills/mugiwara-lessons/SKILL.md +2 -2
- package/content/skills/mugiwara-orchestration/SKILL.md +11 -11
- package/content/skills/mugiwara-orchestration/references/check-ins.md +2 -2
- package/content/skills/mugiwara-orchestration/references/closure.md +17 -12
- package/content/skills/mugiwara-orchestration/references/control-commands.md +14 -0
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +11 -1
- package/content/skills/mugiwara-planning/SKILL.md +26 -29
- package/content/skills/mugiwara-planning/references/large-campaign-subplan.md +41 -0
- package/content/skills/mugiwara-planning/references/plan-template.md +24 -2
- package/content/skills/mugiwara-quality/SKILL.md +21 -12
- package/content/skills/mugiwara-resume/SKILL.md +8 -3
- package/content/skills/mugiwara-review/SKILL.md +19 -13
- package/content/skills/mugiwara-security/SKILL.md +47 -36
- package/content/skills/mugiwara-ship/SKILL.md +1 -1
- package/content/skills/mugiwara-ship/references/cleanup.md +17 -16
- package/content/skills/mugiwara-workflow/SKILL.md +11 -17
- package/content/skills/mugiwara-workflow/references/adaptive-budget-governor.md +5 -0
- package/content/skills/mugiwara-workflow/references/benchmark-governor.md +53 -0
- package/content/skills/mugiwara-workflow/references/cognitive-output-governor.md +5 -0
- package/content/skills/mugiwara-workflow/references/large-campaign-subplan.md +29 -0
- package/content/skills/mugiwara-workflow/references/scope-code-governor.md +14 -0
- package/content/skills/mugiwara-workflow/references/stop-slop-governor.md +14 -0
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +42 -38
- package/dist/mugiwara.js +1642 -528
- package/gemini-extension.json +1 -1
- package/hooks/auto-savepoint.js +18 -10
- package/hooks/auto-savepoint.ts +23 -23
- package/hooks/engagement-marker.js +1 -1
- package/hooks/engagement-marker.ts +1 -1
- package/hooks/pipeline-guard.js +17 -13
- package/hooks/pipeline-guard.ts +24 -20
- package/hooks/session-start.js +13 -10
- package/hooks/session-start.ts +24 -19
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/references/complexity.md +27 -4
- package/references/definition-of-done.md +1 -1
- package/references/multi-actor.md +20 -2
- package/references/posture-routing.md +31 -0
- package/references/prose-style.md +54 -0
- package/scripts/benchmark-governor.ts +516 -0
- package/scripts/benchmark-thresholds.json +47 -0
- package/scripts/check-doc-links.ts +46 -0
- package/scripts/conformance.ts +6 -16
- package/scripts/coverage-gate.ts +20 -6
- package/scripts/gate-selftest.ts +138 -7
- package/scripts/lane.sh +54 -8
- package/scripts/lib/lane-base.sh +4 -4
- package/scripts/lib/patterns.sh +5 -0
- package/scripts/policy-force.ts +22 -0
- package/scripts/retrieval-eval.ts +10 -4
- package/scripts/savepoint.sh +155 -58
- package/scripts/validate-content.ts +43 -11
- package/scripts/verify-install.ts +9 -7
- package/src/adaptive-budget.ts +178 -0
- package/src/args.ts +4 -3
- package/src/budget.ts +47 -0
- package/src/check-artifacts.ts +45 -0
- package/src/cli.ts +257 -108
- package/src/cognition.ts +234 -0
- package/src/config.ts +107 -0
- package/src/context.ts +72 -0
- package/src/continue.ts +29 -10
- package/src/cost.ts +186 -0
- package/src/evidence.ts +160 -0
- package/src/installer.ts +25 -34
- package/src/integrity.ts +158 -0
- package/src/investigation.ts +72 -0
- package/src/mission.ts +341 -94
- package/src/policy.ts +156 -0
- package/src/posture.ts +86 -0
- package/src/provenance.ts +116 -0
- package/src/reporting.ts +225 -0
- package/src/rollback.ts +95 -0
- package/src/routing.ts +69 -0
- package/src/run.ts +2 -2
- package/src/scope.ts +321 -0
- package/src/sign.ts +235 -0
- package/src/slop.ts +306 -0
- package/src/targets/claude.ts +2 -2
- package/src/work.ts +273 -0
- package/.opencode/commands/mugiwara-execute.md +0 -13
- package/.opencode/commands/mugiwara-heal.md +0 -13
- package/.opencode/commands/mugiwara-onboard.md +0 -14
- package/.opencode/commands/mugiwara-plan.md +0 -17
- package/.opencode/commands/mugiwara-ship.md +0 -13
- package/.opencode/commands/using-mugiwara.md +0 -20
- package/content/skills/mugiwara-agent-security/SKILL.md +0 -58
- package/content/skills/mugiwara-agent-security/references/checklist.md +0 -20
- package/content/skills/mugiwara-context-budget/SKILL.md +0 -66
- package/content/skills/mugiwara-context-budget/references/context-budget.md +0 -37
- package/content/skills/mugiwara-pr/SKILL.md +0 -69
- package/content/skills/mugiwara-pr/references/verdict-format.md +0 -31
- package/content/skills/mugiwara-sunset/SKILL.md +0 -82
- package/content/skills/using-mugiwara/SKILL.md +0 -51
- package/references/token-budget.md +0 -56
- package/scripts/evidence.sh +0 -81
- package/scripts/initiative.ts +0 -296
- package/scripts/mission-report.sh +0 -293
- package/src/onboard.ts +0 -207
package/gemini-extension.json
CHANGED
package/hooks/auto-savepoint.js
CHANGED
|
@@ -39,16 +39,22 @@ var root = join2(dirname2(fileURLToPath2(import.meta.url)), "..");
|
|
|
39
39
|
var cwd = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
40
40
|
var SAFE = /^[A-Za-z0-9._-]+$/;
|
|
41
41
|
function bootstrapMission() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const base = join2(cwd, ".mugiwara", "missions");
|
|
43
|
+
if (!existsSync2(base))
|
|
44
|
+
return null;
|
|
45
|
+
for (const e of readdirSync2(base, { withFileTypes: true })) {
|
|
46
|
+
if (!e.isDirectory() || !SAFE.test(e.name) || /^\.+$/.test(e.name))
|
|
45
47
|
continue;
|
|
46
|
-
const files = readdirSync2(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const files = readdirSync2(join2(base, e.name));
|
|
49
|
+
const hasState = files.some((f) => {
|
|
50
|
+
if (!f.endsWith(".json"))
|
|
51
|
+
return false;
|
|
52
|
+
const stem = f.slice(0, -5);
|
|
53
|
+
return stem !== "continue" && !stem.startsWith("continue-");
|
|
54
|
+
});
|
|
55
|
+
const hasArtifacts = ["plan.md", "spec.md", "decisions.md"].some((f) => files.includes(f));
|
|
56
|
+
if (!hasState && hasArtifacts) {
|
|
57
|
+
return { mission: e.name, member: "", wave: "0", mode: readMode(), updated: 0 };
|
|
52
58
|
}
|
|
53
59
|
}
|
|
54
60
|
return null;
|
|
@@ -65,7 +71,7 @@ function readMode() {
|
|
|
65
71
|
return "guided";
|
|
66
72
|
}
|
|
67
73
|
function activeMission() {
|
|
68
|
-
const base = join2(cwd, ".mugiwara", "
|
|
74
|
+
const base = join2(cwd, ".mugiwara", "missions");
|
|
69
75
|
if (!existsSync2(base))
|
|
70
76
|
return bootstrapMission();
|
|
71
77
|
let best = null;
|
|
@@ -76,6 +82,8 @@ function activeMission() {
|
|
|
76
82
|
if (!f.endsWith(".json"))
|
|
77
83
|
continue;
|
|
78
84
|
const stem = f.slice(0, -5);
|
|
85
|
+
if (stem === "continue" || stem.startsWith("continue-"))
|
|
86
|
+
continue;
|
|
79
87
|
const member = stem === "state" ? "" : stem;
|
|
80
88
|
if (member && (!SAFE.test(member) || /^\.+$/.test(member)))
|
|
81
89
|
continue;
|
package/hooks/auto-savepoint.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// hooks/auto-savepoint.ts — Stop hook: refresh the active mission's savepoint.
|
|
3
3
|
//
|
|
4
4
|
// State used to depend on the model remembering the prose instruction "run
|
|
5
|
-
// scripts/savepoint.sh". It forgot, so .
|
|
6
|
-
//
|
|
5
|
+
// scripts/savepoint.sh". It forgot, so state.json and continue.json stayed
|
|
6
|
+
// missing and everything reading them (resume, heal_cycle cap, lane-rise
|
|
7
7
|
// detection, the SessionStart auto-resume) was dead. This makes the write
|
|
8
8
|
// happen whether or not the crew remembers.
|
|
9
9
|
//
|
|
10
|
-
// Refreshes the CURRENT
|
|
11
|
-
// explicit crew action; this only keeps tasks/lane/blockers honest.
|
|
10
|
+
// Refreshes the CURRENT flow stage — it never advances one. Flow transitions
|
|
11
|
+
// stay an explicit crew action; this only keeps tasks/lane/blockers honest.
|
|
12
12
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
13
13
|
import { spawnSync } from 'node:child_process';
|
|
14
14
|
// Shared with `mugiwara run` — a second copy here drifted (it lacked the PATH
|
|
@@ -25,26 +25,24 @@ type Active = { mission: string; member: string; wave: string; mode: string; upd
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Name the mission when no savepoint exists yet, so the first one can be
|
|
28
|
-
* written.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* written. A missions/<name>/ dir holding plan/spec/decisions but no state
|
|
29
|
+
* json means Flow 0 produced artifacts without a savepoint — exactly the
|
|
30
|
+
* drift this hook exists to end.
|
|
31
31
|
*/
|
|
32
32
|
function bootstrapMission(): Active | null {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
for (const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
.
|
|
41
|
-
.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return { mission: name, member: '', wave: '0', mode: readMode(), updated: 0 };
|
|
47
|
-
}
|
|
33
|
+
const base = join(cwd, '.mugiwara', 'missions');
|
|
34
|
+
if (!existsSync(base)) return null;
|
|
35
|
+
for (const e of readdirSync(base, { withFileTypes: true })) {
|
|
36
|
+
if (!e.isDirectory() || !SAFE.test(e.name) || /^\.+$/.test(e.name)) continue;
|
|
37
|
+
const files = readdirSync(join(base, e.name));
|
|
38
|
+
const hasState = files.some((f) => {
|
|
39
|
+
if (!f.endsWith('.json')) return false;
|
|
40
|
+
const stem = f.slice(0, -5);
|
|
41
|
+
return stem !== 'continue' && !stem.startsWith('continue-');
|
|
42
|
+
});
|
|
43
|
+
const hasArtifacts = ['plan.md', 'spec.md', 'decisions.md'].some((f) => files.includes(f));
|
|
44
|
+
if (!hasState && hasArtifacts) {
|
|
45
|
+
return { mission: e.name, member: '', wave: '0', mode: readMode(), updated: 0 };
|
|
48
46
|
}
|
|
49
47
|
}
|
|
50
48
|
return null;
|
|
@@ -61,14 +59,16 @@ function readMode(): string {
|
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
function activeMission(): Active | null {
|
|
64
|
-
const base = join(cwd, '.mugiwara', '
|
|
62
|
+
const base = join(cwd, '.mugiwara', 'missions');
|
|
65
63
|
if (!existsSync(base)) return bootstrapMission();
|
|
66
64
|
let best: Active | null = null;
|
|
67
65
|
for (const e of readdirSync(base, { withFileTypes: true })) {
|
|
68
66
|
if (!e.isDirectory() || !SAFE.test(e.name) || /^\.+$/.test(e.name)) continue;
|
|
69
67
|
for (const f of readdirSync(join(base, e.name))) {
|
|
68
|
+
// state.json (solo) or <member>.json (team) — never continue*.json
|
|
70
69
|
if (!f.endsWith('.json')) continue;
|
|
71
70
|
const stem = f.slice(0, -5);
|
|
71
|
+
if (stem === 'continue' || stem.startsWith('continue-')) continue;
|
|
72
72
|
const member = stem === 'state' ? '' : stem;
|
|
73
73
|
if (member && (!SAFE.test(member) || /^\.+$/.test(member))) continue;
|
|
74
74
|
try {
|
|
@@ -22,7 +22,7 @@ async function main() {
|
|
|
22
22
|
return;
|
|
23
23
|
const dispatched = /zoro-execution|brook-healing|mugiwara-execution|mugiwara-healing|mugiwara-execute|mugiwara-heal/.test(agentField);
|
|
24
24
|
const planned = /nami-planner|mugiwara-planning|mugiwara-plan/.test(agentField);
|
|
25
|
-
const dir = join(cwd, ".mugiwara"
|
|
25
|
+
const dir = join(cwd, ".mugiwara");
|
|
26
26
|
const file = join(dir, ".engaged");
|
|
27
27
|
const sessionId = typeof payload.session_id === "string" ? payload.session_id : "";
|
|
28
28
|
try {
|
|
@@ -49,7 +49,7 @@ async function main(): Promise<void> {
|
|
|
49
49
|
// inline, which is escape #2: only Nami writes the plan.
|
|
50
50
|
const planned = /nami-planner|mugiwara-planning|mugiwara-plan/.test(agentField);
|
|
51
51
|
|
|
52
|
-
const dir = join(cwd, '.mugiwara'
|
|
52
|
+
const dir = join(cwd, '.mugiwara');
|
|
53
53
|
const file = join(dir, '.engaged');
|
|
54
54
|
// session_id scopes the marker when the payload carries one. Its presence on
|
|
55
55
|
// every hook event is NOT verified, so the guard also accepts a recent mtime
|
package/hooks/pipeline-guard.js
CHANGED
|
@@ -29,7 +29,7 @@ function readEnforce() {
|
|
|
29
29
|
return "block";
|
|
30
30
|
}
|
|
31
31
|
function engaged(sessionId) {
|
|
32
|
-
const file = join(cwd, ".mugiwara", "
|
|
32
|
+
const file = join(cwd, ".mugiwara", ".engaged");
|
|
33
33
|
if (!existsSync(file))
|
|
34
34
|
return false;
|
|
35
35
|
try {
|
|
@@ -51,7 +51,7 @@ function sourceChanged() {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
function newestMissionState() {
|
|
54
|
-
const base = join(cwd, ".mugiwara", "
|
|
54
|
+
const base = join(cwd, ".mugiwara", "missions");
|
|
55
55
|
if (!existsSync(base))
|
|
56
56
|
return null;
|
|
57
57
|
let best = null;
|
|
@@ -61,7 +61,8 @@ function newestMissionState() {
|
|
|
61
61
|
if (!e.isDirectory())
|
|
62
62
|
continue;
|
|
63
63
|
for (const f of readdirSync(join(base, e.name))) {
|
|
64
|
-
|
|
64
|
+
const stem = f.replace(/\.json$/, "");
|
|
65
|
+
if (!f.endsWith(".json") || stem === "continue" || stem.startsWith("continue-"))
|
|
65
66
|
continue;
|
|
66
67
|
const p = join(base, e.name, f);
|
|
67
68
|
try {
|
|
@@ -81,7 +82,7 @@ function newestMissionState() {
|
|
|
81
82
|
}
|
|
82
83
|
var LANE_RANK = { direct: 0, lean: 1, standard: 2, full: 3, spike: 4 };
|
|
83
84
|
function executorDispatched(sessionId) {
|
|
84
|
-
const file = join(cwd, ".mugiwara", "
|
|
85
|
+
const file = join(cwd, ".mugiwara", ".engaged");
|
|
85
86
|
if (!existsSync(file))
|
|
86
87
|
return false;
|
|
87
88
|
try {
|
|
@@ -97,7 +98,7 @@ function executorDispatched(sessionId) {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
function plannerDispatched(sessionId) {
|
|
100
|
-
const file = join(cwd, ".mugiwara", "
|
|
101
|
+
const file = join(cwd, ".mugiwara", ".engaged");
|
|
101
102
|
if (!existsSync(file))
|
|
102
103
|
return false;
|
|
103
104
|
try {
|
|
@@ -113,10 +114,10 @@ function plannerDispatched(sessionId) {
|
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
function planTouched() {
|
|
116
|
-
const
|
|
117
|
-
if (!existsSync(
|
|
117
|
+
const missionsDir = join(cwd, ".mugiwara", "missions");
|
|
118
|
+
if (!existsSync(missionsDir))
|
|
118
119
|
return false;
|
|
119
|
-
const markerFile = join(cwd, ".mugiwara", "
|
|
120
|
+
const markerFile = join(cwd, ".mugiwara", ".engaged");
|
|
120
121
|
if (!existsSync(markerFile))
|
|
121
122
|
return false;
|
|
122
123
|
let sessionStart = 0;
|
|
@@ -127,11 +128,14 @@ function planTouched() {
|
|
|
127
128
|
if (!sessionStart)
|
|
128
129
|
return false;
|
|
129
130
|
try {
|
|
130
|
-
for (const e of readdirSync(
|
|
131
|
-
if (!e.
|
|
131
|
+
for (const e of readdirSync(missionsDir, { withFileTypes: true })) {
|
|
132
|
+
if (!e.isDirectory())
|
|
133
|
+
continue;
|
|
134
|
+
const plan = join(missionsDir, e.name, "plan.md");
|
|
135
|
+
if (!existsSync(plan))
|
|
132
136
|
continue;
|
|
133
|
-
const at = lstatSync(
|
|
134
|
-
if (at >= sessionStart)
|
|
137
|
+
const at = lstatSync(plan).mtimeMs;
|
|
138
|
+
if (at + 1000 >= sessionStart)
|
|
135
139
|
return true;
|
|
136
140
|
}
|
|
137
141
|
} catch {}
|
|
@@ -162,7 +166,7 @@ async function main() {
|
|
|
162
166
|
`);
|
|
163
167
|
}
|
|
164
168
|
if (planTouched() && !plannerDispatched(sessionId)) {
|
|
165
|
-
process.stderr.write("\u26A0 Mugiwara: a plan doc
|
|
169
|
+
process.stderr.write("\u26A0 Mugiwara: a plan doc (missions/<mission>/plan.md) was written this session, " + "but no planner (nami-planner / mugiwara-planning) was dispatched or embodied. " + "Only Nami writes the plan \u2014 dispatch nami-planner, or record the plan as a " + `deliberate exception in the decision log. Set enforce=off in .mugiwara/config to disable.
|
|
166
170
|
`);
|
|
167
171
|
}
|
|
168
172
|
return;
|
package/hooks/pipeline-guard.ts
CHANGED
|
@@ -51,7 +51,7 @@ function readEnforce(): Enforce {
|
|
|
51
51
|
|
|
52
52
|
/** Engaged = a mugiwara agent or skill ran in this session. */
|
|
53
53
|
function engaged(sessionId: string): boolean {
|
|
54
|
-
const file = join(cwd, '.mugiwara', '
|
|
54
|
+
const file = join(cwd, '.mugiwara', '.engaged');
|
|
55
55
|
if (!existsSync(file)) return false;
|
|
56
56
|
try {
|
|
57
57
|
const m = JSON.parse(readFileSync(file, 'utf8')) as { session_id?: string; touched_at?: string };
|
|
@@ -89,7 +89,7 @@ function sourceChanged(): boolean {
|
|
|
89
89
|
* the write-boundary check (check 2).
|
|
90
90
|
*/
|
|
91
91
|
function newestMissionState(): { mission: string; lane: string } | null {
|
|
92
|
-
const base = join(cwd, '.mugiwara', '
|
|
92
|
+
const base = join(cwd, '.mugiwara', 'missions');
|
|
93
93
|
if (!existsSync(base)) return null;
|
|
94
94
|
let best: { mission: string; lane: string } | null = null;
|
|
95
95
|
let bestAt = -1;
|
|
@@ -97,7 +97,9 @@ function newestMissionState(): { mission: string; lane: string } | null {
|
|
|
97
97
|
for (const e of readdirSync(base, { withFileTypes: true })) {
|
|
98
98
|
if (!e.isDirectory()) continue;
|
|
99
99
|
for (const f of readdirSync(join(base, e.name))) {
|
|
100
|
-
|
|
100
|
+
// state.json (solo) or <member>.json (team) — never continue*.json
|
|
101
|
+
const stem = f.replace(/\.json$/, '');
|
|
102
|
+
if (!f.endsWith('.json') || stem === 'continue' || stem.startsWith('continue-')) continue;
|
|
101
103
|
const p = join(base, e.name, f);
|
|
102
104
|
try {
|
|
103
105
|
const s = JSON.parse(readFileSync(p, 'utf8')) as { mission?: unknown; lane?: unknown };
|
|
@@ -121,7 +123,7 @@ const LANE_RANK: Record<string, number> = { direct: 0, lean: 1, standard: 2, ful
|
|
|
121
123
|
|
|
122
124
|
/** An executor (Zoro/Brook) was dispatched or embodied in this session. */
|
|
123
125
|
function executorDispatched(sessionId: string): boolean {
|
|
124
|
-
const file = join(cwd, '.mugiwara', '
|
|
126
|
+
const file = join(cwd, '.mugiwara', '.engaged');
|
|
125
127
|
if (!existsSync(file)) return false;
|
|
126
128
|
try {
|
|
127
129
|
const m = JSON.parse(readFileSync(file, 'utf8')) as { session_id?: string; executor_dispatched_at?: string };
|
|
@@ -136,7 +138,7 @@ function executorDispatched(sessionId: string): boolean {
|
|
|
136
138
|
|
|
137
139
|
/** A planner (Nami) was dispatched or embodied in this session. */
|
|
138
140
|
function plannerDispatched(sessionId: string): boolean {
|
|
139
|
-
const file = join(cwd, '.mugiwara', '
|
|
141
|
+
const file = join(cwd, '.mugiwara', '.engaged');
|
|
140
142
|
if (!existsSync(file)) return false;
|
|
141
143
|
try {
|
|
142
144
|
const m = JSON.parse(readFileSync(file, 'utf8')) as { session_id?: string; planner_dispatched_at?: string };
|
|
@@ -150,20 +152,19 @@ function plannerDispatched(sessionId: string): boolean {
|
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
/**
|
|
153
|
-
* A plan doc
|
|
154
|
-
* .mugiwara/ is gitignored, so git status cannot see plan files —
|
|
155
|
-
* filesystem directly and compare mtimes against the session's
|
|
156
|
-
* marker.
|
|
157
|
-
*
|
|
158
|
-
* only Nami may write.
|
|
155
|
+
* A plan doc (missions/<mission>/plan.md) was created or modified in this
|
|
156
|
+
* session. .mugiwara/ is gitignored, so git status cannot see plan files —
|
|
157
|
+
* read the filesystem directly and compare mtimes against the session's
|
|
158
|
+
* first-seen marker. Only a mission plan.md counts — the plan doc is the
|
|
159
|
+
* artifact only Nami may write.
|
|
159
160
|
*/
|
|
160
161
|
function planTouched(): boolean {
|
|
161
|
-
const
|
|
162
|
-
if (!existsSync(
|
|
162
|
+
const missionsDir = join(cwd, '.mugiwara', 'missions');
|
|
163
|
+
if (!existsSync(missionsDir)) return false;
|
|
163
164
|
// session start = the marker's first_seen; anything newer is this session's work.
|
|
164
165
|
// An ABSENT marker means the session never engaged — treat as untouched, never
|
|
165
166
|
// as "every plan counts" (a 0 fallback would flag every pre-existing plan).
|
|
166
|
-
const markerFile = join(cwd, '.mugiwara', '
|
|
167
|
+
const markerFile = join(cwd, '.mugiwara', '.engaged');
|
|
167
168
|
if (!existsSync(markerFile)) return false;
|
|
168
169
|
let sessionStart = 0;
|
|
169
170
|
try {
|
|
@@ -172,12 +173,15 @@ function planTouched(): boolean {
|
|
|
172
173
|
} catch { /* corrupt marker — treat as untouched */ }
|
|
173
174
|
if (!sessionStart) return false;
|
|
174
175
|
try {
|
|
175
|
-
for (const e of readdirSync(
|
|
176
|
-
if (!e.
|
|
177
|
-
|
|
176
|
+
for (const e of readdirSync(missionsDir, { withFileTypes: true })) {
|
|
177
|
+
if (!e.isDirectory()) continue;
|
|
178
|
+
const plan = join(missionsDir, e.name, 'plan.md');
|
|
179
|
+
if (!existsSync(plan)) continue;
|
|
180
|
+
// lstat, not stat: a symlinked plan pointing outside .mugiwara/
|
|
178
181
|
// must not count as a plan write.
|
|
179
|
-
|
|
180
|
-
|
|
182
|
+
// 1s tolerance: file mtime can be ~7ms before first_seen due to FS granularity / clock skew
|
|
183
|
+
const at = lstatSync(plan).mtimeMs;
|
|
184
|
+
if (at + 1000 >= sessionStart) return true;
|
|
181
185
|
}
|
|
182
186
|
} catch { /* unreadable — treat as untouched */ }
|
|
183
187
|
return false;
|
|
@@ -232,7 +236,7 @@ async function main(): Promise<void> {
|
|
|
232
236
|
// across harnesses.
|
|
233
237
|
if (planTouched() && !plannerDispatched(sessionId)) {
|
|
234
238
|
process.stderr.write(
|
|
235
|
-
'⚠ Mugiwara: a plan doc
|
|
239
|
+
'⚠ Mugiwara: a plan doc (missions/<mission>/plan.md) was written this session, ' +
|
|
236
240
|
'but no planner (nami-planner / mugiwara-planning) was dispatched or embodied. ' +
|
|
237
241
|
'Only Nami writes the plan — dispatch nami-planner, or record the plan as a ' +
|
|
238
242
|
'deliberate exception in the decision log. Set enforce=off in .mugiwara/config to disable.\n',
|
package/hooks/session-start.js
CHANGED
|
@@ -50,13 +50,18 @@ var isNum = (s) => /^\d+$/.test(s);
|
|
|
50
50
|
var isSafeKey = (s) => /^[A-Za-z0-9._-]+$/.test(s);
|
|
51
51
|
var resumeContext = "";
|
|
52
52
|
var actor = gitActor();
|
|
53
|
-
var
|
|
53
|
+
var missionsRoot = join(cwd, ".mugiwara", "missions");
|
|
54
54
|
var active = [];
|
|
55
|
-
if (existsSync(
|
|
56
|
-
const missions = readdirSync(
|
|
55
|
+
if (existsSync(missionsRoot)) {
|
|
56
|
+
const missions = readdirSync(missionsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && isSafeKey(e.name)).map((e) => e.name);
|
|
57
57
|
for (const mission of missions) {
|
|
58
|
-
const dir = join(
|
|
59
|
-
const files = readdirSync(dir).filter((f) =>
|
|
58
|
+
const dir = join(missionsRoot, mission);
|
|
59
|
+
const files = readdirSync(dir).filter((f) => {
|
|
60
|
+
if (!f.endsWith(".json"))
|
|
61
|
+
return false;
|
|
62
|
+
const stem = f.slice(0, -".json".length);
|
|
63
|
+
return stem === "continue" || stem.startsWith("continue-");
|
|
64
|
+
});
|
|
60
65
|
for (const f of files) {
|
|
61
66
|
const file = join(dir, f);
|
|
62
67
|
if (!existsSync(file))
|
|
@@ -99,8 +104,6 @@ if (active.length === 1 && mode === "auto") {
|
|
|
99
104
|
${lines}
|
|
100
105
|
` + `Run /mugiwara continue <mission> [member] to resume one explicitly.`;
|
|
101
106
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
` + resumeContext : "")
|
|
106
|
-
}));
|
|
107
|
+
if (resumeContext) {
|
|
108
|
+
console.log(JSON.stringify({ additionalContext: resumeContext }));
|
|
109
|
+
}
|
package/hooks/session-start.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
// hooks/session-start.ts — SessionStart hook:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
2
|
+
// hooks/session-start.ts — SessionStart hook: surfaces in-flight missions for
|
|
3
|
+
// the current git actor from the machine-written continue JSON (D10).
|
|
4
|
+
//
|
|
5
|
+
// Deliberately SILENT otherwise: mugiwara must not announce itself or steer a
|
|
6
|
+
// session that never engaged the crew. The only output is the in-flight
|
|
7
|
+
// listing (every mode) and auto-resume (auto mode, single mission). Listing
|
|
8
|
+
// never auto-resumes when multiple are in-flight — ambiguous resumes are
|
|
9
|
+
// listed, not guessed.
|
|
7
10
|
|
|
8
11
|
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
9
12
|
import { execFileSync } from 'node:child_process';
|
|
@@ -63,23 +66,27 @@ const isSafeKey = (s: string): boolean => /^[A-Za-z0-9._-]+$/.test(s);
|
|
|
63
66
|
|
|
64
67
|
let resumeContext = '';
|
|
65
68
|
const actor = gitActor();
|
|
66
|
-
const
|
|
69
|
+
const missionsRoot = join(cwd, '.mugiwara', 'missions');
|
|
67
70
|
const active: { mission: string; member: string | null; flow: string; done: string; total: string }[] = [];
|
|
68
71
|
|
|
69
|
-
if (existsSync(
|
|
70
|
-
//
|
|
71
|
-
const missions = readdirSync(
|
|
72
|
+
if (existsSync(missionsRoot)) {
|
|
73
|
+
// missions/<mission>/continue.json (solo) | continue-<member>.json (team)
|
|
74
|
+
const missions = readdirSync(missionsRoot, { withFileTypes: true })
|
|
72
75
|
.filter((e) => e.isDirectory() && isSafeKey(e.name))
|
|
73
76
|
.map((e) => e.name);
|
|
74
77
|
for (const mission of missions) {
|
|
75
|
-
const dir = join(
|
|
76
|
-
const files = readdirSync(dir).filter((f) =>
|
|
78
|
+
const dir = join(missionsRoot, mission);
|
|
79
|
+
const files = readdirSync(dir).filter((f) => {
|
|
80
|
+
if (!f.endsWith('.json')) return false;
|
|
81
|
+
const stem = f.slice(0, -'.json'.length);
|
|
82
|
+
return stem === 'continue' || stem.startsWith('continue-');
|
|
83
|
+
});
|
|
77
84
|
for (const f of files) {
|
|
78
85
|
const file = join(dir, f);
|
|
79
86
|
if (!existsSync(file)) continue;
|
|
80
87
|
try {
|
|
81
88
|
const s = JSON.parse(readFileSync(file, 'utf8'));
|
|
82
|
-
// only this actor's states; solo (
|
|
89
|
+
// only this actor's states; solo (continue.json) belongs to whoever
|
|
83
90
|
// owns its actor field
|
|
84
91
|
if (s.actor !== actor) continue;
|
|
85
92
|
if (!isSafeKey(String(s.mission ?? ''))) continue;
|
|
@@ -122,10 +129,8 @@ if (active.length === 1 && mode === 'auto') {
|
|
|
122
129
|
`Run /mugiwara continue <mission> [member] to resume one explicitly.`;
|
|
123
130
|
}
|
|
124
131
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
})
|
|
131
|
-
);
|
|
132
|
+
// Silent unless there is in-flight work: a session that never used mugiwara
|
|
133
|
+
// gets zero injected context.
|
|
134
|
+
if (resumeContext) {
|
|
135
|
+
console.log(JSON.stringify({ additionalContext: resumeContext }));
|
|
136
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ionivetech/mugiwara",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"sync-version": "bun scripts/sync-version.ts",
|
|
64
64
|
"build-hooks": "bun scripts/build-hooks.ts",
|
|
65
65
|
"build-hooks:check": "bun scripts/build-hooks.ts --check",
|
|
66
|
-
"gate": "bun run build-hooks:check && bun run typecheck && bun run test:coverage && bun run build && bun scripts/validate-content.ts --check-manifest --check-docs --check-doc-integrity && bun scripts/lane-base.ts && bun run verify-pack && bun scripts/run-evals.ts && bun scripts/retrieval-eval.ts && bun scripts/verify-install.ts && bun scripts/conformance.ts && bun run coverage-gate",
|
|
66
|
+
"gate": "bun run build-hooks:check && bun run typecheck && bun run test:coverage && bun run build && bun scripts/validate-content.ts --check-manifest --check-docs --check-doc-integrity && bun scripts/lane-base.ts && bun scripts/check-doc-links.ts && bun run verify-pack && bun scripts/run-evals.ts && bun scripts/retrieval-eval.ts && bun scripts/benchmark-governor.ts && bun scripts/verify-install.ts && bun scripts/conformance.ts && bun run coverage-gate",
|
|
67
67
|
"verify-pack": "npm pack --dry-run 2>&1 | node -e \"let s='';process.stdin.on('data',d=>s+=d);process.stdin.on('end',()=>{if(!s.includes('ionivetech-mugiwara')){console.error('npm pack failed');process.exit(1)};console.log('npm package clean')})\"",
|
|
68
68
|
"prepack": "bun run build && bun run sync-version"
|
|
69
69
|
},
|
package/plugin.json
CHANGED
package/references/complexity.md
CHANGED
|
@@ -40,11 +40,34 @@ path:line:functionName — CC 14 (threshold 10)
|
|
|
40
40
|
branches: if x2, for x1, && x3, case x4, catch x1, ternary x3
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
## Cognitive complexity (
|
|
43
|
+
## Cognitive complexity — understandability (Sonar)
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
nesting
|
|
45
|
+
Measures how hard a function is to READ, not how many paths it has. Nesting
|
|
46
|
+
is penalized: a construct nested inside another adds its increment plus a
|
|
47
|
+
nesting increment, so a three-level guard ladder outscores an eight-branch
|
|
48
|
+
flat chain that has higher cyclomatic. The two metrics catch different
|
|
49
|
+
functions — report both, flag on either.
|
|
50
|
+
|
|
51
|
+
Count per function: +1 for every flow break (`if`, `else if`, `else`,
|
|
52
|
+
`for`, `while`, `do`, `case`, `catch`, ternary, each `&&`/`||`/`??` beyond
|
|
53
|
+
the first in its chain), plus +1 per nesting level entered for structures
|
|
54
|
+
nested inside another flow-break structure. Sequences and flattenable
|
|
55
|
+
patterns (`else if` chains, top-level `&&`) stay cheap — that is the point.
|
|
56
|
+
|
|
57
|
+
| Cognitive | Verdict |
|
|
58
|
+
|-----------|---------|
|
|
59
|
+
| ≤15 | clean |
|
|
60
|
+
| 16–25 | flag — flatten nesting or extract; minor/major by context |
|
|
61
|
+
| >25 | major finding — restructure before merge |
|
|
62
|
+
|
|
63
|
+
Evidence format mirrors cyclomatic: `path:line:functionName — COG 18
|
|
64
|
+
(threshold 15)` plus the nesting sketch that produced it. A number without
|
|
65
|
+
the sketch is an unproven claim.
|
|
66
|
+
|
|
67
|
+
Tooling first: ESLint SonarJS `sonarjs/cognitive-complexity` (default max
|
|
68
|
+
15), SonarScanner's `cognitive_complexity` metric, or the language's
|
|
69
|
+
equivalent. The repo's tool is the source of truth; manual counting is the
|
|
70
|
+
fallback. Never weaken a tool's threshold to make a function pass.
|
|
48
71
|
|
|
49
72
|
## Tooling
|
|
50
73
|
|
|
@@ -12,7 +12,7 @@ Every mission wave must satisfy all five before it is Done:
|
|
|
12
12
|
| **Quality** | Lint, format, and tests are clean. | Run project formatter → linter → unit suite. Zero errors. Configs unweakened. |
|
|
13
13
|
| **Integration** | The work fits the existing system. | Build/typecheck exits 0 against full tree. No regression in existing tests. |
|
|
14
14
|
| **Docs** | User-facing and internal docs match the change. | README, changelog, API docs, and in-code docstrings updated where the change requires it. |
|
|
15
|
-
| **Ship-readiness** | No blockers left open. | Blocker ledger `.mugiwara/
|
|
15
|
+
| **Ship-readiness** | No blockers left open. | Blocker ledger `.mugiwara/missions/<mission>/blockers.md` has zero open rows. |
|
|
16
16
|
|
|
17
17
|
## Verdict
|
|
18
18
|
|
|
@@ -65,5 +65,23 @@ The session-start hook (auto mode) surfaces only the missions owned by your git
|
|
|
65
65
|
actor — never another member's. If you hold several in-flight missions it lists
|
|
66
66
|
them and asks, it never guesses.
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
## Ownership & interface declarations (Phase D)
|
|
69
|
+
|
|
70
|
+
Each member declares its owned files/interfaces in the shared plan's ownership
|
|
71
|
+
map. A member or worker never receives another member's scope — in `auto` mode
|
|
72
|
+
this is a hard boundary, never silently crossed (the resumed scope is exactly
|
|
73
|
+
the selected `(mission, member)` file).
|
|
74
|
+
|
|
75
|
+
## Standardized handoff
|
|
76
|
+
|
|
77
|
+
Handoff between members or sequential sub-missions carries: done-criteria,
|
|
78
|
+
branch/base status, dependency status, continuation pointer
|
|
79
|
+
(`continue-<member>.json`), and unresolved blocker references. Never a bare
|
|
80
|
+
"here's where I left off".
|
|
81
|
+
|
|
82
|
+
## Base drift & collisions
|
|
83
|
+
|
|
84
|
+
Base drift, merge, and interface collisions are **routing/escalation events**,
|
|
85
|
+
never a silent retry or an automatic cross-scope edit. When main has moved past
|
|
86
|
+
the mission base, escalate (rebase check) before continuing; an interface
|
|
87
|
+
collision routes to Luffy, never silently overwrites another member's work.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Posture Routing (Flow 0/2)
|
|
2
|
+
|
|
3
|
+
Posture is independent of control mode. Luffy records it; Nami proposes the
|
|
4
|
+
resolved posture; it is chosen deterministically — never opaque scoring.
|
|
5
|
+
|
|
6
|
+
## Initial posture (Flow 0)
|
|
7
|
+
|
|
8
|
+
At triage, alongside route + lane + risk, record the initial execution posture +
|
|
9
|
+
rationale + evidence refs. Ordinary work defaults to `inline-sequential` (a
|
|
10
|
+
no-op route — no posture change).
|
|
11
|
+
|
|
12
|
+
Deterministic matrix: `src/posture.ts` `selectPosture(input)` → `{ posture,
|
|
13
|
+
reason, evidence_refs }`. Inputs: lane, risk, independent_tasks, order_dependent,
|
|
14
|
+
context_pressure, team_members, phases, plan_lines, governor verdict.
|
|
15
|
+
|
|
16
|
+
| Trigger | Posture |
|
|
17
|
+
|---------|---------|
|
|
18
|
+
| ordinary / none | `inline-sequential` (default) |
|
|
19
|
+
| ≥2 independent tasks, no shared files/interfaces | `parallel-workers` |
|
|
20
|
+
| context pressure + ordered tasks | `context-relief` |
|
|
21
|
+
| >3 phases or >1500 plan lines | `phase-isolated` |
|
|
22
|
+
| >1 team member | `team-scoped` |
|
|
23
|
+
| governor stop | safe pause (keep inline, emit state + continue) |
|
|
24
|
+
|
|
25
|
+
Governor stop never silently changes mode or crew roles; it pauses and records.
|
|
26
|
+
|
|
27
|
+
## Plan posture (Flow 2)
|
|
28
|
+
|
|
29
|
+
Nami declares dependencies, write-conflict groups, explicit `[PARALLEL]`
|
|
30
|
+
eligibility (file- AND interface-disjoint), member ownership, phase/sub-plan
|
|
31
|
+
trigger, acceptance evidence. Luffy records the resolved posture.
|