sparkforensics-server 0.5.0 → 0.5.2

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 (309) hide show
  1. package/package.json +3 -3
  2. package/public/assets/{AutoscalingChurn-9Zo6IMGS.js → AutoscalingChurn-8t3U3isq.js} +1 -1
  3. package/public/assets/{CacheUtilization-MBDfMsKC.js → CacheUtilization-CJs9tTum.js} +1 -1
  4. package/public/assets/CachingOpportunity-D1NcHs0V.js +1 -0
  5. package/public/assets/{ColdStart-BtS12b6_.js → ColdStart-CdRJ9gD8.js} +1 -1
  6. package/public/assets/{ConfigAudit-DNPV6EQg.js → ConfigAudit-B5Gbe5js.js} +1 -1
  7. package/public/assets/CoreUsageArea-a--NTRGY.js +1 -0
  8. package/public/assets/{Dashboard-CC_BXrNy.js → Dashboard-C1hNeX49.js} +2 -2
  9. package/public/assets/DisclosureContext-ZpE6CkHx.js +1 -0
  10. package/public/assets/{DuplicatePlanSubtree-vN3zqRsi.js → DuplicatePlanSubtree-dobXdz7E.js} +1 -1
  11. package/public/assets/{ExecutorUtilization-CZObSLIj.js → ExecutorUtilization-D9grwi7t.js} +1 -1
  12. package/public/assets/{GcPressure-ByJlLOua.js → GcPressure-8-G2ri10.js} +1 -1
  13. package/public/assets/{IncompleteRun-MzqV4W8y.js → IncompleteRun-B91lky7a.js} +1 -1
  14. package/public/assets/{JobFailures-LbMvAwAb.js → JobFailures-BsWvBMyJ.js} +1 -1
  15. package/public/assets/{MemoryUtilization-DH4jYvJK.js → MemoryUtilization-BM5schZI.js} +1 -1
  16. package/public/assets/{OverBroadcast-euk6HKg4.js → OverBroadcast-DdN8JIJH.js} +1 -1
  17. package/public/assets/{PartitionHint-DThaMMRT.js → PartitionHint-CUvbnnxy.js} +1 -1
  18. package/public/assets/{PartitionSizing-yIN-iZzx.js → PartitionSizing-CY5HDYtp.js} +1 -1
  19. package/public/assets/{PlanExplorer-Dd9WPK4S.js → PlanExplorer-DgHCDBD_.js} +1 -1
  20. package/public/assets/PlanGraphRoute-DH3KJb8j.js +1 -0
  21. package/public/assets/{RetryWaste-CXH4D-xH.js → RetryWaste-DOnE9vGF.js} +1 -1
  22. package/public/assets/{ShuffleIO-CW7U68ph.js → ShuffleIO-MMnDESkF.js} +1 -1
  23. package/public/assets/{Skew-PL79TppX.js → Skew-BmZZJ5ms.js} +1 -1
  24. package/public/assets/{SlowHost-GBO73hCi.js → SlowHost-CsJzfEqo.js} +1 -1
  25. package/public/assets/{SmallFiles-B5cUjEav.js → SmallFiles-BoK3R1Ex.js} +1 -1
  26. package/public/assets/{SpeculationWaste-mxi3jgb0.js → SpeculationWaste-CnVxB5AJ.js} +1 -1
  27. package/public/assets/Spill-BhNicyBy.js +1 -0
  28. package/public/assets/{StageFailed-YvxC11ii.js → StageFailed-mAhHU4l_.js} +1 -1
  29. package/public/assets/StageFindingGroup-BEtPWcT0.js +1 -0
  30. package/public/assets/{StageHeader-BMNmUy-s.js → StageHeader-DJkYkrsU.js} +1 -1
  31. package/public/assets/{StageShape-DO3mQ6NS.js → StageShape-BDxRknLp.js} +1 -1
  32. package/public/assets/{StageSlowness-SwRxfAEx.js → StageSlowness-BPRw-otN.js} +1 -1
  33. package/public/assets/{Straggler-DHLTiBY-.js → Straggler-CZCbnOWP.js} +1 -1
  34. package/public/assets/{TaskFailures-Bj0QxiOc.js → TaskFailures-C57YcLFQ.js} +1 -1
  35. package/public/assets/{TinyTask-COUQQ5bQ.js → TinyTask-Bi84qU7n.js} +1 -1
  36. package/public/assets/{UnderBroadcast-RAUbQ6vw.js → UnderBroadcast-DdQV7htE.js} +1 -1
  37. package/public/assets/{finding-anchor-Dl8mIHiU.js → finding-anchor-Cl6_5hfV.js} +1 -1
  38. package/public/assets/{impact-sort-Eihua9dY.js → impact-sort-CssZ6MT2.js} +1 -1
  39. package/public/assets/index-2h_TgUAb.css +2 -0
  40. package/public/assets/index-B-03mG7K.js +14 -0
  41. package/public/assets/parser-worker-CA5RxqKc.js +122 -0
  42. package/public/assets/{plan-graph-vendor-B0sEms9Z.js → plan-graph-vendor-BYl-AzN5.js} +1 -1
  43. package/public/assets/{react-vendor-BAKt3vny.js → react-vendor-DEdfrRTJ.js} +1 -1
  44. package/public/assets/{select-DkjFHlXR.js → select-CQjNhprm.js} +1 -1
  45. package/public/assets/{usePagedRows-Bo-aSztx.js → usePagedRows-Dyl6T8Ok.js} +1 -1
  46. package/public/assets/useRecentFiles-XZEnLlal.js +11 -0
  47. package/public/assets/{useSortMode-lDedD5a3.js → useSortMode-Dhvit8dt.js} +1 -1
  48. package/public/docs/404.html +2 -2
  49. package/public/docs/assets/{app.yjczoibf.js → app.eYTPqTeq.js} +1 -1
  50. package/public/docs/assets/chunks/@localSearchIndexroot.u_e5bQz4.js +1 -0
  51. package/public/docs/assets/chunks/{VPLocalSearchBox.DOBbPfix.js → VPLocalSearchBox.DsZrQKQR.js} +1 -1
  52. package/public/docs/assets/chunks/{theme.DFcAl8Ws.js → theme.DsTNE2pE.js} +2 -2
  53. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +1 -0
  54. package/public/docs/assets/{contributor-guide_architecture_board-widgets.md.D0Kkk_r5.lean.js → contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js} +1 -1
  55. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js +1 -0
  56. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js +1 -0
  57. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +1 -0
  58. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js +1 -0
  59. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +1 -0
  60. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js +1 -0
  61. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.js +1 -0
  62. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js +1 -0
  63. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +1 -0
  64. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +1 -0
  65. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.CKjWG56Y.js +1 -0
  66. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.CKjWG56Y.lean.js +1 -0
  67. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.js +1 -0
  68. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js +1 -0
  69. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +1 -0
  70. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js +1 -0
  71. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +1 -0
  72. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js +1 -0
  73. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js +1 -0
  74. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js +1 -0
  75. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +1 -0
  76. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +1 -0
  77. package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.js +1 -0
  78. package/public/docs/assets/{contributor-guide_architecture_index.md.B9yZieYL.lean.js → contributor-guide_architecture_index.md.tymxf1Fh.lean.js} +1 -1
  79. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +1 -0
  80. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DyCBdjuM.js +1 -0
  81. package/public/docs/assets/{contributor-guide_architecture_state-and-history.md.w4lwybdh.lean.js → contributor-guide_architecture_state-and-history.md.DyCBdjuM.lean.js} +1 -1
  82. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.js +1 -0
  83. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.lean.js +1 -0
  84. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.js +1 -0
  85. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.lean.js +1 -0
  86. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +1 -0
  87. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +1 -0
  90. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.js +1 -0
  91. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.lean.js +1 -0
  92. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js +1 -0
  93. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js +1 -0
  94. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.js +1 -0
  95. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.lean.js +1 -0
  96. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js +1 -0
  97. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js +1 -0
  98. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js +1 -0
  99. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +1 -0
  100. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.DC1YqXBp.js +1 -0
  101. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.DC1YqXBp.lean.js +1 -0
  102. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js +1 -0
  103. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js +1 -0
  104. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js +6 -0
  105. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js +1 -0
  106. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js +1 -0
  107. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js +1 -0
  108. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.js +4 -0
  109. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.lean.js +1 -0
  110. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.js +4 -0
  111. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.lean.js +1 -0
  112. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.js +1 -0
  113. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.lean.js +1 -0
  114. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.js +1 -0
  115. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.lean.js +1 -0
  116. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.OUQqw3mI.js +23 -0
  117. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.OUQqw3mI.lean.js +1 -0
  118. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +1 -0
  119. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js +1 -0
  120. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.js +1 -0
  121. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.lean.js +1 -0
  122. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js +4 -0
  123. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js +1 -0
  124. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.js +16 -0
  125. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.lean.js +1 -0
  126. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.js +56 -0
  127. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.lean.js +1 -0
  128. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +23 -0
  129. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js +1 -0
  130. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.js +26 -0
  131. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.lean.js +1 -0
  132. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.js +14 -0
  133. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.lean.js +1 -0
  134. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js +20 -0
  135. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js +1 -0
  136. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.js +5 -0
  137. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.lean.js +1 -0
  138. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js +15 -0
  139. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js +1 -0
  140. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.js +12 -0
  141. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.lean.js +1 -0
  142. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.js +1 -0
  143. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.lean.js +1 -0
  144. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.DSXGu2tA.js +1 -0
  145. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.DSXGu2tA.lean.js +1 -0
  146. package/public/docs/assets/user-guide_understanding-findings.md.C9YRruMT.js +1 -0
  147. package/public/docs/assets/user-guide_understanding-findings.md.C9YRruMT.lean.js +1 -0
  148. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +30 -0
  149. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +30 -0
  150. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +30 -0
  151. package/public/docs/contributor-guide/architecture/board-widgets.html +5 -5
  152. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +30 -0
  153. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +30 -0
  154. package/public/docs/contributor-guide/architecture/detector-contract.html +5 -5
  155. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +30 -0
  156. package/public/docs/contributor-guide/architecture/drill-down.html +5 -5
  157. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +30 -0
  158. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +30 -0
  159. package/public/docs/contributor-guide/architecture/impact-estimation.html +5 -5
  160. package/public/docs/contributor-guide/architecture/index.html +5 -5
  161. package/public/docs/contributor-guide/architecture/overview.html +5 -5
  162. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +30 -0
  163. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +30 -0
  164. package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
  165. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +30 -0
  166. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +30 -0
  167. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +30 -0
  168. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +30 -0
  169. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +30 -0
  170. package/public/docs/contributor-guide/architecture/widget-rendering.html +5 -5
  171. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +30 -0
  172. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +30 -0
  173. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +35 -0
  174. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +30 -0
  175. package/public/docs/contributor-guide/architecture/worker-protocol.html +5 -10
  176. package/public/docs/contributor-guide/contributing.html +4 -4
  177. package/public/docs/contributor-guide/development-setup.html +4 -4
  178. package/public/docs/contributor-guide/testing.html +4 -4
  179. package/public/docs/hashmap.json +1 -1
  180. package/public/docs/index.html +3 -3
  181. package/public/docs/tuning-reference/anti-patterns.html +3 -3
  182. package/public/docs/tuning-reference/aqe.html +3 -3
  183. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +3 -3
  184. package/public/docs/tuning-reference/bottleneck-cold-start.html +3 -3
  185. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +3 -3
  186. package/public/docs/tuning-reference/bottleneck-failures.html +3 -3
  187. package/public/docs/tuning-reference/bottleneck-gc.html +3 -3
  188. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +3 -3
  189. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +3 -3
  190. package/public/docs/tuning-reference/bottleneck-retry-waste.html +3 -3
  191. package/public/docs/tuning-reference/bottleneck-shuffle.html +3 -3
  192. package/public/docs/tuning-reference/bottleneck-skew.html +3 -3
  193. package/public/docs/tuning-reference/bottleneck-slow-host.html +3 -3
  194. package/public/docs/tuning-reference/bottleneck-small-files.html +3 -3
  195. package/public/docs/tuning-reference/bottleneck-spill.html +3 -3
  196. package/public/docs/tuning-reference/bottleneck-straggler.html +3 -3
  197. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +3 -3
  198. package/public/docs/tuning-reference/bottleneck-utilization.html +3 -3
  199. package/public/docs/tuning-reference/caching.html +3 -3
  200. package/public/docs/tuning-reference/cluster-config.html +3 -3
  201. package/public/docs/tuning-reference/config.html +3 -3
  202. package/public/docs/tuning-reference/data-formats.html +3 -3
  203. package/public/docs/tuning-reference/index.html +3 -3
  204. package/public/docs/tuning-reference/intro.html +3 -3
  205. package/public/docs/tuning-reference/joins.html +3 -3
  206. package/public/docs/tuning-reference/memory-model.html +3 -3
  207. package/public/docs/tuning-reference/metrics.html +3 -3
  208. package/public/docs/tuning-reference/partitioning.html +3 -3
  209. package/public/docs/tuning-reference/pyspark.html +3 -3
  210. package/public/docs/tuning-reference/shuffle.html +3 -3
  211. package/public/docs/tuning-reference/spark-architecture.html +3 -3
  212. package/public/docs/tuning-reference/table-formats.html +3 -3
  213. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +33 -0
  214. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +30 -0
  215. package/public/docs/user-guide/alternative-log-retrieval.html +6 -9
  216. package/public/docs/user-guide/getting-started/ci-and-automation.html +52 -0
  217. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +30 -0
  218. package/public/docs/user-guide/getting-started/regression-metric-keys.html +30 -0
  219. package/public/docs/user-guide/getting-started/tuning-thresholds.html +33 -0
  220. package/public/docs/user-guide/getting-started.html +5 -28
  221. package/public/docs/user-guide/mcp-tools/compare-runs.html +85 -0
  222. package/public/docs/user-guide/mcp-tools/diagnose-run.html +52 -0
  223. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +55 -0
  224. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +43 -0
  225. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +49 -0
  226. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +34 -0
  227. package/public/docs/user-guide/mcp-tools/get-run-summary.html +44 -0
  228. package/public/docs/user-guide/mcp-tools/list-runs.html +41 -0
  229. package/public/docs/user-guide/mcp-tools.html +8 -140
  230. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +30 -0
  231. package/public/docs/user-guide/run-comparison.html +5 -5
  232. package/public/docs/user-guide/understanding-findings.html +5 -5
  233. package/public/export-template.html +45 -45
  234. package/public/index.html +5 -5
  235. package/vendor-core/analyzer.js +1 -1
  236. package/vendor-core/api-merge-writes.js +80 -0
  237. package/vendor-core/cli/budgets.js +62 -2
  238. package/vendor-core/cli/regression-budgets.js +35 -0
  239. package/vendor-core/comparison-output.js +50 -0
  240. package/vendor-core/core-source-hash.txt +1 -1
  241. package/vendor-core/delta-commands.js +13 -0
  242. package/vendor-core/delta-log-paths.js +42 -0
  243. package/vendor-core/delta-targets.js +127 -0
  244. package/vendor-core/detectors.js +219 -56
  245. package/vendor-core/docs-content/detection/chrn.md +3 -1
  246. package/vendor-core/docs-content/detection/cold.md +2 -1
  247. package/vendor-core/docs-content/detection/plan.md +9 -2
  248. package/vendor-core/docs-content/detection/shfl.md +8 -2
  249. package/vendor-core/docs-content/detection/skew.md +11 -5
  250. package/vendor-core/docs-content/detection/slow.md +6 -2
  251. package/vendor-core/docs-content/detection/spill.md +4 -1
  252. package/vendor-core/docs-content/detection/strag.md +5 -3
  253. package/vendor-core/docs-content/detection/tiny.md +3 -2
  254. package/vendor-core/event-handlers.js +95 -9
  255. package/vendor-core/event-schemas.js +3 -1
  256. package/vendor-core/evidence-report.js +24 -15
  257. package/vendor-core/finding-presentation.js +39 -13
  258. package/vendor-core/finding-types.js +50 -6
  259. package/vendor-core/mcp-server-factory.js +17 -6
  260. package/vendor-core/mcp-tools.js +53 -35
  261. package/vendor-core/median.js +8 -0
  262. package/vendor-core/plan-graph-model.js +1 -1
  263. package/vendor-core/plan-summary.js +13 -10
  264. package/vendor-core/run-comparison.js +81 -120
  265. package/vendor-core/run-output.js +26 -0
  266. package/vendor-core/sequence-alignment.js +83 -0
  267. package/vendor-core/stage-alignment.js +586 -0
  268. package/vendor-core/stage-identity.js +112 -0
  269. package/vendor-core/stage-quantiles.js +3 -1
  270. package/vendor-core/stage-structure.js +83 -0
  271. package/vendor-core/types.js +5 -0
  272. package/vendor-core/write-target-args.js +48 -0
  273. package/vendor-core/write-targets.js +35 -52
  274. package/public/assets/CachingOpportunity-Cfw3wAFa.js +0 -1
  275. package/public/assets/CoreUsageArea-BObEeenE.js +0 -1
  276. package/public/assets/DisclosureContext-Ds0hcZFn.js +0 -1
  277. package/public/assets/PlanGraphRoute-NV5mCXRm.js +0 -1
  278. package/public/assets/Spill-CH1bNpxL.js +0 -1
  279. package/public/assets/StageFindingGroup-B1A0nf-e.js +0 -1
  280. package/public/assets/index-BcqTLKFp.css +0 -2
  281. package/public/assets/index-DaC8A2FS.js +0 -13
  282. package/public/assets/parser-worker-CfDvPkwF.js +0 -122
  283. package/public/assets/useRecentFiles-Bb4-ukVz.js +0 -11
  284. package/public/docs/assets/chunks/@localSearchIndexroot.r9imX8Aq.js +0 -1
  285. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.D0Kkk_r5.js +0 -1
  286. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.js +0 -1
  287. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.lean.js +0 -1
  288. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.js +0 -1
  289. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.lean.js +0 -1
  290. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.js +0 -1
  291. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.lean.js +0 -1
  292. package/public/docs/assets/contributor-guide_architecture_index.md.B9yZieYL.js +0 -1
  293. package/public/docs/assets/contributor-guide_architecture_overview.md.DBGo-bwn.js +0 -1
  294. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.w4lwybdh.js +0 -1
  295. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.js +0 -1
  296. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.lean.js +0 -1
  297. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.js +0 -6
  298. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.lean.js +0 -1
  299. package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.js +0 -7
  300. package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.lean.js +0 -1
  301. package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.js +0 -24
  302. package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.lean.js +0 -1
  303. package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.js +0 -148
  304. package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.lean.js +0 -1
  305. package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.js +0 -1
  306. package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.lean.js +0 -1
  307. package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.js +0 -1
  308. package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.lean.js +0 -1
  309. /package/public/docs/assets/{contributor-guide_architecture_overview.md.DBGo-bwn.lean.js → contributor-guide_architecture_overview.md.C1sSvm9i.lean.js} +0 -0
