---
name: apt:debug
description: "Systematic debugging with persistent state — observe, hypothesize, test, conclude"
apt-skill-version: {{APT_VERSION}}
stage: debug
intent: debug
when_to_use: "The user describes a bug or regression without a known cause and needs systematic investigation rather than a direct fix."
user_invocable: true
internal: false
spawns_agent: false
agent_name: null
task_context: create-new
default_track: DEBUG
default_execution_mode: auto
execution_modes:
  - auto
  - step
allowed-tools: "Bash, Read, Grep, Glob, Write, Edit"
argument-hint: "apt:debug [symptom description]"
gates: []
---
<objective>
Debug an issue using the scientific method: observe symptoms, form hypotheses, test them systematically, and reach a conclusion. State persists across context resets via `.aperant/debug/{session-id}/DEBUG.md`.

**Diagnose-discipline posture loader.** You MUST
load `appendices/diagnose-discipline.md` into reasoning context at
session start. It carries the 6-phase loop (Reproduce → Minimise →
Hypothesise → Instrument → Fix → Regression-test) and the **Phase-1
prerequisite** constraint: do NOT advance from Observe (Section 2) to
Hypothesize (Section 3) until DEBUG.md records `repro_loop_verified:
true`. The appendix carries the rationale, the anti-pattern list
(sprint-to-hypothesis, stack-trace cargo cult, test-the-fix-not-the-bug,
logging-without-minimising), and the distinction between Phase 1
"reproduce" and Phase 2 "minimise". Open question #1 was verified —
apt:debug's existing Section 2 conflates symptom-gathering with
reproduction, so loading this appendix is non-trivial.
</objective>

<your_environment>
- **Working directory:** The project root (where you were invoked)
- **apt-tools path:** `node packages/framework/bin/apt-tools.mjs` or the locally installed `apt-tools` binary
- **Constitution:** Read `AGENTS.md` in the project root if it exists
- **Debug sessions:** `.aperant/debug/{session-id}/DEBUG.md`
</your_environment>

<state_files>
## State Files

**Reads:**
- `AGENTS.md` — Project constitution
- `.aperant/debug/{session-id}/DEBUG.md` — Previous debug state (if resuming)
- Codebase files discovered during investigation

**Writes:**
- `.aperant/debug/{session-id}/DEBUG.md` — Debug session state (survives context resets)
- `.aperant/debug/{session-id}/checkpoints/` — Git checkpoint refs
</state_files>

<process>

## 1. Initialize or Resume Session

Parse `$ARGUMENTS`:
- **New session:** resolve a canonical task id (see 1a below), then use it as the `{session-id}` for the debug working directory.
- **`--resume <session-id>`:** Read existing `.aperant/debug/{session-id}/DEBUG.md` and continue from last state. Skip Section 1a — the session id is already canonical.
- **`--resume` (no id):** Find the most recent session in `.aperant/debug/` and resume it. Skip Section 1a.

If resuming, read DEBUG.md and skip to the current phase (observe/hypothesize/test/conclude).

### 1a. Resolve canonical task id (idempotent — new sessions only)

DEBUG now produces a canonical state.active_tasks record so `/apt:ship` can find the task via the lite ship profile once the fix is committed. The body owns idempotency because `apt-tools task create` does NOT guard against existing ids (see spec §3 R8).

Two invocation paths converge here:

- **Router path** (`/apt` → `/apt:debug`): the router has already called `task create` and injected `task_id` into your skill context.
- **Direct path** (`/apt:debug foo`): no `task_id` in context — you create one now.

Sequence:

1. **If `task_id` is present in skill context** (router path):
   ```bash
   apt-tools task get . --id {task_id}
   ```
   - If exit 0 AND the returned record has `track === "DEBUG"` → reuse, skip the create call. Adopt `task_id` as `{session-id}`.
   - Else (record missing OR wrong track) → call:
     ```bash
     apt-tools task create . --description "$ARGUMENTS" --track DEBUG --id {task_id}
     ```
     Adopt the id as `{session-id}`.

2. **If no `task_id` in skill context** (direct path):
   ```bash
   apt-tools task create . --description "$ARGUMENTS" --track DEBUG
   ```
   Parse the generated `task_id` from the envelope and adopt it as `{session-id}`. The canonical id replaces the legacy `debug-YYYYMMDD-HHmm` form.

The debug working dir at `.aperant/debug/{session-id}/` stays — that working artifact is preserved (per spec §3 R7). The canonical task record at `.aperant/tasks/{task-id}/` is created in parallel by `task create`, and both share the same id.

### 1b. Create debug working directory

```bash
mkdir -p .aperant/debug/{session-id}
mkdir -p .aperant/debug/{session-id}/checkpoints
```

## 2. Observe — Gather Symptoms + Phase 1 (Reproduce — prerequisite)

Collect all available evidence about the bug, then establish a
**reliable repro loop as a hard prerequisite** before advancing to
Section 3 (Hypothesize). The Phase-1-reproduce constraint comes from
`appendices/diagnose-discipline.md` and is load-bearing — you do NOT
emit any hypothesis until DEBUG.md records `repro_loop_verified: true`.

Sub-steps (the existing 4 + 1 prerequisite check):

1. **User description:** Extract from `$ARGUMENTS` what the issue is
2. **Error output:** If the user provided error messages, record them verbatim
3. **Phase 1 — Reproduce (prerequisite, per appendices/diagnose-discipline.md):**
   Establish a reliable repro loop. The loop MUST be:
   - Deterministic (same input → same failure)
   - Cheap (under 10 seconds preferred; certainly under 60)
   - Minimal (no irrelevant setup; just enough to fail)
   Record the repro command + verification in DEBUG.md as
   `repro_command: "..."` and `repro_loop_verified: true`. If you
   cannot reproduce reliably, do NOT proceed to Section 3 — instead,
   loop here: gather more environment context, add logging to the
   target environment, ask the user for repro details. Hypothesis-driven
   debugging without a reliable repro is just hope, dressed up as
   method.
