@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,15 +1,27 @@
1
1
  #!/usr/bin/env bash
2
- # scripts/savepoint.sh — write .mugiwara/state/<mission>/<member>.json at every
3
- # wave boundary. Computed from git + file counts; zero model judgement.
2
+ # scripts/savepoint.sh — write .mugiwara/missions/<mission>/<member>.json at every
3
+ # flow-stage boundary. Computed from git + file counts; zero model judgement.
4
4
  # Identity is (mission, member), never branch. Solo = member empty → the file
5
5
  # is named state.json. Usage:
6
- # savepoint.sh <mission> [member] [wave] [mode] [lane]
6
+ # savepoint.sh <mission> [member] [flow] [mode] [lane]
7
7
  set -u
8
8
 
9
9
  die() { echo "savepoint: $*" >&2; exit 1; }
10
10
 
11
11
  MUGIWARA_DIR="${MUGIWARA_DIR:-.mugiwara}"
12
12
 
13
+ # optional provider-reported tokens file (T4): --tokens-file <path> JSON {input_tokens, output_tokens}
14
+ TOKENS_FILE=""
15
+ while [ $# -gt 0 ]; do
16
+ case "$1" in
17
+ --tokens-file) TOKENS_FILE="${2:-}"; [ -n "$TOKENS_FILE" ] || die "--tokens-file requires a path"; shift 2 ;;
18
+ --tokens-file=*) TOKENS_FILE="${1#*=}"; shift ;;
19
+ --) shift; break ;;
20
+ -*) die "unknown flag \"$1\"" ;;
21
+ *) break ;;
22
+ esac
23
+ done
24
+
13
25
  # shared path patterns — single source of truth (D3)
14
26
  # shellcheck source=scripts/lib/patterns.sh
15
27
  source "$(dirname "$0")/lib/patterns.sh"
@@ -99,6 +111,12 @@ esac
99
111
  [ "$DELEGATE_THRESHOLD" -gt 100 ] 2>/dev/null && DELEGATE_THRESHOLD=100
100
112
  ACTOR="${STATE_ACTOR:-${GIT_AUTHOR_NAME:-${GIT_ID:-${USER:-${USERNAME:-}}}}}"
101
113
  BRANCH="$(git branch --show-current 2>/dev/null || echo 'unknown')"
114
+ # Per-stage model attribution (A4): record which model produced THIS stage.
115
+ # MUGIWARA_MODEL wins over ANTHROPIC_MODEL; 'unknown' beats a lie. Closure
116
+ # renders the unique set across stage files so mid-mission switches stay
117
+ # visible in provenance instead of every line attributing to the last value.
118
+ MODEL="${MUGIWARA_MODEL:-${ANTHROPIC_MODEL:-}}"
119
+ [ -n "$MODEL" ] || MODEL="unknown"
102
120
 
103
121
  # treat the legacy empty-actor placeholder '""' as "no member" (solo). The old
104
122
  # interface used '""' for the actor slot; the new (mission, member) interface
@@ -123,6 +141,12 @@ esac
123
141
  if [[ "$MEMBER" =~ ^\.+$ ]]; then
124
142
  die "invalid member name \"$MEMBER\" (allowlist: [a-zA-Z0-9._-], not a dot-path)"
125
143
  fi
144
+ # reserved stems — <member>.json and continue-<member>.json share the mission
145
+ # dir with state.json / continue.json; member "state" or "continue" would
146
+ # overwrite the solo files (state pointer or resume pointer).
147
+ case "$MEMBER" in
148
+ state|continue) die "reserved member name \"$MEMBER\" (state, continue)" ;;
149
+ esac
126
150
 
127
151
  # BRANCH_SLUG sanitized to [A-Za-z0-9._-] — it feeds the continue branch field;
128
152
  # a newline/control char in BRANCH would corrupt the JSON (F5). Dropping illegal
@@ -130,15 +154,16 @@ fi
130
154
  # (BSD/macOS-safe: no \+ BRE).
131
155
  BRANCH_SLUG=$(echo "$BRANCH" | tr '/' '-' | tr -cd 'A-Za-z0-9._-' | sed 's/^\.\{1,\}$//' )
132
156
 
133
- # state + continue live per (mission, member). Solo (member empty) → state.json.
134
- STATE_DIR="$MUGIWARA_DIR/state/$MISSION"
135
- CONTINUE_DIR="$MUGIWARA_DIR/continue/$MISSION"
157
+ # state + continue live in the mission dir. Solo (member empty) → state.json
158
+ # + continue.json; team writes <member>.json + continue-<member>.json so
159
+ # parallel members never clobber each other.
160
+ MISSION_DIR="$MUGIWARA_DIR/missions/$MISSION"
136
161
  if [ -n "$MEMBER" ]; then
