@nanonets/graft 0.15.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 (157) hide show
  1. package/README.md +10 -5
  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 +31 -1
  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 +6 -1
  11. package/dist/ai/llm/factory.d.ts.map +1 -1
  12. package/dist/ai/llm/factory.js +8 -0
  13. package/dist/ai/llm/factory.js.map +1 -1
  14. package/dist/ai/llm/orcarouter.d.ts +34 -0
  15. package/dist/ai/llm/orcarouter.d.ts.map +1 -0
  16. package/dist/ai/llm/orcarouter.js +45 -0
  17. package/dist/ai/llm/orcarouter.js.map +1 -0
  18. package/dist/ai/providers.d.ts.map +1 -1
  19. package/dist/ai/providers.js +13 -3
  20. package/dist/ai/providers.js.map +1 -1
  21. package/dist/app/review.d.ts.map +1 -1
  22. package/dist/app/review.js +10 -0
  23. package/dist/app/review.js.map +1 -1
  24. package/dist/blast/blast-cli.d.ts.map +1 -1
  25. package/dist/blast/blast-cli.js +6 -30
  26. package/dist/blast/blast-cli.js.map +1 -1
  27. package/dist/blast/name.d.ts +13 -0
  28. package/dist/blast/name.d.ts.map +1 -1
  29. package/dist/blast/name.js +36 -0
  30. package/dist/blast/name.js.map +1 -1
  31. package/dist/claude/hooks.d.ts.map +1 -1
  32. package/dist/claude/hooks.js +93 -23
  33. package/dist/claude/hooks.js.map +1 -1
  34. package/dist/claude/init.d.ts +1 -0
  35. package/dist/claude/init.d.ts.map +1 -1
  36. package/dist/claude/init.js +1 -1
  37. package/dist/claude/init.js.map +1 -1
  38. package/dist/claude/session-metrics.d.ts +63 -0
  39. package/dist/claude/session-metrics.d.ts.map +1 -0
  40. package/dist/claude/session-metrics.js +181 -0
  41. package/dist/claude/session-metrics.js.map +1 -0
  42. package/dist/claude/settings-merge.d.ts +7 -1
  43. package/dist/claude/settings-merge.d.ts.map +1 -1
  44. package/dist/claude/settings-merge.js +47 -18
  45. package/dist/claude/settings-merge.js.map +1 -1
  46. package/dist/claude/state.d.ts +12 -0
  47. package/dist/claude/state.d.ts.map +1 -1
  48. package/dist/claude/state.js +14 -1
  49. package/dist/claude/state.js.map +1 -1
  50. package/dist/cli.js +29 -3
  51. package/dist/cli.js.map +1 -1
  52. package/dist/context/build.d.ts +11 -0
  53. package/dist/context/build.d.ts.map +1 -1
  54. package/dist/context/build.js +27 -6
  55. package/dist/context/build.js.map +1 -1
  56. package/dist/context/check.d.ts.map +1 -1
  57. package/dist/context/check.js +5 -13
  58. package/dist/context/check.js.map +1 -1
  59. package/dist/context/node-file.d.ts +1 -1
  60. package/dist/context/node-file.d.ts.map +1 -1
  61. package/dist/context/node-file.js +38 -6
  62. package/dist/context/node-file.js.map +1 -1
  63. package/dist/context/savings.d.ts +8 -0
  64. package/dist/context/savings.d.ts.map +1 -1
  65. package/dist/context/savings.js +14 -0
  66. package/dist/context/savings.js.map +1 -1
  67. package/dist/engine.d.ts +2 -0
  68. package/dist/engine.d.ts.map +1 -1
  69. package/dist/engine.js +1 -0
  70. package/dist/engine.js.map +1 -1
  71. package/dist/graph/cards.d.ts +4 -3
  72. package/dist/graph/cards.d.ts.map +1 -1
  73. package/dist/graph/cards.js +34 -6
  74. package/dist/graph/cards.js.map +1 -1
  75. package/dist/graph/enrich.d.ts +4 -4
  76. package/dist/graph/enrich.d.ts.map +1 -1
  77. package/dist/graph/enrich.js +32 -6
  78. package/dist/graph/enrich.js.map +1 -1
  79. package/dist/graph/generic.d.ts +10 -0
  80. package/dist/graph/generic.d.ts.map +1 -1
  81. package/dist/graph/generic.js +20 -2
  82. package/dist/graph/generic.js.map +1 -1
  83. package/dist/graph/workspace.d.ts +2 -0
  84. package/dist/graph/workspace.d.ts.map +1 -1
  85. package/dist/graph/workspace.js +173 -6
  86. package/dist/graph/workspace.js.map +1 -1
  87. package/dist/hosts/antigravity.d.ts +2 -2
  88. package/dist/hosts/antigravity.d.ts.map +1 -1
  89. package/dist/hosts/codex-hooks.d.ts +2 -6
  90. package/dist/hosts/codex-hooks.d.ts.map +1 -1
  91. package/dist/hosts/codex-hooks.js +7 -30
  92. package/dist/hosts/codex-hooks.js.map +1 -1
  93. package/dist/hosts/config-write.d.ts +36 -0
  94. package/dist/hosts/config-write.d.ts.map +1 -0
  95. package/dist/hosts/config-write.js +63 -0
  96. package/dist/hosts/config-write.js.map +1 -0
  97. package/dist/hosts/cursor-hooks.d.ts +18 -0
  98. package/dist/hosts/cursor-hooks.d.ts.map +1 -0
  99. package/dist/hosts/cursor-hooks.js +100 -0
  100. package/dist/hosts/cursor-hooks.js.map +1 -0
  101. package/dist/hosts/init.d.ts +2 -2
  102. package/dist/hosts/init.d.ts.map +1 -1
  103. package/dist/hosts/init.js +8 -2
  104. package/dist/hosts/init.js.map +1 -1
  105. package/dist/hosts/mcp-config.d.ts +3 -5
  106. package/dist/hosts/mcp-config.d.ts.map +1 -1
  107. package/dist/hosts/mcp-config.js +5 -10
  108. package/dist/hosts/mcp-config.js.map +1 -1
  109. package/dist/hosts/plan.d.ts.map +1 -1
  110. package/dist/hosts/plan.js +2 -0
  111. package/dist/hosts/plan.js.map +1 -1
  112. package/dist/index.d.ts +2 -0
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +2 -0
  115. package/dist/index.js.map +1 -1
  116. package/dist/ingest/fs.d.ts +20 -0
  117. package/dist/ingest/fs.d.ts.map +1 -1
  118. package/dist/ingest/fs.js +76 -3
  119. package/dist/ingest/fs.js.map +1 -1
  120. package/dist/mcp/tool-names.d.ts +20 -0
  121. package/dist/mcp/tool-names.d.ts.map +1 -0
  122. package/dist/mcp/tool-names.js +36 -0
  123. package/dist/mcp/tool-names.js.map +1 -0
  124. package/dist/mcp/tools.d.ts +11 -2
  125. package/dist/mcp/tools.d.ts.map +1 -1
  126. package/dist/mcp/tools.js +9 -21
  127. package/dist/mcp/tools.js.map +1 -1
  128. package/dist/telemetry/contract.d.ts.map +1 -1
  129. package/dist/telemetry/contract.js +10 -1
  130. package/dist/telemetry/contract.js.map +1 -1
  131. package/dist/telemetry/flush.d.ts.map +1 -1
  132. package/dist/telemetry/flush.js +1 -0
  133. package/dist/telemetry/flush.js.map +1 -1
  134. package/dist/telemetry/identity.d.ts +7 -0
  135. package/dist/telemetry/identity.d.ts.map +1 -1
  136. package/dist/telemetry/identity.js.map +1 -1
  137. package/dist/telemetry/index.d.ts +2 -2
  138. package/dist/telemetry/index.d.ts.map +1 -1
  139. package/dist/telemetry/index.js +2 -2
  140. package/dist/telemetry/index.js.map +1 -1
  141. package/dist/telemetry/sessions.d.ts +19 -2
  142. package/dist/telemetry/sessions.d.ts.map +1 -1
  143. package/dist/telemetry/sessions.js +57 -40
  144. package/dist/telemetry/sessions.js.map +1 -1
  145. package/dist/telemetry/track.d.ts +22 -3
  146. package/dist/telemetry/track.d.ts.map +1 -1
  147. package/dist/telemetry/track.js +34 -5
  148. package/dist/telemetry/track.js.map +1 -1
  149. package/dist/upkeep-run.js +1 -1
  150. package/dist/upkeep-run.js.map +1 -1
  151. package/dist/upkeep.d.ts +7 -4
  152. package/dist/upkeep.d.ts.map +1 -1
  153. package/dist/upkeep.js +2 -1
  154. package/dist/upkeep.js.map +1 -1
  155. package/package.json +2 -2
  156. package/scripts/postinstall.mjs +42 -3
  157. package/scripts/run-tests.mjs +14 -0
