@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.
- package/README.md +10 -5
- package/dist/ai/crux.d.ts +14 -1
- package/dist/ai/crux.d.ts.map +1 -1
- package/dist/ai/crux.js +31 -1
- package/dist/ai/crux.js.map +1 -1
- package/dist/ai/failure.d.ts +13 -2
- package/dist/ai/failure.d.ts.map +1 -1
- package/dist/ai/failure.js +16 -4
- package/dist/ai/failure.js.map +1 -1
- package/dist/ai/llm/factory.d.ts +6 -1
- package/dist/ai/llm/factory.d.ts.map +1 -1
- package/dist/ai/llm/factory.js +8 -0
- package/dist/ai/llm/factory.js.map +1 -1
- package/dist/ai/llm/orcarouter.d.ts +34 -0
- package/dist/ai/llm/orcarouter.d.ts.map +1 -0
- package/dist/ai/llm/orcarouter.js +45 -0
- package/dist/ai/llm/orcarouter.js.map +1 -0
- package/dist/ai/providers.d.ts.map +1 -1
- package/dist/ai/providers.js +13 -3
- package/dist/ai/providers.js.map +1 -1
- package/dist/app/review.d.ts.map +1 -1
- package/dist/app/review.js +10 -0
- package/dist/app/review.js.map +1 -1
- package/dist/blast/blast-cli.d.ts.map +1 -1
- package/dist/blast/blast-cli.js +6 -30
- package/dist/blast/blast-cli.js.map +1 -1
- package/dist/blast/name.d.ts +13 -0
- package/dist/blast/name.d.ts.map +1 -1
- package/dist/blast/name.js +36 -0
- package/dist/blast/name.js.map +1 -1
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +93 -23
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/init.d.ts +1 -0
- package/dist/claude/init.d.ts.map +1 -1
- package/dist/claude/init.js +1 -1
- package/dist/claude/init.js.map +1 -1
- package/dist/claude/session-metrics.d.ts +63 -0
- package/dist/claude/session-metrics.d.ts.map +1 -0
- package/dist/claude/session-metrics.js +181 -0
- package/dist/claude/session-metrics.js.map +1 -0
- package/dist/claude/settings-merge.d.ts +7 -1
- package/dist/claude/settings-merge.d.ts.map +1 -1
- package/dist/claude/settings-merge.js +47 -18
- package/dist/claude/settings-merge.js.map +1 -1
- package/dist/claude/state.d.ts +12 -0
- package/dist/claude/state.d.ts.map +1 -1
- package/dist/claude/state.js +14 -1
- package/dist/claude/state.js.map +1 -1
- package/dist/cli.js +29 -3
- package/dist/cli.js.map +1 -1
- package/dist/context/build.d.ts +11 -0
- package/dist/context/build.d.ts.map +1 -1
- package/dist/context/build.js +27 -6
- package/dist/context/build.js.map +1 -1
- package/dist/context/check.d.ts.map +1 -1
- package/dist/context/check.js +5 -13
- package/dist/context/check.js.map +1 -1
- package/dist/context/node-file.d.ts +1 -1
- package/dist/context/node-file.d.ts.map +1 -1
- package/dist/context/node-file.js +38 -6
- package/dist/context/node-file.js.map +1 -1
- package/dist/context/savings.d.ts +8 -0
- package/dist/context/savings.d.ts.map +1 -1
- package/dist/context/savings.js +14 -0
- package/dist/context/savings.js.map +1 -1
- package/dist/engine.d.ts +2 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -0
- package/dist/engine.js.map +1 -1
- package/dist/graph/cards.d.ts +4 -3
- package/dist/graph/cards.d.ts.map +1 -1
- package/dist/graph/cards.js +34 -6
- package/dist/graph/cards.js.map +1 -1
- package/dist/graph/enrich.d.ts +4 -4
- package/dist/graph/enrich.d.ts.map +1 -1
- package/dist/graph/enrich.js +32 -6
- package/dist/graph/enrich.js.map +1 -1
- package/dist/graph/generic.d.ts +10 -0
- package/dist/graph/generic.d.ts.map +1 -1
- package/dist/graph/generic.js +20 -2
- package/dist/graph/generic.js.map +1 -1
- package/dist/graph/workspace.d.ts +2 -0
- package/dist/graph/workspace.d.ts.map +1 -1
- package/dist/graph/workspace.js +173 -6
- package/dist/graph/workspace.js.map +1 -1
- package/dist/hosts/antigravity.d.ts +2 -2
- package/dist/hosts/antigravity.d.ts.map +1 -1
- package/dist/hosts/codex-hooks.d.ts +2 -6
- package/dist/hosts/codex-hooks.d.ts.map +1 -1
- package/dist/hosts/codex-hooks.js +7 -30
- package/dist/hosts/codex-hooks.js.map +1 -1
- package/dist/hosts/config-write.d.ts +36 -0
- package/dist/hosts/config-write.d.ts.map +1 -0
- package/dist/hosts/config-write.js +63 -0
- package/dist/hosts/config-write.js.map +1 -0
- package/dist/hosts/cursor-hooks.d.ts +18 -0
- package/dist/hosts/cursor-hooks.d.ts.map +1 -0
- package/dist/hosts/cursor-hooks.js +100 -0
- package/dist/hosts/cursor-hooks.js.map +1 -0
- package/dist/hosts/init.d.ts +2 -2
- package/dist/hosts/init.d.ts.map +1 -1
- package/dist/hosts/init.js +8 -2
- package/dist/hosts/init.js.map +1 -1
- package/dist/hosts/mcp-config.d.ts +3 -5
- package/dist/hosts/mcp-config.d.ts.map +1 -1
- package/dist/hosts/mcp-config.js +5 -10
- package/dist/hosts/mcp-config.js.map +1 -1
- package/dist/hosts/plan.d.ts.map +1 -1
- package/dist/hosts/plan.js +2 -0
- package/dist/hosts/plan.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/ingest/fs.d.ts +20 -0
- package/dist/ingest/fs.d.ts.map +1 -1
- package/dist/ingest/fs.js +76 -3
- package/dist/ingest/fs.js.map +1 -1
- package/dist/mcp/tool-names.d.ts +20 -0
- package/dist/mcp/tool-names.d.ts.map +1 -0
- package/dist/mcp/tool-names.js +36 -0
- package/dist/mcp/tool-names.js.map +1 -0
- package/dist/mcp/tools.d.ts +11 -2
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +9 -21
- package/dist/mcp/tools.js.map +1 -1
- package/dist/telemetry/contract.d.ts.map +1 -1
- package/dist/telemetry/contract.js +10 -1
- package/dist/telemetry/contract.js.map +1 -1
- package/dist/telemetry/flush.d.ts.map +1 -1
- package/dist/telemetry/flush.js +1 -0
- package/dist/telemetry/flush.js.map +1 -1
- package/dist/telemetry/identity.d.ts +7 -0
- package/dist/telemetry/identity.d.ts.map +1 -1
- package/dist/telemetry/identity.js.map +1 -1
- package/dist/telemetry/index.d.ts +2 -2
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/telemetry/index.js +2 -2
- package/dist/telemetry/index.js.map +1 -1
- package/dist/telemetry/sessions.d.ts +19 -2
- package/dist/telemetry/sessions.d.ts.map +1 -1
- package/dist/telemetry/sessions.js +57 -40
- package/dist/telemetry/sessions.js.map +1 -1
- package/dist/telemetry/track.d.ts +22 -3
- package/dist/telemetry/track.d.ts.map +1 -1
- package/dist/telemetry/track.js +34 -5
- package/dist/telemetry/track.js.map +1 -1
- package/dist/upkeep-run.js +1 -1
- package/dist/upkeep-run.js.map +1 -1
- package/dist/upkeep.d.ts +7 -4
- package/dist/upkeep.d.ts.map +1 -1
- package/dist/upkeep.js +2 -1
- package/dist/upkeep.js.map +1 -1
- package/package.json +2 -2
- package/scripts/postinstall.mjs +42 -3
- 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
|
-
|
|
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":"
|
|
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
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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) => !
|
|
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":"
|
|
1
|
+
{"version":3,"file":"settings-merge.js","sourceRoot":"","sources":["../../src/claude/settings-merge.ts"],"names":[],"mappings":"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"}
|
package/dist/claude/state.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/claude/state.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/claude/state.js.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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).
|