@memlab/mcp-server 2.18.1 → 2.86.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 (374) hide show
  1. package/README.md +30 -16
  2. package/bin/.eslintrc.js +26 -0
  3. package/bin/memlab-cli.js +313 -0
  4. package/dist/anonymized-snapshot.d.ts +75 -0
  5. package/dist/anonymized-snapshot.d.ts.map +1 -0
  6. package/dist/anonymized-snapshot.js +101 -0
  7. package/dist/anonymized-snapshot.js.map +1 -0
  8. package/dist/artifact-classes.d.ts +86 -0
  9. package/dist/artifact-classes.d.ts.map +1 -0
  10. package/dist/artifact-classes.js +261 -0
  11. package/dist/artifact-classes.js.map +1 -0
  12. package/dist/capture-mode.d.ts +60 -0
  13. package/dist/capture-mode.d.ts.map +1 -0
  14. package/dist/capture-mode.js +39 -0
  15. package/dist/capture-mode.js.map +1 -0
  16. package/dist/eval-hints.d.ts +41 -0
  17. package/dist/eval-hints.d.ts.map +1 -0
  18. package/dist/eval-hints.js +115 -0
  19. package/dist/eval-hints.js.map +1 -0
  20. package/dist/guardrail.d.ts.map +1 -1
  21. package/dist/guardrail.js +13 -0
  22. package/dist/guardrail.js.map +1 -1
  23. package/dist/heap-budget.d.ts +32 -0
  24. package/dist/heap-budget.d.ts.map +1 -0
  25. package/dist/heap-budget.js +90 -0
  26. package/dist/heap-budget.js.map +1 -0
  27. package/dist/heap-shapes.d.ts +148 -0
  28. package/dist/heap-shapes.d.ts.map +1 -0
  29. package/dist/heap-shapes.js +193 -0
  30. package/dist/heap-shapes.js.map +1 -0
  31. package/dist/heap-state.d.ts +109 -2
  32. package/dist/heap-state.d.ts.map +1 -1
  33. package/dist/heap-state.js +194 -3
  34. package/dist/heap-state.js.map +1 -1
  35. package/dist/index.js +102 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/progress.d.ts +25 -0
  38. package/dist/progress.d.ts.map +1 -0
  39. package/dist/progress.js +46 -0
  40. package/dist/progress.js.map +1 -0
  41. package/dist/react-shapes.d.ts +42 -0
  42. package/dist/react-shapes.d.ts.map +1 -0
  43. package/dist/react-shapes.js +149 -0
  44. package/dist/react-shapes.js.map +1 -0
  45. package/dist/result-handles.d.ts +11 -0
  46. package/dist/result-handles.d.ts.map +1 -0
  47. package/dist/result-handles.js +77 -0
  48. package/dist/result-handles.js.map +1 -0
  49. package/dist/server-version.d.ts +19 -0
  50. package/dist/server-version.d.ts.map +1 -0
  51. package/dist/server-version.js +19 -0
  52. package/dist/server-version.js.map +1 -0
  53. package/dist/snapshot-borrow.d.ts +67 -0
  54. package/dist/snapshot-borrow.d.ts.map +1 -0
  55. package/dist/snapshot-borrow.js +113 -0
  56. package/dist/snapshot-borrow.js.map +1 -0
  57. package/dist/string-index.d.ts +55 -0
  58. package/dist/string-index.d.ts.map +1 -0
  59. package/dist/string-index.js +99 -0
  60. package/dist/string-index.js.map +1 -0
  61. package/dist/tool-registry.d.ts +42 -0
  62. package/dist/tool-registry.d.ts.map +1 -0
  63. package/dist/tool-registry.js +20 -0
  64. package/dist/tool-registry.js.map +1 -0
  65. package/dist/tools/analyze-run.d.ts +12 -0
  66. package/dist/tools/analyze-run.d.ts.map +1 -0
  67. package/dist/tools/analyze-run.js +404 -0
  68. package/dist/tools/analyze-run.js.map +1 -0
  69. package/dist/tools/anonymize-snapshot.d.ts +12 -0
  70. package/dist/tools/anonymize-snapshot.d.ts.map +1 -0
  71. package/dist/tools/anonymize-snapshot.js +159 -0
  72. package/dist/tools/anonymize-snapshot.js.map +1 -0
  73. package/dist/tools/app-config.d.ts +12 -0
  74. package/dist/tools/app-config.d.ts.map +1 -0
  75. package/dist/tools/app-config.js +217 -0
  76. package/dist/tools/app-config.js.map +1 -0
  77. package/dist/tools/app-heap.d.ts +30 -0
  78. package/dist/tools/app-heap.d.ts.map +1 -0
  79. package/dist/tools/app-heap.js +106 -0
  80. package/dist/tools/app-heap.js.map +1 -0
  81. package/dist/tools/artifact-budget.d.ts +12 -0
  82. package/dist/tools/artifact-budget.d.ts.map +1 -0
  83. package/dist/tools/artifact-budget.js +219 -0
  84. package/dist/tools/artifact-budget.js.map +1 -0
  85. package/dist/tools/async-census.d.ts +12 -0
  86. package/dist/tools/async-census.d.ts.map +1 -0
  87. package/dist/tools/async-census.js +243 -0
  88. package/dist/tools/async-census.js.map +1 -0
  89. package/dist/tools/auto-investigate.d.ts.map +1 -1
  90. package/dist/tools/auto-investigate.js +54 -5
  91. package/dist/tools/auto-investigate.js.map +1 -1
  92. package/dist/tools/batch.d.ts +12 -0
  93. package/dist/tools/batch.d.ts.map +1 -0
  94. package/dist/tools/batch.js +196 -0
  95. package/dist/tools/batch.js.map +1 -0
  96. package/dist/tools/cache-analysis.d.ts +15 -0
  97. package/dist/tools/cache-analysis.d.ts.map +1 -1
  98. package/dist/tools/cache-analysis.js +240 -14
  99. package/dist/tools/cache-analysis.js.map +1 -1
  100. package/dist/tools/census-diff.d.ts +55 -0
  101. package/dist/tools/census-diff.d.ts.map +1 -0
  102. package/dist/tools/census-diff.js +228 -0
  103. package/dist/tools/census-diff.js.map +1 -0
  104. package/dist/tools/chain-walk.d.ts +12 -0
  105. package/dist/tools/chain-walk.d.ts.map +1 -0
  106. package/dist/tools/chain-walk.js +184 -0
  107. package/dist/tools/chain-walk.js.map +1 -0
  108. package/dist/tools/class-histogram.d.ts.map +1 -1
  109. package/dist/tools/class-histogram.js +14 -3
  110. package/dist/tools/class-histogram.js.map +1 -1
  111. package/dist/tools/collection-diff.d.ts +44 -0
  112. package/dist/tools/collection-diff.d.ts.map +1 -0
  113. package/dist/tools/collection-diff.js +343 -0
  114. package/dist/tools/collection-diff.js.map +1 -0
  115. package/dist/tools/collection-trend.d.ts +55 -0
  116. package/dist/tools/collection-trend.d.ts.map +1 -0
  117. package/dist/tools/collection-trend.js +315 -0
  118. package/dist/tools/collection-trend.js.map +1 -0
  119. package/dist/tools/detached-dom.d.ts +5 -0
  120. package/dist/tools/detached-dom.d.ts.map +1 -1
  121. package/dist/tools/detached-dom.js +368 -22
  122. package/dist/tools/detached-dom.js.map +1 -1
  123. package/dist/tools/dev-artifacts.d.ts +62 -6
  124. package/dist/tools/dev-artifacts.d.ts.map +1 -1
  125. package/dist/tools/dev-artifacts.js +685 -24
  126. package/dist/tools/dev-artifacts.js.map +1 -1
  127. package/dist/tools/diff-snapshots.d.ts.map +1 -1
  128. package/dist/tools/diff-snapshots.js +18 -2
  129. package/dist/tools/diff-snapshots.js.map +1 -1
  130. package/dist/tools/dom-audit.d.ts +37 -0
  131. package/dist/tools/dom-audit.d.ts.map +1 -0
  132. package/dist/tools/dom-audit.js +247 -0
  133. package/dist/tools/dom-audit.js.map +1 -0
  134. package/dist/tools/dominator-attribution.d.ts +12 -0
  135. package/dist/tools/dominator-attribution.d.ts.map +1 -0
  136. package/dist/tools/dominator-attribution.js +189 -0
  137. package/dist/tools/dominator-attribution.js.map +1 -0
  138. package/dist/tools/dominator-chain.d.ts +12 -0
  139. package/dist/tools/dominator-chain.d.ts.map +1 -0
  140. package/dist/tools/dominator-chain.js +92 -0
  141. package/dist/tools/dominator-chain.js.map +1 -0
  142. package/dist/tools/duplicate-objects.d.ts +12 -0
  143. package/dist/tools/duplicate-objects.d.ts.map +1 -0
  144. package/dist/tools/duplicate-objects.js +172 -0
  145. package/dist/tools/duplicate-objects.js.map +1 -0
  146. package/dist/tools/duplicated-strings.d.ts.map +1 -1
  147. package/dist/tools/duplicated-strings.js +49 -40
  148. package/dist/tools/duplicated-strings.js.map +1 -1
  149. package/dist/tools/eval-across.d.ts +12 -0
  150. package/dist/tools/eval-across.d.ts.map +1 -0
  151. package/dist/tools/eval-across.js +135 -0
  152. package/dist/tools/eval-across.js.map +1 -0
  153. package/dist/tools/eval.d.ts +79 -0
  154. package/dist/tools/eval.d.ts.map +1 -1
  155. package/dist/tools/eval.js +2523 -197
  156. package/dist/tools/eval.js.map +1 -1
  157. package/dist/tools/event-listener-leaks.d.ts.map +1 -1
  158. package/dist/tools/event-listener-leaks.js +42 -17
  159. package/dist/tools/event-listener-leaks.js.map +1 -1
  160. package/dist/tools/event-registry.d.ts.map +1 -1
  161. package/dist/tools/event-registry.js +42 -8
  162. package/dist/tools/event-registry.js.map +1 -1
  163. package/dist/tools/explain-delta.d.ts +12 -0
  164. package/dist/tools/explain-delta.d.ts.map +1 -0
  165. package/dist/tools/explain-delta.js +221 -0
  166. package/dist/tools/explain-delta.js.map +1 -0
  167. package/dist/tools/find-nodes-by-class.d.ts.map +1 -1
  168. package/dist/tools/find-nodes-by-class.js +112 -13
  169. package/dist/tools/find-nodes-by-class.js.map +1 -1
  170. package/dist/tools/finding-index.d.ts +203 -0
  171. package/dist/tools/finding-index.d.ts.map +1 -0
  172. package/dist/tools/finding-index.js +566 -0
  173. package/dist/tools/finding-index.js.map +1 -0
  174. package/dist/tools/for-each.d.ts.map +1 -1
  175. package/dist/tools/for-each.js +15 -3
  176. package/dist/tools/for-each.js.map +1 -1
  177. package/dist/tools/get-referrers.d.ts.map +1 -1
  178. package/dist/tools/get-referrers.js +17 -0
  179. package/dist/tools/get-referrers.js.map +1 -1
  180. package/dist/tools/get-value.d.ts +16 -0
  181. package/dist/tools/get-value.d.ts.map +1 -1
  182. package/dist/tools/get-value.js +96 -2
  183. package/dist/tools/get-value.js.map +1 -1
  184. package/dist/tools/hunt-report.d.ts +12 -0
  185. package/dist/tools/hunt-report.d.ts.map +1 -0
  186. package/dist/tools/hunt-report.js +186 -0
  187. package/dist/tools/hunt-report.js.map +1 -0
  188. package/dist/tools/hypothesis.d.ts +12 -0
  189. package/dist/tools/hypothesis.d.ts.map +1 -0
  190. package/dist/tools/hypothesis.js +280 -0
  191. package/dist/tools/hypothesis.js.map +1 -0
  192. package/dist/tools/id-space-audit.d.ts +12 -0
  193. package/dist/tools/id-space-audit.d.ts.map +1 -0
  194. package/dist/tools/id-space-audit.js +262 -0
  195. package/dist/tools/id-space-audit.js.map +1 -0
  196. package/dist/tools/identify.d.ts +12 -0
  197. package/dist/tools/identify.d.ts.map +1 -0
  198. package/dist/tools/identify.js +205 -0
  199. package/dist/tools/identify.js.map +1 -0
  200. package/dist/tools/identity-diff.d.ts +12 -0
  201. package/dist/tools/identity-diff.d.ts.map +1 -0
  202. package/dist/tools/identity-diff.js +188 -0
  203. package/dist/tools/identity-diff.js.map +1 -0
  204. package/dist/tools/intern-opportunities.d.ts.map +1 -1
  205. package/dist/tools/intern-opportunities.js +116 -50
  206. package/dist/tools/intern-opportunities.js.map +1 -1
  207. package/dist/tools/ladder-probe.d.ts +65 -0
  208. package/dist/tools/ladder-probe.d.ts.map +1 -0
  209. package/dist/tools/ladder-probe.js +445 -0
  210. package/dist/tools/ladder-probe.js.map +1 -0
  211. package/dist/tools/ladder.d.ts +33 -0
  212. package/dist/tools/ladder.d.ts.map +1 -0
  213. package/dist/tools/ladder.js +266 -0
  214. package/dist/tools/ladder.js.map +1 -0
  215. package/dist/tools/largest-objects.d.ts.map +1 -1
  216. package/dist/tools/largest-objects.js +6 -2
  217. package/dist/tools/largest-objects.js.map +1 -1
  218. package/dist/tools/leak-report.d.ts +35 -0
  219. package/dist/tools/leak-report.d.ts.map +1 -0
  220. package/dist/tools/leak-report.js +398 -0
  221. package/dist/tools/leak-report.js.map +1 -0
  222. package/dist/tools/load-snapshot.d.ts +29 -0
  223. package/dist/tools/load-snapshot.d.ts.map +1 -1
  224. package/dist/tools/load-snapshot.js +294 -23
  225. package/dist/tools/load-snapshot.js.map +1 -1
  226. package/dist/tools/map-entries.d.ts +12 -0
  227. package/dist/tools/map-entries.d.ts.map +1 -0
  228. package/dist/tools/map-entries.js +139 -0
  229. package/dist/tools/map-entries.js.map +1 -0
  230. package/dist/tools/match-object.d.ts +12 -0
  231. package/dist/tools/match-object.d.ts.map +1 -0
  232. package/dist/tools/match-object.js +165 -0
  233. package/dist/tools/match-object.js.map +1 -0
  234. package/dist/tools/metric.d.ts +13 -0
  235. package/dist/tools/metric.d.ts.map +1 -0
  236. package/dist/tools/metric.js +185 -0
  237. package/dist/tools/metric.js.map +1 -0
  238. package/dist/tools/module-attribution.d.ts +36 -0
  239. package/dist/tools/module-attribution.d.ts.map +1 -0
  240. package/dist/tools/module-attribution.js +235 -0
  241. package/dist/tools/module-attribution.js.map +1 -0
  242. package/dist/tools/next-measurement.d.ts +26 -0
  243. package/dist/tools/next-measurement.d.ts.map +1 -0
  244. package/dist/tools/next-measurement.js +149 -0
  245. package/dist/tools/next-measurement.js.map +1 -0
  246. package/dist/tools/object-cost-breakdown.d.ts.map +1 -1
  247. package/dist/tools/object-cost-breakdown.js +64 -0
  248. package/dist/tools/object-cost-breakdown.js.map +1 -1
  249. package/dist/tools/object-shape.d.ts.map +1 -1
  250. package/dist/tools/object-shape.js +53 -5
  251. package/dist/tools/object-shape.js.map +1 -1
  252. package/dist/tools/population-diff.d.ts +57 -0
  253. package/dist/tools/population-diff.d.ts.map +1 -0
  254. package/dist/tools/population-diff.js +338 -0
  255. package/dist/tools/population-diff.js.map +1 -0
  256. package/dist/tools/population-vs-owners.d.ts +12 -0
  257. package/dist/tools/population-vs-owners.d.ts.map +1 -0
  258. package/dist/tools/population-vs-owners.js +183 -0
  259. package/dist/tools/population-vs-owners.js.map +1 -0
  260. package/dist/tools/property-names.d.ts +29 -0
  261. package/dist/tools/property-names.d.ts.map +1 -0
  262. package/dist/tools/property-names.js +131 -0
  263. package/dist/tools/property-names.js.map +1 -0
  264. package/dist/tools/quick-diagnosis.d.ts.map +1 -1
  265. package/dist/tools/quick-diagnosis.js +7 -1
  266. package/dist/tools/quick-diagnosis.js.map +1 -1
  267. package/dist/tools/react-owners.d.ts +12 -0
  268. package/dist/tools/react-owners.d.ts.map +1 -0
  269. package/dist/tools/react-owners.js +167 -0
  270. package/dist/tools/react-owners.js.map +1 -0
  271. package/dist/tools/react-update-queues.d.ts +12 -0
  272. package/dist/tools/react-update-queues.d.ts.map +1 -0
  273. package/dist/tools/react-update-queues.js +173 -0
  274. package/dist/tools/react-update-queues.js.map +1 -0
  275. package/dist/tools/replicate.d.ts +49 -0
  276. package/dist/tools/replicate.d.ts.map +1 -0
  277. package/dist/tools/replicate.js +241 -0
  278. package/dist/tools/replicate.js.map +1 -0
  279. package/dist/tools/retainer-diff.d.ts +12 -0
  280. package/dist/tools/retainer-diff.d.ts.map +1 -0
  281. package/dist/tools/retainer-diff.js +168 -0
  282. package/dist/tools/retainer-diff.js.map +1 -0
  283. package/dist/tools/retainer-layers.d.ts +23 -0
  284. package/dist/tools/retainer-layers.d.ts.map +1 -0
  285. package/dist/tools/retainer-layers.js +179 -0
  286. package/dist/tools/retainer-layers.js.map +1 -0
  287. package/dist/tools/retainer-summary.d.ts +18 -0
  288. package/dist/tools/retainer-summary.d.ts.map +1 -1
  289. package/dist/tools/retainer-summary.js +210 -51
  290. package/dist/tools/retainer-summary.js.map +1 -1
  291. package/dist/tools/retainer-trace.d.ts.map +1 -1
  292. package/dist/tools/retainer-trace.js +14 -2
  293. package/dist/tools/retainer-trace.js.map +1 -1
  294. package/dist/tools/round-audit.d.ts +66 -0
  295. package/dist/tools/round-audit.d.ts.map +1 -0
  296. package/dist/tools/round-audit.js +258 -0
  297. package/dist/tools/round-audit.js.map +1 -0
  298. package/dist/tools/round-digest.d.ts +12 -0
  299. package/dist/tools/round-digest.d.ts.map +1 -0
  300. package/dist/tools/round-digest.js +190 -0
  301. package/dist/tools/round-digest.js.map +1 -0
  302. package/dist/tools/script-census.d.ts +12 -0
  303. package/dist/tools/script-census.d.ts.map +1 -0
  304. package/dist/tools/script-census.js +182 -0
  305. package/dist/tools/script-census.js.map +1 -0
  306. package/dist/tools/search-nodes.d.ts.map +1 -1
  307. package/dist/tools/search-nodes.js +5 -1
  308. package/dist/tools/search-nodes.js.map +1 -1
  309. package/dist/tools/search-strings.d.ts.map +1 -1
  310. package/dist/tools/search-strings.js +53 -7
  311. package/dist/tools/search-strings.js.map +1 -1
  312. package/dist/tools/sequence-analysis.d.ts +53 -0
  313. package/dist/tools/sequence-analysis.d.ts.map +1 -1
  314. package/dist/tools/sequence-analysis.js +313 -89
  315. package/dist/tools/sequence-analysis.js.map +1 -1
  316. package/dist/tools/server-status.d.ts.map +1 -1
  317. package/dist/tools/server-status.js +4 -1
  318. package/dist/tools/server-status.js.map +1 -1
  319. package/dist/tools/settle-check.d.ts +12 -0
  320. package/dist/tools/settle-check.d.ts.map +1 -0
  321. package/dist/tools/settle-check.js +188 -0
  322. package/dist/tools/settle-check.js.map +1 -0
  323. package/dist/tools/shape-histogram.d.ts.map +1 -1
  324. package/dist/tools/shape-histogram.js +9 -2
  325. package/dist/tools/shape-histogram.js.map +1 -1
  326. package/dist/tools/sliced-strings.d.ts.map +1 -1
  327. package/dist/tools/sliced-strings.js +2 -1
  328. package/dist/tools/sliced-strings.js.map +1 -1
  329. package/dist/tools/snapshots.d.ts.map +1 -1
  330. package/dist/tools/snapshots.js +24 -6
  331. package/dist/tools/snapshots.js.map +1 -1
  332. package/dist/tools/sparse-elements.d.ts +12 -0
  333. package/dist/tools/sparse-elements.d.ts.map +1 -0
  334. package/dist/tools/sparse-elements.js +215 -0
  335. package/dist/tools/sparse-elements.js.map +1 -0
  336. package/dist/tools/stale-collections.d.ts +17 -0
  337. package/dist/tools/stale-collections.d.ts.map +1 -1
  338. package/dist/tools/stale-collections.js +28 -0
  339. package/dist/tools/stale-collections.js.map +1 -1
  340. package/dist/tools/string-patterns.d.ts.map +1 -1
  341. package/dist/tools/string-patterns.js +7 -5
  342. package/dist/tools/string-patterns.js.map +1 -1
  343. package/dist/tools/tools-index.d.ts +12 -0
  344. package/dist/tools/tools-index.d.ts.map +1 -0
  345. package/dist/tools/tools-index.js +492 -0
  346. package/dist/tools/tools-index.js.map +1 -0
  347. package/dist/tools/trace-all.d.ts +42 -0
  348. package/dist/tools/trace-all.d.ts.map +1 -0
  349. package/dist/tools/trace-all.js +263 -0
  350. package/dist/tools/trace-all.js.map +1 -0
  351. package/dist/tools/unit-cost.d.ts +12 -0
  352. package/dist/tools/unit-cost.d.ts.map +1 -0
  353. package/dist/tools/unit-cost.js +219 -0
  354. package/dist/tools/unit-cost.js.map +1 -0
  355. package/dist/tools/verify-fix.d.ts +23 -0
  356. package/dist/tools/verify-fix.d.ts.map +1 -0
  357. package/dist/tools/verify-fix.js +219 -0
  358. package/dist/tools/verify-fix.js.map +1 -0
  359. package/dist/tools/weakmap-entries.d.ts.map +1 -1
  360. package/dist/tools/weakmap-entries.js +6 -1
  361. package/dist/tools/weakmap-entries.js.map +1 -1
  362. package/dist/tools/weakref-census.d.ts +15 -0
  363. package/dist/tools/weakref-census.d.ts.map +1 -0
  364. package/dist/tools/weakref-census.js +147 -0
  365. package/dist/tools/weakref-census.js.map +1 -0
  366. package/dist/tools/what-if.d.ts +12 -0
  367. package/dist/tools/what-if.d.ts.map +1 -0
  368. package/dist/tools/what-if.js +285 -0
  369. package/dist/tools/what-if.js.map +1 -0
  370. package/dist/utils.d.ts +238 -3
  371. package/dist/utils.d.ts.map +1 -1
  372. package/dist/utils.js +507 -15
  373. package/dist/utils.js.map +1 -1
  374. package/package.json +7 -6
