@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.
Files changed (138) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -7
  3. package/.codex-plugin/plugin.json +2 -7
  4. package/.cursor-plugin/plugin.json +2 -7
  5. package/.kimi-plugin/plugin.json +2 -7
  6. package/.opencode/mugiwara-helpers.mjs +2 -2
  7. package/.opencode/plugins/mugiwara.mjs +3 -18
  8. package/AGENTS.md +5 -4
  9. package/GEMINI.md +3 -3
  10. package/README.md +203 -331
  11. package/content/agents/brook-healing.md +8 -4
  12. package/content/agents/chopper-checkpoint.md +7 -3
  13. package/content/agents/eval-runner.md +4 -4
  14. package/content/agents/franky-gates.md +4 -4
  15. package/content/agents/jinbe-security.md +6 -7
  16. package/content/agents/luffy-orchestrator.md +11 -13
  17. package/content/agents/memory-keeper.md +4 -4
  18. package/content/agents/nami-planner.md +4 -4
  19. package/content/agents/resume-coordinator.md +7 -7
  20. package/content/agents/robin-reviewer.md +7 -3
  21. package/content/agents/sanji-quality.md +2 -2
  22. package/content/agents/skeptic-verifier.md +2 -2
  23. package/content/agents/usopp-brainstorm.md +3 -3
  24. package/content/agents/zoro-execution.md +6 -6
  25. package/content/skills/mugiwara-backend/SKILL.md +54 -43
  26. package/content/skills/mugiwara-backend/references/database.md +61 -0
  27. package/content/skills/mugiwara-brainstorm/SKILL.md +1 -1
  28. package/content/skills/mugiwara-checkpoint/SKILL.md +23 -11
  29. package/content/skills/mugiwara-checkpoint/references/ledger-format.md +1 -1
  30. package/content/skills/mugiwara-contract-first/SKILL.md +46 -1
  31. package/content/skills/mugiwara-execution/SKILL.md +37 -37
  32. package/content/skills/mugiwara-execution/references/dispatch.md +1 -1
  33. package/content/skills/mugiwara-execution/references/execution-phase-flows.md +18 -0
  34. package/content/skills/mugiwara-execution/references/resume-batching.md +2 -2
  35. package/content/skills/mugiwara-frontend/SKILL.md +44 -43
  36. package/content/skills/mugiwara-frontend/references/checklist.md +14 -0
  37. package/content/skills/mugiwara-gates/SKILL.md +23 -14
  38. package/content/skills/mugiwara-healing/SKILL.md +26 -25
  39. package/content/skills/mugiwara-lessons/SKILL.md +2 -2
  40. package/content/skills/mugiwara-orchestration/SKILL.md +11 -11
  41. package/content/skills/mugiwara-orchestration/references/check-ins.md +2 -2
  42. package/content/skills/mugiwara-orchestration/references/closure.md +17 -12
  43. package/content/skills/mugiwara-orchestration/references/control-commands.md +14 -0
  44. package/content/skills/mugiwara-orchestration/references/triage-escalation.md +11 -1
  45. package/content/skills/mugiwara-planning/SKILL.md +26 -29
  46. package/content/skills/mugiwara-planning/references/large-campaign-subplan.md +41 -0
  47. package/content/skills/mugiwara-planning/references/plan-template.md +24 -2
  48. package/content/skills/mugiwara-quality/SKILL.md +21 -12
  49. package/content/skills/mugiwara-resume/SKILL.md +8 -3
  50. package/content/skills/mugiwara-review/SKILL.md +19 -13
  51. package/content/skills/mugiwara-security/SKILL.md +47 -36
  52. package/content/skills/mugiwara-ship/SKILL.md +1 -1
  53. package/content/skills/mugiwara-ship/references/cleanup.md +17 -16
  54. package/content/skills/mugiwara-workflow/SKILL.md +11 -17
  55. package/content/skills/mugiwara-workflow/references/adaptive-budget-governor.md +5 -0
  56. package/content/skills/mugiwara-workflow/references/benchmark-governor.md +53 -0
  57. package/content/skills/mugiwara-workflow/references/cognitive-output-governor.md +5 -0
  58. package/content/skills/mugiwara-workflow/references/large-campaign-subplan.md +29 -0
  59. package/content/skills/mugiwara-workflow/references/scope-code-governor.md +14 -0
  60. package/content/skills/mugiwara-workflow/references/stop-slop-governor.md +14 -0
  61. package/content/skills/mugiwara-workflow/references/workspace-layout.md +42 -38
  62. package/dist/mugiwara.js +1642 -528
  63. package/gemini-extension.json +1 -1
  64. package/hooks/auto-savepoint.js +18 -10
  65. package/hooks/auto-savepoint.ts +23 -23
  66. package/hooks/engagement-marker.js +1 -1
  67. package/hooks/engagement-marker.ts +1 -1
  68. package/hooks/pipeline-guard.js +17 -13
  69. package/hooks/pipeline-guard.ts +24 -20
  70. package/hooks/session-start.js +13 -10
  71. package/hooks/session-start.ts +24 -19
  72. package/package.json +2 -2
  73. package/plugin.json +1 -1
  74. package/references/complexity.md +27 -4
  75. package/references/definition-of-done.md +1 -1
  76. package/references/multi-actor.md +20 -2
  77. package/references/posture-routing.md +31 -0
  78. package/references/prose-style.md +54 -0
  79. package/scripts/benchmark-governor.ts +516 -0
  80. package/scripts/benchmark-thresholds.json +47 -0
  81. package/scripts/check-doc-links.ts +46 -0
  82. package/scripts/conformance.ts +6 -16
  83. package/scripts/coverage-gate.ts +20 -6
  84. package/scripts/gate-selftest.ts +138 -7
  85. package/scripts/lane.sh +54 -8
  86. package/scripts/lib/lane-base.sh +4 -4
  87. package/scripts/lib/patterns.sh +5 -0
  88. package/scripts/policy-force.ts +22 -0
  89. package/scripts/retrieval-eval.ts +10 -4
  90. package/scripts/savepoint.sh +155 -58
  91. package/scripts/validate-content.ts +43 -11
  92. package/scripts/verify-install.ts +9 -7
  93. package/src/adaptive-budget.ts +178 -0
  94. package/src/args.ts +4 -3
  95. package/src/budget.ts +47 -0
  96. package/src/check-artifacts.ts +45 -0
  97. package/src/cli.ts +257 -108
  98. package/src/cognition.ts +234 -0
  99. package/src/config.ts +107 -0
  100. package/src/context.ts +72 -0
  101. package/src/continue.ts +29 -10
  102. package/src/cost.ts +186 -0
  103. package/src/evidence.ts +160 -0
  104. package/src/installer.ts +25 -34
  105. package/src/integrity.ts +158 -0
  106. package/src/investigation.ts +72 -0
  107. package/src/mission.ts +341 -94
  108. package/src/policy.ts +156 -0
  109. package/src/posture.ts +86 -0
  110. package/src/provenance.ts +116 -0
  111. package/src/reporting.ts +225 -0
  112. package/src/rollback.ts +95 -0
  113. package/src/routing.ts +69 -0
  114. package/src/run.ts +2 -2
  115. package/src/scope.ts +321 -0
  116. package/src/sign.ts +235 -0
  117. package/src/slop.ts +306 -0
  118. package/src/targets/claude.ts +2 -2
  119. package/src/work.ts +273 -0
  120. package/.opencode/commands/mugiwara-execute.md +0 -13
  121. package/.opencode/commands/mugiwara-heal.md +0 -13
  122. package/.opencode/commands/mugiwara-onboard.md +0 -14
  123. package/.opencode/commands/mugiwara-plan.md +0 -17
  124. package/.opencode/commands/mugiwara-ship.md +0 -13
  125. package/.opencode/commands/using-mugiwara.md +0 -20
  126. package/content/skills/mugiwara-agent-security/SKILL.md +0 -58
  127. package/content/skills/mugiwara-agent-security/references/checklist.md +0 -20
  128. package/content/skills/mugiwara-context-budget/SKILL.md +0 -66
  129. package/content/skills/mugiwara-context-budget/references/context-budget.md +0 -37
  130. package/content/skills/mugiwara-pr/SKILL.md +0 -69
  131. package/content/skills/mugiwara-pr/references/verdict-format.md +0 -31
  132. package/content/skills/mugiwara-sunset/SKILL.md +0 -82
  133. package/content/skills/using-mugiwara/SKILL.md +0 -51
  134. package/references/token-budget.md +0 -56
  135. package/scripts/evidence.sh +0 -81
  136. package/scripts/initiative.ts +0 -296
  137. package/scripts/mission-report.sh +0 -293
  138. package/src/onboard.ts +0 -207
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mugiwara",
3
- "version": "0.6.6",
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, healing.",
5
5
  "contextFileName": "GEMINI.md"
