---
name: sf-debug-forensics
description: Post-mortem a failed SF autonomous mode run from persisted runtime evidence. Use in automatic recovery and debug workflows when a session crashes, stalls, repeats a unit, or exits unexpectedly.
user-invocable: false
model-invocable: true
side-effects: file-writes
permission-profile: normal
triggers:
  - repair
  - verify
  - "*"
---

<objective>
Turn scattered sf runtime artifacts into one coherent cause chain. The deliverable is a GitHub-issue-ready report that names the file and line where the bug lives, cites the evidence, and proposes a fix. Forensics is archaeology, not re-run — no modifying state, no triggering commands, just reading the paper trail.
</objective>

<context>
sf persists a lot of runtime evidence under `.sf/`:

- `activity/{seq}-{unitType}-{unitId}.jsonl` — full tool-call and message stream per unit
- `journal/YYYY-MM-DD.jsonl` — iteration-level events (dispatch-match, stuck-detected, guard-block, unit-start/end, terminal)
- `metrics.json` — token/cost ledger; duplicate `type/id` entries indicate a stuck loop
- `auto.lock` — JSON snapshot of the currently-owning PID; stale lock = crash mid-unit
- `forensics/` — saved prior reports
- `debug/` — debug logs if enabled
- `runtime/paused-session.json` — serialized session when autonomous mode paused
- `doctor-history.jsonl` — doctor check history

The `/sf forensics` command pre-computes a forensic report with anomalies flagged. This skill is the manual investigation that goes deeper, or runs when the automated report isn't enough.

Invocation points:
- `/sf forensics` has been run and user wants deeper analysis
- Autonomous mode exited unexpectedly, no obvious cause
- Same unit dispatched multiple times (stuck loop suspected)
- A session crashed and `auto.lock` is stale
- User reports "it just stopped" or "it did the wrong thing"
</context>

<core_principle>
**READ-ONLY EXCEPT THE REPORT.** Forensics touches no live runtime state. Non-mutating inspection commands (e.g., `ps`, `top -b`, `cat /proc/*`) are allowed for checking process status or reading system files. Strictly prohibited: `sf_*` writes, commands that modify runtime state, executing binaries that produce side effects, or re-running the failed unit. The only permitted write is the final report under `.sf/forensics/`. The evidence must stay pristine for future investigations.

**SYMPTOM → ROOT CAUSE, WITH CITATIONS.** Every claim in the report is backed by an artifact path and either a line number or a JSONL field. "The loop got stuck because of a race" is not useful; "`.sf/journal/2026-04-19.jsonl:142` shows `stuck-detected` with flowId X, caused by `dispatch-guard.ts:87` returning the same unit after `unit-end`" is.

**PRE-PARSED LEADS, NOT CONCLUSIONS.** If `/sf forensics` has surfaced anomalies, treat them as hypotheses to verify, not answers.
</core_principle>

<process>

## Step 1: Locate the evidence

Read what's in `.sf/`:

1. `auto.lock` — is it stale? Check PID against `ps` (read-only inspection, allowed). Stale = crash.
2. Most recent `.sf/activity/*.jsonl` — sort by mtime, newest first. That's the last unit that ran.
3. Today's `.sf/journal/YYYY-MM-DD.jsonl` — the iteration-level view.
4. `.sf/metrics.json` — does any `type/id` appear more than once? (stuck loop signal)
5. `.sf/runtime/paused-session.json` — if present, what was the pause reason?

## Step 2: Reconstruct the failure from the activity log

Activity JSONL format:
- Each line is `{type: "message", message: {...}}`.
- `message.role: "assistant"` → `content[]` with `type: "text"` reasoning and `type: "toolCall"` invocations.
- `message.role: "toolResult"` → `{toolCallId, toolName, isError, content}`.
- `usage` on assistant messages tracks tokens and cost.

