---
name: tool-anatomy
description: Decision-grade breakdown of any tool in a Jake project. Triage-first, surfaces health, failure modes, file:line locations, prompts, fallback chains, and recent changes so you can debug fast and decide informed. Default mode produces a full anatomy doc; --triage mode produces a focused diagnosis given an error message. Persists to docs/tool-anatomy/<slug>.md by default.
trigger: /tool-anatomy
---

# /tool-anatomy

When a tool acts up, or before it does, give Jake a decision-grade brief on what it does, how it does it, what's likely failing, and where to look. Optimized for the moment a customer just hit an error and the operator needs to triage in under 2 minutes.

## Usage

```
/tool-anatomy <tool name>                     # full anatomy report
/tool-anatomy <tool name> --triage             # focused diagnosis using the most recent error
/tool-anatomy <tool name> --triage "<error>"   # focused diagnosis with a specific error string
/tool-anatomy compare <tool A> <tool B>        # side-by-side comparison (v2, implement after default works)
```

Tool name is fuzzy-matched. "keyword tool" / "keyword research" / "kw research" all resolve to the `keyword-research` slug. Multi-match → list candidates and ask which one.

## What this is for

Jake runs SaaS tools at scale (FUEL Marketing, Elite AI Advantage). When something breaks the cost of slow triage is real, every minute the tool is down costs trust. The anatomy skill turns a vague complaint ("the keyword tool isn't working") into a structured brief with file:line answers in seconds.

The output is **decision-grade**: it tells the operator what they need to know to fix it, not academic explanation. Triage info goes UP TOP. Reference info (anatomy, prompts, helpers) goes below.

The skill must NOT auto-suggest fixes. Anatomy reports get treated as authoritative and "fix suggestions" become footguns. Surface the symptom, the cause, the location, leave the fix decision to Jake.

## Default behavior, file-write ON, deep mode ALWAYS, failure query ALWAYS

- **File-write**: persist to `docs/tool-anatomy/<slug>.md` overwriting on each run. Past snapshots live in git history. Always include a `> Generated: <ISO>` and `> Last commit: <SHA + message>` header.
- **Depth**: always go deep. Brief summaries are not what this skill is for.
- **Failure query**: always run `api_logs` / `error_reports` / equivalent if the project has them. If the project lacks those tables, note it and skip.

## What You Must Do When Invoked

### Step 1, Resolve the tool slug

Try each in order:
1. Exact match against `src/app/api/tools/<slug>/route.ts`
2. Exact match against `src/app/(dashboard)/dashboard/tools/<slug>/page.tsx`
3. Kebab-case the input ("Keyword Research" → "keyword-research") and retry
4. Fuzzy match: list every directory under `src/app/api/tools/` and pick the one with the smallest Levenshtein distance to the input
5. If still ambiguous, list candidates and ask the user which one

For non-FUEL projects (EAA, future projects), the conventions may differ. Adapt: search common shapes, `src/app/tools/`, `app/api/<slug>/`, `pages/api/tools/`, `lib/tools/<slug>/`. Don't fail fast, if a tool isn't where you expect, grep for the slug across `src/` and report what you find.

### Step 2, Discover the tool's surface

For the resolved slug, find and read:
- **API route**: `src/app/api/tools/<slug>/route.ts` (or equivalent)
- **UI page**: `src/app/(dashboard)/dashboard/tools/<slug>/page.tsx` (or equivalent)
- **Prompts file** (if extracted): `src/lib/prompts/<slug>.ts`, or extract from the route
- **Supporting components**: `grep -l '<slug>' src/components/**`
- **DB tables**: `grep -E '(<slug>|tool === ".*<slug>.*")' src/lib/db/schema.ts`
- **Marketing copy**: `playbook-data.ts` (FUEL specifically), find the entry where `href` includes `/tools/<slug>`

### Step 3, Run the live failure query (if project has the tables)

Check whether `error_reports` and `api_logs` tables exist. If they do, query (substitute the route path):

```sql
SELECT created_at, message FROM error_reports
 WHERE route LIKE '%<slug>%'
 ORDER BY last_seen_at DESC LIMIT 5;

SELECT created_at, service, status, duration_ms, error_message
  FROM api_logs
 WHERE created_at > NOW() - INTERVAL '24 hours'
   AND status = 'error'
   AND (endpoint LIKE '%<slug>%' OR error_message LIKE '%<slug>%')
 ORDER BY created_at DESC LIMIT 10;

-- Per-service health while we're at it (DataForSEO, Replicate, Claude, Serper, OpenAI, etc.)
SELECT service,
       COUNT(*) FILTER (WHERE status='success')::int AS ok,
       COUNT(*) FILTER (WHERE status='error')::int   AS err,
       COUNT(*)::int                                  AS total
  FROM api_logs
 WHERE created_at > NOW() - INTERVAL '24 hours'
 GROUP BY service;
```