@@ -18,6 +18,7 @@ import {
18
18
 
19
19
 
20
20
  } from './event-schemas.js';
21
+ import { DELTA_COMMANDS } from './delta-commands.js';
21
22
  import { assertNever } from './assert-never.js';
22
23
  import { finalizeStage } from './stage-quantiles.js';
23
24
  import { MAX_FAILURE_DETAILS_PER_STAGE, extractTaskFailureDetail, taskFailureKey, } from './task-failure.js';
@@ -198,6 +199,9 @@ const MAX_TASK_SAMPLES = 20;
198
199
 
199
200
 
200
201
 
202
+
203
+
204
+
201
205
 
202
206
 
203
207
 
@@ -279,6 +283,10 @@ export function buildChunkDecoder() {
279
283
  // line again after every slice scanned about 10 GB on a 100 MB line.
280
284
  let pendingIsSqlEvent = false;
281
285
  let keyTail = '';
286
+ // Where the pending line's plan-description value starts, once its key is found but too little
287
+ // of the value has arrived to tell whether it is a small Delta command description (see
288
+ // descriptionDisposition); -1 otherwise.
289
+ let undecidedValueStart = -1;
282
290
  let skippingPlanDescription = false;
283
291
  // Length of the backslash run the skipped bytes ended with, which escapes a quote at the start
284
292
  // of the next chunk when odd.
@@ -312,11 +320,24 @@ export function buildChunkDecoder() {
312
320
  keyTail = '';
313
321
  appended = pending; // nothing of it was searched while it was shorter than the prefix
314
322
  }
315
- const keyAt = findPlanKey(appended);
316
- if (keyAt === -1) return; // the key may still arrive in a later chunk
323
+ let valueStart = undecidedValueStart;
324
+ if (valueStart === -1) {
325
+ const keyAt = findPlanKey(appended);
326
+ if (keyAt === -1) return; // the key may still arrive in a later chunk
327
+ valueStart = keyAt + PLAN_DESCRIPTION_KEY.length;
328
+ }
329
+ if (closingQuoteIndex(pending, valueStart) !== -1) { // complete: stripPlanDescription handles it
330
+ undecidedValueStart = -1;
331
+ pendingSettled = true;
332
+ return;
333
+ }
334
+ // A small kept description is not skipped: the line completes in a later chunk and
335
+ // stripPlanDescription keeps its arguments line.
336
+ const disposition = descriptionDisposition(pending, valueStart);
337
+ if (disposition === 'wait') { undecidedValueStart = valueStart; return; }
338
+ undecidedValueStart = -1;
317
339
  pendingSettled = true;
318
- const valueStart = keyAt + PLAN_DESCRIPTION_KEY.length;
319
- if (closingQuoteIndex(pending, valueStart) !== -1) return; // complete: stripPlanDescription empties it
340
+ if (disposition === 'keep') return;
320
341
  // Only a chunk whose last byte is a backslash carries a run over; any other last byte (such
321
342
  // as part of a split multibyte char) ends it.
322
343
  let run = 0;
@@ -378,6 +399,7 @@ export function buildChunkDecoder() {
378
399
  pending = pendingHead = appended = start < text.length ? text.substring(start) : '';
379
400
  pendingSettled = false;
380
401
  pendingIsSqlEvent = false;
402
+ undecidedValueStart = -1;
381
403
  }
382
404
  if (!pendingSettled && pending !== '') settlePending(buffer[buffer.length - 1], appended);
383
405
  }
