@ionivetech/mugiwara 0.6.6 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -7
- package/.codex-plugin/plugin.json +2 -7
- package/.cursor-plugin/plugin.json +2 -7
- package/.kimi-plugin/plugin.json +2 -7
- package/.opencode/mugiwara-helpers.mjs +2 -2
- package/.opencode/plugins/mugiwara.mjs +3 -18
- package/AGENTS.md +5 -4
- package/GEMINI.md +3 -3
- package/README.md +203 -331
- package/content/agents/brook-healing.md +8 -4
- package/content/agents/chopper-checkpoint.md +7 -3
- package/content/agents/eval-runner.md +4 -4
- package/content/agents/franky-gates.md +4 -4
- package/content/agents/jinbe-security.md +6 -7
- package/content/agents/luffy-orchestrator.md +11 -13
- package/content/agents/memory-keeper.md +4 -4
- package/content/agents/nami-planner.md +4 -4
- package/content/agents/resume-coordinator.md +7 -7
- package/content/agents/robin-reviewer.md +7 -3
- package/content/agents/sanji-quality.md +2 -2
- package/content/agents/skeptic-verifier.md +2 -2
- package/content/agents/usopp-brainstorm.md +3 -3
- package/content/agents/zoro-execution.md +6 -6
- package/content/skills/mugiwara-backend/SKILL.md +54 -43
- package/content/skills/mugiwara-backend/references/database.md +61 -0
- package/content/skills/mugiwara-brainstorm/SKILL.md +1 -1
- package/content/skills/mugiwara-checkpoint/SKILL.md +23 -11
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +1 -1
- package/content/skills/mugiwara-contract-first/SKILL.md +46 -1
- package/content/skills/mugiwara-execution/SKILL.md +37 -37
- package/content/skills/mugiwara-execution/references/dispatch.md +1 -1
- package/content/skills/mugiwara-execution/references/execution-phase-flows.md +18 -0
- package/content/skills/mugiwara-execution/references/resume-batching.md +2 -2
- package/content/skills/mugiwara-frontend/SKILL.md +44 -43
- package/content/skills/mugiwara-frontend/references/checklist.md +14 -0
- package/content/skills/mugiwara-gates/SKILL.md +23 -14
- package/content/skills/mugiwara-healing/SKILL.md +26 -25
- package/content/skills/mugiwara-lessons/SKILL.md +2 -2
- package/content/skills/mugiwara-orchestration/SKILL.md +11 -11
- package/content/skills/mugiwara-orchestration/references/check-ins.md +2 -2
- package/content/skills/mugiwara-orchestration/references/closure.md +17 -12
- package/content/skills/mugiwara-orchestration/references/control-commands.md +14 -0
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +11 -1
- package/content/skills/mugiwara-planning/SKILL.md +26 -29
- package/content/skills/mugiwara-planning/references/large-campaign-subplan.md +41 -0
- package/content/skills/mugiwara-planning/references/plan-template.md +24 -2
- package/content/skills/mugiwara-quality/SKILL.md +21 -12
- package/content/skills/mugiwara-resume/SKILL.md +8 -3
- package/content/skills/mugiwara-review/SKILL.md +19 -13
- package/content/skills/mugiwara-security/SKILL.md +47 -36
- package/content/skills/mugiwara-ship/SKILL.md +1 -1
- package/content/skills/mugiwara-ship/references/cleanup.md +17 -16
- package/content/skills/mugiwara-workflow/SKILL.md +11 -17
- package/content/skills/mugiwara-workflow/references/adaptive-budget-governor.md +5 -0
- package/content/skills/mugiwara-workflow/references/benchmark-governor.md +53 -0
- package/content/skills/mugiwara-workflow/references/cognitive-output-governor.md +5 -0
- package/content/skills/mugiwara-workflow/references/large-campaign-subplan.md +29 -0
- package/content/skills/mugiwara-workflow/references/scope-code-governor.md +14 -0
- package/content/skills/mugiwara-workflow/references/stop-slop-governor.md +14 -0
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +42 -38
- package/dist/mugiwara.js +1642 -528
- package/gemini-extension.json +1 -1
- package/hooks/auto-savepoint.js +18 -10
- package/hooks/auto-savepoint.ts +23 -23
- package/hooks/engagement-marker.js +1 -1
- package/hooks/engagement-marker.ts +1 -1
- package/hooks/pipeline-guard.js +17 -13
- package/hooks/pipeline-guard.ts +24 -20
- package/hooks/session-start.js +13 -10
- package/hooks/session-start.ts +24 -19
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/references/complexity.md +27 -4
- package/references/definition-of-done.md +1 -1
- package/references/multi-actor.md +20 -2
- package/references/posture-routing.md +31 -0
- package/references/prose-style.md +54 -0
- package/scripts/benchmark-governor.ts +516 -0
- package/scripts/benchmark-thresholds.json +47 -0
- package/scripts/check-doc-links.ts +46 -0
- package/scripts/conformance.ts +6 -16
- package/scripts/coverage-gate.ts +20 -6
- package/scripts/gate-selftest.ts +138 -7
- package/scripts/lane.sh +54 -8
- package/scripts/lib/lane-base.sh +4 -4
- package/scripts/lib/patterns.sh +5 -0
- package/scripts/policy-force.ts +22 -0
- package/scripts/retrieval-eval.ts +10 -4
- package/scripts/savepoint.sh +155 -58
- package/scripts/validate-content.ts +43 -11
- package/scripts/verify-install.ts +9 -7
- package/src/adaptive-budget.ts +178 -0
- package/src/args.ts +4 -3
- package/src/budget.ts +47 -0
- package/src/check-artifacts.ts +45 -0
- package/src/cli.ts +257 -108
- package/src/cognition.ts +234 -0
- package/src/config.ts +107 -0
- package/src/context.ts +72 -0
- package/src/continue.ts +29 -10
- package/src/cost.ts +186 -0
- package/src/evidence.ts +160 -0
- package/src/installer.ts +25 -34
- package/src/integrity.ts +158 -0
- package/src/investigation.ts +72 -0
- package/src/mission.ts +341 -94
- package/src/policy.ts +156 -0
- package/src/posture.ts +86 -0
- package/src/provenance.ts +116 -0
- package/src/reporting.ts +225 -0
- package/src/rollback.ts +95 -0
- package/src/routing.ts +69 -0
- package/src/run.ts +2 -2
- package/src/scope.ts +321 -0
- package/src/sign.ts +235 -0
- package/src/slop.ts +306 -0
- package/src/targets/claude.ts +2 -2
- package/src/work.ts +273 -0
- package/.opencode/commands/mugiwara-execute.md +0 -13
- package/.opencode/commands/mugiwara-heal.md +0 -13
- package/.opencode/commands/mugiwara-onboard.md +0 -14
- package/.opencode/commands/mugiwara-plan.md +0 -17
- package/.opencode/commands/mugiwara-ship.md +0 -13
- package/.opencode/commands/using-mugiwara.md +0 -20
- package/content/skills/mugiwara-agent-security/SKILL.md +0 -58
- package/content/skills/mugiwara-agent-security/references/checklist.md +0 -20
- package/content/skills/mugiwara-context-budget/SKILL.md +0 -66
- package/content/skills/mugiwara-context-budget/references/context-budget.md +0 -37
- package/content/skills/mugiwara-pr/SKILL.md +0 -69
- package/content/skills/mugiwara-pr/references/verdict-format.md +0 -31
- package/content/skills/mugiwara-sunset/SKILL.md +0 -82
- package/content/skills/using-mugiwara/SKILL.md +0 -51
- package/references/token-budget.md +0 -56
- package/scripts/evidence.sh +0 -81
- package/scripts/initiative.ts +0 -296
- package/scripts/mission-report.sh +0 -293
- package/src/onboard.ts +0 -207
package/scripts/savepoint.sh
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# scripts/savepoint.sh — write .mugiwara/
|
|
3
|
-
#
|
|
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] [
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
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="$
|
|
138
|
-
CONTINUE_FILE="$
|
|
162
|
+
STATE_FILE="$MISSION_DIR/$MEMBER.json"
|
|
163
|
+
CONTINUE_FILE="$MISSION_DIR/continue-$MEMBER.json"
|
|
139
164
|
else
|
|
140
|
-
STATE_FILE="$
|
|
141
|
-
CONTINUE_FILE="$
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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 —
|
|
259
|
-
|
|
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
|
-
|
|
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-
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
# the [^0-9a-z] guard keeps
|
|
289
|
-
#
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
#
|
|
318
|
-
#
|
|
368
|
+
# depth flags — advisory → 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 "$
|
|
393
|
+
for evf in "$MISSION_DIR/$FLOWDIR/"*.md; do
|
|
321
394
|
[ -f "$evf" ] || continue
|
|
322
|
-
EVIDENCE="${EVIDENCE}${EVIDENCE:+,}.mugiwara/
|
|
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 "$
|
|
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="
|
|
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 "$
|
|
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
|
|
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
|
|
454
|
-
# writes
|
|
455
|
-
#
|
|
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,
|
|
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', '
|
|
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
|
-
|
|
378
|
-
|
|
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\/
|
|
91
|
-
/^\.mugiwara\/
|
|
92
|
-
/^\.mugiwara\/
|
|
93
|
-
/^\.mugiwara\/(
|
|
94
|
-
/^\.mugiwara\/
|
|
95
|
-
/^\.mugiwara\/
|
|
96
|
-
/^\.mugiwara\/
|
|
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
|