To trace a failure:
1. Search for `isError: true` tool results in the last activity log. That's usually the proximate symptom.
2. Walk backwards to the assistant message that made the call. Read the `text` content — that's the agent's reasoning at the moment of failure.
3. Keep walking back. Find where the agent's model of the state diverged from reality.

## Step 3: Cross-reference the journal

For each symptom from the activity log, find the matching journal events:
- `stuck-detected` + same `flowId` → the loop detected repetition. `data.reason` says why.
- `guard-block` → a dispatch guard refused to run a unit. Check `data.reason` and trace to `dispatch-guard.ts` logic.
- `unit-end` followed by another `unit-start` for the same `unitId` → re-dispatch. If tied to `stuck-detected`, the artifact verification failed after the unit succeeded.
- `terminal` → autonomous mode decided to stop. `data.reason` tells you why.

Use `flowId` to reconstruct one iteration; use `causedBy` to follow causal chains across iterations.

## Step 4: Name the root cause

A good root cause is:
- Specific: a function, a state transition, a missing guard.
- Falsifiable: if we changed X, would the failure go away?
- Sourced: cites a file and (where applicable) a line number.

Bad root cause: "Autonomous mode got stuck in a loop." Good root cause: "After slice completion, `auto-unit-closeout.ts` emits `unit-end` before `auto-post-unit.ts` updates the roadmap checkbox. The next `iteration-start` finds the same unit `[ ]` and re-dispatches — `dispatch-guard.ts:42` has no check against the freshly-ended `unitId`."

Consult the source map in `src/resources/extensions/sf/prompts/forensics.md` to map symptoms to the likely domain files.

## Step 5: Propose a fix

For the root cause:
- Which file and function holds the bug?
- What minimal change would eliminate it?
- What test would have caught it? Can one be added?
- Is this a regression from a recent commit? Use read-only history commands such as `git log -- path/to/file.ts`; do NOT run commands that modify state.

## Step 6: Write the report

Format the output as a GitHub-issue-ready report:

```markdown
## Symptom

<what the user saw — quote the error or describe the observed behavior>

## Evidence Trail

1. `.sf/auto.lock` — <state: stale / fresh>
2. `.sf/activity/042-slice-S02.jsonl:128` — <isError: true from `sf_task_complete`>
3. `.sf/journal/2026-04-19.jsonl:87` — <stuck-detected flowId 7a3c…>
4. `.sf/metrics.json` — <unit type/id "slice/S02" appears 3 times>

## Root Cause

<specific named cause — file, function, state transition>

`src/resources/extensions/sf/auto-unit-closeout.ts:<line>`: <exactly what goes wrong>

## Proposed Fix

<minimal change — file, function, what to change>

## Test

<what test would have caught this; whether one should be added>

## Confidence

<high / medium / low> — <what would change this confidence>
```

Offer to file this as a GitHub issue through the configured GitHub workflow — explicit confirmation required per the outward-action rule. Also save a copy to `.sf/forensics/<slug>.md` for future reference.

</process>

<anti_patterns>

- **Running any `sf_*` write tool during forensics.** Evidence stays pristine.
- **Re-running the autonomous mode loop to "reproduce."** That overwrites the activity log. Read the existing one.
- **Vague root cause.** "There's a race" is not a root cause. Name the race.
- **No citations.** Every claim gets an artifact path.
- **Skipping the journal.** The journal is the only view that shows dispatch-level decisions.
- **Auto-filing the GitHub issue.** Outward actions need confirmation.

</anti_patterns>

<success_criteria>

- [ ] The symptom is quoted, not paraphrased.
- [ ] Every claim in the evidence trail cites a file and a line or field.
- [ ] The root cause names a specific file, function, or state transition.
- [ ] The proposed fix is minimal and falsifiable.
- [ ] Confidence is stated honestly.
- [ ] Report is saved under `.sf/forensics/` even if not filed as an issue.

</success_criteria>