@@ -1109,6 +1131,10 @@ export function startSqlExecution(event
1109
1131
  sparkPlanInfo,
1110
1132
  hadAdaptiveUpdate: false,
1111
1133
  };
1134
+ if (event.rootExecutionId !== undefined) exec.rootExecutionId = event.rootExecutionId;
1135
+ if (event.physicalPlanDescription && KEPT_ARGUMENTS.test(event.physicalPlanDescription)) {
1136
+ exec.commandArguments = event.physicalPlanDescription;
1137
+ }
1112
1138
  state.sqlExecutions.set(exec.id, exec);
1113
1139
  // A restarted execution carries a new plan: its next end must resolve it again.
1114
1140
  state.resolvedPlanExecutions.delete(exec.id);
@@ -1295,13 +1321,32 @@ const KNOWN_EVENT_TYPES = new Set(
1295
1321
  );
1296
1322
 
1297
1323
  // SQLExecutionStart and SQLAdaptiveExecutionUpdate carry `physicalPlanDescription`, Spark's text
1298
- // rendering of the plan. Nothing reads it (the plan tree comes from sparkPlanInfo), yet on a real
1299
- // 3.5 GB log it was 72% of the AQE-update bytes, which were themselves 73% of the log. Cutting its
1324
+ // rendering of the plan. The plan tree comes from sparkPlanInfo, yet on a real 3.5 GB log the
1325
+ // description was 72% of the AQE-update bytes, which were themselves 73% of the log. Cutting its
1300
1326
  // string value out before JSON.parse halves the parse cost of those lines.
