sparkforensics-server 0.5.0 → 0.5.1

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 (280) hide show
  1. package/package.json +3 -3
  2. package/public/assets/{AutoscalingChurn-9Zo6IMGS.js → AutoscalingChurn-5hDGIXi4.js} +1 -1
  3. package/public/assets/{CacheUtilization-MBDfMsKC.js → CacheUtilization-CT2SCnJJ.js} +1 -1
  4. package/public/assets/{CachingOpportunity-Cfw3wAFa.js → CachingOpportunity-D_O_WkbH.js} +1 -1
  5. package/public/assets/{ColdStart-BtS12b6_.js → ColdStart-B38OiAOp.js} +1 -1
  6. package/public/assets/{ConfigAudit-DNPV6EQg.js → ConfigAudit-BoplryCH.js} +1 -1
  7. package/public/assets/{CoreUsageArea-BObEeenE.js → CoreUsageArea-csgwNxkl.js} +1 -1
  8. package/public/assets/{Dashboard-CC_BXrNy.js → Dashboard-BgKodJl2.js} +2 -2
  9. package/public/assets/{DisclosureContext-Ds0hcZFn.js → DisclosureContext-B0siMVdg.js} +1 -1
  10. package/public/assets/{DuplicatePlanSubtree-vN3zqRsi.js → DuplicatePlanSubtree-BqS2A2j4.js} +1 -1
  11. package/public/assets/{ExecutorUtilization-CZObSLIj.js → ExecutorUtilization-Dp52QGWo.js} +1 -1
  12. package/public/assets/{GcPressure-ByJlLOua.js → GcPressure-CT1yzR_s.js} +1 -1
  13. package/public/assets/{IncompleteRun-MzqV4W8y.js → IncompleteRun-C7Qkjtjn.js} +1 -1
  14. package/public/assets/{JobFailures-LbMvAwAb.js → JobFailures-WHl3ELfh.js} +1 -1
  15. package/public/assets/{MemoryUtilization-DH4jYvJK.js → MemoryUtilization-D8IUDHJI.js} +1 -1
  16. package/public/assets/{OverBroadcast-euk6HKg4.js → OverBroadcast-6oSSAQ_R.js} +1 -1
  17. package/public/assets/{PartitionHint-DThaMMRT.js → PartitionHint-CUvbnnxy.js} +1 -1
  18. package/public/assets/{PartitionSizing-yIN-iZzx.js → PartitionSizing-BwdFL-mw.js} +1 -1
  19. package/public/assets/{PlanExplorer-Dd9WPK4S.js → PlanExplorer-DcxmiQWL.js} +1 -1
  20. package/public/assets/{PlanGraphRoute-NV5mCXRm.js → PlanGraphRoute-pqnHTt4i.js} +1 -1
  21. package/public/assets/{RetryWaste-CXH4D-xH.js → RetryWaste-BTbeHkJc.js} +1 -1
  22. package/public/assets/{ShuffleIO-CW7U68ph.js → ShuffleIO-CRhrYNE0.js} +1 -1
  23. package/public/assets/{Skew-PL79TppX.js → Skew-DSNXRG9V.js} +1 -1
  24. package/public/assets/{SlowHost-GBO73hCi.js → SlowHost-B7Lwu2EU.js} +1 -1
  25. package/public/assets/{SmallFiles-B5cUjEav.js → SmallFiles-C082c2rd.js} +1 -1
  26. package/public/assets/{SpeculationWaste-mxi3jgb0.js → SpeculationWaste-BS2vpadr.js} +1 -1
  27. package/public/assets/{Spill-CH1bNpxL.js → Spill-B4DVKHXx.js} +1 -1
  28. package/public/assets/{StageFailed-YvxC11ii.js → StageFailed-D3ac9Sou.js} +1 -1
  29. package/public/assets/{StageFindingGroup-B1A0nf-e.js → StageFindingGroup-D9naK109.js} +1 -1
  30. package/public/assets/{StageHeader-BMNmUy-s.js → StageHeader-pI1nTwVf.js} +1 -1
  31. package/public/assets/{StageShape-DO3mQ6NS.js → StageShape-eO5le8iG.js} +1 -1
  32. package/public/assets/{StageSlowness-SwRxfAEx.js → StageSlowness-BaNyfNvL.js} +1 -1
  33. package/public/assets/{Straggler-DHLTiBY-.js → Straggler-D5Fnok_y.js} +1 -1
  34. package/public/assets/{TaskFailures-Bj0QxiOc.js → TaskFailures-BWnC-K_G.js} +1 -1
  35. package/public/assets/{TinyTask-COUQQ5bQ.js → TinyTask-8_5lpq_2.js} +1 -1
  36. package/public/assets/{UnderBroadcast-RAUbQ6vw.js → UnderBroadcast-DJ0XSwth.js} +1 -1
  37. package/public/assets/{finding-anchor-Dl8mIHiU.js → finding-anchor-Cys8Z6Yn.js} +1 -1
  38. package/public/assets/{impact-sort-Eihua9dY.js → impact-sort-BTQndbof.js} +1 -1
  39. package/public/assets/index-DCeWge12.js +13 -0
  40. package/public/assets/parser-worker-DmeCnLWU.js +122 -0
  41. package/public/assets/{plan-graph-vendor-B0sEms9Z.js → plan-graph-vendor-BYl-AzN5.js} +1 -1
  42. package/public/assets/{react-vendor-BAKt3vny.js → react-vendor-DEdfrRTJ.js} +1 -1
  43. package/public/assets/{select-DkjFHlXR.js → select-DIYN6D3y.js} +1 -1
  44. package/public/assets/{usePagedRows-Bo-aSztx.js → usePagedRows-BxvdFnPk.js} +1 -1
  45. package/public/assets/{useRecentFiles-Bb4-ukVz.js → useRecentFiles-C3Ov47U7.js} +4 -4
  46. package/public/assets/{useSortMode-lDedD5a3.js → useSortMode-B8SRfAoG.js} +1 -1
  47. package/public/docs/404.html +2 -2
  48. package/public/docs/assets/{app.yjczoibf.js → app.D2GqAkhy.js} +1 -1
  49. package/public/docs/assets/chunks/@localSearchIndexroot.D4OdkQjy.js +1 -0
  50. package/public/docs/assets/chunks/{VPLocalSearchBox.DOBbPfix.js → VPLocalSearchBox.BS4cSmtW.js} +1 -1
  51. package/public/docs/assets/chunks/{theme.DFcAl8Ws.js → theme.BuxVpb0H.js} +2 -2
  52. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +1 -0
  53. 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
  54. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js +1 -0
  55. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js +1 -0
  56. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +1 -0
  57. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js +1 -0
  58. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +1 -0
  59. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js +1 -0
  60. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.js +1 -0
  61. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js +1 -0
  62. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +1 -0
  63. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +1 -0
  64. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.js +1 -0
  65. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.lean.js +1 -0
  66. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.js +1 -0
  67. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js +1 -0
  68. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +1 -0
  69. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js +1 -0
  70. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +1 -0
  71. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js +1 -0
  72. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js +1 -0
  73. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js +1 -0
  74. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +1 -0
  75. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +1 -0
  76. package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.js +1 -0
  77. package/public/docs/assets/{contributor-guide_architecture_index.md.B9yZieYL.lean.js → contributor-guide_architecture_index.md.tymxf1Fh.lean.js} +1 -1
  78. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +1 -0
  79. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DyCBdjuM.js +1 -0
  80. 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
  81. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.js +1 -0
  82. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.lean.js +1 -0
  83. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.js +1 -0
  84. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.lean.js +1 -0
  85. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +1 -0
  86. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js +1 -0
  87. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.js +1 -0
  90. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.lean.js +1 -0
  91. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js +1 -0
  92. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js +1 -0
  93. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.js +1 -0
  94. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.lean.js +1 -0
  95. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js +1 -0
  96. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js +1 -0
  97. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js +1 -0
  98. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +1 -0
  99. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.js +1 -0
  100. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.lean.js +1 -0
  101. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js +1 -0
  102. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js +1 -0
  103. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js +6 -0
  104. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js +1 -0
  105. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js +1 -0
  106. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js +1 -0
  107. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.js +4 -0
  108. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.lean.js +1 -0
  109. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.js +4 -0
  110. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.lean.js +1 -0
  111. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.js +1 -0
  112. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.lean.js +1 -0
  113. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.js +1 -0
  114. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.lean.js +1 -0
  115. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.js +22 -0
  116. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.lean.js +1 -0
  117. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +1 -0
  118. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js +1 -0
  119. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.js +1 -0
  120. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.lean.js +1 -0
  121. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js +4 -0
  122. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js +1 -0
  123. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.js +16 -0
  124. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.lean.js +1 -0
  125. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.js +55 -0
  126. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.lean.js +1 -0
  127. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +23 -0
  128. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js +1 -0
  129. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.js +26 -0
  130. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.lean.js +1 -0
  131. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.js +14 -0
  132. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.lean.js +1 -0
  133. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js +20 -0
  134. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js +1 -0
  135. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.js +5 -0
  136. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.lean.js +1 -0
  137. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js +15 -0
  138. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js +1 -0
  139. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.js +12 -0
  140. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.lean.js +1 -0
  141. package/public/docs/assets/{user-guide_run-comparison.md.qin0a4IX.js → user-guide_run-comparison.md.B6aNtDYF.js} +1 -1
  142. package/public/docs/assets/user-guide_run-comparison.md.B6aNtDYF.lean.js +1 -0
  143. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.js +1 -0
  144. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.lean.js +1 -0
  145. package/public/docs/assets/user-guide_understanding-findings.md.Dqt2H_C1.js +1 -0
  146. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +30 -0
  147. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +30 -0
  148. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +30 -0
  149. package/public/docs/contributor-guide/architecture/board-widgets.html +5 -5
  150. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +30 -0
  151. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +30 -0
  152. package/public/docs/contributor-guide/architecture/detector-contract.html +5 -5
  153. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +30 -0
  154. package/public/docs/contributor-guide/architecture/drill-down.html +5 -5
  155. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +30 -0
  156. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +30 -0
  157. package/public/docs/contributor-guide/architecture/impact-estimation.html +5 -5
  158. package/public/docs/contributor-guide/architecture/index.html +5 -5
  159. package/public/docs/contributor-guide/architecture/overview.html +5 -5
  160. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +30 -0
  161. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +30 -0
  162. package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
  163. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +30 -0
  164. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +30 -0
  165. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +30 -0
  166. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +30 -0
  167. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +30 -0
  168. package/public/docs/contributor-guide/architecture/widget-rendering.html +5 -5
  169. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +30 -0
  170. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +30 -0
  171. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +35 -0
  172. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +30 -0
  173. package/public/docs/contributor-guide/architecture/worker-protocol.html +5 -10
  174. package/public/docs/contributor-guide/contributing.html +4 -4
  175. package/public/docs/contributor-guide/development-setup.html +4 -4
  176. package/public/docs/contributor-guide/testing.html +4 -4
  177. package/public/docs/hashmap.json +1 -1
  178. package/public/docs/index.html +3 -3
  179. package/public/docs/tuning-reference/anti-patterns.html +3 -3
  180. package/public/docs/tuning-reference/aqe.html +3 -3
  181. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +3 -3
  182. package/public/docs/tuning-reference/bottleneck-cold-start.html +3 -3
  183. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +3 -3
  184. package/public/docs/tuning-reference/bottleneck-failures.html +3 -3
  185. package/public/docs/tuning-reference/bottleneck-gc.html +3 -3
  186. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +3 -3
  187. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +3 -3
  188. package/public/docs/tuning-reference/bottleneck-retry-waste.html +3 -3
  189. package/public/docs/tuning-reference/bottleneck-shuffle.html +3 -3
  190. package/public/docs/tuning-reference/bottleneck-skew.html +3 -3
  191. package/public/docs/tuning-reference/bottleneck-slow-host.html +3 -3
  192. package/public/docs/tuning-reference/bottleneck-small-files.html +3 -3
  193. package/public/docs/tuning-reference/bottleneck-spill.html +3 -3
  194. package/public/docs/tuning-reference/bottleneck-straggler.html +3 -3
  195. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +3 -3
  196. package/public/docs/tuning-reference/bottleneck-utilization.html +3 -3
  197. package/public/docs/tuning-reference/caching.html +3 -3
  198. package/public/docs/tuning-reference/cluster-config.html +3 -3
  199. package/public/docs/tuning-reference/config.html +3 -3
  200. package/public/docs/tuning-reference/data-formats.html +3 -3
  201. package/public/docs/tuning-reference/index.html +3 -3
  202. package/public/docs/tuning-reference/intro.html +3 -3
  203. package/public/docs/tuning-reference/joins.html +3 -3
  204. package/public/docs/tuning-reference/memory-model.html +3 -3
  205. package/public/docs/tuning-reference/metrics.html +3 -3
  206. package/public/docs/tuning-reference/partitioning.html +3 -3
  207. package/public/docs/tuning-reference/pyspark.html +3 -3
  208. package/public/docs/tuning-reference/shuffle.html +3 -3
  209. package/public/docs/tuning-reference/spark-architecture.html +3 -3
  210. package/public/docs/tuning-reference/table-formats.html +3 -3
  211. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +33 -0
  212. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +30 -0
  213. package/public/docs/user-guide/alternative-log-retrieval.html +6 -9
  214. package/public/docs/user-guide/getting-started/ci-and-automation.html +51 -0
  215. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +30 -0
  216. package/public/docs/user-guide/getting-started/regression-metric-keys.html +30 -0
  217. package/public/docs/user-guide/getting-started/tuning-thresholds.html +33 -0
  218. package/public/docs/user-guide/getting-started.html +5 -28
  219. package/public/docs/user-guide/mcp-tools/compare-runs.html +84 -0
  220. package/public/docs/user-guide/mcp-tools/diagnose-run.html +52 -0
  221. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +55 -0
  222. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +43 -0
  223. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +49 -0
  224. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +34 -0
  225. package/public/docs/user-guide/mcp-tools/get-run-summary.html +44 -0
  226. package/public/docs/user-guide/mcp-tools/list-runs.html +41 -0
  227. package/public/docs/user-guide/mcp-tools.html +8 -140
  228. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +30 -0
  229. package/public/docs/user-guide/run-comparison.html +5 -5
  230. package/public/docs/user-guide/understanding-findings.html +5 -5
  231. package/public/export-template.html +44 -44
  232. package/public/index.html +4 -4
  233. package/vendor-core/analyzer.js +1 -1
  234. package/vendor-core/api-merge-writes.js +80 -0
  235. package/vendor-core/comparison-output.js +49 -0
  236. package/vendor-core/core-source-hash.txt +1 -1
  237. package/vendor-core/delta-commands.js +13 -0
  238. package/vendor-core/delta-log-paths.js +42 -0
  239. package/vendor-core/delta-targets.js +127 -0
  240. package/vendor-core/event-handlers.js +95 -9
  241. package/vendor-core/event-schemas.js +3 -1
  242. package/vendor-core/evidence-report.js +13 -4
  243. package/vendor-core/mcp-server-factory.js +13 -6
  244. package/vendor-core/mcp-tools.js +44 -35
  245. package/vendor-core/plan-graph-model.js +1 -1
  246. package/vendor-core/plan-summary.js +13 -10
  247. package/vendor-core/run-comparison.js +79 -120
  248. package/vendor-core/run-output.js +26 -0
  249. package/vendor-core/stage-alignment.js +262 -0
  250. package/vendor-core/stage-identity.js +112 -0
  251. package/vendor-core/types.js +5 -0
  252. package/vendor-core/write-target-args.js +48 -0
  253. package/vendor-core/write-targets.js +35 -52
  254. package/public/assets/index-DaC8A2FS.js +0 -13
  255. package/public/assets/parser-worker-CfDvPkwF.js +0 -122
  256. package/public/docs/assets/chunks/@localSearchIndexroot.r9imX8Aq.js +0 -1
  257. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.D0Kkk_r5.js +0 -1
  258. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.js +0 -1
  259. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.lean.js +0 -1
  260. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.js +0 -1
  261. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.lean.js +0 -1
  262. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.js +0 -1
  263. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.lean.js +0 -1
  264. package/public/docs/assets/contributor-guide_architecture_index.md.B9yZieYL.js +0 -1
  265. package/public/docs/assets/contributor-guide_architecture_overview.md.DBGo-bwn.js +0 -1
  266. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.w4lwybdh.js +0 -1
  267. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.js +0 -1
  268. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.lean.js +0 -1
  269. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.js +0 -6
  270. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.lean.js +0 -1
  271. package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.js +0 -7
  272. package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.lean.js +0 -1
  273. package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.js +0 -24
  274. package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.lean.js +0 -1
  275. package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.js +0 -148
  276. package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.lean.js +0 -1
  277. package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.lean.js +0 -1
  278. package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.js +0 -1
  279. /package/public/docs/assets/{contributor-guide_architecture_overview.md.DBGo-bwn.lean.js → contributor-guide_architecture_overview.md.C1sSvm9i.lean.js} +0 -0
  280. /package/public/docs/assets/{user-guide_understanding-findings.md.DzReyCXb.lean.js → user-guide_understanding-findings.md.Dqt2H_C1.lean.js} +0 -0