For Jake's FUEL project, run via:

```
railway run --service Fuel -- node -e "<query script>"
```

If the project doesn't have these tables (or DB access isn't configured), include a "Live failure data unavailable" section noting what's missing.

### Step 4, Build the failure-mode map by scanning the route

Grep the route file for every `throw new Error(...)`, `return NextResponse.json({ error: ... }, { status: ... })`, and `captureError(...)`. For each, record:
- The error message (the string the user sees)
- The condition that triggers it (read 5-10 lines of context above)
- The file:line

This becomes the "Symptom → Likely Cause → Where to Look" table. It's the killer feature, turns a bug report into a file:line jump in seconds.

### Step 5, Build the fallback chain

Read the route's pipeline. For each `if (...)` / `try-catch` / `withDeadline()` / fallback path, document:
1. What the tool tries first
2. What it falls back to
3. What the user sees on each branch (success vs degraded vs error)

Example for keyword-research:
1. DataForSEO + Serper candidates → Claude enrichment → success
2. → Pure Claude generation (Tier 2) → success with "estimated" labels
3. → User-facing error (Tier 3)

### Step 6, Pull recent commits touching this tool

```
git log --oneline -10 -- src/app/api/tools/<slug>/route.ts \
                         src/app/\(dashboard\)/dashboard/tools/<slug>/page.tsx \
                         src/lib/prompts/<slug>.ts 2>/dev/null
```

Most regressions are "the prompt changed" or "max_tokens changed". Surfacing the last 10 commits puts the cause right next to the symptom.

### Step 7, Write the report

The report goes both **inline in chat** (so Jake can read it immediately) AND **persisted** to `docs/tool-anatomy/<slug>.md` (overwrite-each-run). Use this exact section order:

```markdown
# Tool: <Name> (<slug>)

> Generated: <ISO timestamp>
> Last code change: <commit SHA + date + message>
> Health: <emoji + summary, e.g. "🟢 healthy" / "🟡 degraded" / "🔴 actively failing">

## ⚡ Triage Snapshot

**Health pulse (last 24h):**
- <N> runs · <X>% success · p50 <Ns> · p95 <Ns>
- Avg token cost: <N tokens / run>

**Last failure:**
- <when> · `<error message>` · user_id <abbrev>

**External API health (last 24h):**
- DataForSEO: <X>% (<n>/<total>)
- Replicate: <X>% (<n>/<total>)
- Claude: <X>% (<n>/<total>)
- Serper: <X>% (<n>/<total>)

**Recent commits touching this tool (last 10):**
- abc1234 (2d ago): cut keywords to 5-7
- def5678 (5d ago): bump max_tokens to 8192
- ...

## 🚨 Failure Modes → Where to Look

| Symptom (user sees) | Likely cause | File:line |
|---|---|---|
| "We couldn't pull keyword data" | Both passes returned 0 keywords | route.ts:437 |
| 504 timeout | Replicate cold start exceeds maxDuration | replicate.ts:118 |
| Empty featured_image | Cloudinary upload failed silently | replicate.ts:81 |

## 🔁 Fallback Chain

1. **Primary**: <description>, succeeds → user gets <X>
2. **Tier 2**: <description>, succeeds → user gets <Y, possibly degraded>
3. **Tier 3 (error)**: <description>, user sees `<error message>`

## 🧬 Anatomy

### What it does
<One paragraph plain-English summary>

### User flow
1. UI: `<file path>`, <what the user sees>
2. Form fields: <list>
3. POST → `<route path>` (auth: <gating>, token cost: <N>)
4. Pipeline steps:
   - <step 1>
   - <step 2>
5. Result rendered as: <description>

### Token cost & gating
- Cost per run: <N> tokens (or "free")
- Auth required: <admin / member tier / agency seat / etc.>
- Approval workflow: <yes/no, where wired>

### API surface
- Path: `<route>`
- Method: `POST` (or `GET`)
- maxDuration: <Ns>
- Streaming: <SSE pipeline / direct return / chunked stream>
- Rate limit: <if any>

### External APIs called
- **<Provider 1>**: <what it's used for> · <env var(s)>
- **<Provider 2>**: <what it's used for> · <env var(s)>

### Prompts (verbatim)

#### System prompt
\`\`\`
<paste full system prompt content, exact whitespace>
\`\`\`

#### User message template
\`\`\`
<paste user message builder; for templated strings, show the template + an example interpolation>
\`\`\`

### Data model
- Output stored in: `<table>.<column>` (`tool` field = `'<slug>'`)
- Schema: <brief field list>
- History query: <how the page reads back>

### Helpers / shared code touched
- `<file>`: <what it does>
- `<file>`: <what it does>

## 📁 Files (jump-to)

- API route: `<path>`
- UI page: `<path>`
- Prompts: `<path>`
- Components: `<paths>`
- DB schema: `<path>`
```