137
- STATE_FILE="$STATE_DIR/$MEMBER.json"
138
- CONTINUE_FILE="$CONTINUE_DIR/$MEMBER.json"
162
+ STATE_FILE="$MISSION_DIR/$MEMBER.json"
163
+ CONTINUE_FILE="$MISSION_DIR/continue-$MEMBER.json"
139
164
  else
140
- STATE_FILE="$STATE_DIR/state.json"
141
- CONTINUE_FILE="$CONTINUE_DIR/state.json"
165
+ STATE_FILE="$MISSION_DIR/state.json"
166
+ CONTINUE_FILE="$MISSION_DIR/continue.json"
142
167
  fi
143
168
 
144
169
  [ -z "$MISSION" ] && die "usage: savepoint.sh <mission> [member] [wave] [mode] [lane]"
@@ -148,21 +173,46 @@ fi
148
173
  BASE_SHA=$(git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null || git merge-base HEAD "$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')" 2>/dev/null || git rev-parse HEAD~1 2>/dev/null || echo "unknown")
149
174
  HEAD_SHA=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
150
175
 
176
+ # lane_scope_glob (T5): monorepo scoping — count only files matching the glob
177
+ LANE_SCOPE_GLOB=""
178
+ if [ -f "$MUGIWARA_DIR/config" ]; then
179
+ _cfg_scope=$(grep -E '^lane_scope_glob=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr -d '"' | tr -d "'")
180
+ [ -n "$_cfg_scope" ] && LANE_SCOPE_GLOB="$_cfg_scope"
181
+ fi
151
182
  # union of committed + staged + unstaged + untracked (F) — see patterns.sh
152
183
  CHANGED_FILES=$(changed_files "$BASE_SHA")
153
- FILES_TOUCHED=$( [ -n "$CHANGED_FILES" ] && echo "$CHANGED_FILES" | wc -l | tr -d ' ' || echo 0 )
154
-
155
- read -r LOC_INS LOC_DEL <<EOF
184
+ # sensitive-path escalation always uses unfiltered set (safety never shrinks, T5)
185
+ SENSITIVE_PATHS=$(echo "$CHANGED_FILES" | grep -E "$SENSITIVE_PATS" 2>/dev/null | tr '\n' ',' | sed 's/,$//' || true)
186
+ # scoped counting (T5)
187
+ SCOPED_FILES="$CHANGED_FILES"
188
+ if [ -n "$LANE_SCOPE_GLOB" ] && [ -n "$CHANGED_FILES" ]; then
189
+ shopt -s extglob globstar 2>/dev/null || true
190
+ SCOPED_FILES=""
191
+ while IFS= read -r _sf; do
192
+ [ -z "$_sf" ] && continue
193
+ # shellcheck disable=SC2053
194
+ if [[ "$_sf" == $LANE_SCOPE_GLOB ]]; then
195
+ SCOPED_FILES="${SCOPED_FILES}${SCOPED_FILES:+
196
+ }$_sf"
197
+ fi
198
+ done <<< "$CHANGED_FILES"
199
+ fi
200
+ FILES_TOUCHED=$( [ -n "$SCOPED_FILES" ] && echo "$SCOPED_FILES" | wc -l | tr -d ' ' || echo 0 )
201
+ if [ -n "$LANE_SCOPE_GLOB" ]; then
202
+ read -r LOC_INS LOC_DEL <<EOF
203
+ $( { echo "$SCOPED_FILES" | while IFS= read -r _lf; do [ -n "$_lf" ] && git diff --numstat "$BASE_SHA"..HEAD -- "$_lf" 2>/dev/null; git diff --numstat HEAD -- "$_lf" 2>/dev/null; done; echo "$SCOPED_FILES" | while IFS= read -r _uf; do [ -z "$_uf" ] && continue; if git ls-files --others --exclude-standard -- "$_uf" 2>/dev/null | grep -qx "$_uf"; then printf '%s\t0\t%s\n' "$(wc -l < "$_uf" 2>/dev/null | tr -d ' ' || echo 0)" "$_uf"; fi; done; } | awk '{ if ($1 ~ /^[0-9]+$/) i+=$1; if ($2 ~ /^[0-9]+$/) d+=$2 } END { print (i+0)" "(d+0) }')
204
+ EOF
205
+ else
206
+ read -r LOC_INS LOC_DEL <<EOF
156
207
  $(changed_loc "$BASE_SHA")
157
208
  EOF
209
+ fi
158
210
  LOC_INS=$(( ${LOC_INS:-0} + 0 ))
