Skip to content

feat(metrics): score Cursor sessions like Claude Code - #265

Merged
shhdwi merged 1 commit into
trailhq:mainfrom
afeddersen:cursor-metrics
Aug 31, 2026
Merged

shhdwi merged 1 commit into
trailhq:mainfrom
afeddersen:cursor-metrics

Conversation

@afeddersen

Copy link
Copy Markdown
Contributor

Add per-session usage accounting for Cursor — graft vs source reads and token savings — via repo-local .cursor hooks, a graft stats readout, and host-attributed session_summary telemetry.

  • consolidate the Codex/Cursor/MCP config writers onto a shared ConfigWrite type and a readJsonObject load-or-skip helper (src/hosts/config-write.ts)
  • force-close the just-ended conversation on Cursor's sessionEnd instead of waiting for the idle sweep, and stamp the recording host on each session
  • anchor graft MCP-tool detection to the real tool-name list rather than a loose "graft" substring (src/mcp/tool-names.ts)

Add per-session usage accounting for Cursor — graft vs source reads and
token savings — via repo-local .cursor hooks, a `graft stats` readout, and
host-attributed `session_summary` telemetry.
- consolidate the Codex/Cursor/MCP config writers onto a shared ConfigWrite
  type and a readJsonObject load-or-skip helper (src/hosts/config-write.ts)
- force-close the just-ended conversation on Cursor's sessionEnd instead of
  waiting for the idle sweep, and stamp the recording host on each session
- anchor graft MCP-tool detection to the real tool-name list rather than a
  loose "graft" substring (src/mcp/tool-names.ts)
@github-actions

Copy link
Copy Markdown
Contributor

🌱 graft blast radius

5 areas changed → 3 areas can be affected. 5 dependent symbols, depth 2.
Tests: Tool Registry has tests the diff did not touch; 3 areas updated their tests.
Tag: @anirudhkumar-nanonets — 8 of 8 areas · @shhdwi — 5 of 8 areas

flowchart TB
  A0(("Claude Initialization<br/>2 symbols"))
  A1(("MCP Server<br/>2 symbols"))
  A2(("Wiring Configuration<br/>1 symbol"))
  classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
  class A0,A1,A2 reached;
Loading
Can be affected Symbols Nearest hop Reached from
Claude Initialization 2 src/claude/init.ts:L59-L90 runInit — calls, depth 1 Session State Management, Host Integration
MCP Server 2 src/mcp/server.ts:L1-L125 server.ts — imports, depth 1 Tool Registry, Telemetry
Wiring Configuration 1 src/upkeep-run.ts:L41-L46 rewriteWiring — calls, depth 1 Session State Management, Host Integration
Who knows this code — 2 people across 8 areas
Area Who knows it
Host Integration · changed @shhdwi — 11 commits, last 16d ago · @anirudhkumar-nanonets — 4 commits, last 2d ago
Session State Management · changed @shhdwi — 14 commits, last 17d ago · @anirudhkumar-nanonets — 13 commits, last 2d ago
Tool Registry · changed @anirudhkumar-nanonets — 36 commits, last 2d ago · @shhdwi — 28 commits, last 16d ago
Telemetry · changed @anirudhkumar-nanonets — 2 commits, last 2d ago
Test Runner · changed @anirudhkumar-nanonets — 1 commit, last 26d ago
Claude Initialization · affected @anirudhkumar-nanonets — 7 commits, last 17d ago · @shhdwi — 4 commits, last 1mo ago
MCP Server · affected @anirudhkumar-nanonets — 4 commits, last 8d ago · @shhdwi — 2 commits, last 1mo ago
Wiring Configuration · affected @anirudhkumar-nanonets — 1 commit, last 16d ago

Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no @ has no GitHub handle in its commit email — tag them by hand, or add a .mailmap entry. A suggestion from history, not a CODEOWNERS rule.

All 5 dependent symbols, grouped by area

