From develop
Investigation-first debugging for Python projects with CI support. Gathers evidence, traces data flow, forms confirmed root-cause hypothesis via agents, then hands off to fix mode.
How this skill is triggered — by the user, by Claude, or both
Slash command
/develop:debug <symptom or issue # (plain 123 or #123)> [--repo <owner/repo>] [--no-challenge] [--challenge] [--team] [--ci-run <run-id-or-url>] [--codemap] [--no-codemap] [--keep "<items>"]<symptom or issue # (plain 123 or #123)> [--repo <owner/repo>] [--no-challenge] [--challenge] [--team] [--ci-run <run-id-or-url>] [--codemap] [--no-codemap] [--keep "<items>"]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
<objective>
Investigation-first debugging. Gather evidence, trace data flow, form confirmed root-cause hypothesis, hand off to fix mode.
NOT for: production incidents without any CI run ID or local traceback (use /foundry:investigate (requires foundry plugin) for triage); .claude/ config issues (use /foundry:audit (requires foundry plugin)); non-Python projects (JS/TS/Go/Rust) — toolchain assumes pytest; use language-native toolchain instead. CI-only failures ARE supported — pass --ci-run <run-id or URL> to use GitHub Actions logs as evidence source.
Issue ID routing note: issue mode selected when --issue flag present, or when argument (after other flags stripped) is a pure run of digits with an optional # prefix (e.g. 123 or #123). No numeric threshold. Pass --issue <N> to force issue mode for any argument.
Key boundary: after Steps 1+2 — evidence gathered and pattern analysis complete, before hypothesis gate (Step 3). Preserve: debug mode, CI run ID if set, evidence signals (issue body, test path), tried-hypotheses ledger (candidate causes + verdicts — refuted/ruled-out/open), --keep items. Refresh also after any Step 3 probe that rules out a hypothesis — so post-compact gate does not re-test refuted causes (loop guard).
_DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/develop}/bin/dev_shared_resolve.py" 2>/dev/null) # timeout: 5000
# loads: compaction-contract.md
Read $_DEV_SHARED/agent-resolution.md. Contains: foundry check + fallback table. If foundry not installed: substitute each foundry:X with general-purpose per table. Agents this skill uses: foundry:sw-engineer, foundry:challenger.
Read $_DEV_SHARED/task-hygiene.md.
Read $_DEV_SHARED/runner-detection.md — sets $TEST_CMD (full suite) and $PYTEST_CMD (pytest flags). Run at skill start.
Language preflight gate: detect project language; adjust test runner accordingly.
# timeout: 5000
LANG_HINT="python"
if [ ! -f "pyproject.toml" ] && [ ! -f "setup.py" ] && [ ! -f "setup.cfg" ] && [ ! -f "Pipfile" ]; then
if [ -f "package.json" ]; then LANG_HINT="node"
elif [ -f "go.mod" ]; then LANG_HINT="go"
elif [ -f "Cargo.toml" ]; then LANG_HINT="rust"
fi
fi
If LANG_HINT not python: invoke AskUserQuestion — "Non-Python project detected ($LANG_HINT). Toolchain assumes pytest. How to proceed?" · (a) Abort — use language-native runner · (b) Continue — repo also has Python sources. On Abort: stop.
Checkpoint: debug = investigation only — no code changes. .plans/active/debug_<slug>.md (written in Step 4) serves as implicit session state. No .developments/ checkpoint needed.
Parse flags into actual shell variables (not prose) so downstream blocks see correct values:
KEEP_ITEMS=""
if [[ "$ARGUMENTS" =~ --keep[[:space:]]\"([^\"]+)\" ]]; then
KEEP_ITEMS="${BASH_REMATCH[1]}"
fi
echo "$KEEP_ITEMS" > "${TMPDIR:-/tmp}/dev-debug-keep-items"
rm -f .claude/state/skill-contract.md ${TMPDIR:-/tmp}/dev-debug-hypotheses # timeout: 5000
# timeout: 10000
python "${CLAUDE_PLUGIN_ROOT:-plugins/develop}/bin/dev_parse_args.py" \
--skill debug --write-files "$ARGUMENTS"
# URL normalization + log fetching: §URL Normalization in ci-log-extract.md
Codemap resolve — CODEMAP_RAW already written to ${TMPDIR:-/tmp}/dev-debug-codemap (per-skill) and ${TMPDIR:-/tmp}/dev-codemap-raw (legacy) by flag-parsing block above (via dev_parse_args.py --skill debug --write-files). Read per-skill path, then normalize via codemap-resolve:
# timeout: 5000
CODEMAP_RAW=$(cat ${TMPDIR:-/tmp}/dev-debug-codemap 2>/dev/null || echo auto)
CODEMAP_ENABLED=$("${CLAUDE_PLUGIN_ROOT:-plugins/develop}/bin/codemap-resolve" "$CODEMAP_RAW")
RESOLVE_EXIT=$?
if [ "$RESOLVE_EXIT" -ne 0 ]; then
if [ "$CODEMAP_RAW" = "strict" ]; then
echo "! BLOCKED — codemap unavailable but --codemap (strict) passed; run /codemap:scan-codebase or install codemap plugin"
exit 1
fi
CODEMAP_ENABLED=false
fi
# skill-specific path — avoids stale value from prior feature --codemap run
echo "$CODEMAP_ENABLED" > ${TMPDIR:-/tmp}/dev-debug-codemap-enabled
loads: codemap-gates.md
Read $_DEV_SHARED/codemap-gates.md — follow Gate A and Gate B.
Downstream blocks read back: CHALLENGE_ENABLED=$(cat ${TMPDIR:-/tmp}/dev-challenge-enabled 2>/dev/null || echo true), CHALLENGE_FORCED=$(cat ${TMPDIR:-/tmp}/dev-challenge-forced 2>/dev/null || echo false), TEAM_MODE=$(cat ${TMPDIR:-/tmp}/dev-team-mode 2>/dev/null || echo false), CI_RUN_ID=$(cat ${TMPDIR:-/tmp}/dev-ci-run-id 2>/dev/null || echo "").
Read $_DEV_SHARED/ci-log-extract.md. Follow §URL Normalization to set CI_RUN_ID. If CI_RUN_ID set, follow §Log Fetching and §Log Parsing to set CI_LOG_EVIDENCE; use it as evidence source in Step 1 instead of local pytest.
Unsupported flag check — after all supported flags extracted, scan $ARGUMENTS for remaining --<token> tokens. If any found: print ! Unknown flag(s): \--`. Supported: `--no-challenge`, `--challenge`, `--team`, `--ci-run`, `--issue`, `--repo`, `--codemap`, `--no-codemap`, `--keep`.then invokeAskUserQuestion` — (a) Abort (stop, re-invoke with correct flags) · (b) Continue ignoring (skip unknown flags, proceed). On Abort: stop.
Mode selection — debug runs in one of two mutually-exclusive modes; set explicitly before any Step:
# timeout: 5000
# strip flags first — "123 --no-challenge" would fail integer detection otherwise
ARGUMENTS_FOR_MODE_DETECT=$(echo "$ARGUMENTS" | sed -E 's/--no-challenge|--challenge|--team|--ci-run[= ]?[^ ]+|--issue|--repo[= ]?[^ ]+|--no-codemap|--codemap//g' | xargs)
if [[ " $ARGUMENTS " == *" --issue "* ]] || [[ "$ARGUMENTS_FOR_MODE_DETECT" =~ ^#?[0-9]+$ ]]; then
DEBUG_MODE="issue"
else
DEBUG_MODE="symptom"
fi
echo "$DEBUG_MODE" > ${TMPDIR:-/tmp}/dev-debug-mode
Subsequent steps branch by DEBUG_MODE:
If TEAM_MODE=true — execute team investigation now in place of standard Steps 1-2. After team synthesis completes, run Steps 3-4 inline (hypothesis gate + handoff to fix) on winning hypothesis — do not return to standard Steps 1-2. Authoritative reading: team mode replaces Steps 1-2 (parallel hypothesis investigation supplants serial evidence gathering); Steps 3-4 still execute (inline within this block, not by looping back to standard workflow):
$_DEV_SHARED/preflight-helpers.md §Team Spawn Template. Confirm [ROLE_PHRASE] = symptom text (from $ARGUMENTS stripped of flags), [FILE_SLUG] = debug-hypothesis.$_DEV_SHARED/runner-detection.md) to set $TEST_CMD and $PYTEST_CMD.TS=$(date -u +%Y-%m-%dT%H-%M-%SZ) and mkdir -p ".temp/develop/$TS". Spawn 2-3 foundry:sw-engineer agents (model=opus) in parallel — each investigating one independent root-cause hypothesis. Use Team Spawn Template from preflight-helpers: replace [ROLE_PHRASE] with symptom, [FILE_SLUG] with debug-hypothesis, assign each agent a distinct hypothesis number N. Each agent writes full output to .temp/develop/$TS/debug-hypothesis-N.md and returns compact JSON {"status":"done","file":"<path>","findings":N,"confidence":0.N,"summary":"<one-line description of hypothesis>"}.{symptom: <description>, traceback: <key lines>} to teammates before spawning. After all return, facilitate cross-challenge between competing analyses. Convergence rule: select hypothesis with most direct evidence (observable in code or logs); if truly tied, invoke AskUserQuestion presenting top 2 competing hypotheses.foundry:sw-engineer synthesis agent after individual teammate reports — read all teammate findings from .temp/develop/$TS/debug-hypothesis-*.md, produce unified cross-cutting trace map (entry point, modules crossed, state mutations, invariant violations across hypotheses). Write to .temp/develop/$TS/debug-trace-synthesis.md.AskUserQuestion at Step 3 if competing hypotheses remain or convergence declared by default (tied evidence).Health monitoring (CLAUDE.md §6): for each spawned agent, use a per-agent sentinel keyed on loop counter $N (not literal N). Loop over agent indices in actual bash:
# timeout: 5000
for N in 1 2 3; do
touch "${TMPDIR:-/tmp}/debug-team-check-${N}"
done
Poll each independently every 5 min via find .temp/develop/$TS -newer ${TMPDIR:-/tmp}/debug-team-check-${N} -type f | wc -l where $N is actual agent index in loop variable. A single shared sentinel collapses health isolation — stalled agent N=2 cannot be distinguished from active agent N=1. Hard cutoff 15 min no-file-activity per agent; mark timed-out agents with ⏱ in synthesis.
Collect all signals before forming any hypothesis.
Structural context (codemap — only if CODEMAP_ENABLED=true): if index available, run before codebase exploration to pre-load blast-radius context for failing module:
# timeout: 10000
CODEMAP_ENABLED=$(cat ${TMPDIR:-/tmp}/dev-debug-codemap-enabled 2>/dev/null || echo false)
if [ "$CODEMAP_ENABLED" = "true" ]; then
scan-query central --top 5 2>/dev/null
fi
After reading traceback or $ARGUMENTS, derive TARGET_MODULE: strip src/, .py suffix, replace / with . (e.g. src/mypackage/auth.py → mypackage.auth). Then run:
# timeout: 10000
scan-query rdeps <TARGET_MODULE> 2>/dev/null
scan-query fn-blast <TARGET_MODULE::failing_fn> 2>/dev/null # v3 index only
If codemap results returned: prepend ## Structural Context (codemap) block to foundry:sw-engineer spawn prompt (Step 1). Callers of failing module = likely affected paths to verify after fix. fn-blast shows transitive callers — high-depth callers are regression risk.
Issue-number mode first — if $ARGUMENTS is issue number, fetch issue body and extract test path BEFORE invoking pytest:
# timeout: 6000
REPO_NAME=$(cat ${TMPDIR:-/tmp}/dev-upstream 2>/dev/null || echo "")
if [ -n "$REPO_NAME" ]; then
ISSUE_BODY=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/develop}/bin/issue_fetch.py" "$ARGUMENTS" --repo "$REPO_NAME" 2>/dev/null)
else
ISSUE_BODY=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/develop}/bin/issue_fetch.py" "$ARGUMENTS" 2>/dev/null)
fi
echo "$ISSUE_BODY" | tee ${TMPDIR:-/tmp}/dev-issue-body # persist — reloaded in next block (bash state lost between Bash() calls; re-fetching would duplicate the gh call)
ISSUE_BODY=$(cat ${TMPDIR:-/tmp}/dev-issue-body 2>/dev/null || echo "")
TEST_PATH=$(echo "$ISSUE_BODY" | grep -oE '(tests?/[^[:space:]]+\.py|test_[^[:space:]]+\.py)' | head -1)
if [ -z "$TEST_PATH" ]; then
echo "→ No test file found in issue; running full test suite"
elif [ ! -f "$TEST_PATH" ]; then
echo "⚠ test path from issue not found on disk: $TEST_PATH — running full suite"
TEST_PATH=""
fi
Run pytest with extracted path (empty $TEST_PATH → full suite):
# timeout: 600000
$PYTEST_CMD --tb=long ${TEST_PATH} -v 2>&1 | tail -60
GATE_EXIT=${PIPESTATUS[0]}
echo "$GATE_EXIT" > ${TMPDIR:-/tmp}/dev-gate-exit
if [ "$GATE_EXIT" -ne 0 ]; then
echo "Bug reproduced — tests fail. Proceed to fix."
else
echo "Tests pass — bug may not be reproducible via pytest; check symptom directly."
fi
# timeout: 3000
git log --oneline -20
COMMIT_COUNT=$(git rev-list --count HEAD 2>/dev/null || echo 1)
LOOKBACK=$(( COMMIT_COUNT < 5 ? COMMIT_COUNT : 5 ))
SUSPECT_FILE="${TEST_PATH:-}" # empty → full-repo diff
[ "$LOOKBACK" -gt 1 ] && git diff HEAD~${LOOKBACK}..HEAD -- "${SUSPECT_FILE:-}"
Cross-repo adaptation (when REPO_NAME set) — issue from different codebase. After fetching issue:
Symptom-text mode — if $ARGUMENTS is free-text, skip issue fetch + extraction; locate failing test path from symptom directly. <test_path> is a substitution token — resolve into shell variable $TEST_PATH first (via Grep against symptom keywords or heuristic file search), then use $TEST_PATH in pytest call. Do NOT execute with literal <test_path> string — bash would interpret < as stdin redirect from a file named test_path:
# Resolve TEST_PATH before this block — e.g.:
# TEST_PATH=$(grep -rE '<symptom keyword>' tests/ --include='*.py' -l | head -1)
# timeout: 600000
$PYTEST_CMD --tb=long ${TEST_PATH} -v 2>&1 | tail -60
GATE_EXIT=${PIPESTATUS[0]}
echo "$GATE_EXIT" > ${TMPDIR:-/tmp}/dev-gate-exit
if [ "$GATE_EXIT" -ne 0 ]; then
echo "Bug reproduced — tests fail. Proceed to fix."
else
echo "Tests pass — bug may not be reproducible via pytest; check symptom directly."
fi
Claim-validation gate — before debugging, validate that user's expectation is itself correct. A bug report always contains an implicit or explicit claim: "X should behave like Y". Claim may be wrong — misread docs, misunderstood API contract, incorrect formula, outdated assumption. Fixing a correct implementation to match a wrong expectation wastes effort and introduces regressions.
Classify claim type and validate accordingly:
| Claim type | Example | Validation approach |
|---|---|---|
| Numeric / metric result | "IoU should be 0.5 but returns 0.3" | Verify formula from authoritative source; compute expected value independently |
| API contract | "function should return list but returns generator" | Read docstring, type hints, and docs — not current implementation |
| Algorithm correctness | "sorting is wrong — element 3 should come before element 1" | Trace comparison logic against documented sort key or invariant |
| Behavioral invariant | "adding item twice should raise, not silently dedupe" | Check README, docs, or published contract — not assumed behavior |
| Cross-version assumption | "this worked in v1, now broken" | Check changelog/release notes for intentional breaking change |
| Domain-specific formula | ML metric, statistical estimator, signal processing | Spawn research:scientist (requires research plugin); pass metric name, formula used, claimed expected value; ask: "Is the claimed expected value correct per authoritative definition?" |
Resolution rules:
research:scientist; if plugin absent flag: ⚠ Expected value unverified — treating as assumptionUse Grep (pattern: failing symbol, class, or error keyword) to trace call path from entry point to failure site. Path hint: use src/ if exists, else search from project root (.).
Spawn foundry:sw-engineer agent to map execution path and produce:
Scope gate: if root cause spans 3+ modules, flag complexity smell. Use AskUserQuestion to present scope concern before proceeding, with options: "Narrow scope (Recommended)" / "Proceed anyway".
Present agent's analysis summary before proceeding.
Flaky-test branch — if symptom is intermittent (passes alone, fails in full suite): run binary-search isolation. <failing-test-node-id> is a substitution token — before executing this block, resolve failing test node ID from $ARGUMENTS or from prior pytest output (captured in a shell variable, e.g. FAILING_TEST_NODE=tests/foo.py::test_bar), then substitute literal node ID into command. Do NOT execute with literal <failing-test-node-id> string — bash would interpret < as stdin redirect:
# resolve FAILING_TEST_NODE first — bash interprets literal <...> as redirect:
# FAILING_TEST_NODE=$(echo "$ARGUMENTS" | grep -oE 'tests?/[^[:space:]]+::test_[^[:space:]]+' | head -1)
_FOUNDRY_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/develop}/bin/dev_shared_resolve.py" --foundry 2>/dev/null | tail -1) # --foundry emits foundry's skills/_shared on line 2 (no --foundry-bin flag exists)
_FOUNDRY_BIN="${_FOUNDRY_SHARED%/skills/_shared}/bin"
[ -d "$_FOUNDRY_BIN" ] || _FOUNDRY_BIN=$(ls -td ~/.claude/plugins/cache/*/foundry/*/bin 2>/dev/null | head -1 || echo "${CLAUDE_PLUGIN_ROOT:-plugins/foundry}/bin") # timeout: 5000
if [ -z "$FAILING_TEST_NODE" ]; then
echo "⚠ FAILING_TEST_NODE not resolved — cannot run polluter isolation; surface failing test node ID first"
elif [ -f "$_FOUNDRY_BIN/find-polluter.py" ]; then
python "$_FOUNDRY_BIN/find-polluter.py" "$FAILING_TEST_NODE" # timeout: 60000
else
echo "⚠ foundry plugin absent — skipping flaky-test isolation; proceed with standard rerun"
fi
Output names polluting upstream test. Cross-plugin call — find-polluter.py ships in foundry/bin/. Run only when CI shows non-deterministic failure pattern. If foundry plugin absent: skip flaky-test isolation step; proceed with standard rerun.
Find nearest similar working code path, compare exhaustively:
Step catches non-obvious causes — ordering dependency, environment-specific state, type coercion silently changing behaviour.
Record candidates (loop guard) — as each candidate cause identified, append it to hypothesis ledger with verdict open. Ledger inlined into compaction contract at boundary below, so a mid-investigation compaction never loses which causes were already weighed. Verdict values: open · refuted (challenger) · ruled-out (probe).
# WHY: ledger survives compaction via contract; prevents re-testing refuted causes after a mid-investigation compact
echo "<candidate cause> :: open" >> ${TMPDIR:-/tmp}/dev-debug-hypotheses
Decision — three states (default is NOT "skip": it runs on substantial root causes and auto-skips only narrow ones):
--no-challenge (CHALLENGE_ENABLED=false) → skip gate entirely, any size.--challenge (CHALLENGE_FORCED=$(cat ${TMPDIR:-/tmp}/dev-challenge-forced 2>/dev/null || echo false) = true) → always run, even on a narrow root cause.Both flags exist because they cover opposite regimes: --no-challenge suppresses gate on substantial cases where it would otherwise fire; --challenge forces it on narrow cases where it would otherwise auto-skip.
Spawn foundry:challenger with pattern analysis from Step 2 (differences between working/broken paths, candidate causes):
"Review pattern analysis and candidate root causes. Challenge across all 5 dimensions: Assumptions, Missing Cases, Security Risks, Architectural Concerns, Complexity Creep. Apply mandatory refutation step."
Parse result — update hypothesis ledger (${TMPDIR:-/tmp}/dev-debug-hypotheses) with each candidate's verdict as you parse:
:: refuted (challenger) in ledger.:: open (alt); continue.# Compaction contract — boundary: after evidence+challenge, before hypothesis gate (compaction-contract.md §Lifecycle)
_DEBUG_MODE=$(cat "${TMPDIR:-/tmp}/dev-debug-mode" 2>/dev/null || echo "symptom")
_CI_RUN=$(cat "${TMPDIR:-/tmp}/dev-ci-run-id" 2>/dev/null || echo "")
_KEEP=$(cat "${TMPDIR:-/tmp}/dev-debug-keep-items" 2>/dev/null || echo "")
_TRIED=$(head -6 "${TMPDIR:-/tmp}/dev-debug-hypotheses" 2>/dev/null) # cap keeps contract ≤12 lines
_PRESERVE="mode=$_DEBUG_MODE, ci-run=${_CI_RUN:-none}"
[ -n "$_KEEP" ] && _PRESERVE="$_PRESERVE; user-keep: $_KEEP"
mkdir -p .claude/state # timeout: 5000
{
echo "## Active Skill Contract"
echo "- skill: develop:debug · phase: hypothesis+handoff (after evidence gathered and pattern analysis)"
echo "- run-dir: .plans/active/"
echo "- preserve: $_PRESERVE"
if [ -n "$_TRIED" ]; then
echo "- tried (do NOT re-test refuted/ruled-out):"
echo "$_TRIED" | sed 's/^/ - /'
fi
echo "- next: state hypothesis with evidence (Step 3) → confirm root cause → write diagnosis → handoff to /develop:fix. Skip any candidate marked refuted/ruled-out above."
} > .claude/state/skill-contract.md
State root cause hypothesis explicitly before writing any code:
Root cause: <one sentence — what is wrong and why>
Evidence for: [signals that support this]
Evidence against: [anything that contradicts or remains unexplained]
Confidence: high / medium / low
Read $_DEV_SHARED/premise-grounding.md §Premise Grounding Gate. Apply using debug context from Skill contexts table. Run before presenting hypothesis — any ungrounded premise in hypothesis produces a fix that addresses wrong mechanism.
Gate: present hypothesis to user, wait for confirmation or challenge before proceeding to Step 4. Wrong hypothesis produces fix that passes tests but doesn't resolve underlying problem.
If confidence low: propose targeted probe (minimal script, added log statement, single assertion) to gather missing signal — run before committing to fix. If a probe rules out current hypothesis, append <cause> :: ruled-out (probe) to ${TMPDIR:-/tmp}/dev-debug-hypotheses and re-run boundary contract block above before re-hypothesizing — keeps loop guard current so ruled-out cause not revisited.
Test impact (codemap) — hypothesis confirmed — root cause now names a suspect module (and often a function). Query affected test set once here so /develop:fix reuses it instead of re-querying. Gated on CODEMAP_ENABLED + scan-query availability (same gate as Step 1). <SUSPECT> is confirmed suspect as module.path::function (fn known) or bare module.path (module-level):
# timeout: 8000
CODEMAP_ENABLED=$(cat ${TMPDIR:-/tmp}/dev-debug-codemap-enabled 2>/dev/null || echo false)
if [ "$CODEMAP_ENABLED" = "true" ] && command -v scan-query >/dev/null 2>&1; then
# SUSPECT resolved from Step 3 hypothesis — e.g. mypackage.auth::validate or mypackage.auth
scan-query test-impact "$SUSPECT" 2>/dev/null | tee ${TMPDIR:-/tmp}/dev-debug-test-impact
else
rm -f ${TMPDIR:-/tmp}/dev-debug-test-impact # no query — fix falls back to its own live query
fi
Captured JSON carries pytest_cmd, test_files, top-level stale, and index.not_covered. Written into diagnosis file (Step 4) under a marked section so fix can reuse a fresh result. Query returns "error" or empty → skip silently; fix re-queries.
Root cause confirmed. Transition to fix mode with diagnosis as input — fix's Step 1 pre-answered.
# timeout: 5000
if [ ! -f "${CLAUDE_PLUGIN_ROOT:-plugins/develop}/skills/fix/SKILL.md" ]; then
echo "⚠ /develop:fix not found — partial install detected; diagnosis file will be written but handoff cannot be invoked automatically"
fi
Emit handoff block:
Root cause: <confirmed hypothesis from Step 3>
Suspect file(s): <files identified in Steps 1-2>
Evidence: <key signals that confirmed the hypothesis>
Write diagnosis to file before handing off — enables /develop:fix to skip Step 1 analysis via --diagnosis <path>:
SLUG=$(echo "$ARGUMENTS" | tr ' ' '\n' | grep -v '^--' | grep -v '^[0-9]\+$' | head -4 | tr '\n' '-' | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]-' | sed 's/-$//'); [ -z "$SLUG" ] && SLUG="unnamed-$(date +%s)"
# grep -v strips bare numeric tokens (e.g. CI run IDs) — avoids filenames like debug_12345678.md
DIAG_FILE=".plans/active/debug_${SLUG}.md"
mkdir -p .plans/active
Write $DIAG_FILE with this structure:
# Debug Diagnosis: <symptom>
## Root Cause
<one sentence — confirmed hypothesis>
## Suspect Files
- path/to/file.py — <reason>
## Evidence
- <signal 1 that confirmed hypothesis>
- <signal 2>
## Confidence
<high|medium|low>
Append Test Impact section — only when Step 3 captured a non-empty, non-error result (${TMPDIR:-/tmp}/dev-debug-test-impact present). fix reads this to skip re-querying. Records raw JSON plus index scanned_at so fix can verify handoff is not older than current index (freshness guard):
# timeout: 5000
TI_FILE="${TMPDIR:-/tmp}/dev-debug-test-impact"
if [ -s "$TI_FILE" ] && ! grep -q '"error"' "$TI_FILE"; then
PROJ=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || basename "$PWD")
_IDX="${CODEMAP_INDEX_DIR:-.cache/codemap}/${PROJ}.json"
IDX_SCANNED_AT=$(grep -o '"scanned_at"[[:space:]]*:[[:space:]]*"[^"]*"' "$_IDX" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
{
echo ""
echo "## Test Impact (codemap)"
echo "<!-- reused by /develop:fix Step 3 when index_scanned_at still matches the live index and stale != true -->"
echo "- index_scanned_at: ${IDX_SCANNED_AT:-unknown}"
echo '```json'
cat "$TI_FILE"
echo '```'
} >> "$DIAG_FILE"
fi
Hand off: -> /develop:fix --diagnosis $DIAG_FILE. Root cause already known — fix's Step 1 analysis complete.
After root cause confirmed and handoff to /develop:fix complete, emit terminal summary:
Root Cause: <one sentence>
File(s): <suspect files>
Evidence: <key signals>
→ Handed off to /develop:fix --diagnosis $DIAG_FILE
## Confidence
**Score**: 0.N — [high ≥0.9 | moderate 0.85–0.9 | low <0.85 ⚠]
**Gaps**:
- [e.g., unverified alternative hypotheses, hypothesis only — not confirmed via test reproduction]
**Refinements**: N passes.
Follow-up gate (NEVER SKIP) — Call AskUserQuestion tool — do NOT write options as plain text first. Substitute actual $DIAG_FILE path (from bash block above) into option (a) label before calling tool. Map options directly into tool call arguments:
/develop:fix --diagnosis <DIAG_FILE> (substitute resolved path, e.g. /develop:fix --diagnosis .plans/active/debug_<slug>.md) — description: proceed with fix using confirmed diagnosisskip — description: no actionrm -f .claude/state/skill-contract.md ${TMPDIR:-/tmp}/dev-debug-hypotheses # clear contract + ledger — skill complete (compaction-contract.md §Lifecycle) # timeout: 5000
| Temptation | Reality |
|---|---|
| "I already know root cause from traceback" | Tracebacks show where, not why. Unverified assumptions produce fixes for wrong bug. |
| "Fix obvious — Step 2 pattern analysis overkill" | Obvious causes often symptoms. Pattern comparison reveals ordering, timing, or environment differences invisible in traceback. |
"I'll apply fix here instead of handing off to /develop:fix" | Debug = investigation only. Mixing investigation + implementation conflates history, skips regression test gate. |
| "Low confidence fine — I'll try fix and see" | Fix without confirmed hypothesis = guess. Guesses produce fixes that pass tests but don't resolve underlying problem. |
npx claudepluginhub borda/ai-rig --plugin developSystematically investigates bugs by tracing causal chains before proposing fixes. Use for errors, stack traces, regressions, failed tests, or stuck investigations.
Guides systematic root-cause debugging for bugs, test failures, unexpected behavior, performance issues, and build failures via reproduction, evidence gathering, and data flow tracing before fixes.
Enforces four-phase root cause analysis for bugs, errors, test failures, unexpected behavior, and production incidents before proposing fixes.