159
211
  LOC_DEL=$(( ${LOC_DEL:-0} + 0 ))
160
212
  LOC_DELTA=$(( LOC_INS - LOC_DEL ))
161
213
  # churn is insertion+deletion — refactors and deletions are work too (D4)
162
214
  LOC_CHURN=$(( LOC_INS + LOC_DEL ))
163
215
 
164
- SENSITIVE_PATHS=$(echo "$CHANGED_FILES" | grep -E "$SENSITIVE_PATS" 2>/dev/null | tr '\n' ',' | sed 's/,$//' || true)
165
-
166
216
  LANE="direct"
167
217
  LANE_REASON=""
168
218
  # Sensitive-path escalation is unconditional — it wins over any count-based
@@ -196,9 +246,10 @@ fi
196
246
 
197
247
  # path-weighted sizing (mirrors lane.sh): docs-only changes outside the product
198
248
  # surface never escalate to full from file count alone; sensitive-path
199
- # escalation above still wins.
200
- if [ "$LANE" = "full" ] && [ -z "$SENSITIVE_PATHS" ] && [ -n "$CHANGED_FILES" ]; then
201
- CODE_COUNT=$(echo "$CHANGED_FILES" | grep -E "$PRODUCT_PAT" 2>/dev/null | grep -c . || true)
249
+ # escalation above still wins. With lane_scope_glob the check applies to the
250
+ # scoped set but sensitive wins unfiltered above, so safety never shrinks.
251
+ if [ "$LANE" = "full" ] && [ -z "$SENSITIVE_PATHS" ] && [ -n "$SCOPED_FILES" ]; then
252
+ CODE_COUNT=$(echo "$SCOPED_FILES" | grep -E "$PRODUCT_PAT" 2>/dev/null | grep -c . || true)
202
253
  if [ -z "$CODE_COUNT" ] || [ "$CODE_COUNT" -eq 0 ] 2>/dev/null; then
203
254
  PREV="$LANE"
204
255
  LANE="standard"
@@ -206,6 +257,20 @@ if [ "$LANE" = "full" ] && [ -z "$SENSITIVE_PATHS" ] && [ -n "$CHANGED_FILES" ];
206
257
  fi
207
258
  fi
208
259
 
260
+ # policy as code: mugiwara.policy.yml lanes.force_full raises the
261
+ # lane to full — upward only. Optional: no file or no bun = no-op.
262
+ if command -v bun >/dev/null 2>&1 && { [ -f mugiwara.policy.yml ] || [ -f mugiwara.policy.yaml ]; }; then
263
+ POLICY_HITS=$(printf '%s\n' "$CHANGED_FILES" | bun "$(dirname "$0")/policy-force.ts")
264
+ RC=$?
265
+ if [ "$RC" -ne 0 ]; then
266
+ die "mugiwara.policy.yml is invalid — fix or remove it (fail-closed)"
267
+ fi
268
+ if [ -n "$POLICY_HITS" ]; then
269
+ LANE="full"
270
+ LANE_REASON="policy force_full ($POLICY_HITS)"
271
+ fi
272
+ fi
273
+
209
274
  # explicit triage lane (M7) applied as a floor — the computed lane above may
210
275
  # raise it, never lower it. Sensitive-path escalation still wins
211
276
  # unconditionally: spike is a resize, not a rise, so it never displaces the
@@ -255,9 +320,8 @@ if [ -n "$LANE_PREV" ] && [ "$LANE_PREV" != "$LANE" ]; then
255
320
  esac
256
321
  fi
257
322
 
