@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
@@ -0,0 +1,263 @@
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 memlabCore from '@memlab/core';
11
+ import { z } from 'zod';
12
+ import { getSnapshot, getSnapshotMetadata } from '../heap-state.js';
13
+ import { formatTraceChain, getRetainerTrace, traceToKey, } from './retainer-summary.js';
14
+ import { boundedDominatorRetainedSize, errorResult, formatBytes, formatNumber, markdownTable, toolResult, } from '../utils.js';
15
+ const { NumericSet } = memlabCore;
16
+ // Tracing is a pointer walk per node, so the whole population is affordable at
17
+ // heap scale — but not without a bound, because a pathological graph turns it
18
+ // into the load-time dominator pass all over again.
19
+ const DEFAULT_MAX_TRACE = 200000;
20
+ const MAX_IDS_PER_CLUSTER = 50000;
21
+ /**
22
+ * Render a cluster's chain. When a path was truncated for grouping, its last
23
+ * step still carries the edge leading to the next hop, and that edge IS part
24
+ * of the grouping key — so suppressing it (which is correct for a complete
25
+ * path, whose last node is the target) renders two distinct clusters
26
+ * byte-identically. Show it, with a marker for the hops beyond.
27
+ */
28
+ export function renderClusterChain(steps) {
29
+ const base = formatTraceChain(steps);
30
+ const last = steps[steps.length - 1];
31
+ return last?.edgeName != null ? `${base} --${last.edgeName}--> ⋯` : base;
32
+ }
33
+ export function selectPopulation(snapshot, sel) {
34
+ const out = [];
35
+ if (sel.nodeIds != null && sel.nodeIds.length > 0) {
36
+ for (const id of sel.nodeIds) {
37
+ const n = snapshot.getNodeById(id);
38
+ if (n != null)
39
+ out.push(n);
40
+ }
41
+ return out;
42
+ }
43
+ const want = sel.shape != null ? new Set(sel.shape) : null;
44
+ snapshot.nodes.forEach(node => {
45
+ if (node.id <= 3)
46
+ return;
47
+ if (sel.className != null) {
48
+ if (node.name !== sel.className)
49
+ return;
50
+ }
51
+ else if (want != null) {
52
+ const remaining = new Set(want);
53
+ for (const e of node.references) {
54
+ if (e.type !== 'property')
55
+ continue;
56
+ remaining.delete(String(e.name_or_index));
57
+ if (remaining.size === 0)
58
+ break;
59
+ }
60
+ if (remaining.size > 0)
61
+ return;
62
+ }
63
+ out.push(node);
64
+ });
65
+ return out;
66
+ }
67
+ export function clusterByRetainerPath(nodes, frameworkFilter, maxTrace,
68
+ // Hops from the GC root to key on. Paths are root-first, so truncating keeps
69
+ // the owner side and collapses per-instance leaves — the difference between
70
+ // "one owner, 40,000 objects" and 26,000 one-object paths that differ only
71
+ // in which array index they sit at.
72
+ maxDepth) {
73
+ const byKey = new Map();
74
+ let traced = 0;
75
+ let noPath = 0;
76
+ let truncated = false;
77
+ for (const node of nodes) {
78
+ if (traced + noPath >= maxTrace) {
79
+ truncated = true;
80
+ break;
81
+ }
82
+ const steps = getRetainerTrace(node);
83
+ if (steps == null) {
84
+ noPath++;
85
+ continue;
86
+ }
87
+ traced++;
88
+ const keySteps = maxDepth != null && maxDepth > 0 && maxDepth < steps.length
89
+ ? steps.slice(0, maxDepth)
90
+ : steps;
91
+ const key = traceToKey(keySteps, frameworkFilter);
92
+ const existing = byKey.get(key);
93
+ if (existing != null) {
94
+ existing.count++;
95
+ existing.selfSize += node.self_size;
96
+ if (existing.exampleIds.length < 3)
97
+ existing.exampleIds.push(node.id);
98
+ if (existing.ids.length < MAX_IDS_PER_CLUSTER)
99
+ existing.ids.push(node.id);
100
+ else
101
+ existing.idsTruncated = true;
102
+ }
103
+ else {
104
+ byKey.set(key, {
105
+ key,
106
+ steps: keySteps,
107
+ count: 1,
108
+ selfSize: node.self_size,
109
+ exampleIds: [node.id],
110
+ ids: [node.id],
111
+ idsTruncated: false,
112
+ });
113
+ }
114
+ }
115
+ const clusters = [...byKey.values()].sort((a, b) => b.count - a.count);
116
+ return { clusters, traced, noPath, truncated };
117
+ }
118
+ export function registerTraceAll(server) {
119
+ server.tool('memlab_trace_all', 'Retainer-trace an ENTIRE population and cluster the paths server-side, instead of sampling a handful and hoping they are representative.\n\n' +
120
+ '`memlab_retainer_summary` samples ~10 instances and stops early once they agree, which is the right default for a quick read but is structurally unable to find a minority path: a cluster holding 3% of the objects and 60% of the bytes is invisible to it, and that cluster is very often the leak. This traces every member (bounded, and it says so when the bound is hit), groups by structural path signature, and reports each cluster with its share of the population — so the output cost is the number of DISTINCT paths, not the size of the population.\n\n' +
121
+ 'Select by class, shape or explicit ids. Use it when a population is large enough that "are they all retained the same way?" is the question, and when a sampled answer has already been used to justify a conclusion.', {
122
+ class_name: z
123
+ .string()
124
+ .optional()
125
+ .describe('Trace every instance of this class (exact name match).'),
126
+ shape: z
127
+ .array(z.string())
128
+ .optional()
129
+ .describe('Trace every object carrying ALL of these properties — the usable selector on a minified heap.'),
130
+ node_ids: z
131
+ .array(z.number())
132
+ .optional()
133
+ .describe('Trace exactly these node ids.'),
134
+ framework_filter: z
135
+ .boolean()
136
+ .optional()
137
+ .default(true)
138
+ .describe('Collapse V8/framework internals (system / Context, PromiseReaction, …) out of the grouping key so structurally identical application paths cluster together (default true).'),
139
+ limit: z
140
+ .number()
141
+ .optional()
142
+ .default(8)
143
+ .describe('Clusters to render in full (default 8).'),
144
+ exact_bytes: z
145
+ .boolean()
146
+ .optional()
147
+ .default(true)
148
+ .describe('Compute dominator-deduped retained bytes for each rendered cluster (default true). Turn off on very large populations if the walk is slow; self size is always reported.'),
149
+ max_depth: z
150
+ .number()
151
+ .optional()
152
+ .describe('Group on the first N hops from the GC ROOT instead of the whole path. Defaults to the whole path, which is exact but fragments when the leaf hop is per-instance — a per-chat property edge or a per-event listener array gives nearly one path per object. Try 5-7 to see owners rather than instances.'),
153
+ max_trace: z
154
+ .number()
155
+ .optional()
156
+ .default(DEFAULT_MAX_TRACE)
157
+ .describe(`Safety bound on how many nodes to trace (default ${formatNumber(DEFAULT_MAX_TRACE)}). Truncation is always reported — a partial sweep is never presented as a complete one.`),
158
+ }, async ({ class_name, shape, node_ids, framework_filter, limit, exact_bytes, max_depth, max_trace, }) => {
159
+ try {
160
+ const snapshot = getSnapshot();
161
+ const selectors = [
162
+ class_name != null,
163
+ shape != null && shape.length > 0,
164
+ node_ids != null && node_ids.length > 0,
165
+ ].filter(Boolean).length;
166
+ if (selectors !== 1) {
167
+ return errorResult(new Error('Pass exactly one of class_name / shape / node_ids — combining them would make the reported population ambiguous.'));
168
+ }
169
+ const nodes = selectPopulation(snapshot, {
170
+ nodeIds: node_ids,
171
+ className: class_name,
172
+ shape,
173
+ });
174
+ if (nodes.length === 0) {
175
+ return toolResult('Nothing matched. Check the class name with `memlab_class_histogram` or the shape with `memlab_shape_histogram`.');
176
+ }
177
+ const { clusters, traced, noPath, truncated } = clusterByRetainerPath(nodes, framework_filter, max_trace, max_depth);
178
+ if (clusters.length === 0) {
179
+ return toolResult(`All ${formatNumber(nodes.length)} matched objects have no retainer path — they are unreachable and awaiting collection, not retained. Nothing to trace.`);
180
+ }
181
+ const label = class_name != null
182
+ ? `class \`${class_name}\``
183
+ : shape != null
184
+ ? `shape \`{${shape.join(', ')}}\``
185
+ : `${formatNumber(nodes.length)} explicit id(s)`;
186
+ const meta = getSnapshotMetadata();
187
+ const rendered = clusters.slice(0, limit);
188
+ const bytesByKey = new Map();
189
+ if (exact_bytes) {
190
+ for (const c of rendered) {
191
+ // Only the rendered clusters get the dominator walk: it is the
192
+ // expensive part, and a cluster nobody sees does not need a number
193
+ // attached to it. Member ids were collected during the single
194
+ // clustering pass, so this does not re-walk the population.
195
+ const bytes = boundedDominatorRetainedSize(new NumericSet(c.ids), snapshot);
196
+ bytesByKey.set(c.key, {
197
+ retained: bytes.retained,
198
+ exact: bytes.exact && !c.idsTruncated,
199
+ });
200
+ }
201
+ }
202
+ const totalTraced = traced;
203
+ const rows = rendered.map((c, i) => {
204
+ const bytes = bytesByKey.get(c.key);
205
+ return [
206
+ String(i + 1),
207
+ formatNumber(c.count),
208
+ `${((c.count / totalTraced) * 100).toFixed(1)}%`,
209
+ formatBytes(c.selfSize),
210
+ bytes != null
211
+ ? `${formatBytes(bytes.retained)}${bytes.exact ? '' : ' (ub)'}`
212
+ : '—',
213
+ `@${c.exampleIds[0]}`,
214
+ ];
215
+ });
216
+ const lines = [
217
+ `## Retainer paths for ${label}`,
218
+ '',
219
+ `**${formatNumber(traced)} of ${formatNumber(nodes.length)} traced** into **${formatNumber(clusters.length)} distinct path${clusters.length === 1 ? '' : 's'}**` +
220
+ `${noPath > 0 ? `; ${formatNumber(noPath)} had no retainer path (unreachable, awaiting collection)` : ''}` +
221
+ `${truncated ? `. ⚠ Stopped at the ${formatNumber(max_trace)}-node bound, so this is a partial sweep — raise max_trace for the full population` : ''}.`,
222
+ '',
223
+ markdownTable(['#', 'Objects', 'Share', 'Self size', 'Retained', 'Example'], rows, new Set([1, 2, 3, 4])),
224
+ ];
225
+ // A path count close to the population count means the signature is
226
+ // keying on something per-instance, and every cluster is a single
227
+ // object wearing a path. That reads as "no dominant owner" when the
228
+ // truth may be one owner and a per-instance leaf hop.
229
+ if (max_depth == null && clusters.length > totalTraced * 0.5) {
230
+ lines.push('', `⚠ ${formatNumber(clusters.length)} paths for ${formatNumber(totalTraced)} objects — the full-depth signature is keying on a per-instance hop (a per-chat property edge, a per-event listener array), so almost every object is its own "path". Re-run with \`max_depth: 5\` (or 6-7) to group on the owner side of the chain instead. Shares below are computed over this fragmented grouping and should not be read as "no dominant owner".`);
231
+ }
232
+ if (clusters.length > rendered.length) {
233
+ const rest = clusters.slice(rendered.length);
234
+ const restCount = rest.reduce((a, c) => a + c.count, 0);
235
+ lines.push('', `_${formatNumber(clusters.length - rendered.length)} further path(s) hold ${formatNumber(restCount)} object(s) (${((restCount / totalTraced) * 100).toFixed(1)}%); raise \`limit\` to render them._`);
236
+ }
237
+ // The whole point of a full sweep is that it can see what sampling
238
+ // cannot, so say when it did.
239
+ const minority = clusters.filter(c => c.count / totalTraced < 0.1 && c.count > 0);
240
+ if (clusters.length > 1) {
241
+ const dominant = clusters[0];
242
+ const dominantShare = (dominant.count / totalTraced) * 100;
243
+ lines.push('', dominantShare >= 95
244
+ ? `**One path dominates** (${dominantShare.toFixed(1)}%): a single retention mechanism, and a fix at that owner addresses effectively the whole population.`
245
+ : `**Retention is split across ${formatNumber(clusters.length)} paths**, the largest holding ${dominantShare.toFixed(1)}%. A fix aimed at one owner leaves the rest — size it against the cluster, not the class total.`);
246
+ if (minority.length > 0) {
247
+ const minCount = minority.reduce((a, c) => a + c.count, 0);
248
+ lines.push('', `⚠ ${formatNumber(minority.length)} path(s) below 10% share, holding ${formatNumber(minCount)} object(s) in total. A 10-instance sample would very likely have missed every one of them; check whether any is the interesting one before concluding the population is homogeneous.`);
249
+ }
250
+ }
251
+ rendered.forEach((c, i) => {
252
+ const bytes = bytesByKey.get(c.key);
253
+ lines.push('', `### Path ${i + 1} — ${formatNumber(c.count)} object(s), ${((c.count / totalTraced) * 100).toFixed(1)}%${bytes != null ? `, ${formatBytes(bytes.retained)} retained` : ''}`, '', renderClusterChain(c.steps), '', `Examples: ${c.exampleIds.map(id => `@${id}`).join(', ')}`);
254
+ });
255
+ lines.push('', `_Paths are grouped by structural signature${framework_filter ? ' with framework internals collapsed' : ''}; per-instance node ids in V8 internal names are normalized out, so two rows are never byte-identical. Retained bytes are dominator-deduped within a cluster and do not add up across clusters when one cluster dominates another._`, '', `Next: \`memlab_what_if\` to size a fix at one owner, \`memlab_identify\` to name the population, \`memlab_retainer_diff\` to see whether these paths are the ones that grew.${meta != null ? '' : ''}`);
256
+ return toolResult(lines.join('\n'));
257
+ }
258
+ catch (err) {
259
+ return errorResult(err);
260
+ }
261
+ });
262
+ }
263
+ //# sourceMappingURL=trace-all.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-all.js","sourceRoot":"","sources":["../../src/tools/trace-all.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
11
+ export declare function registerUnitCost(server: McpServer): void;
12
+ //# sourceMappingURL=unit-cost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unit-cost.d.ts","sourceRoot":"","sources":["../../src/tools/unit-cost.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AA6FvE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAmNxD"}
@@ -0,0 +1,219 @@
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 memlabCore from '@memlab/core';
11
+ import { z } from 'zod';
12
+ import { getCurrentHandle, getMetadataByHandle, getSnapshot, getSnapshotByHandle, } from '../heap-state.js';
13
+ import { boundedDominatorRetainedSize, errorResult, formatBytes, formatNumber, markdownTable, toolResult, } from '../utils.js';
14
+ const { NumericSet } = memlabCore;
15
+ function matchesShape(node, shape) {
16
+ if (shape.length === 0)
17
+ return false;
18
+ const want = new Set(shape);
19
+ for (const e of node.references) {
20
+ if (e.type !== 'property')
21
+ continue;
22
+ want.delete(String(e.name_or_index));
23
+ if (want.size === 0)
24
+ return true;
25
+ }
26
+ return false;
27
+ }
28
+ function measure(snapshot, opts) {
29
+ const ids = [];
30
+ // Sizing a cap needs the per-entry cost of THAT cache, not of a class that
31
+ // happens to include its entries alongside thousands of unrelated objects.
32
+ // The entries of one container are the population the cap actually bounds.
33
+ if (opts.containerId != null) {
34
+ const container = snapshot.getNodeById(opts.containerId);
35
+ if (container == null)
36
+ return { count: 0, retained: 0, exact: true };
37
+ const seen = new Set();
38
+ for (const edge of container.references) {
39
+ if (edge.type !== 'element' && edge.type !== 'property')
40
+ continue;
41
+ const name = String(edge.name_or_index);
42
+ // Structural properties are not entries; counting them inflates the
43
+ // denominator and understates the per-entry cost.
44
+ if (edge.type === 'property' &&
45
+ (name === 'length' ||
46
+ name === 'size' ||
47
+ name === '__proto__' ||
48
+ name.startsWith('_'))) {
49
+ continue;
50
+ }
51
+ const target = edge.toNode;
52
+ if (target.id <= 3 || seen.has(target.id))
53
+ continue;
54
+ seen.add(target.id);
55
+ ids.push(target.id);
56
+ }
57
+ if (ids.length === 0)
58
+ return { count: 0, retained: 0, exact: true };
59
+ const { retained, exact } = boundedDominatorRetainedSize(new NumericSet(ids), snapshot);
60
+ return { count: ids.length, retained, exact };
61
+ }
62
+ snapshot.nodes.forEach(node => {
63
+ if (node.id <= 3)
64
+ return;
65
+ if (opts.className != null) {
66
+ if (node.name !== opts.className)
67
+ return;
68
+ }
69
+ else if (opts.shape != null) {
70
+ if (!matchesShape(node, opts.shape))
71
+ return;
72
+ }
73
+ ids.push(node.id);
74
+ });
75
+ if (ids.length === 0)
76
+ return { count: 0, retained: 0, exact: true };
77
+ const { retained, exact } = boundedDominatorRetainedSize(new NumericSet(ids), snapshot);
78
+ return { count: ids.length, retained, exact };
79
+ }
80
+ export function registerUnitCost(server) {
81
+ server.tool('memlab_unit_cost', 'How much memory does ONE of these actually cost? Reports dominator-deduped retained bytes per instance for a class or an object shape, ' +
82
+ 'and — given two snapshots — separates the AVERAGE from the MARGINAL cost.\n\n' +
83
+ 'That distinction decides fixes. A population whose first instances drag in shared structure has a high average and a low marginal cost, ' +
84
+ 'so capping it saves far less than the average implies; a population with a flat per-instance cost scales linearly and a cap saves exactly ' +
85
+ 'what the arithmetic says. Sizing an LRU, an undo-stack cap, an eviction policy or a "is this worth interning?" decision all need the ' +
86
+ 'marginal number, and computing it by hand means four calls and a subtraction that is easy to get backwards.\n\n' +
87
+ 'With one snapshot you get the average and a projection table. With `baseline_handle` you also get the marginal cost between the two rungs.', {
88
+ class_name: z
89
+ .string()
90
+ .optional()
91
+ .describe('Class/constructor name as reported by memlab_class_histogram (exact match). Use this OR shape.'),
92
+ shape: z
93
+ .array(z.string())
94
+ .optional()
95
+ .describe('Property names an instance must ALL carry, e.g. ["callback","context"]. Use for minified heaps where the class name is meaningless. Use this OR class_name.'),
96
+ handle: z
97
+ .string()
98
+ .optional()
99
+ .describe('Snapshot to measure (defaults to the active one).'),
100
+ baseline_handle: z
101
+ .string()
102
+ .optional()
103
+ .describe('Earlier snapshot. When given, the marginal cost is (retained_now − retained_baseline) / (count_now − count_baseline) — what one MORE instance costs, which is the number a cap or eviction policy is sized against.'),
104
+ container_id: z
105
+ .number()
106
+ .optional()
107
+ .describe('Measure the ENTRIES of this container (a Map, Set, Array or plain-object cache from memlab_cache_analysis) instead of a class or shape. This is the population an LRU cap actually bounds, so it is the right denominator when sizing one — a class-wide average mixes in instances the cache does not hold. Structural properties (length, size, _-prefixed) are excluded from the entry count.'),
108
+ project_caps: z
109
+ .array(z.number())
110
+ .optional()
111
+ .describe('Instance counts to project retained size for, e.g. [100, 500, 2000] when choosing a cache cap. Uses the marginal cost when available, otherwise the average (and says which).'),
112
+ }, async ({ class_name, shape, container_id, handle, baseline_handle, project_caps, }) => {
113
+ try {
114
+ if (container_id == null &&
115
+ class_name == null &&
116
+ (shape == null || shape.length === 0)) {
117
+ return errorResult(new Error('Pass class_name, shape or container_id. On a minified heap prefer shape — class names like "t"/"e" match thousands of unrelated objects — and prefer container_id when sizing a cap for one specific cache.'));
118
+ }
119
+ if (class_name != null && shape != null && shape.length > 0) {
120
+ return errorResult(new Error('Pass class_name OR shape, not both.'));
121
+ }
122
+ const target = handle != null ? getSnapshotByHandle(handle) : getSnapshot();
123
+ if (target == null) {
124
+ return errorResult(new Error(`Snapshot "${handle}" is not resident.`));
125
+ }
126
+ const targetLabel = handle ?? getCurrentHandle() ?? '(active)';
127
+ if (container_id != null && baseline_handle != null) {
128
+ return errorResult(new Error('container_id cannot be combined with baseline_handle: node ids are per-capture, so the same id in the baseline is a different object (or nothing) and the marginal cost would be computed from two unrelated populations. Select the container in each snapshot separately, or use a shape.'));
129
+ }
130
+ const now = measure(target, {
131
+ className: class_name,
132
+ shape,
133
+ containerId: container_id,
134
+ });
135
+ if (now.count === 0) {
136
+ return toolResult(`No instances found for ${container_id != null ? `the entries of @${container_id}` : class_name != null ? `class \`${class_name}\`` : `shape {${(shape ?? []).join(', ')}}`} in \`${targetLabel}\`. ` +
137
+ 'For a class, check the exact name with memlab_class_histogram; for a shape, with memlab_shape_histogram.');
138
+ }
139
+ const average = now.retained / now.count;
140
+ let marginal = null;
141
+ let base = null;
142
+ if (baseline_handle != null) {
143
+ const baseSnapshot = getSnapshotByHandle(baseline_handle);
144
+ if (baseSnapshot == null) {
145
+ return errorResult(new Error(`Baseline snapshot "${baseline_handle}" is not resident.`));
146
+ }
147
+ base = measure(baseSnapshot, {
148
+ className: class_name,
149
+ shape,
150
+ // Node ids are per-capture, so a container selected by id in one
151
+ // snapshot is a different object (or nothing) in another. Refusing
152
+ // is the only honest option; silently measuring whatever holds
153
+ // that id in the baseline would produce a marginal cost computed
154
+ // from two unrelated populations.
155
+ containerId: undefined,
156
+ });
157
+ const dCount = now.count - base.count;
158
+ const dBytes = now.retained - base.retained;
159
+ if (dCount > 0)
160
+ marginal = dBytes / dCount;
161
+ }
162
+ const label = container_id != null
163
+ ? `entries of @${container_id}`
164
+ : class_name != null
165
+ ? `class \`${class_name}\``
166
+ : `shape \`{${(shape ?? []).join(', ')}}\``;
167
+ const lines = [
168
+ `## Unit cost — ${label}`,
169
+ '',
170
+ `\`${targetLabel}\`${getMetadataByHandle(targetLabel) ? ` (${getMetadataByHandle(targetLabel)?.fileName})` : ''}: ` +
171
+ `**${formatNumber(now.count)} instances retaining ${formatBytes(now.retained)}**${now.exact ? '' : ' (upper bound — the dominator walk hit its depth cap)'}.`,
172
+ '',
173
+ `- **Average**: ${formatBytes(Math.round(average))} per instance.`,
174
+ ];
175
+ if (base != null && marginal != null) {
176
+ lines.push(`- **Marginal**: ${formatBytes(Math.round(marginal))} per instance — measured across ${formatNumber(now.count - base.count)} added instances between \`${baseline_handle}\` (${formatNumber(base.count)} / ${formatBytes(base.retained)}) and \`${targetLabel}\`.`);
177
+ const ratio = average > 0 ? marginal / average : 1;
178
+ if (ratio < 0.6) {
179
+ lines.push('', `⚠ Marginal is ${(ratio * 100).toFixed(0)}% of average: much of what this population retains is **shared structure the first instances pulled in**, not per-instance data. Size any cap or eviction policy on the marginal figure — the average overstates what capping saves.`);
180
+ }
181
+ else if (ratio > 1.4) {
182
+ lines.push('', `⚠ Marginal is ${(ratio * 100).toFixed(0)}% of average: later instances cost MORE than earlier ones (they are retaining progressively more, or a shared structure is growing with them). Check whether the cost per instance is itself unbounded before sizing a cap.`);
183
+ }
184
+ else {
185
+ lines.push('', '_Marginal ≈ average: the population scales linearly, so a cap saves what the arithmetic says._');
186
+ }
187
+ }
188
+ else if (baseline_handle != null) {
189
+ lines.push('', '_No marginal cost: the population did not grow between the two snapshots, so there is no delta to divide._');
190
+ }
191
+ else {
192
+ lines.push('', '_Average only. Pass `baseline_handle` (an earlier rung) for the marginal cost — the two differ whenever the first instances drag in shared structure, and it is the marginal one that sizes a fix._');
193
+ }
194
+ if (project_caps != null && project_caps.length > 0) {
195
+ const unit = marginal ?? average;
196
+ const usingMarginal = marginal != null;
197
+ lines.push('', `### Projected retained size at a cap (using the ${usingMarginal ? 'marginal' : 'average'} cost)`, '');
198
+ const rows = [...project_caps]
199
+ .sort((a, b) => b - a)
200
+ .map(cap => {
201
+ const projected = Math.round(unit * Math.min(cap, now.count));
202
+ const saved = Math.round(Math.max(0, now.retained - unit * cap));
203
+ return [
204
+ formatNumber(cap),
205
+ formatBytes(projected),
206
+ cap >= now.count ? '—' : formatBytes(saved),
207
+ ];
208
+ });
209
+ lines.push(markdownTable(['Cap (instances)', 'Projected retained', 'Saved vs today'], rows, new Set([0, 1, 2])));
210
+ lines.push('', '_A projection, not a measurement: it assumes the per-instance cost holds at the cap and that evicted values are not co-retained elsewhere. Confirm the top candidate with `memlab_what_if` or a capped re-run._');
211
+ }
212
+ return toolResult(lines.join('\n'));
213
+ }
214
+ catch (err) {
215
+ return errorResult(err);
216
+ }
217
+ });
218
+ }
219
+ //# sourceMappingURL=unit-cost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unit-cost.js","sourceRoot":"","sources":["../../src/tools/unit-cost.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
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
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
+ type MetricKind = 'collection_length' | 'class_count' | 'pending_chain';
13
+ /**
14
+ * Measure the metric on one parsed snapshot. One scalar per snapshot is the
15
+ * whole point: the arms are compared on a rate, and a rate needs a number per
16
+ * rung, not a report.
17
+ */
18
+ export declare function measureMetric(snapshot: IHeapSnapshot, kind: MetricKind, locator: string, chainEdge: string): number;
19
+ /** Growth per interaction cycle across an arm's ladder. */
20
+ export declare function perCycleRate(values: number[], cycles: number): number;
21
+ export declare function registerVerifyFix(server: McpServer): void;
22
+ export {};
23
+ //# sourceMappingURL=verify-fix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-fix.d.ts","sourceRoot":"","sources":["../../src/tools/verify-fix.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAY,aAAa,EAAC,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAiBvE,KAAK,UAAU,GAAG,mBAAmB,GAAG,aAAa,GAAG,eAAe,CAAC;AAOxE;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,MAAM,CAuCR;AA4CD,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIrE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAoKzD"}