investigate · diff
git:20260910.889ea9c to git:20260923.767e202
47 added, 115 removed. Audit A to A.
---
name: investigate
- description: Root-cause-first diagnosis gate. Forces a written hypothesis with evidence before any bug-fix code change, and logs it to the event log for later recall.
+ description: Bring past diagnoses to a bug before fixing it, record the new root-cause hypothesis, and close it as confirmed or refuted once the fix is verified. Use when a bug could come from more than one layer, or looks like something seen before.
allowed-tools:
- Bash
- Read
- Grep
- Glob
---
# Investigate
- A diagnosis gate for bug work. Andy's usage data shows repeated cycles where a plausible fix is shipped before the real failure mode is understood — tvOS IAP rejection patched as product-loading when the bug was focus management; gaze replay patched as routing when the bug was coordinate-space; OptiGest tests green on synthetic data while the live app stayed broken. This skill enforces diagnosis before edit.
-
- ## When to invoke
-
- User runs `/investigate <bug summary>` when a bug is reported or discovered. Do NOT propose or write fix code until the user confirms the diagnosis produced by this skill.
-
- ## When to skip
-
- The ~5–10K token overhead is pure tax on obvious bugs. Skip this skill when:
-
- - The cause is stated in the error message (typo, import error, off-by-one in a visible loop).
- - The fix is a one-line change and the call path is already in context.
- - The user explicitly names both the cause and the fix.
-
- Use it when the symptom could plausibly come from more than one layer (logic / state / boundary / validation gap / build) — that's where misdiagnosis cascades burn 50–200K tokens.
-
- ## The contract
-
- Complete these five steps **in order**. Stop and wait for user input after step 5.
-
- ### 1. Reproduce or confirm the failure mode
-
- - If the user gave error output / screenshot / console log, quote the exact line that proves the bug.
- - If not, ask for it before continuing — do not guess. One ask, then wait.
-
- ### 2. Read the failing code path end-to-end
-
- - Grep for the symptom string, entry point, or suspected function.
- - Read the whole path, not just the matching line. Note file:line anchors.
- - If the path crosses a boundary (JS↔native, Electron↔renderer, Playwright↔browser, host↔device), name the boundary explicitly — most of Andy's coordinate and binary-staleness bugs live at boundaries.
+ Cartographer's part of bug work is memory: what was diagnosed before, which
+ hypotheses turned out wrong, and what finally fixed it. How to debug is left to
+ you. This skill adds three steps around your own diagnosis.
- ### 3. Identify the root-cause layer
+ Resolve `ROOT` from `CARTOGRAPHER_ROOT`, `CLAUDE_PLUGIN_ROOT`, or `PLUGIN_ROOT`;
+ otherwise use this skill's base directory (`../..` from `skills/investigate`).
- State which layer owns the bug:
- - **Logic** — the code does the wrong thing
- - **State** — stale cache, stale binary, uncleared hysteresis, wrong env
- - **Boundary** — coordinate space, DPR, unit mismatch, focus, lifecycle
- - **Validation gap** — tests pass on synthetic data; live runtime differs
- - **Config/build** — plugin not loaded, manifest missing, xcodegen wiped capability
+ ## 1. Recall before diagnosing
- If the symptom points one way but the evidence points another, trust the evidence. When Andy names the likely cause, investigate that first (per `feedback_listen_first`).
+ Search for the symptom and for the files or components involved:
- ### 4. Write the hypothesis
+ ```bash
+ bash "$ROOT/scripts/cartographer-search.sh" "Investigated <symptom words>" --limit 10
+ bash "$ROOT/scripts/cartographer-search.sh" "<file or component name>" --limit 10
+ ```
- One short paragraph, plain text. Must contain:
+ For each earlier `investigation` result, search its event id to find how it
+ ended. An `investigation_outcome` event names it in its summary:
- - **Cause:** the specific line / boundary / state responsible
- - **Mechanism:** how that cause produces the observed symptom
- - **Disproof:** one concrete observation that would falsify the hypothesis (a log line, a coord value, a test on real hardware)
+ ```bash
+ bash "$ROOT/scripts/cartographer-search.sh" "evt-abc123def456" --limit 5
+ ```
- ### 5. Log it and wait
+ Use `--get <ids>` for the full records. Report what bears on this bug, most of
+ all hypotheses that were **refuted** for a similar symptom, and a confirmed
+ cause that could be recurring. An investigation with no outcome is an open
+ guess, not a finding. If nothing relevant comes back, say so in one line.
- Write the hypothesis to `changelog.jsonl` — the log `/remember` actually
- searches — and index it.
+ ## 2. Record the hypothesis
- **Run this block as written.** Until 0.5.0 it was routinely paraphrased, which
- produced four different record shapes and 64 diagnoses written to
- `.carto/events/`, a path nothing reads. Every one was unrecoverable until a
- backfill rescued them. The field names below are not stylistic: `event_id` and
- `timestamp` are what the pipeline indexes on, and `summary` is first in the
- extraction chain, so text outside it is invisible to search.
+ Once you have a diagnosis worth testing, record it. It should name the cause,
+ the mechanism that produces the symptom, and one observation that would
+ disprove it.
```bash
- DEV="${CARTOGRAPHER_DEV_DIR:-$HOME/Documents/dev}"
- CLAUDE_SID="${CLAUDE_SESSION_ID:-${CLAUDE_CODE_SESSION_ID:-}}"
- SESSION_ID="${CARTOGRAPHER_SESSION_ID:-${CLAUDE_SID:-${CODEX_SESSION_ID:-unknown}}}"
- PROVIDER="${CARTOGRAPHER_PROVIDER:-unknown}"
- [ "$PROVIDER" = "unknown" ] && [ -n "$CLAUDE_SID" ] && PROVIDER="claude"
- [ "$SESSION_ID" = "unknown" ] && echo "warning: session id unresolved — this event will not link to a transcript" >&2
- TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- EVENT_ID="evt-$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c 12)"
- # Detect project from cwd. Resolve a worktree to its PARENT repo: a session run
- # in repo/.claude/worktrees/<name> whose project is the worktree basename orphans
- # every record the moment that worktree is pruned, and agent tools create and
- # discard worktrees routinely. cartographer-project.sh is the command-line face
- # of cartographer_project() in hooks/common.sh — one definition, two consumers.
- ROOT="${CARTOGRAPHER_ROOT:-${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-$HOME/Documents/dev/session-cartographer}}}"
- GIT_REPO=$(git rev-parse --show-toplevel 2>/dev/null)
- PROJECT=$(bash "$ROOT/scripts/cartographer-project.sh" 2>/dev/null) || PROJECT=""
- if [ -z "$PROJECT" ]; then
- PROJECT=$(basename "${GIT_REPO:-$(pwd)}")
- echo "warning: cartographer-project.sh unavailable — project \"$PROJECT\" is cwd-derived and is wrong inside a worktree" >&2
- fi
-
- # Find the transcript so the record links back to this conversation.
- TRANSCRIPT=$(find ~/.claude/projects -name "${SESSION_ID}.jsonl" 2>/dev/null | head -1)
- if [ -z "$TRANSCRIPT" ] && [ "$SESSION_ID" != "unknown" ]; then
- TRANSCRIPT=$(find ~/.codex/archived_sessions ~/.codex/sessions -name "*${SESSION_ID}*.jsonl" 2>/dev/null | head -1)
- [ -n "$TRANSCRIPT" ] && PROVIDER="codex"
- fi
+ jq -n -c --arg symptom "$SYMPTOM" --arg hypothesis "$HYPOTHESIS" \
+ --arg layer "$LAYER" --argjson files "$FILES_JSON" \
+ '{kind:"open", symptom:$symptom, hypothesis:$hypothesis, layer:$layer, files:$files}' \
+ | bash "$ROOT/scripts/record-investigation.sh"
+ ```
- # $SYMPTOM is the user's bug summary; $HYPOTHESIS is the paragraph from step 4;
- # $LAYER is the root-cause layer from step 3.
- # Flatten to one line — the pipeline is TSV/line-based and a newline in the
- # summary splits the row, whose fragments then mis-parse as rank and outrank
- # every real result.
- SUMMARY=$(printf 'Investigated: %s — %s' "$SYMPTOM" "$HYPOTHESIS" | tr '\n\t' ' ')
+ `layer` is optional (for example `logic`, `state`, `boundary`,
+ `validation`, `build`); `files` is an optional JSON array of paths. Keep the
+ printed `event_id` for step 3. The receipt reports the durable write and the
+ index separately; if indexing failed, the record is still searchable by keyword.
- INVESTIGATION_EVENT=$(jq -nc \
- --arg eid "$EVENT_ID" \
- --arg ts "$TIMESTAMP" \
- --arg session "$SESSION_ID" \
- --arg provider "$PROVIDER" \
- --arg project "$PROJECT" \
- --arg cwd "$(pwd)" \
- --arg transcript "$TRANSCRIPT" \
- --arg summary "$SUMMARY" \
- --arg symptom "$SYMPTOM" \
- --arg hypothesis "$HYPOTHESIS" \
- --arg layer "$LAYER" \
- '{event_id:$eid, timestamp:$ts, type:"investigation", provider:$provider,
- session_id:$session, project:$project, cwd:$cwd, transcript_path:$transcript,
- summary:$summary, symptom:$symptom, hypothesis:$hypothesis,
- root_cause_layer:$layer, salience:0.8}')
+ ## 3. Close it
- [ -z "$INVESTIGATION_EVENT" ] && { echo "error: investigation JSON not built" >&2; exit 1; }
- printf '%s\n' "$INVESTIGATION_EVENT" >> "$DEV/changelog.jsonl"
+ After the fix is verified against the real failure, or the hypothesis is
+ disproved, record the outcome:
- # Index it, or it is keyword-only and invisible to intent-match recall.
- # Pipe the event just built rather than re-reading the log: concurrent sessions
- # append to the same file, so a re-read can index a neighbour's event instead.
- ROOT="${CARTOGRAPHER_ROOT:-${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-$HOME/Documents/dev/session-cartographer}}}"
- printf '%s\n' "$INVESTIGATION_EVENT" | bash "$ROOT/scripts/index-event.sh"
+ ```bash
+ jq -n -c --arg id "$EVENT_ID" --arg outcome confirmed --arg evidence "$EVIDENCE" \
+ '{kind:"close", resolves:$id, outcome:$outcome, evidence:$evidence}' \
+ | bash "$ROOT/scripts/record-investigation.sh"
```
- Then present the hypothesis to the user and **stop**. Do not touch code until they confirm the diagnosis or redirect.
-
- ## What this skill does NOT do
+ `outcome` is `confirmed`, `refuted`, or `abandoned`. `evidence` is the
+ observation that decided it: a log line, a measured value, a passing check on
+ the real target. A refuted hypothesis is worth closing: it is the record that
+ saves the next session from repeating it.
- - Does not write fix code. That's a separate turn, after confirmation.
- - Does not run long exploration. If the path is deep, spawn an Explore agent with a tight brief rather than grepping serially.
- - Does not replace end-to-end verification. After the fix lands, validate against the live artifact (real device, real screenshot, real sensor trace) — synthetic tests passing is not sufficient.
+ If the investigation continues in another session, step 1 finds the open record
+ and its id there.