@ionivetech/mugiwara 0.6.6 → 0.7.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/plugins/mugiwara.mjs +3 -18
- package/AGENTS.md +5 -4
- package/GEMINI.md +3 -3
- package/README.md +69 -63
- 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 +3 -3
- 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 +2 -0
- 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 +6 -5
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +1 -1
- package/content/skills/mugiwara-execution/SKILL.md +8 -8
- package/content/skills/mugiwara-execution/references/dispatch.md +1 -1
- package/content/skills/mugiwara-execution/references/resume-batching.md +2 -2
- package/content/skills/mugiwara-frontend/SKILL.md +3 -2
- package/content/skills/mugiwara-frontend/references/checklist.md +14 -0
- package/content/skills/mugiwara-gates/SKILL.md +8 -5
- package/content/skills/mugiwara-healing/SKILL.md +2 -2
- package/content/skills/mugiwara-lessons/SKILL.md +2 -2
- package/content/skills/mugiwara-orchestration/SKILL.md +7 -7
- 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/triage-escalation.md +11 -1
- package/content/skills/mugiwara-planning/SKILL.md +3 -18
- package/content/skills/mugiwara-planning/references/plan-template.md +2 -2
- package/content/skills/mugiwara-quality/SKILL.md +5 -2
- package/content/skills/mugiwara-resume/SKILL.md +2 -2
- package/content/skills/mugiwara-review/SKILL.md +3 -2
- package/content/skills/mugiwara-security/SKILL.md +1 -1
- 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 +9 -12
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +39 -38
- package/dist/mugiwara.js +904 -462
- 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 +16 -12
- package/hooks/pipeline-guard.ts +22 -19
- 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 +0 -3
- package/references/prose-style.md +54 -0
- package/scripts/check-doc-links.ts +40 -0
- package/scripts/conformance.ts +6 -16
- package/scripts/coverage-gate.ts +20 -6
- package/scripts/gate-selftest.ts +118 -7
- package/scripts/lane.sh +54 -8
- package/scripts/lib/patterns.sh +5 -0
- package/scripts/policy-force.ts +22 -0
- package/scripts/retrieval-eval.ts +1 -1
- package/scripts/savepoint.sh +136 -58
- package/scripts/validate-content.ts +22 -9
- package/scripts/verify-install.ts +9 -7
- package/src/args.ts +2 -2
- package/src/budget.ts +56 -0
- package/src/cli.ts +161 -110
- package/src/continue.ts +29 -10
- package/src/installer.ts +25 -20
- package/src/integrity.ts +158 -0
- package/src/mission.ts +228 -95
- package/src/policy.ts +156 -0
- package/src/provenance.ts +116 -0
- package/src/rollback.ts +95 -0
- package/src/routing.ts +69 -0
- package/src/run.ts +2 -2
- package/src/sign.ts +61 -0
- package/src/targets/claude.ts +2 -2
- 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,10 +128,13 @@ 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(
|
|
137
|
+
const at = lstatSync(plan).mtimeMs;
|
|
134
138
|
if (at >= sessionStart)
|
|
135
139
|
return true;
|
|
136
140
|
}
|
|
@@ -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,11 +173,13 @@ 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
|
-
const at = lstatSync(
|
|
182
|
+
const at = lstatSync(plan).mtimeMs;
|
|
180
183
|
if (at >= sessionStart) return true;
|
|
181
184
|
}
|
|
182
185
|
} catch { /* unreadable — treat as untouched */ }
|
|
@@ -232,7 +235,7 @@ async function main(): Promise<void> {
|
|
|
232
235
|
// across harnesses.
|
|
233
236
|
if (planTouched() && !plannerDispatched(sessionId)) {
|
|
234
237
|
process.stderr.write(
|
|
235
|
-
'⚠ Mugiwara: a plan doc
|
|
238
|
+
'⚠ Mugiwara: a plan doc (missions/<mission>/plan.md) was written this session, ' +
|
|
236
239
|
'but no planner (nami-planner / mugiwara-planning) was dispatched or embodied. ' +
|
|
237
240
|
'Only Nami writes the plan — dispatch nami-planner, or record the plan as a ' +
|
|
238
241
|
'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.7.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/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
|
|
|
@@ -64,6 +64,3 @@ clobber each other's resume position.
|
|
|
64
64
|
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
|
-
|
|
68
|
-
`mugiwara run mission-report.sh` follows the same convention (`MEMBER` env selects a
|
|
69
|
-
team member's state).
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Prose Style — how mugiwara writes
|
|
2
|
+
|
|
3
|
+
Applies to every sentence a human reads in the output: mission reports,
|
|
4
|
+
review findings prose, PR descriptions, closure summaries, code comments.
|
|
5
|
+
Findings keep their one-line format; this governs everything around them.
|
|
6
|
+
|
|
7
|
+
## Openings
|
|
8
|
+
|
|
9
|
+
- Start with the substance — the verdict, the number, the file. Never with a
|
|
10
|
+
warm-up.
|
|
11
|
+
- Banned openers: "It's worth noting", "It should be noted", "It's important
|
|
12
|
+
to remember", "First of all", "In this report".
|
|
13
|
+
- No meta-commentary: never describe the writing ("This section will cover…")
|
|
14
|
+
— just cover it.
|
|
15
|
+
|
|
16
|
+
## Words
|
|
17
|
+
|
|
18
|
+
- Cut hedges and intensifiers: very, quite, fairly, somewhat, rather,
|
|
19
|
+
extremely. The evidence carries the weight, not the adverb.
|
|
20
|
+
- Prefer the plain verb: use over utilize/leverage, help over facilitate,
|
|
21
|
+
start over commence, show over demonstrate.
|
|
22
|
+
- No inflation adjectives: robust, seamless, comprehensive, powerful,
|
|
23
|
+
cutting-edge, state-of-the-art. If a thing is robust, say what it survives.
|
|
24
|
+
- Concrete over abstract: "3 tests fail on empty cart" beats "several issues
|
|
25
|
+
affect checkout stability".
|
|
26
|
+
|
|
27
|
+
## Sentences
|
|
28
|
+
|
|
29
|
+
- Active voice by default. Name the actor: "Zoro added pagination" not
|
|
30
|
+
"pagination was added".
|
|
31
|
+
- One idea per sentence. Vary length deliberately; never settle into
|
|
32
|
+
uniform staccato or uniform sprawl.
|
|
33
|
+
- No rhetorical setup-then-answer ("So what does this mean? It means…").
|
|
34
|
+
Ask nothing you then answer yourself.
|
|
35
|
+
|
|
36
|
+
## Structure
|
|
37
|
+
|
|
38
|
+
- Verdict first, evidence after, reasoning last. A reader who stops early
|
|
39
|
+
still has the conclusion.
|
|
40
|
+
- No binary-contrast crutch: "not just X but Y", "it's not about X, it's
|
|
41
|
+
about Y". State X and Y plainly if both matter.
|
|
42
|
+
- Lists carry items, not introductions. No "The following are some reasons
|
|
43
|
+
why:" before a list that explains itself.
|
|
44
|
+
- No dramatic fragmentation for effect. Fragments are for pace where the
|
|
45
|
+
reader already has context, not for emphasis theater.
|
|
46
|
+
|
|
47
|
+
## Honesty
|
|
48
|
+
|
|
49
|
+
- Severity words follow the rubric only — "critical" means critical, never
|
|
50
|
+
emphasis. No marketing language in reports: a fix is merged, not shipped
|
|
51
|
+
with confidence.
|
|
52
|
+
- Numbers beat adjectives everywhere both exist.
|
|
53
|
+
- Before sending user-facing prose: delete every sentence that survives its
|
|
54
|
+
own removal. If nothing changes meaning, it was filler.
|