1327
+ //
1328
+ // One exception: a Delta write command's node prints only its bare name in sparkPlanInfo, and its
1329
+ // target table appears only on its `Arguments:` line of the description. When the description is
1330
+ // that of one of KEPT_ARGUMENTS_COMMANDS (checked on its first bytes, so no other line pays for it),
1331
+ // the value is cut down to that one line, never kept whole. A value that spans decoder chunks is
1332
+ // still dropped unread (buildChunkDecoder), so the write target then falls back to its child paths.
1301
1333
  const SQL_UI_EVENT_PREFIX = '{"Event":"org.apache.spark.sql.execution.ui.SparkListenerSQL';
1302
1334
  const PLAN_DESCRIPTION_KEY = '"physicalPlanDescription":"';
1303
1335
 
1304
- const QUOTE = 0x22, BACKSLASH = 0x5c, NEWLINE = 0x0a;
1336
+ const QUOTE = 0x22, BACKSLASH = 0x5c, NEWLINE = 0x0a, LOWER_N = 0x6e;
1337
+
1338
+ // Delta commands whose root node carries no target in sparkPlanInfo. The description of a
1339
+ // command execution starts `== Physical Plan ==\nExecute <Command> (1)` (JSON-escaped newline).
1340
+ const KEPT_ARGUMENTS_COMMANDS = new Set(DELTA_COMMANDS);
1341
+ // Those whose description is just the command (no child plan printed under it), so it is small
1342
+ // enough to decode whole when it spans decoder chunks. SaveIntoDataSourceCommand prints the whole
1343
+ // query under it, which can be as large as any other plan description.
1344
+ const SMALL_DESCRIPTION_COMMANDS = new Set(
1345
+ [...KEPT_ARGUMENTS_COMMANDS].filter((command) => command !== 'SaveIntoDataSourceCommand'),
1346
+ );
1347
+ const PLAN_HEADER = '== Physical Plan ==\\nExecute ';
1348
+ // The kept form once JSON-parsed: `<Command>\nArguments: <line>`.
1349
+ const KEPT_ARGUMENTS = /^[A-Za-z]+\nArguments: /;
1305
1350
 
1306
1351
  // Index of the closing quote of the JSON string whose content starts at `valueStart`: the first
1307
1352
  // quote preceded by an even number of backslashes. -1 when the string is unterminated.
@@ -1326,7 +1371,48 @@ function closingQuoteAt(buf , limit , carried )
1326
1371
  return -1;
1327
1372
  }
1328
1373
 
1329
- // Returns `line` with the physicalPlanDescription string value emptied, or `line` unchanged when
1374
+ // The command named by a description that starts at text[valueStart] with PLAN_HEADER, when text
1375
+ // holds the whole name; null otherwise.
1376
+ function describedCommand(text , valueStart ) {
1377
+ if (!text.startsWith(PLAN_HEADER, valueStart)) return null;
1378
+ const nameStart = valueStart + PLAN_HEADER.length;
1379
+ let nameEnd = nameStart;
1380
+ while (nameEnd < text.length && /[A-Za-z]/.test(text[nameEnd])) nameEnd++;
1381
+ return nameEnd < text.length ? text.slice(nameStart, nameEnd) : null;
1382
+ }
1383
+
1384
+ // What to do with an open description value whose first bytes are text[valueStart...]: 'keep' it
1385
+ // (a small Delta command description), 'skip' it, or 'wait' when too little has arrived to tell.
1386
+ function descriptionDisposition(text , valueStart ) {
1387
+ const seen = text.slice(valueStart, valueStart + PLAN_HEADER.length);
1388
+ if (!PLAN_HEADER.startsWith(seen)) return 'skip';
1389
+ if (seen.length < PLAN_HEADER.length) return 'wait';
1390
+ const command = describedCommand(text, valueStart);
1391
+ if (command === null) return 'wait'; // the command name is still arriving
1392
+ return SMALL_DESCRIPTION_COMMANDS.has(command) ? 'keep' : 'skip';
1393
+ }
1394
+
1395
+ // The `<Command>\nArguments: <line>` part (still JSON-escaped) of the root command of a plan
1396
+ // description whose value spans line[valueStart, quote), or null when it is not one of
1397
+ // KEPT_ARGUMENTS_COMMANDS. SaveIntoDataSourceCommand is kept only for the Delta source: its other
1398
+ // providers (JDBC) print option maps that this tool has no use for.
1399
+ function keptCommandArguments(line , valueStart , quote ) {
1400
+ const command = describedCommand(line, valueStart);
1401
+ if (command === null || !KEPT_ARGUMENTS_COMMANDS.has(command)) return null;
1402
+ const key = `${command}\\nArguments: `;
1403
+ const at = line.indexOf(key, valueStart + PLAN_HEADER.length + command.length);
1404
+ if (at === -1 || at >= quote) return null;
1405
+ // The line ends at the first escaped newline: a backslash starts a two-char escape.
1406
+ let end = at + key.length;
1407
+ while (end < quote && !(line.charCodeAt(end) === BACKSLASH && line.charCodeAt(end + 1) === LOWER_N)) {
1408
+ end += line.charCodeAt(end) === BACKSLASH ? 2 : 1;
1409
+ }
1410
+ const kept = line.slice(at, Math.min(end, quote));
1411
+ return command === 'SaveIntoDataSourceCommand' && !kept.includes('DeltaDataSource') ? null : kept;
1412
+ }
1413
+
1414
+ // Returns `line` with the physicalPlanDescription string value emptied (or cut to the kept
1415
+ // arguments line, see keptCommandArguments), or `line` unchanged when
1330
1416
  // the key isn't found in Spark's compact form. The key pattern can't match inside another JSON
