@ionivetech/mugiwara 0.6.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +3 -9
- package/.codex-plugin/plugin.json +2 -8
- package/.cursor-plugin/plugin.json +2 -8
- package/.kimi-plugin/plugin.json +2 -8
- package/.opencode/commands/mugiwara-continue.md +44 -31
- package/.opencode/commands/mugiwara-review.md +1 -1
- package/.opencode/commands/mugiwara-security.md +1 -1
- package/.opencode/commands/mugiwara.md +2 -2
- package/.opencode/plugins/mugiwara.mjs +3 -19
- package/AGENTS.md +6 -6
- package/GEMINI.md +3 -3
- package/README.md +82 -74
- package/content/agents/brook-healing.md +12 -11
- package/content/agents/chopper-checkpoint.md +14 -13
- package/content/agents/eval-runner.md +6 -9
- package/content/agents/franky-gates.md +7 -10
- package/content/agents/jinbe-security.md +10 -14
- package/content/agents/luffy-orchestrator.md +20 -22
- package/content/agents/memory-keeper.md +9 -12
- package/content/agents/nami-planner.md +7 -10
- package/content/agents/resume-coordinator.md +12 -15
- package/content/agents/robin-reviewer.md +13 -12
- package/content/agents/sanji-quality.md +9 -12
- package/content/agents/skeptic-verifier.md +7 -10
- package/content/agents/usopp-brainstorm.md +6 -9
- package/content/agents/zoro-execution.md +9 -12
- package/content/skills/mugiwara-backend/SKILL.md +4 -0
- package/content/skills/mugiwara-backend/references/database.md +61 -0
- package/content/skills/mugiwara-brainstorm/SKILL.md +1 -1
- package/content/skills/mugiwara-checkpoint/SKILL.md +17 -14
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +5 -5
- package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
- package/content/skills/mugiwara-execution/SKILL.md +24 -24
- package/content/skills/mugiwara-execution/references/dispatch.md +3 -3
- package/content/skills/mugiwara-execution/references/resume-batching.md +2 -2
- package/content/skills/mugiwara-frontend/SKILL.md +5 -6
- package/content/skills/mugiwara-frontend/references/checklist.md +14 -0
- package/content/skills/mugiwara-gates/SKILL.md +14 -10
- package/content/skills/mugiwara-git/SKILL.md +7 -2
- package/content/skills/mugiwara-healing/SKILL.md +7 -5
- package/content/skills/mugiwara-healing/references/workers.md +2 -2
- package/content/skills/mugiwara-lessons/SKILL.md +2 -2
- package/content/skills/mugiwara-orchestration/SKILL.md +28 -26
- package/content/skills/mugiwara-orchestration/references/check-ins.md +17 -16
- package/content/skills/mugiwara-orchestration/references/closure.md +18 -13
- package/content/skills/mugiwara-orchestration/references/output-contract.md +9 -9
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +32 -22
- package/content/skills/mugiwara-planning/SKILL.md +5 -18
- package/content/skills/mugiwara-planning/references/plan-template.md +3 -3
- package/content/skills/mugiwara-quality/SKILL.md +10 -7
- package/content/skills/mugiwara-resume/SKILL.md +20 -22
- package/content/skills/mugiwara-review/SKILL.md +7 -2
- package/content/skills/mugiwara-review/references/five-axis-worksheet.md +1 -1
- package/content/skills/mugiwara-root-cause/SKILL.md +2 -0
- package/content/skills/mugiwara-security/SKILL.md +6 -2
- package/content/skills/mugiwara-ship/SKILL.md +2 -2
- package/content/skills/mugiwara-ship/references/cleanup.md +17 -16
- package/content/skills/mugiwara-testcases/SKILL.md +5 -3
- package/content/skills/mugiwara-workflow/SKILL.md +32 -35
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +41 -40
- package/dist/mugiwara.js +1370 -153
- package/gemini-extension.json +1 -1
- package/hooks/auto-savepoint.js +114 -0
- package/hooks/auto-savepoint.ts +102 -0
- package/hooks/engagement-marker.js +59 -0
- package/hooks/engagement-marker.ts +97 -0
- package/hooks/hooks.json +41 -2
- package/hooks/mugiwara-mode-tracker.js +79 -0
- package/hooks/pipeline-guard.js +186 -0
- package/hooks/pipeline-guard.ts +270 -0
- package/hooks/session-start.js +109 -0
- package/hooks/session-start.ts +62 -45
- package/package.json +7 -3
- package/plugin.json +1 -1
- package/references/agent-protocol.md +15 -0
- package/references/complexity.md +77 -0
- package/references/definition-of-done.md +1 -1
- package/references/multi-actor.md +4 -7
- package/references/prose-style.md +54 -0
- package/references/skill-versioning.md +1 -1
- package/references/wave-banners.md +16 -16
- package/scripts/build-hooks.ts +56 -0
- package/scripts/check-doc-links.ts +40 -0
- package/scripts/conformance.ts +23 -16
- package/scripts/coverage-gate.ts +193 -0
- package/scripts/gate-selftest.ts +118 -7
- package/scripts/install.ps1 +5 -4
- package/scripts/install.sh +4 -4
- package/scripts/lane.sh +56 -6
- package/scripts/lib/patterns.sh +89 -0
- package/scripts/policy-force.ts +22 -0
- package/scripts/retrieval-eval.ts +1 -1
- package/scripts/savepoint.sh +249 -67
- package/scripts/validate-content.ts +22 -9
- package/scripts/verify-install.ts +156 -44
- package/src/args.ts +2 -2
- package/src/budget.ts +56 -0
- package/src/cli.ts +272 -4
- package/src/continue.ts +262 -0
- package/src/installer.ts +59 -15
- package/src/integrity.ts +158 -0
- package/src/mission.ts +228 -95
- package/src/policy.ts +156 -0
- package/src/provenance.ts +116 -0
- package/src/rollback.ts +95 -0
- package/src/routing.ts +69 -0
- package/src/run.ts +82 -0
- package/src/sign.ts +61 -0
- package/src/targets/claude.ts +140 -2
- package/src/targets/copilot.ts +20 -1
- package/src/targets/generic.ts +9 -4
- package/src/targets/opencode.ts +0 -1
- package/.opencode/commands/mugiwara-execute.md +0 -13
- package/.opencode/commands/mugiwara-heal.md +0 -13
- package/.opencode/commands/mugiwara-onboard.md +0 -15
- 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/agents/onboarding-guide.md +0 -124
- 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 -64
- 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-security/references/owasp-mapping.md +0 -30
- package/content/skills/mugiwara-sunset/SKILL.md +0 -82
- package/content/skills/using-mugiwara/SKILL.md +0 -51
- package/references/token-budget.md +0 -34
- package/scripts/evidence.sh +0 -81
- package/scripts/initiative.ts +0 -296
- package/scripts/mission-report.sh +0 -292
- package/scripts/onboard.ts +0 -266
- package/scripts/probe.ts +0 -40
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"
|
|
@@ -42,6 +54,28 @@ MISSION="${1:-${STATE_MISSION:-}}"
|
|
|
42
54
|
MEMBER="${2:-${STATE_MEMBER:-}}"
|
|
43
55
|
WAVE="${3:-${STATE_WAVE:-1}}"
|
|
44
56
|
MODE="${4:-${STATE_MODE:-guided}}"
|
|
57
|
+
# Triage lane (M7): the lane Luffy assigned at Flow 0. Without it savepoint
|
|
58
|
+
# recomputed the lane from file counts alone and silently discarded the
|
|
59
|
+
# triage decision — a Lane 3 mission recorded itself as "direct". Explicit
|
|
60
|
+
# lane acts as a FLOOR: the computed lane may still raise it, never lower it.
|
|
61
|
+
LANE_EXPLICIT="${5:-${STATE_LANE:-}}"
|
|
62
|
+
case "$LANE_EXPLICIT" in
|
|
63
|
+
""|direct|lean|standard|full|spike) ;;
|
|
64
|
+
*) die "invalid lane \"$LANE_EXPLICIT\" (one of: direct lean standard full spike)" ;;
|
|
65
|
+
esac
|
|
66
|
+
# Wave may be fractional in the docs (4.5 = the checkpoint between waves).
|
|
67
|
+
# parseInt("4.5")=4 while tr -cd '0-9' gives "45", so state and continue
|
|
68
|
+
# disagreed on the same mission. Normalize once, here, for both writers.
|
|
69
|
+
# Keep only digits and the decimal point, then take the integer part. Garbage
|
|
70
|
+
# input still sanitizes to 0 (the N2 contract the continue writer already had);
|
|
71
|
+
# a fractional wave now truncates identically in BOTH writers instead of
|
|
72
|
+
# splitting into 4 (parseInt) and 45 (digit-strip).
|
|
73
|
+
WAVE_INT=$(printf '%s' "$WAVE" | tr -cd '0-9.')
|
|
74
|
+
WAVE_INT=${WAVE_INT%%.*}
|
|
75
|
+
case "$WAVE_INT" in
|
|
76
|
+
''|*[!0-9]*) WAVE_INT=0 ;;
|
|
77
|
+
esac
|
|
78
|
+
|
|
45
79
|
# verbosity from config (project .mugiwara/config), default normal; env override
|
|
46
80
|
VERBOSITY="${STATE_VERBOSITY:-normal}"
|
|
47
81
|
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
@@ -52,8 +86,37 @@ case "$VERBOSITY" in
|
|
|
52
86
|
normal|full) ;;
|
|
53
87
|
*) VERBOSITY="normal" ;;
|
|
54
88
|
esac
|
|
55
|
-
|
|
89
|
+
|
|
90
|
+
# heal_max_cycles from config (project .mugiwara/config), default 3; env override
|
|
91
|
+
HEAL_MAX_CYCLES="${STATE_HEAL_MAX_CYCLES:-3}"
|
|
92
|
+
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
93
|
+
CFG_HEAL_MAX=$(grep -E '^heal_max_cycles=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
94
|
+
[ -n "$CFG_HEAL_MAX" ] && HEAL_MAX_CYCLES="$CFG_HEAL_MAX"
|
|
95
|
+
fi
|
|
96
|
+
case "$HEAL_MAX_CYCLES" in
|
|
97
|
+
''|*[!0-9]*) HEAL_MAX_CYCLES=3 ;;
|
|
98
|
+
esac
|
|
99
|
+
[ "$HEAL_MAX_CYCLES" -lt 1 ] 2>/dev/null && HEAL_MAX_CYCLES=3
|
|
100
|
+
|
|
101
|
+
# delegate_threshold from config (project .mugiwara/config), default 60; env override
|
|
102
|
+
DELEGATE_THRESHOLD="${STATE_DELEGATE_THRESHOLD:-60}"
|
|
103
|
+
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
104
|
+
CFG_DELEGATE=$(grep -E '^delegate_threshold=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
105
|
+
[ -n "$CFG_DELEGATE" ] && DELEGATE_THRESHOLD="$CFG_DELEGATE"
|
|
106
|
+
fi
|
|
107
|
+
case "$DELEGATE_THRESHOLD" in
|
|
108
|
+
''|*[!0-9]*) DELEGATE_THRESHOLD=60 ;;
|
|
109
|
+
esac
|
|
110
|
+
[ "$DELEGATE_THRESHOLD" -lt 1 ] 2>/dev/null && DELEGATE_THRESHOLD=1
|
|
111
|
+
[ "$DELEGATE_THRESHOLD" -gt 100 ] 2>/dev/null && DELEGATE_THRESHOLD=100
|
|
112
|
+
ACTOR="${STATE_ACTOR:-${GIT_AUTHOR_NAME:-${GIT_ID:-${USER:-${USERNAME:-}}}}}"
|
|
56
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"
|
|
57
120
|
|
|
58
121
|
# treat the legacy empty-actor placeholder '""' as "no member" (solo). The old
|
|
59
122
|
# interface used '""' for the actor slot; the new (mission, member) interface
|
|
@@ -78,6 +141,12 @@ esac
|
|
|
78
141
|
if [[ "$MEMBER" =~ ^\.+$ ]]; then
|
|
79
142
|
die "invalid member name \"$MEMBER\" (allowlist: [a-zA-Z0-9._-], not a dot-path)"
|
|
80
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
|
|
81
150
|
|
|
82
151
|
# BRANCH_SLUG sanitized to [A-Za-z0-9._-] — it feeds the continue branch field;
|
|
83
152
|
# a newline/control char in BRANCH would corrupt the JSON (F5). Dropping illegal
|
|
@@ -85,44 +154,64 @@ fi
|
|
|
85
154
|
# (BSD/macOS-safe: no \+ BRE).
|
|
86
155
|
BRANCH_SLUG=$(echo "$BRANCH" | tr '/' '-' | tr -cd 'A-Za-z0-9._-' | sed 's/^\.\{1,\}$//' )
|
|
87
156
|
|
|
88
|
-
# state + continue live
|
|
89
|
-
|
|
90
|
-
|
|
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"
|
|
91
161
|
if [ -n "$MEMBER" ]; then
|
|
92
|
-
STATE_FILE="$
|
|
93
|
-
CONTINUE_FILE="$
|
|
162
|
+
STATE_FILE="$MISSION_DIR/$MEMBER.json"
|
|
163
|
+
CONTINUE_FILE="$MISSION_DIR/continue-$MEMBER.json"
|
|
94
164
|
else
|
|
95
|
-
STATE_FILE="$
|
|
96
|
-
CONTINUE_FILE="$
|
|
165
|
+
STATE_FILE="$MISSION_DIR/state.json"
|
|
166
|
+
CONTINUE_FILE="$MISSION_DIR/continue.json"
|
|
97
167
|
fi
|
|
98
168
|
|
|
99
|
-
[ -z "$MISSION" ] && die "usage: savepoint.sh <mission> [member] [wave] [mode]"
|
|
169
|
+
[ -z "$MISSION" ] && die "usage: savepoint.sh <mission> [member] [wave] [mode] [lane]"
|
|
100
170
|
[ -d .git ] || die "not a git repository"
|
|
101
171
|
|
|
102
172
|
# --- computed fields ---
|
|
103
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")
|
|
104
174
|
HEAD_SHA=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
105
175
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
INS=$(echo "$STAT" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo 0)
|
|
116
|
-
DEL=$(echo "$STAT" | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' || echo 0)
|
|
117
|
-
LOC_INS=$(( ${INS:-0} + 0 ))
|
|
118
|
-
LOC_DEL=$(( ${DEL:-0} + 0 ))
|
|
119
|
-
LOC_DELTA=$(( LOC_INS - LOC_DEL ))
|
|
120
|
-
# churn is insertion+deletion — refactors and deletions are work too (D4)
|
|
121
|
-
LOC_CHURN=$(( LOC_INS + LOC_DEL ))
|
|
122
|
-
fi
|
|
123
|
-
[ -z "$LOC_DELTA" ] && LOC_DELTA=0
|
|
124
|
-
|
|
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
|
|
182
|
+
# union of committed + staged + unstaged + untracked (F) — see patterns.sh
|
|
183
|
+
CHANGED_FILES=$(changed_files "$BASE_SHA")
|
|
184
|
+
# sensitive-path escalation always uses unfiltered set (safety never shrinks, T5)
|
|
125
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
|
|
207
|
+
$(changed_loc "$BASE_SHA")
|
|
208
|
+
EOF
|
|
209
|
+
fi
|
|
210
|
+
LOC_INS=$(( ${LOC_INS:-0} + 0 ))
|
|
211
|
+
LOC_DEL=$(( ${LOC_DEL:-0} + 0 ))
|
|
212
|
+
LOC_DELTA=$(( LOC_INS - LOC_DEL ))
|
|
213
|
+
# churn is insertion+deletion — refactors and deletions are work too (D4)
|
|
214
|
+
LOC_CHURN=$(( LOC_INS + LOC_DEL ))
|
|
126
215
|
|
|
127
216
|
LANE="direct"
|
|
128
217
|
LANE_REASON=""
|
|
@@ -142,7 +231,7 @@ elif [ "$FILES_TOUCHED" -ge 2 ] 2>/dev/null; then
|
|
|
142
231
|
LANE_REASON="$FILES_TOUCHED files"
|
|
143
232
|
elif [ "$FILES_TOUCHED" -eq 1 ] 2>/dev/null; then
|
|
144
233
|
# 1-file rule mirrors lane.sh: >=20 added LOC -> lean, else direct
|
|
145
|
-
ADDED
|
|
234
|
+
ADDED="$LOC_INS"
|
|
146
235
|
if [ "${ADDED:-0}" -ge 20 ] 2>/dev/null; then
|
|
147
236
|
LANE="lean"
|
|
148
237
|
LANE_REASON="1 file, $ADDED LOC"
|
|
@@ -157,9 +246,10 @@ fi
|
|
|
157
246
|
|
|
158
247
|
# path-weighted sizing (mirrors lane.sh): docs-only changes outside the product
|
|
159
248
|
# surface never escalate to full from file count alone; sensitive-path
|
|
160
|
-
# escalation above still wins.
|
|
161
|
-
|
|
162
|
-
|
|
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)
|
|
163
253
|
if [ -z "$CODE_COUNT" ] || [ "$CODE_COUNT" -eq 0 ] 2>/dev/null; then
|
|
164
254
|
PREV="$LANE"
|
|
165
255
|
LANE="standard"
|
|
@@ -167,6 +257,33 @@ if [ "$LANE" = "full" ] && [ -z "$SENSITIVE_PATHS" ] && [ -n "$CHANGED_FILES" ];
|
|
|
167
257
|
fi
|
|
168
258
|
fi
|
|
169
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
|
+
|
|
274
|
+
# explicit triage lane (M7) applied as a floor — the computed lane above may
|
|
275
|
+
# raise it, never lower it. Sensitive-path escalation still wins
|
|
276
|
+
# unconditionally: spike is a resize, not a rise, so it never displaces the
|
|
277
|
+
# full lane a sensitive path forced.
|
|
278
|
+
if [ -n "$LANE_EXPLICIT" ]; then
|
|
279
|
+
if [ "$LANE_EXPLICIT" = "spike" ] && [ -n "$SENSITIVE_PATHS" ]; then
|
|
280
|
+
LANE_REASON="$LANE_REASON (explicit spike ignored — sensitive paths)"
|
|
281
|
+
elif [ "$(lane_rank "$LANE_EXPLICIT")" -gt "$(lane_rank "$LANE")" ]; then
|
|
282
|
+
LANE_REASON="triage lane $LANE_EXPLICIT (floor; computed $LANE — $LANE_REASON)"
|
|
283
|
+
LANE="$LANE_EXPLICIT"
|
|
284
|
+
fi
|
|
285
|
+
fi
|
|
286
|
+
|
|
170
287
|
# --- lane-rise compare (F9) + monotonic clamp (D2): read previous state ---
|
|
171
288
|
# Clamp runs here — before LANE_BASE/BUDGET — so tokens and budget follow the
|
|
172
289
|
# held lane, never the raw shrunken one.
|
|
@@ -203,9 +320,8 @@ if [ -n "$LANE_PREV" ] && [ "$LANE_PREV" != "$LANE" ]; then
|
|
|
203
320
|
esac
|
|
204
321
|
fi
|
|
205
322
|
|
|
206
|
-
# task counts from plan doc —
|
|
207
|
-
|
|
208
|
-
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"
|
|
209
325
|
TASKS_DONE=0
|
|
210
326
|
TASKS_TOTAL=0
|
|
211
327
|
if [ -n "$PLAN_FILE" ] && [ -f "$PLAN_FILE" ]; then
|
|
@@ -217,7 +333,7 @@ if [ -n "$PLAN_FILE" ] && [ -f "$PLAN_FILE" ]; then
|
|
|
217
333
|
fi
|
|
218
334
|
|
|
219
335
|
# blocker count
|
|
220
|
-
BLOCKERS_FILE
|
|
336
|
+
BLOCKERS_FILE="$MISSION_DIR/blockers.md"
|
|
221
337
|
BLOCKERS_OPEN=0
|
|
222
338
|
if [ -n "$BLOCKERS_FILE" ] && [ -f "$BLOCKERS_FILE" ]; then
|
|
223
339
|
# data rows start with a wave number; header "| wave |" and separator
|
|
@@ -225,36 +341,77 @@ if [ -n "$BLOCKERS_FILE" ] && [ -f "$BLOCKERS_FILE" ]; then
|
|
|
225
341
|
BLOCKERS_OPEN=$(grep -cE '^\| ?[0-9]+ ?\|' "$BLOCKERS_FILE" 2>/dev/null || true)
|
|
226
342
|
fi
|
|
227
343
|
|
|
228
|
-
# heal cycle — count
|
|
229
|
-
#
|
|
230
|
-
#
|
|
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.
|
|
231
350
|
HEAL_CYCLE=1
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
351
|
+
HEAL_COUNT=0
|
|
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
|
|
356
|
+
if [ "$HEAL_COUNT" -gt 0 ]; then
|
|
235
357
|
HEAL_CYCLE=$((HEAL_COUNT + 1))
|
|
236
358
|
fi
|
|
237
359
|
|
|
238
|
-
#
|
|
239
|
-
#
|
|
360
|
+
# heal halt — computed from the heal counter and the config cap (task 4f wires
|
|
361
|
+
# heal_max_cycles into the counter's home). heal_cycle starts at 1, so
|
|
362
|
+
# heal_cycle >= heal_max_cycles means the cap is reached: escalate, never re-run.
|
|
363
|
+
HEAL_HALT=false
|
|
364
|
+
if [ "$HEAL_CYCLE" -ge "$HEAL_MAX_CYCLES" ] 2>/dev/null; then
|
|
365
|
+
HEAL_HALT=true
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
# evidence paths — the mission's flow folder (quoted printf, no sed — mission
|
|
369
|
+
# name is allowlisted above, but avoid sed metacharacter semantics entirely).
|
|
370
|
+
# New missions use flows/; a legacy mission that already keeps waves/ stays on
|
|
371
|
+
# waves/ so an in-flight trail never splits across two directories.
|
|
372
|
+
FLOWDIR="flows"
|
|
373
|
+
if [ -d "$MISSION_DIR/waves" ] && [ ! -d "$MISSION_DIR/flows" ]; then
|
|
374
|
+
FLOWDIR="waves"
|
|
375
|
+
fi
|
|
240
376
|
EVIDENCE=""
|
|
241
|
-
for evf in "$
|
|
377
|
+
for evf in "$MISSION_DIR/$FLOWDIR/"*.md; do
|
|
242
378
|
[ -f "$evf" ] || continue
|
|
243
|
-
EVIDENCE="${EVIDENCE}${EVIDENCE:+,}.mugiwara/
|
|
379
|
+
EVIDENCE="${EVIDENCE}${EVIDENCE:+,}.mugiwara/missions/${MISSION}/${FLOWDIR}/$(basename "$evf")"
|
|
244
380
|
done
|
|
245
381
|
|
|
246
|
-
# skill version
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
382
|
+
# skill version — MUGIWARA's own package.json, resolved from the script
|
|
383
|
+
# location (B3). The old "$MUGIWARA_DIR/../package.json" was the USER's
|
|
384
|
+
# project manifest: the version-mismatch resume prompt fired when a user
|
|
385
|
+
# shipped their own v2.0.0 and stayed silent across every real mugiwara
|
|
386
|
+
# release. Never fall back to the user's file — "unknown" is the honest
|
|
387
|
+
# answer. Full version, not the major: mugiwara's breaking changes land in
|
|
388
|
+
# 0.x minors, which a major-only string can never see.
|
|
389
|
+
SKILL_VERSION="unknown"
|
|
390
|
+
PKG_JSON="$(dirname "$0")/../package.json"
|
|
391
|
+
if [ -f "$PKG_JSON" ]; then
|
|
392
|
+
SKILL_VERSION=$(node -e "try{const v=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version;console.log(v||'unknown')}catch(e){console.log('unknown')}" "$PKG_JSON" 2>/dev/null || echo "unknown")
|
|
251
393
|
fi
|
|
394
|
+
[ -z "$SKILL_VERSION" ] && SKILL_VERSION="unknown"
|
|
252
395
|
|
|
253
396
|
# tokens proxy (F7): deterministic estimate when the harness does not report
|
|
254
397
|
# real usage. Monotonic beats precise — LANE_BASE stands in for the skills
|
|
255
398
|
# loaded this lane (measured from content, validated by scripts/lane-base.ts);
|
|
256
399
|
# loc_churn and written-artifact words scale with growth.
|
|
400
|
+
# auto-detect harness for token reporting (Tier-1: Claude Code / opencode)
|
|
401
|
+
# If --tokens-file not given, try env/file fallbacks so estimator is last resort.
|
|
402
|
+
if [ -z "$TOKENS_FILE" ]; then
|
|
403
|
+
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
|
|
404
|
+
[ -n "$_cand" ] && [ -f "$_cand" ] && TOKENS_FILE="$_cand" && break
|
|
405
|
+
done
|
|
406
|
+
fi
|
|
407
|
+
# detect harness (for logging / future tier checks)
|
|
408
|
+
HARNESS="unknown"
|
|
409
|
+
if [ -n "${CLAUDECODE:-}" ] || [ -n "${CLAUDE_CODE_ENTRYPOINT:-}" ] || echo "${ANTHROPIC_MODEL:-}" | grep -qi claude 2>/dev/null; then HARNESS="claude";
|
|
410
|
+
elif [ -n "${OPENCODE:-}" ] || [ -f ".opencode/config.json" ] || [ -n "${OPENCODE_TOKENS_FILE:-}" ]; then HARNESS="opencode";
|
|
411
|
+
elif [ -n "${CURSOR:-}" ] || [ -n "${VSCODE_GIT_ASKPASS_NODE:-}" ]; then HARNESS="cursor/vscode"; fi
|
|
412
|
+
|
|
257
413
|
# MUGIWARA_TOKENS overrides as the reported value.
|
|
414
|
+
# --tokens-file (T4) is the first-class provider-reported path: JSON {input_tokens, output_tokens}
|
|
258
415
|
LANE_BASE=0
|
|
259
416
|
case "$LANE" in
|
|
260
417
|
lean) LANE_BASE=$LANE_BASE_lean ;;
|
|
@@ -262,14 +419,19 @@ case "$LANE" in
|
|
|
262
419
|
full) LANE_BASE=$LANE_BASE_full ;;
|
|
263
420
|
spike) LANE_BASE=$LANE_BASE_spike ;;
|
|
264
421
|
esac
|
|
265
|
-
DOC_WORDS=$(cat "$
|
|
422
|
+
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 ' ')
|
|
266
423
|
LOC_TOKENS=$(( LOC_CHURN * 12 ))
|
|
267
|
-
TOKENS_SOURCE="
|
|
424
|
+
TOKENS_SOURCE="estimator"
|
|
268
425
|
TOKENS_EST=$(( LANE_BASE + DOC_WORDS * 135 / 100 + LOC_TOKENS ))
|
|
269
426
|
if [ -n "${MUGIWARA_TOKENS:-}" ]; then
|
|
270
427
|
TOKENS_EST="${MUGIWARA_TOKENS}"
|
|
271
428
|
TOKENS_SOURCE="reported"
|
|
272
429
|
fi
|
|
430
|
+
if [ -n "$TOKENS_FILE" ]; then
|
|
431
|
+
[ -f "$TOKENS_FILE" ] || die "tokens file not found: $TOKENS_FILE"
|
|
432
|
+
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)
|
|
433
|
+
TOKENS_SOURCE="reported"
|
|
434
|
+
fi
|
|
273
435
|
|
|
274
436
|
# budget per lane
|
|
275
437
|
BUDGET=0
|
|
@@ -291,7 +453,20 @@ elif [ "$BUDGET" -gt 0 ] && [ "$TOKENS_EST" -ge "$WARN_AT" ] 2>/dev/null; then
|
|
|
291
453
|
STATUS="warn"
|
|
292
454
|
fi
|
|
293
455
|
|
|
294
|
-
|
|
456
|
+
# delegate due — the execution skill's context-pressure trigger (task 4f wires
|
|
457
|
+
# delegate_threshold into a computed flag). tokens_est >= threshold% of budget
|
|
458
|
+
# → remaining sequential tasks dispatch to workers. Threshold stays relative:
|
|
459
|
+
# a bigger budget raises the bar, it does not remove it. Budget 0 (unknown
|
|
460
|
+
# lane) → never due.
|
|
461
|
+
DELEGATE_DUE=false
|
|
462
|
+
if [ "$BUDGET" -gt 0 ] 2>/dev/null; then
|
|
463
|
+
DELEGATE_AT=$(( BUDGET * DELEGATE_THRESHOLD / 100 ))
|
|
464
|
+
if [ "$TOKENS_EST" -ge "$DELEGATE_AT" ] 2>/dev/null; then
|
|
465
|
+
DELEGATE_DUE=true
|
|
466
|
+
fi
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
mkdir -p "$MISSION_DIR"
|
|
295
470
|
|
|
296
471
|
node -e "
|
|
297
472
|
const data = {
|
|
@@ -304,9 +479,10 @@ const data = {
|
|
|
304
479
|
lane_prev: process.argv[24] || null,
|
|
305
480
|
lane_peak: process.argv[27] || null,
|
|
306
481
|
lane_rose: process.argv[25] === 'true',
|
|
307
|
-
|
|
482
|
+
flow: parseInt(process.argv[6], 10),
|
|
308
483
|
mode: process.argv[7],
|
|
309
484
|
verbosity: process.argv[32] || 'normal',
|
|
485
|
+
model: process.argv[37] || 'unknown',
|
|
310
486
|
base_sha: process.argv[8],
|
|
311
487
|
head_sha: process.argv[9],
|
|
312
488
|
files_touched: parseInt(process.argv[10], 10),
|
|
@@ -318,6 +494,10 @@ const data = {
|
|
|
318
494
|
tasks: { done: parseInt(process.argv[13], 10), total: parseInt(process.argv[14], 10) },
|
|
319
495
|
blockers_open: parseInt(process.argv[15], 10),
|
|
320
496
|
heal_cycle: parseInt(process.argv[16], 10),
|
|
497
|
+
heal_max_cycles: parseInt(process.argv[33], 10) || 3,
|
|
498
|
+
heal_halt: process.argv[34] === 'true',
|
|
499
|
+
delegate_threshold: parseInt(process.argv[35], 10) || 60,
|
|
500
|
+
delegate_due: process.argv[36] === 'true',
|
|
321
501
|
tokens_est: parseInt(process.argv[17], 10) || 0,
|
|
322
502
|
tokens_source: process.argv[26] || 'computed',
|
|
323
503
|
budget: parseInt(process.argv[18], 10) || 0,
|
|
@@ -329,26 +509,28 @@ const data = {
|
|
|
329
509
|
require('fs').writeFileSync(process.argv[23], JSON.stringify(data, null, 2) + '\n');
|
|
330
510
|
" \
|
|
331
511
|
"$MISSION" "$ACTOR" "$BRANCH" "$LANE" "$LANE_REASON" \
|
|
332
|
-
"$
|
|
512
|
+
"$WAVE_INT" "$MODE" "$BASE_SHA" "$HEAD_SHA" "$FILES_TOUCHED" \
|
|
333
513
|
"$LOC_DELTA" "$SENSITIVE_PATHS" "$TASKS_DONE" "$TASKS_TOTAL" \
|
|
334
514
|
"$BLOCKERS_OPEN" "$HEAL_CYCLE" "$TOKENS_EST" "$BUDGET" \
|
|
335
515
|
"$STATUS" "$SKILL_VERSION" "$EVIDENCE" \
|
|
336
516
|
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
337
517
|
"$STATE_FILE" "$LANE_PREV" "$LANE_ROSE" "$TOKENS_SOURCE" "$LANE_PEAK" \
|
|
338
|
-
"$LOC_INS" "$LOC_DEL" "$LOC_CHURN" "$MEMBER" "$VERBOSITY"
|
|
518
|
+
"$LOC_INS" "$LOC_DEL" "$LOC_CHURN" "$MEMBER" "$VERBOSITY" \
|
|
519
|
+
"$HEAL_MAX_CYCLES" "$HEAL_HALT" "$DELEGATE_THRESHOLD" "$DELEGATE_DUE" \
|
|
520
|
+
"$MODEL"
|
|
339
521
|
|
|
340
522
|
if [ "$LANE_ROSE" = true ]; then
|
|
341
523
|
echo "⚠ LANE ROSE: $LANE_PREV → $LANE ($LANE_REASON) — escalate per check-in protocol"
|
|
342
524
|
fi
|
|
343
525
|
|
|
344
|
-
# --- continue
|
|
526
|
+
# --- continue.json (D10): machine-written resume point ---
|
|
345
527
|
# Written alongside state.json so an interrupted session (step-limit truncation,
|
|
346
528
|
# crash, new session) can resume without human recall. Same trust as state.json:
|
|
347
529
|
# computed fields, never model judgement. The resume skill treats it as data to
|
|
348
530
|
# verify against the plan/todos, never verbatim instructions.
|
|
349
|
-
# Scoped by (mission, member) like state.json — solo writes
|
|
350
|
-
# writes
|
|
351
|
-
#
|
|
531
|
+
# Scoped by (mission, member) like state.json — solo writes continue.json, team
|
|
532
|
+
# writes continue-<member>.json. The crew-written next_session_prompt line is
|
|
533
|
+
# preserved across savepoints.
|
|
352
534
|
if [ -n "$MISSION" ]; then
|
|
353
535
|
NEXT_SESSION_PROMPT=""
|
|
354
536
|
if [ -f "$CONTINUE_FILE" ]; then
|
|
@@ -356,7 +538,7 @@ if [ -n "$MISSION" ]; then
|
|
|
356
538
|
fi
|
|
357
539
|
# N2: every field echoed into continue is validated first. MISSION + MEMBER
|
|
358
540
|
# are allowlisted upstream; WAVE numeric, MODE enum, BRANCH slug-sanitized.
|
|
359
|
-
CONT_WAVE
|
|
541
|
+
CONT_WAVE="$WAVE_INT"
|
|
360
542
|
case "$MODE" in
|
|
361
543
|
guided|semi|auto) CONT_MODE="$MODE" ;;
|
|
362
544
|
*) CONT_MODE="guided" ;;
|
|
@@ -369,7 +551,7 @@ if [ -n "$MISSION" ]; then
|
|
|
369
551
|
member: process.argv[2] || null,
|
|
370
552
|
actor: process.argv[13] || '',
|
|
371
553
|
branch: process.argv[3],
|
|
372
|
-
|
|
554
|
+
flow: parseInt(process.argv[4], 10) || 0,
|
|
373
555
|
mode: process.argv[5],
|
|
374
556
|
tasks_done: parseInt(process.argv[6], 10) || 0,
|
|
375
557
|
tasks_total: parseInt(process.argv[7], 10) || 0,
|
|
@@ -387,4 +569,4 @@ if [ -n "$MISSION" ]; then
|
|
|
387
569
|
"$CONTINUE_FILE" "$CONTINUE_FILE" "$ACTOR"
|
|
388
570
|
fi
|
|
389
571
|
|
|
390
|
-
echo "✓ savepoint written: $STATE_FILE (lane=$LANE,
|
|
572
|
+
echo "✓ savepoint written: $STATE_FILE (lane=$LANE, flow=$WAVE, files=$FILES_TOUCHED)"
|
|
@@ -286,6 +286,25 @@ if (totalDescChars > INDEX_BUDGET) {
|
|
|
286
286
|
console.log(`✓ index budget: ${totalDescChars}/${INDEX_BUDGET} chars`);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
// The measured index size is published in docs/concepts/cost.md. A measured
|
|
290
|
+
// number in prose that drifted from reality is a defect (it survived one
|
|
291
|
+
// release: cost.md claimed 5,437 chars after the prune measured 4,688).
|
|
292
|
+
// Gate the doc against the measurement so the class stays closed.
|
|
293
|
+
const costDoc = join(root, '..', 'docs', 'concepts', 'cost.md');
|
|
294
|
+
if (existsSync(costDoc)) {
|
|
295
|
+
const m = readFileSync(costDoc, 'utf8').match(/\*\*Current:\*\* (\d[\d,]*) chars/);
|
|
296
|
+
if (!m) {
|
|
297
|
+
errors.push('docs/concepts/cost.md: missing "**Current:** <n> chars" line for the measured index size');
|
|
298
|
+
} else {
|
|
299
|
+
const stated = parseInt(m[1].replace(/,/g, ''), 10);
|
|
300
|
+
if (stated !== totalDescChars) {
|
|
301
|
+
errors.push(`docs/concepts/cost.md states ${stated} index chars but content measures ${totalDescChars} — update the doc`);
|
|
302
|
+
} else {
|
|
303
|
+
console.log(`✓ cost.md index chars match measurement (${stated})`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
289
308
|
// --- docs-drift check: docs/skills.md and docs/agents.md must reference all content/ entries ---
|
|
290
309
|
const docsArg = process.argv.indexOf('--check-docs');
|
|
291
310
|
if (docsArg !== -1) {
|
|
@@ -374,15 +393,9 @@ if (integrityArg !== -1) {
|
|
|
374
393
|
}
|
|
375
394
|
}
|
|
376
395
|
}
|
|
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
|
-
}
|
|
396
|
+
if (!constants.includes('LANE_BASE_lean=7000')) errors.push('doc-integrity: source lane-base.sh lean base drifted (expected 7000)');
|
|
397
|
+
if (!constants.includes('BUDGET_full=50000')) errors.push('doc-integrity: source lane-base.sh full budget drifted (expected 50000)');
|
|
398
|
+
}
|
|
386
399
|
}
|
|
387
400
|
|
|
388
401
|
// Conditional-assertion guard: an expect() reachable only inside a truthiness
|