@@ -6,13 +6,14 @@ import { collectRun } from './cli/collect-run.js';
6
6
  import { deriveEvidenceAvailability } from './evidence-availability.js';
7
7
  import { resolveFromShs, DEFAULT_MAX_ARCHIVE_BYTES, DEFAULT_IDLE_TIMEOUT_MS } from './shs-load.js';
8
8
  import { mcpError } from './mcp-error.js';
9
- import { buildEvidenceReport, toFindingsFilter, } from './evidence-report.js';
10
- import { redactComparison } from './redact.js';
9
+ import { buildEvidenceReport, runFindings, toFindingsFilter, } from './evidence-report.js';
11
10
  import { computeWallClock } from './wall-clock.js';
12
- import { analyze } from './analyzer.js';
13
- import { buildComparison, renderComparisonMarkdown, } from './run-comparison.js';
14
- import { comparisonVerdict, } from './comparison-verdict.js';
11
+ import { renderComparisonMarkdown, } from './run-comparison.js';
12
+ import { buildComparisonOutput, } from './comparison-output.js';
13
+ import { compileNormalizePatterns } from './stage-alignment.js';
14
+ import { runOutputBlocks } from './run-output.js';
15
15
  import { evaluateBudgets, } from './cli/budgets.js';
16
+ import { combineRegressionBudgets } from './cli/regression-budgets.js';
16
17
  import { FINDING_NAMES, titleCase } from './finding-names.js';
