@memlab/mcp-server 2.52.0 → 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 (277) hide show
  1. package/README.md +22 -2
  2. package/dist/anonymized-snapshot.d.ts +75 -0
  3. package/dist/anonymized-snapshot.d.ts.map +1 -0
  4. package/dist/anonymized-snapshot.js +101 -0
  5. package/dist/anonymized-snapshot.js.map +1 -0
  6. package/dist/eval-hints.d.ts +41 -0
  7. package/dist/eval-hints.d.ts.map +1 -0
  8. package/dist/eval-hints.js +115 -0
  9. package/dist/eval-hints.js.map +1 -0
  10. package/dist/heap-budget.d.ts +32 -0
  11. package/dist/heap-budget.d.ts.map +1 -0
  12. package/dist/heap-budget.js +90 -0
  13. package/dist/heap-budget.js.map +1 -0
  14. package/dist/heap-shapes.d.ts +148 -0
  15. package/dist/heap-shapes.d.ts.map +1 -0
  16. package/dist/heap-shapes.js +193 -0
  17. package/dist/heap-shapes.js.map +1 -0
  18. package/dist/heap-state.d.ts +20 -0
  19. package/dist/heap-state.d.ts.map +1 -1
  20. package/dist/heap-state.js +8 -1
  21. package/dist/heap-state.js.map +1 -1
  22. package/dist/index.js +64 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/react-shapes.d.ts +42 -0
  25. package/dist/react-shapes.d.ts.map +1 -0
  26. package/dist/react-shapes.js +149 -0
  27. package/dist/react-shapes.js.map +1 -0
  28. package/dist/server-version.d.ts +19 -0
  29. package/dist/server-version.d.ts.map +1 -0
  30. package/dist/server-version.js +19 -0
  31. package/dist/server-version.js.map +1 -0
  32. package/dist/snapshot-borrow.d.ts +67 -0
  33. package/dist/snapshot-borrow.d.ts.map +1 -0
  34. package/dist/snapshot-borrow.js +113 -0
  35. package/dist/snapshot-borrow.js.map +1 -0
  36. package/dist/string-index.d.ts.map +1 -1
  37. package/dist/string-index.js +8 -0
  38. package/dist/string-index.js.map +1 -1
  39. package/dist/tools/analyze-run.d.ts.map +1 -1
  40. package/dist/tools/analyze-run.js +79 -18
  41. package/dist/tools/analyze-run.js.map +1 -1
  42. package/dist/tools/anonymize-snapshot.d.ts +12 -0
  43. package/dist/tools/anonymize-snapshot.d.ts.map +1 -0
  44. package/dist/tools/anonymize-snapshot.js +159 -0
  45. package/dist/tools/anonymize-snapshot.js.map +1 -0
  46. package/dist/tools/app-config.d.ts +12 -0
  47. package/dist/tools/app-config.d.ts.map +1 -0
  48. package/dist/tools/app-config.js +217 -0
  49. package/dist/tools/app-config.js.map +1 -0
  50. package/dist/tools/app-heap.d.ts +18 -0
  51. package/dist/tools/app-heap.d.ts.map +1 -1
  52. package/dist/tools/app-heap.js +12 -46
  53. package/dist/tools/app-heap.js.map +1 -1
  54. package/dist/tools/artifact-budget.d.ts +12 -0
  55. package/dist/tools/artifact-budget.d.ts.map +1 -0
  56. package/dist/tools/artifact-budget.js +219 -0
  57. package/dist/tools/artifact-budget.js.map +1 -0
  58. package/dist/tools/async-census.d.ts +12 -0
  59. package/dist/tools/async-census.d.ts.map +1 -0
  60. package/dist/tools/async-census.js +243 -0
  61. package/dist/tools/async-census.js.map +1 -0
  62. package/dist/tools/auto-investigate.d.ts.map +1 -1
  63. package/dist/tools/auto-investigate.js +54 -5
  64. package/dist/tools/auto-investigate.js.map +1 -1
  65. package/dist/tools/batch.d.ts.map +1 -1
  66. package/dist/tools/batch.js +33 -5
  67. package/dist/tools/batch.js.map +1 -1
  68. package/dist/tools/cache-analysis.d.ts +15 -0
  69. package/dist/tools/cache-analysis.d.ts.map +1 -1
  70. package/dist/tools/cache-analysis.js +85 -10
  71. package/dist/tools/cache-analysis.js.map +1 -1
  72. package/dist/tools/census-diff.d.ts +55 -0
  73. package/dist/tools/census-diff.d.ts.map +1 -0
  74. package/dist/tools/census-diff.js +228 -0
  75. package/dist/tools/census-diff.js.map +1 -0
  76. package/dist/tools/chain-walk.d.ts +12 -0
  77. package/dist/tools/chain-walk.d.ts.map +1 -0
  78. package/dist/tools/chain-walk.js +184 -0
  79. package/dist/tools/chain-walk.js.map +1 -0
  80. package/dist/tools/collection-diff.d.ts +44 -0
  81. package/dist/tools/collection-diff.d.ts.map +1 -0
  82. package/dist/tools/collection-diff.js +343 -0
  83. package/dist/tools/collection-diff.js.map +1 -0
  84. package/dist/tools/collection-trend.d.ts.map +1 -1
  85. package/dist/tools/collection-trend.js +2 -2
  86. package/dist/tools/collection-trend.js.map +1 -1
  87. package/dist/tools/detached-dom.d.ts +3 -0
  88. package/dist/tools/detached-dom.d.ts.map +1 -1
  89. package/dist/tools/detached-dom.js +78 -3
  90. package/dist/tools/detached-dom.js.map +1 -1
  91. package/dist/tools/dev-artifacts.d.ts.map +1 -1
  92. package/dist/tools/dev-artifacts.js +221 -3
  93. package/dist/tools/dev-artifacts.js.map +1 -1
  94. package/dist/tools/diff-snapshots.d.ts.map +1 -1
  95. package/dist/tools/diff-snapshots.js +18 -2
  96. package/dist/tools/diff-snapshots.js.map +1 -1
  97. package/dist/tools/dom-audit.d.ts +37 -0
  98. package/dist/tools/dom-audit.d.ts.map +1 -0
  99. package/dist/tools/dom-audit.js +247 -0
  100. package/dist/tools/dom-audit.js.map +1 -0
  101. package/dist/tools/duplicated-strings.d.ts.map +1 -1
  102. package/dist/tools/duplicated-strings.js +3 -2
  103. package/dist/tools/duplicated-strings.js.map +1 -1
  104. package/dist/tools/eval-across.d.ts +12 -0
  105. package/dist/tools/eval-across.d.ts.map +1 -0
  106. package/dist/tools/eval-across.js +135 -0
  107. package/dist/tools/eval-across.js.map +1 -0
  108. package/dist/tools/eval.d.ts +78 -0
  109. package/dist/tools/eval.d.ts.map +1 -1
  110. package/dist/tools/eval.js +2270 -701
  111. package/dist/tools/eval.js.map +1 -1
  112. package/dist/tools/event-listener-leaks.d.ts.map +1 -1
  113. package/dist/tools/event-listener-leaks.js +42 -17
  114. package/dist/tools/event-listener-leaks.js.map +1 -1
  115. package/dist/tools/event-registry.d.ts.map +1 -1
  116. package/dist/tools/event-registry.js +42 -8
  117. package/dist/tools/event-registry.js.map +1 -1
  118. package/dist/tools/explain-delta.d.ts.map +1 -1
  119. package/dist/tools/explain-delta.js +61 -10
  120. package/dist/tools/explain-delta.js.map +1 -1
  121. package/dist/tools/finding-index.d.ts +181 -0
  122. package/dist/tools/finding-index.d.ts.map +1 -1
  123. package/dist/tools/finding-index.js +343 -24
  124. package/dist/tools/finding-index.js.map +1 -1
  125. package/dist/tools/get-value.d.ts +16 -0
  126. package/dist/tools/get-value.d.ts.map +1 -1
  127. package/dist/tools/get-value.js +96 -2
  128. package/dist/tools/get-value.js.map +1 -1
  129. package/dist/tools/hypothesis.d.ts.map +1 -1
  130. package/dist/tools/hypothesis.js +117 -58
  131. package/dist/tools/hypothesis.js.map +1 -1
  132. package/dist/tools/id-space-audit.d.ts +12 -0
  133. package/dist/tools/id-space-audit.d.ts.map +1 -0
  134. package/dist/tools/id-space-audit.js +262 -0
  135. package/dist/tools/id-space-audit.js.map +1 -0
  136. package/dist/tools/identify.d.ts +12 -0
  137. package/dist/tools/identify.d.ts.map +1 -0
  138. package/dist/tools/identify.js +205 -0
  139. package/dist/tools/identify.js.map +1 -0
  140. package/dist/tools/identity-diff.d.ts +12 -0
  141. package/dist/tools/identity-diff.d.ts.map +1 -0
  142. package/dist/tools/identity-diff.js +188 -0
  143. package/dist/tools/identity-diff.js.map +1 -0
  144. package/dist/tools/intern-opportunities.d.ts.map +1 -1
  145. package/dist/tools/intern-opportunities.js +4 -3
  146. package/dist/tools/intern-opportunities.js.map +1 -1
  147. package/dist/tools/ladder-probe.d.ts +65 -0
  148. package/dist/tools/ladder-probe.d.ts.map +1 -0
  149. package/dist/tools/ladder-probe.js +445 -0
  150. package/dist/tools/ladder-probe.js.map +1 -0
  151. package/dist/tools/ladder.d.ts.map +1 -1
  152. package/dist/tools/ladder.js +5 -5
  153. package/dist/tools/ladder.js.map +1 -1
  154. package/dist/tools/leak-report.d.ts +23 -0
  155. package/dist/tools/leak-report.d.ts.map +1 -1
  156. package/dist/tools/leak-report.js +153 -11
  157. package/dist/tools/leak-report.js.map +1 -1
  158. package/dist/tools/load-snapshot.d.ts +11 -0
  159. package/dist/tools/load-snapshot.d.ts.map +1 -1
  160. package/dist/tools/load-snapshot.js +83 -10
  161. package/dist/tools/load-snapshot.js.map +1 -1
  162. package/dist/tools/metric.d.ts +13 -0
  163. package/dist/tools/metric.d.ts.map +1 -0
  164. package/dist/tools/metric.js +185 -0
  165. package/dist/tools/metric.js.map +1 -0
  166. package/dist/tools/module-attribution.d.ts +36 -0
  167. package/dist/tools/module-attribution.d.ts.map +1 -0
  168. package/dist/tools/module-attribution.js +235 -0
  169. package/dist/tools/module-attribution.js.map +1 -0
  170. package/dist/tools/next-measurement.d.ts +26 -0
  171. package/dist/tools/next-measurement.d.ts.map +1 -0
  172. package/dist/tools/next-measurement.js +149 -0
  173. package/dist/tools/next-measurement.js.map +1 -0
  174. package/dist/tools/object-cost-breakdown.d.ts.map +1 -1
  175. package/dist/tools/object-cost-breakdown.js +64 -0
  176. package/dist/tools/object-cost-breakdown.js.map +1 -1
  177. package/dist/tools/population-vs-owners.d.ts +12 -0
  178. package/dist/tools/population-vs-owners.d.ts.map +1 -0
  179. package/dist/tools/population-vs-owners.js +183 -0
  180. package/dist/tools/population-vs-owners.js.map +1 -0
  181. package/dist/tools/property-names.d.ts +29 -0
  182. package/dist/tools/property-names.d.ts.map +1 -0
  183. package/dist/tools/property-names.js +131 -0
  184. package/dist/tools/property-names.js.map +1 -0
  185. package/dist/tools/quick-diagnosis.d.ts.map +1 -1
  186. package/dist/tools/quick-diagnosis.js +7 -1
  187. package/dist/tools/quick-diagnosis.js.map +1 -1
  188. package/dist/tools/react-owners.d.ts +12 -0
  189. package/dist/tools/react-owners.d.ts.map +1 -0
  190. package/dist/tools/react-owners.js +167 -0
  191. package/dist/tools/react-owners.js.map +1 -0
  192. package/dist/tools/react-update-queues.d.ts +12 -0
  193. package/dist/tools/react-update-queues.d.ts.map +1 -0
  194. package/dist/tools/react-update-queues.js +173 -0
  195. package/dist/tools/react-update-queues.js.map +1 -0
  196. package/dist/tools/replicate.d.ts +49 -0
  197. package/dist/tools/replicate.d.ts.map +1 -0
  198. package/dist/tools/replicate.js +241 -0
  199. package/dist/tools/replicate.js.map +1 -0
  200. package/dist/tools/retainer-diff.d.ts +12 -0
  201. package/dist/tools/retainer-diff.d.ts.map +1 -0
  202. package/dist/tools/retainer-diff.js +168 -0
  203. package/dist/tools/retainer-diff.js.map +1 -0
  204. package/dist/tools/retainer-summary.d.ts +17 -0
  205. package/dist/tools/retainer-summary.d.ts.map +1 -1
  206. package/dist/tools/retainer-summary.js +85 -22
  207. package/dist/tools/retainer-summary.js.map +1 -1
  208. package/dist/tools/retainer-trace.d.ts.map +1 -1
  209. package/dist/tools/retainer-trace.js +11 -1
  210. package/dist/tools/retainer-trace.js.map +1 -1
  211. package/dist/tools/round-audit.d.ts +66 -0
  212. package/dist/tools/round-audit.d.ts.map +1 -0
  213. package/dist/tools/round-audit.js +258 -0
  214. package/dist/tools/round-audit.js.map +1 -0
  215. package/dist/tools/round-digest.d.ts +12 -0
  216. package/dist/tools/round-digest.d.ts.map +1 -0
  217. package/dist/tools/round-digest.js +190 -0
  218. package/dist/tools/round-digest.js.map +1 -0
  219. package/dist/tools/script-census.d.ts +12 -0
  220. package/dist/tools/script-census.d.ts.map +1 -0
  221. package/dist/tools/script-census.js +182 -0
  222. package/dist/tools/script-census.js.map +1 -0
  223. package/dist/tools/sequence-analysis.d.ts +4 -0
  224. package/dist/tools/sequence-analysis.d.ts.map +1 -1
  225. package/dist/tools/sequence-analysis.js +53 -9
  226. package/dist/tools/sequence-analysis.js.map +1 -1
  227. package/dist/tools/server-status.d.ts.map +1 -1
  228. package/dist/tools/server-status.js +4 -1
  229. package/dist/tools/server-status.js.map +1 -1
  230. package/dist/tools/settle-check.d.ts +12 -0
  231. package/dist/tools/settle-check.d.ts.map +1 -0
  232. package/dist/tools/settle-check.js +188 -0
  233. package/dist/tools/settle-check.js.map +1 -0
  234. package/dist/tools/sliced-strings.d.ts.map +1 -1
  235. package/dist/tools/sliced-strings.js +2 -1
  236. package/dist/tools/sliced-strings.js.map +1 -1
  237. package/dist/tools/snapshots.d.ts.map +1 -1
  238. package/dist/tools/snapshots.js +24 -6
  239. package/dist/tools/snapshots.js.map +1 -1
  240. package/dist/tools/sparse-elements.d.ts +12 -0
  241. package/dist/tools/sparse-elements.d.ts.map +1 -0
  242. package/dist/tools/sparse-elements.js +215 -0
  243. package/dist/tools/sparse-elements.js.map +1 -0
  244. package/dist/tools/stale-collections.d.ts +17 -0
  245. package/dist/tools/stale-collections.d.ts.map +1 -1
  246. package/dist/tools/stale-collections.js +28 -0
  247. package/dist/tools/stale-collections.js.map +1 -1
  248. package/dist/tools/string-patterns.d.ts.map +1 -1
  249. package/dist/tools/string-patterns.js +2 -1
  250. package/dist/tools/string-patterns.js.map +1 -1
  251. package/dist/tools/tools-index.d.ts.map +1 -1
  252. package/dist/tools/tools-index.js +155 -5
  253. package/dist/tools/tools-index.js.map +1 -1
  254. package/dist/tools/trace-all.d.ts +42 -0
  255. package/dist/tools/trace-all.d.ts.map +1 -0
  256. package/dist/tools/trace-all.js +263 -0
  257. package/dist/tools/trace-all.js.map +1 -0
  258. package/dist/tools/unit-cost.d.ts +12 -0
  259. package/dist/tools/unit-cost.d.ts.map +1 -0
  260. package/dist/tools/unit-cost.js +219 -0
  261. package/dist/tools/unit-cost.js.map +1 -0
  262. package/dist/tools/verify-fix.d.ts.map +1 -1
  263. package/dist/tools/verify-fix.js +11 -3
  264. package/dist/tools/verify-fix.js.map +1 -1
  265. package/dist/tools/weakref-census.d.ts +15 -0
  266. package/dist/tools/weakref-census.d.ts.map +1 -0
  267. package/dist/tools/weakref-census.js +147 -0
  268. package/dist/tools/weakref-census.js.map +1 -0
  269. package/dist/tools/what-if.d.ts +12 -0
  270. package/dist/tools/what-if.d.ts.map +1 -0
  271. package/dist/tools/what-if.js +285 -0
  272. package/dist/tools/what-if.js.map +1 -0
  273. package/dist/utils.d.ts +92 -2
  274. package/dist/utils.d.ts.map +1 -1
  275. package/dist/utils.js +172 -5
  276. package/dist/utils.js.map +1 -1
  277. package/package.json +3 -3
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ /**
11
+ * Recognising React structures in a heap snapshot.
12
+ *
13
+ * Fibers, hooks, update queues and update records are ALL plain `Object` in a
14
+ * production bundle, so every class-name-based heuristic walks straight past
15
+ * them. On one population `helpers.owner()` returned `(none)` for 100% of
16
+ * 1,645 records for exactly this reason, and the useful answer —
17
+ * `BaseTooltipSimple` — needed a hand-written walk to the fiber plus a read of
18
+ * `elementType`.
19
+ *
20
+ * That walk has now been hand-written for four findings of the same family
21
+ * across this workstream. The `elementType` / `stateNode` / `memoizedProps`
22
+ * layout is stable across every React version Meta ships, so it belongs here
23
+ * rather than in each investigation.
24
+ */
25
+ import type { IHeapNode } from '@memlab/core';
26
+ /** Property set of a React update record — the eager-bailout leak family. */
27
+ export declare const UPDATE_RECORD_FIELDS: string[];
28
+ /** Does this node look like a React fiber? */
29
+ export declare function isFiberNode(node: IHeapNode): boolean;
30
+ /** Does this node look like a React update record (`queue.pending` member)? */
31
+ export declare function isUpdateRecord(node: IHeapNode): boolean;
32
+ /**
33
+ * The component name behind a fiber.
34
+ *
35
+ * Three shapes, in the order they occur: `elementType` is a closure (function
36
+ * component — the closure's own name is the component name), or a string (host
37
+ * element), or an object carrying `render` / `type` (memo, forwardRef, lazy),
38
+ * in which case the name is one level deeper.
39
+ */
40
+ export declare function fiberComponentName(fiber: IHeapNode): string | null;
41
+ export declare function nearestFiber(node: IHeapNode, maxHops: number): IHeapNode | null;
42
+ //# sourceMappingURL=react-shapes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-shapes.d.ts","sourceRoot":"","sources":["../src/react-shapes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAe5C,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,UAMhC,CAAC;AAYF,8CAA8C;AAC9C,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAOpD;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAWvD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAOlE;AAyCD,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,MAAM,GACd,SAAS,GAAG,IAAI,CAsBlB"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ /**
11
+ * Fields that only a fiber carries. `stateNode` alone is too weak (a class
12
+ * instance has one), so a match needs `elementType` or `memoizedProps`, which
13
+ * together with `return`/`child` are fiber-specific.
14
+ */
15
+ const FIBER_STRONG_FIELDS = ['elementType', 'memoizedProps', 'pendingProps'];
16
+ const FIBER_SUPPORTING_FIELDS = [
17
+ 'return',
18
+ 'child',
19
+ 'stateNode',
20
+ 'memoizedState',
21
+ ];
22
+ /** Property set of a React update record — the eager-bailout leak family. */
23
+ export const UPDATE_RECORD_FIELDS = [
24
+ 'action',
25
+ 'eagerState',
26
+ 'hasEagerState',
27
+ 'lane',
28
+ 'next',
29
+ ];
30
+ /** Own property names of a node, as a Set. Cheap; used by every check here. */
31
+ function propertyNames(node) {
32
+ const names = new Set();
33
+ for (const edge of node.references) {
34
+ if (edge.type !== 'property')
35
+ continue;
36
+ names.add(String(edge.name_or_index));
37
+ }
38
+ return names;
39
+ }
40
+ /** Does this node look like a React fiber? */
41
+ export function isFiberNode(node) {
42
+ if (node.type !== 'object')
43
+ return false;
44
+ const names = propertyNames(node);
45
+ const strong = FIBER_STRONG_FIELDS.filter(f => names.has(f)).length;
46
+ if (strong === 0)
47
+ return false;
48
+ const supporting = FIBER_SUPPORTING_FIELDS.filter(f => names.has(f)).length;
49
+ return strong + supporting >= 3;
50
+ }
51
+ /** Does this node look like a React update record (`queue.pending` member)? */
52
+ export function isUpdateRecord(node) {
53
+ if (node.type !== 'object')
54
+ return false;
55
+ const names = propertyNames(node);
56
+ // `eagerState` may be absent when the update never took the eager path, so
57
+ // require the rest and treat it as optional.
58
+ return (names.has('action') &&
59
+ names.has('lane') &&
60
+ names.has('next') &&
61
+ (names.has('hasEagerState') || names.has('eagerState')));
62
+ }
63
+ /**
64
+ * The component name behind a fiber.
65
+ *
66
+ * Three shapes, in the order they occur: `elementType` is a closure (function
67
+ * component — the closure's own name is the component name), or a string (host
68
+ * element), or an object carrying `render` / `type` (memo, forwardRef, lazy),
69
+ * in which case the name is one level deeper.
70
+ */
71
+ export function fiberComponentName(fiber) {
72
+ for (const edge of fiber.references) {
73
+ if (edge.type !== 'property')
74
+ continue;
75
+ if (String(edge.name_or_index) !== 'elementType')
76
+ continue;
77
+ return elementTypeName(edge.toNode, 0);
78
+ }
79
+ return null;
80
+ }
81
+ function elementTypeName(node, depth) {
82
+ if (depth > 2)
83
+ return null;
84
+ if (node.type === 'string' || node.type === 'concatenated string') {
85
+ const value = node.toStringNode()?.stringValue ?? node.name;
86
+ return value ? `<${value}>` : null;
87
+ }
88
+ if (node.type === 'closure') {
89
+ // A minified bundle still names the closure after the component in the
90
+ // overwhelming majority of cases; an empty name is worth reporting as
91
+ // unknown rather than as "".
92
+ return node.name && node.name !== 'function' ? node.name : null;
93
+ }
94
+ // memo / forwardRef / lazy wrappers.
95
+ for (const edge of node.references) {
96
+ if (edge.type !== 'property')
97
+ continue;
98
+ const name = String(edge.name_or_index);
99
+ if (name !== 'render' && name !== 'type')
100
+ continue;
101
+ const inner = elementTypeName(edge.toNode, depth + 1);
102
+ if (inner != null)
103
+ return inner;
104
+ }
105
+ return null;
106
+ }
107
+ /**
108
+ * Walk up from a hook/queue/record to the fiber that owns it.
109
+ *
110
+ * Prefers the edges that actually lead there — a hook's owner is reached via
111
+ * `memoizedState` / `next` / `queue` / `baseQueue`, not by whatever referrer
112
+ * happens to come first — and falls back to any referrer so a slightly
113
+ * different shape still resolves.
114
+ */
115
+ const OWNER_EDGE_PREFERENCE = [
116
+ 'memoizedState',
117
+ 'queue',
118
+ 'baseQueue',
119
+ 'next',
120
+ 'return',
121
+ ];
122
+ export function nearestFiber(node, maxHops) {
123
+ let current = node;
124
+ const seen = new Set([node.id]);
125
+ for (let hop = 0; hop < maxHops; hop++) {
126
+ if (isFiberNode(current))
127
+ return current;
128
+ let chosen = null;
129
+ let fallback = null;
130
+ for (const edge of current.referrers) {
131
+ const from = edge.fromNode;
132
+ if (from.id <= 3 || seen.has(from.id))
133
+ continue;
134
+ if (OWNER_EDGE_PREFERENCE.includes(String(edge.name_or_index))) {
135
+ chosen = from;
136
+ break;
137
+ }
138
+ if (fallback == null)
139
+ fallback = from;
140
+ }
141
+ const next = chosen ?? fallback;
142
+ if (next == null)
143
+ return null;
144
+ seen.add(next.id);
145
+ current = next;
146
+ }
147
+ return null;
148
+ }
149
+ //# sourceMappingURL=react-shapes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-shapes.js","sourceRoot":"","sources":["../src/react-shapes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ /**
11
+ * The version this server reports about itself.
12
+ *
13
+ * Kept in its own module so `memlab_server_status` can report it without
14
+ * importing `index.ts` (which would be an import cycle — index imports every
15
+ * tool). Keep in sync with `package.json`; `scripts/check-version-sync.mjs`
16
+ * enforces that.
17
+ */
18
+ export declare const SERVER_VERSION = "2.86.0";
19
+ //# sourceMappingURL=server-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-version.d.ts","sourceRoot":"","sources":["../src/server-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,WAAW,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ /**
11
+ * The version this server reports about itself.
12
+ *
13
+ * Kept in its own module so `memlab_server_status` can report it without
14
+ * importing `index.ts` (which would be an import cycle — index imports every
15
+ * tool). Keep in sync with `package.json`; `scripts/check-version-sync.mjs`
16
+ * enforces that.
17
+ */
18
+ export const SERVER_VERSION = '2.86.0';
19
+ //# sourceMappingURL=server-version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-version.js","sourceRoot":"","sources":["../src/server-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import type { IHeapSnapshot } from '@memlab/core';
11
+ /**
12
+ * Run `fn` against the snapshot at `localPath`, loading it only if it is not
13
+ * already resident, and leaving the session exactly as it was found.
14
+ *
15
+ * Every tool that walks an ORDERED ladder needs this, and each one getting it
16
+ * subtly wrong is the failure mode worth designing out. Three invariants:
17
+ *
18
+ * - **Reuse a resident graph.** Re-parsing a 250 MB capture that is already in
19
+ * memory is the single most expensive avoidable thing these tools do.
20
+ * - **Hold one graph at a time.** `memlab_eval_across` needs every rung
21
+ * resident simultaneously, which is precisely why it cannot serve a ladder of
22
+ * large captures. Loading and dropping keeps peak RSS at one rung.
23
+ * - **Restore the caller's active snapshot.** A ladder walk is a measurement,
24
+ * not a navigation; silently leaving the session pointed at rung 6 makes the
25
+ * caller's next `memlab_eval` answer a question about the wrong capture, and
26
+ * nothing in that output says so.
27
+ */
28
+ export declare function withSnapshotAt<T>(localPath: string, fn: (snapshot: IHeapSnapshot) => Promise<T> | T): Promise<T>;
29
+ export interface ResolvedRung {
30
+ /** The path as the caller wrote it, used in report rows. */
31
+ label: string;
32
+ localPath: string;
33
+ sizeMB: number;
34
+ }
35
+ /**
36
+ * Resolve every path and check its size BEFORE anything is loaded.
37
+ *
38
+ * Ordering matters here: an unreadable or oversized rung five should fail in a
39
+ * second, not after four multi-minute loads have already been paid for.
40
+ */
41
+ export declare function resolveRungs(paths: string[], maxFileSizeMB?: number): {
42
+ rungs: ResolvedRung[];
43
+ largestMB: number;
44
+ };
45
+ /**
46
+ * A full-heap walk on a multi-million-node graph does not finish inside the
47
+ * 60 s eval default, and every caller discovering that by timing out first is
48
+ * pure friction. Scale from the largest rung.
49
+ */
50
+ export declare function scaledTimeoutMs(largestMB: number, explicit?: number): number;
51
+ /**
52
+ * Re-arm the whole-heap scan guardrail for the work about to be done.
53
+ *
54
+ * Scaling a tool's own `timeout_ms` is NOT enough on its own, which is only
55
+ * obvious once you run against a big graph. The guardrail in `guardrail.ts`
56
+ * arms a 90s scan budget from the tool's incoming params before the handler
57
+ * runs; a tool that then computes a larger per-snapshot timeout is still inside
58
+ * that 90s budget, so a full-heap walk dies at 90s no matter what it asked for.
59
+ * Measured on a real 690 MB / 5.1M-node Ads Manager capture: a detached-node
60
+ * probe raised its eval timeout to 414s and still lost the rung to
61
+ * `Scan exceeded its 90000ms budget`.
62
+ *
63
+ * Re-arming per snapshot is what `memlab_analyze_run` already does for its
64
+ * steps. The guardrail's own `finally` disarms afterwards, so nothing leaks.
65
+ */
66
+ export declare function armScanBudgetFor(timeoutMs: number): void;
67
+ //# sourceMappingURL=snapshot-borrow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-borrow.d.ts","sourceRoot":"","sources":["../src/snapshot-borrow.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAkBhD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAC9C,OAAO,CAAC,CAAC,CAAC,CA+BZ;AAED,MAAM,WAAW,YAAY;IAC3B,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EAAE,EACf,aAAa,CAAC,EAAE,MAAM,GACrB;IAAC,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,CAoB5C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAExD"}
@@ -0,0 +1,113 @@
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 fs from 'fs';
11
+ import memlabHeapAnalysis from '@memlab/heap-analysis';
12
+ const { getFullHeapFromFile } = memlabHeapAnalysis;
13
+ import { findResidentByPath, getCurrentHandle, getSnapshotByHandle, removeSnapshot, setCurrentSnapshot, setSnapshot, } from './heap-state.js';
14
+ import { beginAnalysisBudget } from './analysis-budget.js';
15
+ import { resolveMaxFileSizeMB, resolveSnapshotPath, } from './tools/load-snapshot.js';
16
+ /**
17
+ * Run `fn` against the snapshot at `localPath`, loading it only if it is not
18
+ * already resident, and leaving the session exactly as it was found.
19
+ *
20
+ * Every tool that walks an ORDERED ladder needs this, and each one getting it
21
+ * subtly wrong is the failure mode worth designing out. Three invariants:
22
+ *
23
+ * - **Reuse a resident graph.** Re-parsing a 250 MB capture that is already in
24
+ * memory is the single most expensive avoidable thing these tools do.
25
+ * - **Hold one graph at a time.** `memlab_eval_across` needs every rung
26
+ * resident simultaneously, which is precisely why it cannot serve a ladder of
27
+ * large captures. Loading and dropping keeps peak RSS at one rung.
28
+ * - **Restore the caller's active snapshot.** A ladder walk is a measurement,
29
+ * not a navigation; silently leaving the session pointed at rung 6 makes the
30
+ * caller's next `memlab_eval` answer a question about the wrong capture, and
31
+ * nothing in that output says so.
32
+ */
33
+ export async function withSnapshotAt(localPath, fn) {
34
+ const resident = findResidentByPath(localPath);
35
+ let snapshot = resident != null ? getSnapshotByHandle(resident.handle) : null;
36
+ const previousHandle = getCurrentHandle();
37
+ let temporaryHandle = null;
38
+ try {
39
+ if (snapshot == null) {
40
+ snapshot = await getFullHeapFromFile(localPath);
41
+ const meta = setSnapshot(snapshot, localPath, {
42
+ fileName: localPath.replace(/^.*\//, ''),
43
+ nodeCount: snapshot.nodes?.length ?? 0,
44
+ edgeCount: snapshot.edges?.length ?? 0,
45
+ totalSize: 0,
46
+ env: 'unknown',
47
+ }, { replace: false });
48
+ temporaryHandle = meta.handle;
49
+ }
50
+ else if (resident != null) {
51
+ setCurrentSnapshot(resident.handle);
52
+ }
53
+ return await fn(snapshot);
54
+ }
55
+ finally {
56
+ if (temporaryHandle != null)
57
+ removeSnapshot(temporaryHandle);
58
+ if (previousHandle != null)
59
+ setCurrentSnapshot(previousHandle);
60
+ }
61
+ }
62
+ /**
63
+ * Resolve every path and check its size BEFORE anything is loaded.
64
+ *
65
+ * Ordering matters here: an unreadable or oversized rung five should fail in a
66
+ * second, not after four multi-minute loads have already been paid for.
67
+ */
68
+ export function resolveRungs(paths, maxFileSizeMB) {
69
+ const rungs = [];
70
+ let largestMB = 0;
71
+ for (const p of paths) {
72
+ const { localPath, fetchedFrom } = resolveSnapshotPath(p);
73
+ if (!fs.existsSync(localPath)) {
74
+ throw new Error(`File not found: ${localPath}`);
75
+ }
76
+ const sizeMB = fs.statSync(localPath).size / (1024 * 1024);
77
+ largestMB = Math.max(largestMB, sizeMB);
78
+ const limit = resolveMaxFileSizeMB(maxFileSizeMB, fetchedFrom != null);
79
+ if (sizeMB > limit) {
80
+ throw new Error(`${p} is ${sizeMB.toFixed(0)} MB — exceeds the ${limit} MB per-file ` +
81
+ 'limit. Raise it with max_file_size_mb.');
82
+ }
83
+ rungs.push({ label: p.replace(/^.*\//, ''), localPath, sizeMB });
84
+ }
85
+ return { rungs, largestMB };
86
+ }
87
+ /**
88
+ * A full-heap walk on a multi-million-node graph does not finish inside the
89
+ * 60 s eval default, and every caller discovering that by timing out first is
90
+ * pure friction. Scale from the largest rung.
91
+ */
92
+ export function scaledTimeoutMs(largestMB, explicit) {
93
+ return explicit ?? Math.max(60000, Math.ceil(largestMB * 600));
94
+ }
95
+ /**
96
+ * Re-arm the whole-heap scan guardrail for the work about to be done.
97
+ *
98
+ * Scaling a tool's own `timeout_ms` is NOT enough on its own, which is only
99
+ * obvious once you run against a big graph. The guardrail in `guardrail.ts`
100
+ * arms a 90s scan budget from the tool's incoming params before the handler
101
+ * runs; a tool that then computes a larger per-snapshot timeout is still inside
102
+ * that 90s budget, so a full-heap walk dies at 90s no matter what it asked for.
103
+ * Measured on a real 690 MB / 5.1M-node Ads Manager capture: a detached-node
104
+ * probe raised its eval timeout to 414s and still lost the rung to
105
+ * `Scan exceeded its 90000ms budget`.
106
+ *
107
+ * Re-arming per snapshot is what `memlab_analyze_run` already does for its
108
+ * steps. The guardrail's own `finally` disarms afterwards, so nothing leaks.
109
+ */
110
+ export function armScanBudgetFor(timeoutMs) {
111
+ beginAnalysisBudget(timeoutMs);
112
+ }
113
+ //# sourceMappingURL=snapshot-borrow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-borrow.js","sourceRoot":"","sources":["../src/snapshot-borrow.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"string-index.d.ts","sourceRoot":"","sources":["../src/string-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAE,aAAa,EAAC,MAAM,cAAc,CAAC;AAG3D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACrC,6CAA6C;IAC7C,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAQD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,aAAa,EACvB,IAAI,GAAE;IACJ,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC;CACzC,GACL,WAAW,CAoDb;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C"}
1
+ {"version":3,"file":"string-index.d.ts","sourceRoot":"","sources":["../src/string-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAE,aAAa,EAAC,MAAM,cAAc,CAAC;AAG3D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACrC,6CAA6C;IAC7C,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAQD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,aAAa,EACvB,IAAI,GAAE;IACJ,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC;CACzC,GACL,WAAW,CA8Db;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C"}
@@ -33,6 +33,14 @@ export function buildStringIndex(snapshot, opts = {}) {
33
33
  const scratch = getEvalScratch();
34
34
  const cached = scratch[CACHE_KEY];
35
35
  const wantDevOnly = opts.withDevOnly === true;
36
+ // `withDevOnly` without an `isDevOnlyNode` is a programmer error, not a
37
+ // degraded mode: without the predicate the pass cannot run, so the index
38
+ // would be built with `hasDevOnly: false`, fail the cache-reuse guard below,
39
+ // and be rebuilt in full on every call. Refuse instead of silently
40
+ // re-scanning the whole graph forever.
41
+ if (wantDevOnly && opts.isDevOnlyNode == null) {
42
+ throw new Error('buildStringIndex: withDevOnly requires isDevOnlyNode; without it the dev-root pass cannot run.');
43
+ }
36
44
  if (cached && (!wantDevOnly || cached.hasDevOnly)) {
37
45
  return cached;
38
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"string-index.js","sourceRoot":"","sources":["../src/string-index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"string-index.js","sourceRoot":"","sources":["../src/string-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAyB/C,uEAAuE;AACvE,oEAAoE;AACpE,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,MAAM,SAAS,GAAG,qBAAqB,CAAC;AAExC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAuB,EACvB,OAGI,EAAE;IAEN,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAA4B,CAAC;IAC7D,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC;IAC9C,wEAAwE;IACxE,yEAAyE;IACzE,6EAA6E;IAC7E,mEAAmE;IACnE,uCAAuC;IACvC,IAAI,WAAW,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CACb,gGAAgG,CACjG,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,IAAI,CAAC,CAAC,WAAW,IAAI,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QAClD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;IAClD,IAAI,iBAAiB,GAAG,CAAC,CAAC;IAC1B,IAAI,gBAAgB,GAAG,CAAC,CAAC;IAEzB,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;YAC1C,iBAAiB,EAAE,CAAC;YACpB,gBAAgB,IAAI,IAAI,CAAC,SAAS,CAAC;QACrC,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO;QACnC,uEAAuE;QACvE,kEAAkE;QAClE,IAAI,IAAI,CAAC,IAAI,KAAK,uBAAuB;YAAE,OAAO;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACpC,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC;QAClC,MAAM,OAAO,GAAG,SAAS,IAAI,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,YAAY,CAAC;YACrC,IAAI,OAAO;gBAAE,KAAK,CAAC,YAAY,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;gBAC9C,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE;gBACjB,KAAK,EAAE,CAAC;gBACR,SAAS,EAAE,IAAI,CAAC,YAAY;gBAC5B,UAAU,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrB,YAAY,EAAE,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aACvD,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAgB;QACzB,OAAO;QACP,UAAU,EAAE,SAAS,IAAI,IAAI;QAC7B,iBAAiB;QACjB,gBAAgB;KACjB,CAAC;IACF,OAAO,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC;IAC3B,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,mBAAmB;IACjC,OAAO,cAAc,EAAE,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC;AAC7C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"analyze-run.d.ts","sourceRoot":"","sources":["../../src/tools/analyze-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AA6KvE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8U1D"}
1
+ {"version":3,"file":"analyze-run.d.ts","sourceRoot":"","sources":["../../src/tools/analyze-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAoMvE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2Y1D"}
@@ -15,7 +15,7 @@ import { getRegisteredTool } from '../tool-registry.js';
15
15
  import { activeElapsedMs, activeTimeoutMs_, beginAnalysisBudget, endAnalysisBudget, ScanTimeoutError, } from '../analysis-budget.js';
16
16
  import { clearAllSnapshots, listSnapshots, setCurrentSnapshot, setSnapshot, } from '../heap-state.js';
17
17
  import { artifactLabel } from '../artifact-classes.js';
18
- import { errorResult, formatBytes, formatNumber, markdownTable, toolResult, } from '../utils.js';
18
+ import { errorResult, formatBytes, formatNumber, markdownTable, pathsHeader, toolResult, } from '../utils.js';
19
19
  import { computeSequenceTrends } from './sequence-analysis.js';
20
20
  const { getFullHeapFromFile } = memlabHeapAnalysis;
21
21
  /**
@@ -44,10 +44,17 @@ const { getFullHeapFromFile } = memlabHeapAnalysis;
44
44
  */
45
45
  async function runTool(name, args, budgetMs, unanalyzed) {
46
46
  const entry = getRegisteredTool(name);
47
- if (entry == null)
47
+ if (entry == null) {
48
+ // A step that did not run belongs under UNANALYZED whatever the reason.
49
+ // Returning only the inline note let a misspelled step name — or a tool
50
+ // missing from this server build — print "every step of the protocol ran"
51
+ // underneath a section that never executed.
52
+ unanalyzed.push(`${name} is not registered on this server`);
48
53
  return `_(${name} is not registered on this server)_`;
54
+ }
49
55
  const outerRemaining = Math.max(0, activeTimeoutMs_() - activeElapsedMs());
50
56
  const outerTimeout = activeTimeoutMs_();
57
+ const stepStart = Date.now();
51
58
  beginAnalysisBudget(budgetMs);
52
59
  try {
53
60
  const res = await entry.handler(args, {});
@@ -85,9 +92,15 @@ async function runTool(name, args, budgetMs, unanalyzed) {
85
92
  finally {
86
93
  // Restore the caller's budget, minus what this step consumed, so the
87
94
  // remaining steps still run under the overall timeout rather than getting
88
- // a fresh one each.
95
+ // a fresh one each. The subtraction is the whole point:
96
+ // `beginAnalysisBudget` resets the clock unconditionally, so restoring the
97
+ // pre-step remaining would pause the outer guardrail for the duration of
98
+ // every step and let a chain of N steps overrun `timeout_ms` by the sum of
99
+ // their wall clocks. The floor of 1ms keeps the budget ARMED once it is
100
+ // exhausted (0 would disable it), so the next step trips immediately.
89
101
  if (outerTimeout > 0) {
90
- beginAnalysisBudget(Math.max(1, outerRemaining));
102
+ const stepElapsed = Date.now() - stepStart;
103
+ beginAnalysisBudget(Math.max(1, outerRemaining - stepElapsed));
91
104
  }
92
105
  else {
93
106
  endAnalysisBudget();
@@ -186,29 +199,77 @@ export function registerAnalyzeRun(server) {
186
199
  if (missing > 0) {
187
200
  unanalyzed.push(`${missing} rung file(s) from the manifest are missing on disk and were excluded.`);
188
201
  }
189
- const paths = rungs.map(r => r.path);
190
- const first = rungs[0];
191
- const last = rungs[rungs.length - 1];
202
+ // A mid-run page reload starts a NEW V8 isolate, so deltas ACROSS the
203
+ // split compare two unrelated heaps. hunt_runner already detects this
204
+ // and records it; nothing downstream was reading it, so the trend was
205
+ // fitted straight through the discontinuity. On one measured round that
206
+ // turned a real +2.1 MB into a reported -3.6 MB — the sign flipped.
207
+ // Analyse the longest unbroken SEGMENT instead, and say so.
208
+ const allRungs = rungs;
209
+ const segments = [];
210
+ for (const rung of allRungs) {
211
+ if (segments.length === 0 || rung.isolate_restarted) {
212
+ segments.push([rung]);
213
+ }
214
+ else {
215
+ segments[segments.length - 1].push(rung);
216
+ }
217
+ }
218
+ const longest = segments.reduce((best, seg) => (seg.length > best.length ? seg : best), segments[0] ?? []);
219
+ const ladderSplit = segments.length > 1;
220
+ if (ladderSplit && longest.length < 2) {
221
+ return errorResult(`The page reloaded ${segments.length - 1} time(s) during this run, and no unbroken segment has 2 or more rungs. ` +
222
+ 'Every delta would compare two different V8 isolates, so there is nothing here to fit a trend to. ' +
223
+ 'Re-drive the round without the reload — a positive control that COLLAPSES across a split is a corrupted ladder, not a failed control.');
224
+ }
225
+ const analysed = ladderSplit ? longest : allRungs;
226
+ const paths = analysed.map(r => r.path);
227
+ const first = analysed[0];
228
+ const last = analysed[analysed.length - 1];
192
229
  const perRungCycles = cycles_between_rungs ??
193
- (rungs.length > 1
194
- ? Math.round((last.cycles - first.cycles) / (rungs.length - 1))
230
+ (analysed.length > 1
231
+ ? Math.round((last.cycles - first.cycles) / (analysed.length - 1))
195
232
  : 0);
196
233
  const out = [
197
234
  `# Leak-hunt analysis — ${manifest.run_id ?? path.basename(path.dirname(manifestPath))}`,
198
235
  '',
199
- `Combos: ${(manifest.config?.combos ?? []).join(', ') || '(unrecorded)'} · cycles driven: ${formatNumber(manifest.totals?.cycles ?? last.cycles)} (ok ${formatNumber(manifest.totals?.ok ?? 0)} / fail ${formatNumber(manifest.totals?.fail ?? 0)}) · ${rungs.length} rungs · ~${formatNumber(perRungCycles)} cycles between rungs`,
236
+ `Combos: ${(manifest.config?.combos ?? []).join(', ') || '(unrecorded)'} · cycles driven: ${formatNumber(manifest.totals?.cycles ?? last.cycles)} (ok ${formatNumber(manifest.totals?.ok ?? 0)} / fail ${formatNumber(manifest.totals?.fail ?? 0)}) · ${analysed.length} rungs analysed${ladderSplit ? ` of ${allRungs.length} captured` : ''} · ~${formatNumber(perRungCycles)} cycles between rungs`,
200
237
  manifest.stop_reason ? `Stop reason: ${manifest.stop_reason}` : '',
201
238
  '',
202
239
  '## Ladder',
203
240
  '',
204
- markdownTable(['Rung', 'Cycles', 'Post-GC heap', 'Reason'], rungs.map(r => [
205
- String(r.index),
206
- formatNumber(r.cycles),
207
- `${r.post_gc_heap_mb.toFixed(1)} MB`,
208
- r.reason ?? '',
209
- ]), new Set([0, 1, 2])),
241
+ markdownTable(
242
+ // The exclusion column only exists when something was excluded.
243
+ // Emitting it unconditionally left an unnamed, empty fifth column
244
+ // on every unsplit ladder — which is most of them.
245
+ ladderSplit
246
+ ? ['Rung', 'Cycles', 'Post-GC heap', 'Reason', 'Analysed?']
247
+ : ['Rung', 'Cycles', 'Post-GC heap', 'Reason'], allRungs.map(r => {
248
+ const row = [
249
+ String(r.index),
250
+ formatNumber(r.cycles),
251
+ `${r.post_gc_heap_mb.toFixed(1)} MB`,
252
+ r.reason ?? '',
253
+ ];
254
+ if (ladderSplit) {
255
+ row.push(analysed.includes(r)
256
+ ? 'yes'
257
+ : r.isolate_restarted
258
+ ? 'no — reload, new isolate'
259
+ : 'no — other segment');
260
+ }
261
+ return row;
262
+ }), new Set([0, 1, 2])),
210
263
  '',
211
264
  ].filter(Boolean);
265
+ if (ladderSplit) {
266
+ out.push(`> ⚠️ **LADDER SPLIT — the page reloaded ${segments.length - 1} time(s) mid-run.** A reload starts a new V8 ` +
267
+ `isolate, so any delta spanning the split compares two unrelated heaps. This analysis uses only the ` +
268
+ `longest unbroken segment (rungs ${longest[0].index}–${longest[longest.length - 1].index}, ` +
269
+ `${longest.length} of ${allRungs.length}); the other rungs are excluded above. ` +
270
+ `Segment lengths: ${segments.map(seg => seg.length).join(', ')}. ` +
271
+ `If a positive control COLLAPSED in this round, read that as a corrupted ladder rather than a failed control.`, '');
272
+ }
212
273
  const heapDelta = last.post_gc_heap_mb - first.post_gc_heap_mb;
213
274
  out.push(`Aggregate post-GC heap moved **${heapDelta >= 0 ? '+' : ''}${heapDelta.toFixed(1)} MB** across the run. Aggregate heap is the weakest signal here and is reported for completeness only: a measured round found a real leak (+6.25 undo entries/cycle, linear across five rungs) while aggregate heap FELL 8.9 MB. The per-class trend below is what decides.`, '');
214
275
  // ---- Step 1: ladder trend (transient loads, one graph at a time) ----
@@ -245,7 +306,7 @@ export function registerAnalyzeRun(server) {
245
306
  `+${formatNumber(g.netCount)}`,
246
307
  perRungCycles > 0
247
308
  ? (g.netCount /
248
- (perRungCycles * (rungs.length - 1))).toFixed(2)
309
+ (perRungCycles * (analysed.length - 1))).toFixed(2)
249
310
  : '—',
250
311
  `${g.netSize >= 0 ? '+' : ''}${formatBytes(g.netSize)}`,
251
312
  g.artifact != null
@@ -333,7 +394,7 @@ export function registerAnalyzeRun(server) {
333
394
  if (resident) {
334
395
  out.push(`_Resident snapshots: ${resident}. Follow up on a specific object with \`memlab_dominator_chain\` / \`memlab_retainer_trace\`, or unload them with \`memlab_snapshots\`._`);
335
396
  }
336
- return toolResult(out.join('\n'));
397
+ return toolResult(out.join('\n'), pathsHeader(paths.map(p => p.replace(/^.*\//, ''))));
337
398
  }
338
399
  catch (err) {
339
400
  return errorResult(err);