@@ -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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"}
@@ -10,7 +10,13 @@ type Json = Record<string, any>;
10
10
  export declare function isGraftAllowEntry(entry: unknown): boolean;
11
11
  /** Is this footer regex graft's? It points at the card tree, which is graft's alone. */
12
12
  export declare function isGraftFooterRegex(re: unknown): boolean;
13
- export declare function mergeGraftSettings(existing: Json): {
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
+ }): {
14
20
  merged: Json;
15
21
  warnings: string[];
16
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;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEzD;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAEvD;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,IAAI,GAAG;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAkCvF"}
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,9 +43,6 @@ 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');
41
- }
42
46
  /**
43
47
  * Is this allowlist entry one graft wrote?
44
48
  *
@@ -54,21 +58,46 @@ export function isGraftAllowEntry(entry) {
54
58
  export function isGraftFooterRegex(re) {
55
59
  return String(re).includes('graft/');
56
60
  }
57
- export function mergeGraftSettings(existing) {
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 = {}) {
58
92
  const merged = { ...(existing ?? {}) };
59
93
  const warnings = [];
60
- if (!merged.statusLine)
61
- merged.statusLine = { type: 'command', command: SL_CMD };
62
- else if (merged.statusLine.command !== SL_CMD)
63
- warnings.push('Existing statusLine left untouched (a session allows only one). To use Graft, point it at .claude/helpers/graft-statusline.cjs.');
64
- if (!merged.subagentStatusLine)
65
- merged.subagentStatusLine = { type: 'command', command: SL_CMD };
66
- else if (merged.subagentStatusLine.command !== SL_CMD)
67
- 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.');
68
97
  merged.hooks = { ...(merged.hooks ?? {}) };
69
98
  for (const [event, blocks] of Object.entries(graftBlocks())) {
70
99
  const prior = Array.isArray(merged.hooks[event]) ? merged.hooks[event] : [];
71
- 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
72
101
  merged.hooks[event] = [...foreign, ...blocks];
73
102
  }
74
103
  // Drop graft's own prior regex before re-adding, so a change to FOOTER replaces
@@ -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 {
@@ -32,7 +33,18 @@ export interface SessionState {
32
33
  * A flag rather than deleting the file: the file still holds `lastQuery` and
33
34
  * `injectedPointers`, which a resumed session needs. */