17
18
  import { docAnchorForType } from './detector-docs.js';
18
19
  import { DETECTORS, } from './detectors.js';
@@ -46,19 +47,15 @@ import { typeTag } from './format-utils.js';
46
47
 
47
48
 
48
49
 
49
- // compareRuns returns a smaller MCP-facing projection of CompareRunsResult
50
- // (runIdA/runIdB/verdict/findingsDelta/metricDeltas/confidence/reason/matchedCoverage), not the full
51
- // raw shape (no baselineLabel/stageSkew/baseStages/candStages/jobOutcomes).
52
-
50
+ // compareRuns returns the shared comparison projection (comparison-output.ts, the CLI's `comparison`
51
+ // object), not the full raw shape (no baselineLabel/stageSkew/baseStages/candStages/jobOutcomes).
52
+ // `verdict` is the dashboard comparison page's headline (baseline = runIdA, candidate = runIdB).
53
+
53
54
 
54
55
 
55
-
56
-
57
-
58
-
56
+
59
57
 
60
-
61
-
58
+
62
59
 
63
60
 
64
61
  function envInt(name , fallback ) {
@@ -192,6 +189,7 @@ export function diagnoseRun(runId , opts
192
189
 
193
190
 
194
191
 
192
+
195
193
  {
196
194
  const appModel = getCachedAppModel(runId);
197
195
  const findingsFilter = toFindingsFilter(opts?.impactBand, opts?.type, opts?.stageId);
@@ -203,6 +201,9 @@ export function diagnoseRun(runId , opts
203
201
  return {
204
202
  runId, verdict: json.verdict, findings: json.findings, recommendations: json.recommendations, cleanChecks: json.cleanChecks,
205
203
  notRunChecks: json.notRunChecks,
204
+ // The CLI report's own blocks, so a run reads the same through either surface.
205
+ writeTargets: json.writeTargets,
206
+ ...runOutputBlocks(appModel, { redact: opts?.redact, thresholds: opts?.thresholds }),
206
207
  runComplete: appModel.app?.endTime != null,
207
208
  // Top level too, so a client that never asks for `summary` still sees the run was tuned.
208
209
  ...(tuned ? { tunedThresholds: tuned } : {}),
@@ -345,16 +346,19 @@ async function resolveAndAnalyze(
345
346
  ref , thresholds ,
346
347
  ) {
347
348
  const { runId, appModel } = await resolveOrCreateRun(ref);
348
- const catalog = analyze(
349
- appModel.app, appModel.stages, appModel.executors.added, appModel.executors.removed,
350
- appModel.jobs, appModel.sql, appModel.runAggregates, { thresholds },
351
- );
352
- return { runId, appModel, catalog };
349
+ return { runId, appModel, catalog: runFindings(appModel, thresholds).catalog };
353
350
  }
354
351
 
355
352
  export async function compareRuns(
356
- a , b , opts ,
353
+ a , b , opts
354
+ ,
357
355
  ) {
356
+ // Before any log is read: a bad pattern refuses the call, as the CLI's usage error does.
357
+ try {
358
+ compileNormalizePatterns(opts?.normalizePath);
359
+ } catch (e) {
360
+ throw mcpError('access-or-upstream-failure', `normalizePath: ${(e ).message}`);
361
+ }
358
362
  const [
359
363
  { runId: runIdA, appModel: appModelA, catalog: catalogA },
360
364
  { runId: runIdB, appModel: appModelB, catalog: catalogB },
@@ -364,26 +368,20 @@ export async function compareRuns(
364
368
  // interactive stage-detail drill-down, which none of compare/matchStages/metricDeltas/findingsDelta
365
369
  // read. Findings/metrics come from `catalog` and appModel.stages/sql, both fully populated. Produces
366
370
  // identical output to the dashboard; the MCP tool just never exposes per-task drill-down.
367
- const built = buildComparison(
371
+ const { comparison, output } = buildComparisonOutput(
368
372
  { label: runIdA, appModel: appModelA, catalog: catalogA },
369
373
  { label: runIdB, appModel: appModelB, catalog: catalogB },
374
+ { redact: opts?.redact, normalizePath: opts?.normalizePath, view: opts?.include?.includes('stagePairs') ? 'full' : 'summary' },
370
375
  );
371
- // Stage names throughout `built` carry raw Spark stage text, which can embed a host/IP token as
372
- // free text, the same residual redactReport() already scrubs from the evidence report.
373
- const result = opts?.redact ? redactComparison(built) : built;
374
- const verdict = comparisonVerdict(result);
375
376
 
376
377
  return {
377
378
  runIdA,
378
379
  runIdB,
379
- verdict,
380
- findingsDelta: result.findings,
381
- metricDeltas: result.metrics,
382
- confidence: result.confidence,
383
- reason: result.reason,
384
- matchedCoverage: result.matchedCoverage,
380
+ ...output,
381
+ findingsDelta: output.findings,
382
+ metricDeltas: output.metrics,
385
383
  ...tunedField(opts?.thresholds),
386
- ...(opts?.markdown ? { markdown: renderComparisonMarkdown(result, verdict, tunedDetectors(opts?.thresholds)) } : {}),
384
+ ...(opts?.markdown ? { markdown: renderComparisonMarkdown(comparison, output.verdict, tunedDetectors(opts?.thresholds)) } : {}),
387
385
  };
388
386
  }
389
387
 
@@ -408,6 +406,16 @@ export async function evaluateBudgetsForRun(
408
406
  if (budgets.regressionMetric !== undefined && budgets.maxRegressionPct === undefined) {
409
407
  throw mcpError('access-or-upstream-failure', 'regressionMetric requires maxRegressionPct.');
410
408
  }
409
+ // The CLI's rule: a metric is budgeted once across the single pair and the list.
410
+ try {
411
+ const pair = budgets.maxRegressionPct === undefined ? [] : [{ metric: budgets.regressionMetric ?? 'wallClock', maxPct: budgets.maxRegressionPct }];
412
+ combineRegressionBudgets([
413
+ ...pair.map((budget) => ({ origin: 'maxRegressionPct/regressionMetric', budget })),
414
+ ...(budgets.regressionBudgets ?? []).map((budget) => ({ origin: 'regressionBudgets', budget })),
415
+ ]);
416
+ } catch (e) {
417
+ throw mcpError('access-or-upstream-failure', (e ).message);
418
+ }
411
419
  const [first, second] = await Promise.all([
412
420
  resolveAndAnalyze(primary, opts?.thresholds),
413
421
  secondary ? resolveAndAnalyze(secondary, opts?.thresholds) : Promise.resolve(undefined),
@@ -418,11 +426,12 @@ export async function evaluateBudgetsForRun(
418
426
  // apply to the candidate.
419
427
  const candidate = second ?? first;
420
428
  const baseline = second ? first : undefined;
429
+ // Not redacted: no budget reads the stage text redaction rewrites (the parity suite checks it).
421
430
  const comparison = baseline
422
- ? buildComparison(
431
+ ? buildComparisonOutput(
423
432
  { label: baseline.runId, appModel: baseline.appModel, catalog: baseline.catalog },
424
433
  { label: candidate.runId, appModel: candidate.appModel, catalog: candidate.catalog },
425
- )
434
+ ).comparison
426
435
  : undefined;
427
436
 
428
437
  const { results, violated, inconclusive } = evaluateBudgets({
@@ -1,6 +1,6 @@
1
1
  // Flattens a resolved planTree into a { nodes, edges } graph shape for the
2
2
  // React Flow + Dagre plan graph view ("Plan graph view" in
3
- // docs-site/contributor-guide/architecture/drill-down.md). The Exchange
3
+ // docs-site/contributor-guide/architecture/drill-down/plan-graph.md). The Exchange
4
4
  // write/read split is done upstream now, in resolvePlanTree
5
5
  // (event-handlers.ts): this file just walks the already-split tree and maps
6
6
  // each real PlanNode onto a PlanGraphNodeData. sourceNodeId still lets a
@@ -44,6 +44,17 @@ function pushLongFilterWarning(result , len ) {
44
44
  result.warnings.push({ type: 'longFilterCondition', detail: `Filter condition is ${len} characters: consider simplifying or pushing the filter down closer to the scan.` });
45
45
  }
46
46
 
47
+ // True when a node reads internal Delta metadata: the _delta_log state scan or an anonymous
48
+ // commit/checkpoint read. `Location:` is often truncated to `Del...`, so it also keys off
49
+ // DeltaLogFileIndex or the Delta-log action column signature. (Column-signature heuristic: a
50
+ // future Spark renaming those action columns would leak a noise row; acceptable.) Shared by
51
+ // `scanRelationId` and the comparison's Delta bookkeeping flag (stage-alignment.ts).
52
+ export function isDeltaLogRead(name , detail ) {
53
+ return /_delta_log/.test(name) || /_delta_log/.test(detail) ||
54
+ /DeltaLogFileIndex/i.test(name + detail) ||
55
+ /checkpointMetadata#|sidecar#|commitInfo#/.test(name + detail);
56
+ }
57
+
47
58
  // Stable relation-identity key for a scan node: "<format>:<relation>" (e.g.
48
59
  // "delta:mx.store_map", "parquet:warehouse.sales", "jdbc:dw.dim_product")
49
60
  // or null when the node is internal Delta metadata / a non-scan / un-nameable.
@@ -70,16 +81,8 @@ export function scanRelationId(name , detail ) {
70
81
  return `${format}:${table.replace(/^spark_catalog\./, '')}`;
71
82
  }
72
83
 
73
- // 2. Internal Delta metadata (the _delta_log state scan and anonymous
74
- // commit/checkpoint reads) → null. Location: is often truncated to `Del...`,
75
- // so also key off DeltaLogFileIndex or the Delta-log action column signature.
76
- // (Column-signature heuristic: a future Spark renaming those action columns
77
- // would leak a noise row; acceptable.)
78
- if (/_delta_log/.test(name) || /_delta_log/.test(detail) ||
79
- /DeltaLogFileIndex/i.test(name + detail) ||
80
- /checkpointMetadata#|sidecar#|commitInfo#/.test(name + detail)) {
81
- return null;
82
- }
84
+ // 2. Internal Delta metadata (see isDeltaLogRead) → null.
85
+ if (isDeltaLogRead(name, detail)) return null;
83
86
 
84
87
  // 3. Anonymous real file read (InMemoryFileIndex, no catalog name) →
85
88
  // best-effort Location basename. Preserves temp/snapshot self-reuse; a
@@ -1,12 +1,12 @@
1
1
  import { computeWallClock } from './wall-clock.js';
2
2
  import { normalizeDetail } from './detectors.js';
3
- import { cyrb53 } from './string-hash.js';
3
+ import { normalizeStageName, stageIdentityWith, identityIndexWith, pairEqualCounts } from './stage-identity.js';
4
4
  import { computeAllocation } from './allocation.js';
5
5
  import { totalExecutorCpuMs, withEarlierAttempts } from './run-totals.js';
6
- import { planNodesOfStage } from './stage-plan-nodes.js';
7
6
  import { captureSnapshot } from './session-snapshot.js';
7
+ import { alignStages, compileNormalizePatterns, COMPARISON_SCHEMA_VERSION, } from './stage-alignment.js';
8
8
  import { tunedRunNote } from './threshold-overrides.js';
9
-
9
+
10
10
 
11
11
 
12
12
  import { isIncompleteRun } from './check-coverage.js';
@@ -21,13 +21,26 @@ import { summarizeRunOutcome } from './run-outcome.js';
21
21
 
22
22
 
23
23
 
24
+
25
+
26
+
27
+
24
28
 
25
29
 
26
-
30
+
31
+
32
+
33
+
27
34
 
35
+
36
+
37
+
38
+
39
+
40
+
28
41
 
29
42
 
30
-
43
+
31
44
 
32
45
 
33
46
 
@@ -41,68 +54,10 @@ function jobOutcome(snapshot ) {
41
54
  return { failedJobs, totalJobs, incomplete: isIncompleteRun(snapshot.catalog) };
42
55
  }
43
56
 
44
- // Replace run-varying tokens (digit runs, long hex ids) with a stable marker so
45
- // the same logical stage across two runs normalizes to one identity.
46
- export function normalizeStageName(name ) {
47
- return String(name)
48
- .toLowerCase()
49
- .replace(/\b[0-9a-f]{8,}\b/g, '#') // hex ids/uuids first (they contain digits)
50
- .replace(/\d+/g, '#')
51
- .replace(/\s+/g, ' ')
52
- .trim();
53
- }
54
-
55
- // Bottom-up, order-independent structural identity of a resolved plan tree:
56
- // each node folds its normalized name/detail with its children's digests
57
- // (children sorted, so AQE picking a different broadcast side still matches),
58
- // so two plans collide only when their whole shape and every node's detail
59
- // agree. `normalizeDetail` strips the run-to-run noise (expr ids, `plan_id=`,
60
- // codegen numbers, AQE build-side choice, commutative-operand order). Each
61
- // node folds to a fixed-length cyrb53 digest (JSON.stringify-encoded, so
62
- // detail text containing `<`/`>`/`{`/`,` can't collide two different plans)
63
- // instead of embedding full child identity strings, which would re-escape
64
- // every level below and blow identity size up to ~2^depth on the 20-60+
65
- // operator-deep plans real Spark produces.
66
- function planTreeIdentity(root ) {
67
- if (!root) return null;
68
- function visit(node ) {
69
- const childDigests = (node.children ?? []).map(visit).sort();
70
- return cyrb53(JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? ''), childDigests]));
71
- }
72
- return visit(root);
73
- }
74
-
75
- // Plan identity for the stage's SQL execution, scoped to only the plan nodes
76
- // this stage actually ran (`node.stageIds`), not the whole tree: two stages
77
- // sharing one SQL execution (e.g. a self-join's two Exchange stages) otherwise
78
- // collapse onto one identity regardless of which part of the plan each ran.
79
- // Falls back to the coarser whole-tree identity when the stage has no
80
- // attributed nodes (hand-built snapshots without `stageIds`, or unmatched
81
- // accumulables).
82
- function sqlNodeIdentity(stage , snapshot ) {
83
- const execId = stage.sqlExecutionId;
84
- if (execId == null) return '';
85
- const root = snapshot.sql.get(execId)?.planTree ?? null;
86
- if (!root) return '';
87
- const fingerprints = planNodesOfStage(stage, snapshot.sql)
88
- .map((node) => JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? '')]));
89
- if (fingerprints.length === 0) return planTreeIdentity(root) ?? '';
90
- return cyrb53(JSON.stringify(fingerprints.sort()));
91
- }
57
+ export { normalizeStageName };
92
58
 
93
59
  export function stageIdentity(stage , snapshot ) {
94
- return normalizeStageName(stage.name ?? '') + '§' + sqlNodeIdentity(stage, snapshot);
95
- }
96
-
97
- function identityIndex(snapshot ) {
98
- const byIdentity = new Map ();
99
- for (const [id, stage] of snapshot.stages) {
100
- const key = stageIdentity(stage, snapshot);
101
- const ids = byIdentity.get(key);
102
- if (ids) ids.push(id);
103
- else byIdentity.set(key, [id]);
104
- }
105
- return byIdentity;
60
+ return stageIdentityWith(stage, snapshot, normalizeDetail);
106
61
  }
107
62
 
108
63
  export function matchStages(baseSnap , candSnap )
@@ -111,10 +66,8 @@ export function matchStages(baseSnap , candSnap
111
66
 
112
67
 
113
68
  {
114
- const baseIdx = identityIndex(baseSnap);
115
- const candIdx = identityIndex(candSnap);
116
- const pairs = [];
117
- const matchedIdentities = new Set ();
69
+ const baseIdx = identityIndexWith(baseSnap, (stage) => stageIdentity(stage, baseSnap));
70
+ const candIdx = identityIndexWith(candSnap, (stage) => stageIdentity(stage, candSnap));
118
71
  const collisionIdentities = new Set ();
119
72
  // A collision is a single-run property: more than one stage in the SAME run
120
73
  // shares an identity. Recorded per-run regardless of whether the other run
@@ -122,26 +75,7 @@ export function matchStages(baseSnap , candSnap
122
75
  // don't, so this set means "was ambiguous", not "stayed unpaired".
123
76
  for (const idx of [baseIdx, candIdx])
124
77
  for (const [identity, ids] of idx) if (ids.length > 1) collisionIdentities.add(identity);
125
- // An identity colliding equally on both sides has no genuine ambiguity about
126
- // *count*, so pair its stages off positionally by sorted id rather than
127
- // dropping them. This is exact when the identity actually distinguishes
128
- // stages (e.g. self-comparing a run: every stage matches itself). It's a
129
- // best-effort guess when the identity is coarse (no SQL/attribution) and
130
- // the two sides are genuinely different runs -- two unrelated same-named
131
- // stages could get cross-paired. Accepted tradeoff: dropping them instead
132
- // would also sacrifice the exact-self-comparison case, which matters more.
133
- for (const identity of [...baseIdx.keys()].sort()) {
134
- const candIds = candIdx.get(identity);
135
- if (!candIds) continue;
136
- const baseIds = baseIdx.get(identity) ;
137
- if (baseIds.length !== candIds.length) continue;
138
- const sortedBaseIds = [...baseIds].sort((a, b) => a - b);
139
- const sortedCandIds = [...candIds].sort((a, b) => a - b);
140
- for (let i = 0; i < sortedBaseIds.length; i++) {
141
- pairs.push({ identity, baseId: sortedBaseIds[i], candId: sortedCandIds[i] });
142
- }
143
- matchedIdentities.add(identity);
144
- }
78
+ const { pairs, matchedIdentities } = pairEqualCounts(baseIdx, candIdx);
145
79
  const total = baseSnap.stages.size + candSnap.stages.size;
146
80
  // Both runs stage-less: nothing to compare, not "nothing matched".
147
81
  const coverage = total === 0 ? 1 : (2 * pairs.length) / total;
@@ -348,19 +282,19 @@ function namesConflict(a , b )
348
282
  }
349
283
 
350
284
  // A pair's skew ratio is null when the stage's duration wasn't measurable (see
351
- // `stageSkewRatio`). `baseId`/`candId` are carried through so a consumer can
352
- // key rows uniquely: two pairs can share one `identity` (a same-run collision
353
- // resolved positionally in matchStages), but never the same baseId+candId.
354
- function stageSkewDeltas(
355
- baseSnap ,
356
- candSnap ,
357
- match ,
358
- ) {
359
- return match.pairs.map((p) => {
360
- const b = stageSkewRatio(baseSnap.stages.get(p.baseId) );
361
- const c = stageSkewRatio(candSnap.stages.get(p.candId) );
362
- return { identity: p.identity, baseId: p.baseId, candId: p.candId, baseline: b, candidate: c, delta: b != null && c != null ? c - b : null };
363
- }).sort((x, y) => x.identity < y.identity ? -1 : x.identity > y.identity ? 1 : 0);
285
+ // `stageSkewRatio`). Rows come from the aligner's `stagePairs`, so the table and the coverage
286
+ // banner agree. A pair covers one stage per side here; `pairId` is the unique row key.
287
+ function stageSkewDeltas(baseSnap , candSnap , stagePairs ) {
288
+ return stagePairs.map((p) => {
289
+ const baseId = p.baseStageIds[0], candId = p.candStageIds[0];
290
+ const baseStage = baseSnap.stages.get(baseId) ;
291
+ const b = stageSkewRatio(baseStage);
292
+ const c = stageSkewRatio(candSnap.stages.get(candId) );
293
+ return {
294
+ pairId: p.pairId, name: normalizeStageName(baseStage.name ?? ''), baseId, candId,
295
+ baseline: b, candidate: c, delta: b != null && c != null ? c - b : null,
296
+ };
297
+ }).sort((x, y) => x.name < y.name ? -1 : x.name > y.name ? 1 : x.baseId - y.baseId);
364
298
  }
365
299
 
366
300
  // Compact, view-friendly per-stage record for the manual stage-pinning panel.
@@ -402,6 +336,14 @@ function stageList(snap )
402
336
  // caller's reference, so one shared empty Map is safe here.
403
337
  const EMPTY_TASK_DATA = new Map ();
404
338
 
339
+
340
+
341
+
342
+
343
+
344
+
345
+
346
+
405
347
  // Wraps the analyze()-to-compareRuns() snapshot-building sequence shared by
406
348
  // the CLI's --baseline path and mcp-tools.ts's compareRuns tool: both need
407
349
  // captureSnapshot (with an empty taskDataCache, the interactive drill-down
@@ -410,50 +352,65 @@ const EMPTY_TASK_DATA = new Map ();
410
352
  export function buildComparison(
411
353
  baseline ,
412
354
  candidate ,
355
+ options ,
413
356
  ) {
414
357
  return compareRuns(
415
358
  { label: baseline.label, snapshot: captureSnapshot(baseline.appModel, baseline.catalog, EMPTY_TASK_DATA) },
416
359
  { label: candidate.label, snapshot: captureSnapshot(candidate.appModel, candidate.catalog, EMPTY_TASK_DATA) },
360
+ options,
417
361
  );
418
362
  }
419
363
 
420
- // matchStages' coverage is a Dice coefficient: (2 * pairs.length) / (baseCount
421
- // + candCount). Below 0.5, more than half of each run's stages went unpaired,
422
- // so the stage-level rows (stageSkew, baseStages/candStages) mostly show
423
- // unrelated work side by side rather than the same stage before/after -- the
424
- // comparison is dominated by guesswork, not genuine pairing. 0.5 is thus the
425
- // natural midpoint for "more matched than not," not an arbitrary tuning knob.
426
- const LOW_COVERAGE_THRESHOLD = 0.5;
364
+ // `ok` needs this share of both runs' executor run time to sit in paired stages. Run time, not
365
+ // stage count: the stages a run spends its time in decide whether a per-stage delta compares the
366
+ // same work, and a count lets many small matched stages hide an unmatched heavy one.
367
+ const RUNTIME_COVERAGE_THRESHOLD = 0.9;
427
368
 
428
369
  export function compareRuns(
429
370
  baseline ,
430
371
  candidate ,
372
+ options = {},
431
373
  ) {
432
374
  const baseSnap = baseline.snapshot, candSnap = candidate.snapshot;
375
+ // The old exact matcher stays the source of `matchedCoverage`; pairs and the gate come from the aligner.
433
376
  const match = matchStages(baseSnap, candSnap);
377
+ const alignment = alignStages(baseSnap, candSnap, { normalizePath: compileNormalizePatterns(options.normalizePath) });
378
+ const runtimeCoverage = alignment.runtimeCoverage;
434
379
  // Name equality is a weak confidence signal, not a hard gate: renaming a job
435
380
  // is the normal way to label an A/B experiment, so a mismatch must not block
436
381
  // the (matching-free, name-independent) deltas. Surface it as `low` instead.
437
382
  const namesDiffer = namesConflict(baseSnap.app, candSnap.app);
438
- // Coverage is the other half of the signal: identical names on two runs that
439
- // barely share any stages are just as misleading as differing names on two
440
- // runs that match well, so either condition alone drops confidence to `low`.
441
- const lowCoverage = match.coverage < LOW_COVERAGE_THRESHOLD;
442
- const reason = namesDiffer && lowCoverage
443
- ? `Run names differ and only ${(match.coverage * 100).toFixed(0)}% of stages matched, so deltas may compare different work.`
383
+ // Runtime coverage is the other half of the signal: identical names on two runs whose heavy
384
+ // stages do not pair are just as misleading as differing names on two runs that pair well, so
385
+ // either condition alone drops confidence to `low`. No run time at all is `insufficient`.
386
+ const insufficient = runtimeCoverage === null;
387
+ const lowCoverage = runtimeCoverage !== null && runtimeCoverage < RUNTIME_COVERAGE_THRESHOLD;
388
+ const confidence = insufficient ? 'insufficient' : namesDiffer || lowCoverage ? 'low' : 'ok';
389
+ // Rounded down, so a share just under the gate never reads as 90%.
390
+ const share = runtimeCoverage === null ? '' : `${Math.floor(runtimeCoverage * 100)}%`;
391
+ const reason = insufficient
392
+ ? `${namesDiffer ? 'Run names differ and no' : 'No'} executor run time was recorded in either run, so there is no work to compare.`
393
+ : namesDiffer && lowCoverage
394
+ ? `Run names differ and only ${share} of executor run time is in matched stages, so deltas may compare different work.`
444
395
  : namesDiffer
445
396
  ? 'Run names differ, so deltas may compare different work.'
446
397
  : lowCoverage
447
- ? `Only ${(match.coverage * 100).toFixed(0)}% of stages matched between runs, so per-stage rows mostly compare unrelated work.`
398
+ ? `Only ${share} of executor run time is in matched stages, so per-stage rows mostly compare unrelated work.`
448
399
  : null;
449
400
  return {
450
401
  baselineLabel: baseline.label, candidateLabel: candidate.label,
451
- confidence: namesDiffer || lowCoverage ? 'low' : 'ok',
402
+ confidence,
452
403
  reason,
453
404
  matchedCoverage: match.coverage,
405
+ comparisonSchemaVersion: COMPARISON_SCHEMA_VERSION,
406
+ runtimeCoverage,
407
+ stagePairs: alignment.pairs,
408
+ unmatched: alignment.unmatched,
409
+ replanned: alignment.replanned,
410
+ bookkeepingStageIds: alignment.bookkeepingStageIds,
454
411
  metrics: metricDeltas(baseSnap, candSnap),
455
412
  findings: findingsDelta(baseSnap, candSnap),
456
- stageSkew: stageSkewDeltas(baseSnap, candSnap, match),
413
+ stageSkew: stageSkewDeltas(baseSnap, candSnap, alignment.pairs),
457
414
  baseStages: stageList(baseSnap),
458
415
  candStages: stageList(candSnap),
459
416
  jobOutcomes: { baseline: jobOutcome(baseSnap), candidate: jobOutcome(candSnap) },
@@ -491,11 +448,13 @@ export function renderComparisonMarkdown(
491
448
  if (verdict.sentences.length > 0) lines.push('', verdict.sentences.join(' '));
492
449
  lines.push('');
493
450
  }
494
- if (comparison.confidence === 'low') {
495
- lines.push(`- confidence: low, ${comparison.reason}`);
451
+ if (comparison.confidence !== 'ok') {
452
+ lines.push(`- confidence: ${comparison.confidence}, ${comparison.reason}`);
496
453
  lines.push('');
497
454
  }
498
455
  lines.push(`- matched stage coverage: ${(comparison.matchedCoverage * 100).toFixed(1)}%`);
456
+ lines.push(`- runtime coverage: ${comparison.runtimeCoverage === null ? 'n/a' : `${(comparison.runtimeCoverage * 100).toFixed(1)}%`}`);
457
+ lines.push(`- stage pairs: ${comparison.stagePairs.length}, unmatched: ${comparison.unmatched.baseStageIds.length} baseline / ${comparison.unmatched.candStageIds.length} candidate, Delta bookkeeping: ${comparison.bookkeepingStageIds.baseStageIds.length} baseline / ${comparison.bookkeepingStageIds.candStageIds.length} candidate`);
499
458
  lines.push('');
500
459
  lines.push('### Metric deltas');
501
460
  lines.push('');
@@ -0,0 +1,26 @@
1
+ import { runFindings } from './evidence-report.js';
2
+ import { redactRunModel } from './redact.js';
3
+ import { computeRunMetrics, } from './run-metrics.js';
4
+ import { buildEffectiveConf, } from './effective-conf.js';
5
+
6
+
7
+
8
+ /** The CLI report's `metrics` and `effectiveConf` blocks, shared with MCP diagnose_run. Under
9
+ * `redact` they come from the run redacted with the report's own inputs, so host and app
10
+ * pseudonyms match the report's. `confKeys`/`confRedactRegex` narrow and mask `effectiveConf`. */
11
+ export function runOutputBlocks(
12
+ appModel ,
13
+ { redact, thresholds, confKeys, confRedactRegex }
14
+
15
+ = {},
16
+ ) {
17
+ let blocksModel = appModel;
18
+ if (redact) {
19
+ const { catalog, config } = runFindings(appModel, thresholds);
20
+ blocksModel = redactRunModel(appModel, catalog, config).appModel;
21
+ }
22
+ return {
23
+ metrics: computeRunMetrics(blocksModel, thresholds),
24
+ effectiveConf: buildEffectiveConf(blocksModel.app, { keys: confKeys, userPattern: confRedactRegex }),
25
+ };
26
+ }