258
- # task counts from plan doc — plan is written date-prefixed (plans/YYYY-MM-DD-<mission>.md)
259
- # or bare (plans/<mission>.md); glob both, first match wins.
260
- PLAN_FILE=$(ls "$MUGIWARA_DIR"/plans/${MISSION}.md "$MUGIWARA_DIR"/plans/*-${MISSION}.md 2>/dev/null | head -1 || true)
323
+ # task counts from plan doc — bare name, one file per mission.
324
+ PLAN_FILE="$MISSION_DIR/plan.md"
261
325
  TASKS_DONE=0
262
326
  TASKS_TOTAL=0
263
327
  if [ -n "$PLAN_FILE" ] && [ -f "$PLAN_FILE" ]; then
@@ -269,11 +333,7 @@ if [ -n "$PLAN_FILE" ] && [ -f "$PLAN_FILE" ]; then
269
333
  fi
270
334
 
271
335
  # blocker count
272
- # The crew writes the DATED name (`YYYY-MM-DD-<mission>-blockers.md`) — that is
273
- # what 15 prose sites mandate and what lands on disk. Matching only the bare
274
- # name made blockers_open structurally 0 forever, and that zero feeds the DoD
275
- # ship-readiness axis. Accept both; newest wins.
276
- BLOCKERS_FILE=$(ls -t "$MUGIWARA_DIR/issues/${MISSION}-blockers.md" "$MUGIWARA_DIR"/????-??-??-"${MISSION}"-blockers.md "$MUGIWARA_DIR/issues"/????-??-??-"${MISSION}"-blockers.md 2>/dev/null | head -1 || true)
336
+ BLOCKERS_FILE="$MISSION_DIR/blockers.md"
277
337
  BLOCKERS_OPEN=0
278
338
  if [ -n "$BLOCKERS_FILE" ] && [ -f "$BLOCKERS_FILE" ]; then
279
339
  # data rows start with a wave number; header "| wave |" and separator
@@ -281,27 +341,18 @@ if [ -n "$BLOCKERS_FILE" ] && [ -f "$BLOCKERS_FILE" ]; then
281
341
  BLOCKERS_OPEN=$(grep -cE '^\| ?[0-9]+ ?\|' "$BLOCKERS_FILE" 2>/dev/null || true)
282
342
  fi
283
343
 
284
- # heal cycle — count Flow-8 (healing) section headings in the DECISION LOG,
285
- # which every mission writes (luffy-orchestrator rule 10). Not the word "heal"
286
- # anywhere (heal workers/healing text would inflate the counter and cause a
287
- # premature halt). Each heal cycle is logged as a "## Flow 8 healing" section;
288
- # the [^0-9a-z] guard keeps adjacent "## Flow 8b"-style sections from counting.
289
- # Aggregate across ALL dated logs (a multi-day mission spans several files) —
290
- # reading only the newest would undercount earlier days' heal cycles.
344
+ # heal cycle — count Flow-8 (healing) section headings in the DECISION LOG
345
+ # (missions/<mission>/decisions.md), which every mission writes (luffy-
346
+ # orchestrator rule 10). Not the word "heal" anywhere (heal workers/healing
347
+ # text would inflate the counter and cause a premature halt). Each heal cycle
348
+ # is logged as a "## Flow 8 — healing" section; the [^0-9a-z] guard keeps
349
+ # adjacent "## Flow 8b"-style sections from counting.
291
350
  HEAL_CYCLE=1
292
351
  HEAL_COUNT=0
293
- # Cap the date prefix to today: a future-dated log (9999-…) must not inflate
294
- # the heal counter and trigger a premature halt.
295
- TODAY=$(date +%Y-%m-%d)
296
- for LOG_FILE in "$MUGIWARA_DIR"/????-??-??-"${MISSION}".md "$MUGIWARA_DIR/logs"/????-??-??-"${MISSION}".md; do
297
- [ -f "$LOG_FILE" ] || continue
298
- LOG_DATE=$(basename "$LOG_FILE" | cut -c1-10)
299
- case "$LOG_DATE" in
300
- ''|*[!0-9-]*) continue ;;
301
- esac
302
- if [ "$LOG_DATE" \> "$TODAY" ]; then continue; fi
303
- HEAL_COUNT=$((HEAL_COUNT + $(grep -ciE '^## flow 8([^0-9a-z]|$)' "$LOG_FILE" 2>/dev/null || true)))
304
- done
352
+ LOG_FILE="$MISSION_DIR/decisions.md"
353
+ if [ -f "$LOG_FILE" ]; then
354
+ HEAL_COUNT=$(grep -ciE '^## flow 8([^0-9a-z]|$)' "$LOG_FILE" 2>/dev/null || true)
355
+ fi
305
356
  if [ "$HEAL_COUNT" -gt 0 ]; then
306
357
  HEAL_CYCLE=$((HEAL_COUNT + 1))
307
358
  fi
@@ -314,12 +365,34 @@ if [ "$HEAL_CYCLE" -ge "$HEAL_MAX_CYCLES" ] 2>/dev/null; then
314
365
  HEAL_HALT=true
315
366
  fi
316
367
 
317
- # evidence pathsper-mission results folder (quoted printf, no sed mission
318
- # name is allowlisted above, but avoid sed metacharacter semantics entirely)
368
+ # depth flagsadvisory measured (roadmap v0.8 item 4). Read from config
369
+ # like the other keys; computed into state.json so enforcement is a fact the
370
+ # gates flow stage can read, not prose.
371
+ DEPTH_REVIEW="full"; DEPTH_QUALITY="full"; DEPTH_VERIFY="off"
372
+ if [ -f "$MUGIWARA_DIR/config" ]; then
373
+ _cfg_r=$(grep -E '^review_depth=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
374
+ [ -n "$_cfg_r" ] && DEPTH_REVIEW="$_cfg_r"
375
+ _cfg_q=$(grep -E '^quality_depth=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
376
+ [ -n "$_cfg_q" ] && DEPTH_QUALITY="$_cfg_q"
377
+ _cfg_v=$(grep -E '^verify_merged=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
378
+ [ -n "$_cfg_v" ] && DEPTH_VERIFY="$_cfg_v"
379
+ fi
380
+ case "$DEPTH_REVIEW" in full|standard|lean) ;; *) DEPTH_REVIEW="full" ;; esac
381
+ case "$DEPTH_QUALITY" in full|standard|lean) ;; *) DEPTH_QUALITY="full" ;; esac
382
+ case "$DEPTH_VERIFY" in on|off) ;; *) DEPTH_VERIFY="off" ;; esac
383
+
384
+ # evidence paths — the mission's flow folder (quoted printf, no sed — mission
385
+ # name is allowlisted above, but avoid sed metacharacter semantics entirely).
386
+ # New missions use flows/; a legacy mission that already keeps waves/ stays on
387
+ # waves/ so an in-flight trail never splits across two directories.
388
+ FLOWDIR="flows"
389
+ if [ -d "$MISSION_DIR/waves" ] && [ ! -d "$MISSION_DIR/flows" ]; then
390
+ FLOWDIR="waves"
391
+ fi
319
392
  EVIDENCE=""
320
- for evf in "$MUGIWARA_DIR/results/${MISSION}/"*.md; do
393
+ for evf in "$MISSION_DIR/$FLOWDIR/"*.md; do
321
394
  [ -f "$evf" ] || continue
322
- EVIDENCE="${EVIDENCE}${EVIDENCE:+,}.mugiwara/results/${MISSION}/$(basename "$evf")"
395
+ EVIDENCE="${EVIDENCE}${EVIDENCE:+,}.mugiwara/missions/${MISSION}/${FLOWDIR}/$(basename "$evf")"
323
396
  done
324
397
 
325
398
  # skill version — MUGIWARA's own package.json, resolved from the script
@@ -340,7 +413,21 @@ fi
340
413
  # real usage. Monotonic beats precise — LANE_BASE stands in for the skills
341
414
  # loaded this lane (measured from content, validated by scripts/lane-base.ts);
342
415
  # loc_churn and written-artifact words scale with growth.
416
+ # auto-detect harness for token reporting (Tier-1: Claude Code / opencode)
417
+ # If --tokens-file not given, try env/file fallbacks so estimator is last resort.
418
+ if [ -z "$TOKENS_FILE" ]; then
419
+ for _cand in "${MUGIWARA_TOKENS_FILE:-}" "${CLAUDE_TOKENS_FILE:-}" "${OPENCODE_TOKENS_FILE:-}" "/tmp/mugiwara-tokens.json" "$HOME/.cache/mugiwara/tokens.json" ".mugiwara/tokens.json"; do
420
+ [ -n "$_cand" ] && [ -f "$_cand" ] && TOKENS_FILE="$_cand" && break
421
+ done
422
+ fi
423
+ # detect harness (for logging / future tier checks)
424
+ HARNESS="unknown"
425
+ if [ -n "${CLAUDECODE:-}" ] || [ -n "${CLAUDE_CODE_ENTRYPOINT:-}" ] || echo "${ANTHROPIC_MODEL:-}" | grep -qi claude 2>/dev/null; then HARNESS="claude";
426
+ elif [ -n "${OPENCODE:-}" ] || [ -f ".opencode/config.json" ] || [ -n "${OPENCODE_TOKENS_FILE:-}" ]; then HARNESS="opencode";
427
+ elif [ -n "${CURSOR:-}" ] || [ -n "${VSCODE_GIT_ASKPASS_NODE:-}" ]; then HARNESS="cursor/vscode"; fi
428
+
343
429
  # MUGIWARA_TOKENS overrides as the reported value.
430
+ # --tokens-file (T4) is the first-class provider-reported path: JSON {input_tokens, output_tokens}
344
431
  LANE_BASE=0
345
432
  case "$LANE" in
346
433
  lean) LANE_BASE=$LANE_BASE_lean ;;
@@ -348,14 +435,19 @@ case "$LANE" in
348
435
  full) LANE_BASE=$LANE_BASE_full ;;
349
436
  spike) LANE_BASE=$LANE_BASE_spike ;;
350
437
  esac
351
- DOC_WORDS=$(cat "$MUGIWARA_DIR"/results/${MISSION}/*.md "$MUGIWARA_DIR"/plans/${MISSION}.md "$MUGIWARA_DIR"/plans/*-${MISSION}.md "$MUGIWARA_DIR"/spec/${MISSION}.md "$MUGIWARA_DIR"/spec/*-${MISSION}.md "$MUGIWARA_DIR"/logs/${MISSION}.md "$MUGIWARA_DIR"/logs/*-${MISSION}.md 2>/dev/null | wc -w | tr -d ' ')
438
+ DOC_WORDS=$(cat "$MISSION_DIR"/"$FLOWDIR"/*.md "$MISSION_DIR"/plan.md "$MISSION_DIR"/spec.md "$MISSION_DIR"/decisions.md 2>/dev/null | wc -w | tr -d ' ')
352
439
  LOC_TOKENS=$(( LOC_CHURN * 12 ))
353
- TOKENS_SOURCE="computed"
440
+ TOKENS_SOURCE="estimator"
354
441
  TOKENS_EST=$(( LANE_BASE + DOC_WORDS * 135 / 100 + LOC_TOKENS ))
355
442
  if [ -n "${MUGIWARA_TOKENS:-}" ]; then
356
443
  TOKENS_EST="${MUGIWARA_TOKENS}"
357
444
  TOKENS_SOURCE="reported"
358
445
  fi
446
+ if [ -n "$TOKENS_FILE" ]; then
447
+ [ -f "$TOKENS_FILE" ] || die "tokens file not found: $TOKENS_FILE"
448
+ TOKENS_EST=$(node -e "try{const j=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));const i=Number(j.input_tokens)||0;const o=Number(j.output_tokens)||0;console.log(i+o)}catch(e){console.log(0)}" "$TOKENS_FILE" 2>/dev/null || echo 0)
449
+ TOKENS_SOURCE="reported"
450
+ fi
359
451
 
360
452
  # budget per lane
361
453
  BUDGET=0
@@ -390,7 +482,7 @@ if [ "$BUDGET" -gt 0 ] 2>/dev/null; then
390
482
  fi
391
483
  fi
392
484
 
393
- mkdir -p "$MUGIWARA_DIR/state/$MISSION" "$MUGIWARA_DIR/continue/$MISSION"
485
+ mkdir -p "$MISSION_DIR"
394
486
 
395
487
  node -e "
396
488
  const data = {
@@ -406,6 +498,7 @@ const data = {
406
498
  flow: parseInt(process.argv[6], 10),
407
499
  mode: process.argv[7],
408
500
  verbosity: process.argv[32] || 'normal',
501
+ model: process.argv[37] || 'unknown',
409
502
  base_sha: process.argv[8],
410
503
  head_sha: process.argv[9],
411
504
  files_touched: parseInt(process.argv[10], 10),
@@ -421,6 +514,9 @@ const data = {
421
514
  heal_halt: process.argv[34] === 'true',
422
515
  delegate_threshold: parseInt(process.argv[35], 10) || 60,
423
516
  delegate_due: process.argv[36] === 'true',
517
+ review_depth: process.argv[38] || 'full',
518
+ quality_depth: process.argv[39] || 'full',
519
+ verify_merged: process.argv[40] || 'off',
424
520
  tokens_est: parseInt(process.argv[17], 10) || 0,
425
521
  tokens_source: process.argv[26] || 'computed',
426
522
  budget: parseInt(process.argv[18], 10) || 0,
@@ -439,20 +535,21 @@ require('fs').writeFileSync(process.argv[23], JSON.stringify(data, null, 2) + '\
439
535
  "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
440
536
  "$STATE_FILE" "$LANE_PREV" "$LANE_ROSE" "$TOKENS_SOURCE" "$LANE_PEAK" \
441
537
  "$LOC_INS" "$LOC_DEL" "$LOC_CHURN" "$MEMBER" "$VERBOSITY" \
442
- "$HEAL_MAX_CYCLES" "$HEAL_HALT" "$DELEGATE_THRESHOLD" "$DELEGATE_DUE"
538
+ "$HEAL_MAX_CYCLES" "$HEAL_HALT" "$DELEGATE_THRESHOLD" "$DELEGATE_DUE" \
539
+ "$MODEL" "$DEPTH_REVIEW" "$DEPTH_QUALITY" "$DEPTH_VERIFY"
443
540
 
444
541
  if [ "$LANE_ROSE" = true ]; then
445
542
  echo "⚠ LANE ROSE: $LANE_PREV → $LANE ($LANE_REASON) — escalate per check-in protocol"
446
543
  fi
447
544
 
448
- # --- continue/<mission>/<member>.json (D10): machine-written resume point ---
545
+ # --- continue.json (D10): machine-written resume point ---
449
546
  # Written alongside state.json so an interrupted session (step-limit truncation,
450
547
  # crash, new session) can resume without human recall. Same trust as state.json:
451
548
  # computed fields, never model judgement. The resume skill treats it as data to
452
549
  # verify against the plan/todos, never verbatim instructions.
453
- # Scoped by (mission, member) like state.json — solo writes state.json, team
454
- # writes <member>.json, so parallel members never clobber each other. The
455
- # crew-written next_session_prompt line is preserved across savepoints.
550
+ # Scoped by (mission, member) like state.json — solo writes continue.json, team
551
+ # writes continue-<member>.json. The crew-written next_session_prompt line is
552
+ # preserved across savepoints.
456
553
  if [ -n "$MISSION" ]; then
457
554
  NEXT_SESSION_PROMPT=""
458
555
  if [ -f "$CONTINUE_FILE" ]; then
@@ -491,4 +588,4 @@ if [ -n "$MISSION" ]; then
491
588
  "$CONTINUE_FILE" "$CONTINUE_FILE" "$ACTOR"
492
589
  fi
493
590
 
494
- echo "✓ savepoint written: $STATE_FILE (lane=$LANE, wave=$WAVE, files=$FILES_TOUCHED)"
591
+ echo "✓ savepoint written: $STATE_FILE (lane=$LANE, flow=$WAVE, files=$FILES_TOUCHED)"
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  // scripts/validate-content.ts
3
3
  import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
4
- import { join, basename } from 'node:path';
4
+ import { join, basename, dirname } from 'node:path';
5
5
  import { parseFrontmatter } from '../src/frontmatter.ts';
6
6
 
7
7
  const root = join(import.meta.dirname, '..', 'content');
@@ -28,6 +28,25 @@ function checkFile(file: string, wantName: string, kind: 'skill' | 'agent'): Rec
28
28
  if (bullets.length > 4) errors.push(`skill ${file}: "## Skip when" block exceeds 4 bullets`);
29
29
  }
30
30
  if (kind === 'skill' && !body.includes('## Red flags')) errors.push(`skill ${file}: missing required "## Red flags" block`);
31
+ if (kind === 'skill') {
32
+ // gate_artifact (roadmap item 2): a declared artifact must name a
33
+ // verifiable path (flows/ mission evidence, plan.md, or a references/
34
+ // file that must actually exist beside the skill).
35
+ const ga = (data as Record<string, unknown>).gate_artifact;
36
+ if (ga !== undefined) {
37
+ const v = String(ga).trim();
38
+ if (!v) errors.push(`skill ${file}: gate_artifact declared but empty`);
39
+ else if (!/(flows\/|plan\.md|references\/)/.test(v)) {
40
+ errors.push(`skill ${file}: gate_artifact "${v}" must name a flows/, plan.md, or references/ path`);
41
+ } else {
42
+ const refMatch = v.match(/(references\/[\w./-]+\.md)/);
43
+ if (refMatch) {
44
+ const refPath = join(dirname(file), refMatch[1]);
45
+ if (!existsSync(refPath)) errors.push(`skill ${file}: gate_artifact references "${refMatch[1]}" but file does not exist`);
46
+ }
47
+ }
48
+ }
49
+ }
31
50
  if (kind === 'skill') {
32
51
  const lines = body.split(/\r?\n/);
33
52
  const headingRe = /^## /;
@@ -286,6 +305,25 @@ if (totalDescChars > INDEX_BUDGET) {
286
305
  console.log(`✓ index budget: ${totalDescChars}/${INDEX_BUDGET} chars`);
287
306
  }
288
307
 
308
+ // The measured index size is published in docs/concepts/cost.md. A measured
309
+ // number in prose that drifted from reality is a defect (it survived one
310
+ // release: cost.md claimed 5,437 chars after the prune measured 4,688).
311
+ // Gate the doc against the measurement so the class stays closed.
312
+ const costDoc = join(root, '..', 'docs', 'concepts', 'cost.md');
313
+ if (existsSync(costDoc)) {
314
+ const m = readFileSync(costDoc, 'utf8').match(/\*\*Current:\*\* (\d[\d,]*) chars/);
315
+ if (!m) {
316
+ errors.push('docs/concepts/cost.md: missing "**Current:** <n> chars" line for the measured index size');
317
+ } else {
318
+ const stated = parseInt(m[1].replace(/,/g, ''), 10);
319
+ if (stated !== totalDescChars) {
320
+ errors.push(`docs/concepts/cost.md states ${stated} index chars but content measures ${totalDescChars} — update the doc`);
321
+ } else {
322
+ console.log(`✓ cost.md index chars match measurement (${stated})`);
323
+ }
324
+ }
325
+ }
326
+
289
327
  // --- docs-drift check: docs/skills.md and docs/agents.md must reference all content/ entries ---
290
328
  const docsArg = process.argv.indexOf('--check-docs');
291
329
  if (docsArg !== -1) {
@@ -338,7 +376,7 @@ if (integrityArg !== -1) {
338
376
  const constants = readFileSync(src, 'utf8');
339
377
  const docs = ['docs/concepts/cost.md', 'docs/concepts/lanes.md', 'README.md'];
340
378
  const expected: [string, string][] = [
341
- ['lean', '7,000'], ['standard', '13,000'], ['full', '23,000'],
379
+ ['lean', '8,000'], ['standard', '13,000'], ['full', '22,000'],
342
380
  ['lean', '12,000'], ['standard', '25,000'], ['full', '50,000'],
343
381
  ];
344
382
  for (const doc of docs) {
@@ -374,15 +412,9 @@ if (integrityArg !== -1) {
374
412
  }
375
413
  }
376
414
  }
377
- if (!constants.includes('LANE_BASE_lean=7000')) errors.push('doc-integrity: source lane-base.sh lean base drifted (expected 7000)');
378
- if (!constants.includes('BUDGET_full=50000')) errors.push('doc-integrity: source lane-base.sh full budget drifted (expected 50000)');
379
- }
380
- // evidence exit-code claim must be test-backed
381
- const evTest = join(import.meta.dirname, '..', 'test', 'lane-integrity.test.ts');
382
- const testText = existsSync(evTest) ? readFileSync(evTest, 'utf8') : '';
383
- if (!testText.includes('# Exit:') || !testText.includes('# Verdict:')) {
384
- errors.push('doc-integrity: audit-trail claims evidence logs carry exit code + verdict, but no test asserts them');
385
- }
415
+ if (!constants.includes('LANE_BASE_lean=8421')) errors.push('doc-integrity: source lane-base.sh lean base drifted (expected 8421)');
416
+ if (!constants.includes('BUDGET_full=50000')) errors.push('doc-integrity: source lane-base.sh full budget drifted (expected 50000)');
417
+ }
386
418
  }
387
419
 
388
420
  // Conditional-assertion guard: an expect() reachable only inside a truthiness
@@ -87,13 +87,15 @@ for (const id of TARGET_IDS) {
87
87
  const RUNTIME_SHAPES: RegExp[] = [
88
88
  /^\.mugiwara\/?(\*\*)?$/, // bare mention of the dir
89
89
  /^~?\/?\.mugiwara\/config$/, // project + global config
90
- /^\.mugiwara\/(state|continue)\/<mission>\/(\[member\]|state|<member>)\.json$/,
91
- /^\.mugiwara\/issues\/(YYYY-MM-DD-<mission>-blockers\.md)?$/,
92
- /^\.mugiwara\/logs\/(YYYY-MM-DD-<mission>\.md|lessons\.md)?$/,
93
- /^\.mugiwara\/(plans|spec|reports)\/(YYYY-MM-DD-<mission>\.md)?$/,
94
- /^\.mugiwara\/review\/(YYYY-MM-DD-<mission>-(review|security|verifier)\.md)?$/,
95
- /^\.mugiwara\/results\/<mission>\/([0-9]{2}-[a-z-]+|eval|resume|todos)\.md$/,
96
- /^\.mugiwara\/results\/?$/,
90
+ /^\.mugiwara\/lessons\.md$/,
91
+ /^\.mugiwara\/index\.md$/,
92
+ /^\.mugiwara\/missions\/(<mission>|[a-z0-9._-]+)\/(plan|spec|decisions|blockers|review|security|report)\.md$/,
93
+ /^\.mugiwara\/missions\/(<mission>|[a-z0-9._-]+)\/flows\/([0-9]{2}-[a-z-]+|eval|resume|todos)\.md$/,
94
+ /^\.mugiwara\/missions\/<mission>\/flows\/?$/,
95
+ /^\.mugiwara\/missions\/(<mission>|[a-z0-9._-]+)\/waves\/([0-9]{2}-[a-z-]+|eval|resume|todos)\.md$/, // legacy layout, still read
96
+ /^\.mugiwara\/missions\/<mission>\/waves\/?$/, // legacy layout, still read
97
+ /^\.mugiwara\/missions\/<mission>\/(state\.json|<member>\.json)$/,
98
+ /^\.mugiwara\/missions\/<mission>\/(continue\.json|continue-<member>\.json)$/,
97
99
  ];
98
100
 
99
101
  // Shape violations that exist today and are NOT fixed in this mission because