34
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;
35
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[];
36
48
  export declare function readSession(d: string, id: string): SessionState;
37
49
  export declare function writeSession(d: string, id: string, s: SessionState): void;
38
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;;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;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;AAoC1B,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"}
package/dist/cli.js CHANGED
@@ -13,6 +13,7 @@ import { resolveConfig } from "./ai/providers.js";
13
13
  import { formatCheckReport } from "./context/check.js";
14
14
  import { formatGraphCheckReport } from "./graph/check.js";
15
15
  import { buildGraphIfMissing, runInit } from "./claude/init.js";
16
+ import { statuslineWanted } from "./claude/settings-merge.js";
16
17
  import { runHostsInit } from "./hosts/init.js";
17
18
  import { hostIds } from "./hosts/registry.js";
18
19
  import { contextDirFor } from "./context/node-file.js";
@@ -31,6 +32,7 @@ import { homedir } from "node:os";
31
32
  import { formatUpgradeReport, formatVersionReport, getNpmViewVersion, readCurrentVersion, runUpgrade } from "./cli-meta.js";
32
33
  import { patchBuildConfig } from "./util/state.js";
33
34
  import { normalizePathPrefix } from "./util/paths.js";
35
+ import { latestSession, formatSessionStats } from "./claude/session-metrics.js";
34
36
  import { formatUpdateNudge, maybeRefreshInBackground, readUpdateCache, refreshUpdateCache, writeStamp } from "./upkeep.js";
