memtrace 0.3.43 → 0.3.44

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.
@@ -203,6 +203,91 @@ function detectHomebrew(opts = {}) {
203
203
  return r.status === 0;
204
204
  }
205
205
 
206
+ // ── rtk init (hook activation) ──────────────────────────────────────
207
+ //
208
+ // `brew install rtk` (or curl|sh) only installs the binary. Without
209
+ // `rtk init -g`, RTK is dormant — Claude Code does NOT auto-rewrite
210
+ // commands through the hook, so the user sees zero token savings.
211
+ // To deliver "maximum token savings" we must chain rtk init right
212
+ // after the binary install.
213
+ //
214
+ // rtk init -g --auto-patch:
215
+ // - installs hook to ~/.claude/hooks/rtk-rewrite.sh
216
+ // - creates ~/.claude/RTK.md (10-line breadcrumb)
217
+ // - adds @RTK.md reference to ~/.claude/CLAUDE.md
218
+ // - patches ~/.claude/settings.json's hooks (with .bak)
219
+ //
220
+ // The CLAUDE.md / settings.json edits coexist with memtrace's own
221
+ // blocks via the sentinel-installer contract (see test/uninstall-
222
+ // cleanliness.test.js's 7 coexistence tests).
223
+
224
+ /**
225
+ * Choose the rtk init command to run, or null to skip.
226
+ *
227
+ * Env-var overrides:
228
+ * MEMTRACE_NO_RTK_INIT=1 → skip init entirely (binary only)
229
+ * MEMTRACE_RTK_INIT_MODE → "global" (default) | "hook-only" | "local"
230
+ */
231
+ function chooseRtkInitCommand(input = {}) {
232
+ const env = (input && input.env) || {};
233
+ if (isTruthyEnv(env.MEMTRACE_NO_RTK_INIT)) return null;
234
+
235
+ const mode = (env.MEMTRACE_RTK_INIT_MODE || "global").trim().toLowerCase();
236
+ switch (mode) {
237
+ case "local":
238
+ return ["rtk", "init", "--auto-patch"];
239
+ case "hook-only":
240
+ return ["rtk", "init", "-g", "--hook-only", "--auto-patch"];
241
+ case "global":
242
+ default:
243
+ return ["rtk", "init", "-g", "--auto-patch"];
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Pure check: has rtk init already been run? Detects by looking for
249
+ * the two artefacts rtk init -g writes. Avoids re-running on every
250
+ * memtrace upgrade.
251
+ */
252
+ function isRtkInitAlreadyDone(input = {}) {
253
+ const home = input && input.home;
254
+ if (!home) return false;
255
+ const fs = (input && input.fs) || require("fs");
256
+ const path = (input && input.path) || require("path");
257
+ const hookPath = path.join(home, ".claude", "hooks", "rtk-rewrite.sh");
258
+ const rtkMdPath = path.join(home, ".claude", "RTK.md");
259
+ return fs.existsSync(hookPath) && fs.existsSync(rtkMdPath);
260
+ }
261
+
262
+ /**
263
+ * Side-effecting: run `rtk init` with the chosen flags. Returns
264
+ * { ok: boolean, ranCommand?: string[], error?: string, skipped?: string }
265
+ */
266
+ function runRtkInit(opts = {}) {
267
+ const env = opts.env || process.env;
268
+ const home = opts.home || require("os").homedir();
269
+ const spawnSync = opts.spawnSync || require("child_process").spawnSync;
270
+
271
+ const cmd = chooseRtkInitCommand({ env });
272
+ if (!cmd) {
273
+ return { ok: true, skipped: "MEMTRACE_NO_RTK_INIT" };
274
+ }
275
+ if (isRtkInitAlreadyDone({ home })) {
276
+ return { ok: true, skipped: "already-initialized" };
277
+ }
278
+
279
+ const [bin, ...args] = cmd;
280
+ const r = spawnSync(bin, args, { stdio: "inherit", timeout: 30000 });
281
+ if (r.status !== 0) {
282
+ return {
283
+ ok: false,
284
+ ranCommand: cmd,
285
+ error: `${bin} ${args.join(" ")} exited with status ${r.status}`,
286
+ };
287
+ }
288
+ return { ok: true, ranCommand: cmd };
289
+ }
290
+
206
291
  /**
207
292
  * Side-effecting RTK installer. Spawns the chosen install command,
208
293
  * inheriting stdio so the user sees progress. Returns
@@ -246,7 +331,28 @@ function installRtk(opts = {}) {
246
331
  };
247
332
  }
248
333
 
249
- return { ok: true, method: strategy.method };
334
+ // CRITICAL: run rtk init -g --auto-patch so the hook actually
335
+ // activates. Without this, the binary is installed but RTK is
336
+ // dormant — Claude Code does not auto-rewrite anything and the
337
+ // user gets zero token savings. This is what delivers the
338
+ // "maximum token savings" promise of the opt-in.
339
+ const initResult = runRtkInit({ spawnSync });
340
+ if (!initResult.ok) {
341
+ return {
342
+ ok: false,
343
+ method: strategy.method,
344
+ error:
345
+ `binary installed but \`rtk init\` failed: ${initResult.error}. ` +
346
+ `Run manually: rtk init -g --auto-patch`,
347
+ };
348
+ }
349
+
350
+ return {
351
+ ok: true,
352
+ method: strategy.method,
353
+ initSkipped: initResult.skipped, // "already-initialized" | "MEMTRACE_NO_RTK_INIT" | undefined
354
+ initCommand: initResult.ranCommand,
355
+ };
250
356
  }
251
357
 
252
358
  /**
@@ -370,6 +476,8 @@ module.exports = {
370
476
  shouldPromptForRtk,
371
477
  effectiveRtkAction,
372
478
  chooseInstallStrategy,
479
+ chooseRtkInitCommand,
480
+ isRtkInitAlreadyDone,
373
481
  rtkHintLine,
374
482
  parseRtkAnswer,
375
483
  rtkPromptText,
@@ -377,6 +485,7 @@ module.exports = {
377
485
  detectRtk,
378
486
  detectHomebrew,
379
487
  installRtk,
488
+ runRtkInit,
380
489
  openTtyStreams,
381
490
  // Constants
382
491
  RTK_INSTALL_URL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memtrace",
3
- "version": "0.3.43",
3
+ "version": "0.3.44",
4
4
  "description": "Code intelligence graph — MCP server + AI agent skills + visualization UI",
5
5
  "keywords": [
6
6
  "mcp",
@@ -39,9 +39,9 @@
39
39
  "fs-extra": "^11.0.0"
40
40
  },
41
41
  "optionalDependencies": {
42
- "@memtrace/darwin-arm64": "0.3.42",
43
- "@memtrace/linux-x64": "0.3.42",
44
- "@memtrace/win32-x64": "0.3.42"
42
+ "@memtrace/darwin-arm64": "0.3.44",
43
+ "@memtrace/linux-x64": "0.3.44",
44
+ "@memtrace/win32-x64": "0.3.44"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=18"