Claude Initialization — 2 symbols in 2 files

  • src/claude/init.ts:L59-L90 — runInit (calls, depth 1)
    67: try { existing = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch { /* none/invalid → start fresh */ }
  • src/claude/statusline.ts:L24-L35 — main (calls, depth 2)
    24: export function main(): void {

MCP Server — 2 symbols in 1 file

  • src/mcp/server.ts:L1-L125 — server.ts (imports, depth 1)
    6: import { TOOLS, callTool } from './tools.js';
  • src/mcp/server.ts:L44-L124 — startMcpServer (calls, depth 2)
    47: // refreshes rule files an older `graft init` wrote, and kicks off the cached

Wiring Configuration — 1 symbol in 1 file

  • src/upkeep-run.ts:L41-L46 — rewriteWiring (calls, depth 1)
    45: runHostsInit(repo, { agents: others, global: opts.global, mcp: opts.mcp, hooks: opts.hooks });
Test signal per changed area — 3 ✓ · 1 ⚠ · 1 –

Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.

  • ✓ Host Integration — 8 of 13 reached · 2 test files changed here: test/hosts-config-write.test.ts, test/hosts-cursor-hooks.test.ts
    • not reached: writeOwned, configPathFor, desiredEntries, shimPathFor, mergeJsonKey
  • ✓ Session State Management — 9 of 20 reached · 2 test files changed here: test/claude-settings-merge.test.ts, test/session-metrics.test.ts
    • not reached: classifyAndScore, cursorSessionId, handleCursorMcp, handleCursorPostTool, handleToolUse, main, isShellTool, graftBlocks, …3 more
  • ⚠ Tool Registry — 1 of 2 reached · 5 test files reach it, none changed here
    • not reached: sumSavingsFooters
  • ✓ Telemetry — 2 of 2 reached · 1 test file changed here: test/telemetry-sessions.test.ts
  • – Test Runner — no function, method or class changed here
15 test suites also reference this code

17 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.

  • test/claude-init.test.ts
  • test/claude-state.test.ts
  • test/claude-tally.test.ts
  • test/cli-picker.test.ts
  • test/graph-follow-submodules.test.ts
  • test/graph-load.test.ts
  • test/graph-refresh.test.ts
  • test/graph-seed.test.ts
  • test/hosts-antigravity.test.ts
  • test/hosts-codex-hooks.test.ts
  • test/hosts-init.test.ts
  • test/hosts-mcp-config.test.ts
  • test/hosts-plan.test.ts
  • test/hosts-retract.test.ts
  • test/mcp-tools.test.ts

⚠️ 1 changed file not in the graph (.gitignore) — no parser claims the extension, or the index predates the file.

graft blast · origin/main...HEAD · depth 2 · 25 changed files

Open the interactive graph → — click an area to see its dependent symbols at file:line.

github-actions Bot added a commit that referenced this pull request Aug 29, 2026
@shhdwi

shhdwi commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Verified locally on top of current main (clean merge; build clean; 95/95 across session-metrics, cursor-hooks, claude-hooks, telemetry-sessions).

Real session scoring for Cursor, mirroring Claude Code: cursor-hooks.ts writes .cursor/hooks.json wiring postToolUse/afterMCPExecution/sessionEnd to shared classify+record in session-metrics.ts (previously graftReads/sourceReads were never incremented, so Cursor session_summary shipped 0/0). Live demo through the hook: before → "no session recorded"; after a Cursor Read + a graft MCP call + a graft ask: graft reads 2, source reads 1, 67% graft, ~6,600 saved, attributed to host cursor. MCP double-count is correctly guarded, and the new graft stats reads the local mix. Nice, complete piece of work — thanks! Merging.

@shhdwi
shhdwi merged commit d89583d into trailhq:main Aug 31, 2026
2 checks passed
github-actions Bot added a commit that referenced this pull request Aug 31, 2026
FrancescoMerenda pushed a commit to FrancescoMerenda/Graft that referenced this pull request Sep 7, 2026
Add per-session usage accounting for Cursor — graft vs source reads and
token savings — via repo-local .cursor hooks, a `graft stats` readout, and
host-attributed `session_summary` telemetry.
- consolidate the Codex/Cursor/MCP config writers onto a shared ConfigWrite
  type and a readJsonObject load-or-skip helper (src/hosts/config-write.ts)
- force-close the just-ended conversation on Cursor's sessionEnd instead of
  waiting for the idle sweep, and stamp the recording host on each session
- anchor graft MCP-tool detection to the real tool-name list rather than a
  loose "graft" substring (src/mcp/tool-names.ts)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants