Recording probe for the reference session, throwaway

<img src=".github/assets/header.svg" alt="cc-graph: see what Claude knows" width="100%">
<samp>install · the pane · privacy · roadmap · changelog</samp>
<samp>After a long turn, nobody can say without scrolling back what Claude actually has in context. cc-graph is a Claude Code mod that draws it in a live pane beside the conversation: every file Claude read, searched or changed, how those files import each other, and what compaction took away.</samp>
<a name="install"></a> <img src=".github/assets/h-install.svg" alt="install" width="100%">
/plugin install cc-graph --marketplace kuehn-lars/cc-graph
<samp>Then type /graph. Needs Claude Code v2.1.287 or later, in the terminal.</samp>
<a name="the-pane"></a> <img src=".github/assets/h-the-pane.svg" alt="the pane" width="100%">
<img src=".github/assets/showcase.svg" alt="The cc-graph pane in the terminal, annotated: the context window meter, partly read and compacted files, import edges, folder frames, size bars, the focus, search-only files, an edit and a new file, the now line and the legend" width="100%">
| <samp>control</samp> | <samp>what it does</samp> |
|---|---|
| <kbd>g</kbd> | <samp>graph view</samp> |
| <kbd>l</kbd> | <samp>list view</samp> |
| <kbd>1</kbd> … <kbd>9</kbd> | <samp>pick a row in the list</samp> |
maxNodes = 60 | <samp>most files in the graph, in /config</samp> |
importEdges = on | <samp>show file imports, in /config</samp> |
blindSpots = on | <samp>look for blind spots with git, in /config</samp> |
captureThinking = on | <samp>keep thinking excerpts, in /config</samp> |
<samp>Files a shell command created, deleted or changed show too, named with the command that did it, and so do files Claude read with cat, head or sed -n. Files that import a change but were never opened are flagged as blind spots while the turn is still running.</samp>
<samp>The list also shows Claude's newest thinking: a short excerpt per step, with its turn and the files that step read or changed. Claude Code passes thinking text to mods only when "showThinkingSummaries": true is in your settings (or with --thinking-display summarized). This holds for every 5.5 model: Opus, Sonnet and Haiku. Without it, the footer says thinking is hidden.</samp>
<a name="privacy"></a> <img src=".github/assets/h-privacy.svg" alt="privacy" width="100%">
<samp>cc-graph only watches. Its hooks pass every tool result on untouched and record what happened in session memory.</samp>
captureThinking is on. All of it is gone when the conversation ends or on /clear.</samp><a name="roadmap"></a> <img src=".github/assets/h-roadmap.svg" alt="roadmap" width="100%">
<img src=".github/assets/roadmap.svg" alt="Roadmap: M0 Foundations, v0.1 What Claude knows, v0.2 The graph and v0.3 Blind spots shipped; next v0.4 Why it did that, then v0.5 Desktop graph and pointer, v0.6 Decisions and timeline, v1.0 Hardening and listing" width="100%">
<samp>Features, exit gates and spikes per milestone are in ROADMAP.md.</samp>
<samp>The project is spec-driven: specs/ is the source of truth, and each feature goes from spec to failing tests to code. Instructions for Claude Code working on this repository are in CLAUDE.md.</samp>
claude --plugin-dir . # run with the mod loaded (use a copy of fixtures/sample-app)
npx tsc -p . && claude plugin test && node scripts/check-calls.mjs
node scripts/trace.mjs --check # every approved acceptance criterion has a test
<samp><a href="LICENSE">MIT</a> © 2026 Lars Kühn · built with and for <a href="https://claude.com/claude-code">Claude Code</a></samp>
hooks/register.tsx 67 lines1// Throwaway recording probe for fixtures/sessions/reference.ts (see ../../README.md). Not part of the mod.
2// It logs the events of the reference session as the hooks see them and serves the F12 tool `log_decision`.
3import type { Register } from 'claude-code'
4
5const OUT = '/tmp/cc-graph-reference/log-' + Date.now() + '.json'
6const rows: any[] = []
7// Decisions logged by earlier processes of the recording: 0 for turn 1 and the compaction, 1 for turn 2.
8let decisions = 0
9const open = (event: string, input: any) => { const row: any = { event, input }; rows.push(row); return row }
10const dump = () => JSON.stringify(rows, null, 1)
11// F12-R1 and F12-R4 in spirit. The engine refuses an object as a plugin tool's result (string, array or undefined only),
12// so the answer is the JSON of `{ logged, id }`.
13const mine = (e: any) => e.tool === 'mcp__cc-graph__log_decision' ? { result: JSON.stringify({ logged: true, id: 'D' + ++decisions }) } : null
14
15export const register: Register = on => {
16 on('session.start', async ($, e: any, next: any) => {
17 await $.tool.register({ name: 'log_decision', description: 'Log a design decision: a short title, the files it affects and why.', isDeferred: false, inputSchema: { type: 'object', properties: { title: { type: 'string' }, files: { type: 'array', items: { type: 'string' } }, because: { type: 'string' } }, required: ['title', 'files'] } })
18 const row = open('session.start', e)
19 const out = await next(e)
20 row.result = out
21 await $.fs.write(OUT, dump())
22 return out
23 }).catch(($, e, next) => next(e))
24 on('turn.start', async ($, e: any, next: any) => {
25 const row = open('turn.start', e)
26 const out = await next(e)
27 row.result = out
28 await $.fs.write(OUT, dump())
29 return out
30 }).catch(($, e, next) => next(e))
31 on('prompt.submit', async ($, e: any, next: any) => {
32 const row = open('prompt.submit', e)
33 const out = await next(e)
34 row.result = out
35 await $.fs.write(OUT, dump())
36 return out
37 }).catch(($, e, next) => next(e))
38 on('prompt.attachment', async ($, e: any, next: any) => {
39 const row = open('prompt.attachment', e)
40 const out = await next(e)
41 row.result = out
42 await $.fs.write(OUT, dump())
43 return out
44 }).catch(($, e, next) => next(e))
45 on('session.compact', async ($, e: any, next: any) => {
46 const row = open('session.compact', e)
47 const out = await next(e)
48 row.result = out
49 await $.fs.write(OUT, dump())
50 return out
51 }).catch(($, e, next) => next(e))
52 on('classic.InstructionsLoaded', async ($, e: any, next: any) => {
53 const row = open('classic.InstructionsLoaded', e)
54 const out = await next(e)
55 row.result = out
56 await $.fs.write(OUT, dump())
57 return out
58 }).catch(($, e, next) => next(e))
59 on('tool.call', async ($, e: any, next: any) => {
60 const row = open('tool.call', e)
61 const out = mine(e) ?? await next(e)
62 row.result = out
63 await $.fs.write(OUT, dump())
64 return out
65 }).catch(($, e, next) => next(e))
66}
67