package/README.md CHANGED
@@ -76,21 +76,12 @@ Then configure (`~/.claude.json` for Claude Code, or `.mcp.json` for Cursor/Wind
76
76
 
77
77
  ## Auto-Approving Tool Permissions
78
78
 
79
- By default, Claude Code prompts you to approve each MCP tool call. To auto-approve all MemLab tools, add this to your `~/.claude/settings.json` (user-level) or `.claude/settings.json` (project-level):
79
+ By default, Claude Code prompts you to approve each MCP tool call. Every MemLab tool except `memlab_eval` and `memlab_for_each` is strictly read-only heap analysis and safe to auto-approve.
80
80
 
81
- ```json
82
- {
83
- "permissions": {
84
- "allow": [
85
- "mcp__memlab__*"
86
- ]
87
- }
88
- }
89
- ```
81
+ > [!CAUTION]
82
+ > **Do not auto-approve `memlab_eval` or `memlab_for_each`.** They execute arbitrary JavaScript with the full privileges of the MCP server process (see the warning under [`memlab_eval`](#memlab_eval)). Leave them on manual approval so each invocation is reviewed. Avoid the `mcp__memlab__*` wildcard, which would auto-approve them too.
90
83
 
91
- The naming convention is `mcp__<server-name>__*` where `memlab` matches the key you used in your MCP server config. The `*` wildcard auto-approves all tools from that server.
92
-
93
- You can also allowlist individual tools for granular control, add this to your `~/.claude/settings.json` (user-level) or `.claude/settings.json` (project-level):
84
+ To auto-approve the read-only tools while keeping the two code-execution tools on manual approval, allowlist the read-only tools explicitly in your `~/.claude/settings.json` (user-level) or `.claude/settings.json` (project-level):
94
85
 
95
86
  ```json
96
87
  {
@@ -105,7 +96,11 @@ You can also allowlist individual tools for granular control, add this to your `
105
96
 
106
97
  ## How It Works
107
98
 
108
- The server holds a loaded `IHeapSnapshot` in memory across tool calls (loading is expensive for large heaps). Only one snapshot can be loaded at a time. All tools are read-only — they analyze the heap but never modify it.
99
+ The server holds loaded `IHeapSnapshot` graphs in memory across tool calls (loading is
100
+ expensive for large heaps). Several snapshots can be resident at once (`keep_previous: true`);
101
+ tools read the active one, which `memlab_snapshots` switches. When a load's projected working
102
+ set exceeds the free old-space budget, the least-recently-used resident snapshot is evicted
103
+ before the load is refused. All tools are read-only — they analyze the heap but never modify it.
109
104
 
110
105
  ## Getting a Heap Snapshot
111
106
 
@@ -129,11 +124,14 @@ console.log(`Heap snapshot written to ${snapshot}`);
129
124
  ### `memlab_load_snapshot`
130
125
 
131
126
  Load and parse a `.heapsnapshot` file. Builds indexes, computes the dominator tree, and calculates retained sizes. `file_path` may be a local absolute path, a `manifold://bucket/key` URL, or a bare snapshot filename (resolved against the `nest_server_nodejs_heap_snapshots` bucket and fetched via `manifold get`). Pass `keep_previous: true` to keep earlier snapshots resident for diffing/comparison (each gets a handle; manage with `memlab_snapshots`). `quiet` / `suppress_suggestions` set session-wide output controls to trim repeated boilerplate.
127
+ Pass `light: true` for a count-only load that skips the dominator / retained-size /
128
+ shortest-path pass; tools needing retained sizes, dominators or retainer paths then return an
129
+ error naming the light-safe tools instead of reporting zeros.
132
130
 
133
131
  ```
134
132
  Input: { file_path: "snap.heapsnapshot" | "/abs/path" | "manifold://bucket/key",
135
133
  alias?: "before", keep_previous?: false, quiet?: false,
136
- suppress_suggestions?: false, max_file_size_mb?: 900 }
134
+ suppress_suggestions?: false, light?: false, max_file_size_mb?: 900 }
137
135
  Output: { status, file_path, node_count, edge_count, total_size, handle }
138
136
  ```
139
137
 
@@ -157,6 +155,19 @@ Input: { action?: "list"|"switch"|"unload", handle?: "before",
157
155
  Output: resident snapshots (active one marked), or switch/unload result
158
156
  ```
159
157
 
158
+ ### `memlab_batch`
159
+
160
+ Run several tools against ONE snapshot load and return all their outputs together, instead of
161
+ re-paying the multi-minute parse on every call. Steps run in order against the resident snapshot
162
+ and see each other's side effects. An unknown tool name is rejected before any step runs, and by
163
+ default a failing step is recorded and the batch continues.
164
+
165
+ ```
166
+ Input: { load?: { ...memlab_load_snapshot args }, steps: [{ tool, args? }],
167
+ stop_on_error?: false }
168
+ Output: a per-step output block for each step, headed by a run/failed step count
169
+ ```
170
+
160
171
  ### `memlab_property_distribution`
161
172
 
162
173
  For a class/shape and a property, report value cardinality plus the top-K most frequent values. The key tool for diagnosing cardinality explosions (OTel metric attributes, cache keys, per-record fields).
@@ -371,7 +382,10 @@ Input: { report: "list"|"full_analysis"|"detached_dom"|"duplicated_strings"|...
371
382
 
372
383
  ### `memlab_eval`
373
384
 
374
- Execute arbitrary JavaScript against the loaded heap snapshot in a sandboxed VM. Has access to `snapshot`, `utils`, and `helpers` but no filesystem/network access.
385
+ Execute arbitrary JavaScript against the loaded heap snapshot. Has access to `snapshot`, `utils`, and `helpers`.
386
+
387
+ > [!WARNING]
388
+ > **This tool runs arbitrary code with the full privileges of the MCP server process.** It uses `node:vm` to scope the globals it injects, but [`node:vm` is not a security sandbox](https://nodejs.org/api/vm.html) — code can reach the host realm (e.g. `Object.constructor('return process')()`) and from there touch the filesystem, network, and shell. Treat calling `memlab_eval` as equivalent to running code on your machine. The same applies to `memlab_for_each`, whose `filter`/`map`/`reduce` code strings are executed the same way. Do not auto-approve these two tools, and be aware that heap snapshot contents are attacker-influenceable input — a prompt-injection could steer the model into running hostile code here.
375
389
 
376
390
  ```
377
391
  Input: { code: "...", timeout_ms?: 30000 }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+
11
+ // Files in this directory are npm `bin` entry points: package.json declares
12
+ // them as executables, so POSIX requires `#!/usr/bin/env node` on line 1.
13
+ //
14
+ // `fb-www/docblock-first` treats anything preceding the file docblock as
15
+ // disqualifying, and the shebang counts — verified by removing it, at which
16
+ // point the rule stops firing. The two requirements are therefore mutually
17
+ // exclusive for this directory, and the rule's stated rationale (ESLint skips
18
+ // pragma parsing, producing spurious no-undef errors for Flow utility types)
19
+ // does not apply: these are plain CommonJS/ESM Node scripts with no Flow types.
20
+ //
21
+ // Scoped to bin/ so the rule keeps applying to every other file in the package.
22
+ module.exports = {
23
+ rules: {
24
+ 'fb-www/docblock-first': 'off',
25
+ },
26
+ };
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ /// usr/bin/env node
11
+
12
+ /**
13
+ * Non-MCP command line for the memlab MCP server.
14
+ *
15
+ * Why this exists: the server is only ever reachable through an MCP client, so
16
+ * when a host fails to attach it (a dropped stdio handshake, a plugin that never
17
+ * finishes connecting) every tool in the package becomes unavailable at once,
18
+ * with no diagnostic and no fallback. That happened in practice and cost an
19
+ * agent ~20 minutes and a hand-written JSON-RPC client before any heap could be
20
+ * analyzed at all. The server itself was healthy the whole time.
21
+ *
22
+ * This speaks the same stdio JSON-RPC the host would, so the tools stay usable
23
+ * without one:
24
+ *
25
+ * memlab-cli doctor
26
+ * memlab-cli list
27
+ * memlab-cli schema memlab_load_snapshot
28
+ * memlab-cli call memlab_snapshot_header '{"file_path":"/tmp/a.heapsnapshot"}'
29
+ * memlab-cli script steps.jsonl # one {"tool":..,"args":{..}} per line
30
+ *
31
+ * `script` (and multiple `call` pairs) reuse ONE server process, which matters:
32
+ * the server is stateful and a large snapshot costs minutes to load, so a
33
+ * process per call pays that repeatedly.
34
+ */
35
+
36
+ import {spawn} from 'child_process';
37
+ import fs from 'fs';
38
+ import path from 'path';
39
+ import {fileURLToPath} from 'url';
40
+
41
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
42
+
43
+ function resolveServerPath() {
44
+ if (process.env.MEMLAB_MCP_SERVER) {
45
+ return process.env.MEMLAB_MCP_SERVER;
46
+ }
47
+ // Packaged layout is bin/ next to dist/; the plugin copies this script to the
48
+ // install root, where dist/ is a direct child.
49
+ const candidates = [
50
+ path.join(HERE, '..', 'dist', 'index.js'),
51
+ path.join(HERE, 'dist', 'index.js'),
52
+ ];
53
+ for (const c of candidates) {
54
+ if (fs.existsSync(c)) {
55
+ return c;
56
+ }
57
+ }
58
+ return candidates[0];
59
+ }
60
+
61
+ /**
62
+ * The interpreter to run the SERVER with, which is not necessarily the one
63
+ * running this CLI: `node` on PATH is frequently older than the package's
64
+ * `>= 18` engine requirement, and spawning the server with it fails at module
65
+ * load with an opaque syntax error. Mirrors the plugin start.sh lookup.
66
+ */
67
+ function resolveNodeBin() {
68
+ if (Number(process.versions.node.split('.')[0]) >= 18) {
69
+ return process.execPath;
70
+ }
71
+ for (const c of ['/usr/local/bin/claude_code/node']) {
72
+ if (fs.existsSync(c)) {
73
+ return c;
74
+ }
75
+ }
76
+ return process.execPath;
77
+ }
78
+
79
+ class Client {
80
+ constructor(serverPath) {
81
+ this.proc = spawn(
82
+ resolveNodeBin(),
83
+ ['--max-old-space-size=8192', serverPath],
84
+ {stdio: ['pipe', 'pipe', 'pipe']},
85
+ );
86
+ this.nextId = 0;
87
+ this.buf = '';
88
+ this.pending = new Map();
89
+ this.stderr = [];
90
+ // Drain stderr. A chatty server that fills this pipe would otherwise block
91
+ // its own writes and deadlock the session.
92
+ this.proc.stderr.on('data', d => {
93
+ this.stderr.push(String(d));
94
+ if (this.stderr.length > 400) {
95
+ this.stderr.splice(0, 200);
96
+ }
97
+ });
98
+ this.proc.stdout.on('data', d => this.onData(String(d)));
99
+ this.proc.on('exit', code => {
100
+ for (const {reject} of this.pending.values()) {
101
+ reject(
102
+ new Error(
103
+ `memlab server exited (code ${code}). stderr tail:\n${this.stderr.slice(-40).join('')}`,
104
+ ),
105
+ );
106
+ }
107
+ this.pending.clear();
108
+ });
109
+ }
110
+
111
+ onData(chunk) {
112
+ this.buf += chunk;
113
+ let nl;
114
+ while ((nl = this.buf.indexOf('\n')) >= 0) {
115
+ const line = this.buf.slice(0, nl).trim();
116
+ this.buf = this.buf.slice(nl + 1);
117
+ if (!line) {
118
+ continue;
119
+ }
120
+ let msg;
121
+ try {
122
+ msg = JSON.parse(line);
123
+ } catch {
124
+ continue;
125
+ }
126
+ const entry = this.pending.get(msg.id);
127
+ if (!entry) {
128
+ continue;
129
+ }
130
+ this.pending.delete(msg.id);
131
+ if (msg.error) {
132
+ entry.reject(new Error(JSON.stringify(msg.error)));
133
+ } else {
134
+ entry.resolve(msg.result);
135
+ }
136
+ }
137
+ }
138
+
139
+ request(method, params) {
140
+ const id = ++this.nextId;
141
+ this.proc.stdin.write(
142
+ JSON.stringify({jsonrpc: '2.0', id, method, params}) + '\n',
143
+ );
144
+ return new Promise((resolve, reject) =>
145
+ this.pending.set(id, {resolve, reject}),
146
+ );
147
+ }
148
+
149
+ notify(method, params) {
150
+ this.proc.stdin.write(
151
+ JSON.stringify({jsonrpc: '2.0', method, params}) + '\n',
152
+ );
153
+ }
154
+
155
+ async init() {
156
+ await this.request('initialize', {
157
+ protocolVersion: '2024-11-05',
158
+ capabilities: {},
159
+ clientInfo: {name: 'memlab-cli', version: '1.0'},
160
+ });
161
+ this.notify('notifications/initialized', {});
162
+ }
163
+
164
+ async listTools() {
165
+ return (await this.request('tools/list', {})).tools ?? [];
166
+ }
167
+
168
+ async callTool(name, args) {
169
+ const res = await this.request('tools/call', {name, arguments: args});
170
+ return (res.content ?? [])
171
+ .map(c => (c.type === 'text' ? c.text : JSON.stringify(c)))
172
+ .join('\n');
173
+ }
174
+
175
+ close() {
176
+ this.proc.stdin.end();
177
+ }
178
+ }
179
+
180
+ async function doctor(serverPath) {
181
+ const major = Number(process.versions.node.split('.')[0]);
182
+ const nodeBin = resolveNodeBin();
183
+ const lines = [
184
+ `cli node: ${process.versions.node}${major >= 18 ? '' : ' (below the >= 18 engine requirement)'}`,
185
+ `server node: ${nodeBin}${nodeBin === process.execPath && major < 18 ? ' — NO >= 18 interpreter found; the server will fail to load' : ''}`,
186
+ ];
187
+ lines.push(`server path: ${serverPath}`);
188
+ const exists = fs.existsSync(serverPath);
189
+ lines.push(`server present: ${exists ? 'yes' : 'NO'}`);
190
+ if (!exists) {
191
+ lines.push(
192
+ 'Fix: build the package (`npm run build-pkg`), or set MEMLAB_MCP_SERVER to a built dist/index.js.',
193
+ );
194
+ console.log(lines.join('\n'));
195
+ return 1;
196
+ }
197
+ // The decisive check: can the server actually start and answer? A corrupted
198
+ // or partial node_modules only shows up here, not from a file listing.
199
+ const client = new Client(serverPath);
200
+ const timer = setTimeout(() => client.proc.kill('SIGKILL'), 60000);
201
+ try {
202
+ await client.init();
203
+ const tools = await client.listTools();
204
+ lines.push(`handshake: OK`);
205
+ lines.push(`tools registered: ${tools.length}`);
206
+ console.log(lines.join('\n'));
207
+ return 0;
208
+ } catch (err) {
209
+ lines.push(`handshake: FAILED — ${err.message}`);
210
+ lines.push(
211
+ 'Fix: check the install dir for build-errors.log / deps-check-errors.log, ' +
212
+ 'then rebuild (the plugin start.sh does a clean rebuild when dependencies fail to load).',
213
+ );
214
+ console.log(lines.join('\n'));
215
+ return 1;
216
+ } finally {
217
+ clearTimeout(timer);
218
+ client.close();
219
+ }
220
+ }
221
+
222
+ async function main() {
223
+ const [cmd, ...rest] = process.argv.slice(2);
224
+ const serverPath = resolveServerPath();
225
+
226
+ if (!cmd || cmd === '--help' || cmd === '-h') {
227
+ console.log(
228
+ [
229
+ 'Usage: memlab-cli <command>',
230
+ '',
231
+ ' doctor check node, server presence, and a live handshake',
232
+ ' list list tool names and one-line descriptions',
233
+ ' schema <tool> print a tool input schema as JSON',
234
+ ' call <tool> <json> [...] call one or more tools in ONE server session',
235
+ ' script <file.jsonl> run {"tool":..,"args":{..}} lines in ONE session',
236
+ '',
237
+ 'Server is resolved from $MEMLAB_MCP_SERVER, else dist/index.js next to this script.',
238
+ ].join('\n'),
239
+ );
240
+ return 0;
241
+ }
242
+
243
+ if (cmd === 'doctor') {
244
+ return doctor(serverPath);
245
+ }
246
+
247
+ const client = new Client(serverPath);
248
+ try {
249
+ await client.init();
250
+ if (cmd === 'list') {
251
+ for (const t of await client.listTools()) {
252
+ console.log(`${t.name}\t${(t.description ?? '').slice(0, 150)}`);
253
+ }
254
+ } else if (cmd === 'schema') {
255
+ const want = rest[0];
256
+ const tool = (await client.listTools()).find(t => t.name === want);
257
+ if (!tool) {
258
+ console.error(`Unknown tool: ${want}`);
259
+ return 1;
260
+ }
261
+ console.log(JSON.stringify(tool.inputSchema ?? {}, null, 2));
262
+ } else if (cmd === 'call') {
263
+ for (let i = 0; i < rest.length; i += 2) {
264
+ const tool = rest[i];
265
+ const args = rest[i + 1] ? JSON.parse(rest[i + 1]) : {};
266
+ console.log(`\n===== ${tool} =====`);
267
+ // Sequential by construction: JSON-RPC over ONE stdio socket to a
268
+ // stateful server (a loaded snapshot). These cannot be parallelised —
269
+ // the server answers one request at a time and later calls depend on
270
+ // earlier ones.
271
+ // eslint-disable-next-line no-await-in-loop
272
+ console.log(await client.callTool(tool, args));
273
+ }
274
+ } else if (cmd === 'script') {
275
+ const file = rest[0];
276
+ // Sequential by construction: these are JSON-RPC calls over ONE stdio
277
+ // socket to a stateful server, so they cannot be parallelised — the next
278
+ // request depends on the previous one having been answered.
279
+
280
+ for (const raw of fs.readFileSync(file, 'utf8').split('\n')) {
281
+ const line = raw.trim();
282
+ if (!line || line.startsWith('#')) {
283
+ continue;
284
+ }
285
+ const spec = JSON.parse(line);
286
+ console.log(`\n===== ${spec.tool} =====`);
287
+ // Sequential by construction: JSON-RPC over ONE stdio socket to a
288
+ // stateful server (a loaded snapshot). These cannot be parallelised —
289
+ // the server answers one request at a time and later calls depend on
290
+ // earlier ones.
291
+ // eslint-disable-next-line no-await-in-loop
292
+ console.log(await client.callTool(spec.tool, spec.args ?? {}));
293
+ }
294
+ } else {
295
+ console.error(`Unknown command: ${cmd}. Try --help.`);
296
+ return 1;
297
+ }
298
+ return 0;
299
+ } finally {
300
+ client.close();
301
+ }
302
+ }
303
+
304
+ // Terminal by construction: both arms call process.exit, so there is nothing
305
+ // left to chain and no rejection can escape.
306
+ // eslint-disable-next-line fb-www/promise-termination
307
+ main().then(
308
+ code => process.exit(code ?? 0),
309
+ err => {
310
+ console.error(err.message ?? String(err));
311
+ process.exit(1);
312
+ },
313
+ );
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import type { IHeapSnapshot } from '@memlab/core';
11
+ /**
12
+ * Has this capture had its string content replaced — and if so, in a way that
13
+ * corrupts the duplication numbers, or not?
14
+ *
15
+ * Anonymisers replace string CONTENT while preserving LENGTH. Whether that
16
+ * matters to an analysis depends entirely on which kind you are looking at, so
17
+ * this reports three states rather than a boolean:
18
+ *
19
+ * - `uniform` — every character replaced by ONE filler character. Every
20
+ * distinct string of a given length collapses into the same value, so every
21
+ * string-duplication analysis measures the anonymiser instead of the app.
22
+ * Measured on one such capture: `memlab_quick_diagnosis` reported **"Total
23
+ * interning savings: 47.4 MB"** and `memlab_duplicated_strings` reported a
24
+ * single 5.2 KB string duplicated 939 times for 42.1 MB. Both were pure
25
+ * artifact. Nothing flagged it; it was caught only because the sample values
26
+ * rendered visibly as runs of `?`.
27
+ * - `stable` — replaced by a value-derived token, so distinctness survives
28
+ * along with length. Duplication and interning figures are as true as on the
29
+ * original capture, and warning about them would make readers discard
30
+ * correct findings. Only the content is unreadable.
31
+ * - `none` — an ordinary capture.
32
+ *
33
+ * Both tests are deliberately cheap and deliberately conservative: sample
34
+ * strings, and only classify when a large majority match. A normal heap has
35
+ * plenty of short repeated-character strings (" ", "----") and plenty of short
36
+ * lowercase words, so the length floor matters more than the ratio for either.
37
+ */
38
+ export type AnonymizationKind =
39
+ /** ordinary capture */
40
+ 'none'
41
+ /**
42
+ * every character replaced by one filler character. Length survives, so
43
+ * distinct values of equal length collapse into one and duplication figures
44
+ * become fiction.
45
+ */
46
+ | 'uniform'
47
+ /**
48
+ * replaced by a value-derived token. Length AND distinctness survive, so
49
+ * duplication figures stay true; only the content is gone.
50
+ */
51
+ | 'stable';
52
+ export interface AnonymizationCheck {
53
+ kind: AnonymizationKind;
54
+ /**
55
+ * true only for `uniform`, which is the kind that corrupts duplication
56
+ * numbers. Existing call sites read this to decide whether to warn, and a
57
+ * `stable` capture must NOT trip them — its numbers are trustworthy.
58
+ */
59
+ anonymized: boolean;
60
+ sampled: number;
61
+ singleCharRuns: number;
62
+ tokenRuns: number;
63
+ exampleChar?: string;
64
+ }
65
+ export declare function detectAnonymizedStrings(snapshot: IHeapSnapshot): AnonymizationCheck;
66
+ /** The banner every string-content tool prints when the capture is anonymised. */
67
+ export declare function anonymizedStringsBanner(check: AnonymizationCheck): string;
68
+ /**
69
+ * Prepend the anonymisation banner to a tool's output when it applies.
70
+ *
71
+ * Wraps the result rather than being left to each caller to remember, because
72
+ * "remember to check" is exactly the failure this exists to prevent.
73
+ */
74
+ export declare function withAnonymizedBanner(snapshot: IHeapSnapshot, text: string): string;
75
+ //# sourceMappingURL=anonymized-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymized-snapshot.d.ts","sourceRoot":"","sources":["../src/anonymized-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,iBAAiB;AAC3B,uBAAuB;AACrB,MAAM;AACR;;;;GAIG;GACD,SAAS;AACX;;;GAGG;GACD,QAAQ,CAAC;AAEb,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,iBAAiB,CAAC;IACxB;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAMD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,aAAa,GACtB,kBAAkB,CAqDpB;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAwBzE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,MAAM,GACX,MAAM,CAIR"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ const MIN_LEN = 8;
11
+ const SAMPLE_TARGET = 400;
12
+ const RATIO = 0.8;
13
+ export function detectAnonymizedStrings(snapshot) {
14
+ let sampled = 0;
15
+ let singleCharRuns = 0;
16
+ let tokenRuns = 0;
17
+ let exampleChar;
18
+ let visited = 0;
19
+ snapshot.nodes.forEach(node => {
20
+ if (sampled >= SAMPLE_TARGET)
21
+ return;
22
+ if (node.type !== 'string')
23
+ return;
24
+ // Stride rather than take the first N: the first strings in a heap are
25
+ // V8 internals and interned literals, which are never anonymised and would
26
+ // make every capture look clean.
27
+ visited++;
28
+ if (visited % 7 !== 0)
29
+ return;
30
+ const value = node.name;
31
+ if (typeof value !== 'string' || value.length < MIN_LEN)
32
+ return;
33
+ sampled++;
34
+ const first = value[0];
35
+ let uniform = true;
36
+ for (let i = 1; i < value.length; i++) {
37
+ if (value[i] !== first) {
38
+ uniform = false;
39
+ break;
40
+ }
41
+ }
42
+ if (uniform) {
43
+ singleCharRuns++;
44
+ exampleChar ??= first;
45
+ return;
46
+ }
47
+ // `stable` fill is a run of lowercase letters and nothing else. Real string
48
+ // values of this length essentially always carry a space, digit, capital or
49
+ // punctuation mark somewhere, so a heap where most of them are bare
50
+ // lowercase runs has been rewritten.
51
+ if (/^[a-z]+$/.test(value)) {
52
+ tokenRuns++;
53
+ }
54
+ });
55
+ const uniformRatio = sampled >= 20 ? singleCharRuns / sampled : 0;
56
+ const tokenRatio = sampled >= 20 ? tokenRuns / sampled : 0;
57
+ const kind = uniformRatio >= RATIO ? 'uniform' : tokenRatio >= RATIO ? 'stable' : 'none';
58
+ return {
59
+ kind,
60
+ anonymized: kind === 'uniform',
61
+ sampled,
62
+ singleCharRuns,
63
+ tokenRuns,
64
+ exampleChar,
65
+ };
66
+ }
67
+ /** The banner every string-content tool prints when the capture is anonymised. */
68
+ export function anonymizedStringsBanner(check) {
69
+ if (check.kind === 'stable') {
70
+ // Deliberately NOT a warning about the numbers. `stable` anonymization
71
+ // preserves distinctness, so duplication and interning figures are exactly
72
+ // as true as on the original capture — saying otherwise here would make
73
+ // readers discard correct findings.
74
+ const pct = check.sampled
75
+ ? Math.round((check.tokenRuns / check.sampled) * 100)
76
+ : 0;
77
+ return (`> ℹ️ **This capture is ANONYMISED (value-stable) — ${pct}% of sampled string values are opaque tokens.** ` +
78
+ 'String CONTENT has been replaced, but lengths and distinctness were preserved, so counts, sizes, duplication and interning figures below are accurate. ' +
79
+ 'What you cannot do is read the strings: a sample value tells you nothing about the real data, so identify records by property shape rather than by value.');
80
+ }
81
+ const pct = check.sampled
82
+ ? Math.round((check.singleCharRuns / check.sampled) * 100)
83
+ : 0;
84
+ return (`> ⚠️ **This capture looks ANONYMISED — ${pct}% of sampled strings are a single character (\`${check.exampleChar ?? '?'}\`) repeated.** ` +
85
+ 'An anonymiser replaces string content but preserves length, so every distinct string of the same length collapses to the same value. ' +
86
+ '**Every duplication and interning figure below is manufactured by that collapse and does not exist in the real app.** ' +
87
+ 'Do not quote them, and do not open an interning task from them. Composition, populations, retainer paths and dominator-deduped sizes are unaffected and remain trustworthy.');
88
+ }
89
+ /**
90
+ * Prepend the anonymisation banner to a tool's output when it applies.
91
+ *
92
+ * Wraps the result rather than being left to each caller to remember, because
93
+ * "remember to check" is exactly the failure this exists to prevent.
94
+ */
95
+ export function withAnonymizedBanner(snapshot, text) {
96
+ const check = detectAnonymizedStrings(snapshot);
97
+ if (check.kind === 'none')
98
+ return text;
99
+ return `${anonymizedStringsBanner(check)}\n\n${text}`;
100
+ }
101
+ //# sourceMappingURL=anonymized-snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymized-snapshot.js","sourceRoot":"","sources":["../src/anonymized-snapshot.ts"],"names":[],"mappings":"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"}