@ionivetech/mugiwara 0.6.4 → 0.6.6
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 -3
- package/.codex-plugin/plugin.json +48 -2
- package/.cursor-plugin/plugin.json +48 -2
- package/.kimi-plugin/plugin.json +48 -2
- package/.opencode/commands/mugiwara-continue.md +44 -31
- package/.opencode/commands/mugiwara-execute.md +1 -1
- package/.opencode/commands/mugiwara-heal.md +1 -1
- package/.opencode/commands/mugiwara-onboard.md +8 -9
- package/.opencode/commands/mugiwara-plan.md +1 -1
- package/.opencode/commands/mugiwara-review.md +1 -1
- package/.opencode/commands/mugiwara-security.md +1 -1
- package/.opencode/commands/mugiwara-ship.md +1 -1
- package/.opencode/commands/mugiwara.md +2 -2
- package/.opencode/mugiwara-helpers.mjs +3 -0
- package/.opencode/plugins/mugiwara.mjs +1 -1
- package/AGENTS.md +12 -2
- package/README.md +53 -32
- package/content/agents/brook-healing.md +5 -8
- package/content/agents/chopper-checkpoint.md +8 -11
- package/content/agents/eval-runner.md +2 -5
- package/content/agents/franky-gates.md +4 -7
- package/content/agents/jinbe-security.md +4 -7
- package/content/agents/luffy-orchestrator.md +13 -13
- package/content/agents/memory-keeper.md +5 -8
- package/content/agents/nami-planner.md +4 -7
- package/content/agents/resume-coordinator.md +7 -10
- package/content/agents/robin-reviewer.md +6 -9
- package/content/agents/sanji-quality.md +7 -10
- package/content/agents/skeptic-verifier.md +5 -8
- package/content/agents/usopp-brainstorm.md +3 -6
- package/content/agents/zoro-execution.md +7 -10
- package/content/skills/mugiwara-backend/SKILL.md +2 -0
- package/content/skills/mugiwara-checkpoint/SKILL.md +14 -11
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +4 -4
- package/content/skills/mugiwara-context-budget/SKILL.md +5 -3
- package/content/skills/mugiwara-contract-first/SKILL.md +2 -0
- package/content/skills/mugiwara-execution/SKILL.md +27 -27
- package/content/skills/mugiwara-execution/references/dispatch.md +2 -2
- package/content/skills/mugiwara-execution/references/resume-batching.md +1 -1
- package/content/skills/mugiwara-frontend/SKILL.md +2 -4
- package/content/skills/mugiwara-gates/SKILL.md +9 -7
- package/content/skills/mugiwara-git/SKILL.md +7 -2
- package/content/skills/mugiwara-healing/SKILL.md +6 -4
- package/content/skills/mugiwara-healing/references/workers.md +2 -2
- package/content/skills/mugiwara-lessons/SKILL.md +1 -0
- package/content/skills/mugiwara-orchestration/SKILL.md +34 -35
- package/content/skills/mugiwara-orchestration/references/check-ins.md +17 -16
- package/content/skills/mugiwara-orchestration/references/closure.md +5 -5
- package/content/skills/mugiwara-orchestration/references/output-contract.md +77 -0
- package/content/skills/mugiwara-orchestration/references/triage-escalation.md +22 -22
- package/content/skills/mugiwara-planning/SKILL.md +3 -1
- package/content/skills/mugiwara-planning/references/plan-template.md +2 -2
- package/content/skills/mugiwara-pr/SKILL.md +6 -6
- package/content/skills/mugiwara-pr/references/verdict-format.md +3 -3
- package/content/skills/mugiwara-quality/SKILL.md +8 -7
- package/content/skills/mugiwara-resume/SKILL.md +19 -20
- package/content/skills/mugiwara-review/SKILL.md +6 -1
- 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 +5 -1
- package/content/skills/mugiwara-ship/SKILL.md +1 -1
- package/content/skills/mugiwara-ship/references/cleanup.md +1 -1
- package/content/skills/mugiwara-testcases/SKILL.md +5 -3
- package/content/skills/mugiwara-workflow/SKILL.md +38 -39
- package/content/skills/mugiwara-workflow/references/workspace-layout.md +15 -15
- package/content/skills/using-mugiwara/SKILL.md +5 -5
- package/dist/mugiwara.js +807 -31
- package/gemini-extension.json +1 -1
- package/hooks/auto-savepoint.js +106 -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 +182 -0
- package/hooks/pipeline-guard.ts +267 -0
- package/hooks/session-start.js +106 -0
- package/hooks/session-start.ts +44 -32
- package/package.json +7 -3
- package/plugin.json +1 -1
- package/references/agent-protocol.md +15 -0
- package/references/complexity.md +54 -0
- package/references/definition-of-done.md +1 -1
- package/references/multi-actor.md +5 -5
- package/references/skill-versioning.md +1 -1
- package/references/token-budget.md +30 -8
- package/references/wave-banners.md +21 -31
- package/scripts/build-hooks.ts +56 -0
- package/scripts/conformance.ts +232 -0
- package/scripts/coverage-gate.ts +179 -0
- package/scripts/evidence.sh +17 -4
- package/scripts/gate-selftest.ts +14 -0
- package/scripts/initiative.ts +51 -17
- package/scripts/install.ps1 +5 -4
- package/scripts/install.sh +4 -4
- package/scripts/lane.sh +6 -2
- package/scripts/lib/patterns.sh +84 -0
- package/scripts/mission-report.sh +12 -11
- package/scripts/savepoint.sh +152 -37
- package/scripts/validate-content.ts +22 -2
- package/scripts/verify-install.ts +154 -44
- package/src/args.ts +1 -1
- package/src/cli.ts +217 -0
- package/src/continue.ts +243 -0
- package/src/installer.ts +39 -0
- package/src/onboard.ts +207 -0
- package/src/run.ts +82 -0
- package/src/targets/claude.ts +140 -2
- package/src/targets/copilot.ts +20 -1
- package/src/targets/generic.ts +9 -4
- package/content/agents/onboarding-guide.md +0 -124
- package/content/skills/mugiwara-security/references/owasp-mapping.md +0 -30
- package/scripts/onboard.ts +0 -266
- package/scripts/probe.ts +0 -40
package/scripts/lane.sh
CHANGED
|
@@ -19,9 +19,13 @@ if ! git rev-parse "$BASE" >/dev/null 2>&1; then
|
|
|
19
19
|
[ -n "$ALT" ] && BASE="$ALT" || BASE="HEAD~1"
|
|
20
20
|
fi
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
# union of committed + staged + unstaged + untracked (F) — see patterns.sh
|
|
23
|
+
CHANGED=$(changed_files "$BASE")
|
|
23
24
|
FILE_COUNT=0
|
|
24
25
|
[ -n "$CHANGED" ] && FILE_COUNT=$(echo "$CHANGED" | wc -l | tr -d ' ')
|
|
26
|
+
read -r ADDED_INS ADDED_DEL <<EOF
|
|
27
|
+
$(changed_loc "$BASE")
|
|
28
|
+
EOF
|
|
25
29
|
|
|
26
30
|
SENSITIVE=$(echo "$CHANGED" | grep -E "$SENSITIVE_PATS" 2>/dev/null | tr '\n' ',' | sed 's/,$//' || true)
|
|
27
31
|
HAS_SENSITIVE=0
|
|
@@ -35,7 +39,7 @@ if [ "$FILE_COUNT" -eq 0 ] 2>/dev/null; then
|
|
|
35
39
|
LANE="direct"
|
|
36
40
|
REASON="no changed files"
|
|
37
41
|
elif [ "$FILE_COUNT" -le 1 ] 2>/dev/null; then
|
|
38
|
-
ADDED
|
|
42
|
+
ADDED="${ADDED_INS:-0}"
|
|
39
43
|
if [ "$ADDED" -lt 20 ] 2>/dev/null; then
|
|
40
44
|
LANE="direct"
|
|
41
45
|
REASON="1 file, <20 LOC"
|
package/scripts/lib/patterns.sh
CHANGED
|
@@ -19,3 +19,87 @@ SENSITIVE_PATS="auth/|oauth2?/|payment/|payments/|billing/|crypto/|secrets/|cred
|
|
|
19
19
|
# outside this surface are docs/config/asset and never escalate to full on
|
|
20
20
|
# count alone (path-weighted sizing).
|
|
21
21
|
PRODUCT_PAT="^content/|^src/|^scripts/|^test/|^hooks/|^\.opencode/|^\.claude/|^evals/"
|
|
22
|
+
|
|
23
|
+
# Installed-harness rules dirs — what `mugiwara install` writes into a project
|
|
24
|
+
# (~50 files across the 9 targets). In a CONSUMER project these are installed
|
|
25
|
+
# config, no different from the crew's own .mugiwara/ bookkeeping: the first
|
|
26
|
+
# savepoint after an install counted them and sized every fresh mission Lane 3
|
|
27
|
+
# before a single line of the user's own work existed. In MUGIWARA'S OWN repo
|
|
28
|
+
# the same dirs are the product surface (they are in PRODUCT_PAT), so the
|
|
29
|
+
# exclusion is conditional on the repo identity, never unconditional.
|
|
30
|
+
# Unanchored on purpose — call sites anchor with ^ (name lists) or a tab
|
|
31
|
+
# (numstat rows).
|
|
32
|
+
HARNESS_PAT="\.claude/|\.opencode/|\.github/(instructions|agents)/|\.gemini/mugiwara/|\.codex/mugiwara/|\.devin/rules/|\.clinerules/|\.kilo/rules/|\.agents/rules/"
|
|
33
|
+
|
|
34
|
+
# harness_excluded — true (exit 0) when harness rules dirs must NOT count
|
|
35
|
+
# toward mission sizing, i.e. this repo is not mugiwara itself. Identity comes
|
|
36
|
+
# from the repo root package.json name ("mugiwara" or "@scope/mugiwara"), the
|
|
37
|
+
# same manifest savepoint already trusts for skill_version.
|
|
38
|
+
harness_excluded() {
|
|
39
|
+
local root
|
|
40
|
+
root=$(git rev-parse --show-toplevel 2>/dev/null || echo .)
|
|
41
|
+
! grep -qE '"name"[[:space:]]*:[[:space:]]*"(@[^"/]+/)?mugiwara"' "$root/package.json" 2>/dev/null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# drop_harness <anchor> — filter stdin, dropping harness paths in consumer
|
|
45
|
+
# repos and passing everything through in mugiwara's own.
|
|
46
|
+
drop_harness() {
|
|
47
|
+
if harness_excluded; then grep -vE "$1($HARNESS_PAT)" || true; else cat; fi
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# --- working-tree-aware change set (F) -------------------------------------
|
|
51
|
+
# `git diff BASE..HEAD` alone is blind to uncommitted work, and the old
|
|
52
|
+
# `|| git diff --cached` fallback was dead code (|| fires on non-zero exit,
|
|
53
|
+
# never on empty output). With auto_commit=off nothing is committed, so lane
|
|
54
|
+
# sizing and files_touched read 0 while the tree holds real changes.
|
|
55
|
+
# These two helpers are the single source of truth for BOTH lane.sh and
|
|
56
|
+
# savepoint.sh so the two can never drift (lane-integrity case 15).
|
|
57
|
+
|
|
58
|
+
# Path of the crew's bookkeeping dir, relative to the repo root. MUGIWARA_DIR
|
|
59
|
+
# may be absolute (the harness passes a full path); git reports repo-relative
|
|
60
|
+
# paths, so normalize before comparing.
|
|
61
|
+
mugiwara_rel() {
|
|
62
|
+
local d="${MUGIWARA_DIR:-.mugiwara}" root
|
|
63
|
+
case "$d" in
|
|
64
|
+
/*) root=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
|
65
|
+
[ -n "$root" ] && d="${d#"$root"/}"
|
|
66
|
+
# symlinked repo roots (/var vs /private/var on macOS) defeat the
|
|
67
|
+
# prefix strip; fall back to the leaf name, which is what git reports.
|
|
68
|
+
case "$d" in /*) d=$(basename "$d") ;; esac ;;
|
|
69
|
+
esac
|
|
70
|
+
printf '%s' "$d"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# changed_files <base-ref> — union of committed (BASE..HEAD), staged, unstaged
|
|
74
|
+
# and untracked files. .gitignore'd files are excluded (git status default),
|
|
75
|
+
# and so is the crew's own .mugiwara/ bookkeeping: savepoint writes state on
|
|
76
|
+
# every wave, and mission state must never size the mission it measures.
|
|
77
|
+
changed_files() {
|
|
78
|
+
local base="${1:-}"
|
|
79
|
+
{
|
|
80
|
+
if [ -n "$base" ] && [ "$base" != "unknown" ]; then
|
|
81
|
+
git diff --name-only "$base"..HEAD 2>/dev/null || true
|
|
82
|
+
fi
|
|
83
|
+
# porcelain covers staged + unstaged + untracked in one pass; strip the
|
|
84
|
+
# 2-char status + space, and keep the destination side of a rename.
|
|
85
|
+
git status --porcelain --untracked-files=all 2>/dev/null | sed 's/^...//; s/^.* -> //' || true
|
|
86
|
+
} | grep -v '^[[:space:]]*$' | grep -v "^$(mugiwara_rel)/" | drop_harness '^' | sort -u
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# changed_loc <base-ref> — echoes "<insertions> <deletions>" over the same
|
|
90
|
+
# union. Untracked files count every line as an insertion.
|
|
91
|
+
changed_loc() {
|
|
92
|
+
local base="${1:-}" MUGI_REL
|
|
93
|
+
MUGI_REL=$(mugiwara_rel)
|
|
94
|
+
{
|
|
95
|
+
if [ -n "$base" ] && [ "$base" != "unknown" ]; then
|
|
96
|
+
git diff --numstat "$base"..HEAD 2>/dev/null || true
|
|
97
|
+
fi
|
|
98
|
+
git diff --numstat HEAD 2>/dev/null || true
|
|
99
|
+
git ls-files --others --exclude-standard 2>/dev/null | while IFS= read -r f; do
|
|
100
|
+
[ -f "$f" ] || continue
|
|
101
|
+
case "$f" in "$MUGI_REL"/*) continue ;; esac
|
|
102
|
+
printf '%s\t0\t%s\n' "$(wc -l < "$f" 2>/dev/null | tr -d ' ' || echo 0)" "$f"
|
|
103
|
+
done
|
|
104
|
+
} | grep -v " $MUGI_REL/" | drop_harness ' ' | awk '{ if ($1 ~ /^[0-9]+$/) i+=$1; if ($2 ~ /^[0-9]+$/) d+=$2 } END { print (i+0)" "(d+0) }'
|
|
105
|
+
}
|
|
@@ -70,11 +70,12 @@ const now = new Date().toISOString().slice(0, 10);
|
|
|
70
70
|
const reportFile = path.join(reportDir, now + "-" + mission + ".md");
|
|
71
71
|
const lane = val(s, 'lane', 'n/a');
|
|
72
72
|
const lanePeak = val(s, 'lane_peak', 'n/a');
|
|
73
|
+
const laneRose = val(s, 'lane_rose', false);
|
|
73
74
|
const laneReason = val(s, 'lane_reason', 'n/a');
|
|
74
75
|
const mode = val(s, 'mode', 'n/a');
|
|
75
76
|
const actor = val(s, 'actor', 'n/a');
|
|
76
77
|
const branch = val(s, 'branch', 'n/a');
|
|
77
|
-
const wave = val(s, 'wave', 'n/a');
|
|
78
|
+
const wave = val(s, 'flow', val(s, 'wave', 'n/a'));
|
|
78
79
|
const filesTouched = val(s, 'files_touched', 0);
|
|
79
80
|
const locDelta = val(s, 'loc_delta', 0);
|
|
80
81
|
const locChurn = val(s, 'loc_churn', 0);
|
|
@@ -91,12 +92,12 @@ const updated = val(s, 'updated_at', now);
|
|
|
91
92
|
|
|
92
93
|
// --- wave artifact scan: results/<mission>/NN-*.md with verdict sniffing ---
|
|
93
94
|
const WAVE_LABEL = {
|
|
94
|
-
"01-execution": "Execute (
|
|
95
|
-
"02-audit": "Checkpoint (
|
|
96
|
-
"03-quality": "Quality (
|
|
97
|
-
"04-gates": "Gates (
|
|
98
|
-
"05-healing": "Healing (
|
|
99
|
-
"06-closure": "Closure (
|
|
95
|
+
"01-execution": "Execute (Flow 3)",
|
|
96
|
+
"02-audit": "Checkpoint (Flow 4)",
|
|
97
|
+
"03-quality": "Quality (Flow 5)",
|
|
98
|
+
"04-gates": "Gates (Flow 6)",
|
|
99
|
+
"05-healing": "Healing (Flow 8)",
|
|
100
|
+
"06-closure": "Closure (Flow 9)",
|
|
100
101
|
"07-pr-verdict": "PR material",
|
|
101
102
|
};
|
|
102
103
|
|
|
@@ -227,11 +228,11 @@ let report = "# Mission: " + mission + " . " + now + "\n\n";
|
|
|
227
228
|
report += "## Mission header\n\n| Field | Value |\n|-------|-------|\n";
|
|
228
229
|
report += "| Mission | " + mission + " |\n";
|
|
229
230
|
report += "| Branch | " + branch + " |\n";
|
|
230
|
-
report += "| Lane | " + lane + " |\n";
|
|
231
|
+
report += "| Lane | " + lane + (laneRose ? " ⬆ ROSE" : "") + " |\n";
|
|
231
232
|
report += "| Lane peak | " + lanePeak + " |\n";
|
|
232
233
|
report += "| Lane reason | " + laneReason + " |\n";
|
|
233
234
|
report += "| Mode | " + mode + " |\n";
|
|
234
|
-
report += "|
|
|
235
|
+
report += "| Flow | " + wave + " |\n";
|
|
235
236
|
report += "| Actor | " + actor + " |\n\n";
|
|
236
237
|
|
|
237
238
|
report += "## Token budget\n\n| Field | Value |\n|-------|-------|\n";
|
|
@@ -252,7 +253,7 @@ report += filesTouched + " files, +" + locDelta + " LOC (" + locChurn + " churn)
|
|
|
252
253
|
if (sensitive.length) report += "\nSensitive paths: " + sensitive.join(", ");
|
|
253
254
|
report += "\n\n";
|
|
254
255
|
|
|
255
|
-
report += "##
|
|
256
|
+
report += "## Flow stages\n\n| Flow stage | Artifact | Verdict |\n|------|----------|---------|";
|
|
256
257
|
if (waveRows.length) {
|
|
257
258
|
for (const r of waveRows) report += "\n| " + r.label + " | `" + r.file + "` | " + r.verdict + " |";
|
|
258
259
|
} else {
|
|
@@ -277,7 +278,7 @@ report += "Blocker ledger rows: " + issueRows.length + (issueRows.length ? "\n"
|
|
|
277
278
|
report += "\n\n";
|
|
278
279
|
|
|
279
280
|
report += "## State\n\n| Field | Value |\n|-------|-------|\n";
|
|
280
|
-
report += "|
|
|
281
|
+
report += "| Flow | " + wave + " |\n";
|
|
281
282
|
report += "| Tasks | " + (tasks.done || 0) + "/" + (tasks.total || 0) + " done |\n";
|
|
282
283
|
report += "| Blockers open | " + blockers + " |\n";
|
|
283
284
|
report += "| Heal cycles | " + healCycle + " |\n";
|
package/scripts/savepoint.sh
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# wave 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]
|
|
6
|
+
# savepoint.sh <mission> [member] [wave] [mode] [lane]
|
|
7
7
|
set -u
|
|
8
8
|
|
|
9
9
|
die() { echo "savepoint: $*" >&2; exit 1; }
|
|
@@ -42,7 +42,62 @@ MISSION="${1:-${STATE_MISSION:-}}"
|
|
|
42
42
|
MEMBER="${2:-${STATE_MEMBER:-}}"
|
|
43
43
|
WAVE="${3:-${STATE_WAVE:-1}}"
|
|
44
44
|
MODE="${4:-${STATE_MODE:-guided}}"
|
|
45
|
-
|
|
45
|
+
# Triage lane (M7): the lane Luffy assigned at Flow 0. Without it savepoint
|
|
46
|
+
# recomputed the lane from file counts alone and silently discarded the
|
|
47
|
+
# triage decision — a Lane 3 mission recorded itself as "direct". Explicit
|
|
48
|
+
# lane acts as a FLOOR: the computed lane may still raise it, never lower it.
|
|
49
|
+
LANE_EXPLICIT="${5:-${STATE_LANE:-}}"
|
|
50
|
+
case "$LANE_EXPLICIT" in
|
|
51
|
+
""|direct|lean|standard|full|spike) ;;
|
|
52
|
+
*) die "invalid lane \"$LANE_EXPLICIT\" (one of: direct lean standard full spike)" ;;
|
|
53
|
+
esac
|
|
54
|
+
# Wave may be fractional in the docs (4.5 = the checkpoint between waves).
|
|
55
|
+
# parseInt("4.5")=4 while tr -cd '0-9' gives "45", so state and continue
|
|
56
|
+
# disagreed on the same mission. Normalize once, here, for both writers.
|
|
57
|
+
# Keep only digits and the decimal point, then take the integer part. Garbage
|
|
58
|
+
# input still sanitizes to 0 (the N2 contract the continue writer already had);
|
|
59
|
+
# a fractional wave now truncates identically in BOTH writers instead of
|
|
60
|
+
# splitting into 4 (parseInt) and 45 (digit-strip).
|
|
61
|
+
WAVE_INT=$(printf '%s' "$WAVE" | tr -cd '0-9.')
|
|
62
|
+
WAVE_INT=${WAVE_INT%%.*}
|
|
63
|
+
case "$WAVE_INT" in
|
|
64
|
+
''|*[!0-9]*) WAVE_INT=0 ;;
|
|
65
|
+
esac
|
|
66
|
+
|
|
67
|
+
# verbosity from config (project .mugiwara/config), default normal; env override
|
|
68
|
+
VERBOSITY="${STATE_VERBOSITY:-normal}"
|
|
69
|
+
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
70
|
+
CFG_VERBOSITY=$(grep -E '^verbosity=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
71
|
+
[ -n "$CFG_VERBOSITY" ] && VERBOSITY="$CFG_VERBOSITY"
|
|
72
|
+
fi
|
|
73
|
+
case "$VERBOSITY" in
|
|
74
|
+
normal|full) ;;
|
|
75
|
+
*) VERBOSITY="normal" ;;
|
|
76
|
+
esac
|
|
77
|
+
|
|
78
|
+
# heal_max_cycles from config (project .mugiwara/config), default 3; env override
|
|
79
|
+
HEAL_MAX_CYCLES="${STATE_HEAL_MAX_CYCLES:-3}"
|
|
80
|
+
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
81
|
+
CFG_HEAL_MAX=$(grep -E '^heal_max_cycles=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
82
|
+
[ -n "$CFG_HEAL_MAX" ] && HEAL_MAX_CYCLES="$CFG_HEAL_MAX"
|
|
83
|
+
fi
|
|
84
|
+
case "$HEAL_MAX_CYCLES" in
|
|
85
|
+
''|*[!0-9]*) HEAL_MAX_CYCLES=3 ;;
|
|
86
|
+
esac
|
|
87
|
+
[ "$HEAL_MAX_CYCLES" -lt 1 ] 2>/dev/null && HEAL_MAX_CYCLES=3
|
|
88
|
+
|
|
89
|
+
# delegate_threshold from config (project .mugiwara/config), default 60; env override
|
|
90
|
+
DELEGATE_THRESHOLD="${STATE_DELEGATE_THRESHOLD:-60}"
|
|
91
|
+
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
92
|
+
CFG_DELEGATE=$(grep -E '^delegate_threshold=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
93
|
+
[ -n "$CFG_DELEGATE" ] && DELEGATE_THRESHOLD="$CFG_DELEGATE"
|
|
94
|
+
fi
|
|
95
|
+
case "$DELEGATE_THRESHOLD" in
|
|
96
|
+
''|*[!0-9]*) DELEGATE_THRESHOLD=60 ;;
|
|
97
|
+
esac
|
|
98
|
+
[ "$DELEGATE_THRESHOLD" -lt 1 ] 2>/dev/null && DELEGATE_THRESHOLD=1
|
|
99
|
+
[ "$DELEGATE_THRESHOLD" -gt 100 ] 2>/dev/null && DELEGATE_THRESHOLD=100
|
|
100
|
+
ACTOR="${STATE_ACTOR:-${GIT_AUTHOR_NAME:-${GIT_ID:-${USER:-${USERNAME:-}}}}}"
|
|
46
101
|
BRANCH="$(git branch --show-current 2>/dev/null || echo 'unknown')"
|
|
47
102
|
|
|
48
103
|
# treat the legacy empty-actor placeholder '""' as "no member" (solo). The old
|
|
@@ -86,31 +141,25 @@ else
|
|
|
86
141
|
CONTINUE_FILE="$CONTINUE_DIR/state.json"
|
|
87
142
|
fi
|
|
88
143
|
|
|
89
|
-
[ -z "$MISSION" ] && die "usage: savepoint.sh <mission> [member] [wave] [mode]"
|
|
144
|
+
[ -z "$MISSION" ] && die "usage: savepoint.sh <mission> [member] [wave] [mode] [lane]"
|
|
90
145
|
[ -d .git ] || die "not a git repository"
|
|
91
146
|
|
|
92
147
|
# --- computed fields ---
|
|
93
148
|
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")
|
|
94
149
|
HEAD_SHA=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
95
150
|
|
|
96
|
-
|
|
151
|
+
# union of committed + staged + unstaged + untracked (F) — see patterns.sh
|
|
152
|
+
CHANGED_FILES=$(changed_files "$BASE_SHA")
|
|
97
153
|
FILES_TOUCHED=$( [ -n "$CHANGED_FILES" ] && echo "$CHANGED_FILES" | wc -l | tr -d ' ' || echo 0 )
|
|
98
154
|
|
|
99
|
-
LOC_INS
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
LOC_INS=$(( ${INS:-0} + 0 ))
|
|
108
|
-
LOC_DEL=$(( ${DEL:-0} + 0 ))
|
|
109
|
-
LOC_DELTA=$(( LOC_INS - LOC_DEL ))
|
|
110
|
-
# churn is insertion+deletion — refactors and deletions are work too (D4)
|
|
111
|
-
LOC_CHURN=$(( LOC_INS + LOC_DEL ))
|
|
112
|
-
fi
|
|
113
|
-
[ -z "$LOC_DELTA" ] && LOC_DELTA=0
|
|
155
|
+
read -r LOC_INS LOC_DEL <<EOF
|
|
156
|
+
$(changed_loc "$BASE_SHA")
|
|
157
|
+
EOF
|
|
158
|
+
LOC_INS=$(( ${LOC_INS:-0} + 0 ))
|
|
159
|
+
LOC_DEL=$(( ${LOC_DEL:-0} + 0 ))
|
|
160
|
+
LOC_DELTA=$(( LOC_INS - LOC_DEL ))
|
|
161
|
+
# churn is insertion+deletion — refactors and deletions are work too (D4)
|
|
162
|
+
LOC_CHURN=$(( LOC_INS + LOC_DEL ))
|
|
114
163
|
|
|
115
164
|
SENSITIVE_PATHS=$(echo "$CHANGED_FILES" | grep -E "$SENSITIVE_PATS" 2>/dev/null | tr '\n' ',' | sed 's/,$//' || true)
|
|
116
165
|
|
|
@@ -132,7 +181,7 @@ elif [ "$FILES_TOUCHED" -ge 2 ] 2>/dev/null; then
|
|
|
132
181
|
LANE_REASON="$FILES_TOUCHED files"
|
|
133
182
|
elif [ "$FILES_TOUCHED" -eq 1 ] 2>/dev/null; then
|
|
134
183
|
# 1-file rule mirrors lane.sh: >=20 added LOC -> lean, else direct
|
|
135
|
-
ADDED
|
|
184
|
+
ADDED="$LOC_INS"
|
|
136
185
|
if [ "${ADDED:-0}" -ge 20 ] 2>/dev/null; then
|
|
137
186
|
LANE="lean"
|
|
138
187
|
LANE_REASON="1 file, $ADDED LOC"
|
|
@@ -157,6 +206,19 @@ if [ "$LANE" = "full" ] && [ -z "$SENSITIVE_PATHS" ] && [ -n "$CHANGED_FILES" ];
|
|
|
157
206
|
fi
|
|
158
207
|
fi
|
|
159
208
|
|
|
209
|
+
# explicit triage lane (M7) applied as a floor — the computed lane above may
|
|
210
|
+
# raise it, never lower it. Sensitive-path escalation still wins
|
|
211
|
+
# unconditionally: spike is a resize, not a rise, so it never displaces the
|
|
212
|
+
# full lane a sensitive path forced.
|
|
213
|
+
if [ -n "$LANE_EXPLICIT" ]; then
|
|
214
|
+
if [ "$LANE_EXPLICIT" = "spike" ] && [ -n "$SENSITIVE_PATHS" ]; then
|
|
215
|
+
LANE_REASON="$LANE_REASON (explicit spike ignored — sensitive paths)"
|
|
216
|
+
elif [ "$(lane_rank "$LANE_EXPLICIT")" -gt "$(lane_rank "$LANE")" ]; then
|
|
217
|
+
LANE_REASON="triage lane $LANE_EXPLICIT (floor; computed $LANE — $LANE_REASON)"
|
|
218
|
+
LANE="$LANE_EXPLICIT"
|
|
219
|
+
fi
|
|
220
|
+
fi
|
|
221
|
+
|
|
160
222
|
# --- lane-rise compare (F9) + monotonic clamp (D2): read previous state ---
|
|
161
223
|
# Clamp runs here — before LANE_BASE/BUDGET — so tokens and budget follow the
|
|
162
224
|
# held lane, never the raw shrunken one.
|
|
@@ -207,7 +269,11 @@ if [ -n "$PLAN_FILE" ] && [ -f "$PLAN_FILE" ]; then
|
|
|
207
269
|
fi
|
|
208
270
|
|
|
209
271
|
# blocker count
|
|
210
|
-
|
|
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)
|
|
211
277
|
BLOCKERS_OPEN=0
|
|
212
278
|
if [ -n "$BLOCKERS_FILE" ] && [ -f "$BLOCKERS_FILE" ]; then
|
|
213
279
|
# data rows start with a wave number; header "| wave |" and separator
|
|
@@ -215,16 +281,39 @@ if [ -n "$BLOCKERS_FILE" ] && [ -f "$BLOCKERS_FILE" ]; then
|
|
|
215
281
|
BLOCKERS_OPEN=$(grep -cE '^\| ?[0-9]+ ?\|' "$BLOCKERS_FILE" 2>/dev/null || true)
|
|
216
282
|
fi
|
|
217
283
|
|
|
218
|
-
# heal cycle — count
|
|
219
|
-
#
|
|
220
|
-
# cause a
|
|
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.
|
|
221
291
|
HEAL_CYCLE=1
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
292
|
+
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
|
|
305
|
+
if [ "$HEAL_COUNT" -gt 0 ]; then
|
|
225
306
|
HEAL_CYCLE=$((HEAL_COUNT + 1))
|
|
226
307
|
fi
|
|
227
308
|
|
|
309
|
+
# heal halt — computed from the heal counter and the config cap (task 4f wires
|
|
310
|
+
# heal_max_cycles into the counter's home). heal_cycle starts at 1, so
|
|
311
|
+
# heal_cycle >= heal_max_cycles means the cap is reached: escalate, never re-run.
|
|
312
|
+
HEAL_HALT=false
|
|
313
|
+
if [ "$HEAL_CYCLE" -ge "$HEAL_MAX_CYCLES" ] 2>/dev/null; then
|
|
314
|
+
HEAL_HALT=true
|
|
315
|
+
fi
|
|
316
|
+
|
|
228
317
|
# evidence paths — per-mission results folder (quoted printf, no sed — mission
|
|
229
318
|
# name is allowlisted above, but avoid sed metacharacter semantics entirely)
|
|
230
319
|
EVIDENCE=""
|
|
@@ -233,12 +322,19 @@ for evf in "$MUGIWARA_DIR/results/${MISSION}/"*.md; do
|
|
|
233
322
|
EVIDENCE="${EVIDENCE}${EVIDENCE:+,}.mugiwara/results/${MISSION}/$(basename "$evf")"
|
|
234
323
|
done
|
|
235
324
|
|
|
236
|
-
# skill version
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
325
|
+
# skill version — MUGIWARA's own package.json, resolved from the script
|
|
326
|
+
# location (B3). The old "$MUGIWARA_DIR/../package.json" was the USER's
|
|
327
|
+
# project manifest: the version-mismatch resume prompt fired when a user
|
|
328
|
+
# shipped their own v2.0.0 and stayed silent across every real mugiwara
|
|
329
|
+
# release. Never fall back to the user's file — "unknown" is the honest
|
|
330
|
+
# answer. Full version, not the major: mugiwara's breaking changes land in
|
|
331
|
+
# 0.x minors, which a major-only string can never see.
|
|
332
|
+
SKILL_VERSION="unknown"
|
|
333
|
+
PKG_JSON="$(dirname "$0")/../package.json"
|
|
334
|
+
if [ -f "$PKG_JSON" ]; then
|
|
335
|
+
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")
|
|
241
336
|
fi
|
|
337
|
+
[ -z "$SKILL_VERSION" ] && SKILL_VERSION="unknown"
|
|
242
338
|
|
|
243
339
|
# tokens proxy (F7): deterministic estimate when the harness does not report
|
|
244
340
|
# real usage. Monotonic beats precise — LANE_BASE stands in for the skills
|
|
@@ -281,6 +377,19 @@ elif [ "$BUDGET" -gt 0 ] && [ "$TOKENS_EST" -ge "$WARN_AT" ] 2>/dev/null; then
|
|
|
281
377
|
STATUS="warn"
|
|
282
378
|
fi
|
|
283
379
|
|
|
380
|
+
# delegate due — the execution skill's context-pressure trigger (task 4f wires
|
|
381
|
+
# delegate_threshold into a computed flag). tokens_est >= threshold% of budget
|
|
382
|
+
# → remaining sequential tasks dispatch to workers. Threshold stays relative:
|
|
383
|
+
# a bigger budget raises the bar, it does not remove it. Budget 0 (unknown
|
|
384
|
+
# lane) → never due.
|
|
385
|
+
DELEGATE_DUE=false
|
|
386
|
+
if [ "$BUDGET" -gt 0 ] 2>/dev/null; then
|
|
387
|
+
DELEGATE_AT=$(( BUDGET * DELEGATE_THRESHOLD / 100 ))
|
|
388
|
+
if [ "$TOKENS_EST" -ge "$DELEGATE_AT" ] 2>/dev/null; then
|
|
389
|
+
DELEGATE_DUE=true
|
|
390
|
+
fi
|
|
391
|
+
fi
|
|
392
|
+
|
|
284
393
|
mkdir -p "$MUGIWARA_DIR/state/$MISSION" "$MUGIWARA_DIR/continue/$MISSION"
|
|
285
394
|
|
|
286
395
|
node -e "
|
|
@@ -294,8 +403,9 @@ const data = {
|
|
|
294
403
|
lane_prev: process.argv[24] || null,
|
|
295
404
|
lane_peak: process.argv[27] || null,
|
|
296
405
|
lane_rose: process.argv[25] === 'true',
|
|
297
|
-
|
|
406
|
+
flow: parseInt(process.argv[6], 10),
|
|
298
407
|
mode: process.argv[7],
|
|
408
|
+
verbosity: process.argv[32] || 'normal',
|
|
299
409
|
base_sha: process.argv[8],
|
|
300
410
|
head_sha: process.argv[9],
|
|
301
411
|
files_touched: parseInt(process.argv[10], 10),
|
|
@@ -307,6 +417,10 @@ const data = {
|
|
|
307
417
|
tasks: { done: parseInt(process.argv[13], 10), total: parseInt(process.argv[14], 10) },
|
|
308
418
|
blockers_open: parseInt(process.argv[15], 10),
|
|
309
419
|
heal_cycle: parseInt(process.argv[16], 10),
|
|
420
|
+
heal_max_cycles: parseInt(process.argv[33], 10) || 3,
|
|
421
|
+
heal_halt: process.argv[34] === 'true',
|
|
422
|
+
delegate_threshold: parseInt(process.argv[35], 10) || 60,
|
|
423
|
+
delegate_due: process.argv[36] === 'true',
|
|
310
424
|
tokens_est: parseInt(process.argv[17], 10) || 0,
|
|
311
425
|
tokens_source: process.argv[26] || 'computed',
|
|
312
426
|
budget: parseInt(process.argv[18], 10) || 0,
|
|
@@ -318,13 +432,14 @@ const data = {
|
|
|
318
432
|
require('fs').writeFileSync(process.argv[23], JSON.stringify(data, null, 2) + '\n');
|
|
319
433
|
" \
|
|
320
434
|
"$MISSION" "$ACTOR" "$BRANCH" "$LANE" "$LANE_REASON" \
|
|
321
|
-
"$
|
|
435
|
+
"$WAVE_INT" "$MODE" "$BASE_SHA" "$HEAD_SHA" "$FILES_TOUCHED" \
|
|
322
436
|
"$LOC_DELTA" "$SENSITIVE_PATHS" "$TASKS_DONE" "$TASKS_TOTAL" \
|
|
323
437
|
"$BLOCKERS_OPEN" "$HEAL_CYCLE" "$TOKENS_EST" "$BUDGET" \
|
|
324
438
|
"$STATUS" "$SKILL_VERSION" "$EVIDENCE" \
|
|
325
439
|
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
326
440
|
"$STATE_FILE" "$LANE_PREV" "$LANE_ROSE" "$TOKENS_SOURCE" "$LANE_PEAK" \
|
|
327
|
-
"$LOC_INS" "$LOC_DEL" "$LOC_CHURN" "$MEMBER"
|
|
441
|
+
"$LOC_INS" "$LOC_DEL" "$LOC_CHURN" "$MEMBER" "$VERBOSITY" \
|
|
442
|
+
"$HEAL_MAX_CYCLES" "$HEAL_HALT" "$DELEGATE_THRESHOLD" "$DELEGATE_DUE"
|
|
328
443
|
|
|
329
444
|
if [ "$LANE_ROSE" = true ]; then
|
|
330
445
|
echo "⚠ LANE ROSE: $LANE_PREV → $LANE ($LANE_REASON) — escalate per check-in protocol"
|
|
@@ -345,7 +460,7 @@ if [ -n "$MISSION" ]; then
|
|
|
345
460
|
fi
|
|
346
461
|
# N2: every field echoed into continue is validated first. MISSION + MEMBER
|
|
347
462
|
# are allowlisted upstream; WAVE numeric, MODE enum, BRANCH slug-sanitized.
|
|
348
|
-
CONT_WAVE
|
|
463
|
+
CONT_WAVE="$WAVE_INT"
|
|
349
464
|
case "$MODE" in
|
|
350
465
|
guided|semi|auto) CONT_MODE="$MODE" ;;
|
|
351
466
|
*) CONT_MODE="guided" ;;
|
|
@@ -358,7 +473,7 @@ if [ -n "$MISSION" ]; then
|
|
|
358
473
|
member: process.argv[2] || null,
|
|
359
474
|
actor: process.argv[13] || '',
|
|
360
475
|
branch: process.argv[3],
|
|
361
|
-
|
|
476
|
+
flow: parseInt(process.argv[4], 10) || 0,
|
|
362
477
|
mode: process.argv[5],
|
|
363
478
|
tasks_done: parseInt(process.argv[6], 10) || 0,
|
|
364
479
|
tasks_total: parseInt(process.argv[7], 10) || 0,
|
|
@@ -207,10 +207,15 @@ for (const f of [...agentFiles, ...skillDirs.map(d => join(root, 'skills', d, 'S
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
// --- manifest-sync check:
|
|
210
|
+
// --- manifest-sync check: every marketplace plugin.json set-equal content/ ---
|
|
211
211
|
const manifestArg = process.argv.indexOf('--check-manifest');
|
|
212
212
|
if (manifestArg !== -1) {
|
|
213
|
-
const manifests = [
|
|
213
|
+
const manifests = [
|
|
214
|
+
'.claude-plugin/plugin.json',
|
|
215
|
+
'.codex-plugin/plugin.json',
|
|
216
|
+
'.cursor-plugin/plugin.json',
|
|
217
|
+
'.kimi-plugin/plugin.json',
|
|
218
|
+
];
|
|
214
219
|
let manifestErrors = 0;
|
|
215
220
|
|
|
216
221
|
for (const mp of manifests) {
|
|
@@ -380,5 +385,20 @@ if (integrityArg !== -1) {
|
|
|
380
385
|
}
|
|
381
386
|
}
|
|
382
387
|
|
|
388
|
+
// Conditional-assertion guard: an expect() reachable only inside a truthiness
|
|
389
|
+
// check silently passes when the value is absent. This class produced 9 defects.
|
|
390
|
+
// Allowed: checks keyed on a declared invariant (tier, fixture keys).
|
|
391
|
+
const ALLOWED_COND = /if \((?:t\.tier === 3|keys\.length|label === 'exact'|fx\.expect\.(?:lane|sensitive_paths_(?:min|max)))/;
|
|
392
|
+
const repoRoot = join(import.meta.dirname, '..');
|
|
393
|
+
for (const f of readdirSync(join(repoRoot, 'test')).filter(x => x.endsWith('.test.ts'))) {
|
|
394
|
+
const src = readFileSync(join(repoRoot, 'test', f), 'utf8');
|
|
395
|
+
// matches both braced and brace-less conditionals whose body reaches an expect()
|
|
396
|
+
for (const m of src.matchAll(/if \([^)]+\)(?:\s*\{[^}]{0,400}?)?expect\(/gs)) {
|
|
397
|
+
if (!ALLOWED_COND.test(m[0])) {
|
|
398
|
+
errors.push(`test/${f}: expect() inside a conditional — assert presence explicitly instead:\n ${m[0].slice(0, 80)}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
383
403
|
if (errors.length) { console.error(errors.map(e => `✗ ${e}`).join('\n')); process.exit(1); }
|
|
384
404
|
console.log(`✓ content valid: ${skillDirs.length} skills, ${agentFiles.length} agents`);
|