6
6
  }
@@ -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
- for (const dir of ["plans", "spec", "logs"]) {
43
- const d = join2(cwd, ".mugiwara", dir);
44
- if (!existsSync2(d))
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(d).filter((f) => f.endsWith(".md") && f !== "lessons.md").sort().reverse();
47
- for (const f of files) {
48
- const name = f.replace(/\.md$/, "").replace(/^\d{4}-\d{2}-\d{2}-/, "");
49
- if (name && SAFE.test(name) && !/^\.+$/.test(name)) {
50
- return { mission: name, member: "", wave: "0", mode: readMode(), updated: 0 };
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", "state");
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;
@@ -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 .mugiwara/state/ and .mugiwara/continue/
6
- // stayed empty and everything reading them (resume, heal_cycle cap, lane-rise
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 wave — it never advances one. Wave transitions stay an
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. Ordered most- to least-authoritative; returns null when nothing
29
- * indicates a mission is underway, which keeps this hook silent on projects
30
- * that never engaged the crew.
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
- // A plan, spec, or decision log means Flow 0 produced artifacts but no
34
- // savepoint exactly the drift this hook exists to end.
35
- for (const dir of ['plans', 'spec', 'logs']) {
36
- const d = join(cwd, '.mugiwara', dir);
37
- if (!existsSync(d)) continue;
38
- const files = readdirSync(d)
39
- .filter((f) => f.endsWith('.md') && f !== 'lessons.md')
40
- .sort()
41
- .reverse();
42
- for (const f of files) {
43
- // strip the YYYY-MM-DD- prefix the crew writes; what remains is the mission
44
- const name = f.replace(/\.md$/, '').replace(/^\d{4}-\d{2}-\d{2}-/, '');
45
- if (name && SAFE.test(name) && !/^\.+$/.test(name)) {
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', 'state');
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", "state");
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', 'state');
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
@@ -29,7 +29,7 @@ function readEnforce() {
29
29
  return "block";
30
30
  }
31
31
  function engaged(sessionId) {
32
- const file = join(cwd, ".mugiwara", "state", ".engaged");
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", "state");
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
- if (!f.endsWith(".json"))
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", "state", ".engaged");
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", "state", ".engaged");
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 plansDir = join(cwd, ".mugiwara", "plans");
117
- if (!existsSync(plansDir))
117
+ const missionsDir = join(cwd, ".mugiwara", "missions");
118
+ if (!existsSync(missionsDir))
118
119
  return false;
119
- const markerFile = join(cwd, ".mugiwara", "state", ".engaged");
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(plansDir)) {
131
- if (!e.endsWith(".md"))
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(join(plansDir, e)).mtimeMs;
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 under .mugiwara/plans/ 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.
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;
@@ -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', 'state', '.engaged');
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', 'state');
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
- if (!f.endsWith('.json')) continue;
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', 'state', '.engaged');
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', 'state', '.engaged');
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 under .mugiwara/plans/ was created or modified in this session.
154
- * .mugiwara/ is gitignored, so git status cannot see plan files — read the
155
- * filesystem directly and compare mtimes against the session's first-seen
156
- * marker. A plan written after the session engaged counts as "touched this
157
- * session". Only .mugiwara/plans/*.md counts — the plan doc is the artifact
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 plansDir = join(cwd, '.mugiwara', 'plans');
162
- if (!existsSync(plansDir)) return false;
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', 'state', '.engaged');
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(plansDir)) {
176
- if (!e.endsWith('.md')) continue;
177
- // lstat, not stat: a symlinked plan pointing outside .mugiwara/plans/
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(join(plansDir, e)).mtimeMs;
180
- if (at >= sessionStart) return true;
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 under .mugiwara/plans/ was written this session, ' +
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',
@@ -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 continueRoot = join(cwd, ".mugiwara", "continue");
53
+ var missionsRoot = join(cwd, ".mugiwara", "missions");
54
54
  var active = [];
55
- if (existsSync(continueRoot)) {
56
- const missions = readdirSync(continueRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && isSafeKey(e.name)).map((e) => e.name);
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(continueRoot, mission);
59
- const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
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
- console.log(JSON.stringify({
103
- additionalContext: "Mugiwara crew active by default. Say \\`mugiwara off\\` for a request and the crew stands down (Luffy acknowledges, records it in the decision log). Before ANY task \u2014 load \\`mugiwara-orchestration\\` skill as gatekeeper. NEVER execute, answer, or make changes without Flow 0 triage. Classification overhead <15 seconds \u2014 cheaper than an incorrect fix. Lane 0 for trivial work (single-file/<20 LOC) skips pipeline; Lane 1+ follows full pipeline. Mode: guided / semi / auto (see .mugiwara/config). Switch with \\`/mugiwara <mode>\\` \u2014 applies from the next flow stage. Every flow stage opens with a banner \\`===== \u2694\uFE0F FLOW N \u2014 CREW (ROLE) =====\\` and closes with a handoff \\`\u2192 Flow N+1 \u2014 Crew (Role)\\`; Zoro shows per-task progress \\`[task N/M]\\` with each task's evidence. See skills/mugiwara-workflow." + (resumeContext ? `
104
-
105
- ` + resumeContext : "")
106
- }));
107
+ if (resumeContext) {
108
+ console.log(JSON.stringify({ additionalContext: resumeContext }));
109
+ }
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env bun
2
- // hooks/session-start.ts — SessionStart hook: reminds the agent the crew is
3
- // available, and surfaces in-flight missions for the current git actor from
4
- // the machine-written continue JSON (D10). Listing in-flight work runs in
5
- // every mode; auto-resume is auto-only. Never auto-resumes when multiple are
6
- // in-flight ambiguous resumes are listed, not guessed.
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 continueRoot = join(cwd, '.mugiwara', 'continue');
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(continueRoot)) {
70
- // continue/<mission>/*.json scan every mission folder
71
- const missions = readdirSync(continueRoot, { withFileTypes: true })
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(continueRoot, mission);
76
- const files = readdirSync(dir).filter((f) => f.endsWith('.json'));
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 (state.json) belongs to whoever
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
- console.log(
126
- JSON.stringify({
127
- additionalContext:
128
- "Mugiwara crew active by default. Say \\`mugiwara off\\` for a request and the crew stands down (Luffy acknowledges, records it in the decision log). Before ANY task — load \\`mugiwara-orchestration\\` skill as gatekeeper. NEVER execute, answer, or make changes without Flow 0 triage. Classification overhead <15 seconds — cheaper than an incorrect fix. Lane 0 for trivial work (single-file/<20 LOC) skips pipeline; Lane 1+ follows full pipeline. Mode: guided / semi / auto (see .mugiwara/config). Switch with \\`/mugiwara <mode>\\` — applies from the next flow stage. Every flow stage opens with a banner \\`===== ⚔️ FLOW N — CREW (ROLE) =====\\` and closes with a handoff \\`→ Flow N+1 — Crew (Role)\\`; Zoro shows per-task progress \\`[task N/M]\\` with each task's evidence. See skills/mugiwara-workflow." +
129
- (resumeContext ? "\n\n" + resumeContext : "")
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.6.6",
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mugiwara",
3
3
  "description": "The Straw Hat crew of AI agents and skills: brainstorm, plan, execute, checkpoint, quality, gates, review, security, healing.",
4
- "version": "0.6.6",
4
+ "version": "0.8.0",
5
5
  "author": {
6
6
  "name": "ionivetech"
7
7
  },
@@ -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 (secondary)
43
+ ## Cognitive complexity — understandability (Sonar)
44
44
 
45
- Cyclomatic is the gate; cognitive complexity (nesting depth) supplements it.
46
- Flag functions nested >4 levels deep even when cyclomatic is low — deep
47
- nesting is a readability problem a branch count misses.
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/issues/YYYY-MM-DD-<mission>-blockers.md` has zero open rows. |
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
- `mugiwara run mission-report.sh` follows the same convention (`MEMBER` env selects a
69
- team member's state).
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.