### Step 8, File-write

Write the report to `docs/tool-anatomy/<slug>.md`. Create the directory if it doesn't exist. Overwrite if a previous report exists; the timestamp in the header tells the user when it was last refreshed. Don't keep dated copies, git history serves that need.

If a working tree has uncommitted changes blocking the write, ask before forcing.

## Triage mode (`--triage`)

Same data sources, different lens. Triage mode skips most of the anatomy and produces a focused brief:

```
# Tool: <Name>, Active Triage
> Generated: <ISO>
> Error context: <user-supplied or pulled from latest error_reports row>

## What's likely happening
<Match the error against the failure-mode map. Quote the message, point at the file:line, explain the condition that triggers it in 2-3 sentences.>

## Live signal
- Last 5 failures: <times + brief>
- Recent commits that could explain it: <relevant subset>
- External API health: <only the providers this tool uses>

## Where to look first
1. <file:line>, <one-line reason>
2. <file:line>, <one-line reason>
3. <file:line>, <one-line reason>

## What to NOT do
<If the failure mode is well-known and the fix is in flight or known-bad, call it out. Example: "Don't increase max_tokens, the recent fix at commit X already raised this to 8192; the issue is the prompt asking for too much output, not the cap.">
```

The triage report does NOT get written to a file by default, it's situational. If the user wants to archive a specific incident, they can ask explicitly.

## Calibration notes for Jake's projects

- **FUEL Marketing**:
  - Tools live at `src/app/api/tools/<slug>/route.ts` + `src/app/(dashboard)/dashboard/tools/<slug>/page.tsx`
  - Prompts often inline in the route, sometimes extracted to `src/lib/prompts/<slug>.ts`
  - Output stored in `tool_outputs` table with `tool` = `<slug>`
  - Brand voice injection happens via `getBrandVoiceBlock()` and `FUEL_BRAND_POLICY` (auto-prefixed in `src/lib/claude.ts`)
  - Token charging via `chargeTokensAndLog` from `src/lib/token-pool.ts`
  - History via `useToolHistory` hook in `src/lib/hooks/use-tool-history.ts`
  - Approval workflow via `shouldEnqueueForApproval`

- **EAA**:
  - Different conventions; grep first, don't assume

## What to avoid

- **No fix suggestions**. The skill names the symptom and the location, not "I think you should change line X." Skills that prescribe fixes get treated as authoritative and that's a footgun.
- **No padding**. If the failure-mode map only has 2 entries, don't invent a third for symmetry.
- **No academic explanation**. Triage info first; reference info second.
- **No skipping the file-write**. The doc archive is the point, Jake can `git diff` past anatomies to see how a tool's failure profile changed over time.
- **No prompt summary in place of the verbatim prompt**. The model's actual instructions are the most-asked-for piece of information; quote them in full.
- **No assuming database access**. If queries fail (no DB env, project lacks the tables), gracefully degrade with a "Live failure data unavailable: <reason>" note.

## Example output (abbreviated)

```markdown
# Tool: Keyword Research (keyword-research)

> Generated: 2026-05-02T22:14:08Z
> Last code change: b0aa3fb (today): "Keyword research truncation + site-optimizer history rendering"
> Health: 🟢 healthy (was 🟡 24h ago, recent fix bumped max_tokens to 8192)

## ⚡ Triage Snapshot

**Health pulse (last 24h):**
- 14 runs · 100% success · p50 32s · p95 41s
- Avg token cost: 2 tokens / run

**Last failure:**
- 2026-05-02 21:58 · "We couldn't pull keyword data..." · user 7cb8fc5a
  (pre-fix; resolved in b0aa3fb)

**External API health:**
- DataForSEO: 100% (12/12)
- Serper: 100% (14/14)
- Claude: 100% (28/28)

**Recent commits:**
- b0aa3fb (today): Keyword research truncation + site-optimizer history rendering
- ...

## 🚨 Failure Modes → Where to Look

| Symptom | Likely cause | File:line |
|---|---|---|
| "We couldn't pull keyword data" | Both passes returned 0 keywords (max_tokens truncation, fixed) | route.ts:437 |
| Slow first run | DataForSEO cold-cache | dataforseo.ts:84 |
| Stale volume numbers | DataForSEO returned stale snapshot | dataforseo.ts:140 |

## 🔁 Fallback Chain

1. DataForSEO + Serper candidates → Claude enrichment → tagged "real" data
2. → Pure Claude generation, dataSource:"claude" + volume:"estimated" (Tier 2)
3. → "We couldn't pull keyword data..." error + zero token charge (Tier 3)

## 🧬 Anatomy

### What it does
Hybrid keyword research pipeline: DataForSEO Google Ads volume + Serper Google
SERP related searches → Claude picks the best 5-7 with real numbers.
...

(rest of report)
```

That's the shape. Triage-first, decision-grade, file:line specific, persisted for diff.