35
37
  import { errorCode, filesBucket, durationBucket, formatDebug, formatStatus, firstRunNotice, isTrackedCommand, langsValue, offReason, maybeFlushInBackground, patchState, runFlush, track, trackFirstRunIfNew, } from "./telemetry/index.js";
36
38
  const program = new Command();
@@ -61,7 +63,7 @@ program
61
63
  .description("Build a repo's context graph as linked markdown, and keep it in sync with the code.")
62
64
  .version(currentVersion, "-v, --version")
63
65
  .option("--dir <path>", "context graph directory (default: <repo>/graft)")
64
- .option("--provider <name>", "LLM wire format: openai | anthropic (env GRAFT_PROVIDER)")
66
+ .option("--provider <name>", "LLM wire format: openai | anthropic | litellm | orcarouter (env GRAFT_PROVIDER)")
65
67
  .option("--model <id>", "model id for the LLM pass (env GRAFT_MODEL)")
66
68
  .option("--api-key <key>", "provider API key (env GRAFT_API_KEY)")
67
69
  .option("--base-url <url>", "OpenAI-compatible endpoint URL (env GRAFT_BASE_URL)");
@@ -266,7 +268,8 @@ program
266
268
  .option("--no-follow-nested-repos", "exclude untracked nested Git clones; persisted for later builds and automatic refreshes (default)")
267
269
  .option("--include-dir <name>", "override SKIP_DIRS for this repo's walks — repeatable (e.g. --include-dir build --include-dir tools); " +
268
270
  "persisted, so a later build (and the hooks/refresh path) include it without the flag; dot-dirs are never overridable", (val, prev) => [...prev, val], [])
