@nanonets/graft 0.10.1 → 0.12.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 (229) hide show
  1. package/README.md +32 -11
  2. package/dist/ai/crux.d.ts.map +1 -1
  3. package/dist/ai/crux.js +7 -6
  4. package/dist/ai/crux.js.map +1 -1
  5. package/dist/ai/failure.d.ts +49 -0
  6. package/dist/ai/failure.d.ts.map +1 -0
  7. package/dist/ai/failure.js +74 -0
  8. package/dist/ai/failure.js.map +1 -0
  9. package/dist/ai/llm/anthropic.d.ts.map +1 -1
  10. package/dist/ai/llm/anthropic.js +4 -1
  11. package/dist/ai/llm/anthropic.js.map +1 -1
  12. package/dist/ai/llm/claude-cli.d.ts +60 -0
  13. package/dist/ai/llm/claude-cli.d.ts.map +1 -0
  14. package/dist/ai/llm/claude-cli.js +417 -0
  15. package/dist/ai/llm/claude-cli.js.map +1 -0
  16. package/dist/ai/llm/openai.d.ts.map +1 -1
  17. package/dist/ai/llm/openai.js +2 -0
  18. package/dist/ai/llm/openai.js.map +1 -1
  19. package/dist/ai/llm/types.d.ts +9 -0
  20. package/dist/ai/llm/types.d.ts.map +1 -1
  21. package/dist/ai/llm/types.js +12 -1
  22. package/dist/ai/llm/types.js.map +1 -1
  23. package/dist/ask/ask.d.ts.map +1 -1
  24. package/dist/ask/ask.js +39 -45
  25. package/dist/ask/ask.js.map +1 -1
  26. package/dist/ask/fuse.d.ts +83 -46
  27. package/dist/ask/fuse.d.ts.map +1 -1
  28. package/dist/ask/fuse.js +165 -67
  29. package/dist/ask/fuse.js.map +1 -1
  30. package/dist/blast/blast-cli.d.ts +30 -0
  31. package/dist/blast/blast-cli.d.ts.map +1 -0
  32. package/dist/blast/blast-cli.js +163 -0
  33. package/dist/blast/blast-cli.js.map +1 -0
  34. package/dist/blast/blast.d.ts +119 -0
  35. package/dist/blast/blast.d.ts.map +1 -0
  36. package/dist/blast/blast.js +383 -0
  37. package/dist/blast/blast.js.map +1 -0
  38. package/dist/blast/diff.d.ts +53 -0
  39. package/dist/blast/diff.d.ts.map +1 -0
  40. package/dist/blast/diff.js +193 -0
  41. package/dist/blast/diff.js.map +1 -0
  42. package/dist/blast/modules.d.ts +33 -0
  43. package/dist/blast/modules.d.ts.map +1 -0
  44. package/dist/blast/modules.js +83 -0
  45. package/dist/blast/modules.js.map +1 -0
  46. package/dist/blast/name.d.ts +61 -0
  47. package/dist/blast/name.d.ts.map +1 -0
  48. package/dist/blast/name.js +233 -0
  49. package/dist/blast/name.js.map +1 -0
  50. package/dist/blast/render.d.ts +37 -0
  51. package/dist/blast/render.d.ts.map +1 -0
  52. package/dist/blast/render.js +300 -0
  53. package/dist/blast/render.js.map +1 -0
  54. package/dist/blast/viz.d.ts +17 -0
  55. package/dist/blast/viz.d.ts.map +1 -0
  56. package/dist/blast/viz.js +324 -0
  57. package/dist/blast/viz.js.map +1 -0
  58. package/dist/claude/hooks.d.ts.map +1 -1
  59. package/dist/claude/hooks.js +18 -2
  60. package/dist/claude/hooks.js.map +1 -1
  61. package/dist/claude/shim-template.d.ts.map +1 -1
  62. package/dist/claude/shim-template.js +45 -15
  63. package/dist/claude/shim-template.js.map +1 -1
  64. package/dist/claude/state.d.ts +5 -0
  65. package/dist/claude/state.d.ts.map +1 -1
  66. package/dist/claude/state.js.map +1 -1
  67. package/dist/cli-picker.d.ts +44 -5
  68. package/dist/cli-picker.d.ts.map +1 -1
  69. package/dist/cli-picker.js +73 -26
  70. package/dist/cli-picker.js.map +1 -1
  71. package/dist/cli.js +270 -14
  72. package/dist/cli.js.map +1 -1
  73. package/dist/context/build.d.ts +8 -0
  74. package/dist/context/build.d.ts.map +1 -1
  75. package/dist/context/build.js +57 -9
  76. package/dist/context/build.js.map +1 -1
  77. package/dist/context/check.d.ts.map +1 -1
  78. package/dist/context/check.js +7 -6
  79. package/dist/context/check.js.map +1 -1
  80. package/dist/context/node-file.d.ts.map +1 -1
  81. package/dist/context/node-file.js +9 -2
  82. package/dist/context/node-file.js.map +1 -1
  83. package/dist/graph/bindings.d.ts.map +1 -1
  84. package/dist/graph/bindings.js +171 -8
  85. package/dist/graph/bindings.js.map +1 -1
  86. package/dist/graph/build.d.ts.map +1 -1
  87. package/dist/graph/build.js +36 -16
  88. package/dist/graph/build.js.map +1 -1
  89. package/dist/graph/check.d.ts +4 -0
  90. package/dist/graph/check.d.ts.map +1 -1
  91. package/dist/graph/check.js +8 -1
  92. package/dist/graph/check.js.map +1 -1
  93. package/dist/graph/container.d.ts +63 -0
  94. package/dist/graph/container.d.ts.map +1 -0
  95. package/dist/graph/container.js +185 -0
  96. package/dist/graph/container.js.map +1 -0
  97. package/dist/graph/enrich.d.ts +18 -0
  98. package/dist/graph/enrich.d.ts.map +1 -1
  99. package/dist/graph/enrich.js +55 -2
  100. package/dist/graph/enrich.js.map +1 -1
  101. package/dist/graph/extract.d.ts +3 -1
  102. package/dist/graph/extract.d.ts.map +1 -1
  103. package/dist/graph/extract.js +215 -8
  104. package/dist/graph/extract.js.map +1 -1
  105. package/dist/graph/generic.d.ts +34 -1
  106. package/dist/graph/generic.d.ts.map +1 -1
  107. package/dist/graph/generic.js +152 -3
  108. package/dist/graph/generic.js.map +1 -1
  109. package/dist/graph/queries/clojure.scm +58 -0
  110. package/dist/graph/resolve.d.ts.map +1 -1
  111. package/dist/graph/resolve.js +132 -2
  112. package/dist/graph/resolve.js.map +1 -1
  113. package/dist/graph/scopes.d.ts +2 -2
  114. package/dist/graph/scopes.d.ts.map +1 -1
  115. package/dist/graph/scopes.js +8 -6
  116. package/dist/graph/scopes.js.map +1 -1
  117. package/dist/graph/source-files.d.ts +13 -4
  118. package/dist/graph/source-files.d.ts.map +1 -1
  119. package/dist/graph/source-files.js +35 -12
  120. package/dist/graph/source-files.js.map +1 -1
  121. package/dist/graph/types.d.ts +1 -1
  122. package/dist/graph/types.d.ts.map +1 -1
  123. package/dist/graph/workspace-cli.d.ts +2 -0
  124. package/dist/graph/workspace-cli.d.ts.map +1 -1
  125. package/dist/graph/workspace-cli.js +10 -4
  126. package/dist/graph/workspace-cli.js.map +1 -1
  127. package/dist/graph/workspace.d.ts +11 -6
  128. package/dist/graph/workspace.d.ts.map +1 -1
  129. package/dist/graph/workspace.js +24 -13
  130. package/dist/graph/workspace.js.map +1 -1
  131. package/dist/graph/write.d.ts.map +1 -1
  132. package/dist/graph/write.js +25 -2
  133. package/dist/graph/write.js.map +1 -1
  134. package/dist/hosts/antigravity.d.ts +7 -0
  135. package/dist/hosts/antigravity.d.ts.map +1 -0
  136. package/dist/hosts/antigravity.js +42 -0
  137. package/dist/hosts/antigravity.js.map +1 -0
  138. package/dist/hosts/init.d.ts.map +1 -1
  139. package/dist/hosts/init.js +6 -1
  140. package/dist/hosts/init.js.map +1 -1
  141. package/dist/hosts/mcp-config.d.ts.map +1 -1
  142. package/dist/hosts/mcp-config.js +7 -0
  143. package/dist/hosts/mcp-config.js.map +1 -1
  144. package/dist/hosts/plan.d.ts +1 -1
  145. package/dist/hosts/plan.d.ts.map +1 -1
  146. package/dist/hosts/plan.js +2 -0
  147. package/dist/hosts/plan.js.map +1 -1
  148. package/dist/hosts/registry.d.ts.map +1 -1
  149. package/dist/hosts/registry.js +13 -0
  150. package/dist/hosts/registry.js.map +1 -1
  151. package/dist/ingest/fs.d.ts +9 -2
  152. package/dist/ingest/fs.d.ts.map +1 -1
  153. package/dist/ingest/fs.js +100 -16
  154. package/dist/ingest/fs.js.map +1 -1
  155. package/dist/mcp/server.d.ts.map +1 -1
  156. package/dist/mcp/server.js +45 -2
  157. package/dist/mcp/server.js.map +1 -1
  158. package/dist/telemetry/contract.d.ts +90 -0
  159. package/dist/telemetry/contract.d.ts.map +1 -0
  160. package/dist/telemetry/contract.js +203 -0
  161. package/dist/telemetry/contract.js.map +1 -0
  162. package/dist/telemetry/flush.d.ts +21 -0
  163. package/dist/telemetry/flush.d.ts.map +1 -0
  164. package/dist/telemetry/flush.js +84 -0
  165. package/dist/telemetry/flush.js.map +1 -0
  166. package/dist/telemetry/gate.d.ts +16 -0
  167. package/dist/telemetry/gate.d.ts.map +1 -0
  168. package/dist/telemetry/gate.js +64 -0
  169. package/dist/telemetry/gate.js.map +1 -0
  170. package/dist/telemetry/identity.d.ts +33 -0
  171. package/dist/telemetry/identity.d.ts.map +1 -0
  172. package/dist/telemetry/identity.js +80 -0
  173. package/dist/telemetry/identity.js.map +1 -0
  174. package/dist/telemetry/index.d.ts +18 -0
  175. package/dist/telemetry/index.d.ts.map +1 -0
  176. package/dist/telemetry/index.js +16 -0
  177. package/dist/telemetry/index.js.map +1 -0
  178. package/dist/telemetry/key.d.ts +21 -0
  179. package/dist/telemetry/key.d.ts.map +1 -0
  180. package/dist/telemetry/key.js +49 -0
  181. package/dist/telemetry/key.js.map +1 -0
  182. package/dist/telemetry/notice.d.ts +19 -0
  183. package/dist/telemetry/notice.d.ts.map +1 -0
  184. package/dist/telemetry/notice.js +90 -0
  185. package/dist/telemetry/notice.js.map +1 -0
  186. package/dist/telemetry/queue.d.ts +35 -0
  187. package/dist/telemetry/queue.d.ts.map +1 -0
  188. package/dist/telemetry/queue.js +162 -0
  189. package/dist/telemetry/queue.js.map +1 -0
  190. package/dist/telemetry/send.d.ts +19 -0
  191. package/dist/telemetry/send.d.ts.map +1 -0
  192. package/dist/telemetry/send.js +84 -0
  193. package/dist/telemetry/send.js.map +1 -0
  194. package/dist/telemetry/sessions.d.ts +8 -0
  195. package/dist/telemetry/sessions.d.ts.map +1 -0
  196. package/dist/telemetry/sessions.js +81 -0
  197. package/dist/telemetry/sessions.js.map +1 -0
  198. package/dist/telemetry/track.d.ts +43 -0
  199. package/dist/telemetry/track.d.ts.map +1 -0
  200. package/dist/telemetry/track.js +108 -0
  201. package/dist/telemetry/track.js.map +1 -0
  202. package/dist/upkeep-run.d.ts +15 -0
  203. package/dist/upkeep-run.d.ts.map +1 -0
  204. package/dist/upkeep-run.js +60 -0
  205. package/dist/upkeep-run.js.map +1 -0
  206. package/dist/upkeep.d.ts +114 -0
  207. package/dist/upkeep.d.ts.map +1 -0
  208. package/dist/upkeep.js +232 -0
  209. package/dist/upkeep.js.map +1 -0
  210. package/dist/util/num.d.ts +3 -0
  211. package/dist/util/num.d.ts.map +1 -0
  212. package/dist/util/num.js +20 -0
  213. package/dist/util/num.js.map +1 -0
  214. package/dist/util/state.d.ts +11 -4
  215. package/dist/util/state.d.ts.map +1 -1
  216. package/dist/util/state.js +9 -0
  217. package/dist/util/state.js.map +1 -1
  218. package/dist/viewer/app.js +1 -1
  219. package/dist/viewer/index.html +1 -0
  220. package/dist/viewer/style.css +23 -2
  221. package/dist/viz/assemble.d.ts +33 -0
  222. package/dist/viz/assemble.d.ts.map +1 -1
  223. package/dist/viz/assemble.js.map +1 -1
  224. package/dist/viz/export.d.ts +39 -0
  225. package/dist/viz/export.d.ts.map +1 -0
  226. package/dist/viz/export.js +98 -0
  227. package/dist/viz/export.js.map +1 -0
  228. package/package.json +6 -5
  229. package/scripts/stamp-telemetry-key.mjs +53 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The one network call in graft that is not an LLM request.
