agentsclimarketplace

Debug

Skill Borda/AI-Rig/plugins/cc_develop/skills/debug

Investigation-first debugging — gather evidence, form confirmed root-cause hypothesis, hand off to fix mode with diagnosis file. TRIGGER when: user reports a symptom or failing test with Python traceback, or asks to investigate a runtime/CI failure with reproducible evidence; phrases: "debug this failure", "why is X broken", "find the root cause of <error>", "investigate this CI failure". SKIP when: pure config quality issues (use `/foundry:audit`); broad system-wide diagnosis without traceback (use `/foundry:investigate`); user already knows the fix (use `/develop:fix`); non-Python project.From its SKILL.md

Install
npx -y skills add Borda/AI-Rig --skill debug

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

3 things to look at

  • skips confirmationTells the agent to proceed without asking first, 2 times: "present converged hypothesis without a new user confirmation prompt" and 1 more.
  • 24 stars24 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 8 commands, including `python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py"` and 7 more.

SKILL.md

32.4 KB, ~8.5k tokens by cl100k_base, as published. Nobody here has run it

<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.

</objective> <compaction>

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).

</compaction> <workflow> <!-- Agent resolution: see _DEV_SHARED/agent-resolution.md (mounted by develop plugin init) -->

Agent Resolution

_DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py" 2>/dev/null)  # timeout: 5000
# loads: compaction-contract.md
cat "$_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.

_DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py" 2>/dev/null)  # timeout: 5000
cat "$_DEV_SHARED/task-hygiene.md"

Project Detection

_DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py" 2>/dev/null)  # timeout: 5000
cat "$_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.

Flag parsing

Parse flags into actual shell variables (not prose) so downstream blocks see correct values:

export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
KEEP_ITEMS=""
if [[ "$ARGUMENTS" =~ --keep[[:space:]]\"([^\"]+)\" ]]; then
    KEEP_ITEMS="${BASH_REMATCH[1]}"
fi
echo "$KEEP_ITEMS" > "${TMPDIR:-/tmp}/dev-debug-keep-items-${CSID}"
rm -f .temp/state/skill-contract.md ${TMPDIR:-/tmp}/dev-debug-hypotheses-${CSID}  # timeout: 5000
# timeout: 10000
python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_parse_args.py" \
    --skill debug --write-files "$ARGUMENTS"
# URL normalization + log fetching: §URL Normalization in ci-log-extract.md

Codemap resolveCODEMAP_RAW already written to ${TMPDIR:-/tmp}/dev-debug-codemap-${CSID} (per-skill) and ${TMPDIR:-/tmp}/dev-codemap-raw-${CSID} (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
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r CODEMAP_RAW < "${TMPDIR:-/tmp}/dev-debug-codemap-${CSID}" 2>/dev/null || CODEMAP_RAW="auto"
CODEMAP_ENABLED=$("${CLAUDE_PLUGIN_ROOT:-plugins/cc_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-py: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-${CSID}

loads: codemap-gates.md

_DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py" 2>/dev/null)  # timeout: 5000
cat "$_DEV_SHARED/codemap-gates.md"

Follow Gate A and Gate B.

Downstream blocks read back: IFS= read -r CHALLENGE_ENABLED < "${TMPDIR:-/tmp}/dev-challenge-enabled-${CSID}" 2>/dev/null || CHALLENGE_ENABLED=true, IFS= read -r CHALLENGE_FORCED < "${TMPDIR:-/tmp}/dev-challenge-forced-${CSID}" 2>/dev/null || CHALLENGE_FORCED=false, IFS= read -r TEAM_MODE < "${TMPDIR:-/tmp}/dev-team-mode-${CSID}" 2>/dev/null || TEAM_MODE=false, IFS= read -r CI_RUN_ID < "${TMPDIR:-/tmp}/dev-ci-run-id-${CSID}" 2>/dev/null || CI_RUN_ID="".

_DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py" 2>/dev/null)  # timeout: 5000
cat "$_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): \--<token>`. 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
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
# 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-${CSID}

Subsequent steps branch by DEBUG_MODE:

  • Issue mode: Step 1 fetches issue body and extracts test path before invoking pytest; skip symptom-text pytest block. Stop after Step 4 (handoff) — do not run symptom-text branches.
  • Symptom mode: Step 1 skips issue fetch; uses free-text symptom directly. Skip issue-mode pytest block entirely.

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):

  1. _DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py" 2>/dev/null); cat "$_DEV_SHARED/preflight-helpers.md" §Team Spawn Template. Confirm [ROLE_PHRASE] = symptom text (from $ARGUMENTS stripped of flags), [FILE_SLUG] = debug-hypothesis.
  2. Run project detection (_DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py" 2>/dev/null); cat "$_DEV_SHARED/runner-detection.md") to set $TEST_CMD and $PYTEST_CMD.
  3. Compute 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>"}.
  4. Coordination: lead broadcasts {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.
  5. Synthesis trace agent: spawn one 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.
  6. Lead synthesises consensus root cause from synthesis trace + competing hypotheses. Run Steps 3-4 of standard workflow (hypothesis gate + hand off to fix) on winning hypothesis — execute those steps inline here; do not loop back through Steps 1-2. Step 3 gate in team mode: if convergence reached by synthesis agent (all hypotheses point to same root cause with high confidence), present converged hypothesis without a new user confirmation prompt — state "Team converged on root cause (no ambiguity)" and proceed directly to Step 4 handoff. Only invoke 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
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
for N in 1 2 3; do
    touch "${TMPDIR:-/tmp}/debug-team-check-${N}-${CSID}"
done

Poll each independently every 5 min via find .temp/develop/$TS -newer ${TMPDIR:-/tmp}/debug-team-check-${N}-${CSID} -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.

Step 1: Understand the symptom

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
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r CODEMAP_ENABLED < "${TMPDIR:-/tmp}/dev-debug-codemap-enabled-${CSID}" 2>/dev/null || CODEMAP_ENABLED="false"
if [ "$CODEMAP_ENABLED" = "true" ]; then
    codemap-py 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.pymypackage.auth). Then run:

# timeout: 10000
codemap-py query rdeps <TARGET_MODULE> 2>/dev/null
codemap-py 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
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r REPO_NAME < "${TMPDIR:-/tmp}/dev-upstream-${CSID}" 2>/dev/null || REPO_NAME=""
if [ -n "$REPO_NAME" ]; then
    ISSUE_BODY=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/issue_fetch.py" "$ARGUMENTS" --repo "$REPO_NAME" 2>/dev/null)
else
    ISSUE_BODY=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/issue_fetch.py" "$ARGUMENTS" 2>/dev/null)
fi
echo "$ISSUE_BODY" | tee ${TMPDIR:-/tmp}/dev-issue-body-${CSID}   # persist — reloaded in next block (bash state lost between Bash() calls; re-fetching would duplicate the gh call)
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r ISSUE_BODY < "${TMPDIR:-/tmp}/dev-issue-body-${CSID}" 2>/dev/null || ISSUE_BODY=""
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
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
$PYTEST_CMD --tb=long ${TEST_PATH} -v 2>&1 | tail -60
GATE_EXIT=${PIPESTATUS[0]}
echo "$GATE_EXIT" > ${TMPDIR:-/tmp}/dev-gate-exit-${CSID}
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:

  1. Extract bug's root cause intent — what invariant violated, not just described symptoms (which may reference upstream structure or code paths)
  2. Search LOCAL codebase for equivalent failure site — grep for related symbols; code paths may differ from upstream due to divergence
  3. Treat upstream issue as debugging context, not as a map — trace actual failure in local code

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
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
$PYTEST_CMD --tb=long ${TEST_PATH} -v 2>&1 | tail -60
GATE_EXIT=${PIPESTATUS[0]}
echo "$GATE_EXIT" > ${TMPDIR:-/tmp}/dev-gate-exit-${CSID}
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 typeExampleValidation 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 formulaML metric, statistical estimator, signal processingSpawn 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:

  1. Claim verifiable from docs/type hints/tests → read source now; confirm before proceeding
  2. Claim verifiable by quick computation → run inline script to compute expected value independently
  3. Domain-specific claim requiring literature → spawn research:scientist; if plugin absent flag: ⚠ Expected value unverified — treating as assumption
  4. Claim contradicts docs/contract → it is a documentation misunderstanding, not a bug; surface this to user before any code change
  5. Gate: do not form root-cause hypothesis until claimed expectation confirmed or explicitly flagged as unverified; wrong expectation → wrong fix

Use 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:

  • Entry point to failure: which modules does call cross?
  • What state mutated along the way?
  • What invariant violated at failure point?
  • Any recent commit touching this path (from git log output)

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/cc_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/cc_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.

Step 2: Pattern analysis

Find nearest similar working code path, compare exhaustively:

  1. Locate 2-3 code paths handling similar input or similar work successfully
  2. List every difference between working path and broken one — not just obvious one
  3. Check across axes:
    • Same input, different environment (versions, config, data shape)?
    • Same logic, different call order or timing?
    • Conditionals taking different branches on different inputs?
    • None/empty guards present in working path but absent in broken one?

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
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
echo "<candidate cause> :: open" >> ${TMPDIR:-/tmp}/dev-debug-hypotheses-${CSID}

Challenger gate

Decision — three states (default is NOT "skip": it runs on substantial root causes and auto-skips only narrow ones):

  1. --no-challenge (CHALLENGE_ENABLED=false) → skip gate entirely, any size.
  2. else --challenge (IFS= read -r CHALLENGE_FORCED < "${TMPDIR:-/tmp}/dev-challenge-forced-${CSID}" 2>/dev/null || CHALLENGE_FORCED=false = true) → always run, even on a narrow root cause.
  3. else defaultrun when root cause is substantial (spans multiple files, a larger change, or touches public API); auto-skip when narrow (single file, ≲50 lines, no API change) — hypothesis simple enough to proceed directly.

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-${CSID}) with each candidate's verdict as you parse:

  • Blockers found → STOP. Present findings. Incorporate challenger's surviving challenges into hypothesis list before Step 3 gate. Mark any candidate the challenger refuted :: refuted (challenger) in ledger.
  • Concerns only → add as alternative hypotheses in Step 3; append each new concern to ledger as :: open (alt); continue.
  • No findings / all refuted → proceed.
# Compaction contract — boundary: after evidence+challenge, before hypothesis gate (compaction-contract.md §Lifecycle)
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEBUG_MODE < "${TMPDIR:-/tmp}/dev-debug-mode-${CSID}" 2>/dev/null || _DEBUG_MODE="symptom"
IFS= read -r _CI_RUN < "${TMPDIR:-/tmp}/dev-ci-run-id-${CSID}" 2>/dev/null || _CI_RUN=""
IFS= read -r _KEEP < "${TMPDIR:-/tmp}/dev-debug-keep-items-${CSID}" 2>/dev/null || _KEEP=""
_TRIED=$(head -6 "${TMPDIR:-/tmp}/dev-debug-hypotheses-${CSID}" 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 .temp/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."
} > .temp/state/skill-contract.md

Step 3: Hypothesis and gate

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
_DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py" 2>/dev/null)  # timeout: 5000
cat "$_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-${CSID} 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 + codemap-py 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
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r CODEMAP_ENABLED < "${TMPDIR:-/tmp}/dev-debug-codemap-enabled-${CSID}" 2>/dev/null || CODEMAP_ENABLED="false"
if [ "$CODEMAP_ENABLED" = "true" ] && command -v codemap-py >/dev/null 2>&1; then
    # SUSPECT resolved from Step 3 hypothesis — e.g. mypackage.auth::validate or mypackage.auth
    codemap-py query test-impact "$SUSPECT" 2>/dev/null | tee ${TMPDIR:-/tmp}/dev-debug-test-impact-${CSID}
else
    rm -f ${TMPDIR:-/tmp}/dev-debug-test-impact-${CSID}  # 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.

Step 4: Hand off to fix

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/cc_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-${CSID} 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
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
TI_FILE="${TMPDIR:-/tmp}/dev-debug-test-impact-${CSID}"
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.

Final Report

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:

  • question: "Proceed with fix?"
  • (a) label: /develop:fix --diagnosis <DIAG_FILE> (substitute resolved path, e.g. /develop:fix --diagnosis .plans/active/debug_<slug>.md) — description: proceed with fix using confirmed diagnosis
  • (b) label: skip — description: no action
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
rm -f .temp/state/skill-contract.md ${TMPDIR:-/tmp}/dev-debug-hypotheses-${CSID}  # clear contract + ledger — skill complete (compaction-contract.md §Lifecycle)  # timeout: 5000
</workflow> <notes>

Anti-Rationalizations

<!-- Reference only — execution-dead at runtime; included for agent behavioral context -->
TemptationReality
"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.
<!-- Team spawn logic: see Flag parsing block above for team mode branch --> </notes>

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.