269
- .option("--only-dir <path>", "only index files under this repo-relative path — repeatable; persisted, so a later build " +
271
+ .option("--only-dir <path>", "only index files under this repo-relative path — repeatable; the wiring walk and the --deep " +
272
+ "concept pass both honor it. Recorded in the graph fingerprint so a later build " +
270
273
  "(and the hooks/refresh path) walks the same set; everything outside the list is skipped", (val, prev) => [...prev, val], [])
271
274
  .option("--no-gitignore", "skip writing graft/ into .gitignore (same as GRAFT_NO_GITIGNORE=1)")
272
275
  .option("--no-ignore", "skip writing .ignore for ripgrep re-admit (same as GRAFT_NO_IGNORE=1)")
@@ -371,6 +374,7 @@ program
371
374
  if (deep) {
372
375
  const c = await engine.init(dir, {
373
376
  extensions: opts.extensions,
377
+ onlyDirs,
374
378
  onProgress: ({ phase, index, total, file }) => process.stderr.write(`\r${phase === "summarize" ? "reading" : "writing"} concepts ${index + 1}/${total}: ${file.slice(0, 40).padEnd(40)}`),
375
379
  }).catch((err) => {
376
380
  // Only the stage and a code enum; the message stays on this machine.
@@ -557,6 +561,23 @@ program
557
561
  if (bothMissing || markdownFail || wiringFail)
558
562
  process.exit(1);
559
563
  });
564
+ program
565
+ .command("stats")
566
+ .description("Show this agent session's graft-vs-source usage mix and tokens saved")
567
+ .argument(...DIR_ARG)
568
+ .option("--json", "output the session stats as JSON")
569
+ .action((dirArg, opts) => {
570
+ // Reads local session JSON only — no graph, no network. This is how a Cursor
571
+ // user (no statusline) sees the numbers the Claude Code bar would show, and it
572
+ // works under DO_NOT_TRACK because it never touches telemetry.
573
+ const dir = queryRoot(dirArg);
574
+ const s = latestSession(dir);
575
+ if (opts.json) {
576
+ console.log(JSON.stringify(s, null, 2));
577
+ return;
578
+ }
579
+ console.log(formatSessionStats(s));
580
+ });
560
581
  program
561
582
  .command("viz")
562
583
  .description("Serve an interactive visualization of the context graph (and graph.json when present)")
@@ -766,6 +787,7 @@ program
766
787
  .option("--list-agents", "list known agent ids and exit")
767
788
  .option("--no-mcp", "skip MCP server registration for other agents")
768
789
  .option("--no-hooks", "skip hook installation for other agents")
790
+ .option("--no-statusline", "skip writing Claude Code statusLine (keep a user-defined one)")
769
791
  .option("--dry-run", "print every file init would touch, then exit without writing")
770
792
  .option("-y, --yes", "skip the picker and wire every detected agent (the pre-0.8 default)")
771
793
  .option("--no-global", "skip writes outside this repo (the ~/.codex/ config + hooks)")
@@ -875,6 +897,7 @@ program
875
897
  /** One repo's worth of `init` writes — the parent, then each workspace child. */
876
898
  function wireTarget(repo, ids, ctx) {
877
899
  const { home, cliPath, plan, wantClaude, opts } = ctx;
900
+ const wantStatusline = statuslineWanted({ statusline: opts.statusline });
878
901
  // Converge, don't just add. init writes the selected hosts; without this it
879
902
  // never touches the rest, so a repo wired by an older version (or by the same
880
903
  // version with different --agents) keeps that run's files forever — and
@@ -885,7 +908,7 @@ function wireTarget(repo, ids, ctx) {
885
908
  for (const r of retracted)
886
909
  console.error(`- removed ${r.path} (${r.what}) — agent not selected`);
887
910
  if (wantClaude) {
888
- const res = runInit(repo, { build: opts.build, cliPath });
911
+ const res = runInit(repo, { build: opts.build, cliPath, statusline: wantStatusline });
889
912
  console.error(`✓ wrote ${res.settingsPath}`);
890
913
  for (const s of res.shims)
891
914
  console.error(`✓ wrote ${s}`);
@@ -897,6 +920,8 @@ function wireTarget(repo, ids, ctx) {
897
920
  else
898
921
  console.error(`✓ mcp claude: ${res.mcp.path} (${res.mcp.action}) — restart Claude Code to load the graft MCP server`);
899
922
  console.error(res.built ? "✓ built the graph (graft build)" : "· skipped graph build");
923
+ if (!wantStatusline)
924
+ console.error("· skipped Claude Code statusLine (--no-statusline)");
900
925
  for (const w of res.warnings)
901
926
  console.error(`⚠ ${w}`);
902
927
  }
@@ -930,6 +955,7 @@ function wireTarget(repo, ids, ctx) {
930
955
  global: opts.global !== false,
931
956
  mcp: opts.mcp !== false,
932
957
  hooks: opts.hooks !== false,
958
+ statusline: wantStatusline,
933
959
  });
934
960
  // Every host's wiring points at graft/, so the graph is built whatever was
935
961
  // selected — not only when Claude Code is in the list (runInit does its own).