@nanonets/graft 0.10.0 → 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 (238) hide show
  1. package/README.md +34 -13
  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 +13 -0
  59. package/dist/claude/hooks.d.ts.map +1 -1
  60. package/dist/claude/hooks.js +44 -4
  61. package/dist/claude/hooks.js.map +1 -1
  62. package/dist/claude/shim-template.d.ts.map +1 -1
  63. package/dist/claude/shim-template.js +45 -15
  64. package/dist/claude/shim-template.js.map +1 -1
  65. package/dist/claude/state.d.ts +5 -0
  66. package/dist/claude/state.d.ts.map +1 -1
  67. package/dist/claude/state.js.map +1 -1
  68. package/dist/cli-picker.d.ts +44 -5
  69. package/dist/cli-picker.d.ts.map +1 -1
  70. package/dist/cli-picker.js +73 -26
  71. package/dist/cli-picker.js.map +1 -1
  72. package/dist/cli.js +270 -14
  73. package/dist/cli.js.map +1 -1
  74. package/dist/context/build.d.ts +8 -0
  75. package/dist/context/build.d.ts.map +1 -1
  76. package/dist/context/build.js +57 -9
  77. package/dist/context/build.js.map +1 -1
  78. package/dist/context/check.d.ts.map +1 -1
  79. package/dist/context/check.js +7 -6
  80. package/dist/context/check.js.map +1 -1
  81. package/dist/context/node-file.d.ts.map +1 -1
  82. package/dist/context/node-file.js +9 -2
  83. package/dist/context/node-file.js.map +1 -1
  84. package/dist/graph/bindings.d.ts.map +1 -1
  85. package/dist/graph/bindings.js +245 -2
  86. package/dist/graph/bindings.js.map +1 -1
  87. package/dist/graph/build.d.ts.map +1 -1
  88. package/dist/graph/build.js +36 -16
  89. package/dist/graph/build.js.map +1 -1
  90. package/dist/graph/check.d.ts +4 -0
  91. package/dist/graph/check.d.ts.map +1 -1
  92. package/dist/graph/check.js +8 -1
  93. package/dist/graph/check.js.map +1 -1
  94. package/dist/graph/container.d.ts +63 -0
  95. package/dist/graph/container.d.ts.map +1 -0
  96. package/dist/graph/container.js +185 -0
  97. package/dist/graph/container.js.map +1 -0
  98. package/dist/graph/enrich.d.ts +18 -0
  99. package/dist/graph/enrich.d.ts.map +1 -1
  100. package/dist/graph/enrich.js +55 -2
  101. package/dist/graph/enrich.js.map +1 -1
  102. package/dist/graph/extract.d.ts +8 -1
  103. package/dist/graph/extract.d.ts.map +1 -1
  104. package/dist/graph/extract.js +401 -12
  105. package/dist/graph/extract.js.map +1 -1
  106. package/dist/graph/generic.d.ts +34 -1
  107. package/dist/graph/generic.d.ts.map +1 -1
  108. package/dist/graph/generic.js +176 -6
  109. package/dist/graph/generic.js.map +1 -1
  110. package/dist/graph/invariants.d.ts +32 -0
  111. package/dist/graph/invariants.d.ts.map +1 -0
  112. package/dist/graph/invariants.js +49 -0
  113. package/dist/graph/invariants.js.map +1 -0
  114. package/dist/graph/queries/clojure.scm +58 -0
  115. package/dist/graph/resolve.d.ts.map +1 -1
  116. package/dist/graph/resolve.js +249 -23
  117. package/dist/graph/resolve.js.map +1 -1
  118. package/dist/graph/scopes.d.ts +2 -2
  119. package/dist/graph/scopes.d.ts.map +1 -1
  120. package/dist/graph/scopes.js +8 -6
  121. package/dist/graph/scopes.js.map +1 -1
  122. package/dist/graph/source-files.d.ts +13 -4
  123. package/dist/graph/source-files.d.ts.map +1 -1
  124. package/dist/graph/source-files.js +35 -12
  125. package/dist/graph/source-files.js.map +1 -1
  126. package/dist/graph/types.d.ts +3 -1
  127. package/dist/graph/types.d.ts.map +1 -1
  128. package/dist/graph/workspace-cli.d.ts +2 -0
  129. package/dist/graph/workspace-cli.d.ts.map +1 -1
  130. package/dist/graph/workspace-cli.js +10 -4
  131. package/dist/graph/workspace-cli.js.map +1 -1
  132. package/dist/graph/workspace.d.ts +11 -6
  133. package/dist/graph/workspace.d.ts.map +1 -1
  134. package/dist/graph/workspace.js +24 -13
  135. package/dist/graph/workspace.js.map +1 -1
  136. package/dist/graph/write.d.ts.map +1 -1
  137. package/dist/graph/write.js +25 -2
  138. package/dist/graph/write.js.map +1 -1
  139. package/dist/hosts/antigravity.d.ts +7 -0
  140. package/dist/hosts/antigravity.d.ts.map +1 -0
  141. package/dist/hosts/antigravity.js +42 -0
  142. package/dist/hosts/antigravity.js.map +1 -0
  143. package/dist/hosts/codex-hooks.d.ts +3 -3
  144. package/dist/hosts/codex-hooks.d.ts.map +1 -1
  145. package/dist/hosts/codex-hooks.js +27 -21
  146. package/dist/hosts/codex-hooks.js.map +1 -1
  147. package/dist/hosts/init.d.ts.map +1 -1
  148. package/dist/hosts/init.js +6 -1
  149. package/dist/hosts/init.js.map +1 -1
  150. package/dist/hosts/mcp-config.d.ts.map +1 -1
  151. package/dist/hosts/mcp-config.js +7 -0
  152. package/dist/hosts/mcp-config.js.map +1 -1
  153. package/dist/hosts/plan.d.ts +1 -1
  154. package/dist/hosts/plan.d.ts.map +1 -1
  155. package/dist/hosts/plan.js +2 -0
  156. package/dist/hosts/plan.js.map +1 -1
  157. package/dist/hosts/registry.d.ts.map +1 -1
  158. package/dist/hosts/registry.js +13 -0
  159. package/dist/hosts/registry.js.map +1 -1
  160. package/dist/ingest/fs.d.ts +9 -2
  161. package/dist/ingest/fs.d.ts.map +1 -1
  162. package/dist/ingest/fs.js +100 -16
  163. package/dist/ingest/fs.js.map +1 -1
  164. package/dist/mcp/server.d.ts.map +1 -1
  165. package/dist/mcp/server.js +45 -2
  166. package/dist/mcp/server.js.map +1 -1
  167. package/dist/telemetry/contract.d.ts +90 -0
  168. package/dist/telemetry/contract.d.ts.map +1 -0
  169. package/dist/telemetry/contract.js +203 -0
  170. package/dist/telemetry/contract.js.map +1 -0
  171. package/dist/telemetry/flush.d.ts +21 -0
  172. package/dist/telemetry/flush.d.ts.map +1 -0
  173. package/dist/telemetry/flush.js +84 -0
  174. package/dist/telemetry/flush.js.map +1 -0
  175. package/dist/telemetry/gate.d.ts +16 -0
  176. package/dist/telemetry/gate.d.ts.map +1 -0
  177. package/dist/telemetry/gate.js +64 -0
  178. package/dist/telemetry/gate.js.map +1 -0
  179. package/dist/telemetry/identity.d.ts +33 -0
  180. package/dist/telemetry/identity.d.ts.map +1 -0
  181. package/dist/telemetry/identity.js +80 -0
  182. package/dist/telemetry/identity.js.map +1 -0
  183. package/dist/telemetry/index.d.ts +18 -0
  184. package/dist/telemetry/index.d.ts.map +1 -0
  185. package/dist/telemetry/index.js +16 -0
  186. package/dist/telemetry/index.js.map +1 -0
  187. package/dist/telemetry/key.d.ts +21 -0
  188. package/dist/telemetry/key.d.ts.map +1 -0
  189. package/dist/telemetry/key.js +49 -0
  190. package/dist/telemetry/key.js.map +1 -0
  191. package/dist/telemetry/notice.d.ts +19 -0
  192. package/dist/telemetry/notice.d.ts.map +1 -0
  193. package/dist/telemetry/notice.js +90 -0
  194. package/dist/telemetry/notice.js.map +1 -0
  195. package/dist/telemetry/queue.d.ts +35 -0
  196. package/dist/telemetry/queue.d.ts.map +1 -0
  197. package/dist/telemetry/queue.js +162 -0
  198. package/dist/telemetry/queue.js.map +1 -0
  199. package/dist/telemetry/send.d.ts +19 -0
  200. package/dist/telemetry/send.d.ts.map +1 -0
  201. package/dist/telemetry/send.js +84 -0
  202. package/dist/telemetry/send.js.map +1 -0
  203. package/dist/telemetry/sessions.d.ts +8 -0
  204. package/dist/telemetry/sessions.d.ts.map +1 -0
  205. package/dist/telemetry/sessions.js +81 -0
  206. package/dist/telemetry/sessions.js.map +1 -0
  207. package/dist/telemetry/track.d.ts +43 -0
  208. package/dist/telemetry/track.d.ts.map +1 -0
  209. package/dist/telemetry/track.js +108 -0
  210. package/dist/telemetry/track.js.map +1 -0
  211. package/dist/upkeep-run.d.ts +15 -0
  212. package/dist/upkeep-run.d.ts.map +1 -0
  213. package/dist/upkeep-run.js +60 -0
  214. package/dist/upkeep-run.js.map +1 -0
  215. package/dist/upkeep.d.ts +114 -0
  216. package/dist/upkeep.d.ts.map +1 -0
  217. package/dist/upkeep.js +232 -0
  218. package/dist/upkeep.js.map +1 -0
  219. package/dist/util/num.d.ts +3 -0
  220. package/dist/util/num.d.ts.map +1 -0
  221. package/dist/util/num.js +20 -0
  222. package/dist/util/num.js.map +1 -0
  223. package/dist/util/state.d.ts +11 -4
  224. package/dist/util/state.d.ts.map +1 -1
  225. package/dist/util/state.js +9 -0
  226. package/dist/util/state.js.map +1 -1
  227. package/dist/viewer/app.js +1 -1
  228. package/dist/viewer/index.html +1 -0
  229. package/dist/viewer/style.css +23 -2
  230. package/dist/viz/assemble.d.ts +33 -0
  231. package/dist/viz/assemble.d.ts.map +1 -1
  232. package/dist/viz/assemble.js.map +1 -1
  233. package/dist/viz/export.d.ts +39 -0
  234. package/dist/viz/export.d.ts.map +1 -0
  235. package/dist/viz/export.js +98 -0
  236. package/dist/viz/export.js.map +1 -0
  237. package/package.json +15 -5
  238. package/scripts/stamp-telemetry-key.mjs +53 -0
