@nanonets/graft 0.13.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/README.md +26 -10
  2. package/dist/ai/crux.d.ts +14 -1
  3. package/dist/ai/crux.d.ts.map +1 -1
  4. package/dist/ai/crux.js +39 -51
  5. package/dist/ai/crux.js.map +1 -1
  6. package/dist/ai/failure.d.ts +13 -2
  7. package/dist/ai/failure.d.ts.map +1 -1
  8. package/dist/ai/failure.js +16 -4
  9. package/dist/ai/failure.js.map +1 -1
  10. package/dist/ai/llm/factory.d.ts +10 -1
  11. package/dist/ai/llm/factory.d.ts.map +1 -1
  12. package/dist/ai/llm/factory.js +16 -0
  13. package/dist/ai/llm/factory.js.map +1 -1
  14. package/dist/ai/llm/litellm.d.ts +34 -0
  15. package/dist/ai/llm/litellm.d.ts.map +1 -0
  16. package/dist/ai/llm/litellm.js +45 -0
  17. package/dist/ai/llm/litellm.js.map +1 -0
  18. package/dist/ai/llm/openai.d.ts.map +1 -1
  19. package/dist/ai/llm/openai.js +66 -7
  20. package/dist/ai/llm/openai.js.map +1 -1
  21. package/dist/ai/llm/orcarouter.d.ts +34 -0
  22. package/dist/ai/llm/orcarouter.d.ts.map +1 -0
  23. package/dist/ai/llm/orcarouter.js +45 -0
  24. package/dist/ai/llm/orcarouter.js.map +1 -0
  25. package/dist/ai/llm/recover-tool.d.ts +19 -0
  26. package/dist/ai/llm/recover-tool.d.ts.map +1 -0
  27. package/dist/ai/llm/recover-tool.js +109 -0
  28. package/dist/ai/llm/recover-tool.js.map +1 -0
  29. package/dist/ai/providers.d.ts.map +1 -1
  30. package/dist/ai/providers.js +15 -3
  31. package/dist/ai/providers.js.map +1 -1
  32. package/dist/ai/synthesize.d.ts.map +1 -1
  33. package/dist/ai/synthesize.js +19 -5
  34. package/dist/ai/synthesize.js.map +1 -1
  35. package/dist/app/checkout.d.ts +28 -0
  36. package/dist/app/checkout.d.ts.map +1 -0
  37. package/dist/app/checkout.js +100 -0
  38. package/dist/app/checkout.js.map +1 -0
  39. package/dist/app/events.d.ts +37 -0
  40. package/dist/app/events.d.ts.map +1 -0
  41. package/dist/app/events.js +59 -0
  42. package/dist/app/events.js.map +1 -0
  43. package/dist/app/identity.d.ts +54 -0
  44. package/dist/app/identity.d.ts.map +1 -0
  45. package/dist/app/identity.js +96 -0
  46. package/dist/app/identity.js.map +1 -0
  47. package/dist/app/main.d.ts +2 -0
  48. package/dist/app/main.d.ts.map +1 -0
  49. package/dist/app/main.js +56 -0
  50. package/dist/app/main.js.map +1 -0
  51. package/dist/app/pages.d.ts +39 -0
  52. package/dist/app/pages.d.ts.map +1 -0
  53. package/dist/app/pages.js +86 -0
  54. package/dist/app/pages.js.map +1 -0
  55. package/dist/app/queue.d.ts +40 -0
  56. package/dist/app/queue.d.ts.map +1 -0
  57. package/dist/app/queue.js +68 -0
  58. package/dist/app/queue.js.map +1 -0
  59. package/dist/app/review.d.ts +23 -0
  60. package/dist/app/review.d.ts.map +1 -0
  61. package/dist/app/review.js +139 -0
  62. package/dist/app/review.js.map +1 -0
  63. package/dist/app/server.d.ts +40 -0
  64. package/dist/app/server.d.ts.map +1 -0
  65. package/dist/app/server.js +117 -0
  66. package/dist/app/server.js.map +1 -0
  67. package/dist/ask/ask.d.ts +35 -0
  68. package/dist/ask/ask.d.ts.map +1 -1
  69. package/dist/ask/ask.js +427 -63
  70. package/dist/ask/ask.js.map +1 -1
  71. package/dist/ask/file-rank.d.ts +72 -0
  72. package/dist/ask/file-rank.d.ts.map +1 -0
  73. package/dist/ask/file-rank.js +160 -0
  74. package/dist/ask/file-rank.js.map +1 -0
  75. package/dist/ask/file-selection.d.ts +4 -0
  76. package/dist/ask/file-selection.d.ts.map +1 -1
  77. package/dist/ask/file-selection.js +25 -0
  78. package/dist/ask/file-selection.js.map +1 -1
  79. package/dist/ask/fuse.d.ts +16 -0
  80. package/dist/ask/fuse.d.ts.map +1 -1
  81. package/dist/ask/fuse.js +21 -4
  82. package/dist/ask/fuse.js.map +1 -1
  83. package/dist/ask/graphrank.d.ts +18 -0
  84. package/dist/ask/graphrank.d.ts.map +1 -1
  85. package/dist/ask/graphrank.js +65 -19
  86. package/dist/ask/graphrank.js.map +1 -1
  87. package/dist/blast/blast-cli.d.ts +5 -0
  88. package/dist/blast/blast-cli.d.ts.map +1 -1
  89. package/dist/blast/blast-cli.js +21 -30
  90. package/dist/blast/blast-cli.js.map +1 -1
  91. package/dist/blast/blast.d.ts +9 -0
  92. package/dist/blast/blast.d.ts.map +1 -1
  93. package/dist/blast/blast.js.map +1 -1
  94. package/dist/blast/name.d.ts +13 -0
  95. package/dist/blast/name.d.ts.map +1 -1
  96. package/dist/blast/name.js +36 -0
  97. package/dist/blast/name.js.map +1 -1
  98. package/dist/blast/owners.d.ts +85 -0
  99. package/dist/blast/owners.d.ts.map +1 -0
  100. package/dist/blast/owners.js +272 -0
  101. package/dist/blast/owners.js.map +1 -0
  102. package/dist/blast/render.d.ts.map +1 -1
  103. package/dist/blast/render.js +105 -0
  104. package/dist/blast/render.js.map +1 -1
  105. package/dist/blast/viz.d.ts.map +1 -1
  106. package/dist/blast/viz.js +10 -0
  107. package/dist/blast/viz.js.map +1 -1
  108. package/dist/claude/format.js +1 -1
  109. package/dist/claude/format.js.map +1 -1
  110. package/dist/claude/hooks.d.ts.map +1 -1
  111. package/dist/claude/hooks.js +132 -22
  112. package/dist/claude/hooks.js.map +1 -1
  113. package/dist/claude/init.d.ts +1 -0
  114. package/dist/claude/init.d.ts.map +1 -1
  115. package/dist/claude/init.js +1 -1
  116. package/dist/claude/init.js.map +1 -1
  117. package/dist/claude/session-metrics.d.ts +63 -0
  118. package/dist/claude/session-metrics.d.ts.map +1 -0
  119. package/dist/claude/session-metrics.js +181 -0
  120. package/dist/claude/session-metrics.js.map +1 -0
  121. package/dist/claude/settings-merge.d.ts +18 -1
  122. package/dist/claude/settings-merge.d.ts.map +1 -1
  123. package/dist/claude/settings-merge.js +70 -27
  124. package/dist/claude/settings-merge.js.map +1 -1
  125. package/dist/claude/state.d.ts +25 -0
  126. package/dist/claude/state.d.ts.map +1 -1
  127. package/dist/claude/state.js +14 -1
  128. package/dist/claude/state.js.map +1 -1
  129. package/dist/claude/statusline.d.ts +5 -3
  130. package/dist/claude/statusline.d.ts.map +1 -1
  131. package/dist/claude/statusline.js +6 -4
  132. package/dist/claude/statusline.js.map +1 -1
  133. package/dist/claude/tally.d.ts +23 -0
  134. package/dist/claude/tally.d.ts.map +1 -0
  135. package/dist/claude/tally.js +138 -0
  136. package/dist/claude/tally.js.map +1 -0
  137. package/dist/cli.js +145 -4
  138. package/dist/cli.js.map +1 -1
  139. package/dist/context/build.d.ts +11 -0
  140. package/dist/context/build.d.ts.map +1 -1
  141. package/dist/context/build.js +49 -9
  142. package/dist/context/build.js.map +1 -1
  143. package/dist/context/check.d.ts.map +1 -1
  144. package/dist/context/check.js +5 -12
  145. package/dist/context/check.js.map +1 -1
  146. package/dist/context/node-file.d.ts +2 -1
  147. package/dist/context/node-file.d.ts.map +1 -1
  148. package/dist/context/node-file.js +70 -3
  149. package/dist/context/node-file.js.map +1 -1
  150. package/dist/context/savings.d.ts +8 -0
  151. package/dist/context/savings.d.ts.map +1 -1
  152. package/dist/context/savings.js +14 -0
  153. package/dist/context/savings.js.map +1 -1
  154. package/dist/engine.d.ts +4 -0
  155. package/dist/engine.d.ts.map +1 -1
  156. package/dist/engine.js +2 -0
  157. package/dist/engine.js.map +1 -1
  158. package/dist/graph/bindings.d.ts +6 -5
  159. package/dist/graph/bindings.d.ts.map +1 -1
  160. package/dist/graph/bindings.js +117 -5
  161. package/dist/graph/bindings.js.map +1 -1
  162. package/dist/graph/build.d.ts +4 -0
  163. package/dist/graph/build.d.ts.map +1 -1
  164. package/dist/graph/build.js +7 -4
  165. package/dist/graph/build.js.map +1 -1
  166. package/dist/graph/cards.d.ts +4 -3
  167. package/dist/graph/cards.d.ts.map +1 -1
  168. package/dist/graph/cards.js +34 -6
  169. package/dist/graph/cards.js.map +1 -1
  170. package/dist/graph/check.d.ts.map +1 -1
  171. package/dist/graph/check.js +36 -6
  172. package/dist/graph/check.js.map +1 -1
  173. package/dist/graph/enrich.d.ts +4 -4
  174. package/dist/graph/enrich.d.ts.map +1 -1
  175. package/dist/graph/enrich.js +32 -6
  176. package/dist/graph/enrich.js.map +1 -1
  177. package/dist/graph/extract.d.ts +9 -2
  178. package/dist/graph/extract.d.ts.map +1 -1
  179. package/dist/graph/extract.js +356 -12
  180. package/dist/graph/extract.js.map +1 -1
  181. package/dist/graph/fingerprint.d.ts +6 -1
  182. package/dist/graph/fingerprint.d.ts.map +1 -1
  183. package/dist/graph/fingerprint.js +7 -3
  184. package/dist/graph/fingerprint.js.map +1 -1
  185. package/dist/graph/generic.d.ts +10 -0
  186. package/dist/graph/generic.d.ts.map +1 -1
  187. package/dist/graph/generic.js +20 -3
  188. package/dist/graph/generic.js.map +1 -1
  189. package/dist/graph/invariants.d.ts +3 -2
  190. package/dist/graph/invariants.d.ts.map +1 -1
  191. package/dist/graph/invariants.js +6 -3
  192. package/dist/graph/invariants.js.map +1 -1
  193. package/dist/graph/refresh.d.ts.map +1 -1
  194. package/dist/graph/refresh.js +6 -2
  195. package/dist/graph/refresh.js.map +1 -1
  196. package/dist/graph/resolve.d.ts +1 -1
  197. package/dist/graph/resolve.d.ts.map +1 -1
  198. package/dist/graph/resolve.js +106 -5
  199. package/dist/graph/resolve.js.map +1 -1
  200. package/dist/graph/scopes.d.ts.map +1 -1
  201. package/dist/graph/scopes.js +2 -1
  202. package/dist/graph/scopes.js.map +1 -1
  203. package/dist/graph/source-files.d.ts +7 -2
  204. package/dist/graph/source-files.d.ts.map +1 -1
  205. package/dist/graph/source-files.js +19 -6
  206. package/dist/graph/source-files.js.map +1 -1
  207. package/dist/graph/workspace-cli.d.ts +2 -0
  208. package/dist/graph/workspace-cli.d.ts.map +1 -1
  209. package/dist/graph/workspace-cli.js +3 -0
  210. package/dist/graph/workspace-cli.js.map +1 -1
  211. package/dist/graph/workspace.d.ts +2 -0
  212. package/dist/graph/workspace.d.ts.map +1 -1
  213. package/dist/graph/workspace.js +173 -6
  214. package/dist/graph/workspace.js.map +1 -1
  215. package/dist/hosts/antigravity.d.ts +2 -2
  216. package/dist/hosts/antigravity.d.ts.map +1 -1
  217. package/dist/hosts/codex-hooks.d.ts +2 -6
  218. package/dist/hosts/codex-hooks.d.ts.map +1 -1
  219. package/dist/hosts/codex-hooks.js +7 -30
  220. package/dist/hosts/codex-hooks.js.map +1 -1
  221. package/dist/hosts/config-write.d.ts +36 -0
  222. package/dist/hosts/config-write.d.ts.map +1 -0
  223. package/dist/hosts/config-write.js +63 -0
  224. package/dist/hosts/config-write.js.map +1 -0
  225. package/dist/hosts/cursor-hooks.d.ts +18 -0
  226. package/dist/hosts/cursor-hooks.d.ts.map +1 -0
  227. package/dist/hosts/cursor-hooks.js +100 -0
  228. package/dist/hosts/cursor-hooks.js.map +1 -0
  229. package/dist/hosts/init.d.ts +2 -2
  230. package/dist/hosts/init.d.ts.map +1 -1
  231. package/dist/hosts/init.js +8 -2
  232. package/dist/hosts/init.js.map +1 -1
  233. package/dist/hosts/mcp-config.d.ts +15 -5
  234. package/dist/hosts/mcp-config.d.ts.map +1 -1
  235. package/dist/hosts/mcp-config.js +51 -15
  236. package/dist/hosts/mcp-config.js.map +1 -1
  237. package/dist/hosts/plan.d.ts.map +1 -1
  238. package/dist/hosts/plan.js +2 -0
  239. package/dist/hosts/plan.js.map +1 -1
  240. package/dist/hosts/retract.d.ts +47 -0
  241. package/dist/hosts/retract.d.ts.map +1 -0
  242. package/dist/hosts/retract.js +473 -0
  243. package/dist/hosts/retract.js.map +1 -0
  244. package/dist/index.d.ts +2 -0
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js +2 -0
  247. package/dist/index.js.map +1 -1
  248. package/dist/ingest/fs.d.ts +39 -5
  249. package/dist/ingest/fs.d.ts.map +1 -1
  250. package/dist/ingest/fs.js +104 -11
  251. package/dist/ingest/fs.js.map +1 -1
  252. package/dist/mcp/tool-names.d.ts +20 -0
  253. package/dist/mcp/tool-names.d.ts.map +1 -0
  254. package/dist/mcp/tool-names.js +36 -0
  255. package/dist/mcp/tool-names.js.map +1 -0
  256. package/dist/mcp/tools.d.ts +11 -2
  257. package/dist/mcp/tools.d.ts.map +1 -1
  258. package/dist/mcp/tools.js +9 -21
  259. package/dist/mcp/tools.js.map +1 -1
  260. package/dist/telemetry/contract.d.ts.map +1 -1
  261. package/dist/telemetry/contract.js +17 -3
  262. package/dist/telemetry/contract.js.map +1 -1
  263. package/dist/telemetry/flush.d.ts.map +1 -1
  264. package/dist/telemetry/flush.js +1 -0
  265. package/dist/telemetry/flush.js.map +1 -1
  266. package/dist/telemetry/identity.d.ts +7 -0
  267. package/dist/telemetry/identity.d.ts.map +1 -1
  268. package/dist/telemetry/identity.js.map +1 -1
  269. package/dist/telemetry/index.d.ts +2 -2
  270. package/dist/telemetry/index.d.ts.map +1 -1
  271. package/dist/telemetry/index.js +2 -2
  272. package/dist/telemetry/index.js.map +1 -1
  273. package/dist/telemetry/sessions.d.ts +19 -2
  274. package/dist/telemetry/sessions.d.ts.map +1 -1
  275. package/dist/telemetry/sessions.js +57 -34
  276. package/dist/telemetry/sessions.js.map +1 -1
  277. package/dist/telemetry/track.d.ts +22 -3
  278. package/dist/telemetry/track.d.ts.map +1 -1
  279. package/dist/telemetry/track.js +34 -5
  280. package/dist/telemetry/track.js.map +1 -1
  281. package/dist/upkeep-run.js +1 -1
  282. package/dist/upkeep-run.js.map +1 -1
  283. package/dist/upkeep.d.ts +7 -4
  284. package/dist/upkeep.d.ts.map +1 -1
  285. package/dist/upkeep.js +2 -1
  286. package/dist/upkeep.js.map +1 -1
  287. package/dist/util/state.d.ts +12 -2
  288. package/dist/util/state.d.ts.map +1 -1
  289. package/dist/util/state.js +4 -0
  290. package/dist/util/state.js.map +1 -1
  291. package/dist/viewer/app.js +2 -1
  292. package/dist/viewer/style.css +14 -0
  293. package/dist/viz/assemble.d.ts +17 -0
  294. package/dist/viz/assemble.d.ts.map +1 -1
  295. package/dist/viz/assemble.js.map +1 -1
  296. package/package.json +12 -4
  297. package/scripts/graph-quality.mjs +3 -3
  298. package/scripts/postinstall.mjs +42 -3
  299. package/scripts/run-tests.mjs +14 -0
  300. package/scripts/tally-audit.mjs +140 -0
  301. package/dist/ai/llm/claude-cli.d.ts +0 -60
  302. package/dist/ai/llm/claude-cli.d.ts.map +0 -1
  303. package/dist/ai/llm/claude-cli.js +0 -417
  304. package/dist/ai/llm/claude-cli.js.map +0 -1
  305. package/dist/util/num.d.ts +0 -3
  306. package/dist/util/num.d.ts.map +0 -1
  307. package/dist/util/num.js +0 -20
  308. package/dist/util/num.js.map +0 -1
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Session usage accounting, shared across every host (Claude Code, Cursor, and
3
+ * plain MCP). It answers the one question the README rests on: when an agent had
4
+ * both graft and grep in front of it, which did it reach for, and how much did
5
+ * that save?
6
+ *
7
+ * The counters (`graftReads`, `sourceReads`, `savedTokens`) already live on
8
+ * {@link SessionState}; before this module nothing ever incremented the first
9
+ * two, so `session_summary` telemetry shipped 0/0 for every session. Everything
10
+ * here is a pure classify + read-modify-write over `graft/.cache/session/`, so a
11
+ * host adapter is a few lines: parse its payload, call {@link recordToolUse}.
12
+ *
13
+ * Two payload shapes feed it, confirmed against the vendor docs rather than
14
+ * guessed (the matcher depends on getting the names right):
15
+ *
16
+ * - Claude Code `PostToolUse` — `{ tool_name, tool_input: { command }, tool_response }`.
17
+ * A graft retrieval prints a `[graft] tokens saved ≈ N` footer into
18
+ * `tool_response`, which is itself proof graft ran.
19
+ * - Cursor `postToolUse` — `{ tool_name, tool_input, tool_output, conversation_id }`
20
+ * (https://cursor.com/docs/hooks). `tool_output` is the JSON-stringified
21
+ * result; a Shell `graft …` call carries the same footer inside its stdout.
22
+ * - Cursor `afterMCPExecution` — `{ tool_name, tool_input, result_json, duration }`.
23
+ * Fires only for MCP tools, so a graft tool is recognised by its name and
24
+ * its savings read out of `result_json`.
25
+ *
26
+ * MCP tool names arrive host-prefixed in different shapes (`graft_find_code`,
27
+ * `MCP:graft_find_code`, `mcp__graft__graft_find_code`). {@link isGraftMcpTool}
28
+ * strips the prefix to the last delimited segment and checks it against the real
29
+ * name list (`GRAFT_MCP_TOOL_NAMES`), so a graft tool is recognised across every
30
+ * host format while a third-party tool that merely contains "graft" is not.
31
+ */
32
+ import { statSync } from 'node:fs';
33
+ import { join } from 'node:path';
34
+ import { sumSavingsFooters } from '../context/savings.js';
35
+ import { readSession, writeSession, sessionDir, listSessionIds } from './state.js';
36
+ import { GRAFT_MCP_TOOL_NAMES } from '../mcp/tool-names.js';
37
+ /** Native tools that mean "the agent went to read source itself" — the grep/read
38
+ * path graft is meant to replace. These are the host tool NAMES (Claude Code and
39
+ * Cursor both use Read/Grep/Glob; Cursor adds Search); shell builtins like `ls`
40
+ * are commands, never tool names, so they belong to the Shell branch, not here.
41
+ * Compared lower-cased. */
42
+ const SOURCE_TOOLS = new Set(['read', 'grep', 'glob', 'search']);
43
+ /** The host's shell tool, whose `command` we then inspect for a graft invocation. */
44
+ function isShellTool(t) {
45
+ return t === 'bash' || t === 'shell';
46
+ }
47
+ /** An MCP-style tool name (server-prefixed), as opposed to a native Read/Shell.
48
+ * Used to hand graft MCP calls to the dedicated MCP hook so a host that fires
49
+ * BOTH a generic post-tool hook and an MCP hook can't double-count them. */
50
+ export function isMcpToolName(toolName) {
51
+ const t = toolName.toLowerCase();
52
+ return t.startsWith('mcp') || t.includes(':') || t.includes('__');
53
+ }
54
+ /**
55
+ * Whether a tool name is one of graft's MCP tools, across every host prefix.
56
+ * Anchored on the real name list (`graft_find_code`, its legacy aliases, …), not
57
+ * a loose `includes('graft')`: a third-party MCP tool whose name merely contains
58
+ * "graft" (or a repo working on graft itself) must not count as a graft read —
59
+ * the same care `commandInvokesGraft` takes for the CLI side. Strips the host
60
+ * prefix first (`mcp__graft__graft_find_code`, `MCP:graft_find_code`) by taking
61
+ * the last delimited segment. */
62
+ export function isGraftMcpTool(toolName) {
63
+ const t = toolName.toLowerCase();
64
+ const bare = t.split(/[:./]|__/).pop() ?? t;
65
+ return GRAFT_MCP_TOOL_NAMES.has(bare);
66
+ }
67
+ /** Whether a shell command line invokes the graft CLI (any of its install shapes). */
68
+ export function commandInvokesGraft(command) {
69
+ const c = command.trim();
70
+ if (/dist[/\\]cli\.js/.test(c))
71
+ return true; // running from a source checkout
72
+ // `graft …`, `graft-dev …`, `npx [-y] [@nanonets/]graft …`, at the start of the
73
+ // line or of a &&/;/| segment — not "mygraft" or a path that merely contains it.
74
+ return /(^|[|&;]\s*)(npx\s+(-y\s+)?(@nanonets\/)?)?graft(-dev)?\b/i.test(c);
75
+ }
76
+ /**
77
+ * Classify one tool use as a graft retrieval, a source read, or neither
78
+ * (an edit, a task spawn, an unrelated shell command — no retrieval signal).
79
+ * `command` is the shell command line when `toolName` is the host's shell tool.
80
+ */
81
+ export function classifyToolUse(toolName, command) {
82
+ const t = (toolName ?? '').toLowerCase();
83
+ if (!t)
84
+ return null;
85
+ if (isGraftMcpTool(t))
86
+ return 'graft';
87
+ if (SOURCE_TOOLS.has(t))
88
+ return 'source';
89
+ if (isShellTool(t) && command && commandInvokesGraft(command))
90
+ return 'graft';
91
+ return null;
92
+ }
93
+ /** Sum every `[graft] tokens saved ≈ N` footer in a blob of tool output. Thin
94
+ * alias over {@link sumSavingsFooters}, which lives next to the code that writes
95
+ * the footer so the two can't drift. */
96
+ export function parseSavings(blob) {
97
+ return sumSavingsFooters(blob);
98
+ }
99
+ /**
100
+ * Fold one tool use into a session's counters. A no-op when there is nothing to
101
+ * record (kind null and no savings), so a host can call it unconditionally after
102
+ * every tool without first checking whether the tool was interesting — the
103
+ * no-write path is what keeps it cheap on the Write/Edit/Task majority.
104
+ *
105
+ * Best-effort read-modify-write, not locked — mirroring `patchStats` in
106
+ * util/state.ts. Two tool-use hooks racing on the same session file can lose a
107
+ * count; that is acceptable for an episodic usage estimate (worst case is an
108
+ * undercount by one, never corruption thanks to the atomic write), and a lock
109
+ * here would contend with the build lock these hooks also touch.
110
+ */
111
+ export function recordToolUse(dir, sessionId, use) {
112
+ const saved = use.savedTokens ?? 0;
113
+ if (!use.kind && saved <= 0)
114
+ return;
115
+ const id = sessionId || 'default';
116
+ const s = readSession(dir, id);
117
+ if (use.kind === 'graft')
118
+ s.graftReads = (s.graftReads ?? 0) + 1;
119
+ else if (use.kind === 'source')
120
+ s.sourceReads = (s.sourceReads ?? 0) + 1;
121
+ if (saved > 0)
122
+ s.savedTokens = (s.savedTokens ?? 0) + saved;
123
+ // A graft use owes a tally in this turn's reply; the Stop hook (countTallyTurn)
124
+ // resolves whether it got one and clears the flag. A flag, not a count — a turn
125
+ // with several graft calls is still one reply to the user.
126
+ if (use.kind === 'graft')
127
+ s.turnUsedGraft = true;
128
+ // Stamp the host once; the first tool use that lands owns the attribution.
129
+ if (use.host && !s.host)
130
+ s.host = use.host;
131
+ writeSession(dir, id, s);
132
+ }
133
+ /** The most-recently-touched session file for a repo, or null when none exist —
134
+ * what `graft stats` shows by default: the session you were just in. */
135
+ export function latestSession(dir) {
136
+ const sdir = sessionDir(dir);
137
+ let bestId = null;
138
+ let bestMtime = -Infinity;
139
+ for (const id of listSessionIds(dir)) {
140
+ let mtime;
141
+ try {
142
+ mtime = statSync(join(sdir, `${id}.json`)).mtimeMs;
143
+ }
144
+ catch {
145
+ continue;
146
+ }
147
+ if (mtime > bestMtime) {
148
+ bestMtime = mtime;
149
+ bestId = id;
150
+ }
151
+ }
152
+ if (bestId === null)
153
+ return null;
154
+ return { id: bestId, ...readSession(dir, bestId) };
155
+ }
156
+ /**
157
+ * A one-line-per-fact readout of a session's usage mix — Cursor has no
158
+ * statusline, so this is how you see the numbers the Claude Code bar would show.
159
+ * Reads local JSON only; sends nothing.
160
+ */
161
+ export function formatSessionStats(s) {
162
+ if (s === null) {
163
+ return 'graft stats: no session recorded yet — use graft in an agent session, then look again.';
164
+ }
165
+ const graft = s.graftReads ?? 0;
166
+ const source = s.sourceReads ?? 0;
167
+ const saved = s.savedTokens ?? 0;
168
+ const total = graft + source;
169
+ const mix = total === 0 ? 'no retrieval yet' : `${Math.round((graft / total) * 100)}% graft`;
170
+ const lines = [
171
+ `graft stats — session ${s.id}`,
172
+ ` graft reads: ${graft}`,
173
+ ` source reads: ${source} (Read / Grep / Glob)`,
174
+ ` mix: ${mix}`,
175
+ ` tokens saved: ~${saved.toLocaleString()}`,
176
+ ];
177
+ if (s.lastQuery)
178
+ lines.push(` last query: ${s.lastQuery}`);
179
+ return lines.join('\n');
180
+ }
181
+ //# sourceMappingURL=session-metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-metrics.js","sourceRoot":"","sources":["../../src/claude/session-metrics.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,22 @@
1
1
  type Json = Record<string, any>;
2
- export declare function mergeGraftSettings(existing: Json): {
2
+ /**
3
+ * Is this allowlist entry one graft wrote?
4
+ *
5
+ * Scoped to the forms graft is actually invoked as — NOT any rule mentioning
6
+ * "graft". A user who allowlists their own `Bash(graft-mytool:*)` keeps it; only
7
+ * graft's own set is replaced, which is what lets a renamed entry disappear on
8
+ * upgrade instead of accumulating beside its replacement.
9
+ */
10
+ export declare function isGraftAllowEntry(entry: unknown): boolean;
11
+ /** Is this footer regex graft's? It points at the card tree, which is graft's alone. */
12
+ export declare function isGraftFooterRegex(re: unknown): boolean;
13
+ /** True when init should write (or refresh) Graft's Claude Code statusLine. */
14
+ export declare function statuslineWanted(opts?: {
15
+ statusline?: boolean;
16
+ }): boolean;
17
+ export declare function mergeGraftSettings(existing: Json, opts?: {
18
+ statusline?: boolean;
19
+ }): {
3
20
  merged: Json;
4
21
  warnings: string[];
5
22
  };
@@ -1 +1 @@
1
- {"version":3,"file":"settings-merge.d.ts","sourceRoot":"","sources":["../../src/claude/settings-merge.ts"],"names":[],"mappings":"AAAA,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AA6ChC,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,IAAI,GAAG;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAiCvF"}
1
+ {"version":3,"file":"settings-merge.d.ts","sourceRoot":"","sources":["../../src/claude/settings-merge.ts"],"names":[],"mappings":"AAEA,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AA+ChC;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEzD;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAEvD;AAOD,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAE7E;AA6BD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,IAAI,EACd,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAClC;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAoCtC"}
@@ -1,4 +1,8 @@
1
+ import { isGraftEntry } from '../hosts/config-write.js';
1
2
  const SL_CMD = 'node "${CLAUDE_PROJECT_DIR:-.}/.claude/helpers/graft-statusline.cjs"';
3
+ /** Stable marker for "this statusLine is Graft's", not the full command string —
4
+ * an older shim path or a GRAFT_DIR wrapper still names this file. */
5
+ const GRAFT_STATUSLINE_HELPER = 'graft-statusline.cjs';
2
6
  const FOOTER = 'graft/[\\w./-]+\\.md';
3
7
  // Every form graft is actually invoked as. 'graft:*' covers a global install;
4
8
  // the other two cover a repo working on graft itself (or any consumer running it
@@ -17,11 +21,14 @@ function graftBlocks() {
17
21
  return {
18
22
  PostToolUse: [
19
23
  { matcher: 'Write|Edit|MultiEdit', hooks: [{ type: 'command', command: hookCmd('post-edit'), timeout: 10000 }] },
20
- // A retrieval tool (CLI `graft …` via Bash, or the `graft_*` MCP tools) prints a
21
- // `[graft] tokens saved ≈ N` footer; this hook sums it into the session total the
22
- // statusline shows. Broad matcher, but the handler no-ops instantly unless a footer
23
- // is actually present, so non-graft Bash calls cost only a stdin read.
24
- { matcher: 'Bash|mcp__graft__', hooks: [{ type: 'command', command: hookCmd('tool-savings'), timeout: 8000 }] },
24
+ // Score the usage mix and sum token savings. A graft retrieval (CLI `graft …`
25
+ // via Bash, or the `graft_*` MCP tools) prints a `[graft] tokens saved ≈ N`
26
+ // footer this hook sums into the session total; the same hook classifies
27
+ // Read/Grep/Glob as source reads vs graft as graft reads, which is what feeds
28
+ // `graft stats` and the `session_summary` graft-vs-grep ratio. Broad matcher,
29
+ // but the handler no-ops instantly unless there is something to record, so an
30
+ // unrelated Bash or a plain Read costs only a stdin read.
31
+ { matcher: 'Bash|mcp__graft__|Read|Grep|Glob', hooks: [{ type: 'command', command: hookCmd('tool-savings'), timeout: 8000 }] },
25
32
  ],
26
33
  // Longer budget than the other hooks: its `graft ask` is a real query, and a
27
34
  // query now brings the graph up to date first (graph/refresh.ts) — usually
@@ -36,39 +43,75 @@ function graftBlocks() {
36
43
  Stop: [{ hooks: [{ type: 'command', command: hookCmd('stop'), timeout: 8000 }] }],
37
44
  };
38
45
  }
39
- function isGraftHookEntry(entry) {
40
- return JSON.stringify(entry ?? '').includes('graft-hooks.cjs');
46
+ /**
47
+ * Is this allowlist entry one graft wrote?
48
+ *
49
+ * Scoped to the forms graft is actually invoked as — NOT any rule mentioning
50
+ * "graft". A user who allowlists their own `Bash(graft-mytool:*)` keeps it; only
51
+ * graft's own set is replaced, which is what lets a renamed entry disappear on
52
+ * upgrade instead of accumulating beside its replacement.
53
+ */
54
+ export function isGraftAllowEntry(entry) {
55
+ return /^Bash\((?:graft|npx graft|graft-dev|node dist\/cli\.js)(?::|\))/.test(String(entry));
41
56
  }
42
- export function mergeGraftSettings(existing) {
57
+ /** Is this footer regex graft's? It points at the card tree, which is graft's alone. */
58
+ export function isGraftFooterRegex(re) {
59
+ return String(re).includes('graft/');
60
+ }
61
+ function envNoStatusline() {
62
+ const v = process.env.GRAFT_NO_STATUSLINE;
63
+ return v !== undefined && v !== '' && v !== '0' && v !== 'false';
64
+ }
65
+ /** True when init should write (or refresh) Graft's Claude Code statusLine. */
66
+ export function statuslineWanted(opts = {}) {
67
+ return opts.statusline !== false && !envNoStatusline();
68
+ }
69
+ function isGraftStatusline(value) {
70
+ if (!value || typeof value !== 'object')
71
+ return false;
72
+ const command = value.command;
73
+ return typeof command === 'string' && command.includes(GRAFT_STATUSLINE_HELPER);
74
+ }
75
+ function applyStatusline(merged, key, warnings, wanted, foreignWarning) {
76
+ const current = merged[key];
77
+ const ours = isGraftStatusline(current);
78
+ if (!wanted) {
79
+ if (!current || ours)
80
+ delete merged[key];
81
+ else
82
+ warnings.push(foreignWarning);
83
+ return;
84
+ }
85
+ if (!current || ours) {
86
+ merged[key] = { type: 'command', command: SL_CMD };
87
+ return;
88
+ }
89
+ warnings.push(foreignWarning);
90
+ }
91
+ export function mergeGraftSettings(existing, opts = {}) {
43
92
  const merged = { ...(existing ?? {}) };
44
93
  const warnings = [];
45
- if (!merged.statusLine)
46
- merged.statusLine = { type: 'command', command: SL_CMD };
47
- else if (merged.statusLine.command !== SL_CMD)
48
- warnings.push('Existing statusLine left untouched (a session allows only one). To use Graft, point it at .claude/helpers/graft-statusline.cjs.');
49
- if (!merged.subagentStatusLine)
50
- merged.subagentStatusLine = { type: 'command', command: SL_CMD };
51
- else if (merged.subagentStatusLine.command !== SL_CMD)
52
- warnings.push('Existing subagentStatusLine left untouched.');
94
+ const wanted = statuslineWanted(opts);
95
+ applyStatusline(merged, 'statusLine', warnings, wanted, 'Existing statusLine left untouched (a session allows only one). To use Graft, point it at .claude/helpers/graft-statusline.cjs.');
96
+ applyStatusline(merged, 'subagentStatusLine', warnings, wanted, 'Existing subagentStatusLine left untouched.');
53
97
  merged.hooks = { ...(merged.hooks ?? {}) };
54
98
  for (const [event, blocks] of Object.entries(graftBlocks())) {
55
99
  const prior = Array.isArray(merged.hooks[event]) ? merged.hooks[event] : [];
56
- const foreign = prior.filter((e) => !isGraftHookEntry(e)); // drop old Graft entries → idempotent
100
+ const foreign = prior.filter((e) => !isGraftEntry(e)); // drop old Graft entries → idempotent
57
101
  merged.hooks[event] = [...foreign, ...blocks];
58
102
  }
59
- const footer = Array.isArray(merged.footerLinksRegexes) ? [...merged.footerLinksRegexes] : [];
60
- if (!footer.includes(FOOTER))
61
- footer.push(FOOTER);
62
- merged.footerLinksRegexes = footer;
103
+ // Drop graft's own prior regex before re-adding, so a change to FOOTER replaces
104
+ // the old pattern instead of stacking beside it. The user's regexes are kept.
105
+ const priorFooter = Array.isArray(merged.footerLinksRegexes) ? merged.footerLinksRegexes : [];
106
+ merged.footerLinksRegexes = [...priorFooter.filter((r) => !isGraftFooterRegex(r)), FOOTER];
63
107
  // headless/subagent runs hard-deny Bash by default; without an allowlist entry
64
108
  // `graft ask`'s own Bash calls (and the skill it installs) can't run out-of-box.
109
+ // Same shape as the hooks merge above: drop graft's prior entries, then add the
110
+ // current set. Append-only left a renamed invocation form in the user's settings
111
+ // forever, with nothing able to remove it.
65
112
  merged.permissions = { ...(merged.permissions ?? {}) };
66
- const allow = Array.isArray(merged.permissions.allow) ? [...merged.permissions.allow] : [];
67
- for (const entry of ALLOW_ENTRIES) {
68
- if (!allow.includes(entry))
69
- allow.push(entry);
70
- }
71
- merged.permissions.allow = allow;
113
+ const priorAllow = Array.isArray(merged.permissions.allow) ? merged.permissions.allow : [];
114
+ merged.permissions.allow = [...priorAllow.filter((e) => !isGraftAllowEntry(e)), ...ALLOW_ENTRIES];
72
115
  return { merged, warnings };
73
116
  }
74
117
  //# sourceMappingURL=settings-merge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings-merge.js","sourceRoot":"","sources":["../../src/claude/settings-merge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings-merge.js","sourceRoot":"","sources":["../../src/claude/settings-merge.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { AgentHost } from '../telemetry/contract.js';
1
2
  export { LOCK_STALE_MS, acquireLock, acquireLockIn, releaseLockIn, cacheDir, emptyStats, patchStats, readStats, releaseLock, resolveContextDir, writeJsonAtomic, writeStats, } from '../util/state.js';
2
3
  export type { Stats } from '../util/state.js';
3
4
  export interface SessionState {
@@ -14,12 +15,36 @@ export interface SessionState {
14
15
  /** Weak-match nudges spent this session, capped so the line stays signal.
15
16
  * Optional for the same backwards-compatibility reason as above. */
16
17
  nudges?: number;
18
+ /** Turns this session in which the agent used a graft retrieval tool — the
19
+ * denominator for "did it tell the user what that saved". Counted at Stop,
20
+ * and only for turns whose reply we could actually read (see claude/tally.ts). */
21
+ graftTurns?: number;
22
+ /** Of those turns, the ones whose reply carried a "graft saved ~N tokens"
23
+ * tally. `graftTurns - reportedTurns` is silent value: saved, never said. */
24
+ reportedTurns?: number;
25
+ /** Set by the tool-savings hook when the current turn touched graft, cleared
26
+ * at Stop once the turn has been counted. Transient, not a total. */
27
+ turnUsedGraft?: boolean;
28
+ /** `uuid` of the last assistant reply already examined, so a Stop that fires
29
+ * without new prose (or fires twice) can't count one reply as two turns. */
30
+ lastTallyUuid?: string;
17
31
  /** Set once this session has been rolled up into a `session_summary`
18
32
  * telemetry event, so a resumed or long-lived session is counted once.
19
33
  * A flag rather than deleting the file: the file still holds `lastQuery` and
20
34
  * `injectedPointers`, which a resumed session needs. */
21
35
  summarized?: boolean;
36
+ /** Which host recorded this session, stamped on the first tool use. Lets the
37
+ * `session_summary` be attributed correctly even when Claude Code's idle sweep
38
+ * is what finally flushes a Cursor session. Optional: files written before
39
+ * host-stamping (or an empty session never touched by a tool) fall back to the
40
+ * flushing host. */
41
+ host?: AgentHost;
22
42
  }
43
+ /** The per-repo session directory holding one `<id>.json` per agent session. */
44
+ export declare function sessionDir(d: string): string;
45
+ /** Every session id with a file on disk, or `[]` when none exist (never throws).
46
+ * Shared by the telemetry rollup and `graft stats` so they agree on the set. */
47
+ export declare function listSessionIds(d: string): string[];
23
48
  export declare function readSession(d: string, id: string): SessionState;
24
49
  export declare function writeSession(d: string, id: string, s: SessionState): void;
25
50
  //# sourceMappingURL=state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/claude/state.ts"],"names":[],"mappings":"AASA,OAAO,EACL,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,QAAQ,EACR,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IACxC,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB;;8DAE0D;IAC1D,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;wEACoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;4DAGwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAQD,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,YAAY,CAE/D;AACD,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,GAAG,IAAI,CAEzE"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/claude/state.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EACL,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,QAAQ,EACR,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IACxC,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB;;8DAE0D;IAC1D,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;wEACoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;sFAEkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;iFAC6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;yEACqE;IACrE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;gFAC4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;4DAGwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;wBAIoB;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAMD,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAyC;AAItF;iFACiF;AACjF,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAIlD;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,YAAY,CAE/D;AACD,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,GAAG,IAAI,CAEzE"}
@@ -4,13 +4,26 @@
4
4
  * pre-query auto-refresh can take the same lock; they are re-exported here so
5
5
  * every existing import path keeps working.
6
6
  */
7
+ import { readdirSync } from 'node:fs';
7
8
  import { join } from 'node:path';
8
9
  import { cacheDir, readJson, writeJsonAtomic } from '../util/state.js';
9
10
  export { LOCK_STALE_MS, acquireLock, acquireLockIn, releaseLockIn, cacheDir, emptyStats, patchStats, readStats, releaseLock, resolveContextDir, writeJsonAtomic, writeStats, } from '../util/state.js';
10
11
  function emptySession() {
11
12
  return { lastQuery: null, perAgentQuery: {}, graftReads: 0, sourceReads: 0, savedTokens: 0, injectedPointers: [], nudges: 0 };
12
13
  }
13
- function sessionPath(d, id) { return join(cacheDir(d), 'session', `${id}.json`); }
14
+ /** The per-repo session directory holding one `<id>.json` per agent session. */
15
+ export function sessionDir(d) { return join(cacheDir(d), 'session'); }
16
+ function sessionPath(d, id) { return join(sessionDir(d), `${id}.json`); }
17
+ /** Every session id with a file on disk, or `[]` when none exist (never throws).
18
+ * Shared by the telemetry rollup and `graft stats` so they agree on the set. */
19
+ export function listSessionIds(d) {
20
+ try {
21
+ return readdirSync(sessionDir(d)).filter((f) => f.endsWith('.json')).map((f) => f.slice(0, -'.json'.length));
22
+ }
23
+ catch {
24
+ return [];
25
+ }
26
+ }
14
27
  export function readSession(d, id) {
15
28
  return readJson(sessionPath(d, id)) ?? emptySession();
16
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/claude/state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EACL,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,QAAQ,EACR,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,kBAAkB,CAAC;AAuB1B,SAAS,YAAY;IACnB,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AAChI,CAAC;AAED,SAAS,WAAW,CAAC,CAAS,EAAE,EAAU,IAAY,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAE1G,MAAM,UAAU,WAAW,CAAC,CAAS,EAAE,EAAU;IAC/C,OAAO,QAAQ,CAAe,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,YAAY,EAAE,CAAC;AACtE,CAAC;AACD,MAAM,UAAU,YAAY,CAAC,CAAS,EAAE,EAAU,EAAE,CAAe;IACjE,eAAe,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/claude/state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGvE,OAAO,EACL,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,QAAQ,EACR,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,kBAAkB,CAAC;AA0C1B,SAAS,YAAY;IACnB,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AAChI,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,UAAU,CAAC,CAAS,IAAY,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;AAEtF,SAAS,WAAW,CAAC,CAAS,EAAE,EAAU,IAAY,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAEjG;iFACiF;AACjF,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/G,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,CAAS,EAAE,EAAU;IAC/C,OAAO,QAAQ,CAAe,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,YAAY,EAAE,CAAC;AACtE,CAAC;AACD,MAAM,UAAU,YAAY,CAAC,CAAS,EAAE,EAAU,EAAE,CAAe;IACjE,eAAe,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC"}
@@ -3,9 +3,11 @@ import { type Stats } from './state.js';
3
3
  * The statusline's fast path is the hook-maintained cache (graft/.cache/stats.json).
4
4
  * When it's absent — a fresh checkout, or a plain `graft build` that doesn't write the
5
5
  * cache — fall back to reading the graph itself (wiring.json) so the bar reflects reality
6
- * immediately instead of showing "not built". The graph carries no drift signal, so it
7
- * reads as synced until the next edit repopulates the cache. Still a pure read (no
8
- * subprocess): the cache is preferred because it carries live dirty/stale state.
6
+ * immediately instead of showing "not built". An empty wiring.json is still a graph
7
+ * (docs-only repos legitimately have 0 nodes); "not built" is only when the artifact
8
+ * is missing. The graph carries no drift signal, so it reads as synced until the next
9
+ * edit repopulates the cache. Still a pure read (no subprocess): the cache is preferred
10
+ * because it carries live dirty/stale state.
9
11
  */
10
12
  export declare function resolveStats(dir: string): Stats | null;
11
13
  export declare function main(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"statusline.d.ts","sourceRoot":"","sources":["../../src/claude/statusline.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsC,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAG5E;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAMtD;AAED,wBAAgB,IAAI,IAAI,IAAI,CAW3B"}
1
+ {"version":3,"file":"statusline.d.ts","sourceRoot":"","sources":["../../src/claude/statusline.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsC,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAG5E;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAMtD;AAED,wBAAgB,IAAI,IAAI,IAAI,CAW3B"}
@@ -6,9 +6,11 @@ import { readWiring, computeStats } from './stats.js';
6
6
  * The statusline's fast path is the hook-maintained cache (graft/.cache/stats.json).
7
7
  * When it's absent — a fresh checkout, or a plain `graft build` that doesn't write the
8
8
  * cache — fall back to reading the graph itself (wiring.json) so the bar reflects reality
9
- * immediately instead of showing "not built". The graph carries no drift signal, so it
10
- * reads as synced until the next edit repopulates the cache. Still a pure read (no
11
- * subprocess): the cache is preferred because it carries live dirty/stale state.
9
+ * immediately instead of showing "not built". An empty wiring.json is still a graph
10
+ * (docs-only repos legitimately have 0 nodes); "not built" is only when the artifact
11
+ * is missing. The graph carries no drift signal, so it reads as synced until the next
12
+ * edit repopulates the cache. Still a pure read (no subprocess): the cache is preferred
13
+ * because it carries live dirty/stale state.
12
14
  */
13
15
  export function resolveStats(dir) {
14
16
  const cached = readStats(dir);
@@ -17,7 +19,7 @@ export function resolveStats(dir) {
17
19
  const wiring = readWiring(dir);
18
20
  if (wiring)
19
21
  return { ...emptyStats(), ...computeStats(wiring) };
20
- return cached;
22
+ return null;
21
23
  }
22
24
  export function main() {
23
25
  let input = {};
@@ -1 +1 @@
1
- {"version":3,"file":"statusline.js","sourceRoot":"","sources":["../../src/claude/statusline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAc,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAClD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,MAAM;QAAE,OAAO,EAAE,GAAG,UAAU,EAAE,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;IAChE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,IAAI;IAClB,IAAI,KAAK,GAAQ,EAAE,CAAC;IACpB,IAAI,CAAC;QAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC;IACrF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzE,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,UAAU,IAAI,SAAS,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC;IACjC,IAAI,KAAK,EAAE,CAAC;QAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IAC5E,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,KAAK,EAAE,cAAc,EAAE,eAAe,CAAC;IACnD,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAChF,CAAC"}
1
+ {"version":3,"file":"statusline.js","sourceRoot":"","sources":["../../src/claude/statusline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAc,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAClD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,MAAM;QAAE,OAAO,EAAE,GAAG,UAAU,EAAE,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,IAAI;IAClB,IAAI,KAAK,GAAQ,EAAE,CAAC;IACpB,IAAI,CAAC;QAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC;IACrF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzE,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,UAAU,IAAI,SAAS,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC;IACjC,IAAI,KAAK,EAAE,CAAC;QAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IAC5E,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,KAAK,EAAE,cAAc,EAAE,eAAe,CAAC;IACnD,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAChF,CAAC"}
@@ -0,0 +1,23 @@
1
+ /** Did this reply tell the user what graft saved? */
2
+ export declare function hasSavingsTally(text: string): boolean;
3
+ /** One turn's worth of assistant prose, plus the id of the entry it ended on. */
4
+ export interface AssistantTurn {
5
+ /** `uuid` of the last assistant entry, so the same reply is never counted twice. */
6
+ uuid: string;
7
+ /** Every text block the agent emitted since the last user prompt, joined. */
8
+ text: string;
9
+ }
10
+ /**
11
+ * The assistant prose of the turn the transcript ends on, or null when it can't
12
+ * be read (no path — a host whose Stop hook doesn't name one — unreadable file,
13
+ * or a tail with no assistant text in it).
14
+ *
15
+ * Null is meaningfully different from "no tally found": a turn we cannot observe
16
+ * is left out of BOTH the numerator and the denominator by the caller, so a host
17
+ * that exposes no transcript reports no ratio rather than a ratio of zero.
18
+ *
19
+ * Sidechain entries (subagents) are skipped throughout — a subagent's own prose
20
+ * is never what the user read.
21
+ */
22
+ export declare function lastAssistantTurn(transcriptPath: unknown): AssistantTurn | null;
23
+ //# sourceMappingURL=tally.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tally.d.ts","sourceRoot":"","sources":["../../src/claude/tally.ts"],"names":[],"mappings":"AA0CA,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;CACd;AAuCD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CA0B/E"}