memtrace 0.3.80 → 0.3.85
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 +39 -2
- package/hooks/userprompt-claude.sh +159 -2
- package/install.js +20 -0
- package/lib/exit-code.js +99 -0
- package/package.json +6 -6
package/bin/memtrace.js
CHANGED
|
@@ -10,6 +10,12 @@ const { getBinaryPath } = require("../install.js");
|
|
|
10
10
|
const { platformBinary, spawnOptionsForPlatform } = require("../lib/spawn-helper");
|
|
11
11
|
const { shouldPromptForUpgrade, isPromptDisabled } = require("../lib/update-prompt");
|
|
12
12
|
const { fetchLatestVersion, readCachedVersion } = require("../lib/update-check");
|
|
13
|
+
// ─── Honest-lifecycle (Leaf B / fortress-round-1 T5) ──────────────────────
|
|
14
|
+
// Pure exit-code translator — pins child(0) → 0, child(75) → 75,
|
|
15
|
+
// SIGKILL → 137, SIGTERM → 143, SIGINT → 130. Pre-fix the shim
|
|
16
|
+
// translated SIGKILL → 1 (mcp branch) or → 0 (sync branch) which
|
|
17
|
+
// is the silent-disappearance bug from /tmp/mt-debug/exit.
|
|
18
|
+
const { propagateChildExit } = require("../lib/exit-code");
|
|
13
19
|
|
|
14
20
|
// ── Handle `memtrace uninstall` before delegating to the Rust binary ────────
|
|
15
21
|
// npm v7+ does NOT fire preuninstall hooks for global packages (npm/cli#3042).
|
|
@@ -37,6 +43,29 @@ if (args[0] === "install" || args[0] === "update" || args[0] === "upgrade") {
|
|
|
37
43
|
const npmCmd = platformBinary("npm", process.platform);
|
|
38
44
|
const memtraceCmd = platformBinary("memtrace", process.platform);
|
|
39
45
|
|
|
46
|
+
// ── Pre-commit hook drift check (agent-precommit-optin, 2026-05-08) ──
|
|
47
|
+
// `memtrace install` is the natural place a user re-runs after an
|
|
48
|
+
// upgrade. If a prior version installed the pre-commit hook (now
|
|
49
|
+
// opt-in by default), surface a one-line note so partners hitting
|
|
50
|
+
// the ~2 min agentic-pipeline tax know the off-ramp. We DO NOT
|
|
51
|
+
// remove the hook automatically — that's the user's call.
|
|
52
|
+
try {
|
|
53
|
+
const cwdHook = path.join(process.cwd(), ".git", "hooks", "pre-commit");
|
|
54
|
+
if (fs.existsSync(cwdHook)) {
|
|
55
|
+
const body = fs.readFileSync(cwdHook, "utf8");
|
|
56
|
+
// Match the canonical BEGIN marker we ship in hooks.rs.
|
|
57
|
+
if (body.includes("BEGIN MEMTRACE BLOCK")) {
|
|
58
|
+
process.stdout.write(
|
|
59
|
+
"memtrace: pre-commit hook is installed in this repo — " +
|
|
60
|
+
"uninstall with `memtrace uninstall-hooks --pre-commit` " +
|
|
61
|
+
"if it's slowing your agentic pipeline.\n"
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch (_) {
|
|
66
|
+
// Best-effort — never fail the install on a hook-detection hiccup.
|
|
67
|
+
}
|
|
68
|
+
|
|
40
69
|
process.stdout.write("memtrace: fetching latest from npm registry…\n");
|
|
41
70
|
const installResult = spawnSync(
|
|
42
71
|
npmCmd,
|
|
@@ -269,7 +298,12 @@ if (args[0] === "mcp") {
|
|
|
269
298
|
process.exit(1);
|
|
270
299
|
});
|
|
271
300
|
child.on("exit", (code, signal) => {
|
|
272
|
-
|
|
301
|
+
// ─── Leaf B T5 ─── propagate the child's typed exit code (or
|
|
302
|
+
// signal-derived 128+sig) so CI / supervisors can grep $? and
|
|
303
|
+
// distinguish exit 75 (Phase-2 partial) from SIGKILL (137) from a
|
|
304
|
+
// clean exit (0). Pre-fix this folded SIGKILL into 1 → silent jet-
|
|
305
|
+
// sam disappearance.
|
|
306
|
+
process.exit(propagateChildExit({ code, signal }));
|
|
273
307
|
});
|
|
274
308
|
|
|
275
309
|
} else {
|
|
@@ -296,6 +330,9 @@ if (args[0] === "mcp") {
|
|
|
296
330
|
console.error(`Failed to run memtrace: ${result.error.message}`);
|
|
297
331
|
process.exit(1);
|
|
298
332
|
}
|
|
299
|
-
|
|
333
|
+
// ─── Leaf B T5 ─── same propagation contract as the mcp branch.
|
|
334
|
+
// `spawnSync` returns `{status, signal}` — `status` is null when
|
|
335
|
+
// the child was killed; `?? 0` would silently drop signal info.
|
|
336
|
+
process.exit(propagateChildExit({ code: result.status, signal: result.signal }));
|
|
300
337
|
})();
|
|
301
338
|
}
|
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
# not on which file the model chose to grep
|
|
15
15
|
# - non-blocking → just adds context, never denies a tool call
|
|
16
16
|
#
|
|
17
|
+
# Per-session debounce (round-2 G4 / agent-I):
|
|
18
|
+
# The hook still fires once per user turn, but in a long Orbit-
|
|
19
|
+
# style automated session that's dozens of fires per prompt.
|
|
20
|
+
# Each fire pings the daemon health endpoint — cheap individually,
|
|
21
|
+
# death-by-thousand-cuts in aggregate. We add a per-session lock
|
|
22
|
+
# file at $HOME/.memtrace/hook-debounce/<session_id>.lock; if its
|
|
23
|
+
# mtime is within MEMTRACE_HOOK_DEBOUNCE_SECS (default 120s) we
|
|
24
|
+
# short-circuit to a no-op-but-well-formed JSON output and skip
|
|
25
|
+
# the daemon probe entirely.
|
|
26
|
+
#
|
|
17
27
|
# Exit codes:
|
|
18
28
|
# 0 : success (stdout is parsed for hook output)
|
|
19
29
|
# 2 : would block the prompt (we never want this)
|
|
@@ -22,14 +32,161 @@
|
|
|
22
32
|
# { "decision": "continue", "additionalContext": "..." }
|
|
23
33
|
#
|
|
24
34
|
# Override:
|
|
25
|
-
# MEMTRACE_HOOK_MODE=off
|
|
26
|
-
# MEMTRACE_HEALTH_URL=...
|
|
35
|
+
# MEMTRACE_HOOK_MODE=off → unconditional no-op (skips lock too)
|
|
36
|
+
# MEMTRACE_HEALTH_URL=... → custom health endpoint (default 3030)
|
|
37
|
+
# MEMTRACE_HOOK_DEBOUNCE_SECS=120 → debounce window seconds (0 disables)
|
|
38
|
+
# MEMTRACE_HOOK_DEBOUNCE_DIR=... → override lock dir (default $HOME/.memtrace/hook-debounce)
|
|
39
|
+
# CLAUDE_SESSION_ID / CLAUDE_CONVERSATION_ID → session id sources
|
|
27
40
|
#
|
|
28
41
|
set -euo pipefail
|
|
29
42
|
|
|
30
43
|
mode="${MEMTRACE_HOOK_MODE:-advisory}"
|
|
31
44
|
[[ "$mode" == "off" ]] && exit 0
|
|
32
45
|
|
|
46
|
+
# ── Session-id resolution (G4.2) ────────────────────────────────────
|
|
47
|
+
#
|
|
48
|
+
# Source priority:
|
|
49
|
+
# 1. CLAUDE_SESSION_ID env (preferred — Claude Code may set this)
|
|
50
|
+
# 2. CLAUDE_CONVERSATION_ID env (alternate naming)
|
|
51
|
+
# 3. fallback: stable hash of $PPID + parent process start time
|
|
52
|
+
# (so the same parent process tree always resolves to the same
|
|
53
|
+
# id, but a new shell parent gets its own id).
|
|
54
|
+
#
|
|
55
|
+
# Output is sanitised (only [A-Za-z0-9_-]) so it's safe to use as a
|
|
56
|
+
# filename component on every supported OS.
|
|
57
|
+
resolve_session_id() {
|
|
58
|
+
local raw=""
|
|
59
|
+
if [[ -n "${CLAUDE_SESSION_ID:-}" ]]; then
|
|
60
|
+
raw="$CLAUDE_SESSION_ID"
|
|
61
|
+
elif [[ -n "${CLAUDE_CONVERSATION_ID:-}" ]]; then
|
|
62
|
+
raw="$CLAUDE_CONVERSATION_ID"
|
|
63
|
+
else
|
|
64
|
+
# Hash of PPID + parent start-time. ps prints lstart for
|
|
65
|
+
# the parent process; combined with PPID this is stable
|
|
66
|
+
# within the parent's lifetime and changes when a new
|
|
67
|
+
# parent process is spawned.
|
|
68
|
+
local ppid="${PPID:-0}"
|
|
69
|
+
local pstart=""
|
|
70
|
+
pstart="$(ps -o lstart= -p "$ppid" 2>/dev/null || true)"
|
|
71
|
+
# Fold to a short hash so the lock file name stays short.
|
|
72
|
+
# md5/shasum availability varies; prefer shasum (POSIX-ish),
|
|
73
|
+
# fall back to a python one-liner, then to the raw string.
|
|
74
|
+
local input="ppid=${ppid};start=${pstart}"
|
|
75
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
76
|
+
raw="$(printf '%s' "$input" | shasum -a 1 | awk '{print $1}')"
|
|
77
|
+
elif command -v sha1sum >/dev/null 2>&1; then
|
|
78
|
+
raw="$(printf '%s' "$input" | sha1sum | awk '{print $1}')"
|
|
79
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
80
|
+
raw="$(printf '%s' "$input" | python3 -c '
|
|
81
|
+
import hashlib, sys
|
|
82
|
+
print(hashlib.sha1(sys.stdin.buffer.read()).hexdigest())
|
|
83
|
+
' 2>/dev/null || true)"
|
|
84
|
+
else
|
|
85
|
+
raw="$input"
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Sanitise for filesystem safety: keep only [A-Za-z0-9_-], replace
|
|
90
|
+
# everything else with `_`. Also collapse to at most 128 chars so
|
|
91
|
+
# we don't blow path-length limits.
|
|
92
|
+
local cleaned
|
|
93
|
+
cleaned="$(printf '%s' "$raw" | tr -c 'A-Za-z0-9_-' '_' | cut -c1-128)"
|
|
94
|
+
if [[ -z "$cleaned" ]]; then
|
|
95
|
+
cleaned="unknown_session"
|
|
96
|
+
fi
|
|
97
|
+
printf '%s' "$cleaned"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# ── Debounce window parsing ─────────────────────────────────────────
|
|
101
|
+
#
|
|
102
|
+
# Validates that MEMTRACE_HOOK_DEBOUNCE_SECS is a non-negative
|
|
103
|
+
# integer. Anything malformed falls back to 120s. A literal `0`
|
|
104
|
+
# disables debounce entirely (every fire proceeds).
|
|
105
|
+
parse_debounce_secs() {
|
|
106
|
+
local raw="${MEMTRACE_HOOK_DEBOUNCE_SECS:-120}"
|
|
107
|
+
if [[ "$raw" =~ ^[0-9]+$ ]]; then
|
|
108
|
+
printf '%s' "$raw"
|
|
109
|
+
else
|
|
110
|
+
printf '%s' "120"
|
|
111
|
+
fi
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# ── Orphan cleanup (G4.5) ───────────────────────────────────────────
|
|
115
|
+
#
|
|
116
|
+
# Opportunistic + bounded: at hook entry, remove lock files older
|
|
117
|
+
# than 24h, but cap how many we touch per fire so we don't stat
|
|
118
|
+
# thousands of files on every prompt. `find ... -mtime +1 -delete`
|
|
119
|
+
# is the portable form (BSD + GNU find both support it). We pipe
|
|
120
|
+
# through `head -n N` to bound the work.
|
|
121
|
+
ORPHAN_CLEANUP_MAX="${MEMTRACE_HOOK_ORPHAN_CLEANUP_MAX:-32}"
|
|
122
|
+
orphan_cleanup() {
|
|
123
|
+
local dir="$1"
|
|
124
|
+
local max="$2"
|
|
125
|
+
[[ -d "$dir" ]] || return 0
|
|
126
|
+
# `-mmin +1440` matches files modified more than 1440 minutes
|
|
127
|
+
# (24h) ago. We deliberately use mmin (not -mtime +1) because
|
|
128
|
+
# BSD `find` truncates `-mtime` to whole days then strict-
|
|
129
|
+
# compares — so a 25h-old file does NOT match `-mtime +1`. The
|
|
130
|
+
# mmin form is unambiguous on both BSD (macOS) and GNU (Linux).
|
|
131
|
+
# We list candidates, head-bound them, then rm. This avoids
|
|
132
|
+
# walking a giant directory linearly on every fire.
|
|
133
|
+
local f
|
|
134
|
+
while IFS= read -r f; do
|
|
135
|
+
[[ -z "$f" ]] && continue
|
|
136
|
+
rm -f -- "$f" 2>/dev/null || true
|
|
137
|
+
done < <(find "$dir" -maxdepth 1 -type f -name '*.lock' -mmin +1440 2>/dev/null | head -n "$max")
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# ── Lock-file gate ──────────────────────────────────────────────────
|
|
141
|
+
LOCK_DIR="${MEMTRACE_HOOK_DEBOUNCE_DIR:-${HOME:-/tmp}/.memtrace/hook-debounce}"
|
|
142
|
+
DEBOUNCE_SECS="$(parse_debounce_secs)"
|
|
143
|
+
|
|
144
|
+
# Ensure lock dir exists. If we can't create it (read-only home,
|
|
145
|
+
# permission denied, etc.) the hook still works — we just skip the
|
|
146
|
+
# debounce gate this fire.
|
|
147
|
+
mkdir -p "$LOCK_DIR" 2>/dev/null || true
|
|
148
|
+
|
|
149
|
+
# Cleanup orphans BEFORE evaluating the gate so a stale lock that's
|
|
150
|
+
# been orphaned for 24h+ doesn't accidentally suppress the fire.
|
|
151
|
+
if [[ -d "$LOCK_DIR" ]]; then
|
|
152
|
+
orphan_cleanup "$LOCK_DIR" "$ORPHAN_CLEANUP_MAX"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
SESSION_ID="$(resolve_session_id)"
|
|
156
|
+
LOCK_FILE="$LOCK_DIR/$SESSION_ID.lock"
|
|
157
|
+
|
|
158
|
+
# Debounce gate: if lock exists and is fresh, short-circuit.
|
|
159
|
+
# DEBOUNCE_SECS==0 is the explicit disable knob; we skip the gate
|
|
160
|
+
# entirely so every fire proceeds (useful for debugging & tests).
|
|
161
|
+
if (( DEBOUNCE_SECS > 0 )) && [[ -f "$LOCK_FILE" ]]; then
|
|
162
|
+
NOW=$(date +%s)
|
|
163
|
+
# `stat -f %m` is BSD/macOS, `stat -c %Y` is GNU/Linux. Try
|
|
164
|
+
# both; if neither works (extremely unusual) we treat the lock
|
|
165
|
+
# as fresh enough to suppress, on the principle that "we just
|
|
166
|
+
# touched it" is the safer default than "spam the daemon".
|
|
167
|
+
LAST="$(stat -f %m "$LOCK_FILE" 2>/dev/null || stat -c %Y "$LOCK_FILE" 2>/dev/null || printf '%s' "$NOW")"
|
|
168
|
+
if [[ "$LAST" =~ ^[0-9]+$ ]]; then
|
|
169
|
+
AGE=$((NOW - LAST))
|
|
170
|
+
if (( AGE < DEBOUNCE_SECS )); then
|
|
171
|
+
# Within debounce window → emit a well-formed no-op
|
|
172
|
+
# hook output and exit. We do NOT probe the daemon.
|
|
173
|
+
cat <<'EOF'
|
|
174
|
+
{
|
|
175
|
+
"decision": "continue",
|
|
176
|
+
"additionalContext": ""
|
|
177
|
+
}
|
|
178
|
+
EOF
|
|
179
|
+
exit 0
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# Outside the window (or first fire): touch the lock and proceed.
|
|
185
|
+
# `touch` creates the file if missing and updates mtime if present;
|
|
186
|
+
# this is the canonical "I just fired" signal for the next gate
|
|
187
|
+
# evaluation in the same session.
|
|
188
|
+
touch "$LOCK_FILE" 2>/dev/null || true
|
|
189
|
+
|
|
33
190
|
# ── Daemon liveness (portable: works on macOS/Linux/Windows-WSL) ──
|
|
34
191
|
#
|
|
35
192
|
# We use the Memtrace UI's status endpoint instead of `pgrep` so
|
package/install.js
CHANGED
|
@@ -293,6 +293,26 @@ if (require.main === module) {
|
|
|
293
293
|
} catch (e) {
|
|
294
294
|
console.warn(`memtrace: failed to persist uninstall script: ${e.message}`);
|
|
295
295
|
}
|
|
296
|
+
|
|
297
|
+
// 6. Background-daemon discovery hint (Leaf H / fortress-round-2).
|
|
298
|
+
// Operators on long-running setups (Sivant on Windows, Orbit on
|
|
299
|
+
// Linux, macOS lab hosts) shouldn't have to keep a terminal open
|
|
300
|
+
// to keep the indexer alive — point them at the subcommand. The
|
|
301
|
+
// hint is post-install only, never on update; CI systems don't
|
|
302
|
+
// benefit and we don't want to nag.
|
|
303
|
+
//
|
|
304
|
+
// The exact substring `memtrace daemon install` is contract-pinned
|
|
305
|
+
// by the F2F test row H-035
|
|
306
|
+
// (`npm_shim_post_install_prints_daemon_install_hint`). Renaming
|
|
307
|
+
// the subcommand here without updating that test is a regression.
|
|
308
|
+
try {
|
|
309
|
+
if (process.env.MEMTRACE_SUPPRESS_DAEMON_HINT !== "1") {
|
|
310
|
+
console.log(
|
|
311
|
+
"memtrace: want it to run as a background service? `memtrace daemon install` " +
|
|
312
|
+
"(macOS launchd / Linux systemd-user / Windows Service)."
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
} catch { /* best-effort — never fail install on a logging hiccup */ }
|
|
296
316
|
}
|
|
297
317
|
|
|
298
318
|
module.exports = {
|
package/lib/exit-code.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// ─── Honest-lifecycle (Leaf B / fortress-round-1 T5) ─────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// The npm shim spawns the native `memtrace` binary as a child process and
|
|
6
|
+
// MUST propagate the child's exit code (or signal-derived equivalent) so
|
|
7
|
+
// CI / supervisors can distinguish a clean exit (0), Phase-2 partial
|
|
8
|
+
// failure (75 / EX_TEMPFAIL — the new contract from cmd_start), and
|
|
9
|
+
// signal-driven exits (SIGKILL → 137, SIGINT → 130, etc.).
|
|
10
|
+
//
|
|
11
|
+
// Pre-fix shipped two divergent behaviours:
|
|
12
|
+
// * `mcp` branch: `process.exit(signal ? 1 : (code ?? 0))` —
|
|
13
|
+
// SIGKILL became exit 1, NOT 137. That's the silent-disappearance
|
|
14
|
+
// bug from /tmp/mt-debug/exit: macOS jetsam'd the embedder, the
|
|
15
|
+
// shim returned 0/1, and the user saw "memtrace silently
|
|
16
|
+
// disappeared" with no way to grep the exit code.
|
|
17
|
+
// * non-mcp `spawnSync` branch: `process.exit(result.status ?? 0)`
|
|
18
|
+
// — `result.status` is null when the child was killed by signal,
|
|
19
|
+
// and `?? 0` collapses that to a clean exit. Same bug, different
|
|
20
|
+
// surface.
|
|
21
|
+
//
|
|
22
|
+
// The contract this file pins (rows B-013 / B-014 / B-015 / B-022):
|
|
23
|
+
// * child exit(0) → shim exit 0
|
|
24
|
+
// * child exit(75) → shim exit 75
|
|
25
|
+
// * child killed by SIGKILL → shim exit 137 (= 128 + 9)
|
|
26
|
+
// * child killed by SIGTERM → shim exit 143 (= 128 + 15)
|
|
27
|
+
// * child killed by SIGINT → shim exit 130 (= 128 + 2)
|
|
28
|
+
//
|
|
29
|
+
// Pure function; safe to require from anywhere; no side effects.
|
|
30
|
+
|
|
31
|
+
// Numeric signal map for the platforms the shim runs on (POSIX). On
|
|
32
|
+
// Windows, `child.kill(signal)` is best-effort and the OS doesn't
|
|
33
|
+
// surface a real signal number — the helper falls back to 1 (the
|
|
34
|
+
// generic failure code) when the signal name isn't recognised.
|
|
35
|
+
const SIGNAL_NUMBERS = Object.freeze({
|
|
36
|
+
SIGHUP: 1,
|
|
37
|
+
SIGINT: 2,
|
|
38
|
+
SIGQUIT: 3,
|
|
39
|
+
SIGILL: 4,
|
|
40
|
+
SIGTRAP: 5,
|
|
41
|
+
SIGABRT: 6,
|
|
42
|
+
SIGBUS: 7,
|
|
43
|
+
SIGFPE: 8,
|
|
44
|
+
SIGKILL: 9,
|
|
45
|
+
SIGUSR1: 10,
|
|
46
|
+
SIGSEGV: 11,
|
|
47
|
+
SIGUSR2: 12,
|
|
48
|
+
SIGPIPE: 13,
|
|
49
|
+
SIGALRM: 14,
|
|
50
|
+
SIGTERM: 15,
|
|
51
|
+
SIGCHLD: 17,
|
|
52
|
+
SIGCONT: 18,
|
|
53
|
+
SIGSTOP: 19,
|
|
54
|
+
SIGTSTP: 20,
|
|
55
|
+
SIGTTIN: 21,
|
|
56
|
+
SIGTTOU: 22,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Pure decision: given the `(code, signal)` shape Node hands back from
|
|
61
|
+
* `child.on('exit')` or `spawnSync`'s `{status, signal}`, return the
|
|
62
|
+
* exit code the shim should pass to `process.exit`.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} args
|
|
65
|
+
* @param {number|null|undefined} args.code Numeric exit code, or null when killed by signal.
|
|
66
|
+
* @param {string|null|undefined} args.signal Signal name (e.g. "SIGKILL"), or null on clean exit.
|
|
67
|
+
* @returns {number} Exit code to forward to `process.exit`.
|
|
68
|
+
*/
|
|
69
|
+
function propagateChildExit({ code, signal } = {}) {
|
|
70
|
+
// Clean exit takes priority. Node guarantees `code` is non-null
|
|
71
|
+
// when the child exited normally, even if it exited with 0.
|
|
72
|
+
if (typeof code === "number") {
|
|
73
|
+
// Sanitise: process.exit clamps to [0, 255] anyway, but pin
|
|
74
|
+
// the contract so ill-behaved children producing > 255 don't
|
|
75
|
+
// confuse the supervisor.
|
|
76
|
+
return code & 0xff;
|
|
77
|
+
}
|
|
78
|
+
// Signal-driven exit. POSIX shells use `128 + signal_number` as
|
|
79
|
+
// the exit-status convention so callers can disambiguate "child
|
|
80
|
+
// returned 9" from "child got SIGKILL". We follow the same rule.
|
|
81
|
+
if (signal && typeof signal === "string") {
|
|
82
|
+
const n = SIGNAL_NUMBERS[signal];
|
|
83
|
+
if (typeof n === "number") {
|
|
84
|
+
return 128 + n;
|
|
85
|
+
}
|
|
86
|
+
// Unknown signal name (Windows, exotic platforms): fall back
|
|
87
|
+
// to 1 — generic failure — rather than guessing.
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
// Neither code nor signal — should not happen on Node ≥ 0.12,
|
|
91
|
+
// but pin a deterministic default so the shim never silently
|
|
92
|
+
// exits 0 on a malformed child shape.
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
propagateChildExit,
|
|
98
|
+
SIGNAL_NUMBERS,
|
|
99
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memtrace",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "v0.3.85",
|
|
4
4
|
"description": "Code intelligence graph — MCP server + AI agent skills + visualization UI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -39,11 +39,11 @@
|
|
|
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.
|
|
45
|
-
"@memtrace/linux-x64-noavx2": "0.3.
|
|
46
|
-
"@memtrace/win32-x64-noavx2": "0.3.
|
|
42
|
+
"@memtrace/darwin-arm64": "0.3.81",
|
|
43
|
+
"@memtrace/linux-x64": "0.3.81",
|
|
44
|
+
"@memtrace/win32-x64": "0.3.81",
|
|
45
|
+
"@memtrace/linux-x64-noavx2": "0.3.81",
|
|
46
|
+
"@memtrace/win32-x64-noavx2": "0.3.81"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=18"
|