@@ -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":"AAoCA;;;;;;;;;GASG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;kEACkE;AAClE,eAAO,MAAM,aAAa,QAAsB,CAAC;AAMjD;;;8CAG8C;AAC9C,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5D;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAGtF;AAMD,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;sFACkF;IAClF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;mFACmF;AACnF,wBAAgB,eAAe,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEhE;AAED,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAEjE;AAMD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,WAAW,CAK/E;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,EAAE,GAAG,SAAa,GAAG,OAAO,CAEjF;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO,CAcjF;AAED;0BAC0B;AAC1B,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGnG;AAMD;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,MAAM,EAAE,OAAO,CAAC;IAChB,yDAAyD;IACzD,GAAG,EAAE,OAAO,CAAC;IACb,qDAAqD;IACrD,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,eAAO,MAAM,mBAAmB,EAAE,UAAqD,CAAC;AAExF,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,UAAU,CAEhE;AAED,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB;mEAC+D;IAC/D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;4EAC4E;AAC5E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAE1D;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,GAAE,OAAO,CAAC,UAAU,CAAM,EAC9B,EAAE,SAA2B,GAC5B,IAAI,CASN;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAcnD;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;uFACmF;IACnF,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IACJ,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IACnC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACpE,GACA,aAAa,GAAG,IAAI,CAmBtB;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAM1E"}
package/dist/upkeep.js ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Self-maintenance: keeping an installed graft, and the wiring it wrote, current.
3
+ *
4
+ * Two independent kinds of staleness, both invisible to the user today:
5
+ *
6
+ * 1. **Binary staleness** — they installed once and never upgraded. `graft
7
+ * version` would tell them, but nobody runs it. So we keep a cached,
8
+ * machine-global registry answer and let every entry point surface a
9
+ * one-line nudge from it.
10
+ *
11
+ * 2. **Wiring staleness** — `graft init` copies hooks, shims, skill text and
12
+ * rule files INTO the repo. `npm i -g` replaces the binary but touches none
13
+ * of them, so a repo wired by 0.7 keeps 0.7's prompts and 0.7's hook
14
+ * timeouts forever (see the comment on `promptAskTimeout`, which exists
15
+ * only to work around exactly this). A version stamp written next to the
16
+ * graph lets any entry point notice the skew and re-run the writes.
17
+ *
18
+ * Everything here is fail-soft by construction: it runs inside hooks and inside
19
+ * the MCP server's boot path, where a throw is a broken session, and it must
20
+ * never block on the network — the registry is only ever read from cache, and
21
+ * the refresh happens in a detached child.
22
+ *
23
+ * Called from all three hosts' entry points so no editor is left out:
24
+ * • Claude Code — `session-start` hook (src/claude/hooks.ts)
25
+ * • Cursor/Codex — MCP server boot (src/mcp/server.ts), and any CLI command
26
+ */
27
+ import { existsSync, readFileSync } from 'node:fs';
28
+ import { spawn } from 'node:child_process';
29
+ import { join } from 'node:path';
30
+ import { homedir } from 'node:os';
31
+ import { readJson, writeJsonAtomic, cacheDir } from './util/state.js';
32
+ import { HOSTS } from './hosts/registry.js';
33
+ import { START } from './hosts/sections.js';
34
+ import { getNpmViewVersion, readCurrentVersion } from './cli-meta.js';
35
+ import { graftCliPath } from './claude/paths.js';
36
+ /**
37
+ * The version of the graft package this code was loaded from.
38
+ *
39
+ * Resolved from *this* module rather than the caller's: `readCurrentVersion`
40
+ * looks one level up from the module URL it's given, which lands on the package
41
+ * root for `dist/upkeep.js` but misses entirely for `dist/claude/hooks.js` and
42
+ * `dist/mcp/server.js`. Every caller asking here instead of passing its own
43
+ * `import.meta.url` is what keeps the hook and the MCP server from reading
44
+ * `0.0.0` and re-initing on every single session.
45
+ */
46
+ export function runningVersion() {
47
+ try {
48
+ return readCurrentVersion(import.meta.url);
49
+ }
50
+ catch {
51
+ return '0.0.0';
52
+ }
53
+ }
54
+ /** How long a registry answer is considered current. A day: graft ships far less
55
+ * often than that, and this is a nudge, not a security update. */
56
+ export const UPDATE_TTL_MS = 24 * 60 * 60 * 1000;
57
+ /* -------------------------------------------------------------------------- */
58
+ /* version comparison */
59
+ /* -------------------------------------------------------------------------- */
60
+ /** Numeric-dotted compare of the release part only (`1.2.3-beta.1` → `1.2.3`).
61
+ * Prerelease ordering doesn't matter here: the only question either caller asks
62
+ * is "is the thing on npm ahead of what's on disk", and a prerelease that
63
+ * compares equal simply produces no nudge. */
64
+ export function compareVersions(a, b) {
65
+ const parts = (v) => String(v).split('-')[0].split('.').map((n) => Number(n) || 0);
66
+ const pa = parts(a);
67
+ const pb = parts(b);
68
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
69
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
70
+ if (d !== 0)
71
+ return d < 0 ? -1 : 1;
72
+ }
73
+ return 0;
74
+ }
75
+ export function isNewer(candidate, current) {
76
+ if (!candidate)
77
+ return false;
78
+ return compareVersions(candidate, current) > 0;
79
+ }
80
+ /** Machine-global, not per-repo: "what's the latest graft" is one fact, and a
81
+ * dev with twelve repos should cost the registry one request a day, not twelve. */
82
+ export function updateCachePath(home = homedir()) {
83
+ return join(home, '.graft', 'update-check.json');
84
+ }
85
+ export function readUpdateCache(home) {
86
+ return readJson(updateCachePath(home));
87
+ }
88
+ function writeUpdateCache(cache, home) {
89
+ try {
90
+ writeJsonAtomic(updateCachePath(home), cache);
91
+ }
92
+ catch { /* unwritable home — skip */ }
93
+ }
94
+ /**
95
+ * The `graft _update-check` command body: hit the registry, store the answer.
96
+ * Runs in a detached child so nothing user-facing ever waits on the network.
97
+ */
98
+ export function refreshUpdateCache(home, now = Date.now()) {
99
+ const res = getNpmViewVersion();
100
+ const cache = { latest: res.ok ? (res.version ?? null) : null, checkedAt: now };
101
+ writeUpdateCache(cache, home);
102
+ return cache;
103
+ }
104
+ /** True when the cached answer is missing or older than the TTL. */
105
+ export function needsRefresh(cache, now = Date.now()) {
106
+ return !cache || typeof cache.checkedAt !== 'number' || now - cache.checkedAt >= UPDATE_TTL_MS;
107
+ }
108
+ /**
109
+ * Kick off a background registry check if the cache has gone stale. Touches
110
+ * `checkedAt` first so concurrent callers (and a child that dies) don't spawn a
111
+ * fetch per invocation. Never waits, never throws.
112
+ *
113
+ * Only called from long-lived or already-slow contexts (a CLI command, MCP
114
+ * boot) — never from a hook, which reads the cache and nothing else.
115
+ */
116
+ export function maybeRefreshInBackground(home, now = Date.now()) {
117
+ const cache = readUpdateCache(home);
118
+ if (!needsRefresh(cache, now))
119
+ return false;
120
+ writeUpdateCache({ latest: cache?.latest ?? null, checkedAt: now }, home);
121
+ try {
122
+ const child = spawn(process.execPath, [graftCliPath(), '_update-check'], {
123
+ detached: true,
124
+ stdio: 'ignore',
125
+ });
126
+ child.unref();
127
+ return true;
128
+ }
129
+ catch {
130
+ return false; // no spawn (sandbox, ENOMEM) — the nudge just uses the old cache
131
+ }
132
+ }
133
+ /** One line, or nothing. Nothing is the common case — don't spend context on
134
+ * "you're up to date". */
135
+ export function formatUpdateNudge(current, latest) {
136
+ if (!isNewer(latest, current))
137
+ return null;
138
+ return `⬆ graft ${current} → ${latest} available: run \`npm i -g @nanonets/graft@latest\` (restart your agent after).`;
139
+ }
140
+ export const DEFAULT_WIRING_OPTS = { global: true, mcp: true, hooks: true };
141
+ /** An older stamp has no `opts`; a plain `graft init` wired everything. */
142
+ export function wiringOpts(stamp) {
143
+ return { ...DEFAULT_WIRING_OPTS, ...(stamp?.opts ?? {}) };
144
+ }
145
+ /** Under `graft/.cache/`, beside the other derived state: git-ignored, per-clone,
146
+ * and cheap to lose — a missing stamp just means one idempotent refresh. */
147
+ export function stampPath(repo) {
148
+ return join(cacheDir(repo), 'wiring-stamp.json');
149
+ }
150
+ export function readStamp(repo) {
151
+ return readJson(stampPath(repo));
152
+ }
153
+ export function writeStamp(repo, version, hosts, opts = {}, at = new Date().toISOString()) {
154
+ try {
155
+ writeJsonAtomic(stampPath(repo), {
156
+ version,
157
+ hosts: [...hosts].sort(),
158
+ opts: { ...DEFAULT_WIRING_OPTS, ...opts },
159
+ at,
160
+ });
161
+ }
162
+ catch { /* unwritable graft/ — a refresh will just be retried next session */ }
163
+ }
164
+ /**
165
+ * Which agents this repo is *already* wired for, read off disk rather than
166
+ * re-detected. Detection answers "which editors does this machine have"; for a
167
+ * refresh we need "which files did a previous init actually write" — otherwise
168
+ * installing Windsurf once would silently add graft rules to every repo.
169
+ */
170
+ export function wiredHostIds(repo) {
171
+ const ids = [];
172
+ if (existsSync(join(repo, '.claude', 'helpers', 'graft-hooks.cjs')))
173
+ ids.push('claude');
174
+ for (const host of HOSTS) {
175
+ const path = join(repo, host.relPath);
176
+ if (!existsSync(path))
177
+ continue;
178
+ // A shared file (AGENTS.md, GEMINI.md) counts only if graft's fenced section
179
+ // is in it — the user may own the file for entirely unrelated reasons.
180
+ if (host.kind === 'section') {
181
+ try {
182
+ if (!readFileSync(path, 'utf8').includes(START))
183
+ continue;
184
+ }
185
+ catch {
186
+ continue;
187
+ }
188
+ }
189
+ ids.push(host.id);
190
+ }
191
+ return ids;
192
+ }
193
+ /**
194
+ * Re-run init's writes when the stamp and the running binary disagree.
195
+ *
196
+ * Deliberately narrow: it refreshes the hosts already wired, replays the flags
197
+ * that init was given, never builds the graph (this runs at session start — a
198
+ * rebuild there would stall the agent's first turn), and no-ops when the repo has
199
+ * no graft wiring at all.
200
+ */
201
+ export function reconcileWiring(repo, current, deps) {
202
+ try {
203
+ const stamp = readStamp(repo);
204
+ if (stamp && stamp.version === current)
205
+ return null;
206
+ // The stamp is the record of *intent* (what the picker chose); disk is the
207
+ // fallback for repos wired before stamps existed. Union, not just disk:
208
+ // otherwise a host whose rule file went missing — deleted by hand, lost to a
209
+ // merge, or clobbered by another tool — is silently dropped from every future
210
+ // refresh, and that file is precisely what a refresh exists to restore.
211
+ const onDisk = (deps.wired ?? wiredHostIds)(repo);
212
+ const hosts = [...new Set([...(stamp?.hosts ?? []), ...onDisk])].sort();
213
+ if (hosts.length === 0)
214
+ return null; // never wired here — not our business
215
+ const opts = wiringOpts(stamp);
216
+ deps.rewrite(repo, hosts, opts);
217
+ writeStamp(repo, current, hosts, opts);
218
+ return { from: stamp?.version ?? 'unwired', to: current, hosts, global: opts.global };
219
+ }
220
+ catch {
221
+ return null; // a refresh is an optimization; never fail the caller over it
222
+ }
223
+ }
224
+ export function formatWiringRefresh(r) {
225
+ if (!r)
226
+ return null;
227
+ // Name the out-of-repo writes explicitly: those are machine-wide and shared by
228
+ // every repo, so a user seeing this line should not have to guess what moved.
229
+ const scope = r.global && r.hosts.includes('agents') ? " (including this machine's ~/.codex config)" : '';
230
+ return `· graft refreshed this repo's agent wiring${scope} (written by ${r.from}, now ${r.to}): ${r.hosts.join(', ')}.`;
231
+ }
232
+ //# sourceMappingURL=upkeep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upkeep.js","sourceRoot":"","sources":["../src/upkeep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Thousands-separated integer, identical on every machine. */
2
+ export declare function formatCount(n: number): string;
3
+ //# sourceMappingURL=num.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"num.d.ts","sourceRoot":"","sources":["../../src/util/num.ts"],"names":[],"mappings":"AAgBA,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Number formatting for text graft emits.
3
+ *
4
+ * `toLocaleString()` with no locale follows the MACHINE's locale, which is the
5
+ * wrong authority for two of graft's outputs. The savings lines are read by an
6
+ * LLM and explicitly ask it to sum them across calls; on a pt-BR/de-DE box the
7
+ * bare call renders 1990 as "1.990", which an agent reads as 1.99 — so the
8
+ * headline "tokens saved this turn" total comes out ~1000× low. The graph's own
9
+ * stats are compared across machines and CI, where a separator that moves with
10
+ * the host makes two identical runs look different.
11
+ *
12
+ * One pinned locale for both: the output is a data interchange format that
13
+ * happens to be human-readable, not localized UI.
14
+ */
15
+ const FORMATTER = new Intl.NumberFormat("en-US");
16
+ /** Thousands-separated integer, identical on every machine. */
17
+ export function formatCount(n) {
18
+ return FORMATTER.format(Math.round(n));
19
+ }
20
+ //# sourceMappingURL=num.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"num.js","sourceRoot":"","sources":["../../src/util/num.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;AAEjD,+DAA+D;AAC/D,MAAM,UAAU,WAAW,CAAC,CAAS;IACnC,OAAO,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC"}
@@ -32,16 +32,18 @@ export declare function writeJsonAtomic(p: string, value: unknown, compact?: boo
32
32
  export declare function readStats(d: string): Stats | null;
33
33
  export declare function writeStats(d: string, s: Stats): void;
34
34
  /**
35
- * Persisted per-repo build configuration — currently just `graft build
36
- * --include-dir`'s override. An explicit input like the graph itself: absent
37
- * or empty `includeDirs` means today's default (every `SKIP_DIRS` name stays
38
- * skipped), so a repo that never used the flag is entirely unaffected.
35
+ * Persisted per-repo build configuration. Explicit CLI choices live here so
36
+ * later no-flag builds and automatic refreshes enumerate the same file set.
37
+ * Missing fields always retain backwards-compatible defaults.
39
38
  */
40
39
  export interface BuildConfig {
41
40
  /** SKIP_DIRS names to include in this repo's walks, persisted so a LATER
42
41
  * no-flag build — and the fingerprint/refresh path, which never sees CLI
43
42
  * flags at all — behave identically to the invocation that set it. */
44
43
  includeDirs?: string[];
44
+ /** Whether initialized Git submodules are folded into this repo's graph.
45
+ * Absent/false keeps the historical boundary at the superproject. */
46
+ followSubmodules?: boolean;
45
47
  }
46
48
  /** Local, Git-ignored repository configuration. Kept outside generated
47
49
  * `graft/` output so deleting/replacing that cache, workspace federation, and
@@ -50,12 +52,17 @@ export declare const BUILD_CONFIG_DIR = ".graft";
50
52
  export declare function buildConfigPath(d: string): string;
51
53
  export declare function readBuildConfig(d: string): BuildConfig | null;
52
54
  export declare function writeBuildConfig(d: string, c: BuildConfig): void;
55
+ /** Merge explicit CLI choices into the existing local config, so updating one
56
+ * persisted build option cannot erase another. */
57
+ export declare function patchBuildConfig(d: string, patch: BuildConfig): void;
53
58
  /** The persisted `--include-dir` override for repo `d`, as a Set — `undefined`
54
59
  * when nothing was ever persisted (or the persisted list is empty), which every
55
60
  * `shouldSkipDir`/`walkDir` caller treats as "today's default behavior". Shared
56
61
  * by every walkDir-driven entry point (source-files.ts, scopes.ts) so a build,
57
62
  * a later no-flag rebuild, and the hooks/refresh path all agree. */
58
63
  export declare function readIncludeDirs(d: string): Set<string> | undefined;
64
+ /** Missing and explicit false both retain the backwards-compatible default. */
65
+ export declare function readFollowSubmodules(d: string): boolean;
59
66
  export declare function patchStats(d: string, patch: Partial<Stats>): Stats;
60
67
  export declare function acquireLock(d: string): boolean;
61
68
  export declare function releaseLock(d: string): void;