From mmp
Run mmp health checks: required upstream plugins, gh auth, repo state, config validity, status.yaml integrity. Reports a green-check matrix. Exit non-zero on hard problems.
How this command is triggered — by the user, by Claude, or both
Slash command
/mmp:check [--fix]The summary Claude sees in its command listing — used to decide when to auto-load this command
**Arguments:**
- `--fix` — attempt auto-repair of safe findings via `scripts/doctor-fix.sh`.
# /mmp:check
Perform health check. Output is a checklist of ✓ / ⚠ / ✗ items.
## Checks
<!-- v0.6-shared-INSERT-AFTER:checks-block-start -->
0. **Pre-flight tooling (auto-detect + offer install)**
Run `bash "${CLAUDE_PLUGIN_ROOT}/scripts/preflight-check.sh"`. If any
`present: false` or `bash.major_ok: false` is reported:
- If `--fix` flag was passed: run `bash "${CLAUDE_PLUGIN_ROOT}/scripts/preflight-fix.sh" --plan`,
show the plan to the user via `AskUserQuestion`:
"Folgende ...Arguments:
--fix — attempt auto-repair of safe findings via scripts/doctor-fix.sh.Perform health check. Output is a checklist of ✓ / ⚠ / ✗ items.
Pre-flight tooling (auto-detect + offer install)
Run bash "${CLAUDE_PLUGIN_ROOT}/scripts/preflight-check.sh". If any
present: false or bash.major_ok: false is reported:
--fix flag was passed: run bash "${CLAUDE_PLUGIN_ROOT}/scripts/preflight-fix.sh" --plan,
show the plan to the user via AskUserQuestion:
"Folgende Tools fehlen: . Auto-installieren via <command>?"yes: run bash "${CLAUDE_PLUGIN_ROOT}/scripts/preflight-fix.sh" --apply --yes.no: report the manual install instructions and continue with
remaining checks.--fix was not passed: report the missing tools as ✗ with the
manual install command.Tools checked: bash (≥4), flock, gh, jq, yq, git.
Tooling
git --version
gh --version
jq --version
yq --version
yq must be the Go variant (mikefarah/yq), not the Python wrapper.
Verify by running:
echo 'a: 1' | yq -o=json '.a' 2>&1 | grep -q "^1$"
If this fails, flag as ✗ with: "yq must be the Go variant
(mikefarah/yq); install via brew install yq."
Git repo
git rev-parse --is-inside-work-tree → must be true.gh auth
gh auth status --active --hostname github.com exit 0 (Multi-Account-safe;
fällt für gh < 2.46 auf gh api user zurück, da --active dort fehlt).gh repo view --json name,owner exit 0Required upstream plugins — check existence of plugin cache dirs
using marketplace-agnostic globs (the cache is keyed by marketplace,
not GitHub org, so do not hardcode claude-plugins-official or any
other marketplace name). Use shell globbing such that any of the
following patterns matches at least one directory:
~/.claude/plugins/cache/*/superpowers/~/.claude/plugins/cache/*/code-simplifier/~/.claude/plugins/cache/*/code-review/Concretely, for each plugin name P in superpowers code-simplifier code-review, evaluate:
shopt -s nullglob
matches=( ~/.claude/plugins/cache/*/"$P" )
if (( ${#matches[@]} == 0 )); then
echo "✗ upstream plugin missing: $P"
else
echo "✓ upstream plugin present: $P (${matches[0]})"
fi
The canonical source today is the claude-plugins-official
marketplace, but the resolver must not depend on that name — only on
the plugin directory name.
5. mmp setup
.orchestrator/config.yaml exists and valid YAML..orchestrator/config.yaml has ticket_source.strategy set..orchestrator/config.yaml ticket_improvement.mode != live OR
diff_preview == per_ticket (else hard error).Status integrity (if status.yaml exists)
schema_version in status.yaml must be 2.
config_hash equals current sha256 of config.yaml. Mismatch → ⚠.worktree: verify
git worktree list --porcelain | grep -F <worktree> (note: literal
match). Missing → ✗.pr_number: gh pr view <num> succeeds. Closed
PR with phase != merge-ready → ⚠.Wave-lock provenance (#43)
.orchestrator/wave.lock.json exists. Missing → ✗ "no wave-lock; run
bash scripts/wave-lock.sh build <milestone> <wave> to create".bash "${CLAUDE_PLUGIN_ROOT}/scripts/wave-lock.sh" validate exit 0.
Non-zero → ✗ "wave.lock.json malformed; lock_schema_version != 1".bash "${CLAUDE_PLUGIN_ROOT}/scripts/wave-lock.sh" drift exit 0.
Exit 2 (drift) → ⚠ with drift lines from stdout.Audit-chain integrity (#45)
bash "${CLAUDE_PLUGIN_ROOT}/scripts/audit-verify.sh" exit 0.
audit-verify.sh --migrate .orchestrator/audit.jsonl to repair".Replay-tape readiness (#51 #52)
.orchestrator/.replay/ missing → ⚠ "no replay tapes; budget-cap +
replay-harness lose tamper-evident input"..orchestrator/.replay/<wave-id>/*.jsonl. Zero → ⚠.Secret-scan tooling (#48)
command -v gitleaks → present? If not → ⚠ "gitleaks not on PATH;
hooks/secret-scan.sh runs in fail-open mode (no actual scan)".Webhook receiver health (#53, optional)
config.ci.webhook_mode == smee (Mode A): smee process reachable?
Best-effort pgrep -f smee → ⚠ on miss.config.ci.webhook_mode == bridge (Mode B): unimplemented — ⚠
"webhook bridge mode declared in config but Mode B not implemented".webhook_mode unset / polling: skip silently.Domain-tag completeness
boundary.yaml under .orchestrator/chunks/:
check whether touches_domains key is present (may be []).bash scripts/migrate-v0.4-to-v0.5.sh to add empty default"[] → ok "[] (no cross-domain cuts declared)"Secret references
bash "${CLAUDE_PLUGIN_ROOT}/scripts/secret-preflight-check.sh" --no-audit.missing non-empty: ✗ "secret-preflight — missing secrets: . Run /mmp:run
to interactively resolve, or set via gh secret set <name>.".orchestrator/.overrides/secrets-skipped.json exists: ⚠
"secret-preflight skipped at ; missing were: "missing empty and no override: ✓ "secret references — all referenced
secrets defined".github/workflows/ absent and no issues reference secrets: ✓
"secret references — no secret refs found"Phase-history evidence integrity
status.yaml does not exist: skip silently.phase_history from status.yaml.phase_history absent or empty: ✓ "phase-history evidence — no entries (pre-ch09 or first run)"evidence, entries with absent/empty evidence.evidence is absent/empty: ⚠ (may be mid-write).evidence absent or empty: ✗ with detail from/to/ts.phase_history_evidence — <N> total, <M> with evidence, <K> without ✗ phase <from>→<to> at <ts>: evidence absentImplementation:
entry_count=$(yq '.phase_history | length' .orchestrator/status.yaml 2>/dev/null || echo 0)
if [ "$entry_count" -gt 0 ]; then
yq -o=json '.phase_history' .orchestrator/status.yaml \
| jq -c '.[]' \
| while IFS= read -r entry; do
from=$(echo "$entry" | jq -r '.from // "?"')
to=$(echo "$entry" | jq -r '.to // "?"')
ts=$(echo "$entry" | jq -r '.ts // "?"')
ev_count=$(echo "$entry" | jq '.evidence | if type == "object" then length else 0 end' 2>/dev/null || echo 0)
echo "$from|$to|$ts|$ev_count"
done
fi
Run command -v for each supported dead-code detection tool. These tools are
optional — the scripts/dead-code-check.sh script gracefully skips languages
whose tools are absent. Report ✓ if present, ⚠ if absent (no ✗; absence
is not a hard failure).
| Language | Tool(s) checked |
|---|---|
| Python (.py) | vulture |
| TypeScript/JS (.ts .tsx .js) | ts-prune, knip (either suffices) |
| Go (.go) | deadcode |
| Bash (.sh) | built-in grep heuristic (always available) |
Implementation:
# Bash heuristic — always available
echo "✓ dead-code-check — bash heuristic always available"
# Python
if command -v vulture >/dev/null 2>&1; then
echo "✓ dead-code-check — vulture present ($(vulture --version 2>&1 | head -1))"
else
echo "⚠ dead-code-check — vulture not on PATH; Python dead-code check will be skipped"
fi
# TypeScript/JS
if command -v ts-prune >/dev/null 2>&1; then
echo "✓ dead-code-check — ts-prune present"
elif command -v knip >/dev/null 2>&1; then
echo "✓ dead-code-check — knip present"
else
echo "⚠ dead-code-check — ts-prune/knip not on PATH; TS/JS dead-code check will be skipped"
fi
# Go
if command -v deadcode >/dev/null 2>&1; then
echo "✓ dead-code-check — deadcode present"
else
echo "⚠ dead-code-check — deadcode not on PATH; Go dead-code check will be skipped"
fi
This check reports tool availability only. To run the actual dead-code
analysis, use: bash "${CLAUDE_PLUGIN_ROOT}/scripts/dead-code-check.sh".
Policy: skills/orchestrating-milestones/references/dead-code-policy.md.
Implements ticket #86 (feat(discipline): Deferred-AC-Ledger).
Run SLA check on open deferred-ac issues:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/deferred-sla-check.sh" --sla-check
sla:7d open > 7 days → ⚠ one line per issuesla:30d open > 30 days → ⚠ one line per issuesla:next-wave → no numeric expiry; skipNo external notifications are sent — warnings are in-session only.
Policy: skills/orchestrating-milestones/references/deferred-ac-discipline.md
Removed in v0.9.4 (issue #351). Claim binding is now performed per-issue by
spec-claim-validator in Phase 0 Step 10a; no chunks.md-level cache file is
written. Sub-check intentionally empty.
For each chunk with a cross-cut-brief.md that has a ## Sentinel Markers
section: verify each listed marker is present in the corresponding file.
for brief in .orchestrator/chunks/*/cross-cut-brief.md; do
chunk=$(basename "$(dirname "$brief")")
# Extract file+marker rows from Sentinel Markers table
in_table=0
while IFS='|' read -r _ tfile tmarker ttype _; do
tfile=$(echo "$tfile" | xargs)
tmarker=$(echo "$tmarker" | xargs)
[ -z "$tfile" ] || [ -z "$tmarker" ] && continue
# Skip header row
echo "$tmarker" | grep -q '^Marker' && continue
if [ -f "$tfile" ] && grep -qF "$tmarker" "$tfile" 2>/dev/null; then
echo "✓ sentinel-marker — ${chunk}: marker present in ${tfile}"
else
echo "✗ sentinel-marker — marker \`${tmarker}\` missing in \`${tfile}\`"
fi
done < <(awk '/^## Sentinel Markers/{found=1; next} found && /^## /{exit} found{print}' "$brief" | grep '|' || true)
done
<m> missing in <file>"Run bash "${CLAUDE_PLUGIN_ROOT}/scripts/plugin-version-check.sh".
This check is advisory (WARN, not FAIL) in /mmp:check -- it does not fail the overall exit code. The hard-block lives in /mmp:run Pre-Flight.
Checks that the local Memory-Tool staging area is present and writable.
# Check 1: .orchestrator/.memory/ exists and is writable
if [ -d ".orchestrator/.memory" ]; then
if [ -w ".orchestrator/.memory" ]; then
echo "✓ memory-hygiene -- .orchestrator/.memory/ exists and writable"
else
echo "✗ memory-hygiene -- .orchestrator/.memory/ not writable"
fi
else
echo "⚠ memory-hygiene -- .orchestrator/.memory/ absent; run migrate-v0.4-to-v0.5.sh --step memory-layout"
fi
# Check 2: milestone-specific staging dir exists (if status.yaml present)
if [ -f ".orchestrator/status.yaml" ]; then
milestone=$(yq '.milestone.name' .orchestrator/status.yaml 2>/dev/null | grep -v "^null$" || echo "")
if [ -n "$milestone" ]; then
staging=".orchestrator/.memory/${milestone}"
if [ -d "$staging" ]; then
echo "✓ memory-hygiene -- staging dir present: $staging"
else
echo "⚠ memory-hygiene -- milestone staging absent: $staging; run state-sync.sh --memory-target $milestone"
fi
# Check staging files readable
for f in "status.yaml" "open-questions.md"; do
if [ -f "$staging/$f" ] && [ -r "$staging/$f" ]; then
echo "✓ memory-hygiene -- $staging/$f readable"
elif [ -f "$staging/$f" ]; then
echo "✗ memory-hygiene -- $staging/$f not readable"
fi
done
fi
fi
# Check 3: .orchestrator/.memory/ in .gitignore
if [ -f ".gitignore" ]; then
if grep -qF ".orchestrator/.memory" .gitignore; then
echo "✓ memory-hygiene -- .orchestrator/.memory/ is gitignored"
else
echo "⚠ memory-hygiene -- .orchestrator/.memory/ not in .gitignore; memory staging should not be tracked"
fi
fi
If .orchestrator/status.yaml and .orchestrator/last-compact-phase both exist:
Read phase_global from status.yaml and compare to the content of
last-compact-phase.
if [ -f ".orchestrator/status.yaml" ] && [ -f ".orchestrator/last-compact-phase" ]; then
current=$(yq '.phase_global // ""' .orchestrator/status.yaml 2>/dev/null \
| grep -v '^null$' | tr -d '[:space:]' || true)
marker=$(cat .orchestrator/last-compact-phase 2>/dev/null | tr -d '[:space:]' || true)
if [ -z "$current" ]; then
echo "⚠ compact-marker -- phase_global unset in status.yaml; skipping marker check"
elif [ "$marker" = "$current" ]; then
echo "✓ compact-marker -- last-compact-phase matches phase_global (${current})"
else
echo "⚠ compact-marker -- last-compact-phase (${marker}) behind phase_global (${current}); compact-hint may not have fired"
fi
elif [ -f ".orchestrator/status.yaml" ] && [ ! -f ".orchestrator/last-compact-phase" ]; then
echo "⚠ compact-marker -- no last-compact-phase marker; compact-hint has not fired yet (OK for new waves)"
fi
Check for mmp/integration/ branches older than 24 hours (stale after
composite-red investigation or interrupted Phase 5.5).
stale_found=0
remote_refs=$(git ls-remote --refs origin 'refs/heads/mmp/integration/*' 2>/dev/null) || {
echo "⚠ stale-integration-branch -- could not list remote refs (offline or no access)"
remote_refs=""
}
while IFS= read -r ref_line; do
[ -z "$ref_line" ] && continue
branch=$(echo "$ref_line" | awk '{print $2}' | sed 's|refs/heads/||')
[[ "$branch" == mmp/integration/* ]] || continue
# Get branch age via last commit timestamp
commit_ts=$(git log -1 --format="%ct" "origin/${branch}" 2>/dev/null || echo 0)
now=$(date +%s)
age_h=$(( (now - commit_ts) / 3600 ))
if (( age_h >= 24 )); then
echo "⚠ stale-integration-branch -- ${branch} is ${age_h}h old (>24h); delete with: git push origin --delete ${branch}"
stale_found=1
fi
done <<< "$remote_refs"
if [ "$stale_found" -eq 0 ] && [ -n "$remote_refs" ]; then
echo "✓ stale-integration-branch -- no stale mmp/integration/* branches found"
elif [ "$stale_found" -eq 0 ] && [ -z "$remote_refs" ]; then
# Either offline (warned above) or no integration branches
echo "✓ stale-integration-branch -- no mmp/integration/* branches found on remote"
fi
<branch> is <N>h old; delete with …"Check whether phase_global is blocked_after_partial_merge. If so, report
the stuck wave and list open compensations (those with revert_pr_number: null).
pg=$(yq '.phase_global // ""' .orchestrator/status.yaml 2>/dev/null || true)
if [ "$pg" = "blocked_after_partial_merge" ]; then
open_comps=$(yq '[.merge_compensations[] | select(.revert_pr_number == null)] | length' \
.orchestrator/status.yaml 2>/dev/null || echo 0)
milestone=$(yq '.milestone.name // "unknown"' .orchestrator/status.yaml 2>/dev/null || echo "unknown")
echo "⚠ blocked-partial-merge -- wave ${milestone} is stuck in blocked_after_partial_merge; ${open_comps} compensation(s) pending revert; run /mmp:resume-wave or trigger Saga-Compensation"
else
echo "✓ blocked-partial-merge -- no stuck blocked_after_partial_merge wave"
fi
phase_global = blocked_after_partial_merge: ⚠ "blocked-partial-merge — wave <name> is stuck; <N> compensation(s) pending revert; run /mmp:resume-wave or trigger Saga-Compensation"Check whether merge_strategy.mode matches actual repo merge-queue state.
if [ ! -f ".orchestrator/config.yaml" ]; then
echo "⚠ merge-queue-config -- no config.yaml found; skipping"
else
mode=$(yq '.merge_strategy.mode // "direct"' .orchestrator/config.yaml 2>/dev/null \
| grep -v '^null$' || echo "direct")
if [ "$mode" = "queue" ]; then
owner_repo=$(gh repo view --json owner,name --jq '"\(.owner.login)/\(.name)"' 2>/dev/null || echo "")
if [ -n "$owner_repo" ]; then
mq_enabled=$(gh api "repos/${owner_repo}" --jq '.merge_queue_enabled // false' 2>/dev/null || echo "false")
if [ "$mq_enabled" = "true" ]; then
echo "✓ merge-queue-config -- mode=queue and merge_queue_enabled=true on repo"
else
echo "⚠ merge-queue-config -- mode=queue but merge_queue_enabled=false on repo; Phase 7 will fail"
fi
else
echo "⚠ merge-queue-config -- could not determine repo owner/name (offline?); skipping merge-queue check"
fi
else
echo "✓ merge-queue-config -- mode=${mode} (no merge-queue check required)"
fi
fi
mode=queue + repo has queue enabled: ✓ "merge-queue-config — mode=queue and merge_queue_enabled=true on repo"mode=queue + repo has no queue: ⚠ "merge-queue-config — mode=queue but merge_queue_enabled=false on repo; Phase 7 will fail"mode != queue: ✓ "merge-queue-config — mode=<mode> (no merge-queue check required)"Verify all 4 skills referenced by the L0 skill-loading discipline are
discoverable in the plugin cache (same glob pattern as check §4 uses for
superpowers).
LC_ALL=C
echo "--- skill-presence check ---"
skill_fail=0
for skill_id in \
"verification-before-completion" \
"dispatching-parallel-agents" \
"using-git-worktrees" \
"finishing-a-development-branch"; do
shopt -s nullglob
matches=( ~/.claude/plugins/cache/*/superpowers/*/skills/${skill_id} )
shopt -u nullglob
if (( ${#matches[@]} > 0 )); then
echo "✓ skill-presence — superpowers:${skill_id} found (${matches[0]})"
else
# Fallback: search for SKILL.md with skill_id in path under any superpowers/ dir
found=0
for sp_dir in ~/.claude/plugins/cache/*/superpowers/; do
[ -d "$sp_dir" ] || continue
if find "$sp_dir" -name "SKILL.md" -path "*${skill_id}*" 2>/dev/null | grep -q .; then
found=1
break
fi
done
if [ "$found" -eq 1 ]; then
echo "✓ skill-presence — superpowers:${skill_id} found (nested path)"
else
echo "✗ skill-presence — superpowers:${skill_id} not found in plugin cache; install superpowers plugin"
skill_fail=1
fi
fi
done
[ "$skill_fail" -eq 0 ] || exit 1
<id> found (<path>)"<id> not found in plugin cache; install superpowers plugin"For each chunk with a worktree path in status.yaml, verify the worktree
HEAD is not behind origin/<base>:
LC_ALL=C
if [ ! -f ".orchestrator/status.yaml" ]; then
echo "⚠ worktree-base-sync -- no status.yaml; skipping"
else
base_branch=$(yq '.milestone.base_branch // "main"' .orchestrator/status.yaml 2>/dev/null \
| grep -v '^null$' || echo "main")
# Fetch latest origin state (quiet — check only, no modification)
git fetch origin "$base_branch" --quiet 2>/dev/null || true
origin_sha=$(git rev-parse "origin/${base_branch}" 2>/dev/null || echo "")
if [ -z "$origin_sha" ]; then
echo "⚠ worktree-base-sync -- could not resolve origin/${base_branch}; skipping"
else
stale_count=0
synced_count=0
while IFS= read -r chunk_json; do
chunk_id=$(echo "$chunk_json" | jq -r '.chunk_id // .id // "unknown"')
worktree=$(echo "$chunk_json" | jq -r '.worktree')
if [ ! -d "$worktree" ]; then
echo "⚠ worktree-base-sync -- ${chunk_id}: worktree path missing: ${worktree}"
stale_count=$((stale_count + 1))
continue
fi
wt_head=$(git -C "$worktree" rev-parse HEAD 2>/dev/null || echo "")
if [ -z "$wt_head" ]; then
echo "⚠ worktree-base-sync -- ${chunk_id}: cannot read HEAD in ${worktree}"
stale_count=$((stale_count + 1))
continue
fi
behind=$(git rev-list --count "${wt_head}..origin/${base_branch}" 2>/dev/null || echo "?")
if [ "$behind" = "0" ]; then
echo "✓ worktree-base-sync -- ${chunk_id}: in-sync with origin/${base_branch}"
synced_count=$((synced_count + 1))
else
echo "⚠ worktree-base-sync -- ${chunk_id}: ${behind} commit(s) behind origin/${base_branch}; run pre-dispatch-rebase manually"
stale_count=$((stale_count + 1))
fi
done < <(
yq -o=json '.chunks // []' .orchestrator/status.yaml 2>/dev/null \
| jq -c '.[] | select(.worktree != null and .worktree != "")'
)
if [ "$stale_count" -eq 0 ] && [ "$synced_count" -eq 0 ]; then
echo "✓ worktree-base-sync -- no active worktrees in status.yaml"
fi
fi
fi
<chunk>: in-sync with origin/<base>"<chunk>: <N> commit(s) behind origin/<base>; run pre-dispatch-rebase manually"If .orchestrator/evidence/ does not exist: ✓ "evidence-store — directory
absent (fail-open; pre-#85 wave)".
Otherwise, for each manifest under .orchestrator/evidence/<wave-id>/*/manifest.json:
if [ ! -d ".orchestrator/evidence" ]; then
echo "✓ evidence-store — directory absent (fail-open; pre-#85 wave)"
else
wave_id=$(yq '.milestone.name // ""' .orchestrator/status.yaml 2>/dev/null \
| grep -v '^null$' || echo "")
if [ -z "$wave_id" ]; then
echo "⚠ evidence-store — cannot determine wave-id from status.yaml; skipping"
else
issues_checked=0
issues_bad=0
for manifest in .orchestrator/evidence/"$wave_id"/*/manifest.json; do
[ -f "$manifest" ] || continue
issue=$(basename "$(dirname "$manifest")")
issues_checked=$((issues_checked + 1))
total=$(jq '.ac_checks | length' "$manifest" 2>/dev/null || echo 0)
verified=$(jq '[.ac_checks[] | select(.status == "VERIFIED")] | length' "$manifest" 2>/dev/null || echo 0)
if [ "$total" -eq 0 ]; then
echo "⚠ evidence-store — issue #${issue}: manifest has no ac_checks"
issues_bad=$((issues_bad + 1))
elif [ "$verified" -eq "$total" ]; then
echo "✓ evidence-store — issue #${issue}: ${verified}/${total} ACs VERIFIED"
else
pending=$((total - verified))
echo "✗ evidence-store — issue #${issue}: ${pending}/${total} ACs not VERIFIED"
issues_bad=$((issues_bad + 1))
fi
done
if [ "$issues_checked" -eq 0 ]; then
echo "✓ evidence-store — no manifests found for wave ${wave_id} (OK for in-progress waves)"
fi
fi
fi
<N>: <M>/<M> ACs VERIFIED"<N>: <K>/<M> ACs not VERIFIED"<N>: manifest has no ac_checks"For each chunk listed in .orchestrator/status.yaml, verify that the
corresponding boundary.yaml has a parallelizability field set to a
valid enum value (auto, always, never).
LC_ALL=C
if [ ! -f ".orchestrator/status.yaml" ]; then
echo "⚠ parallelizability-mode -- no status.yaml; skipping"
else
fail_count=0
warn_count=0
ok_count=0
while IFS= read -r chunk_id; do
[ -z "$chunk_id" ] && continue
bf=".orchestrator/chunks/${chunk_id}/boundary.yaml"
if [ ! -f "$bf" ]; then
echo "⚠ parallelizability-mode -- ${chunk_id}: boundary.yaml not found (skipping)"
warn_count=$((warn_count + 1))
continue
fi
pval=$(yq -r '.parallelizability // "absent"' "$bf" 2>/dev/null || echo "absent")
case "$pval" in
auto|always|never)
if [ "$pval" = "auto" ]; then
domains=$(yq -r '(.touches_domains // []) | length' "$bf" 2>/dev/null || echo 0)
if [ "${domains:-0}" -eq 0 ]; then
echo "⚠ parallelizability-mode -- ${chunk_id}: parallelizability=auto but touches_domains=[] (heuristic cannot evaluate; treating as parallel)"
warn_count=$((warn_count + 1))
else
echo "✓ parallelizability-mode -- ${chunk_id}: parallelizability=${pval}"
ok_count=$((ok_count + 1))
fi
else
echo "✓ parallelizability-mode -- ${chunk_id}: parallelizability=${pval}"
ok_count=$((ok_count + 1))
fi
;;
absent)
echo "⚠ parallelizability-mode -- ${chunk_id}: parallelizability field absent in boundary.yaml (treating as auto; run migrate-v0.4-to-v0.5.sh --step parallelizability)"
warn_count=$((warn_count + 1))
;;
*)
echo "✗ parallelizability-mode -- ${chunk_id}: invalid value '${pval}' (must be auto|always|never)"
fail_count=$((fail_count + 1))
;;
esac
done < <(yq -r '.chunks[].id // empty' .orchestrator/status.yaml 2>/dev/null)
if [ "$fail_count" -gt 0 ]; then
echo "✗ parallelizability-mode -- ${fail_count} chunk(s) with invalid parallelizability value"
elif [ "$warn_count" -gt 0 ]; then
echo "⚠ parallelizability-mode -- ${warn_count} warning(s) (see above)"
else
echo "✓ parallelizability-mode -- all ${ok_count} chunk(s) OK"
fi
fi
Pass: ✓ "parallelizability-mode — all N chunk(s) OK" Warn: ⚠ "parallelizability-mode — N warning(s)" Fail: ✗ "parallelizability-mode — N chunk(s) with invalid parallelizability value"
Reports availability of helper CLIs that mmp does NOT require itself, but which improve the surrounding developer experience. Absence is reported as ⚠ (advisory), never ✗.
Currently checked:
| CLI | Source | Purpose |
|---|---|---|
ctx | context-mode plugin | Backend for /context-mode:ctx-stats, ctx-doctor, ctx-purge, ctx-insight. mmp does not call it. |
if command -v ctx >/dev/null 2>&1; then
echo "✓ helper-cli — ctx present ($(ctx --version 2>&1 | head -1))"
else
echo "⚠ helper-cli — ctx not on PATH (optional; only needed for /context-mode:ctx-* slash commands; mmp itself does not require it)"
fi
<version>)"For each check, print one line:
✓ <check name> — <detail>⚠ <check name> — <detail>✗ <check name> — <detail>End with a summary line: <N> ok, <N> warn, <N> fail.
If any ✗, exit 1; otherwise 0.
Walk the checks in order. Use Bash tool for shell commands. Use Read for
config + status files. Use yq/jq for parsing. Don't use mcp tools for
this — it's a quick local check.
When called with --fix, after running all checks, additionally invoke the
safe auto-repair flow:
doctor-fix.sh --plan to list proposed fixes.AskUserQuestion: "Apply these N safe fixes? [yes|no]"yes: run doctor-fix.sh --apply --yes.no: skip auto-fix; report issues.bash "${CLAUDE_PLUGIN_ROOT}/scripts/doctor-fix.sh" --plan
# AskUserQuestion → confirm
bash "${CLAUDE_PLUGIN_ROOT}/scripts/doctor-fix.sh" --apply --yes
--yes is required when L0 invokes the script: stdin is non-interactive in
that context, so the script would otherwise refuse with exit 2.
doctor-fix.sh --apply --yes applies all safe fixes (mkdir missing dirs,
copy missing templates, append gitignore-snippet, chmod +x hook scripts).
It will NOT touch:
Those remaining issues still exit non-zero from /mmp:check.
Also add a new check: lock-issue health. For each milestone with an
existing lock-issue, verify body parses, holder-fields are consistent, and
no STOLEN comment without subsequent ACQUIRE. Report inconsistencies as ⚠
(not auto-fixable).
!bash -c '"${CLAUDE_PLUGIN_ROOT}/scripts/check-drift.sh"; rc=$?; "${CLAUDE_PLUGIN_ROOT}/scripts/audit-summary.sh" --since 24h || true; exit $rc'
When called with --determinism, after all standard checks, additionally run:
!bash "${CLAUDE_PLUGIN_ROOT}/scripts/check-determinism.sh"
Output all findings. If exit code 1 (ERROR findings), append one ✗ line:
✗ determinism-scan — <N> error(s) found; see findings above
If exit 0, append:
✓ determinism-scan — no ERROR-severity findings (<N> warn, <N> info)
The determinism scan does not count towards the standard check pass/fail — it adds one extra ✓/✗ item to the output matrix and is reflected in the final summary line count.
When called with --replay, after all standard checks, additionally run:
Check .orchestrator/.replay/ directory exists and is not empty.
If missing: WARN replay-tapes — no tapes found; run replay-events.sh write-tapes <wave-id> to populate
For the current wave-id (derived from status.yaml), run:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/replay-full-wave.sh" status <wave-id>
If REPLAY_READY N tapes: OK replay — N tapes available
If REPLAY_EMPTY: WARN replay — tape dir exists but empty
If REPLAY_MISSING: WARN replay — no tape dir for wave <wave-id>
Optionally (if --replay=full flag): run
bash "${CLAUDE_PLUGIN_ROOT}/scripts/replay-full-wave.sh" run <wave-id> --mode lenient
and report the summary (matched/mismatched/missing counts).
The --replay check does not count towards the standard pass/fail exit
code. It appends one or two extra OK/WARN items to the output matrix.
Verify dispatch.max_parallel_l1 in .orchestrator/config.yaml is set and
within the recommended range (≤ 3 per Wave-2-Lesson 2026-05-21).
LC_ALL=C
cfg=".orchestrator/config.yaml"
if [ -f "$cfg" ]; then
cap=$(yq -r '.dispatch.max_parallel_l1 // "missing"' "$cfg" 2>/dev/null)
case "$cap" in
missing)
echo "⚠ parallel-cap — dispatch.max_parallel_l1 not set; default 3 will apply"
;;
0)
echo "⚠ parallel-cap — dispatch.max_parallel_l1=0 (unlimited; NOT recommended per Wave-2-Lesson)"
;;
[1-3])
echo "✓ parallel-cap — dispatch.max_parallel_l1=${cap} (within Wave-2-Lesson recommendation)"
;;
*)
if [ "$cap" -gt 3 ] 2>/dev/null; then
echo "⚠ parallel-cap — dispatch.max_parallel_l1=${cap} > 3 (Wave-2-Lesson: notification-flood risk above 3)"
else
echo "⚠ parallel-cap — dispatch.max_parallel_l1=${cap} invalid"
fi
;;
esac
else
echo "⚠ parallel-cap — .orchestrator/config.yaml not found; skipping"
fi
dispatch.max_parallel_l1=N (within Wave-2-Lesson recommendation)"dispatch.max_parallel_l1=N > 3 (Wave-2-Lesson: notification-flood risk above 3)"dispatch.max_parallel_l1=0 (unlimited; NOT recommended per Wave-2-Lesson)"Checks the Worktree-Pool-Daemon state (ticket #61). Skip silently if
config.pool.enabled is false or .mmp-pool/ directory is absent.
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
ORCHESTRATOR_DIR="$REPO_ROOT/.orchestrator"
PID_FILE="$ORCHESTRATOR_DIR/.pool-daemon.pid"
POOL_DIR="$REPO_ROOT/.mmp-pool"
# Skip check if pool not enabled
pool_enabled="true"
cfg="$ORCHESTRATOR_DIR/config.yaml"
if [ -f "$cfg" ] && command -v yq >/dev/null 2>&1; then
pool_enabled=$(yq -r '.pool.enabled // "true"' "$cfg" 2>/dev/null | grep -v '^null$' || echo "true")
fi
if [ "$pool_enabled" = "false" ]; then
echo "✓ pool-health — pool disabled in config (skip)"
exit 0
fi
# 1. Daemon running?
if [ -f "$PID_FILE" ]; then
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
echo "✓ pool-daemon — running (pid=$pid)"
else
echo "⚠ pool-daemon — pid file present but process not running; run /mmp:pool start"
fi
else
echo "⚠ pool-daemon — not running; run /mmp:pool start to enable pre-warming"
fi
# 2. Pool size >= 1?
pool_count=0
[ -d "$POOL_DIR" ] && pool_count=$(ls -1 "$POOL_DIR" 2>/dev/null | wc -l | xargs)
if [ "$pool_count" -ge 1 ]; then
echo "✓ pool-size — $pool_count slot(s) available"
else
echo "⚠ pool-size — 0 slots; run /mmp:pool refill"
fi
# 3. Last refill age < 10 minutes?
newest=$(ls -t "$POOL_DIR" 2>/dev/null | head -1 || true)
if [ -n "$newest" ] && [ -f "$POOL_DIR/$newest/.pool-ready" ]; then
ts=$(cat "$POOL_DIR/$newest/.pool-ready")
# Parse epoch from slot name (pool-<epoch>-...) for cross-platform compat
name_epoch=$(echo "$newest" | sed 's/^pool-\([0-9]*\)-.*/\1/' 2>/dev/null || echo "")
if [ -n "$name_epoch" ] && [ "$name_epoch" != "$newest" ]; then
slot_epoch="$name_epoch"
else
# Fallback: try date parsing with millis stripped
ts_clean=$(echo "$ts" | sed 's/\.[0-9]*Z$/Z/' 2>/dev/null || echo "$ts")
slot_epoch=$(date -d "$ts_clean" +%s 2>/dev/null \
|| date -j -f '%FT%TZ' "$ts_clean" +%s 2>/dev/null \
|| echo 0)
fi
age_min=$(( ($(date +%s) - slot_epoch) / 60 ))
if [ "$age_min" -le 10 ]; then
echo "✓ pool-refill-age — ${age_min}min (fresh)"
else
echo "⚠ pool-refill-age — ${age_min}min (>10min; daemon may be stalled)"
fi
else
echo "⚠ pool-refill-age — no .pool-ready found; pool not initialized"
fi
# 4. Stale slots (>7d)?
stale_count=0
now=$(date +%s)
cutoff=$((now - 7 * 86400))
for slot in "$POOL_DIR"/pool-*; do
[ -d "$slot" ] || continue
slot_basename=$(basename "$slot")
name_epoch=$(echo "$slot_basename" | sed 's/^pool-\([0-9]*\)-.*/\1/' 2>/dev/null || echo "")
if [ -n "$name_epoch" ] && [ "$name_epoch" != "$slot_basename" ]; then
[ "$name_epoch" -lt "$cutoff" ] && stale_count=$((stale_count + 1))
fi
done
if [ "$stale_count" -gt 0 ]; then
echo "⚠ pool-stale — $stale_count stale slot(s) (>7d); run /mmp:pool cleanup"
else
echo "✓ pool-stale — no stale slots"
fi
npx claudepluginhub ahlerjam/mmp --plugin mmp/checkRuns project validation checks like lint/test across JavaScript/TypeScript, Python, Go, Rust, Ruby and auto-fixes errors without committing changes.
/checkRuns project validation checks like lint/test across JavaScript/TypeScript, Python, Go, Rust, Ruby and auto-fixes errors without committing changes.
/checkDetects drift between SPEC.md and source code, classifying items as HOLD/VIOLATE/UNVERIFIABLE (or MATCH/DRIFT/MISSING/EXTRA for interfaces) with file:line evidence and remedy hints. Read-only, writes nothing.
/checkRuns a unified pre-publish quality gate on marketing content, scoring across hallucination, brand voice, structure, and claims dimensions. Returns a pass/warn/blocked decision with fix suggestions.
/checkGenerates CodeDNA coverage report for the project, identifying unannotated source files and stale used_by references in Python, TypeScript/JS, Go, Rust, Java.
/checkChecks spec-kit CLI (uv and specify) installation status and project setup (.specify/ directory), outputs ✅/❌ results with install guides and next steps.