@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,221 @@
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 { z } from 'zod';
11
+ import { getSnapshotByHandle, getMetadataByHandle, getCurrentHandle, listSnapshots, findResidentByPath, } from '../heap-state.js';
12
+ import { getRegisteredTool } from '../tool-registry.js';
13
+ import { formatBytes, formatNumber, markdownTable, errorResult, toolResult, } from '../utils.js';
14
+ import { classifyArtifact } from '../artifact-classes.js';
15
+ import { normalizeClassName } from './sequence-analysis.js';
16
+ /**
17
+ * Attribute every node's SELF size to its immediate dominator's class.
18
+ *
19
+ * Self size, not retained: retained sizes overlap wherever objects nest, so
20
+ * summing them per owner reports more memory than exists (a sum over one class
21
+ * in a 200 MB heap has been measured at 5.9 GB). Self sizes partition the heap
22
+ * exactly, which is what makes two snapshots comparable.
23
+ *
24
+ * Immediate dominator, not nearest app-owned ancestor: resolving an owner chain
25
+ * per node needs a memo table the size of the heap, and on a multi-million-node
26
+ * snapshot that is a large allocation to make while answering a question about
27
+ * memory. The immediate dominator is already the accountable parent for the
28
+ * bytes hanging off it, and it is one field read per node.
29
+ */
30
+ function attributeByOwner(snapshot) {
31
+ const byOwner = new Map();
32
+ let totalSelf = 0;
33
+ snapshot.nodes.forEach(node => {
34
+ if (node.id <= 3)
35
+ return;
36
+ totalSelf += node.self_size;
37
+ const dom = node.dominatorNode;
38
+ // Normalize the per-instance node id V8 appends to Context/scope names
39
+ // ("system / Context / scope @706909"). Those ids differ per capture, so
40
+ // without this every scope looks like an owner that appeared from nothing
41
+ // in the target and vanished from the baseline — pure phantom delta.
42
+ const key = dom == null || dom.id === node.id
43
+ ? '(GC roots)'
44
+ : dom.name.length > 0
45
+ ? normalizeClassName(dom.name)
46
+ : `(unnamed ${dom.type})`;
47
+ const e = byOwner.get(key);
48
+ if (e) {
49
+ e.selfBytes += node.self_size;
50
+ e.nodes++;
51
+ }
52
+ else {
53
+ byOwner.set(key, { selfBytes: node.self_size, nodes: 1 });
54
+ }
55
+ });
56
+ return { byOwner, totalSelf };
57
+ }
58
+ /**
59
+ * Resolve a `baseline` that turned out to be a file path by loading it into the
60
+ * session (keeping whatever is already resident), and return its handle.
61
+ *
62
+ * Dispatches to the registered `memlab_load_snapshot` handler rather than
63
+ * duplicating its guards — the ceilings, the memory-headroom check and the
64
+ * already-resident fast path all still apply.
65
+ */
66
+ async function loadSnapshotByPath(filePath) {
67
+ const already = findResidentByPath(filePath);
68
+ if (already != null)
69
+ return already.handle;
70
+ const entry = getRegisteredTool('memlab_load_snapshot');
71
+ if (entry == null)
72
+ return null;
73
+ try {
74
+ const args = entry.shape != null
75
+ ? z
76
+ .object(entry.shape)
77
+ .parse({ file_path: filePath, keep_previous: true })
78
+ : { file_path: filePath, keep_previous: true };
79
+ await entry.handler(args, {});
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ return findResidentByPath(filePath)?.handle ?? null;
85
+ }
86
+ export function registerExplainDelta(server) {
87
+ server.tool('memlab_explain_delta', 'Explain WHERE a heap grew between two loaded snapshots, attributed by dominator (who owns the new bytes) rather than by class (what the new bytes are). ' +
88
+ '"Object grew by 99,418 instances" does not tell you what to fix; "the bytes hanging off CometStyleXSheet grew by 12 MB" does. Every node\'s SELF size is attributed to its immediate dominator\'s class and the two attributions are differenced, so the numbers partition the heap and add up — unlike a per-class sum of retained sizes, which double-counts nested objects and can exceed the heap. ' +
89
+ 'Known measurement-artifact owners (JIT warmup, CDP inspector, a11y caches, captured Error stacks) are flagged so an inspector-driven delta is not read as an application regression. Both snapshots must be resident: load them with keep_previous:true.', {
90
+ baseline_handle: z
91
+ .string()
92
+ .optional()
93
+ .describe('Handle of the earlier snapshot (memlab_snapshots lists them). Its siblings memlab_artifact_budget and memlab_census_diff take a `baseline` PATH instead, so `baseline` is accepted here as an alias — a batch authored against the recipe used to fail argument validation on this one parameter alone.'),
94
+ baseline: z
95
+ .string()
96
+ .optional()
97
+ .describe('Alias for `baseline_handle`. Accepts a resident handle, or a file path — which is auto-loaded with keep_previous so the whole batch does not have to be re-authored.'),
98
+ target_handle: z
99
+ .string()
100
+ .optional()
101
+ .describe('Handle of the later snapshot. Defaults to the current snapshot.'),
102
+ limit: z
103
+ .number()
104
+ .optional()
105
+ .default(20)
106
+ .describe('Maximum owners to report per direction (default 20).'),
107
+ min_delta_bytes: z
108
+ .number()
109
+ .optional()
110
+ .default(65536)
111
+ .describe('Ignore owners whose attributed size moved by less than this (default 64 KB).'),
112
+ include_artifacts: z
113
+ .boolean()
114
+ .optional()
115
+ .default(false)
116
+ .describe('Include owners that are known measurement artifacts (default false: counted in a one-line summary instead).'),
117
+ }, async ({ baseline_handle, baseline: baselineAlias, target_handle, limit, min_delta_bytes, include_artifacts, }) => {
118
+ try {
119
+ let baselineRef = baseline_handle ?? baselineAlias;
120
+ if (baselineRef == null) {
121
+ return errorResult('Pass `baseline_handle` (a resident snapshot handle) or `baseline` (a handle or a file path).');
122
+ }
123
+ // A path where a handle was expected is the documented sibling spelling,
124
+ // not a mistake: `artifact_budget` and `census_diff` both take paths.
125
+ // Loading it is exactly what the "must be resident" error tells the
126
+ // caller to go and do by hand, and a re-authored batch costs a reload of
127
+ // every rung.
128
+ if (getSnapshotByHandle(baselineRef) == null &&
129
+ /[/\\.]/.test(baselineRef)) {
130
+ const loaded = await loadSnapshotByPath(baselineRef);
131
+ if (loaded == null) {
132
+ return errorResult(`\`baseline\` "${baselineRef}" is neither a resident handle nor a readable snapshot file.`);
133
+ }
134
+ baselineRef = loaded;
135
+ }
136
+ const targetHandle = target_handle ?? getCurrentHandle();
137
+ if (targetHandle == null) {
138
+ return errorResult('No current snapshot; pass target_handle.');
139
+ }
140
+ if (targetHandle === baselineRef) {
141
+ return errorResult('baseline_handle and target_handle are the same snapshot.');
142
+ }
143
+ const baselineSnapshot = getSnapshotByHandle(baselineRef);
144
+ const target = getSnapshotByHandle(targetHandle);
145
+ if (baselineSnapshot == null || target == null) {
146
+ const available = listSnapshots()
147
+ .map(m => m.handle)
148
+ .join(', ');
149
+ return errorResult(`Both snapshots must be resident. Missing: ${baselineSnapshot == null ? baselineRef : targetHandle}. Resident: ${available || '(none)'}. Load with memlab_load_snapshot({file_path, keep_previous: true}).`);
150
+ }
151
+ for (const h of [baselineRef, targetHandle]) {
152
+ if (getMetadataByHandle(h)?.light) {
153
+ return errorResult(`Snapshot "${h}" was loaded in LIGHT mode and has no dominator tree, which this attribution requires. Reload it without light.`);
154
+ }
155
+ }
156
+ const a = attributeByOwner(baselineSnapshot);
157
+ const b = attributeByOwner(target);
158
+ const rows = [];
159
+ const owners = new Set([...a.byOwner.keys(), ...b.byOwner.keys()]);
160
+ for (const owner of owners) {
161
+ const before = a.byOwner.get(owner);
162
+ const after = b.byOwner.get(owner);
163
+ const delta = (after?.selfBytes ?? 0) - (before?.selfBytes ?? 0);
164
+ if (Math.abs(delta) < min_delta_bytes)
165
+ continue;
166
+ rows.push({
167
+ owner,
168
+ delta,
169
+ before: before?.selfBytes ?? 0,
170
+ after: after?.selfBytes ?? 0,
171
+ nodesDelta: (after?.nodes ?? 0) - (before?.nodes ?? 0),
172
+ artifact: classifyArtifact(owner) != null,
173
+ });
174
+ }
175
+ const totalDelta = b.totalSelf - a.totalSelf;
176
+ const lines = [
177
+ `## Heap delta by owner: "${baselineRef}" → "${targetHandle}"`,
178
+ '',
179
+ `Total self size ${totalDelta >= 0 ? 'grew' : 'shrank'} by **${formatBytes(Math.abs(totalDelta))}** (${formatBytes(a.totalSelf)} → ${formatBytes(b.totalSelf)}).`,
180
+ '',
181
+ ];
182
+ const artifactRows = rows.filter(r => r.artifact);
183
+ const usable = include_artifacts ? rows : rows.filter(r => !r.artifact);
184
+ const grew = usable
185
+ .filter(r => r.delta > 0)
186
+ .sort((x, y) => y.delta - x.delta)
187
+ .slice(0, limit);
188
+ const shrank = usable
189
+ .filter(r => r.delta < 0)
190
+ .sort((x, y) => x.delta - y.delta)
191
+ .slice(0, limit);
192
+ if (grew.length === 0 && shrank.length === 0) {
193
+ lines.push(`No owner moved by at least ${formatBytes(min_delta_bytes)}. The change is spread thinly rather than concentrated under one owner — lower \`min_delta_bytes\`, or compare class counts with \`memlab_diff_snapshots\`.`);
194
+ return toolResult(lines.join('\n'));
195
+ }
196
+ const render = (rs) => markdownTable(['Owner (dominator class)', 'Δ self', 'Before', 'After', 'Δ nodes'], rs.map(r => [
197
+ r.owner.length > 44 ? r.owner.slice(0, 41) + '…' : r.owner,
198
+ `${r.delta >= 0 ? '+' : '−'}${formatBytes(Math.abs(r.delta))}`,
199
+ formatBytes(r.before),
200
+ formatBytes(r.after),
201
+ `${r.nodesDelta >= 0 ? '+' : '−'}${formatNumber(Math.abs(r.nodesDelta))}`,
202
+ ]), new Set([1, 2, 3, 4]));
203
+ if (grew.length > 0) {
204
+ lines.push('### Owners that grew', '', render(grew), '');
205
+ }
206
+ if (shrank.length > 0) {
207
+ lines.push('### Owners that shrank', '', render(shrank), '');
208
+ }
209
+ if (!include_artifacts && artifactRows.length > 0) {
210
+ const bytes = artifactRows.reduce((s, r) => s + r.delta, 0);
211
+ lines.push(`> 🧹 **${formatNumber(artifactRows.length)} owner(s) suppressed as known measurement artifacts** (net ${bytes >= 0 ? '+' : '−'}${formatBytes(Math.abs(bytes))}) — JIT warmup, CDP inspector retention, a11y caches, captured Error stacks. Pass \`include_artifacts: true\` to see them.`, '> Note the split of responsibilities: the JIT-warmup families counted here (`system/Code`, `InstructionStream`, `BytecodeArray`, `ProtectedFixedArray`) are NOT counted by `memlab_dev_artifacts`, which measures retention by a dev root instead. Neither total is the whole artifact bill on its own — read both before deciding how much of a round is real.', '');
212
+ }
213
+ lines.push('_Attribution is by IMMEDIATE dominator and uses SELF size, so the rows partition the heap and sum to the total above. An owner growing does not prove a leak — a cache filling legitimately looks identical here; use `memlab_dominator_chain` on an instance to find the accountable application object, and a ladder (`memlab_leak_report`) to tell filling from unbounded growth._');
214
+ return toolResult(lines.join('\n'));
215
+ }
216
+ catch (err) {
217
+ return errorResult(err);
218
+ }
219
+ });
220
+ }
221
+ //# sourceMappingURL=explain-delta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain-delta.js","sourceRoot":"","sources":["../../src/tools/explain-delta.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"find-nodes-by-class.d.ts","sourceRoot":"","sources":["../../src/tools/find-nodes-by-class.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAgBvE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwFhE"}
1
+ {"version":3,"file":"find-nodes-by-class.d.ts","sourceRoot":"","sources":["../../src/tools/find-nodes-by-class.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAiBvE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2MhE"}
@@ -11,26 +11,94 @@ import memlabCore from '@memlab/core';
11
11
  const { utils, NumericSet } = memlabCore;
12
12
  import { z } from 'zod';
13
13
  import { getSnapshot } from '../heap-state.js';
14
- import { filterLargestObjects, serializeNodeSummary, formatNodeSummaryTable, formatNumber, formatBytes, errorResult, toolResult, } from '../utils.js';
14
+ import { filterLargestObjects, serializeNodeSummary, formatNodeSummaryTable, formatNumber, formatBytes, errorResult, toolResult, makeNamePatternTest, } from '../utils.js';
15
15
  export function registerFindNodesByClass(server) {
16
- server.tool('memlab_find_nodes_by_class', 'Find heap objects by constructor/class name. Returns matching objects sorted by retained size. Follow up with memlab_retainer_trace on a result node to see why it is retained, memlab_get_references to inspect its properties, or memlab_retainer_summary to find common retainer patterns across all instances.', {
16
+ server.tool('memlab_find_nodes_by_class', 'Find heap nodes by constructor/class name, exactly (class_name) or by case-insensitive regex/substring (name_pattern). Matches ANY node type by default (object, closure, array, string, native, …) — pass node_type to narrow. Ordering is controlled by order: the biggest by retained size (default), or the newest by node id. If the exact name matches nothing, reports near-miss names and the types they exist under instead of a bare "not found". Follow up with memlab_retainer_trace on a result node to see why it is retained, memlab_get_references to inspect its properties, or memlab_retainer_summary to find common retainer patterns across all instances.', {
17
17
  class_name: z
18
18
  .string()
19
- .describe('The constructor or class name to search for'),
19
+ .optional()
20
+ .describe('The exact constructor or class name to search for. Either this or name_pattern is required.'),
21
+ name_pattern: z
22
+ .string()
23
+ .optional()
24
+ .describe('Match class names by pattern instead of exactly — a case-insensitive regular expression, or a plain substring when the pattern is not valid regex (e.g. "blink::UndoStep", "^WAWeb", "Collection$"). Use when the exact name is unknown, or to sweep a family in one call. Combined with class_name, both must match.'),
25
+ order: z
26
+ .enum(['retained_size', 'newest'])
27
+ .optional()
28
+ .default('retained_size')
29
+ .describe('How to pick which instances to show. "retained_size" (default) shows the biggest. "newest" shows the highest node ids — V8 assigns ids in allocation order, so within one snapshot this approximates the most recently allocated instances. When a class has grown from N to 10N across a ladder, the accumulating tail is what you want to inspect, and the biggest instances are usually the oldest — not the ones that are piling up. Approximate: ids are an allocation-order proxy, not a timestamp.'),
30
+ node_type: z
31
+ .string()
32
+ .optional()
33
+ .describe('Restrict to a single heap node type (e.g. "object", "closure", "array", "string", "native"). Omit to match every type — which is the default because class names surfaced by other tools (memlab_sequence_analysis reports "type::name") are frequently closures or arrays, not objects.'),
20
34
  output_mode: z
21
35
  .enum(['full', 'count', 'ids'])
22
36
  .optional()
23
37
  .default('full')
24
- .describe('Output verbosity: "full" returns node summaries sorted by retained size (default), "count" returns only total count and aggregate retained size, "ids" returns only node IDs sorted by retained size'),
38
+ .describe('Output verbosity: "full" returns node summaries (default), "count" returns only total count and aggregate retained size, "ids" returns only node IDs. "full" and "ids" are both ordered by the "order" parameter, not always by retained size.'),
25
39
  limit: z
26
40
  .number()
41
+ .int()
42
+ .min(1)
27
43
  .optional()
28
44
  .default(20)
29
45
  .describe('Maximum number of results (default 20)'),
30
- }, async ({ class_name, output_mode, limit }) => {
46
+ }, async ({ class_name, name_pattern, node_type, output_mode, limit, order, }) => {
31
47
  try {
48
+ if (class_name == null && name_pattern == null) {
49
+ return errorResult('Pass class_name (exact) or name_pattern (regex/substring); one of the two is required.');
50
+ }
32
51
  const snapshot = getSnapshot();
33
- const classFilter = (node) => node.name === class_name && node.type === 'object';
52
+ const nameMatches = makeNamePatternTest(name_pattern);
53
+ const label = class_name ?? `/${name_pattern}/`;
54
+ // Historically this hard-filtered `type === 'object'`, so closures,
55
+ // arrays, strings and natives were invisible: a class reported as a top
56
+ // grower by memlab_sequence_analysis (which groups by `type::name`)
57
+ // could return "No objects found" here, e.g. the closure `setValues_$0`.
58
+ // Default to every type and let the caller narrow explicitly.
59
+ const classFilter = (node) => (class_name == null || node.name === class_name) &&
60
+ nameMatches(node.name) &&
61
+ (node_type == null || node.type === node_type);
62
+ // Zero exact matches is usually a type filter or a near-miss name, not
63
+ // an empty heap — say which, so the caller does not conclude the class
64
+ // is absent.
65
+ const notFound = () => {
66
+ // The near-miss report keys on an exact name; with only a pattern
67
+ // there is no "similar name" to suggest — say what was searched
68
+ // instead of pretending to have a suggestion.
69
+ if (class_name == null) {
70
+ return `No nodes whose class name matches ${label}${node_type ? ` with node_type="${node_type}"` : ''}. The pattern is a case-insensitive regex (falling back to substring) — widen it, or drop node_type.`;
71
+ }
72
+ const byType = new Map();
73
+ const similar = new Map();
74
+ const needle = class_name.toLowerCase();
75
+ snapshot.nodes.forEach(node => {
76
+ if (node.name === class_name) {
77
+ byType.set(node.type, (byType.get(node.type) ?? 0) + 1);
78
+ }
79
+ else if (node.name.length < 120 &&
80
+ node.name.toLowerCase().includes(needle)) {
81
+ const k = `${node.name} (${node.type})`;
82
+ similar.set(k, (similar.get(k) ?? 0) + 1);
83
+ }
84
+ });
85
+ if (byType.size > 0) {
86
+ const got = [...byType.entries()]
87
+ .sort((a, b) => b[1] - a[1])
88
+ .map(([t, n]) => `${t} (${formatNumber(n)})`)
89
+ .join(', ');
90
+ return `No nodes named "${class_name}" with node_type="${node_type ?? 'any'}". It DOES exist under: ${got}. Re-run without node_type, or with the matching one.`;
91
+ }
92
+ if (similar.size > 0) {
93
+ const top = [...similar.entries()]
94
+ .sort((a, b) => b[1] - a[1])
95
+ .slice(0, 8)
96
+ .map(([k, n]) => `${k} ×${formatNumber(n)}`)
97
+ .join(', ');
98
+ return `No nodes named exactly "${class_name}". Similar names present: ${top}`;
99
+ }
100
+ return `No nodes found with class "${class_name}" (no similar names either — check the snapshot is the one you expect).`;
101
+ };
34
102
  if (output_mode === 'count') {
35
103
  let totalCount = 0;
36
104
  let totalSelf = 0;
@@ -43,15 +111,46 @@ export function registerFindNodesByClass(server) {
43
111
  nodeIds.add(node.id);
44
112
  });
45
113
  if (totalCount === 0) {
46
- return toolResult(`No objects found with class "${class_name}"`);
114
+ return toolResult(notFound());
47
115
  }
48
116
  // Dominator-aware aggregate: instances of a class often nest on the
49
117
  // dominator tree (e.g. a Foo retaining another Foo), so a raw sum of
50
118
  // retainedSize would double-count and can exceed 100% of heap.
51
119
  const totalRetained = utils.aggregateDominatorMetrics(nodeIds, snapshot, () => true, (node) => node.retainedSize);
52
- return toolResult(`"${class_name}": ${formatNumber(totalCount)} instances, ${formatBytes(totalSelf)} total self size, ${formatBytes(totalRetained)} aggregate retained size (dominator-deduplicated)`);
120
+ return toolResult(`"${label}": ${formatNumber(totalCount)} instances, ${formatBytes(totalSelf)} total self size, ${formatBytes(totalRetained)} aggregate retained size (dominator-deduplicated)`);
121
+ }
122
+ let nodes;
123
+ if (order === 'newest') {
124
+ // Highest node ids = latest allocated within this snapshot. Keep a
125
+ // bounded top-N rather than collecting every match and sorting, so a
126
+ // class with a million instances does not materialize a huge array.
127
+ // Relies on the schema's integer `limit >= 1`: a fractional limit
128
+ // never satisfies `length === limit`, and `limit <= 0` would fall
129
+ // through to the eviction branch and keep one node anyway.
130
+ const newest = [];
131
+ let minKept = -1;
132
+ snapshot.nodes.forEach(node => {
133
+ if (!classFilter(node))
134
+ return;
135
+ if (newest.length < limit) {
136
+ newest.push(node);
137
+ if (newest.length === limit) {
138
+ newest.sort((a, b) => a.id - b.id);
139
+ minKept = newest[0].id;
140
+ }
141
+ return;
142
+ }
143
+ if (node.id <= minKept)
144
+ return;
145
+ newest[0] = node;
146
+ newest.sort((a, b) => a.id - b.id);
147
+ minKept = newest[0].id;
148
+ });
149
+ nodes = newest.sort((a, b) => b.id - a.id);
150
+ }
151
+ else {
152
+ nodes = filterLargestObjects(snapshot, classFilter, limit);
53
153
  }
54
- const nodes = filterLargestObjects(snapshot, classFilter, limit);
55
154
  let totalCount = 0;
56
155
  if (nodes.length === limit) {
57
156
  snapshot.nodes.forEach(node => {
@@ -63,16 +162,16 @@ export function registerFindNodesByClass(server) {
63
162
  totalCount = nodes.length;
64
163
  }
65
164
  if (nodes.length === 0) {
66
- return toolResult(`No objects found with class "${class_name}"`);
165
+ return toolResult(notFound());
67
166
  }
68
167
  if (output_mode === 'ids') {
69
- return toolResult(`"${class_name}": ${formatNumber(totalCount)} total instances (showing ${nodes.length} IDs by retained size)\n\nIDs: ${nodes.map(n => n.id).join(', ')}`);
168
+ return toolResult(`"${label}": ${formatNumber(totalCount)} total instances (showing ${nodes.length} IDs by ${order === 'newest' ? 'node id — newest first' : 'retained size'})\n\nIDs: ${nodes.map(n => n.id).join(', ')}`);
70
169
  }
71
170
  const summaries = nodes.map(serializeNodeSummary);
72
171
  const countNote = totalCount > nodes.length
73
- ? ` (${formatNumber(totalCount)} total, showing top ${nodes.length})`
172
+ ? ` (${formatNumber(totalCount)} total, showing ${order === 'newest' ? `the ${nodes.length} newest by node id` : `top ${nodes.length} by retained size`})`
74
173
  : '';
75
- return toolResult(`Found ${formatNumber(totalCount)} "${class_name}" objects${countNote}\n\n${formatNodeSummaryTable(summaries)}`);
174
+ return toolResult(`Found ${formatNumber(totalCount)} "${label}" objects${countNote}\n\n${formatNodeSummaryTable(summaries)}`);
76
175
  }
77
176
  catch (err) {
78
177
  return errorResult(err);
@@ -1 +1 @@
1
- {"version":3,"file":"find-nodes-by-class.js","sourceRoot":"","sources":["../../src/tools/find-nodes-by-class.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"find-nodes-by-class.js","sourceRoot":"","sources":["../../src/tools/find-nodes-by-class.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,203 @@
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
+ import { z } from 'zod';
12
+ /**
13
+ * A cross-round index of leak findings, keyed by a normalized retainer-path
14
+ * signature.
15
+ *
16
+ * The problem this solves is the most expensive one a hunt hits: rounds
17
+ * re-discover leaks that are already known or already fixed. One measured round
18
+ * produced three findings — a listener accumulation, a completion-map grower and
19
+ * a module-registry grower — all three of which were known, two already fixed
20
+ * behind gates, and the first only visible because the gated fixes were not
21
+ * enabled. That is an entire round spent re-deriving history.
22
+ *
23
+ * Why a retainer-path signature rather than the class name: the class that grows
24
+ * is rarely distinctive (`Object`, `Array`, `(object properties)` are the top
25
+ * growers in almost every heap), while the PATH that retains it is. Node ids,
26
+ * array indices and per-instance scope ids differ per capture, so they are
27
+ * stripped; what remains — the ordered edge names and class names — is stable
28
+ * across captures of the same leak and different between different leaks.
29
+ *
30
+ * Deliberately local (a JSON file), not a service: the index is only useful if
31
+ * it is written by default on every round, and anything requiring a backend
32
+ * would not be. It is seeded from prior runs' manifests.
33
+ */
34
+ /**
35
+ * Where the index lives, most specific wins.
36
+ *
37
+ * The default is a per-machine home directory, and that default is the reason
38
+ * the tool has already produced a wrong verdict: an index with no history
39
+ * answers `check` with **NEW** for a finding that is fully documented and
40
+ * already has a fix diff. A home-dir file cannot be shared, does not survive a
41
+ * host change, and is empty on every new devserver — so the failure recurs for
42
+ * every operator rather than once.
43
+ *
44
+ * `MEMLAB_FINDINGS_INDEX` therefore points at a checked-in, shared file, and
45
+ * `workstream` scopes several of them side by side. The home-dir path is kept as
46
+ * the fallback so nothing that already works breaks.
47
+ */
48
+ export declare function resolveIndexPath(workstream?: string): string;
49
+ interface Finding {
50
+ fingerprint: string;
51
+ signature: string;
52
+ growing_classes: string[];
53
+ first_seen_round: string;
54
+ last_seen_round: string;
55
+ status: 'new' | 'known' | 'fixed';
56
+ fixed_behind?: string;
57
+ note?: string;
58
+ seen_count: number;
59
+ }
60
+ interface FindingIndex {
61
+ version: number;
62
+ findings: Record<string, Finding>;
63
+ combos_driven: Record<string, string[]>;
64
+ }
65
+ /**
66
+ * Findings accepted by `action: "import"`.
67
+ *
68
+ * Loose on purpose: the source is a hand-maintained team document or a previous
69
+ * round's notes, and rejecting a row for a missing optional field would mean the
70
+ * bootstrap does not happen at all — which is the status quo this fixes.
71
+ */
72
+ declare const IMPORTED_FINDING_SCHEMA: z.ZodObject<{
73
+ retainer_path: z.ZodOptional<z.ZodString>;
74
+ signature: z.ZodOptional<z.ZodString>;
75
+ growing_classes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
76
+ round: z.ZodOptional<z.ZodString>;
77
+ status: z.ZodOptional<z.ZodEnum<["new", "known", "fixed"]>>;
78
+ fixed_behind: z.ZodOptional<z.ZodString>;
79
+ note: z.ZodOptional<z.ZodString>;
80
+ }, "strip", z.ZodTypeAny, {
81
+ status?: "fixed" | "new" | "known" | undefined;
82
+ note?: string | undefined;
83
+ retainer_path?: string | undefined;
84
+ signature?: string | undefined;
85
+ growing_classes?: string[] | undefined;
86
+ round?: string | undefined;
87
+ fixed_behind?: string | undefined;
88
+ }, {
89
+ status?: "fixed" | "new" | "known" | undefined;
90
+ note?: string | undefined;
91
+ retainer_path?: string | undefined;
92
+ signature?: string | undefined;
93
+ growing_classes?: string[] | undefined;
94
+ round?: string | undefined;
95
+ fixed_behind?: string | undefined;
96
+ }>;
97
+ type ImportedFinding = z.infer<typeof IMPORTED_FINDING_SCHEMA>;
98
+ export declare function importFindings(index: FindingIndex, incoming: ImportedFinding[]): {
99
+ imported: number;
100
+ updated: number;
101
+ skipped: string[];
102
+ };
103
+ /**
104
+ * Normalize a retainer path into a signature that is stable across captures.
105
+ *
106
+ * Strips exactly the things that differ per capture and nothing else:
107
+ * - `@12345` node ids, including the ` @…`-suffixed Context/scope names;
108
+ * - numeric array indices (`[47]`, `.47`), which depend on insertion order;
109
+ * - hex/long digit runs inside names (ids embedded in keys, e.g. a chat id).
110
+ */
111
+ export declare function normalizeRetainerPath(raw: string): string;
112
+ export declare function fingerprintOf(signature: string, classes: string[]): string;
113
+ /**
114
+ * Artifact families every hunt re-derives, seeded into a fresh index.
115
+ *
116
+ * An empty index answers NEW to everything — including populations that are
117
+ * documented, well understood, and not app memory at all. That makes the first
118
+ * `check` of a workstream actively misleading rather than merely unhelpful, and
119
+ * hand-seeding never happens because it is a step nobody is prompted to take.
120
+ *
121
+ * These entries are deliberately the GENERIC, tool-detectable families (the
122
+ * same taxonomy `artifact-classes.ts` classifies, plus the two dev-build
123
+ * registries `dev_artifacts` detects). They are marked `known` rather than
124
+ * `fixed`: they are not defects to be fixed, they are populations a hunt must
125
+ * subtract. Anything app-specific belongs in a real `action: "import"`.
126
+ */
127
+ export declare function builtinSeedFindings(): ImportedFinding[];
128
+ /**
129
+ * Known entries that share a growing class with the candidate.
130
+ *
131
+ * The fingerprint is an EXACT match on signature + class set, which is right for
132
+ * "have we filed this before" and useless for "is this population a known
133
+ * artifact". Measured against a freshly-seeded index: a realistic check —
134
+ * retainer path `(GC roots) > (Global handles) > Object.allSignaturesByType`,
135
+ * growing class `allSignaturesByType` — returned NEW, even though the React Fast
136
+ * Refresh registry entry naming that exact class was sitting in the index. A
137
+ * caller has to reproduce the seeded signature string verbatim to get a hit,
138
+ * which nobody will ever do, so without this the seeding is decorative.
139
+ *
140
+ * Reported ALONGSIDE the NEW verdict rather than replacing it: a class overlap
141
+ * is a lead, not an identification, and silently converting NEW to KNOWN on one
142
+ * shared class name would suppress real findings.
143
+ */
144
+ export declare function relatedByClass(index: FindingIndex, classes: readonly string[], excludeFingerprint: string): Finding[];
145
+ /**
146
+ * Does the index hold anything beyond the generic built-in artifact families?
147
+ *
148
+ * `loadIndex` seeds a fresh index with those families, so a plain
149
+ * `Object.keys(findings).length === 0` is never true and every "is this index
150
+ * usable" guard built on it silently never fires. The question that actually
151
+ * matters is whether the index carries history for THIS app; built-in entries
152
+ * are stamped `first_seen_round: 'builtin'` and say nothing about it.
153
+ */
154
+ export declare function hasAppHistory(index: {
155
+ findings: Record<string, {
156
+ first_seen_round: string;
157
+ }>;
158
+ }): boolean;
159
+ /**
160
+ * Fingerprint a candidate and look it up, for callers that are not the
161
+ * `memlab_finding_index` tool itself.
162
+ *
163
+ * Exists because the index only prevents re-discovery if somebody remembers to
164
+ * consult it, and in practice nobody does: a measured session produced three
165
+ * findings that were all already fixed behind gates, and never called `check`
166
+ * once. The tools that actually surface findings — `auto_investigate`,
167
+ * `leak_report` — now annotate inline instead of relying on that recall.
168
+ *
169
+ * Returns `indexEmpty` so the caller can say the verdict means nothing rather
170
+ * than printing a confident NEW from an index that has never been seeded.
171
+ */
172
+ export type FindingLookup = {
173
+ verdict: 'NEW' | 'KNOWN' | 'KNOWN-AND-FIXED';
174
+ fixedBehind?: string;
175
+ note?: string;
176
+ round?: string;
177
+ related: string[];
178
+ indexEmpty: boolean;
179
+ indexPath: string;
180
+ };
181
+ /**
182
+ * The index, read once, for callers that look up many candidates in a row.
183
+ *
184
+ * `lookupFinding` reads and parses the index file on every call, which is
185
+ * fine for a one-off check and wasteful inside a report loop that annotates
186
+ * every finding — that turns one read into N reads of the same file.
187
+ */
188
+ export type LoadedFindingIndex = {
189
+ index: FindingIndex;
190
+ indexPath: string;
191
+ };
192
+ export declare function loadFindingIndex(workstream?: string): LoadedFindingIndex;
193
+ export declare function lookupFinding(retainerPath: string, classes: readonly string[], workstream?: string): FindingLookup;
194
+ export declare function lookupFindingIn(loaded: LoadedFindingIndex, retainerPath: string, classes: readonly string[]): FindingLookup;
195
+ /** One-line renderer for an inline verdict badge. */
196
+ export declare function renderFindingVerdict(l: FindingLookup): string;
197
+ /**
198
+ * The banner to print once per report when the index cannot support a verdict.
199
+ */
200
+ export declare function findingIndexEmptyBanner(indexPath: string): string;
201
+ export declare function registerFindingIndex(server: McpServer): void;
202
+ export {};
203
+ //# sourceMappingURL=finding-index.d.ts.map