1331
1417
  // string: there its quotes would be backslash-escaped. buildChunkDecoder already empties a value
1332
1418
  // that spans chunks, so here the value is empty or within one chunk.
@@ -1339,7 +1425,7 @@ export function stripPlanDescription(line ) {
1339
1425
  const quote = closingQuoteIndex(line, valueStart);
1340
1426
  // Unterminated string (a truncated line): leave it for JSON.parse to reject.
1341
1427
  if (quote === -1) return line;
1342
- return line.slice(0, valueStart) + line.slice(quote);
1428
+ return line.slice(0, valueStart) + (keptCommandArguments(line, valueStart, quote) ?? '') + line.slice(quote);
1343
1429
  }
1344
1430
 
1345
1431
  const TASK_END_PREFIX = '{"Event":"SparkListenerTaskEnd",';
@@ -321,13 +321,15 @@ export const TaskEndEventSchema = z.object({
321
321
  export const SqlExecutionStartEventSchema = z.object({
322
322
  Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerSQLExecutionStart'),
323
323
  executionId: z.number(),
324
+ rootExecutionId: z.number().optional(),
324
325
  description: z.string().optional(),
326
+ physicalPlanDescription: z.string().optional(),
325
327
  time: z.number(),
326
328
  sparkPlanInfo: SparkPlanInfoFieldSchema,
327
329
  });
328
330
 
329
331
  // applyAdaptiveExecutionUpdate: AQE re-plans mid-query and re-emits sparkPlanInfo for the same
330
- // executionId; last write wins. Neither SQL schema declares physicalPlanDescription: nothing reads
332
+ // executionId; last write wins. This schema does not declare physicalPlanDescription: nothing reads
331
333
  // it, so zod strips it (dispatchLine already empties it before JSON.parse, see stripPlanDescription).
332
334
  export const SqlAdaptiveExecutionUpdateEventSchema = z.object({
333
335
  Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerSQLAdaptiveExecutionUpdate'),
@@ -180,29 +180,29 @@ export const EVIDENCE_SCHEMA_VERSION = 5;
180
180
  // module (stageShape's totalCores, utilization's unrounded fraction) is left off both, so it never
181
181
  // reaches the report; adding, renaming or dropping a key here changes the report contract.
182
182
  const EVIDENCE_KEYS = {
183
- skew: [],
183
+ skew: ['origin'],
184
184
  stageShape: ['rule'],
185
- shuffle: [],
186
- partitionSizing: ['rule'],
187
- spill: ['spillMagnitude'],
185
+ shuffle: ['partitions'],
186
+ partitionSizing: ['rule', 'origin', 'partitions'],
187
+ spill: ['spillMagnitude', 'reads'],
188
188
  gc: ['direction'],
189
189
  slowHost: ['variant', 'host', 'hostTaskShare', 'hostMeanMs', 'dimension', 'executorId', 'execMaxValue'],
190
- stageSlowness: [],
190
+ stageSlowness: ['reads'],
191
191
  stageFailed: ['variant', 'numTasks', 'memoryBytesSpilled', 'failedTaskDetails'],
192
192
  failures: ['failedTasks', 'dominantReason', 'dominantError', 'failureGroups', 'otherFailedTasks'],
193
- straggler: ['unit', 'speculativeTasks', 'stragglerCount'],
193
+ straggler: ['unit', 'speculativeTasks', 'stragglerCount', 'origin'],
194
194
  speculationWaste: [],
195
195
  retryWaste: ['numTasks', 'memoryBytesSpilled', 'retriedTaskDetails'],
196
- tinyTask: [],
196
+ tinyTask: ['reads'],
197
197
  incompleteRun: [],
198
- coldStart: [],
198
+ coldStart: ['dynamicAllocation'],
199
199
  utilization: ['cpuUtilizationPct'],
200
200
  memoryUtilization: ['variant', 'rule', 'executorId', 'heap', 'dataUnavailable'],
201
201
  cacheUtilization: [
202
202
  'variant', 'rddId', 'rddName', 'memorySize', 'diskSize', 'numCachedPartitions', 'numPartitions', 'dataUnavailable',
203
203
  ],
204
204
  coreLocality: ['nonLocalTaskCount'],
205
- autoscalingChurn: ['shortLivedExecutorCount'],
205
+ autoscalingChurn: ['shortLivedExecutorCount', 'dynamicAllocation'],
206
206
  cachingOpportunity: ['variant', 'relation', 'format', 'relations', 'operator', 'executionIds', 'totalReadBytes'],
207
207
  jobFailureRate: ['failedJobs', 'totalJobs', 'failedTasks', 'totalTasks', 'avgJobDurationMs', 'taskFailureRate'],
208
208
  configAudit: ['property'],
@@ -211,8 +211,8 @@ const EVIDENCE_KEYS = {
211
211
  'groupIndex',
212
212
  ],
213
213
  smallFiles: ['executionId', 'stageIds', 'fileCount', 'direction', 'nodeName'],
214
- underBroadcast: ['executionId', 'stageIds', 'largerSideBytes'],
215
- overBroadcast: ['executionId', 'stageIds'],
214
+ underBroadcast: ['executionId', 'stageIds', 'largerSideBytes', 'broadcastThreshold'],
215
+ overBroadcast: ['executionId', 'stageIds', 'broadcastThreshold'],
216
216
  } ;
217
217
 
218
218
  // The other direction: an evidence field EVIDENCE_KEYS doesn't list fails here.
@@ -405,13 +405,13 @@ function verdictJson(model ) {
405
405
  // A WeakMap needs no invalidation: once mcp-tools.ts evicts the appModel, this entry is collectible.
406
406
  // Each cache is split first by the overrides object the report ran under (one fixed, frozen object
407
407
  // per CLI invocation or MCP server process; DEFAULT_THRESHOLDS for the specification's).
408
-
408
+
409
409
  const DEFAULT_THRESHOLDS = {};
410
410
  const jsonCache = new WeakMap();
411
411
  // The redacted report, keyed by the unredacted appModel it was built from.
412
412
  const redactedJsonCache = new WeakMap();
413
413
 
414
- function cacheFor(cache , thresholds ) {
414
+ function cacheFor (cache , thresholds ) {
415
415
  const key = thresholds ?? DEFAULT_THRESHOLDS;
416
416
  let byModel = cache.get(key);
417
417
  if (!byModel) {
@@ -421,14 +421,23 @@ function cacheFor(cache , thresholds
421
421
  return byModel;
422
422
  }
423
423
 
424
- function runFindings(appModel , thresholds ) {
424
+ const findingsCache = new WeakMap();
425
+
426
+ /** The run's detector catalog and config audit, computed once per run and overrides so the report
427
+ * and the blocks built beside it (run-output.ts) share one analysis. */
428
+ export function runFindings(appModel , thresholds ) {
429
+ const cache = cacheFor(findingsCache, thresholds);
430
+ const cached = cache.get(appModel);
431
+ if (cached) return cached;
425
432
  const { app, stages, executors, sql, jobs, runAggregates } = appModel;
426
433
  const catalog = analyze(
427
434
  app, stages, executors?.added ?? [], executors?.removed ?? [],
428
435
  jobs ?? new Map(), sql ?? new Map(),
429
436
  runAggregates ?? null, { thresholds },
430
437
  );
431
- return { catalog, config: auditConfig(app) };
438
+ const result = { catalog, config: auditConfig(app) };
439
+ cache.set(appModel, result);
440
+ return result;
432
441
  }
433
442
 
434
443
  // Redacts the model and findings before the report derives any text from them, the same order
@@ -8,7 +8,7 @@
8
8
  // detectors.ts is type-only, so it is erased at build time. A detector's scope, order and emits
9
9
  // list stay on its DETECTORS entry; renderers get them through detectorInfoByType().
10
10
 
11
-
11
+
12
12
 
13
13
 
14
14
 
@@ -43,7 +43,9 @@ const SKEW_JOIN_ALREADY_ON = 'AQE skew-join handling is already on, so salt the
43
43
  const SKEW_JOIN_AQE_OFF = 'AQE is off, so enable it (spark.sql.adaptive.enabled) for skew-join handling to apply; otherwise salt the key or repartition on a better key.';
44
44
 
45
45
  // The skew-join generic line, worded per the row's remediation: AQE logged off, switch already on, or neither.
46
- function skewJoinGeneric(f , unset ) {
46
+ function skewJoinGeneric(f , unset ) {
47
+ if (f.origin === 'inputScan') return 'Uneven input files: compact small files or split large ones (lower spark.sql.files.maxPartitionBytes).';
48
+ if (f.origin === 'other') return 'Work is uneven across tasks: salt the key or repartition on a better key.';
47
49
  if (f.remediation?.some((r) => r.key === 'spark.sql.adaptive.enabled')) return SKEW_JOIN_AQE_OFF;
48
50
  return switchAlreadyOn(f, SKEW_JOIN_KEY) ? SKEW_JOIN_ALREADY_ON : unset;
49
51
  }
@@ -120,8 +122,9 @@ export const FINDING_PRESENTATION
120
122
  tag: 'TINY',
121
123
  thresholdSummary: (t) => `${t.minTasks}+ tasks with a median of ${t.maxP50}ms or less and a P95 of ${t.maxP95}ms or less`,
122
124
  actionLabel: () => 'Coalesce small tasks',
123
- // The shuffle-vs-no-shuffle fix isn't a Finding field, so one sentence covers both.
124
- genericRecommendation: () => 'Scheduler overhead may dominate: lower spark.sql.shuffle.partitions, or coalesce down to fewer, larger tasks.',
125
+ genericRecommendation: (f) => (f.reads != null && f.reads !== 'shuffle'
126
+ ? 'Scheduler overhead may dominate: coalesce down to fewer, larger tasks.'
127
+ : 'Scheduler overhead may dominate: lower spark.sql.shuffle.partitions, or coalesce down to fewer, larger tasks.'),
125
128
  },
126
129
 
127
130
  shuffle: {
@@ -129,7 +132,14 @@ export const FINDING_PRESENTATION
129
132
  tag: 'SHFL',
130
133
  thresholdSummary: (t) => `stage shuffle read above ${t.minBytes / 1048576} MiB`,
131
134
  actionLabel: () => 'Reduce shuffle size',
132
- genericRecommendation: () => 'Raise spark.sql.shuffle.partitions, or use a broadcast join for the smaller side.',
135
+ genericRecommendation: (f) => {
136
+ switch (f.partitions) {
137
+ case 'sufficient': return 'The tasks are already a good size, so more partitions will not help: use a broadcast join for the smaller side.';
138
+ case 'aqeCoalesced': return 'AQE coalesced the shuffle: lower spark.sql.adaptive.advisoryPartitionSizeInBytes, or use a broadcast join for the smaller side.';
139
+ case 'ownPartitioning': return "spark.sql.shuffle.partitions is already high enough, so raise this stage's own partition count (its repartition(n) or RDD parallelism), or use a broadcast join for the smaller side.";
140
+ default: return 'Raise spark.sql.shuffle.partitions, or use a broadcast join for the smaller side.';
141
+ }
142
+ },
133
143
  },
134
144
  partitionSizing: {
135
145
  name: 'partition sizing',
@@ -146,7 +156,9 @@ export const FINDING_PRESENTATION
146
156
  genericRecommendation(f) {
147
157
  switch (f.rule) {
148
158
  case 'shufflePartitionSkew': return skewJoinGeneric(f, 'For join skew, enable AQE skew-join handling (spark.sql.adaptive.skewJoin.enabled); otherwise salt the key or repartition on a better key.');
149
- case 'lowShuffleParallelism': return switchAlreadyOn(f, 'spark.sql.shuffle.partitions')
159
+ case 'lowShuffleParallelism':
160
+ if (f.partitions === 'aqeCoalesced') return 'AQE coalesced the shuffle into few tasks: lower spark.sql.adaptive.advisoryPartitionSizeInBytes so each partition is smaller.';
161
+ return switchAlreadyOn(f, 'spark.sql.shuffle.partitions')
150
162
  ? "spark.sql.shuffle.partitions is already high enough, so raise this stage's own partition count (its repartition(n) or RDD parallelism) so each partition is smaller."
151
163
  : 'Raise spark.sql.shuffle.partitions so each partition is smaller.';
152
164
  case 'maxPartitionTooBig': return 'Repartition to break up the oversized partition before this stage.';
@@ -161,7 +173,9 @@ export const FINDING_PRESENTATION
161
173
  thresholdSummary: (t) => `single-task disk spill above ${t.singleTaskDiskGiB} GiB`,
162
174
  actionLabel: () => 'Reduce spill',
163
175
  // The skew/volume classification isn't a Finding field, so one sentence covers both.
164
- genericRecommendation: () => 'If the spill is skew-driven, fix task skew first: adding memory will not help. Otherwise raise spark.sql.shuffle.partitions or increase executor memory.',
176
+ genericRecommendation: (f) => (f.reads != null && f.reads !== 'shuffle'
177
+ ? 'If the spill is skew-driven, fix task skew first: adding memory will not help. Otherwise increase executor memory or process less data per task.'
178
+ : 'If the spill is skew-driven, fix task skew first: adding memory will not help. Otherwise raise spark.sql.shuffle.partitions or increase executor memory.'),
165
179
  },
166
180
 
167
181
  gc: {
@@ -219,14 +233,18 @@ export const FINDING_PRESENTATION
219
233
  tag: 'SLOW',
220
234
  thresholdSummary: () => 'a stage running far longer than its peers, not attributable to a single slow host',
221
235
  actionLabel: () => 'Profile slow stage',
222
- genericRecommendation: () => 'Often a partition-count problem: raise parallelism via spark.sql.shuffle.partitions or spark.default.parallelism, or check for a large per-task data volume driving heavy shuffle and spill.',
236
+ genericRecommendation: (f) => f.reads === 'input'
237
+ ? 'Often too few or too uneven input partitions: check input file sizes and lower spark.sql.files.maxPartitionBytes, or look for a large per-task data volume driving heavy spill.'
238
+ : f.reads === 'other'
239
+ ? 'Check what the stage computes and for a large per-task data volume driving heavy spill.'
240
+ : 'Often a partition-count problem: raise parallelism via spark.sql.shuffle.partitions or spark.default.parallelism, or check for a large per-task data volume driving heavy shuffle and spill.',
223
241
  },
224
242
  straggler: {
225
243
  name: 'straggling task',
226
244
  tag: 'STRAG',
227
245
  thresholdSummary: () => 'one or more tasks finishing far after the rest of their stage',
228
246
  actionLabel: () => 'Fix stragglers',
229
- genericRecommendation: () => 'Rule out a GC pause or a slow shuffle fetch before assuming a hardware issue. If a skewed key is the real cause, that is a candidate for AQE\'s skew-join handling.',
247
+ genericRecommendation: (f) => `Rule out a GC pause or a slow shuffle fetch before assuming a hardware issue. If uneven data is the cause: ${skewJoinGeneric(f, 'for join-driven skew, enable AQE skew-join handling (spark.sql.adaptive.skewJoin.enabled); otherwise salt the key or repartition on a better key.')}`,
230
248
  },
231
249
  speculationWaste: {
232
250
  name: 'speculation waste',
@@ -240,7 +258,9 @@ export const FINDING_PRESENTATION
240
258
  tag: 'COLD',
241
259
  thresholdSummary: (t) => `the first stage waiting over ${t.gapSeconds}s for an executor`,
242
260
  actionLabel: () => 'Pre-warm cluster',
243
- genericRecommendation: () => 'Keep a warm pool of idle executors, or if using dynamic allocation, raise the minimum/initial executor count so it does not scale up from zero.',
261
+ genericRecommendation: (f) => (f.dynamicAllocation === 'off'
262
+ ? 'Keep a warm pool of idle executors: dynamic allocation is off, so no executor-count property applies.'
263
+ : 'Keep a warm pool of idle executors, or if using dynamic allocation, raise the minimum/initial executor count so it does not scale up from zero.'),
244
264
  },
245
265
 
246
266
  memoryUtilization: {
@@ -322,7 +342,9 @@ export const FINDING_PRESENTATION
322
342
  tag: 'CHRN',
323
343
  thresholdSummary: (t) => `over ${shareLabel(t.warningPct)} of executors living under ${t.shortLivedMs / 60000} minutes`,
324
344
  actionLabel: () => 'Reduce autoscaling churn',
325
- genericRecommendation: () => 'This looks like wasteful re-provisioning rather than normal scale-down: consider raising spark.dynamicAllocation.executorIdleTimeout or widening the minExecutors/maxExecutors bounds to reduce flapping.',
345
+ genericRecommendation: (f) => (f.dynamicAllocation === 'off'
346
+ ? 'Dynamic allocation is off, so the churn comes from executors lost or preempted: check the cluster manager\'s preemption and executor-loss logs.'
347
+ : 'This looks like wasteful re-provisioning rather than normal scale-down: consider raising spark.dynamicAllocation.executorIdleTimeout or widening the minExecutors/maxExecutors bounds to reduce flapping.'),
326
348
  },
327
349
 
328
350
  configAudit: CONFIG_AUDIT_PRESENTATION,
@@ -349,14 +371,18 @@ export const FINDING_PRESENTATION
349
371
  tag: 'PLAN',
350
372
  thresholdSummary: () => 'a join below the configured size floor that skipped broadcast',
351
373
  actionLabel: () => 'Use broadcast join',
352
- genericRecommendation: () => 'This could have been a broadcast join: consider a broadcast() hint or raising spark.sql.autoBroadcastJoinThreshold.',
374
+ genericRecommendation: (f) => (f.broadcastThreshold === 'notLimiting'
375
+ ? 'The threshold already admits the smaller side, so it is not what stopped the broadcast: consider a broadcast() hint or collecting table statistics.'
376
+ : 'This could have been a broadcast join: consider a broadcast() hint or raising spark.sql.autoBroadcastJoinThreshold.'),
353
377
  },
354
378
  overBroadcast: {
355
379
  name: 'oversized broadcast join',
356
380
  tag: 'PLAN',
357
381
  thresholdSummary: (t) => `a broadcast over ${t.overBroadcastBytes / 1073741824} GiB`,
358
382
  actionLabel: () => 'Fix oversized broadcast',
359
- genericRecommendation: (f) => (switchAlreadyOn(f, 'spark.sql.autoBroadcastJoinThreshold')
383
+ genericRecommendation: (f) => (f.broadcastThreshold === 'notLimiting'
384
+ ? 'The configured threshold is below this broadcast, so remove the broadcast() hint that forced it.'
385
+ : switchAlreadyOn(f, 'spark.sql.autoBroadcastJoinThreshold')
360
386
  ? 'Automatic broadcast is already disabled, so remove the broadcast() hint that forced it.'
361
387
  : 'Check for a misapplied broadcast hint or a misconfigured spark.sql.autoBroadcastJoinThreshold.'),
362
388
  },
@@ -79,7 +79,22 @@
79
79
 
80
80
  // ── Per-stage detectors ─────────────────────────────────────────────────────
81
81
 
82
-
82
+ /** What a skewed stage reads, which decides the fix: 'shuffleJoin' (a shuffle feeding a join, where
83
+ * AQE skew-join handling applies), 'inputScan' (uneven input files) or 'other'. */
84
+
85
+
86
+ /** What a stage reads, by the dominant side: a shuffle, input files, or neither. */
87
+
88
+
89
+ /** Why a shuffle finding's partition-count advice is or is not given: 'raise' (the property limits
90
+ * the stage), 'sufficient' (tasks are already a good size), 'aqeCoalesced' (AQE merged the
91
+ * partitions, so the advisory size is the lever) or 'ownPartitioning' (the property is already high
92
+ * enough, so the stage's own repartition(n) or RDD parallelism is). */
93
+
94
+
95
+
96
+
97
+
83
98
 
84
99
 
85
100
 
@@ -90,16 +105,24 @@
90
105
 
91
106
 
92
107
 
93
-
108
+
109
+
110
+
94
111
 
95
112
 
96
113
 
97
114
 
115
+
116
+
117
+
118
+
98
119
 
99
120
 
100
121
 
101
122
 
102
123
 
124
+
125
+
103
126
 
104
127
 
105
128
 
@@ -124,7 +147,10 @@
124
147
 
125
148
 
126
149
 
127
-
150
+
151
+
152
+
153
+
128
154
 
129
155
 
130
156
 
@@ -151,6 +177,8 @@
151
177
 
152
178
 
153
179
 
180
+
181
+
154
182
 
155
183
 
156
184
 
@@ -167,7 +195,10 @@
167
195
 
168
196
 
169
197
 
170
-
198
+
199
+
200
+
201
+
171
202
 
172
203
 
173
204
  // ── App-level detectors ─────────────────────────────────────────────────────
@@ -176,7 +207,10 @@
176
207
  // valueText is always 'missing': the ApplicationEnd event the log lacks.
177
208
 
178
209
 
179
-
210
+
211
+
212
+
213
+
180
214
 
181
215
 
182
216
 
@@ -231,6 +265,8 @@
231
265
 
232
266
 
233
267
 
268
+
269
+
234
270
 
235
271
 
236
272
 
@@ -296,13 +332,21 @@
296
332
 
297
333
 
298
334
 
335
+
336
+ /** How the effective spark.sql.autoBroadcastJoinThreshold relates to the finding: 'limits' (the
337
+ * property decided it, or is unknown), 'notLimiting' (already admits, or already below, the
338
+ * broadcast, so something else decided it) or 'disabled' (-1). */
339
+
299
340
 
300
341
 
301
342
 
343
+
302
344
 
303
345
 
304
346
 
305
-
347
+
348
+
349
+
306
350
 
307
351
 
308
352
  // ── Unions ──────────────────────────────────────────────────────────────────
@@ -6,6 +6,7 @@ import {
6
6
  } from './mcp-tools.js';
7
7
  import { listRuns } from './list-runs.js';
8
8
 
9
+ import { MAX_NORMALIZE_PATTERN_LENGTH, MAX_NORMALIZE_PATTERNS } from './stage-alignment.js';
9
10
 
10
11
  const sourceSchema = z.union([
11
12
  z.object({ path: z.string() }),
@@ -71,7 +72,7 @@ export function createMcpServer({ thresholds }
71
72
  }, (params) => toolResult(listRuns(params)));
72
73
 
73
74
  server.registerTool('diagnose_run', {
74
- description: 'Diagnose a Spark run: the dashboard verdict (title, summary, and the top places to look, ranked by potential savings), thresholded findings with remediation text, an impact-ranked fix recommendation rollup, clean-check status, and the checks the log lacked the data to run. When the server was started with --thresholds, findings and clean checks from a tuned detector carry tunedThresholds, and their impact estimates are uncalibrated.',
75
+ description: 'Diagnose a Spark run: the dashboard verdict (title, summary, and the top places to look, ranked by potential savings), thresholded findings with remediation text, an impact-ranked fix recommendation rollup, clean-check status, the checks the log lacked the data to run, and the writeTargets, metrics and effectiveConf blocks of the CLI report. When the server was started with --thresholds, findings and clean checks from a tuned detector carry tunedThresholds, and their impact estimates are uncalibrated.',
75
76
  inputSchema: {
76
77
  ...runRefSchema, redact: z.boolean().optional(),
77
78
  include: z.array(z.enum(['summary', 'evidenceAvailability', 'detectors'])).optional(),
@@ -91,15 +92,19 @@ export function createMcpServer({ thresholds }
91
92
  ));
92
93
 
93
94
  server.registerTool('compare_runs', {
94
- description: 'Compare two runs: categorized findings delta and metric deltas.',
95
+ description: 'Compare two runs: the verdict, metric deltas (metrics), categorized findings delta (findings) and the stage pairs (stagePairs) with the share of executor run time they cover (runtimeCoverage). confidence is ok, low or insufficient. metricDeltas and findingsDelta repeat metrics and findings under their earlier names and will be removed.',
95
96
  inputSchema: {
96
97
  runIdA: z.string().optional(), sourceA: sourceSchema.optional(),
97
98
  ...secondRunRefSchema,
98
99
  redact: z.boolean().optional(),
100
+ include: z.array(z.enum(['stagePairs'])).optional()
101
+ .describe('Extra blocks to return. stagePairs: one row per paired stage with its deltas, which can be large.'),
102
+ normalizePath: z.array(z.string().max(MAX_NORMALIZE_PATTERN_LENGTH)).max(MAX_NORMALIZE_PATTERNS).optional()
103
+ .describe('Regular expressions whose every match in a plan node\'s text is replaced with a fixed token before stages are paired, so run-specific text (an output directory that differs per run) does not keep the same stage from pairing. A generic pattern, not only for paths. Affects stage pairing, runtimeCoverage and confidence, never findings. A pattern that backtracks catastrophically can stall the server: keep them simple and anchored.'),
99
104
  ...formatSchema,
100
105
  },
101
- }, ({ runIdA, sourceA, runIdB, sourceB, redact, format }) => toolResultWithMarkdown(
102
- compareRuns({ runId: runIdA, source: sourceA }, { runId: runIdB, source: sourceB }, { redact, markdown: format === 'md', thresholds }),
106
+ }, ({ runIdA, sourceA, runIdB, sourceB, redact, include, normalizePath, format }) => toolResultWithMarkdown(
107
+ compareRuns({ runId: runIdA, source: sourceA }, { runId: runIdB, source: sourceB }, { redact, markdown: format === 'md', thresholds, normalizePath, include }),
103
108
  ));
104
109
 
105
110
  server.registerTool('evaluate_budgets', {
@@ -112,14 +117,20 @@ export function createMcpServer({ thresholds }
112
117
  sourceB: sourceSchema.optional().describe('Optional candidate run, compared against source/runId as the regression baseline (maxRegressionPct/failOnIntroduced). When given, the absolute budgets (maxRuntimeMs etc.) are evaluated on this run.'),
113
118
  runIdB: secondRunRefSchema.runIdB.describe('Same as `sourceB`, referencing an already-resolved run by id.'),
114
119
  maxRegressionPct: z.number().optional(), regressionMetric: z.string().optional(), failOnIntroduced: z.string().optional(),
120
+ regressionBudgets: z.array(z.object({ metric: z.string(), maxPct: z.number().nonnegative() })).optional()
121
+ .describe('Further regression budgets, one per metric (the CLI\'s repeated --regression-budget). A metric can be budgeted once across this list and maxRegressionPct/regressionMetric.'),
122
+ stageRegressionBudgets: z.array(z.object({ metric: z.string(), maxPct: z.number().nonnegative() })).optional()
123
+ .describe('Per paired stage (needs sourceB/runIdB): fail when any eligible pair\'s metric (executorRunTime, executorCpuTime, memoryBytesSpilled, diskBytesSpilled, shuffleReadBytes, shuffleWriteBytes) regressed by more than maxPct. One entry per metric. Inconclusive when no stage pairs.'),
124
+ stageQualities: z.array(z.enum(['exact', 'structural', 'aligned'])).optional()
125
+ .describe('Pair qualities the stage budgets read. Default exact and structural: an aligned pair may compare different work.'),
115
126
  },
116
127
  }, ({
117
128
  source, runId, runIdB, sourceB,
118
129
  maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct,
119
- maxRegressionPct, regressionMetric, failOnIntroduced,
130
+ maxRegressionPct, regressionMetric, failOnIntroduced, regressionBudgets, stageRegressionBudgets, stageQualities,
120
131
  }) => toolResult(evaluateBudgetsForRun(
121
132
  { source, runId },
122
- { maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct, maxRegressionPct, regressionMetric, failOnIntroduced },
133
+ { maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct, maxRegressionPct, regressionMetric, failOnIntroduced, regressionBudgets, stageRegressionBudgets, stageQualities },
123
134
  (runIdB || sourceB) ? { runId: runIdB, source: sourceB } : undefined,
124
135
  { thresholds },
125
136
  )));