3
+ *
4
+ * A single POST of the whole queued batch to PostHog's capture endpoint, with
5
+ * `fetch` and no SDK. `posthog-node` would be the conventional choice, but it is
6
+ * a runtime dependency in a CLI whose install weight users notice, it wants to
7
+ * own batching and flushing (both of which the queue already does, and does
8
+ * differently because we are a short-lived process), and "an analytics library
9
+ * that can see everything" is precisely the objection this whole design exists
10
+ * to disarm. Twenty lines of `fetch` is auditable in a sitting.
11
+ *
12
+ * `$process_person_profile: false` on every event is what makes these ANONYMOUS
13
+ * events in PostHog: no person profile is created, no identity is stored, and
14
+ * the `distinct_id` is only ever the random install UUID.
15
+ */
16
+ import { posthogKey, posthogHost } from './key.js';
17
+ /** Anything longer and the detached child is just holding a socket open. */
18
+ const SEND_TIMEOUT_MS = 8000;
19
+ /**
20
+ * The events as PostHog wants them — everything the wire body contains EXCEPT
21
+ * the project key.
22
+ *
23
+ * The key is deliberately not in here. `graft telemetry debug` prints this
24
+ * object so a user can audit exactly what graft sends, and a command that exists
25
+ * to be pasted into a bug report must not also paste our ingestion key into it.
26
+ * `sendBatch` adds the key at the moment of the request and nowhere else, which
27
+ * keeps the key entirely out of every code path that can reach a terminal.
28
+ */
29
+ export function buildBatch(events) {
30
+ return {
31
+ batch: events.map((e) => {
32
+ const ev = e;
33
+ return {
34
+ event: ev.event,
35
+ timestamp: ev.timestamp,
36
+ properties: {
37
+ ...(ev.properties ?? {}),
38
+ distinct_id: ev.distinct_id,
39
+ // Anonymous event: PostHog stores it without creating a person.
40
+ $process_person_profile: false,
41
+ },
42
+ };
43
+ }),
44
+ };
45
+ }
46
+ /**
47
+ * The ingest path.
48
+ *
49
+ * `/batch/` only, with no fallback. An earlier revision also tried `/e/`,
50
+ * because assign's `frontend/src/lib/posthog.ts` documents `events.nanonets.com`
51
+ * as serving the older path and answering `400 invalid_payload` to bodies it
52
+ * will not take. Probed directly, that host returns **401** to a well-formed
53
+ * batch with a bad key — so `/batch/` is there and does parse the body, and the
54
+ * historical 400 was gzip from `posthog-js` in a browser, which we never send.
55
+ *
56
+ * The fallback was therefore a second request that could not fire, and a branch
57
+ * production never exercises is worse than no branch. If a future host needs
58
+ * `/e/`, point `GRAFT_POSTHOG_HOST` at it and add the path back with evidence.
59
+ */
60
+ const INGEST_PATH = '/batch/';
61
+ export async function sendBatch(events) {
62
+ if (events.length === 0)
63
+ return { ok: true };
64
+ const key = posthogKey();
65
+ if (!key)
66
+ return { ok: false, error: 'no key' };
67
+ try {
68
+ const res = await fetch(`${posthogHost()}${INGEST_PATH}`, {
69
+ method: 'POST',
70
+ headers: { 'content-type': 'application/json' },
71
+ // The one place the key is ever attached: the request body itself.
72
+ body: JSON.stringify({ api_key: key, ...buildBatch(events) }),
73
+ signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
74
+ });
75
+ // A 4xx is our bug (a bad key, a malformed body) and retrying it forever
76
+ // would pin the queue at its cap; only 5xx and transport errors go back on
77
+ // the queue — see shouldRequeue in flush.ts.
78
+ return { ok: res.ok, status: res.status };
79
+ }
80
+ catch (e) {
81
+ return { ok: false, error: e instanceof Error ? e.name : 'unknown' };
82
+ }
83
+ }
84
+ //# sourceMappingURL=send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/telemetry/send.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEnD,4EAA4E;AAC5E,MAAM,eAAe,GAAG,IAAI,CAAC;AAS7B;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,MAAiB;IAC1C,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACtB,MAAM,EAAE,GAAG,CAAqG,CAAC;YACjH,OAAO;gBACL,KAAK,EAAE,EAAE,CAAC,KAAK;gBACf,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,UAAU,EAAE;oBACV,GAAG,CAAC,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC;oBACxB,WAAW,EAAE,EAAE,CAAC,WAAW;oBAC3B,gEAAgE;oBAChE,uBAAuB,EAAE,KAAK;iBAC/B;aACF,CAAC;QACJ,CAAC,CAAC;KACH,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,GAAG,SAAS,CAAC;AAE9B,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAiB;IAC/C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAC7C,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,WAAW,EAAE,GAAG,WAAW,EAAE,EAAE;YACxD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,mEAAmE;YACnE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,eAAe,CAAC;SAC7C,CAAC,CAAC;QACH,yEAAyE;QACzE,2EAA2E;QAC3E,6CAA6C;QAC7C,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAC5C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IACvE,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ /** Untouched for this long and the session is treated as over. */
2
+ export declare const SESSION_IDLE_MS: number;
3
+ /**
4
+ * Queue a `session_summary` for every closed, not-yet-summarised session in this
5
+ * repo. Returns how many were queued (for tests). Never throws.
6
+ */
7
+ export declare function flushClosedSessions(repo: string, now?: number, home?: string, env?: NodeJS.ProcessEnv): number;
8
+ //# sourceMappingURL=sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/telemetry/sessions.ts"],"names":[],"mappings":"AA2BA,kEAAkE;AAClE,eAAO,MAAM,eAAe,QAAqB,CAAC;AAMlD;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,GAAG,SAAa,EAChB,IAAI,CAAC,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GACtB,MAAM,CAoCR"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Rolling a finished agent session up into one `session_summary` event.
3
+ *
4
+ * This is the event that justifies the whole exercise. Everything else here
5
+ * counts commands; this one answers the question the product actually rests on —
6
+ * when an agent had both graft and grep available, which did it reach for, and
7
+ * how many tokens did that save? The counters already exist: the Claude Code
8
+ * hooks have been writing `graftReads`, `sourceReads` and `savedTokens` per
9
+ * session into `graft/.cache/session/` since well before telemetry did. Nothing
10
+ * new is measured here; a closed session is simply bucketed and queued.
11
+ *
12
+ * "Closed" is inferred from mtime rather than observed, because there is no
13
+ * reliable end-of-session hook — `Stop` fires per turn, and an editor that
14
+ * crashes fires nothing at all. A session untouched for two hours is over.
15
+ *
16
+ * Called from the session-start hook, which is the one place guaranteed to run
17
+ * again after a session ends. A hook must never touch the network (see the note
18
+ * atop upkeep.ts) and this doesn't: it only appends to the local queue.
19
+ */
20
+ import { readdirSync, statSync } from 'node:fs';
21
+ import { join } from 'node:path';
22
+ import { cacheDir } from '../util/state.js';
23
+ import { readSession, writeSession } from '../claude/state.js';
24
+ import { countBucket, savedTokensBucket } from './contract.js';
25
+ import { track } from './track.js';
26
+ import { telemetryOn } from './gate.js';
27
+ /** Untouched for this long and the session is treated as over. */
28
+ export const SESSION_IDLE_MS = 2 * 60 * 60 * 1000;
29
+ /** Never roll up more than this in one hook run — a repo with a huge backlog of
30
+ * session files must not slow down a session start. The rest wait for the next. */
31
+ const MAX_PER_RUN = 20;
32
+ /**
33
+ * Queue a `session_summary` for every closed, not-yet-summarised session in this
34
+ * repo. Returns how many were queued (for tests). Never throws.
35
+ */
36
+ export function flushClosedSessions(repo, now = Date.now(), home, env) {
37
+ try {
38
+ if (!telemetryOn(home, env))
39
+ return 0;
40
+ const dir = join(cacheDir(repo), 'session');
41
+ let files;
42
+ try {
43
+ files = readdirSync(dir).filter((f) => f.endsWith('.json'));
44
+ }
45
+ catch {
46
+ return 0;
47
+ }
48
+ let queued = 0;
49
+ for (const file of files.sort()) {
50
+ if (queued >= MAX_PER_RUN)
51
+ break;
52
+ let mtime;
53
+ try {
54
+ mtime = statSync(join(dir, file)).mtimeMs;
55
+ }
56
+ catch {
57
+ continue;
58
+ }
59
+ if (now - mtime < SESSION_IDLE_MS)
60
+ continue;
61
+ const id = file.slice(0, -'.json'.length);
62
+ const s = readSession(repo, id);
63
+ if (s.summarized)
64
+ continue;
65
+ // An empty session — opened, nothing asked — is still a fact worth having:
66
+ // it is the difference between "graft is installed" and "graft is used".
67
+ track('session_summary', {
68
+ graft_reads_bucket: countBucket(s.graftReads ?? 0),
69
+ source_reads_bucket: countBucket(s.sourceReads ?? 0),
70
+ saved_tokens_bucket: savedTokensBucket(s.savedTokens ?? 0),
71
+ }, { repo, host: 'claude-code', home, env });
72
+ writeSession(repo, id, { ...s, summarized: true });
73
+ queued++;
74
+ }
75
+ return queued;
76
+ }
77
+ catch {
78
+ return 0; // a session start is never worth failing over a metric
79
+ }
80
+ }
81
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/telemetry/sessions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,kEAAkE;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAElD;oFACoF;AACpF,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,EAChB,IAAa,EACb,GAAuB;IAEvB,IAAI,CAAC;QACH,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;QAC5C,IAAI,KAAe,CAAC;QACpB,IAAI,CAAC;YAAC,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,CAAC,CAAC;QAAC,CAAC;QAExF,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAChC,IAAI,MAAM,IAAI,WAAW;gBAAE,MAAM;YACjC,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC;gBAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,SAAS;YAAC,CAAC;YACtE,IAAI,GAAG,GAAG,KAAK,GAAG,eAAe;gBAAE,SAAS;YAE5C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAChC,IAAI,CAAC,CAAC,UAAU;gBAAE,SAAS;YAE3B,2EAA2E;YAC3E,yEAAyE;YACzE,KAAK,CACH,iBAAiB,EACjB;gBACE,kBAAkB,EAAE,WAAW,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC;gBAClD,mBAAmB,EAAE,WAAW,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC;gBACpD,mBAAmB,EAAE,iBAAiB,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC;aAC3D,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,CACzC,CAAC;YACF,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,MAAM,EAAE,CAAC;QACX,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC,CAAC,uDAAuD;IACnE,CAAC;AACH,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { AgentHost } from './contract.js';
2
+ /** One queued event, exactly as it will be sent. */
3
+ export interface QueuedEvent {
4
+ event: string;
5
+ properties: Record<string, string>;
6
+ /** ISO timestamp, so a batch flushed a day late still lands on the right day. */
7
+ timestamp: string;
8
+ /** The install id, used as PostHog's `distinct_id` at send time. */
9
+ distinct_id: string;
10
+ }
11
+ export interface TrackContext {
12
+ /** Repo root, when the call site has one — supplies `repo_id`. */
13
+ repo?: string;
14
+ /** Which surface is calling. Defaults to the CLI. */
15
+ host?: AgentHost;
16
+ /** Test seam: a scratch `$HOME`. Production callers never pass this. */
17
+ home?: string;
18
+ /** Test seam: an environment to gate against. Production callers never pass
19
+ * this — a real run must be gated on the real `DO_NOT_TRACK` and `CI`. */
20
+ env?: NodeJS.ProcessEnv;
21
+ }
22
+ /**
23
+ * Which agent graft is running under, from the surface plus one env probe.
24
+ *
25
+ * Only the PRESENCE of `CLAUDECODE` is read, never its value — the distinction
26
+ * matters: a value could be anything a user's shell profile put there, presence
27
+ * is a boolean. Anything unrecognised is `cli`, which is the honest answer.
28
+ */
29
+ export declare function detectHost(explicit?: AgentHost, env?: NodeJS.ProcessEnv): AgentHost;
30
+ /**
31
+ * Record one event, if the contract allows it and the gates are open.
32
+ *
33
+ * Returns the event that was queued, or null — the return value exists for
34
+ * tests and for `graft telemetry debug`, not for control flow at call sites.
35
+ */
36
+ export declare function track(event: string, props?: Record<string, string | undefined>, ctx?: TrackContext): QueuedEvent | null;
37
+ /**
38
+ * The `first_run` event, fired the once. Separate from `track` because it is the
39
+ * only event whose trigger is "the id did not exist a moment ago", which only
40
+ * `installId()` can know.
41
+ */
42
+ export declare function trackFirstRunIfNew(ctx?: TrackContext): void;
43
+ //# sourceMappingURL=track.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/telemetry/track.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAM/C,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,SAAS,CAIhG;AAmBD;;;;;GAKG;AACH,wBAAgB,KAAK,CACnB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAM,EAC9C,GAAG,GAAE,YAAiB,GACrB,WAAW,GAAG,IAAI,CA0BpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,YAAiB,GAAG,IAAI,CAM/D"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * `track()` — the only way an event is ever created, and the place the contract
3
+ * is enforced.
4
+ *
5
+ * Call sites pass a name and some properties; this drops anything the contract
6
+ * (contract.ts) does not list, stamps the common properties, and appends to the
7
+ * local queue. It never sends, never blocks, and never throws — a telemetry bug
8
+ * must be incapable of failing a build or a query.
9
+ *
10
+ * Two rules do the actual privacy work, and both are enforced here rather than
11
+ * asked of callers:
12
+ *
13
+ * • unknown event → dropped whole; unknown property key → dropped, the rest of
14
+ * the event survives. A careless `track('query', { path })` sends `query`
15
+ * without a path.
16
+ * • non-string values → dropped. Everything that crosses the wire is a bucket
17
+ * label or an enum member, so a raw count or a raw message cannot get out
18
+ * even under a key that happens to be allowed.
19
+ */
20
+ import { EVENTS } from './contract.js';
21
+ import { enqueue } from './queue.js';
22
+ import { telemetryOn } from './gate.js';
23
+ import { installId, repoId } from './identity.js';
24
+ import { runningVersion } from '../upkeep.js';
25
+ /**
26
+ * Which agent graft is running under, from the surface plus one env probe.
27
+ *
28
+ * Only the PRESENCE of `CLAUDECODE` is read, never its value — the distinction
29
+ * matters: a value could be anything a user's shell profile put there, presence
30
+ * is a boolean. Anything unrecognised is `cli`, which is the honest answer.
31
+ */
32
+ export function detectHost(explicit, env = process.env) {
33
+ if (explicit)
34
+ return explicit;
35
+ if (env.CLAUDECODE !== undefined)
36
+ return 'claude-code';
37
+ return 'cli';
38
+ }
39
+ /** The properties every event carries. Kept in one place so no call site can
40
+ * disagree about them. */
41
+ function commonProps(ctx) {
42
+ const props = {
43
+ app_version: runningVersion(),
44
+ os: process.platform,
45
+ arch: process.arch,
46
+ node_major: String(process.versions.node.split('.')[0]),
47
+ ci: 'false', // an event only exists at all when the CI gate passed
48
+ agent_host: detectHost(ctx.host, ctx.env ?? process.env),
49
+ };
50
+ if (ctx.repo) {
51
+ try {
52
+ props.repo_id = repoId(ctx.repo);
53
+ }
54
+ catch { /* unwritable cache — omit */ }
55
+ }
56
+ return props;
57
+ }
58
+ /**
59
+ * Record one event, if the contract allows it and the gates are open.
60
+ *
61
+ * Returns the event that was queued, or null — the return value exists for
62
+ * tests and for `graft telemetry debug`, not for control flow at call sites.
63
+ */
64
+ export function track(event, props = {}, ctx = {}) {
65
+ try {
66
+ if (!telemetryOn(ctx.home, ctx.env))
67
+ return null;
68
+ // `Object.hasOwn`, not `EVENTS[event]`: a bare object literal inherits from
69
+ // Object.prototype, so `EVENTS['constructor']` is truthy and would sail past
70
+ // a plain existence check — only failing later, on a TypeError swallowed by
71
+ // the catch below. The allowlist must reject by design, not by exception.
72
+ if (!Object.hasOwn(EVENTS, event))
73
+ return null;
74
+ const allowed = EVENTS[event];
75
+ const properties = commonProps(ctx);
76
+ for (const [k, v] of Object.entries(props)) {
77
+ if (allowed.has(k) && typeof v === 'string')
78
+ properties[k] = v;
79
+ }
80
+ const queued = {
81
+ event,
82
+ properties,
83
+ timestamp: new Date().toISOString(),
84
+ distinct_id: installId(ctx.home).id,
85
+ };
86
+ enqueue(queued, ctx.home);
87
+ return queued;
88
+ }
89
+ catch {
90
+ return null; // telemetry never surfaces as a user-visible failure
91
+ }
92
+ }
93
+ /**
94
+ * The `first_run` event, fired the once. Separate from `track` because it is the
95
+ * only event whose trigger is "the id did not exist a moment ago", which only
96
+ * `installId()` can know.
97
+ */
98
+ export function trackFirstRunIfNew(ctx = {}) {
99
+ try {
100
+ if (!telemetryOn(ctx.home, ctx.env))
101
+ return;
102
+ if (!installId(ctx.home).firstRun)
103
+ return;
104
+ track('first_run', {}, ctx);
105
+ }
106
+ catch { /* never */ }
107
+ }
108
+ //# sourceMappingURL=track.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"track.js","sourceRoot":"","sources":["../../src/telemetry/track.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface UpkeepResult {
2
+ /** Lines worth showing the agent/user, already formatted. Usually empty. */
3
+ lines: string[];
4
+ }
5
+ /**
6
+ * @param repo the project dir
7
+ * @param current the running graft's version
8
+ * @param opts.background false in hook contexts: read the update cache, never
9
+ * spawn a fetch. Hooks run under a hard timeout and must stay off the network.
10
+ */
11
+ export declare function runUpkeep(repo: string, current: string, opts?: {
12
+ background?: boolean;
13
+ home?: string;
14
+ }): UpkeepResult;
15
+ //# sourceMappingURL=upkeep-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upkeep-run.d.ts","sourceRoot":"","sources":["../src/upkeep-run.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAyBD;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GACjD,YAAY,CAad"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The wired-up version of `./upkeep.ts` — one call every entry point makes.
3
+ *
4
+ * Split from `upkeep.ts` so that module stays pure and unit-testable (no init
5
+ * writes, no spawning): this file is the only place that knows how to actually
6
+ * re-run the wiring, and it is deliberately thin.
7
+ */
8
+ import { runInit } from './claude/init.js';
9
+ import { runHostsInit } from './hosts/init.js';
10
+ import { graftCliPath } from './claude/paths.js';
11
+ import { formatUpdateNudge, formatWiringRefresh, maybeRefreshInBackground, readUpdateCache, reconcileWiring, } from './upkeep.js';
12
+ /**
13
+ * Re-write the wiring for exactly the hosts a previous `init` chose here, with
14
+ * exactly the flags it was given.
15
+ *
16
+ * `build: false` matters: this runs at session start and at MCP boot, and a graph
17
+ * build there would stall the agent's first turn.
18
+ *
19
+ * The out-of-repo writes (`~/.codex/hooks.json`, `~/.codex/config.toml`) ARE
20
+ * included, because nothing else would ever refresh them: no skill, rule file, or
21
+ * MCP instruction tells an agent to run `graft init`, so leaving them out means a
22
+ * Codex user upgrades the binary and keeps the old hook config forever. They're
23
+ * safe to replay — `installCodexHooks` no-ops when `~/.codex` is absent, rewrites
24
+ * only its own entry (matched on `graft-hooks.cjs`), and reports `unchanged` when
25
+ * the bytes match. A user who declined them at init time is honoured via
26
+ * `opts.global`/`opts.hooks`, replayed from the stamp.
27
+ */
28
+ function rewriteWiring(repo, hosts, opts) {
29
+ if (hosts.includes('claude'))
30
+ runInit(repo, { build: false, cliPath: graftCliPath() });
31
+ const others = hosts.filter((h) => h !== 'claude');
32
+ if (others.length)
33
+ runHostsInit(repo, { agents: others, global: opts.global, mcp: opts.mcp, hooks: opts.hooks });
34
+ }
35
+ /**
36
+ * @param repo the project dir
37
+ * @param current the running graft's version
38
+ * @param opts.background false in hook contexts: read the update cache, never
39
+ * spawn a fetch. Hooks run under a hard timeout and must stay off the network.
40
+ */
41
+ export function runUpkeep(repo, current, opts = {}) {
42
+ const lines = [];
43
+ try {
44
+ const refreshed = reconcileWiring(repo, current, { rewrite: rewriteWiring });
45
+ const refreshLine = formatWiringRefresh(refreshed);
46
+ if (refreshLine)
47
+ lines.push(refreshLine);
48
+ }
49
+ catch { /* fail-soft: wiring refresh is never worth breaking a session for */ }
50
+ try {
51
+ if (opts.background !== false)
52
+ maybeRefreshInBackground(opts.home);
53
+ const nudge = formatUpdateNudge(current, readUpdateCache(opts.home)?.latest);
54
+ if (nudge)
55
+ lines.push(nudge);
56
+ }
57
+ catch { /* same */ }
58
+ return { lines };
59
+ }
60
+ //# sourceMappingURL=upkeep-run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upkeep-run.js","sourceRoot":"","sources":["../src/upkeep-run.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,eAAe,EACf,eAAe,GAEhB,MAAM,aAAa,CAAC;AAOrB;;;;;;;;;;;;;;;GAeG;AACH,SAAS,aAAa,CAAC,IAAY,EAAE,KAAe,EAAE,IAAgB;IACpE,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;IACnD,IAAI,MAAM,CAAC,MAAM;QACf,YAAY,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;AAClG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,OAAe,EACf,OAAgD,EAAE;IAElD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;QAC7E,MAAM,WAAW,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QACnD,IAAI,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC,CAAC,qEAAqE,CAAC,CAAC;IACjF,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,UAAU,KAAK,KAAK;YAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,EAAE,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QAC7E,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC;IACtB,OAAO,EAAE,KAAK,EAAE,CAAC;AACnB,CAAC"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The version of the graft package this code was loaded from.
3
+ *
4
+ * Resolved from *this* module rather than the caller's: `readCurrentVersion`
5
+ * looks one level up from the module URL it's given, which lands on the package
6
+ * root for `dist/upkeep.js` but misses entirely for `dist/claude/hooks.js` and
7
+ * `dist/mcp/server.js`. Every caller asking here instead of passing its own
8
+ * `import.meta.url` is what keeps the hook and the MCP server from reading
9
+ * `0.0.0` and re-initing on every single session.
10
+ */
11
+ export declare function runningVersion(): string;
12
+ /** How long a registry answer is considered current. A day: graft ships far less
13
+ * often than that, and this is a nudge, not a security update. */
14
+ export declare const UPDATE_TTL_MS: number;
15
+ /** Numeric-dotted compare of the release part only (`1.2.3-beta.1` → `1.2.3`).
16
+ * Prerelease ordering doesn't matter here: the only question either caller asks
17
+ * is "is the thing on npm ahead of what's on disk", and a prerelease that
18
+ * compares equal simply produces no nudge. */
19
+ export declare function compareVersions(a: string, b: string): number;
20
+ export declare function isNewer(candidate: string | null | undefined, current: string): boolean;
21
+ export interface UpdateCache {
22
+ /** Latest version seen on npm, or null when the last fetch failed. */
23
+ latest: string | null;
24
+ /** Epoch ms of the last *attempt* — written by the parent before it spawns the
25
+ * fetch, so a repeatedly-failing fetch can't make every command spawn a child. */
26
+ checkedAt: number;
27
+ }
28
+ /** Machine-global, not per-repo: "what's the latest graft" is one fact, and a
29
+ * dev with twelve repos should cost the registry one request a day, not twelve. */
30
+ export declare function updateCachePath(home?: string): string;
31
+ export declare function readUpdateCache(home?: string): UpdateCache | null;
32
+ /**
33
+ * The `graft _update-check` command body: hit the registry, store the answer.
34
+ * Runs in a detached child so nothing user-facing ever waits on the network.
35
+ */
36
+ export declare function refreshUpdateCache(home?: string, now?: number): UpdateCache;
37
+ /** True when the cached answer is missing or older than the TTL. */
38
+ export declare function needsRefresh(cache: UpdateCache | null, now?: number): boolean;
39
+ /**
40
+ * Kick off a background registry check if the cache has gone stale. Touches
41
+ * `checkedAt` first so concurrent callers (and a child that dies) don't spawn a
42
+ * fetch per invocation. Never waits, never throws.
43
+ *
44
+ * Only called from long-lived or already-slow contexts (a CLI command, MCP
45
+ * boot) — never from a hook, which reads the cache and nothing else.
46
+ */
47
+ export declare function maybeRefreshInBackground(home?: string, now?: number): boolean;
48
+ /** One line, or nothing. Nothing is the common case — don't spend context on
49
+ * "you're up to date". */
50
+ export declare function formatUpdateNudge(current: string, latest: string | null | undefined): string | null;
51
+ /**
52
+ * The subset of `graft init`'s flags a refresh has to replay.
53
+ *
54
+ * Without these, an auto-refresh would install things the user explicitly
55
+ * declined: someone who ran `graft init --no-global` (or `--no-hooks`) said "keep
56
+ * out of `~/.codex`", and a later session silently writing there would be graft
57
+ * overriding a decision rather than maintaining one. Absent from an older stamp →
58
+ * all true, which is what plain `graft init` does.
59
+ */
60
+ export interface WiringOpts {
61
+ /** false → never write outside the repo (`--no-global`). */
62
+ global: boolean;
63
+ /** false → skip MCP server registration (`--no-mcp`). */
64
+ mcp: boolean;
65
+ /** false → skip hook installation (`--no-hooks`). */
66
+ hooks: boolean;
67
+ }
68
+ export declare const DEFAULT_WIRING_OPTS: WiringOpts;
69
+ /** An older stamp has no `opts`; a plain `graft init` wired everything. */
70
+ export declare function wiringOpts(stamp: WiringStamp | null): WiringOpts;
71
+ export interface WiringStamp {
72
+ /** The graft version whose `init` wrote this repo's agent files. */
73
+ version: string;
74
+ /** Host ids that were wired, so a refresh re-writes exactly those and never
75
+ * silently adopts an agent the user declined in the picker. */
76
+ hosts: string[];
77
+ /** The init flags to replay — see {@link WiringOpts}. */
78
+ opts?: Partial<WiringOpts>;
79
+ at: string;
80
+ }
81
+ /** Under `graft/.cache/`, beside the other derived state: git-ignored, per-clone,
82
+ * and cheap to lose — a missing stamp just means one idempotent refresh. */
83
+ export declare function stampPath(repo: string): string;
84
+ export declare function readStamp(repo: string): WiringStamp | null;
85
+ export declare function writeStamp(repo: string, version: string, hosts: string[], opts?: Partial<WiringOpts>, at?: string): void;
86
+ /**
87
+ * Which agents this repo is *already* wired for, read off disk rather than
88
+ * re-detected. Detection answers "which editors does this machine have"; for a
89
+ * refresh we need "which files did a previous init actually write" — otherwise
90
+ * installing Windsurf once would silently add graft rules to every repo.
91
+ */
92
+ export declare function wiredHostIds(repo: string): string[];
93
+ export interface WiringRefresh {
94
+ from: string;
95
+ to: string;
96
+ hosts: string[];
97
+ /** True when the refresh included writes outside the repo (`~/.codex/`), so the
98
+ * caller can say so — a session changing machine-wide config should be visible. */
99
+ global: boolean;
100
+ }
101
+ /**
102
+ * Re-run init's writes when the stamp and the running binary disagree.
103
+ *
104
+ * Deliberately narrow: it refreshes the hosts already wired, replays the flags
105
+ * that init was given, never builds the graph (this runs at session start — a
106
+ * rebuild there would stall the agent's first turn), and no-ops when the repo has
107
+ * no graft wiring at all.
108
+ */
109
+ export declare function reconcileWiring(repo: string, current: string, deps: {
110
+ wired?: (repo: string) => string[];
111
+ rewrite: (repo: string, hosts: string[], opts: WiringOpts) => void;
112
+ }): WiringRefresh | null;
113
+ export declare function formatWiringRefresh(r: WiringRefresh | null): string | null;
114
+ //# sourceMappingURL=upkeep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upkeep.d.ts","sourceRoot":"","sources":["../src/upkeep.ts"],"names":[],"mappings":"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"}