/reflect-tree — session quest-tree Produce a single-file interactive HTML quest-tree of a Claude Code session. The vertical spine is the linear sequence of turns; back-edges and off-spine nodes encode wrong turns (corrections, retries, reversals, dead-ends, backtracks) so the user can see the whole journey at a glance — including detours and where the agent had to redo work. This is a sibling to . Same input contract, different output: is a two-pane incidents/recommendations dashboard; is a graph. When to use - User invokes (no args) → analyze the current session from the in-context conversat…

, text, re.M):\n quotes.append(m.group(1))\n # Dedup, preserve order\n seen, out = set(), []\n for q in quotes:\n k = q.strip().lower()\n if k not in seen:\n seen.add(k)\n out.append(q.strip())\n return out\n\n\ndef summarize_tool_input(name, inp):\n \"\"\"One-line summary of a tool call's salient args.\"\"\"\n if not isinstance(inp, dict):\n return \"\"\n keys = [\"file_path\", \"path\", \"command\", \"pattern\", \"url\", \"prompt\", \"description\", \"query\"]\n parts = []\n for k in keys:\n if k in inp and inp[k]:\n v = str(inp[k])\n parts.append(f\"{k}={squash(v, 120)}\")\n if len(parts) >= 2:\n break\n if not parts:\n # Fallback: dump first ~120 chars of the whole input\n try:\n parts.append(squash(json.dumps(inp, ensure_ascii=False), 120))\n except Exception:\n parts.append(\"?\")\n return \", \".join(parts)\n\n\ndef render_tool_result(tr):\n body = tr.get(\"content\", \"\")\n if isinstance(body, list):\n body = \" \".join(\n x.get(\"text\", \"\") if isinstance(x, dict) else str(x) for x in body\n )\n body = str(body)\n low = body.lower()\n if any(m in low for m in REJECTION_MARKERS):\n return f\" ← [REJECTED] {squash(body, 160)}\"\n if tr.get(\"is_error\"):\n return f\" ← [ERR] {squash(body, 160)}\"\n return f\" ← {squash(body, 120)}\"\n\n\ndef render_assistant_text(text):\n text = strip_system_reminders(text).strip()\n if not text:\n return \"\"\n # Collapse internal whitespace lightly but keep paragraph structure for readability.\n lines = [l.rstrip() for l in text.splitlines()]\n lines = [l for l in lines if l.strip()]\n joined = \"\\n\".join(lines)\n return squash(joined, 600)\n\n\ndef compress(events):\n # Meta\n cwd = \"\"\n session_id = \"\"\n for ev in events:\n if not cwd and ev.get(\"cwd\"):\n cwd = ev.get(\"cwd\", \"\")\n if not session_id and ev.get(\"sessionId\"):\n session_id = ev.get(\"sessionId\", \"\")\n if cwd and session_id:\n break\n\n lines = []\n lines.append(f\"# Session transcript (compressed)\")\n lines.append(f\"\")\n lines.append(f\"- session: `{session_id}`\")\n lines.append(f\"- cwd: `{cwd}`\")\n lines.append(f\"- events: {len(events)}\")\n lines.append(f\"\")\n lines.append(f\"Legend: `→` assistant tool call · `←` tool result · `[ERR]` failed · `[REJECTED]` user blocked it · `[COMPACTED]` auto-summary block · `[INTERRUPT]` user interrupted\")\n lines.append(f\"\")\n lines.append(f\"---\")\n lines.append(f\"\")\n\n turn = 0\n first_user_goal = None\n user_count = 0\n rejection_count = 0\n interrupt_count = 0\n compaction_count = 0\n\n for ev in events:\n t = ev.get(\"type\")\n msg = ev.get(\"message\", {}) or {}\n content = msg.get(\"content\")\n\n if t == \"user\":\n turn += 1\n # Distinguish: tool_result-only message vs real user text\n if isinstance(content, list) and content and all(\n isinstance(b, dict) and b.get(\"type\") == \"tool_result\" for b in content\n ):\n for tr in content:\n line = render_tool_result(tr)\n if \"[REJECTED]\" in line:\n rejection_count += 1\n if \"[INTERRUPT\" in line.upper() or \"interrupted by user\" in line.lower():\n interrupt_count += 1\n lines.append(line)\n continue\n\n # Real user message (string or list with text blocks)\n if isinstance(content, str):\n raw = content\n elif isinstance(content, list):\n raw = \"\\n\".join(\n b.get(\"text\", \"\") for b in content if isinstance(b, dict) and b.get(\"type\") == \"text\"\n )\n else:\n raw = \"\"\n\n cleaned = strip_system_reminders(raw)\n\n if not cleaned or is_pure_boilerplate(cleaned):\n continue\n\n if is_compaction(cleaned):\n compaction_count += 1\n quotes = extract_compaction_quotes(cleaned)\n lines.append(f\"## T{turn} · USER [COMPACTED]\")\n lines.append(f\"\")\n lines.append(f\"_(auto-summary block — embedded user quotes extracted below)_\")\n if quotes:\n lines.append(f\"\")\n for q in quotes[:25]:\n lines.append(f\"- > {squash(q, 280)}\")\n lines.append(f\"\")\n continue\n\n user_count += 1\n if first_user_goal is None:\n first_user_goal = squash(cleaned, 300)\n\n lines.append(f\"## T{turn} · USER\")\n lines.append(f\"\")\n lines.append(squash(cleaned, 1200))\n lines.append(f\"\")\n\n elif t == \"assistant\":\n turn += 1\n if not isinstance(content, list):\n continue\n text_blocks = [b.get(\"text\", \"\") for b in content if isinstance(b, dict) and b.get(\"type\") == \"text\"]\n tool_uses = [b for b in content if isinstance(b, dict) and b.get(\"type\") == \"tool_use\"]\n\n text = \"\\n\".join(text_blocks).strip()\n rendered_text = render_assistant_text(text) if text else \"\"\n\n if not rendered_text and not tool_uses:\n continue\n\n header = f\"## T{turn} · ASSISTANT\"\n lines.append(header)\n if rendered_text:\n lines.append(f\"\")\n lines.append(rendered_text)\n for tu in tool_uses:\n name = tu.get(\"name\", \"?\")\n args = summarize_tool_input(name, tu.get(\"input\", {}))\n lines.append(f\"\")\n lines.append(f\"→ {name}({args})\")\n lines.append(f\"\")\n\n # Trailer with cheap structural summary (no classification — just counts)\n lines.append(f\"---\")\n lines.append(f\"\")\n lines.append(f\"## Structural summary\")\n lines.append(f\"\")\n lines.append(f\"- real user turns (after stripping system reminders): {user_count}\")\n lines.append(f\"- compaction blocks: {compaction_count}\")\n lines.append(f\"- tool rejections: {rejection_count}\")\n lines.append(f\"- explicit interrupts: {interrupt_count}\")\n if first_user_goal:\n lines.append(f\"- first user goal: {first_user_goal}\")\n lines.append(f\"\")\n lines.append(f\"_Analyze this transcript yourself: identify wrong-turn moments (corrections, retries, reversals, dead-ends, drift) by reading the user/assistant flow. Do not rely on counts alone._\")\n lines.append(f\"\")\n lines.append(f\"_This transcript is **lossy by design** — tool args, results, and long user messages are truncated. When an incident hinges on detail not visible here (exact rejected input, full error body, diff content), read the source JSONL directly with `Read` (scoped via `offset`/`limit`). The compressed view is an index; the JSONL is the source of truth._\")\n\n return \"\\n\".join(lines)\n\n\ndef main():\n if len(sys.argv) \u003c 2:\n print(\"usage: analyze_session.py \u003cjsonl>\", file=sys.stderr)\n sys.exit(2)\n path = Path(sys.argv[1])\n events = load_events(path)\n sys.stdout.write(compress(events))\n sys.stdout.write(\"\\n\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":10687,"content_sha256":"4445739247e297d9bd45e3c22d3825bbea4d7e7817ca6553096c88aea975e5ab"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"/reflect-tree — session quest-tree","type":"text"}]},{"type":"paragraph","content":[{"text":"Produce a single-file interactive HTML quest-tree of a Claude Code session. The vertical spine is the linear sequence of turns; back-edges and off-spine nodes encode wrong turns (corrections, retries, reversals, dead-ends, backtracks) so the user can see the whole journey at a glance — including detours and where the agent had to redo work.","type":"text"}]},{"type":"paragraph","content":[{"text":"This is a ","type":"text"},{"text":"sibling","type":"text","marks":[{"type":"strong"}]},{"text":" to ","type":"text"},{"text":"/reflect","type":"text","marks":[{"type":"code_inline"}]},{"text":". Same input contract, different output: ","type":"text"},{"text":"/reflect","type":"text","marks":[{"type":"code_inline"}]},{"text":" is a two-pane incidents/recommendations dashboard; ","type":"text"},{"text":"/reflect-tree","type":"text","marks":[{"type":"code_inline"}]},{"text":" is a graph.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to use","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User invokes ","type":"text"},{"text":"/reflect-tree","type":"text","marks":[{"type":"code_inline"}]},{"text":" (no args) → analyze the ","type":"text"},{"text":"current session","type":"text","marks":[{"type":"strong"}]},{"text":" from the in-context conversation. Do ","type":"text"},{"text":"not","type":"text","marks":[{"type":"strong"}]},{"text":" re-read the session JSONL — work from the agent's own memory.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User invokes ","type":"text"},{"text":"/reflect-tree \u003csession-id>","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"/reflect-tree \u003cpath-to-jsonl>","type":"text","marks":[{"type":"code_inline"}]},{"text":" → run ","type":"text"},{"text":"scripts/analyze_session.py \u003cpath>","type":"text","marks":[{"type":"code_inline"}]},{"text":" directly (this skill runs in a forked context — ","type":"text"},{"text":"context: fork","type":"text","marks":[{"type":"code_inline"}]},{"text":" — so the compressed transcript is safe to ingest). The script outputs a compact markdown transcript: system reminders stripped, tool calls/results collapsed to one-liners, compaction blocks expanded with embedded user quotes. Then ","type":"text"},{"text":"you","type":"text","marks":[{"type":"strong"}]},{"text":" classify each turn — the script does NOT classify, it only compresses.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Session JSONLs live under: ","type":"text"},{"text":"~/.claude/projects/\u003cencoded-cwd>/\u003csession-id>.jsonl","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Node classifications","type":"text"}]},{"type":"paragraph","content":[{"text":"Each turn becomes a node. Pick the strongest applicable label:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"normal","type":"text","marks":[{"type":"strong"}]},{"text":" — routine forward progress (user request or assistant tool call/result with no problem)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"correction","type":"text","marks":[{"type":"strong"}]},{"text":" — user pushed back on an approach (","type":"text"},{"text":"\"no\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"don't\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"stop\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"actually\"","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"retry","type":"text","marks":[{"type":"strong"}]},{"text":" — agent ran the same tool/intent 2+ times with variations before it worked","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"reversal","type":"text","marks":[{"type":"strong"}]},{"text":" — agent edited then unwound (Edit → revert, Write → delete)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"dead-end","type":"text","marks":[{"type":"strong"}]},{"text":" — tool failed because of the environment (missing binary, wrong path, OS mismatch)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"backtrack","type":"text","marks":[{"type":"strong"}]},{"text":" — agent abandoned a path and resumed from an earlier state","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"self-correction","type":"text","marks":[{"type":"strong"}]},{"text":" — agent caught its own mistake mid-stream","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Edge types","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"flow","type":"text","marks":[{"type":"strong"}]},{"text":" (default) — solid thin line between consecutive turns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"back-edge","type":"text","marks":[{"type":"strong"}]},{"text":" — curved dashed line from a retry/reversal/correction node back to the ancestor turn it relates to (color matches the classification)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"backtrack","type":"text","marks":[{"type":"strong"}]},{"text":" — curved solid arrow showing where the agent jumped back to","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"wasted-segment","type":"text","marks":[{"type":"strong"}]},{"text":" — thicker grey edge with a ","type":"text"},{"text":"T12–T18 · 7 turns","type":"text","marks":[{"type":"code_inline"}]},{"text":" label when many turns of fruitless searching collapse into one capsule","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Each non-normal node carries a ","type":"text"},{"text":"refs","type":"text","marks":[{"type":"code_inline"}]},{"text":" array listing the ancestor turn IDs it relates to.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Knowledge mining (per-node insight)","type":"text"}]},{"type":"paragraph","content":[{"text":"Every node must carry an ","type":"text"},{"text":"insight","type":"text","marks":[{"type":"code_inline"}]},{"text":" block — the ","type":"text"},{"text":"single most important, reusable lesson","type":"text","marks":[{"type":"strong"}]},{"text":" from that turn, framed so a future agent could drop it into a prompt or rule. This is the value of the tree: each node becomes copyable knowledge.","type":"text"}]},{"type":"paragraph","content":[{"text":"For each turn, extract:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"highlight","type":"text","marks":[{"type":"strong"}]},{"text":" (1 line, ≤120 chars) — the single most important fact/decision/lesson. The \"if you only read one thing from this turn\" sentence.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"insight","type":"text","marks":[{"type":"strong"}]},{"text":" (2–5 lines) — the ","type":"text"},{"text":"why","type":"text","marks":[{"type":"em"}]},{"text":" behind the highlight. What the agent learned, what was non-obvious, what a future agent should do differently or keep doing. Phrase it as durable advice, not a play-by-play.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"prompt_snippet","type":"text","marks":[{"type":"strong"}]},{"text":" (copyable text) — a self-contained chunk a user could paste into CLAUDE.md, a memory file, or a future prompt. Must read independently of the tree (no \"see T5\" references). Format depends on the classification:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"correction / dead-end","type":"text","marks":[{"type":"strong"}]},{"text":" → a rule: ","type":"text"},{"text":"- Always X, because Y","type":"text","marks":[{"type":"code_inline"}]},{"text":" or a ","type":"text"},{"text":"\u003crule>","type":"text","marks":[{"type":"code_inline"}]},{"text":" block","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"retry / waste","type":"text","marks":[{"type":"strong"}]},{"text":" → a recipe: the canonical command/path/tool that worked","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"reversal / backtrack","type":"text","marks":[{"type":"strong"}]},{"text":" → a guard: \"Don't edit X — edit Y instead\" with the reason","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"self-correction","type":"text","marks":[{"type":"strong"}]},{"text":" → a heuristic: \"Before doing X, check Y\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"normal","type":"text","marks":[{"type":"strong"}]},{"text":" (only the load-bearing ones — request, key decision, final answer) → a fact: the goal statement, the chosen approach, the outcome","type":"text"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Skip ","type":"text"},{"text":"prompt_snippet","type":"text","marks":[{"type":"code_inline"}]},{"text":" for filler ","type":"text"},{"text":"normal","type":"text","marks":[{"type":"code_inline"}]},{"text":" turns (routine Reads/Greps with no insight). Better to have 6 strong insights than 50 weak ones.","type":"text"}]},{"type":"paragraph","content":[{"text":"The HTML drawer renders these per-node with a ","type":"text"},{"text":"Copy insight","type":"text","marks":[{"type":"strong"}]},{"text":" button that copies ","type":"text"},{"text":"prompt_snippet","type":"text","marks":[{"type":"code_inline"}]},{"text":" to the clipboard. A header-level ","type":"text"},{"text":"Copy all insights","type":"text","marks":[{"type":"strong"}]},{"text":" button assembles every node's ","type":"text"},{"text":"prompt_snippet","type":"text","marks":[{"type":"code_inline"}]},{"text":" into one markdown document grouped by classification.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Visual language","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Class","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Shape","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Color (CSS token)","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Symbol","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"normal user","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"filled circle","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--accent","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"—","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"normal assistant","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"open circle","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--fg","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"—","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"correction","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"diamond","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--err","type":"text","marks":[{"type":"code_inline"}]},{"text":" border","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"!","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"retry","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"double ring","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--warn","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"↻","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"reversal","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"hollow square","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--warn","type":"text","marks":[{"type":"code_inline"}]},{"text":" dashed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"⇄","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dead-end","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"filled X","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--err","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✕","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"backtrack","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"arrow node","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--muted","type":"text","marks":[{"type":"code_inline"}]},{"text":" dashed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"↶","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"self-correction","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"small diamond","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--ok","type":"text","marks":[{"type":"code_inline"}]},{"text":" border","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✓!","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"X-position: normal = center spine; correction/reversal nudged left; retry/dead-end nudged right. This keeps the spine readable while making detours visually distinct.","type":"text"}]},{"type":"paragraph","content":[{"text":"A legend is rendered inline in the header.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Collapsed-run capsule","type":"text"}]},{"type":"paragraph","content":[{"text":"When grouping consecutive ","type":"text"},{"text":"normal","type":"text","marks":[{"type":"code_inline"}]},{"text":" turns into a capsule, render the ","type":"text"},{"text":"entire label inside the rounded rect","type":"text","marks":[{"type":"strong"}]},{"text":" — turn-range, count, and topic together. Do not place the turn-range outside the box; it looks broken when the rect background only covers part of the label.","type":"text"}]},{"type":"paragraph","content":[{"text":"Pattern (SVG):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"html"},"content":[{"text":"\u003cg class=\"capsule\" transform=\"translate(300 380)\">\n \u003crect x=\"-150\" y=\"-13\" width=\"300\" height=\"26\"/>\n \u003ctext x=\"0\" y=\"0\">\n \u003ctspan class=\"turn-range\">T120–T139\u003c/tspan> · 11 turns · refactor + INFRA_GUIDE.md\n \u003c/text>\n\u003c/g>","type":"text"}]},{"type":"paragraph","content":[{"text":"The rect width must accommodate the full text. Center the ","type":"text"},{"text":"\u003ctext>","type":"text","marks":[{"type":"code_inline"}]},{"text":" (anchor middle, dominant-baseline middle) so it sits inside the rect. Use ","type":"text"},{"text":"rx","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"ry","type":"text","marks":[{"type":"code_inline"}]},{"text":" ≥ 8 for the pill shape. Style the turn-range inline with ","type":"text"},{"text":"\u003ctspan class=\"turn-range\">","type":"text","marks":[{"type":"code_inline"}]},{"text":" so it stays bold/foreground while the rest of the label is muted.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"How to render","type":"text"}]},{"type":"paragraph","content":[{"text":"Synthesize a fresh single-file HTML each run, using ","type":"text"},{"text":"reference/example.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" as ","type":"text"},{"text":"inspiration","type":"text","marks":[{"type":"strong"}]},{"text":" (override anything that doesn't fit the actual session). The reference establishes:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pure SVG + vanilla JS (no external libs). Sessions are \u003c200 turns; force-directed layouts are overkill.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CSS tokens (","type":"text"},{"text":"--bg","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--panel","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--fg","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--muted","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--accent","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--err","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--warn","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--ok","type":"text","marks":[{"type":"code_inline"}]},{"text":", …) — keep this scheme.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Type pairing: Newsreader italic for the wordmark, JetBrains Mono for everything technical, system sans for prose.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Header with goal banner, session metadata, theme toggle, filter chips (one per classification).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SVG canvas (vertical spine) with pan + zoom (","type":"text"},{"text":"viewBox","type":"text","marks":[{"type":"code_inline"}]},{"text":"-based: wheel zoom on cursor; drag to pan; ","type":"text"},{"text":"0","type":"text","marks":[{"type":"code_inline"}]},{"text":" resets; ","type":"text"},{"text":"+","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"-","type":"text","marks":[{"type":"code_inline"}]},{"text":" zoom).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Click node → right-side drawer with full text/excerpt, tool calls, args, results.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Hover node → tooltip with turn id + 1-line summary; highlight all incident edges.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Filter chips dim non-matching nodes/edges to ~15% opacity.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Click a back-edge or a ","type":"text"},{"text":"refs","type":"text","marks":[{"type":"code_inline"}]},{"text":" chip in the drawer → center+pulse the referenced ancestor.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Auto-collapse runs of consecutive ","type":"text"},{"text":"normal","type":"text","marks":[{"type":"code_inline"}]},{"text":" turns into a single capsule labeled ","type":"text"},{"text":"T12–T18 · 7 turns","type":"text","marks":[{"type":"code_inline"}]},{"text":" (click to expand inline).","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Output directory — do ","type":"text"},{"text":"not","type":"text","marks":[{"type":"strong"}]},{"text":" write inside the skill folder. Resolve a temp dir from the environment, in this preference order:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"$TMPDIR","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Unix/macOS)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"$TMP","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"$TEMP","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Windows / Git Bash)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"/tmp","type":"text","marks":[{"type":"code_inline"}]},{"text":" as fallback","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Then create a ","type":"text"},{"text":"reflect-tree/","type":"text","marks":[{"type":"code_inline"}]},{"text":" subdir inside it (","type":"text"},{"text":"mkdir -p","type":"text","marks":[{"type":"code_inline"}]},{"text":") and write the report as ","type":"text"},{"text":"\u003cthat-dir>/reflect-tree/\u003cslug>.html","type":"text","marks":[{"type":"code_inline"}]},{"text":". Slug rules — kebab-case, derived from session goal:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"2–5 words, lowercase, hyphen-separated, ASCII only","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"describe the ","type":"text"},{"text":"task","type":"text","marks":[{"type":"em"}]},{"text":", not the session id (e.g. ","type":"text"},{"text":"auth-middleware-rewrite","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"if the goal is unclear, fall back to ","type":"text"},{"text":"\u003cYYYY-MM-DD>-\u003ctopic>.html","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"Open it in the browser when done: ","type":"text"},{"text":"start \"\" \u003cpath>","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Git Bash on Windows).","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Files in this skill","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"reference/example.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" — canonical inspiration HTML showing the tree layout, all node classifications, all edge types, theme toggle, filters, drawer, pan/zoom. Read before generating.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/analyze_session.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" — JSONL compressor (independent copy of reflect's; no symlink). Strips system reminders, preserves real user messages, collapses tool calls/results to one-liners, expands compaction blocks. Output is markdown to stdout. Use only for explicit sessions — never on the current in-context session.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"Default (no args)","type":"text","marks":[{"type":"strong"}]},{"text":" — reflect on current session:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"From conversation memory, walk the turns in order. For each, decide the classification (default ","type":"text"},{"text":"normal","type":"text","marks":[{"type":"code_inline"}]},{"text":"); for non-normal turns, fill in ","type":"text"},{"text":"refs","type":"text","marks":[{"type":"code_inline"}]},{"text":" (which earlier turn(s) this re-attempts/undoes/contradicts).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Group consecutive ","type":"text"},{"text":"normal","type":"text","marks":[{"type":"code_inline"}]},{"text":" turns into capsules where they would clutter the spine.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read ","type":"text"},{"text":"reference/example.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the current aesthetic / SVG layout patterns.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write the tree to the temp dir (","type":"text"},{"text":"\u003ctemp>/reflect-tree/\u003cslug>.html","type":"text","marks":[{"type":"code_inline"}]},{"text":") — fresh HTML, same look-and-feel as the reference, populated with real turns/edges.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Open it with ","type":"text"},{"text":"start \"\" \u003cpath>","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Explicit session","type":"text","marks":[{"type":"strong"}]},{"text":" — ","type":"text"},{"text":"/reflect-tree \u003cid-or-path>","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resolve to a JSONL path (if just an id, look under ","type":"text"},{"text":"~/.claude/projects/\u003cencoded-cwd>/\u003cid>.jsonl","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"scripts/analyze_session.py \u003cpath>","type":"text","marks":[{"type":"code_inline"}]},{"text":". Skill is forked (","type":"text"},{"text":"context: fork","type":"text","marks":[{"type":"code_inline"}]},{"text":"), so the compressed transcript is safe in context. No subagent.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Walk the transcript and classify each turn yourself — the script does not classify. Focus on user/assistant exchange (what the user wanted vs. what the agent did), not raw tool patterns.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fallback to JSONL when transcript is lossy.","type":"text","marks":[{"type":"strong"}]},{"text":" Transcript truncates tool args/results and long messages. When detail matters (exact rejected input, full error body, Edit diff), ","type":"text"},{"text":"Read","type":"text","marks":[{"type":"code_inline"}]},{"text":" the JSONL directly with ","type":"text"},{"text":"offset","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"limit","type":"text","marks":[{"type":"code_inline"}]},{"text":" scoped to the event. JSONL is source of truth; transcript is the index.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Render and open (default steps 2–5).","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Notes","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The tree is a claim about what ","type":"text"},{"text":"happened","type":"text","marks":[{"type":"em"}]},{"text":". Be honest — include the agent's own mistakes, not just user corrections.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A linear, all-normal spine is a valid output. Do not invent detours to make the tree look more interesting.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sibling: ","type":"text"},{"text":"/reflect","type":"text","marks":[{"type":"code_inline"}]},{"text":" complements this view by producing actionable recommendations. The two can be run on the same session.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","author":"@skillopedia","source":{"stars":127,"repo_name":"claude-code-rules","origin_url":"https://github.com/nikiforovall/claude-code-rules/blob/HEAD/plugins/handbook-reflect/skills/reflect-tree/SKILL.md","repo_owner":"nikiforovall","body_sha256":"c58ebdea61a60f19ebdf47bd0e0c5849be97879f4b5aa7455bd556585eadba2d","cluster_key":"299434f7bf79bed5998e0c24d796191c39a32fd64ad001763ca6cb402cc94b60","clean_bundle":{"format":"clean-skill-bundle-v1","source":"nikiforovall/claude-code-rules/plugins/handbook-reflect/skills/reflect-tree/SKILL.md","attachments":[{"id":"1a1e940d-52e5-5248-a1f7-604067737982","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1a1e940d-52e5-5248-a1f7-604067737982/attachment.html","path":"reference/example.html","size":37204,"sha256":"8e1f12430021693c43915cfee1d51e5f905bbdc27e4d0b4fb4afba91c92f043f","contentType":"text/html; charset=utf-8"},{"id":"0c0229c3-0ac7-5af2-acdd-c7042a5edcbf","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0c0229c3-0ac7-5af2-acdd-c7042a5edcbf/attachment.py","path":"scripts/analyze_session.py","size":10687,"sha256":"4445739247e297d9bd45e3c22d3825bbea4d7e7817ca6553096c88aea975e5ab","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"31e679a8fd7a5df85ac4273e14c50950688899301b39ac5efb1e7fe0077d6377","attachment_count":2,"text_attachments":2,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"plugins/handbook-reflect/skills/reflect-tree/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"general","category_label":"General"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"general","import_tag":"clean-skills-v1","_yaml_error":"YAMLException: bad indentation of a mapping entry (4:16)\n\n 1 | name: reflect-tree\n 2 | description: Visualize a Claude Code session as ...\n 3 | context: fork\n 4 | argument-hint: `\u003ccurrent_session> or "}},"renderedAt":1782980017793}

/reflect-tree — session quest-tree Produce a single-file interactive HTML quest-tree of a Claude Code session. The vertical spine is the linear sequence of turns; back-edges and off-spine nodes encode wrong turns (corrections, retries, reversals, dead-ends, backtracks) so the user can see the whole journey at a glance — including detours and where the agent had to redo work. This is a sibling to . Same input contract, different output: is a two-pane incidents/recommendations dashboard; is a graph. When to use - User invokes (no args) → analyze the current session from the in-context conversat…