memtrace 0.3.42 → 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.
- package/bin/memtrace.js +6 -1
- package/install.js +8 -0
- package/lib/rtk-integration.js +141 -6
- package/package.json +4 -4
package/bin/memtrace.js
CHANGED
|
@@ -138,12 +138,17 @@ if (args[0] === "install" || args[0] === "update" || args[0] === "upgrade") {
|
|
|
138
138
|
const memtraceCmd = platformBinary("memtrace", process.platform);
|
|
139
139
|
|
|
140
140
|
process.stdout.write("memtrace: fetching latest from npm registry…\n");
|
|
141
|
+
// Tell the postinstall RTK opt-in to stay silent — *we* own the
|
|
142
|
+
// prompt and have a clean TTY (npm's progress spinner shares the
|
|
143
|
+
// user's terminal with the postinstall, which makes its prompt
|
|
144
|
+
// unreadable and readline picks up a phantom EOF, default-yes
|
|
145
|
+
// installing RTK without the user seeing the question).
|
|
141
146
|
const installResult = spawnSync(
|
|
142
147
|
npmCmd,
|
|
143
148
|
["install", "-g", "memtrace@latest"],
|
|
144
149
|
spawnOptionsForPlatform(process.platform, {
|
|
145
150
|
stdio: "inherit",
|
|
146
|
-
env: process.env,
|
|
151
|
+
env: { ...process.env, MEMTRACE_INSTALL_PARENT: "1" },
|
|
147
152
|
})
|
|
148
153
|
);
|
|
149
154
|
|
package/install.js
CHANGED
|
@@ -197,6 +197,14 @@ if (require.main === module) {
|
|
|
197
197
|
const readline = require("readline");
|
|
198
198
|
const rtk = require("./lib/rtk-integration");
|
|
199
199
|
|
|
200
|
+
// Nested under `memtrace install` shim? The parent owns the prompt
|
|
201
|
+
// and has a clean TTY. Two prompt paths racing over the same
|
|
202
|
+
// terminal means npm's progress spinner overwrites the question
|
|
203
|
+
// line and readline observes a phantom EOF — user gets RTK
|
|
204
|
+
// installed without ever seeing or answering Y/n. Bail out here;
|
|
205
|
+
// bin/memtrace.js will run the prompt cleanly after npm exits.
|
|
206
|
+
if (rtk.isPostinstallNestedUnderShim({ env: process.env })) return;
|
|
207
|
+
|
|
200
208
|
let alreadyInstalled = false;
|
|
201
209
|
try { alreadyInstalled = rtk.detectRtk(); } catch (_) { /* skip */ }
|
|
202
210
|
|
package/lib/rtk-integration.js
CHANGED
|
@@ -53,6 +53,23 @@ function isRtkAutoInstall(input = {}) {
|
|
|
53
53
|
return isTruthyEnv(env.MEMTRACE_AUTO_INSTALL_RTK);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* True when this postinstall is running nested under the `memtrace
|
|
58
|
+
* install` shim — i.e. the parent process is bin/memtrace.js spawning
|
|
59
|
+
* `npm install -g memtrace@latest`. The parent owns user interaction
|
|
60
|
+
* (it has a clean TTY, and it runs its own RTK opt-in flow after npm
|
|
61
|
+
* exits). The nested postinstall must therefore stay completely silent
|
|
62
|
+
* for RTK — otherwise both prompt paths race over the same terminal,
|
|
63
|
+
* the spinner overwrites the prompt, and readline picks up a phantom
|
|
64
|
+
* EOF.
|
|
65
|
+
*
|
|
66
|
+
* Coordinated via `MEMTRACE_INSTALL_PARENT=1` in the spawn env.
|
|
67
|
+
*/
|
|
68
|
+
function isPostinstallNestedUnderShim(input = {}) {
|
|
69
|
+
const env = input.env || {};
|
|
70
|
+
return isTruthyEnv(env.MEMTRACE_INSTALL_PARENT);
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
// ── Decision helpers ────────────────────────────────────────────────
|
|
57
74
|
|
|
58
75
|
/**
|
|
@@ -186,6 +203,91 @@ function detectHomebrew(opts = {}) {
|
|
|
186
203
|
return r.status === 0;
|
|
187
204
|
}
|
|
188
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
|
+
|
|
189
291
|
/**
|
|
190
292
|
* Side-effecting RTK installer. Spawns the chosen install command,
|
|
191
293
|
* inheriting stdio so the user sees progress. Returns
|
|
@@ -229,7 +331,28 @@ function installRtk(opts = {}) {
|
|
|
229
331
|
};
|
|
230
332
|
}
|
|
231
333
|
|
|
232
|
-
|
|
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
|
+
};
|
|
233
356
|
}
|
|
234
357
|
|
|
235
358
|
/**
|
|
@@ -275,19 +398,27 @@ function openTtyStreams(opts = {}) {
|
|
|
275
398
|
const fs = opts.fs || require("fs");
|
|
276
399
|
const tty = opts.tty || require("tty");
|
|
277
400
|
|
|
278
|
-
|
|
401
|
+
// Open TWO fds — one for read, one for write. Sharing a single
|
|
402
|
+
// r+ fd between ReadStream and WriteStream causes readline to
|
|
403
|
+
// immediately observe EOF on some setups (notably when a parent
|
|
404
|
+
// process is also writing progress to the same controlling tty,
|
|
405
|
+
// e.g. npm's spinner during `memtrace install`).
|
|
406
|
+
let inFd, outFd;
|
|
279
407
|
try {
|
|
280
|
-
|
|
408
|
+
inFd = fs.openSync("/dev/tty", "r");
|
|
409
|
+
outFd = fs.openSync("/dev/tty", "w");
|
|
281
410
|
} catch (_) {
|
|
411
|
+
if (inFd != null) { try { fs.closeSync(inFd); } catch (_) {} }
|
|
282
412
|
return null; // no controlling tty (Docker, CI, etc.)
|
|
283
413
|
}
|
|
284
414
|
|
|
285
415
|
let input, output;
|
|
286
416
|
try {
|
|
287
|
-
input = new tty.ReadStream(
|
|
288
|
-
output = new tty.WriteStream(
|
|
417
|
+
input = new tty.ReadStream(inFd);
|
|
418
|
+
output = new tty.WriteStream(outFd);
|
|
289
419
|
} catch (e) {
|
|
290
|
-
try { fs.closeSync(
|
|
420
|
+
try { fs.closeSync(inFd); } catch (_) {}
|
|
421
|
+
try { fs.closeSync(outFd); } catch (_) {}
|
|
291
422
|
return null;
|
|
292
423
|
}
|
|
293
424
|
|
|
@@ -341,9 +472,12 @@ module.exports = {
|
|
|
341
472
|
// Pure helpers
|
|
342
473
|
isRtkPromptDisabled,
|
|
343
474
|
isRtkAutoInstall,
|
|
475
|
+
isPostinstallNestedUnderShim,
|
|
344
476
|
shouldPromptForRtk,
|
|
345
477
|
effectiveRtkAction,
|
|
346
478
|
chooseInstallStrategy,
|
|
479
|
+
chooseRtkInitCommand,
|
|
480
|
+
isRtkInitAlreadyDone,
|
|
347
481
|
rtkHintLine,
|
|
348
482
|
parseRtkAnswer,
|
|
349
483
|
rtkPromptText,
|
|
@@ -351,6 +485,7 @@ module.exports = {
|
|
|
351
485
|
detectRtk,
|
|
352
486
|
detectHomebrew,
|
|
353
487
|
installRtk,
|
|
488
|
+
runRtkInit,
|
|
354
489
|
openTtyStreams,
|
|
355
490
|
// Constants
|
|
356
491
|
RTK_INSTALL_URL,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memtrace",
|
|
3
|
-
"version": "0.3.
|
|
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.
|
|
43
|
-
"@memtrace/linux-x64": "0.3.
|
|
44
|
-
"@memtrace/win32-x64": "0.3.
|
|
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"
|