4. **Context:** Note environment details (branch, recent commits, dependencies)

After Phase 1 succeeds, run **Phase 2 — Minimise** (also from the
appendix): strip the repro to the smallest case that still fails.
Record the minimised repro in DEBUG.md as `minimised_repro: "..."`.
Phase 2 prevents Sections 3-5 from anchoring on the first plausible
cause inside a 200-line repro.

```bash
git log --oneline -10
git diff --stat
```

Write initial DEBUG.md:

```markdown
# Debug Session: {session-id}

## Status: OBSERVING

## Problem Statement
{User's description of the issue}

## Symptoms
- {symptom 1}
- {symptom 2}

## Error Output
```
{verbatim error messages}
```

## Environment
- Branch: {branch}
- Recent commits: {list}
- Relevant files: {list}

## Hypotheses
(none yet)

## Tests Performed
(none yet)

## Checkpoints
(none yet)

## Conclusion
(pending)

## Timeline
- {timestamp} — Session started, observing symptoms
```

## 3. Hypothesize — Form Theories

Based on observations, generate 2-5 ranked hypotheses:

1. Investigate the codebase around the failure point using Glob/Grep/Read
2. Look at recent changes that could have introduced the bug
3. Check for common causes: typos, missing imports, wrong types, race conditions, stale cache

Rank hypotheses by likelihood (most likely first).

Update DEBUG.md:
```markdown
## Hypotheses
1. **[MOST LIKELY]** {hypothesis 1} — {evidence supporting it}
2. **[LIKELY]** {hypothesis 2} — {evidence supporting it}
3. **[POSSIBLE]** {hypothesis 3} — {evidence supporting it}
```

## 4. Checkpoint — Save State Before Changes

Before making any code changes, create a git checkpoint:

```bash
git stash push -m "debug-checkpoint-{session-id}-pre-test" || true
git rev-parse HEAD > .aperant/debug/{session-id}/checkpoints/pre-test.ref
```

Record the checkpoint in DEBUG.md:
```markdown
## Checkpoints
- {timestamp} — pre-test: {commit-sha} (before testing hypotheses)
```

If a hypothesis test fails badly, revert to checkpoint:
```bash
git checkout $(cat .aperant/debug/{session-id}/checkpoints/pre-test.ref) -- .
```

## 5. Test — Validate Hypotheses

For each hypothesis (starting with most likely):

### 5a. Design Test
- What specific change or check would confirm/refute this hypothesis?
- What is the expected outcome if the hypothesis is correct?

### 5b. Execute Test
- Make the minimal change needed to test the hypothesis
- Run the relevant command/test
- Record the actual outcome

### 5c. Evaluate
- **Confirmed:** Hypothesis explains the bug. Move to conclusion.
- **Refuted:** Record why. Move to next hypothesis.
- **Partially confirmed:** Refine hypothesis and retest.

Update DEBUG.md after each test:
```markdown
## Tests Performed
### Test 1: {hypothesis being tested}
- **Action:** {what was done}
- **Expected:** {expected result}
- **Actual:** {actual result}
- **Verdict:** CONFIRMED / REFUTED / INCONCLUSIVE
- **Notes:** {any learnings}
```

If all hypotheses are refuted:
- Re-observe with fresh eyes
- Broaden investigation scope
- Generate new hypotheses
- Update DEBUG.md and repeat

## 6. Conclude — Apply Fix

Once the root cause is identified:

### 6a. Root Cause Analysis
Document the root cause clearly:

```markdown
## Conclusion

### Root Cause
{Clear explanation of what caused the bug}

### Why It Happened
{How the bug was introduced — wrong assumption, missing edge case, etc.}

### Fix Applied
{Description of the fix}

### Files Changed
- {file1}: {what changed}
- {file2}: {what changed}

### Prevention
{How to prevent similar bugs — test, lint rule, type constraint, etc.}
```

### 6b. Implement Fix
- Apply the minimal fix that addresses the root cause
- Run verification (tests, typecheck, lint)
- If verification passes, commit:

```bash
node packages/framework/bin/apt-tools.mjs commit "fix: {concise description of fix}" --files {changed-files}
```

### 6c. Update Status

```markdown
## Status: RESOLVED
```

Add to timeline:
```markdown
- {timestamp} — Root cause identified: {brief}
- {timestamp} — Fix applied and verified
- {timestamp} — Session complete
```

## 7. Handle Stuck Cases

If stuck after testing all hypotheses (3+ rounds without progress):

1. Write current state to DEBUG.md with status `STUCK`
2. List what has been tried and ruled out
3. Suggest next steps:
   - Bisect with `git bisect` to find the introducing commit
   - Add more logging/instrumentation
   - Seek human input on specific questions
4. Output:
   ```
   Debug session stuck after {n} rounds.
   State saved to: .aperant/debug/{session-id}/DEBUG.md

   Tried and ruled out:
   {list}

   Suggested next steps:
   {list}

   Resume with: /apt:debug --resume {session-id}
   ```

## 8. Report

On completion:
```
Debug Session: {session-id}
Status: {RESOLVED | STUCK}
Root Cause: {brief description}
Fix: {commit message or "pending"}
Hypotheses tested: {n}
Time: {start} → {end}
State: .aperant/debug/{session-id}/DEBUG.md

{If resolved:} Fix committed. Run /apt:verify to validate.
{If stuck:} Resume with /apt:debug --resume {session-id}
```

</process>
