@launchsecure/launch-kit 0.0.29 → 0.0.31

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 (202) hide show
  1. package/dist/beacon/beacon.mjs +2825 -1243
  2. package/dist/beacon/beacon.mjs.map +1 -1
  3. package/dist/beacon/beacon.umd.js +710 -95
  4. package/dist/beacon/beacon.umd.js.map +1 -1
  5. package/dist/beacon/types/core.d.ts +14 -0
  6. package/dist/beacon/types/core.d.ts.map +1 -0
  7. package/dist/beacon/types/ctx.d.ts +14 -0
  8. package/dist/beacon/types/ctx.d.ts.map +1 -0
  9. package/dist/beacon/types/element.d.ts +16 -48
  10. package/dist/beacon/types/element.d.ts.map +1 -1
  11. package/dist/beacon/types/index.d.ts +5 -4
  12. package/dist/beacon/types/index.d.ts.map +1 -1
  13. package/dist/beacon/types/internal/annotation-cache.d.ts +10 -0
  14. package/dist/beacon/types/internal/annotation-cache.d.ts.map +1 -0
  15. package/dist/beacon/types/internal/element-capture.d.ts +19 -0
  16. package/dist/beacon/types/internal/element-capture.d.ts.map +1 -0
  17. package/dist/beacon/types/internal/event-buffer.d.ts +16 -0
  18. package/dist/beacon/types/internal/event-buffer.d.ts.map +1 -0
  19. package/dist/beacon/types/internal/framework-detect.d.ts +6 -0
  20. package/dist/beacon/types/internal/framework-detect.d.ts.map +1 -0
  21. package/dist/beacon/types/internal/markers.d.ts +17 -0
  22. package/dist/beacon/types/internal/markers.d.ts.map +1 -0
  23. package/dist/beacon/types/internal/monitor/capture-dom.d.ts +14 -0
  24. package/dist/beacon/types/internal/monitor/capture-dom.d.ts.map +1 -0
  25. package/dist/beacon/types/internal/monitor/capture-network.d.ts +12 -0
  26. package/dist/beacon/types/internal/monitor/capture-network.d.ts.map +1 -0
  27. package/dist/beacon/types/internal/monitor/overlay.d.ts +16 -0
  28. package/dist/beacon/types/internal/monitor/overlay.d.ts.map +1 -0
  29. package/dist/beacon/types/internal/monitor/session.d.ts +41 -0
  30. package/dist/beacon/types/internal/monitor/session.d.ts.map +1 -0
  31. package/dist/beacon/types/{monitor → internal/monitor}/transport.d.ts +3 -3
  32. package/dist/beacon/types/internal/monitor/transport.d.ts.map +1 -0
  33. package/dist/beacon/types/{monitor/types.d.ts → internal/monitor/wire.d.ts} +69 -27
  34. package/dist/beacon/types/internal/monitor/wire.d.ts.map +1 -0
  35. package/dist/beacon/types/{ui → internal}/pick-mode-overlay.d.ts +4 -5
  36. package/dist/beacon/types/internal/pick-mode-overlay.d.ts.map +1 -0
  37. package/dist/beacon/types/{capture → internal}/picker.d.ts +0 -1
  38. package/dist/beacon/types/internal/picker.d.ts.map +1 -0
  39. package/dist/beacon/types/{ui → internal}/pin-popover.d.ts +1 -1
  40. package/dist/beacon/types/internal/pin-popover.d.ts.map +1 -0
  41. package/dist/beacon/types/internal/screenshot.d.ts +26 -0
  42. package/dist/beacon/types/internal/screenshot.d.ts.map +1 -0
  43. package/dist/beacon/types/internal/selector.d.ts.map +1 -0
  44. package/dist/beacon/types/plugins/domEle.d.ts +14 -0
  45. package/dist/beacon/types/plugins/domEle.d.ts.map +1 -0
  46. package/dist/beacon/types/plugins/domSS.d.ts +8 -0
  47. package/dist/beacon/types/plugins/domSS.d.ts.map +1 -0
  48. package/dist/beacon/types/plugins/errors.d.ts +3 -0
  49. package/dist/beacon/types/plugins/errors.d.ts.map +1 -0
  50. package/dist/beacon/types/plugins/index.d.ts +8 -0
  51. package/dist/beacon/types/plugins/index.d.ts.map +1 -0
  52. package/dist/beacon/types/plugins/liveMonitor.d.ts +14 -0
  53. package/dist/beacon/types/plugins/liveMonitor.d.ts.map +1 -0
  54. package/dist/beacon/types/plugins/metadata.d.ts +3 -0
  55. package/dist/beacon/types/plugins/metadata.d.ts.map +1 -0
  56. package/dist/beacon/types/registry.d.ts +33 -0
  57. package/dist/beacon/types/registry.d.ts.map +1 -0
  58. package/dist/beacon/types/styles.d.ts +8 -0
  59. package/dist/beacon/types/styles.d.ts.map +1 -0
  60. package/dist/beacon/types/transport.d.ts +3 -0
  61. package/dist/beacon/types/transport.d.ts.map +1 -0
  62. package/dist/beacon/types/types.d.ts +152 -68
  63. package/dist/beacon/types/types.d.ts.map +1 -1
  64. package/dist/beacon/types/ui/dialog.d.ts +53 -0
  65. package/dist/beacon/types/ui/dialog.d.ts.map +1 -0
  66. package/dist/beacon/types/ui/form.d.ts +7 -0
  67. package/dist/beacon/types/ui/form.d.ts.map +1 -0
  68. package/dist/beacon/types/ui/overlay.d.ts +6 -0
  69. package/dist/beacon/types/ui/overlay.d.ts.map +1 -0
  70. package/dist/chart-client/assets/{index-CJ4mgRRF.css → index-CDIhdgWg.css} +1 -1
  71. package/dist/chart-client/index.html +2 -2
  72. package/dist/client/assets/{index-DI5qSR_w.css → index-CfW4n40I.css} +1 -1
  73. package/dist/client/index.html +2 -2
  74. package/dist/council-client/assets/{index-C_-vAM9L.css → index-CZim6x1u.css} +1 -1
  75. package/dist/council-client/index.html +2 -2
  76. package/dist/deck-client/assets/{_baseUniq-W2JQDmje.js → _baseUniq-DdHaBFYO.js} +1 -1
  77. package/dist/deck-client/assets/{arc-DIBWAId9.js → arc-D98e_18X.js} +1 -1
  78. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-CAIRMvJK.js → architectureDiagram-Q4EWVU46-DNFZzh-4.js} +1 -1
  79. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BeNaNiOi.js → blockDiagram-DXYQGD6D-DeQvGUdX.js} +1 -1
  80. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-B9Ozi62h.js → c4Diagram-AHTNJAMY-B6ekZf1n.js} +1 -1
  81. package/dist/deck-client/assets/channel-DmR7Tyyt.js +1 -0
  82. package/dist/deck-client/assets/{chunk-4BX2VUAB-D7AZ47dt.js → chunk-4BX2VUAB-9aDWymq2.js} +1 -1
  83. package/dist/deck-client/assets/{chunk-4TB4RGXK-DnVnNPcI.js → chunk-4TB4RGXK-DtKQqaI7.js} +1 -1
  84. package/dist/deck-client/assets/{chunk-55IACEB6-UKYs-YNd.js → chunk-55IACEB6-COy9hEae.js} +1 -1
  85. package/dist/deck-client/assets/{chunk-EDXVE4YY-D43b-SKn.js → chunk-EDXVE4YY-D_f861An.js} +1 -1
  86. package/dist/deck-client/assets/{chunk-FMBD7UC4-QzBAoyyW.js → chunk-FMBD7UC4-CmuA5UKn.js} +1 -1
  87. package/dist/deck-client/assets/{chunk-OYMX7WX6-Cjif4r6W.js → chunk-OYMX7WX6-vT8z8D-0.js} +1 -1
  88. package/dist/deck-client/assets/{chunk-QZHKN3VN-CqLDirEI.js → chunk-QZHKN3VN-CTlwwg-R.js} +1 -1
  89. package/dist/deck-client/assets/{chunk-YZCP3GAM-_FQvmMs4.js → chunk-YZCP3GAM-C44yr620.js} +1 -1
  90. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-Bl4ozQWs.js +1 -0
  91. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-Bl4ozQWs.js +1 -0
  92. package/dist/deck-client/assets/clone-BAy58j24.js +1 -0
  93. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-rfrocesE.js → cose-bilkent-S5V4N54A-DBB2J2nL.js} +1 -1
  94. package/dist/deck-client/assets/{dagre-KV5264BT-Bv_7DJat.js → dagre-KV5264BT-DxDTYbKl.js} +1 -1
  95. package/dist/deck-client/assets/{diagram-5BDNPKRD-4F1414G5.js → diagram-5BDNPKRD-DByWrWd1.js} +1 -1
  96. package/dist/deck-client/assets/{diagram-G4DWMVQ6-C4-Pszqm.js → diagram-G4DWMVQ6-B8B6ddMq.js} +1 -1
  97. package/dist/deck-client/assets/{diagram-MMDJMWI5-B647TIx9.js → diagram-MMDJMWI5-BMUZ2PWK.js} +1 -1
  98. package/dist/deck-client/assets/{diagram-TYMM5635-BFAqpezd.js → diagram-TYMM5635-Bk9e8BB-.js} +1 -1
  99. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-BfBfrJOC.js → erDiagram-SMLLAGMA-DcOSwSol.js} +1 -1
  100. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-DX9YAYes.js → flowDiagram-DWJPFMVM-DI-4BR0F.js} +1 -1
  101. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-DCuiy7wF.js → ganttDiagram-T4ZO3ILL-BeZuXBoU.js} +1 -1
  102. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-CGp1IXUh.js → gitGraphDiagram-UUTBAWPF-Bcki__f-.js} +1 -1
  103. package/dist/deck-client/assets/{graph-B7g8aoxv.js → graph-CifKx6G1.js} +1 -1
  104. package/dist/deck-client/assets/index-6sdqbm2o.js +2 -0
  105. package/dist/deck-client/assets/{index-DsIZ3LqL.css → index-BlTlhxFW.css} +1 -1
  106. package/dist/deck-client/assets/index-CB-qlwRT.js +1195 -0
  107. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-L3fahMkF.js → infoDiagram-42DDH7IO-CReN1nFN.js} +1 -1
  108. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-aS_EjWBZ.js → ishikawaDiagram-UXIWVN3A-CDF_VLN_.js} +1 -1
  109. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-djTSQZF9.js → journeyDiagram-VCZTEJTY-DwgGrNVB.js} +1 -1
  110. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CcTHo4CM.js → kanban-definition-6JOO6SKY-DB_zohh5.js} +1 -1
  111. package/dist/deck-client/assets/{layout-mEJiadb7.js → layout-DFfX1O3z.js} +1 -1
  112. package/dist/deck-client/assets/{linear-XgTKqyRu.js → linear-CtKb4EXj.js} +1 -1
  113. package/dist/deck-client/assets/{min-Ct9jZdpd.js → min-DCRRwUZv.js} +1 -1
  114. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-BaFxCGNU.js → mindmap-definition-QFDTVHPH-D0QBOiFe.js} +1 -1
  115. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-CIbYYjtw.js → pieDiagram-DEJITSTG-CD-EV5WB.js} +1 -1
  116. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-D9EtCOvh.js → quadrantDiagram-34T5L4WZ-B-JXZ8xI.js} +1 -1
  117. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-xeni9eVG.js → requirementDiagram-MS252O5E-D2_OK5Dp.js} +1 -1
  118. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-LYeknz9h.js → sankeyDiagram-XADWPNL6-BbBJqVSC.js} +1 -1
  119. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-RDbsKFZf.js → sequenceDiagram-FGHM5R23-Db8A-Rkk.js} +1 -1
  120. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-BH1Zjglk.js → stateDiagram-FHFEXIEX-DGJnanjS.js} +1 -1
  121. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CR7riiab.js +1 -0
  122. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-IFXxKptt.js → timeline-definition-GMOUNBTQ-BRkr6T4w.js} +1 -1
  123. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-D-sLkQs9.js → vennDiagram-DHZGUBPP-d0rsTqFo.js} +1 -1
  124. package/dist/deck-client/assets/{wardley-RL74JXVD-C010F8l4.js → wardley-RL74JXVD-2t7cMqdS.js} +1 -1
  125. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-BTjjuDU3.js → wardleyDiagram-NUSXRM2D-DzboAsHh.js} +1 -1
  126. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-AYbv92n-.js → xychartDiagram-5P7HB3ND-CgTP9u2V.js} +1 -1
  127. package/dist/deck-client/index.html +2 -2
  128. package/dist/server/beacon-monitor-entry.js +548 -6
  129. package/dist/server/chart-serve.js +917 -248
  130. package/dist/server/cli.js +2033 -385
  131. package/dist/server/deck-mcp-entry.js +141 -21
  132. package/dist/server/deck-serve.js +141 -21
  133. package/dist/server/graph-mcp-entry.js +1991 -333
  134. package/dist/server/init-entry.js +24 -13
  135. package/dist/server/orbit-entry.js +135 -7
  136. package/dist/server/parse-worker-entry.js +918 -247
  137. package/package.json +4 -2
  138. package/scaffolds/ls-marketplace/plugins/kit/skills/analyse/SKILL.md +180 -0
  139. package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-array/SKILL.md +107 -0
  140. package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-clear/SKILL.md +94 -0
  141. package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-pulse/SKILL.md +82 -0
  142. package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-scan/SKILL.md +66 -0
  143. package/scaffolds/ls-marketplace/plugins/kit/skills/blast-radius/SKILL.md +117 -0
  144. package/scaffolds/ls-marketplace/plugins/kit/skills/brief/SKILL.md +112 -0
  145. package/scaffolds/ls-marketplace/plugins/kit/skills/course/SKILL.md +84 -0
  146. package/scaffolds/ls-marketplace/plugins/kit/skills/debug/SKILL.md +85 -0
  147. package/scaffolds/ls-marketplace/plugins/kit/skills/deploy-check/SKILL.md +160 -0
  148. package/scaffolds/ls-marketplace/plugins/kit/skills/diagram/SKILL.md +152 -0
  149. package/scaffolds/ls-marketplace/plugins/kit/skills/orbit/SKILL.md +87 -0
  150. package/scaffolds/ls-marketplace/plugins/kit/skills/prototype/SKILL.md +110 -0
  151. package/scaffolds/ls-marketplace/plugins/kit/skills/recovery/SKILL.md +95 -0
  152. package/scaffolds/ls-marketplace/plugins/kit/{commands/show-mcp-status.md → skills/show-mcp-status/SKILL.md} +4 -4
  153. package/scaffolds/ls-marketplace/plugins/kit/skills/wireframe/SKILL.md +90 -0
  154. package/scaffolds/statusline/statusline-mcp.sh +21 -9
  155. package/dist/beacon/types/capture/element.d.ts +0 -3
  156. package/dist/beacon/types/capture/element.d.ts.map +0 -1
  157. package/dist/beacon/types/capture/events.d.ts +0 -20
  158. package/dist/beacon/types/capture/events.d.ts.map +0 -1
  159. package/dist/beacon/types/capture/framework.d.ts +0 -3
  160. package/dist/beacon/types/capture/framework.d.ts.map +0 -1
  161. package/dist/beacon/types/capture/metadata.d.ts +0 -3
  162. package/dist/beacon/types/capture/metadata.d.ts.map +0 -1
  163. package/dist/beacon/types/capture/overlay.d.ts +0 -7
  164. package/dist/beacon/types/capture/overlay.d.ts.map +0 -1
  165. package/dist/beacon/types/capture/picker.d.ts.map +0 -1
  166. package/dist/beacon/types/capture/screenshot.d.ts +0 -7
  167. package/dist/beacon/types/capture/screenshot.d.ts.map +0 -1
  168. package/dist/beacon/types/capture/selector.d.ts.map +0 -1
  169. package/dist/beacon/types/monitor/dom.d.ts +0 -13
  170. package/dist/beacon/types/monitor/dom.d.ts.map +0 -1
  171. package/dist/beacon/types/monitor/index.d.ts +0 -19
  172. package/dist/beacon/types/monitor/index.d.ts.map +0 -1
  173. package/dist/beacon/types/monitor/network.d.ts +0 -12
  174. package/dist/beacon/types/monitor/network.d.ts.map +0 -1
  175. package/dist/beacon/types/monitor/transport.d.ts.map +0 -1
  176. package/dist/beacon/types/monitor/types.d.ts.map +0 -1
  177. package/dist/beacon/types/transport/submit.d.ts +0 -3
  178. package/dist/beacon/types/transport/submit.d.ts.map +0 -1
  179. package/dist/beacon/types/ui/button.d.ts +0 -2
  180. package/dist/beacon/types/ui/button.d.ts.map +0 -1
  181. package/dist/beacon/types/ui/drawer.d.ts +0 -33
  182. package/dist/beacon/types/ui/drawer.d.ts.map +0 -1
  183. package/dist/beacon/types/ui/icons.d.ts +0 -9
  184. package/dist/beacon/types/ui/icons.d.ts.map +0 -1
  185. package/dist/beacon/types/ui/monitor-panel.d.ts +0 -19
  186. package/dist/beacon/types/ui/monitor-panel.d.ts.map +0 -1
  187. package/dist/beacon/types/ui/pick-mode-overlay.d.ts.map +0 -1
  188. package/dist/beacon/types/ui/pin-popover.d.ts.map +0 -1
  189. package/dist/deck-client/assets/channel-CRdozqbp.js +0 -1
  190. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-lIZMp57W.js +0 -1
  191. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-lIZMp57W.js +0 -1
  192. package/dist/deck-client/assets/clone-BtWeSTyJ.js +0 -1
  193. package/dist/deck-client/assets/index-Dg1r-WSN.js +0 -476
  194. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BrV78NDR.js +0 -1
  195. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-array.md +0 -92
  196. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-clear.md +0 -68
  197. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-pulse.md +0 -80
  198. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-scan.md +0 -62
  199. /package/dist/beacon/types/{capture → internal}/selector.d.ts +0 -0
  200. /package/dist/chart-client/assets/{index-Ccy-DpI-.js → index-B__ARB8k.js} +0 -0
  201. /package/dist/client/assets/{index-Dp0_okva.js → index-h8kMzVtG.js} +0 -0
  202. /package/dist/council-client/assets/{index-Dt4zWKSj.js → index-CWaDcsFR.js} +0 -0
@@ -653,7 +653,7 @@ function resolveWorkerPath() {
653
653
  );
654
654
  }
655
655
  function runParseInWorker(req) {
656
- return new Promise((resolve5, reject) => {
656
+ return new Promise((resolve6, reject) => {
657
657
  let workerPath;
658
658
  try {
659
659
  workerPath = resolveWorkerPath();
@@ -672,7 +672,7 @@ function runParseInWorker(req) {
672
672
  };
673
673
  worker.on("message", (reply) => {
674
674
  if (reply.ok) {
675
- finish(() => resolve5({ results: reply.results, failedFiles: reply.failedFiles }));
675
+ finish(() => resolve6({ results: reply.results, failedFiles: reply.failedFiles }));
676
676
  } else {
677
677
  const err2 = new Error(reply.error.message);
678
678
  err2.name = reply.error.name;
@@ -916,7 +916,7 @@ var init_freshness = __esm({
916
916
  function getAvailableLayers(rootDir) {
917
917
  const dir = (0, import_node_path10.join)(rootDir, GRAPHS_DIR2);
918
918
  if (!(0, import_node_fs8.existsSync)(dir)) return [];
919
- return (0, import_node_fs8.readdirSync)(dir).filter((f) => f.endsWith(".json") && !NON_LAYER_GRAPH_FILES.has(f)).map((f) => f.replace(".json", ""));
919
+ return (0, import_node_fs8.readdirSync)(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".") && !NON_LAYER_GRAPH_FILES.has(f)).map((f) => f.replace(".json", ""));
920
920
  }
921
921
  function graphsDir(rootDir) {
922
922
  return (0, import_node_path10.join)(rootDir, GRAPHS_DIR2);
@@ -2354,6 +2354,8 @@ function extractDeep(absPath) {
2354
2354
  false
2355
2355
  );
2356
2356
  const hasEffects = Object.keys(fileEffects).length > 0;
2357
+ const uiLabels = collectUiLabels(root);
2358
+ const notes = collectNotes(root);
2357
2359
  return {
2358
2360
  elements,
2359
2361
  stateVars,
@@ -2361,10 +2363,77 @@ function extractDeep(absPath) {
2361
2363
  variables,
2362
2364
  responses,
2363
2365
  params,
2364
- ...hasEffects ? { effects: fileEffects } : {}
2366
+ ...hasEffects ? { effects: fileEffects } : {},
2367
+ ...uiLabels.length > 0 ? { ui_labels: uiLabels } : {},
2368
+ ...notes.length > 0 ? { notes } : {}
2365
2369
  };
2366
2370
  }
2367
- var import_node_fs11, import_node_path13, tsxLanguage, parserInstance, TreeSitterCtor, initPromise, initialized, queriesDir, queryCache, MAX_PARSEABLE_BYTES, MAX_CONSECUTIVE_PARSE_FAILURES, consecutiveParseFailures, ParseCascadeError, PRISMA_MUTATION_METHODS_BUILTIN, SUPABASE_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods, INLINE_AUTH_IMPORTS, EXEMPT_NAME_PATTERNS, PROTECT_NAME_PATTERNS, TRUST_AS_PROTECT_KEYS, TIMER_FNS, DOM_METHOD_NAMES, CLASSLIST_METHODS, STORAGE_OBJECTS, HISTORY_METHODS, LOCATION_METHODS, ASSIGN_DOM_PROPS;
2371
+ function collectNotes(root) {
2372
+ const out = [];
2373
+ const seen = /* @__PURE__ */ new Set();
2374
+ function visit(node) {
2375
+ if (out.length >= NOTES_MAX) return;
2376
+ if (node.type === "comment") {
2377
+ const text = node.text;
2378
+ const startRow = node.startPosition.row;
2379
+ NOTE_REGEX.lastIndex = 0;
2380
+ let m;
2381
+ while ((m = NOTE_REGEX.exec(text)) !== null) {
2382
+ const kind = m[1];
2383
+ const author = m[2];
2384
+ const body = m[3];
2385
+ if (!kind || !body) continue;
2386
+ const newlinesBefore = (text.slice(0, m.index).match(/\n/g) ?? []).length;
2387
+ const line = startRow + 1 + newlinesBefore;
2388
+ const key = `${line}:${kind}`;
2389
+ if (seen.has(key)) continue;
2390
+ seen.add(key);
2391
+ const note = {
2392
+ kind,
2393
+ text: body.length <= 200 ? body : body.slice(0, 200) + "...",
2394
+ line
2395
+ };
2396
+ if (author) note.author = author;
2397
+ out.push(note);
2398
+ if (out.length >= NOTES_MAX) return;
2399
+ }
2400
+ }
2401
+ for (const child of node.namedChildren) {
2402
+ visit(child);
2403
+ if (out.length >= NOTES_MAX) return;
2404
+ }
2405
+ }
2406
+ visit(root);
2407
+ return out;
2408
+ }
2409
+ function collectUiLabels(root) {
2410
+ const out = [];
2411
+ const seen = /* @__PURE__ */ new Set();
2412
+ function visit(node) {
2413
+ if (out.length >= UI_LABELS_MAX) return;
2414
+ if (node.type === "pair") {
2415
+ const key = node.childForFieldName("key");
2416
+ const val = node.childForFieldName("value");
2417
+ if (key && val) {
2418
+ const keyText = key.type === "property_identifier" ? key.text : stringLiteralValue(key) ?? key.text;
2419
+ if (UI_LABEL_KEYS.has(keyText)) {
2420
+ const strVal = stringLiteralValue(val);
2421
+ if (strVal && strVal.length > 0 && strVal.length <= 200 && !seen.has(strVal)) {
2422
+ seen.add(strVal);
2423
+ out.push(strVal);
2424
+ }
2425
+ }
2426
+ }
2427
+ }
2428
+ for (const child of node.namedChildren) {
2429
+ visit(child);
2430
+ if (out.length >= UI_LABELS_MAX) return;
2431
+ }
2432
+ }
2433
+ visit(root);
2434
+ return out;
2435
+ }
2436
+ var import_node_fs11, import_node_path13, tsxLanguage, parserInstance, TreeSitterCtor, initPromise, initialized, queriesDir, queryCache, MAX_PARSEABLE_BYTES, MAX_CONSECUTIVE_PARSE_FAILURES, consecutiveParseFailures, ParseCascadeError, PRISMA_MUTATION_METHODS_BUILTIN, SUPABASE_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods, INLINE_AUTH_IMPORTS, EXEMPT_NAME_PATTERNS, PROTECT_NAME_PATTERNS, TRUST_AS_PROTECT_KEYS, TIMER_FNS, DOM_METHOD_NAMES, CLASSLIST_METHODS, STORAGE_OBJECTS, HISTORY_METHODS, LOCATION_METHODS, ASSIGN_DOM_PROPS, UI_LABEL_KEYS, UI_LABELS_MAX, NOTE_REGEX, NOTES_MAX;
2368
2437
  var init_ts_extractor = __esm({
2369
2438
  "src/server/graph/core/ts-extractor.ts"() {
2370
2439
  "use strict";
@@ -2483,6 +2552,27 @@ var init_ts_extractor = __esm({
2483
2552
  "selected",
2484
2553
  "disabled"
2485
2554
  ]);
2555
+ UI_LABEL_KEYS = /* @__PURE__ */ new Set([
2556
+ "label",
2557
+ "title",
2558
+ "name",
2559
+ "text",
2560
+ "description",
2561
+ "placeholder",
2562
+ "tooltip",
2563
+ "heading",
2564
+ "subheading",
2565
+ "sheetTitle",
2566
+ "sheetDescription",
2567
+ "caption",
2568
+ "cta",
2569
+ "buttonText",
2570
+ "emptyText",
2571
+ "subtitle"
2572
+ ]);
2573
+ UI_LABELS_MAX = 200;
2574
+ NOTE_REGEX = /^\s*(?:\/\/|\/\*+|\*)\s*([A-Z][A-Z0-9_]{1,15})(?:\(([^)]+)\))?:\s*(\S.*?)\s*(?:\*\/)?$/gm;
2575
+ NOTES_MAX = 100;
2486
2576
  }
2487
2577
  });
2488
2578
 
@@ -2881,6 +2971,7 @@ function generate(rootDir) {
2881
2971
  responses: deep.responses,
2882
2972
  params: deep.params,
2883
2973
  ...deep.effects ? { effects: deep.effects } : {},
2974
+ ...deep.notes ? { notes: deep.notes } : {},
2884
2975
  _dbCalls: dbCalls
2885
2976
  // temp: used for cross-ref building below
2886
2977
  });
@@ -2901,6 +2992,8 @@ function generate(rootDir) {
2901
2992
  conditions: deep.conditions,
2902
2993
  variables: deep.variables,
2903
2994
  ...deep.effects ? { effects: deep.effects } : {},
2995
+ ...deep.ui_labels ? { ui_labels: deep.ui_labels } : {},
2996
+ ...deep.notes ? { notes: deep.notes } : {},
2904
2997
  ...authWrappers.length > 0 ? { auth: authWrappers } : {},
2905
2998
  ...dbCalls.length > 0 ? { _dbCalls: dbCalls } : {}
2906
2999
  });
@@ -3539,241 +3632,6 @@ function pgTypeToPrisma(pgType) {
3539
3632
  const upper = pgType.toUpperCase().trim();
3540
3633
  return PG_TO_PRISMA[upper] ?? upper;
3541
3634
  }
3542
- function bareName(captured) {
3543
- const parts = captured.split(".");
3544
- const last = parts[parts.length - 1];
3545
- return last.replace(/^"(.*)"$/, "$1").trim();
3546
- }
3547
- function parseCreateTable(sql, state) {
3548
- const re = new RegExp(
3549
- `CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${QID})\\s*\\(([\\s\\S]*?)\\);`,
3550
- "gi"
3551
- );
3552
- let m;
3553
- while ((m = re.exec(sql)) !== null) {
3554
- const tableName = bareName(m[1]);
3555
- const body = m[2];
3556
- const columns = /* @__PURE__ */ new Map();
3557
- let primaryCol = null;
3558
- const inlineFks = [];
3559
- const lines = splitTopLevelCommas(body);
3560
- for (const raw of lines) {
3561
- const trimmed = raw.trim().replace(/,\s*$/, "");
3562
- if (!trimmed || trimmed.startsWith("--")) continue;
3563
- const namedPk = trimmed.match(new RegExp(`^CONSTRAINT\\s+${ID}\\s+PRIMARY\\s+KEY\\s*\\(\\s*(${QID})`, "i"));
3564
- if (namedPk) {
3565
- primaryCol = bareName(namedPk[1]);
3566
- continue;
3567
- }
3568
- const tablePk = trimmed.match(new RegExp(`^PRIMARY\\s+KEY\\s*\\(\\s*(${QID})`, "i"));
3569
- if (tablePk) {
3570
- primaryCol = bareName(tablePk[1]);
3571
- continue;
3572
- }
3573
- if (/^UNIQUE\s*\(/i.test(trimmed)) continue;
3574
- const namedFk = trimmed.match(new RegExp(
3575
- `^CONSTRAINT\\s+(${ID})\\s+FOREIGN\\s+KEY\\s*\\(\\s*(${QID})\\s*\\)\\s+REFERENCES\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?`,
3576
- "i"
3577
- ));
3578
- if (namedFk) {
3579
- inlineFks.push({
3580
- constraintName: bareName(namedFk[1]),
3581
- sourceTable: tableName,
3582
- sourceColumn: bareName(namedFk[2]),
3583
- targetTable: bareName(namedFk[3]),
3584
- targetColumn: bareName(namedFk[4]),
3585
- onDelete: namedFk[5] ?? null
3586
- });
3587
- continue;
3588
- }
3589
- const bareFk = trimmed.match(new RegExp(
3590
- `^FOREIGN\\s+KEY\\s*\\(\\s*(${QID})\\s*\\)\\s+REFERENCES\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?`,
3591
- "i"
3592
- ));
3593
- if (bareFk) {
3594
- inlineFks.push({
3595
- constraintName: `${tableName}_${bareName(bareFk[1])}_fkey`,
3596
- sourceTable: tableName,
3597
- sourceColumn: bareName(bareFk[1]),
3598
- targetTable: bareName(bareFk[2]),
3599
- targetColumn: bareName(bareFk[3]),
3600
- onDelete: bareFk[4] ?? null
3601
- });
3602
- continue;
3603
- }
3604
- if (/^CONSTRAINT\s/i.test(trimmed)) continue;
3605
- const colMatch = trimmed.match(new RegExp(`^(${ID})\\s+(.+)`, "i"));
3606
- if (!colMatch) continue;
3607
- const colName = bareName(colMatch[1]);
3608
- let rest = colMatch[2];
3609
- const inlineRefMatch = rest.match(new RegExp(
3610
- `\\bREFERENCES\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?`,
3611
- "i"
3612
- ));
3613
- if (inlineRefMatch) {
3614
- inlineFks.push({
3615
- constraintName: `${tableName}_${colName}_fkey`,
3616
- sourceTable: tableName,
3617
- sourceColumn: colName,
3618
- targetTable: bareName(inlineRefMatch[1]),
3619
- targetColumn: bareName(inlineRefMatch[2]),
3620
- onDelete: inlineRefMatch[3] ?? null
3621
- });
3622
- rest = rest.replace(inlineRefMatch[0], "").trim();
3623
- }
3624
- const isNotNull = /\bNOT\s+NULL\b/i.test(rest);
3625
- const isPrimaryKey = /\bPRIMARY\s+KEY\b/i.test(rest);
3626
- const isUnique = /\bUNIQUE\b/i.test(rest);
3627
- const defaultMatch = rest.match(/\bDEFAULT\s+(.+?)(?:\s*,?\s*$)/i);
3628
- const defaultVal = defaultMatch ? defaultMatch[1].trim() : null;
3629
- let colType = rest.replace(/\bNOT\s+NULL\b/gi, "").replace(/\bPRIMARY\s+KEY\b/gi, "").replace(/\bUNIQUE\b/gi, "").replace(/\bDEFAULT\s+.*/gi, "").trim().replace(/,\s*$/, "").trim();
3630
- columns.set(colName, {
3631
- name: colName,
3632
- type: colType,
3633
- nullable: !isNotNull && !isPrimaryKey,
3634
- primary: isPrimaryKey,
3635
- unique: isUnique,
3636
- default: defaultVal
3637
- });
3638
- if (isPrimaryKey) primaryCol = colName;
3639
- }
3640
- if (primaryCol && columns.has(primaryCol)) {
3641
- columns.get(primaryCol).primary = true;
3642
- }
3643
- state.tables.set(tableName, { name: tableName, columns });
3644
- state.fks.push(...inlineFks);
3645
- }
3646
- }
3647
- function splitTopLevelCommas(body) {
3648
- const out = [];
3649
- let depth = 0;
3650
- let buf = "";
3651
- let inString = null;
3652
- for (const ch of body) {
3653
- if (inString) {
3654
- buf += ch;
3655
- if (ch === inString) inString = null;
3656
- continue;
3657
- }
3658
- if (ch === "'" || ch === '"') {
3659
- inString = ch;
3660
- buf += ch;
3661
- continue;
3662
- }
3663
- if (ch === "(") depth++;
3664
- else if (ch === ")") depth--;
3665
- if (ch === "," && depth === 0) {
3666
- out.push(buf);
3667
- buf = "";
3668
- continue;
3669
- }
3670
- buf += ch;
3671
- }
3672
- if (buf.trim()) out.push(buf);
3673
- return out;
3674
- }
3675
- function parseCreateEnum(sql, state) {
3676
- const re = new RegExp(
3677
- `CREATE\\s+TYPE\\s+(${QID})\\s+AS\\s+ENUM\\s*\\(([^)]+)\\)`,
3678
- "gi"
3679
- );
3680
- let m;
3681
- while ((m = re.exec(sql)) !== null) {
3682
- const enumName = bareName(m[1]);
3683
- const valuesStr = m[2];
3684
- const values = new Set(
3685
- valuesStr.split(",").map((v) => v.trim().replace(/^'(.*)'$/, "$1")).filter(Boolean)
3686
- );
3687
- state.enums.set(enumName, { name: enumName, values });
3688
- }
3689
- }
3690
- function parseAlterTable(sql, state) {
3691
- const addColRe = new RegExp(
3692
- `ALTER\\s+TABLE\\s+(${QID})\\s+ADD\\s+COLUMN\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${QID})\\s+(.+?);`,
3693
- "gi"
3694
- );
3695
- let m;
3696
- while ((m = addColRe.exec(sql)) !== null) {
3697
- const tableName = bareName(m[1]);
3698
- const colName = bareName(m[2]);
3699
- let rest = m[3];
3700
- const table = state.tables.get(tableName);
3701
- if (!table) continue;
3702
- const isNotNull = /\bNOT\s+NULL\b/i.test(rest);
3703
- const defaultMatch = rest.match(/\bDEFAULT\s+(.+?)$/i);
3704
- const defaultVal = defaultMatch ? defaultMatch[1].trim() : null;
3705
- let colType = rest.replace(/\bNOT\s+NULL\b/gi, "").replace(/\bDEFAULT\s+.*/gi, "").trim();
3706
- table.columns.set(colName, {
3707
- name: colName,
3708
- type: colType,
3709
- nullable: !isNotNull,
3710
- primary: false,
3711
- unique: false,
3712
- default: defaultVal
3713
- });
3714
- }
3715
- const dropColRe = new RegExp(
3716
- `ALTER\\s+TABLE\\s+(${QID})\\s+DROP\\s+COLUMN\\s+(?:IF\\s+EXISTS\\s+)?(${QID})`,
3717
- "gi"
3718
- );
3719
- while ((m = dropColRe.exec(sql)) !== null) {
3720
- const table = state.tables.get(bareName(m[1]));
3721
- if (table) table.columns.delete(bareName(m[2]));
3722
- }
3723
- const fkRe = new RegExp(
3724
- `ALTER\\s+TABLE\\s+(${QID})\\s+ADD\\s+CONSTRAINT\\s+(${ID})\\s+FOREIGN\\s+KEY\\s*\\(\\s*(${QID})\\s*\\)\\s+REFERENCES\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?`,
3725
- "gi"
3726
- );
3727
- while ((m = fkRe.exec(sql)) !== null) {
3728
- state.fks.push({
3729
- constraintName: bareName(m[2]),
3730
- sourceTable: bareName(m[1]),
3731
- sourceColumn: bareName(m[3]),
3732
- targetTable: bareName(m[4]),
3733
- targetColumn: bareName(m[5]),
3734
- onDelete: m[6] ?? null
3735
- });
3736
- }
3737
- }
3738
- function parseAlterEnum(sql, state) {
3739
- const re = new RegExp(
3740
- `ALTER\\s+TYPE\\s+(${QID})\\s+ADD\\s+VALUE\\s+'([^']+)'`,
3741
- "gi"
3742
- );
3743
- let m;
3744
- while ((m = re.exec(sql)) !== null) {
3745
- const en = state.enums.get(bareName(m[1]));
3746
- if (en) en.values.add(m[2]);
3747
- }
3748
- }
3749
- function parseDropTable(sql, state) {
3750
- const re = new RegExp(
3751
- `DROP\\s+TABLE\\s+(?:IF\\s+EXISTS\\s+)?(${QID})`,
3752
- "gi"
3753
- );
3754
- let m;
3755
- while ((m = re.exec(sql)) !== null) {
3756
- const dropped = bareName(m[1]);
3757
- state.tables.delete(dropped);
3758
- state.fks = state.fks.filter((fk) => fk.sourceTable !== dropped && fk.targetTable !== dropped);
3759
- }
3760
- }
3761
- function parseUniqueIndex(sql, state) {
3762
- const re = new RegExp(
3763
- `CREATE\\s+UNIQUE\\s+INDEX\\s+(?:(?:IF\\s+NOT\\s+EXISTS\\s+)?(?:${ID}\\s+)?)?ON\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)`,
3764
- "gi"
3765
- );
3766
- let m;
3767
- while ((m = re.exec(sql)) !== null) {
3768
- const tableName = bareName(m[1]);
3769
- const colName = bareName(m[2]);
3770
- const table = state.tables.get(tableName);
3771
- const col = table?.columns.get(colName);
3772
- if (col) col.unique = true;
3773
- if (!state.uniqueIndexes.has(tableName)) state.uniqueIndexes.set(tableName, /* @__PURE__ */ new Set());
3774
- state.uniqueIndexes.get(tableName).add(colName);
3775
- }
3776
- }
3777
3635
  function discoverMigrationFiles(migrationsDir) {
3778
3636
  if (!(0, import_node_fs14.existsSync)(migrationsDir)) return [];
3779
3637
  const out = [];
@@ -3788,25 +3646,589 @@ function discoverMigrationFiles(migrationsDir) {
3788
3646
  }
3789
3647
  return out;
3790
3648
  }
3791
- function parseMigrations(migrationsDir) {
3649
+ function parseMigrations(migrationsDir, dialect = postgresDialect) {
3792
3650
  const state = {
3793
3651
  tables: /* @__PURE__ */ new Map(),
3794
3652
  enums: /* @__PURE__ */ new Map(),
3795
3653
  fks: [],
3796
- uniqueIndexes: /* @__PURE__ */ new Map()
3654
+ uniqueIndexes: /* @__PURE__ */ new Map(),
3655
+ indexes: [],
3656
+ policies: [],
3657
+ extensions: [],
3658
+ triggers: [],
3659
+ functions: [],
3660
+ views: []
3797
3661
  };
3798
3662
  if (!migrationsDir) return state;
3799
3663
  for (const sqlPath of discoverMigrationFiles(migrationsDir)) {
3800
3664
  const sql = (0, import_node_fs14.readFileSync)(sqlPath, "utf-8");
3801
- parseCreateEnum(sql, state);
3802
- parseCreateTable(sql, state);
3803
- parseAlterTable(sql, state);
3804
- parseAlterEnum(sql, state);
3805
- parseDropTable(sql, state);
3806
- parseUniqueIndex(sql, state);
3665
+ let ast;
3666
+ try {
3667
+ ast = dialect.parse(sql);
3668
+ } catch {
3669
+ continue;
3670
+ }
3671
+ dialect.applyAll(ast, state, sqlPath);
3807
3672
  }
3808
3673
  return state;
3809
3674
  }
3675
+ function extractIndexesFromStmts(stmts, state, filepath) {
3676
+ for (const wrap of stmts) {
3677
+ const stmt = wrap.stmt ?? {};
3678
+ const ix = stmt.IndexStmt;
3679
+ if (!ix) continue;
3680
+ const name = ix.idxname ?? "";
3681
+ if (!name) continue;
3682
+ const table = ix.relation?.relname ?? "";
3683
+ const unique = !!ix.unique;
3684
+ const method = String(ix.accessMethod ?? "btree").toLowerCase();
3685
+ const params = ix.indexParams ?? [];
3686
+ const columns = [];
3687
+ let hasExpressions = false;
3688
+ for (const p of params) {
3689
+ const elem = p.IndexElem;
3690
+ if (!elem) continue;
3691
+ if (elem.name) columns.push(elem.name);
3692
+ else if (elem.expr) hasExpressions = true;
3693
+ }
3694
+ const hasPredicate = !!ix.whereClause;
3695
+ const existing = state.indexes.findIndex((i) => i.name === name);
3696
+ const next = { name, table, unique, method, columns, hasExpressions, hasPredicate, filepath };
3697
+ if (existing >= 0) state.indexes[existing] = next;
3698
+ else state.indexes.push(next);
3699
+ if (unique && columns.length === 1 && !hasPredicate && !hasExpressions) {
3700
+ const t = state.tables.get(table);
3701
+ const col = t?.columns.get(columns[0]);
3702
+ if (col) col.unique = true;
3703
+ if (!state.uniqueIndexes.has(table)) state.uniqueIndexes.set(table, /* @__PURE__ */ new Set());
3704
+ state.uniqueIndexes.get(table).add(columns[0]);
3705
+ }
3706
+ }
3707
+ }
3708
+ function applyDropIndexes(stmts, state) {
3709
+ for (const wrap of stmts) {
3710
+ const drop = wrap.stmt?.DropStmt;
3711
+ if (!drop || drop.removeType !== "OBJECT_INDEX") continue;
3712
+ const objects = drop.objects ?? [];
3713
+ const droppedNames = /* @__PURE__ */ new Set();
3714
+ for (const obj of objects) {
3715
+ const items = obj.List?.items ?? [];
3716
+ const last = items[items.length - 1]?.String?.sval;
3717
+ if (last) droppedNames.add(last);
3718
+ }
3719
+ if (droppedNames.size > 0) {
3720
+ state.indexes = state.indexes.filter((i) => !droppedNames.has(i.name));
3721
+ }
3722
+ }
3723
+ }
3724
+ function formatPgTypeName(typeName) {
3725
+ const names = (typeName?.names ?? []).map((n) => n.String?.sval ?? "").filter(Boolean);
3726
+ const base = (names[names.length - 1] ?? "").toLowerCase();
3727
+ const PG_INTERNAL_MAP = {
3728
+ int4: "INTEGER",
3729
+ int8: "BIGINT",
3730
+ int2: "SMALLINT",
3731
+ float8: "DOUBLE PRECISION",
3732
+ float4: "REAL",
3733
+ bool: "BOOLEAN",
3734
+ bpchar: "CHAR",
3735
+ timestamptz: "TIMESTAMPTZ",
3736
+ timestamp: "TIMESTAMP",
3737
+ numeric: "NUMERIC",
3738
+ text: "TEXT",
3739
+ varchar: "VARCHAR",
3740
+ jsonb: "JSONB",
3741
+ json: "JSON",
3742
+ uuid: "UUID",
3743
+ date: "DATE",
3744
+ bytea: "BYTEA"
3745
+ };
3746
+ return PG_INTERNAL_MAP[base] ?? base.toUpperCase();
3747
+ }
3748
+ function applyAstAlterations(stmts, state) {
3749
+ for (const wrap of stmts) {
3750
+ const stmt = wrap.stmt ?? {};
3751
+ const kind = Object.keys(stmt)[0];
3752
+ if (!kind) continue;
3753
+ if (kind === "AlterTableStmt") {
3754
+ const body = stmt.AlterTableStmt;
3755
+ const tableName = body.relation?.relname ?? "";
3756
+ const table = state.tables.get(tableName);
3757
+ if (!table) continue;
3758
+ const cmds = body.cmds ?? [];
3759
+ for (const c of cmds) {
3760
+ const cmd = c.AlterTableCmd;
3761
+ if (!cmd) continue;
3762
+ const subtype = cmd.subtype ?? "";
3763
+ const colName = cmd.name ?? "";
3764
+ const col = colName ? table.columns.get(colName) : void 0;
3765
+ switch (subtype) {
3766
+ case "AT_AlterColumnType": {
3767
+ if (!col) break;
3768
+ const typeName = cmd.def?.ColumnDef?.typeName ?? cmd.def?.typeName;
3769
+ if (typeName) col.type = formatPgTypeNameWithMods(typeName);
3770
+ break;
3771
+ }
3772
+ case "AT_SetNotNull":
3773
+ if (col) col.nullable = false;
3774
+ break;
3775
+ case "AT_DropNotNull":
3776
+ if (col) col.nullable = true;
3777
+ break;
3778
+ case "AT_AddColumn": {
3779
+ const cd = cmd.def?.ColumnDef;
3780
+ if (!cd) break;
3781
+ const newColName = cd.colname ?? "";
3782
+ if (!newColName) break;
3783
+ if (table.columns.has(newColName)) break;
3784
+ let nullable = true;
3785
+ let primary = false;
3786
+ let unique = false;
3787
+ let defaultVal = null;
3788
+ for (const c2 of cd.constraints ?? []) {
3789
+ const ct = c2.Constraint;
3790
+ if (!ct) continue;
3791
+ if (ct.contype === "CONSTR_NOTNULL") nullable = false;
3792
+ else if (ct.contype === "CONSTR_PRIMARY") {
3793
+ primary = true;
3794
+ nullable = false;
3795
+ } else if (ct.contype === "CONSTR_UNIQUE") unique = true;
3796
+ else if (ct.contype === "CONSTR_DEFAULT") defaultVal = "<expr>";
3797
+ }
3798
+ table.columns.set(newColName, {
3799
+ name: newColName,
3800
+ type: formatPgTypeNameWithMods(cd.typeName),
3801
+ nullable,
3802
+ primary,
3803
+ unique,
3804
+ default: defaultVal
3805
+ });
3806
+ break;
3807
+ }
3808
+ case "AT_DropColumn":
3809
+ if (colName) table.columns.delete(colName);
3810
+ break;
3811
+ case "AT_AddConstraint": {
3812
+ const ct = cmd.def?.Constraint;
3813
+ if (!ct) break;
3814
+ if (ct.contype !== "CONSTR_FOREIGN") break;
3815
+ const fkCols = (ct.fk_attrs ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
3816
+ const pkCols = (ct.pk_attrs ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
3817
+ const pkTable = ct.pktable?.relname ?? "";
3818
+ if (fkCols.length && pkCols.length && pkTable) {
3819
+ state.fks.push({
3820
+ constraintName: ct.conname || `${tableName}_${fkCols[0]}_fkey`,
3821
+ sourceTable: tableName,
3822
+ sourceColumn: fkCols[0],
3823
+ targetTable: pkTable,
3824
+ targetColumn: pkCols[0],
3825
+ onDelete: mapFkAction(ct.fk_del_action)
3826
+ });
3827
+ }
3828
+ break;
3829
+ }
3830
+ }
3831
+ }
3832
+ } else if (kind === "RenameStmt") {
3833
+ const body = stmt.RenameStmt;
3834
+ const renameType = body.renameType ?? "";
3835
+ const newName = body.newname ?? "";
3836
+ if (renameType === "OBJECT_COLUMN") {
3837
+ const tableName = body.relation?.relname ?? "";
3838
+ const oldName = body.subname ?? "";
3839
+ const table = state.tables.get(tableName);
3840
+ if (!table || !oldName || !newName) continue;
3841
+ const col = table.columns.get(oldName);
3842
+ if (col) {
3843
+ col.name = newName;
3844
+ table.columns.delete(oldName);
3845
+ table.columns.set(newName, col);
3846
+ }
3847
+ } else if (renameType === "OBJECT_TABLE") {
3848
+ const oldName = body.relation?.relname ?? "";
3849
+ if (!oldName || !newName) continue;
3850
+ const t = state.tables.get(oldName);
3851
+ if (!t) continue;
3852
+ state.tables.delete(oldName);
3853
+ t.name = newName;
3854
+ state.tables.set(newName, t);
3855
+ for (const fk of state.fks) {
3856
+ if (fk.sourceTable === oldName) fk.sourceTable = newName;
3857
+ if (fk.targetTable === oldName) fk.targetTable = newName;
3858
+ }
3859
+ }
3860
+ }
3861
+ }
3862
+ }
3863
+ function extractPoliciesFromStmts(stmts, state, filepath) {
3864
+ for (const wrap of stmts) {
3865
+ const body = wrap.stmt?.CreatePolicyStmt;
3866
+ if (!body) continue;
3867
+ const name = body.policy_name ?? "";
3868
+ if (!name) continue;
3869
+ const table = body.table?.relname ?? "";
3870
+ const cmdRaw = String(body.cmd_name ?? "all").toUpperCase();
3871
+ const command = ["SELECT", "INSERT", "UPDATE", "DELETE", "ALL"].includes(cmdRaw) ? cmdRaw : "ALL";
3872
+ const permissive = body.permissive === true;
3873
+ const roles = (body.roles ?? []).map((r) => {
3874
+ const rs = r.RoleSpec;
3875
+ if (!rs) return "";
3876
+ if (rs.roletype === "ROLESPEC_PUBLIC") return "public";
3877
+ if (rs.roletype === "ROLESPEC_CURRENT_USER") return "current_user";
3878
+ if (rs.roletype === "ROLESPEC_CSTRING" && rs.rolename) return rs.rolename;
3879
+ return "";
3880
+ }).filter(Boolean);
3881
+ const hasUsing = !!body.qual;
3882
+ const hasWithCheck = !!body.with_check;
3883
+ const existing = state.policies.findIndex((p) => p.table === table && p.name === name);
3884
+ const next = { name, table, command, permissive, roles, hasUsing, hasWithCheck, filepath };
3885
+ if (existing >= 0) state.policies[existing] = next;
3886
+ else state.policies.push(next);
3887
+ }
3888
+ }
3889
+ function applyDropPolicies(stmts, state) {
3890
+ for (const wrap of stmts) {
3891
+ const drop = wrap.stmt?.DropStmt;
3892
+ if (!drop || drop.removeType !== "OBJECT_POLICY") continue;
3893
+ const objects = drop.objects ?? [];
3894
+ for (const obj of objects) {
3895
+ const items = obj.List?.items ?? [];
3896
+ if (items.length < 2) continue;
3897
+ const table = items[0]?.String?.sval ?? "";
3898
+ const policyName = items[items.length - 1]?.String?.sval ?? "";
3899
+ if (!table || !policyName) continue;
3900
+ state.policies = state.policies.filter((p) => !(p.table === table && p.name === policyName));
3901
+ }
3902
+ }
3903
+ }
3904
+ function mapFkAction(action) {
3905
+ if (!action) return null;
3906
+ const m = {
3907
+ r: "RESTRICT",
3908
+ c: "CASCADE",
3909
+ s: "SET NULL",
3910
+ d: "SET DEFAULT",
3911
+ a: "NO ACTION",
3912
+ // pgsql-parser may also emit FKCONSTR_ACTION_* enum strings:
3913
+ FKCONSTR_ACTION_RESTRICT: "RESTRICT",
3914
+ FKCONSTR_ACTION_CASCADE: "CASCADE",
3915
+ FKCONSTR_ACTION_SETNULL: "SET NULL",
3916
+ FKCONSTR_ACTION_SETDEFAULT: "SET DEFAULT",
3917
+ FKCONSTR_ACTION_NOACTION: "NO ACTION"
3918
+ };
3919
+ return m[action] ?? null;
3920
+ }
3921
+ function formatPgTypeNameWithMods(typeName) {
3922
+ const base = formatPgTypeName(typeName);
3923
+ if (base === "String" || base === "unknown") return base;
3924
+ const typmods = [];
3925
+ for (const m of typeName?.typmods ?? []) {
3926
+ const v = m.A_Const?.ival?.ival;
3927
+ if (typeof v === "number") typmods.push(v);
3928
+ }
3929
+ return typmods.length ? `${base}(${typmods.join(",")})` : base;
3930
+ }
3931
+ function extractTablesFromStmts(stmts, state) {
3932
+ for (const wrap of stmts) {
3933
+ const body = wrap.stmt?.CreateStmt;
3934
+ if (!body) continue;
3935
+ const tableName = body.relation?.relname ?? "";
3936
+ if (!tableName) continue;
3937
+ const columns = /* @__PURE__ */ new Map();
3938
+ const fks = [];
3939
+ let primaryCol = null;
3940
+ for (const elt of body.tableElts ?? []) {
3941
+ if (elt.ColumnDef) {
3942
+ const cd = elt.ColumnDef;
3943
+ const colName = cd.colname ?? "";
3944
+ if (!colName) continue;
3945
+ const colType = formatPgTypeNameWithMods(cd.typeName);
3946
+ let nullable = true;
3947
+ let primary = false;
3948
+ let unique = false;
3949
+ let defaultVal = null;
3950
+ for (const c of cd.constraints ?? []) {
3951
+ const ct = c.Constraint;
3952
+ if (!ct) continue;
3953
+ switch (ct.contype) {
3954
+ case "CONSTR_NOTNULL":
3955
+ nullable = false;
3956
+ break;
3957
+ case "CONSTR_PRIMARY":
3958
+ primary = true;
3959
+ nullable = false;
3960
+ primaryCol = colName;
3961
+ break;
3962
+ case "CONSTR_UNIQUE":
3963
+ unique = true;
3964
+ break;
3965
+ case "CONSTR_DEFAULT":
3966
+ defaultVal = "<expr>";
3967
+ break;
3968
+ case "CONSTR_FOREIGN": {
3969
+ const pkTable = ct.pktable?.relname ?? "";
3970
+ const pkCols = (ct.pk_attrs ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
3971
+ if (pkTable && pkCols.length) {
3972
+ fks.push({
3973
+ constraintName: ct.conname || `${tableName}_${colName}_fkey`,
3974
+ sourceTable: tableName,
3975
+ sourceColumn: colName,
3976
+ targetTable: pkTable,
3977
+ targetColumn: pkCols[0],
3978
+ onDelete: mapFkAction(ct.fk_del_action)
3979
+ });
3980
+ }
3981
+ break;
3982
+ }
3983
+ }
3984
+ }
3985
+ columns.set(colName, {
3986
+ name: colName,
3987
+ type: colType,
3988
+ nullable,
3989
+ primary,
3990
+ unique,
3991
+ default: defaultVal
3992
+ });
3993
+ } else if (elt.Constraint) {
3994
+ const ct = elt.Constraint;
3995
+ switch (ct.contype) {
3996
+ case "CONSTR_PRIMARY": {
3997
+ const keys = (ct.keys ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
3998
+ if (keys.length) primaryCol = keys[0];
3999
+ break;
4000
+ }
4001
+ case "CONSTR_FOREIGN": {
4002
+ const fkCols = (ct.fk_attrs ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
4003
+ const pkCols = (ct.pk_attrs ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
4004
+ const pkTable = ct.pktable?.relname ?? "";
4005
+ if (fkCols.length && pkCols.length && pkTable) {
4006
+ fks.push({
4007
+ constraintName: ct.conname || `${tableName}_${fkCols[0]}_fkey`,
4008
+ sourceTable: tableName,
4009
+ sourceColumn: fkCols[0],
4010
+ targetTable: pkTable,
4011
+ targetColumn: pkCols[0],
4012
+ onDelete: mapFkAction(ct.fk_del_action)
4013
+ });
4014
+ }
4015
+ break;
4016
+ }
4017
+ }
4018
+ }
4019
+ }
4020
+ if (primaryCol && columns.has(primaryCol)) {
4021
+ columns.get(primaryCol).primary = true;
4022
+ columns.get(primaryCol).nullable = false;
4023
+ }
4024
+ state.tables.set(tableName, { name: tableName, columns });
4025
+ state.fks.push(...fks);
4026
+ }
4027
+ }
4028
+ function extractEnumsFromStmts(stmts, state) {
4029
+ for (const wrap of stmts) {
4030
+ const body = wrap.stmt?.CreateEnumStmt;
4031
+ if (!body) continue;
4032
+ const names = (body.typeName ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
4033
+ const enumName = names[names.length - 1] ?? "";
4034
+ if (!enumName) continue;
4035
+ const vals = new Set(
4036
+ (body.vals ?? []).map((s) => s.String?.sval ?? "").filter(Boolean)
4037
+ );
4038
+ state.enums.set(enumName, { name: enumName, values: vals });
4039
+ }
4040
+ }
4041
+ function applyAstAlterEnums(stmts, state) {
4042
+ for (const wrap of stmts) {
4043
+ const body = wrap.stmt?.AlterEnumStmt;
4044
+ if (!body) continue;
4045
+ const names = (body.typeName ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
4046
+ const enumName = names[names.length - 1] ?? "";
4047
+ const en = state.enums.get(enumName);
4048
+ if (!en) continue;
4049
+ if (body.newVal) en.values.add(String(body.newVal));
4050
+ }
4051
+ }
4052
+ function extractExtensionsFromStmts(stmts, state, filepath) {
4053
+ for (const wrap of stmts) {
4054
+ const body = wrap.stmt?.CreateExtensionStmt;
4055
+ if (!body) continue;
4056
+ const name = body.extname ?? "";
4057
+ if (!name) continue;
4058
+ let schema = null;
4059
+ let version = null;
4060
+ for (const opt of body.options ?? []) {
4061
+ const de = opt.DefElem;
4062
+ if (!de) continue;
4063
+ if (de.defname === "schema" && de.arg?.String?.sval) schema = de.arg.String.sval;
4064
+ else if (de.defname === "new_version" && de.arg?.String?.sval) version = de.arg.String.sval;
4065
+ }
4066
+ const next = { name, schema, version, filepath };
4067
+ const existing = state.extensions.findIndex((e) => e.name === name);
4068
+ if (existing >= 0) state.extensions[existing] = next;
4069
+ else state.extensions.push(next);
4070
+ }
4071
+ }
4072
+ function extractTriggersFromStmts(stmts, state, filepath) {
4073
+ for (const wrap of stmts) {
4074
+ const body = wrap.stmt?.CreateTrigStmt;
4075
+ if (!body) continue;
4076
+ const name = body.trigname ?? "";
4077
+ if (!name) continue;
4078
+ const table = body.relation?.relname ?? "";
4079
+ const timingVal = body.timing ?? 0;
4080
+ const eventsVal = body.events ?? 0;
4081
+ const timing = timingVal & 2 ? "BEFORE" : timingVal & 64 ? "INSTEAD OF" : "AFTER";
4082
+ const events = [];
4083
+ if (eventsVal & 4) events.push("INSERT");
4084
+ if (eventsVal & 8) events.push("DELETE");
4085
+ if (eventsVal & 16) events.push("UPDATE");
4086
+ if (eventsVal & 32) events.push("TRUNCATE");
4087
+ const funcname = body.funcname ?? [];
4088
+ const funcCall = funcname[funcname.length - 1]?.String?.sval ?? "";
4089
+ const forEach = body.row ? "ROW" : "STATEMENT";
4090
+ const hasWhen = !!body.whenClause;
4091
+ const next = { name, table, timing, events, function: funcCall, hasWhen, forEach, filepath };
4092
+ const existing = state.triggers.findIndex((t) => t.table === table && t.name === name);
4093
+ if (existing >= 0) state.triggers[existing] = next;
4094
+ else state.triggers.push(next);
4095
+ }
4096
+ }
4097
+ function functionIdFor(name, schema) {
4098
+ return schema ? `${schema}.${name}` : name;
4099
+ }
4100
+ function extractFunctionsFromStmts(stmts, state, filepath) {
4101
+ for (const wrap of stmts) {
4102
+ const body = wrap.stmt?.CreateFunctionStmt;
4103
+ if (!body) continue;
4104
+ const fn = body.funcname ?? [];
4105
+ if (fn.length === 0) continue;
4106
+ const name = fn[fn.length - 1]?.String?.sval ?? "";
4107
+ if (!name) continue;
4108
+ const schema = fn.length > 1 ? fn[fn.length - 2]?.String?.sval ?? null : null;
4109
+ let language = "sql";
4110
+ for (const opt of body.options ?? []) {
4111
+ const de = opt.DefElem;
4112
+ if (de?.defname === "language" && de.arg?.String?.sval) language = de.arg.String.sval;
4113
+ }
4114
+ const returnType = body.returnType ? formatPgTypeName(body.returnType) : "";
4115
+ const isProcedure = !!body.is_procedure;
4116
+ const next = { name, schema, language, returnType, isProcedure, filepath };
4117
+ const id = functionIdFor(name, schema);
4118
+ const existing = state.functions.findIndex((f) => functionIdFor(f.name, f.schema) === id);
4119
+ if (existing >= 0) state.functions[existing] = next;
4120
+ else state.functions.push(next);
4121
+ }
4122
+ }
4123
+ function extractViewsFromStmts(stmts, state, filepath) {
4124
+ for (const wrap of stmts) {
4125
+ const stmt = wrap.stmt ?? {};
4126
+ const view = stmt.ViewStmt;
4127
+ if (view) {
4128
+ const name = view.view?.relname ?? "";
4129
+ if (!name) continue;
4130
+ const schema = view.view?.schemaname ?? null;
4131
+ const next = {
4132
+ name,
4133
+ schema,
4134
+ isMaterialized: false,
4135
+ withCheckOption: String(view.withCheckOption ?? "NO_CHECK_OPTION"),
4136
+ filepath
4137
+ };
4138
+ const id = functionIdFor(name, schema);
4139
+ const existing = state.views.findIndex((v) => functionIdFor(v.name, v.schema) === id);
4140
+ if (existing >= 0) state.views[existing] = next;
4141
+ else state.views.push(next);
4142
+ }
4143
+ const ctas = stmt.CreateTableAsStmt;
4144
+ if (ctas && ctas.objtype === "OBJECT_MATVIEW") {
4145
+ const name = ctas.into?.rel?.relname ?? "";
4146
+ if (!name) continue;
4147
+ const schema = ctas.into?.rel?.schemaname ?? null;
4148
+ const next = {
4149
+ name,
4150
+ schema,
4151
+ isMaterialized: true,
4152
+ withCheckOption: "N/A",
4153
+ filepath
4154
+ };
4155
+ const id = functionIdFor(name, schema);
4156
+ const existing = state.views.findIndex((v) => functionIdFor(v.name, v.schema) === id);
4157
+ if (existing >= 0) state.views[existing] = next;
4158
+ else state.views.push(next);
4159
+ }
4160
+ }
4161
+ }
4162
+ function applyDropsForSchemaObjects(stmts, state) {
4163
+ for (const wrap of stmts) {
4164
+ const drop = wrap.stmt?.DropStmt;
4165
+ if (!drop) continue;
4166
+ const removeType = drop.removeType ?? "";
4167
+ const objects = drop.objects ?? [];
4168
+ if (removeType === "OBJECT_TABLE") {
4169
+ const droppedTables = /* @__PURE__ */ new Set();
4170
+ for (const obj of objects) {
4171
+ const items = obj.List?.items ?? [];
4172
+ const last = items[items.length - 1]?.String?.sval;
4173
+ if (last) droppedTables.add(last);
4174
+ }
4175
+ if (droppedTables.size > 0) {
4176
+ for (const t of droppedTables) state.tables.delete(t);
4177
+ state.fks = state.fks.filter((fk) => !droppedTables.has(fk.sourceTable) && !droppedTables.has(fk.targetTable));
4178
+ }
4179
+ } else if (removeType === "OBJECT_TYPE") {
4180
+ const droppedEnums = /* @__PURE__ */ new Set();
4181
+ for (const obj of objects) {
4182
+ const tn = obj.TypeName;
4183
+ if (!tn) continue;
4184
+ const names = (tn.names ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
4185
+ const last = names[names.length - 1];
4186
+ if (last) droppedEnums.add(last);
4187
+ }
4188
+ for (const e of droppedEnums) state.enums.delete(e);
4189
+ } else if (removeType === "OBJECT_EXTENSION") {
4190
+ const names = /* @__PURE__ */ new Set();
4191
+ for (const obj of objects) {
4192
+ const sval = obj.String?.sval;
4193
+ if (sval) names.add(sval);
4194
+ }
4195
+ if (names.size > 0) state.extensions = state.extensions.filter((e) => !names.has(e.name));
4196
+ } else if (removeType === "OBJECT_TRIGGER") {
4197
+ for (const obj of objects) {
4198
+ const items = obj.List?.items ?? [];
4199
+ if (items.length < 2) continue;
4200
+ const table = items[0]?.String?.sval ?? "";
4201
+ const trigName = items[items.length - 1]?.String?.sval ?? "";
4202
+ if (!table || !trigName) continue;
4203
+ state.triggers = state.triggers.filter((t) => !(t.table === table && t.name === trigName));
4204
+ }
4205
+ } else if (removeType === "OBJECT_FUNCTION" || removeType === "OBJECT_PROCEDURE") {
4206
+ for (const obj of objects) {
4207
+ const items = obj.ObjectWithArgs?.objname?.items ?? obj.ObjectWithArgs?.objname ?? obj.List?.items ?? [];
4208
+ if (!items.length) continue;
4209
+ const segs = items.map((s) => s.String?.sval ?? "").filter(Boolean);
4210
+ if (!segs.length) continue;
4211
+ const name = segs[segs.length - 1];
4212
+ const schema = segs.length > 1 ? segs[segs.length - 2] : null;
4213
+ const id = functionIdFor(name, schema);
4214
+ state.functions = state.functions.filter((f) => functionIdFor(f.name, f.schema) !== id);
4215
+ }
4216
+ } else if (removeType === "OBJECT_VIEW" || removeType === "OBJECT_MATVIEW") {
4217
+ for (const obj of objects) {
4218
+ const items = obj.List?.items ?? [];
4219
+ if (!items.length) continue;
4220
+ const name = items[items.length - 1]?.String?.sval ?? "";
4221
+ const schema = items.length > 1 ? items[items.length - 2]?.String?.sval ?? null : null;
4222
+ if (!name) continue;
4223
+ const id = functionIdFor(name, schema);
4224
+ state.views = state.views.filter((v) => functionIdFor(v.name, v.schema) !== id);
4225
+ }
4226
+ }
4227
+ }
4228
+ }
4229
+ function indexIsPrismaUncoverable(idx) {
4230
+ return idx.hasPredicate || idx.hasExpressions || idx.method !== "btree";
4231
+ }
3810
4232
  function loadPrismaState(schemaPath) {
3811
4233
  if (!schemaPath || !(0, import_node_fs14.existsSync)(schemaPath)) return null;
3812
4234
  const content = (0, import_node_fs14.readFileSync)(schemaPath, "utf-8");
@@ -3973,6 +4395,96 @@ function verify(sqlState, prisma) {
3973
4395
  }
3974
4396
  return { contradictions, flaggedEdges };
3975
4397
  }
4398
+ function deriveMigrationName(sqlPath) {
4399
+ const segments = sqlPath.split(/[\\/]/);
4400
+ const last = segments[segments.length - 1];
4401
+ if (last === "migration.sql" && segments.length >= 2) {
4402
+ return segments[segments.length - 2];
4403
+ }
4404
+ return last.replace(/\.sql$/, "");
4405
+ }
4406
+ function extractMigrationInfoFromStmts(stmts, name, filepath) {
4407
+ let isDestructive = false;
4408
+ let hasOrphanCheck = false;
4409
+ let hasSidecarBackup = false;
4410
+ let hasPreFlightNotice = false;
4411
+ let containsBackfill = false;
4412
+ let containsDropColumn = false;
4413
+ let containsDropTable = false;
4414
+ for (const wrap of stmts) {
4415
+ const stmt = wrap.stmt ?? {};
4416
+ const kind = Object.keys(stmt)[0];
4417
+ if (!kind) continue;
4418
+ const body = stmt[kind] ?? {};
4419
+ switch (kind) {
4420
+ case "AlterTableStmt": {
4421
+ const cmds = body.cmds ?? [];
4422
+ for (const c of cmds) {
4423
+ const subtype = c.AlterTableCmd?.subtype;
4424
+ if (subtype === "AT_DropColumn") {
4425
+ containsDropColumn = true;
4426
+ isDestructive = true;
4427
+ } else if (subtype === "AT_AlterColumnType" || subtype === "AT_DropNotNull" || subtype === "AT_DropConstraint") {
4428
+ isDestructive = true;
4429
+ }
4430
+ }
4431
+ break;
4432
+ }
4433
+ case "DropStmt": {
4434
+ const removeType = body.removeType ?? "";
4435
+ if (removeType === "OBJECT_TABLE") {
4436
+ containsDropTable = true;
4437
+ isDestructive = true;
4438
+ } else if (removeType === "OBJECT_TYPE" || removeType === "OBJECT_COLUMN" || removeType === "OBJECT_INDEX" || removeType === "OBJECT_POLICY") {
4439
+ isDestructive = true;
4440
+ }
4441
+ break;
4442
+ }
4443
+ case "CreateStmt": {
4444
+ const relname = body.relation?.relname ?? "";
4445
+ if (relname.startsWith("_backup_")) hasSidecarBackup = true;
4446
+ break;
4447
+ }
4448
+ case "CreateTableAsStmt": {
4449
+ const relname = body.into?.rel?.relname ?? "";
4450
+ if (relname.startsWith("_backup_")) hasSidecarBackup = true;
4451
+ break;
4452
+ }
4453
+ case "UpdateStmt":
4454
+ case "InsertStmt":
4455
+ case "DeleteStmt": {
4456
+ containsBackfill = true;
4457
+ break;
4458
+ }
4459
+ case "DoStmt": {
4460
+ const args = body.args ?? [];
4461
+ for (const arg of args) {
4462
+ const def = arg.DefElem;
4463
+ if (!def || def.defname !== "as") continue;
4464
+ const code = def.arg?.String?.sval ?? "";
4465
+ if (/\bRAISE\s+EXCEPTION\b/i.test(code)) hasOrphanCheck = true;
4466
+ if (/\bRAISE\s+NOTICE\b/i.test(code)) hasPreFlightNotice = true;
4467
+ }
4468
+ break;
4469
+ }
4470
+ }
4471
+ }
4472
+ const tsMatch = name.match(/^(\d{8,14})/);
4473
+ const timestamp = tsMatch ? tsMatch[1] : null;
4474
+ return {
4475
+ name,
4476
+ filepath,
4477
+ timestamp,
4478
+ isDestructive,
4479
+ hasOrphanCheck,
4480
+ hasSidecarBackup,
4481
+ hasPreFlightNotice,
4482
+ containsBackfill,
4483
+ containsDropColumn,
4484
+ containsDropTable,
4485
+ statementCount: stmts.length
4486
+ };
4487
+ }
3976
4488
  function migrationsDirFor(rootDir) {
3977
4489
  const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
3978
4490
  if (!paths) return null;
@@ -4027,6 +4539,132 @@ function generate3(rootDir) {
4027
4539
  values: [...sqlEnum.values]
4028
4540
  });
4029
4541
  }
4542
+ let indexNodeCount = 0;
4543
+ for (const idx of sqlState.indexes) {
4544
+ if (!indexIsPrismaUncoverable(idx)) continue;
4545
+ nodes.push({
4546
+ id: `index:${idx.name}`,
4547
+ type: "index",
4548
+ name: idx.name,
4549
+ source: "sql",
4550
+ table: idx.table,
4551
+ unique: idx.unique,
4552
+ method: idx.method,
4553
+ columns: idx.columns,
4554
+ has_expressions: idx.hasExpressions,
4555
+ has_predicate: idx.hasPredicate,
4556
+ filepath: idx.filepath
4557
+ });
4558
+ indexNodeCount++;
4559
+ }
4560
+ let extensionNodeCount = 0;
4561
+ for (const ext of sqlState.extensions) {
4562
+ nodes.push({
4563
+ id: `extension:${ext.name}`,
4564
+ type: "extension",
4565
+ name: ext.name,
4566
+ source: "sql",
4567
+ schema: ext.schema,
4568
+ version: ext.version,
4569
+ filepath: ext.filepath
4570
+ });
4571
+ extensionNodeCount++;
4572
+ }
4573
+ let triggerNodeCount = 0;
4574
+ for (const trg of sqlState.triggers) {
4575
+ nodes.push({
4576
+ id: `trigger:${trg.table}:${trg.name}`,
4577
+ type: "trigger",
4578
+ name: trg.name,
4579
+ source: "sql",
4580
+ table: trg.table,
4581
+ timing: trg.timing,
4582
+ events: trg.events,
4583
+ function: trg.function,
4584
+ has_when: trg.hasWhen,
4585
+ for_each: trg.forEach,
4586
+ filepath: trg.filepath
4587
+ });
4588
+ triggerNodeCount++;
4589
+ }
4590
+ let functionNodeCount = 0;
4591
+ for (const fn of sqlState.functions) {
4592
+ const qualified = fn.schema ? `${fn.schema}.${fn.name}` : fn.name;
4593
+ nodes.push({
4594
+ id: `function:${qualified}`,
4595
+ type: "function",
4596
+ name: fn.name,
4597
+ source: "sql",
4598
+ schema: fn.schema,
4599
+ language: fn.language,
4600
+ return_type: fn.returnType,
4601
+ is_procedure: fn.isProcedure,
4602
+ filepath: fn.filepath
4603
+ });
4604
+ functionNodeCount++;
4605
+ }
4606
+ let viewNodeCount = 0;
4607
+ for (const vw of sqlState.views) {
4608
+ const qualified = vw.schema ? `${vw.schema}.${vw.name}` : vw.name;
4609
+ nodes.push({
4610
+ id: `${vw.isMaterialized ? "matview" : "view"}:${qualified}`,
4611
+ type: vw.isMaterialized ? "materialized_view" : "view",
4612
+ name: vw.name,
4613
+ source: "sql",
4614
+ schema: vw.schema,
4615
+ is_materialized: vw.isMaterialized,
4616
+ with_check_option: vw.withCheckOption,
4617
+ filepath: vw.filepath
4618
+ });
4619
+ viewNodeCount++;
4620
+ }
4621
+ let policyNodeCount = 0;
4622
+ for (const pol of sqlState.policies) {
4623
+ nodes.push({
4624
+ id: `policy:${pol.table}:${pol.name}`,
4625
+ type: "policy",
4626
+ name: pol.name,
4627
+ source: "sql",
4628
+ table: pol.table,
4629
+ command: pol.command,
4630
+ permissive: pol.permissive,
4631
+ roles: pol.roles,
4632
+ has_using: pol.hasUsing,
4633
+ has_with_check: pol.hasWithCheck,
4634
+ filepath: pol.filepath
4635
+ });
4636
+ policyNodeCount++;
4637
+ }
4638
+ const migrationFiles = migrationsDir ? discoverMigrationFiles(migrationsDir) : [];
4639
+ let migrationNodeCount = 0;
4640
+ for (const sqlPath of migrationFiles) {
4641
+ const sql = (0, import_node_fs14.readFileSync)(sqlPath, "utf-8");
4642
+ const name = deriveMigrationName(sqlPath);
4643
+ let ast;
4644
+ try {
4645
+ ast = postgresDialect.parse(sql);
4646
+ } catch {
4647
+ ast = { stmts: [] };
4648
+ }
4649
+ const info = postgresDialect.extractMigrationInfo(ast, name, sqlPath);
4650
+ nodes.push({
4651
+ id: `migration:${name}`,
4652
+ type: "migration",
4653
+ name,
4654
+ source: "sql",
4655
+ filepath: info.filepath,
4656
+ timestamp: info.timestamp,
4657
+ is_destructive: info.isDestructive,
4658
+ has_orphan_check: info.hasOrphanCheck,
4659
+ has_sidecar_backup: info.hasSidecarBackup,
4660
+ has_pre_flight_notice: info.hasPreFlightNotice,
4661
+ contains_backfill: info.containsBackfill,
4662
+ contains_drop_column: info.containsDropColumn,
4663
+ contains_drop_table: info.containsDropTable,
4664
+ statement_count: info.statementCount
4665
+ });
4666
+ migrationNodeCount++;
4667
+ }
4030
4668
  const sqlOnlyTables = new Set(nodes.filter((n) => n.type === "table").map((n) => n.id));
4031
4669
  const edges = sqlState.fks.filter((fk) => sqlOnlyTables.has(fk.sourceTable)).map((fk) => ({
4032
4670
  source: fk.sourceTable,
@@ -4045,6 +4683,13 @@ function generate3(rootDir) {
4045
4683
  sql_tables: sqlState.tables.size,
4046
4684
  sql_enums: sqlState.enums.size,
4047
4685
  sql_fks: sqlState.fks.length,
4686
+ sql_index_nodes: indexNodeCount,
4687
+ sql_policy_nodes: policyNodeCount,
4688
+ sql_extension_nodes: extensionNodeCount,
4689
+ sql_trigger_nodes: triggerNodeCount,
4690
+ sql_function_nodes: functionNodeCount,
4691
+ sql_view_nodes: viewNodeCount,
4692
+ sql_migration_nodes: migrationNodeCount,
4048
4693
  additive_nodes: nodes.length,
4049
4694
  contradictions_found: contradictions.length,
4050
4695
  flagged_edges_found: flaggedEdges.length
@@ -4057,12 +4702,13 @@ function generate3(rootDir) {
4057
4702
  flagged_edges: flaggedEdges
4058
4703
  };
4059
4704
  }
4060
- var import_node_fs14, import_node_path15, PG_TO_PRISMA, ID, QID, sqlMigrationsParser;
4705
+ var import_node_fs14, import_node_path15, import_pgsql_parser, PG_TO_PRISMA, postgresDialect, sqlMigrationsParser;
4061
4706
  var init_sql_migrations = __esm({
4062
4707
  "src/server/graph/parsers/db/sql-migrations.ts"() {
4063
4708
  "use strict";
4064
4709
  import_node_fs14 = require("node:fs");
4065
4710
  import_node_path15 = require("node:path");
4711
+ import_pgsql_parser = require("pgsql-parser");
4066
4712
  init_config();
4067
4713
  init_resolve_paths();
4068
4714
  PG_TO_PRISMA = {
@@ -4091,8 +4737,31 @@ var init_sql_migrations = __esm({
4091
4737
  "UUID": "String",
4092
4738
  "TEXT[]": "String[]"
4093
4739
  };
4094
- ID = `(?:"[\\w$]+"|[\\w$]+)`;
4095
- QID = `(?:${ID}\\.)?${ID}`;
4740
+ postgresDialect = {
4741
+ parse(sql) {
4742
+ return (0, import_pgsql_parser.parseSync)(sql);
4743
+ },
4744
+ applyAll(ast, state, filepath) {
4745
+ const stmts = ast.stmts ?? [];
4746
+ extractTablesFromStmts(stmts, state);
4747
+ extractEnumsFromStmts(stmts, state);
4748
+ extractIndexesFromStmts(stmts, state, filepath);
4749
+ extractPoliciesFromStmts(stmts, state, filepath);
4750
+ extractExtensionsFromStmts(stmts, state, filepath);
4751
+ extractTriggersFromStmts(stmts, state, filepath);
4752
+ extractFunctionsFromStmts(stmts, state, filepath);
4753
+ extractViewsFromStmts(stmts, state, filepath);
4754
+ applyDropIndexes(stmts, state);
4755
+ applyDropPolicies(stmts, state);
4756
+ applyDropsForSchemaObjects(stmts, state);
4757
+ applyAstAlterEnums(stmts, state);
4758
+ applyAstAlterations(stmts, state);
4759
+ },
4760
+ extractMigrationInfo(ast, name, filepath) {
4761
+ const stmts = ast.stmts ?? [];
4762
+ return extractMigrationInfoFromStmts(stmts, name, filepath);
4763
+ }
4764
+ };
4096
4765
  sqlMigrationsParser = {
4097
4766
  id: "sql-migrations",
4098
4767
  layer: "db",
@@ -6270,14 +6939,14 @@ function serveIndex(res, clientDir) {
6270
6939
  serveStatic(res, indexPath);
6271
6940
  }
6272
6941
  function tryListen(server, port) {
6273
- return new Promise((resolve5, reject) => {
6942
+ return new Promise((resolve6, reject) => {
6274
6943
  const onError = (err2) => {
6275
6944
  server.off("listening", onListening);
6276
6945
  reject(err2);
6277
6946
  };
6278
6947
  const onListening = () => {
6279
6948
  server.off("error", onError);
6280
- resolve5(port);
6949
+ resolve6(port);
6281
6950
  };
6282
6951
  server.once("error", onError);
6283
6952
  server.once("listening", onListening);
@@ -6717,6 +7386,73 @@ var init_chart_serve = __esm({
6717
7386
  }
6718
7387
  });
6719
7388
 
7389
+ // src/server/orbit/registry.ts
7390
+ function emptyRegistry() {
7391
+ return { version: 1, worktrees: {} };
7392
+ }
7393
+ function readRegistry() {
7394
+ if (!(0, import_node_fs22.existsSync)(REGISTRY_PATH)) return emptyRegistry();
7395
+ try {
7396
+ const parsed = JSON.parse((0, import_node_fs22.readFileSync)(REGISTRY_PATH, "utf-8"));
7397
+ if (parsed?.version === 1 && parsed.worktrees && typeof parsed.worktrees === "object") {
7398
+ return parsed;
7399
+ }
7400
+ } catch {
7401
+ }
7402
+ return emptyRegistry();
7403
+ }
7404
+ function listWorktrees() {
7405
+ return Object.values(readRegistry().worktrees);
7406
+ }
7407
+ var import_node_fs22, import_node_os2, import_node_path25, REGISTRY_DIR, REGISTRY_PATH, LOCK_PATH;
7408
+ var init_registry = __esm({
7409
+ "src/server/orbit/registry.ts"() {
7410
+ "use strict";
7411
+ import_node_fs22 = require("node:fs");
7412
+ import_node_os2 = require("node:os");
7413
+ import_node_path25 = require("node:path");
7414
+ init_launch_kit_paths();
7415
+ REGISTRY_DIR = (0, import_node_path25.join)((0, import_node_os2.homedir)(), LAUNCHSECURE_DIR, "orbit");
7416
+ REGISTRY_PATH = (0, import_node_path25.join)(REGISTRY_DIR, "state.json");
7417
+ LOCK_PATH = (0, import_node_path25.join)(REGISTRY_DIR, "state.json.lock");
7418
+ }
7419
+ });
7420
+
7421
+ // src/server/lib/worktree.ts
7422
+ function resolveWorktreeRoot(slug, monorepoRoot) {
7423
+ const local = listWorktrees().filter((w) => w.projectRoot === monorepoRoot);
7424
+ const match = local.find((w) => w.slug === slug);
7425
+ if (match) return match.path;
7426
+ if (local.length === 0) {
7427
+ throw new Error(
7428
+ `worktree="${slug}" requested but no worktrees are registered for this project. Run \`launch-orbit create <branch>\` first, or use \`project_root\` to point at an arbitrary path.`
7429
+ );
7430
+ }
7431
+ const available = local.map((w) => `"${w.slug}" (${w.branch})`).join(", ");
7432
+ throw new Error(`Unknown worktree "${slug}". Available: ${available}.`);
7433
+ }
7434
+ function resolveWorktreeOrProjectRoot(args, monorepoRoot) {
7435
+ const projectRoot = typeof args.project_root === "string" ? args.project_root.trim() : "";
7436
+ if (projectRoot) {
7437
+ return (0, import_node_path26.isAbsolute)(projectRoot) ? projectRoot : (0, import_node_path26.resolve)(monorepoRoot, projectRoot);
7438
+ }
7439
+ const worktree = typeof args.worktree === "string" ? args.worktree.trim() : "";
7440
+ if (worktree) {
7441
+ return resolveWorktreeRoot(worktree, monorepoRoot);
7442
+ }
7443
+ return null;
7444
+ }
7445
+ var import_node_path26, WORKTREE_PARAM_DESCRIPTION, PROJECT_ROOT_PARAM_DESCRIPTION;
7446
+ var init_worktree = __esm({
7447
+ "src/server/lib/worktree.ts"() {
7448
+ "use strict";
7449
+ import_node_path26 = require("node:path");
7450
+ init_registry();
7451
+ WORKTREE_PARAM_DESCRIPTION = "Optional orbit worktree slug (from `launch-orbit create`). Resolves to the worktree's path via the orbit registry (~/.launchsecure/orbit/state.json). Lets you query a worktree's state from a Claude Code session pinned to the main repo. Superseded by `project_root`.";
7452
+ PROJECT_ROOT_PARAM_DESCRIPTION = "Optional explicit project root. Accepts an absolute path or a path relative to the monorepo root. Escape hatch when `worktree` doesn't fit. Takes precedence over all other root args.";
7453
+ }
7454
+ });
7455
+
6720
7456
  // src/server/graph/core/projects.ts
6721
7457
  function listProjects(monorepoRoot) {
6722
7458
  const cfg = loadConfig(monorepoRoot);
@@ -6724,7 +7460,7 @@ function listProjects(monorepoRoot) {
6724
7460
  return entries.map((p) => ({
6725
7461
  name: p.name,
6726
7462
  root: p.root,
6727
- absoluteRoot: (0, import_node_path25.resolve)(monorepoRoot, p.root)
7463
+ absoluteRoot: (0, import_node_path27.resolve)(monorepoRoot, p.root)
6728
7464
  }));
6729
7465
  }
6730
7466
  function resolveProject(name, projects) {
@@ -6746,12 +7482,21 @@ function resolveProjectRoot(project, monorepoRoot) {
6746
7482
  }
6747
7483
  return resolveProject(raw, projects).absoluteRoot;
6748
7484
  }
6749
- var import_node_path25, PROJECT_PARAM_DESCRIPTION;
7485
+ function resolveRequestRoot2(args, monorepoRoot) {
7486
+ const fromArgs = resolveWorktreeOrProjectRoot(args, monorepoRoot);
7487
+ if (fromArgs) return fromArgs;
7488
+ const project = typeof args.project === "string" ? args.project : void 0;
7489
+ return resolveProjectRoot(project, monorepoRoot);
7490
+ }
7491
+ var import_node_path27, WORKTREE_PARAM_DESCRIPTION2, PROJECT_ROOT_PARAM_DESCRIPTION2, PROJECT_PARAM_DESCRIPTION;
6750
7492
  var init_projects = __esm({
6751
7493
  "src/server/graph/core/projects.ts"() {
6752
7494
  "use strict";
6753
- import_node_path25 = require("node:path");
7495
+ import_node_path27 = require("node:path");
7496
+ init_worktree();
6754
7497
  init_config();
7498
+ WORKTREE_PARAM_DESCRIPTION2 = WORKTREE_PARAM_DESCRIPTION;
7499
+ PROJECT_ROOT_PARAM_DESCRIPTION2 = PROJECT_ROOT_PARAM_DESCRIPTION;
6755
7500
  PROJECT_PARAM_DESCRIPTION = "Optional sub-project name (or root path) from .launchchart.json projects[]. Defaults to the monorepo root. Run detect_project_stack to list configured projects.";
6756
7501
  }
6757
7502
  });
@@ -6890,10 +7635,10 @@ var init_graph_cli = __esm({
6890
7635
  // src/server/graph/core/language-detection.ts
6891
7636
  function walkForExtensions(dir, extCounts, depth = 0) {
6892
7637
  if (depth > 10) return;
6893
- if (!(0, import_node_fs22.existsSync)(dir)) return;
7638
+ if (!(0, import_node_fs23.existsSync)(dir)) return;
6894
7639
  let entries;
6895
7640
  try {
6896
- entries = (0, import_node_fs22.readdirSync)(dir, { withFileTypes: true });
7641
+ entries = (0, import_node_fs23.readdirSync)(dir, { withFileTypes: true });
6897
7642
  } catch {
6898
7643
  return;
6899
7644
  }
@@ -6901,9 +7646,9 @@ function walkForExtensions(dir, extCounts, depth = 0) {
6901
7646
  if (entry.name.startsWith(".") && entry.isDirectory()) continue;
6902
7647
  if (entry.isDirectory()) {
6903
7648
  if (IGNORE_DIRS.has(entry.name)) continue;
6904
- walkForExtensions((0, import_node_path26.join)(dir, entry.name), extCounts, depth + 1);
7649
+ walkForExtensions((0, import_node_path28.join)(dir, entry.name), extCounts, depth + 1);
6905
7650
  } else {
6906
- const ext = (0, import_node_path26.extname)(entry.name).toLowerCase();
7651
+ const ext = (0, import_node_path28.extname)(entry.name).toLowerCase();
6907
7652
  if (ext && EXTENSION_TO_LANGUAGE[ext]) {
6908
7653
  extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
6909
7654
  }
@@ -6942,12 +7687,12 @@ function detectLanguages(rootDir, supportedLanguages) {
6942
7687
  });
6943
7688
  return results;
6944
7689
  }
6945
- var import_node_fs22, import_node_path26, EXTENSION_TO_LANGUAGE, IGNORE_DIRS, AUXILIARY_LANGUAGES;
7690
+ var import_node_fs23, import_node_path28, EXTENSION_TO_LANGUAGE, IGNORE_DIRS, AUXILIARY_LANGUAGES;
6946
7691
  var init_language_detection = __esm({
6947
7692
  "src/server/graph/core/language-detection.ts"() {
6948
7693
  "use strict";
6949
- import_node_fs22 = require("node:fs");
6950
- import_node_path26 = require("node:path");
7694
+ import_node_fs23 = require("node:fs");
7695
+ import_node_path28 = require("node:path");
6951
7696
  init_launch_kit_paths();
6952
7697
  EXTENSION_TO_LANGUAGE = {
6953
7698
  // Web / Frontend
@@ -7069,7 +7814,7 @@ __export(watcher_exports, {
7069
7814
  function isIgnoredPath(rel) {
7070
7815
  if (rel.startsWith(GRAPHS_RELATIVE)) return true;
7071
7816
  if (rel.endsWith(".lock") || rel.endsWith(".log")) return true;
7072
- for (const part of rel.split(import_node_path27.sep)) {
7817
+ for (const part of rel.split(import_node_path29.sep)) {
7073
7818
  if (IGNORE_SEGMENTS.has(part)) return true;
7074
7819
  }
7075
7820
  return false;
@@ -7111,7 +7856,7 @@ function startGraphWatcher(rootDir, opts = {}) {
7111
7856
  regenerating = false;
7112
7857
  }
7113
7858
  }
7114
- const watcher = (0, import_node_fs23.watch)(rootDir, { recursive: true }, (event, filename) => {
7859
+ const watcher = (0, import_node_fs24.watch)(rootDir, { recursive: true }, (event, filename) => {
7115
7860
  if (!filename) return;
7116
7861
  const rel = filename.toString();
7117
7862
  if (process.env.LAUNCH_CHART_WATCH_TRACE === "1") {
@@ -7144,12 +7889,12 @@ function startGraphWatcher(rootDir, opts = {}) {
7144
7889
  freshness: () => getFreshnessTracker(rootDir).get()
7145
7890
  };
7146
7891
  }
7147
- var import_node_fs23, import_node_path27, IGNORE_SEGMENTS, TRIGGER_EXTENSIONS, GRAPHS_RELATIVE;
7892
+ var import_node_fs24, import_node_path29, IGNORE_SEGMENTS, TRIGGER_EXTENSIONS, GRAPHS_RELATIVE;
7148
7893
  var init_watcher = __esm({
7149
7894
  "src/server/graph/core/watcher.ts"() {
7150
7895
  "use strict";
7151
- import_node_fs23 = require("node:fs");
7152
- import_node_path27 = require("node:path");
7896
+ import_node_fs24 = require("node:fs");
7897
+ import_node_path29 = require("node:path");
7153
7898
  init_launch_kit_paths();
7154
7899
  init_graph();
7155
7900
  init_freshness();
@@ -7178,7 +7923,7 @@ var init_watcher = __esm({
7178
7923
  ".prisma",
7179
7924
  ".sql"
7180
7925
  ]);
7181
- GRAPHS_RELATIVE = (0, import_node_path27.join)(LAUNCHSECURE_DIR, "graphs");
7926
+ GRAPHS_RELATIVE = (0, import_node_path29.join)(LAUNCHSECURE_DIR, "graphs");
7182
7927
  }
7183
7928
  });
7184
7929
 
@@ -7194,14 +7939,43 @@ function matchesSearch(node, query) {
7194
7939
  if (node.name.toLowerCase().includes(q)) return true;
7195
7940
  const route = node.route;
7196
7941
  if (route && route.toLowerCase().includes(q)) return true;
7942
+ const elements = node.elements;
7943
+ if (elements) {
7944
+ for (const el of elements) {
7945
+ if (el.text && el.text.toLowerCase().includes(q)) return true;
7946
+ if (el.props) {
7947
+ for (const v of Object.values(el.props)) {
7948
+ if (typeof v !== "string") continue;
7949
+ if (v.includes("=>") || v.startsWith("(") || v.startsWith("{")) continue;
7950
+ if (v.toLowerCase().includes(q)) return true;
7951
+ }
7952
+ }
7953
+ }
7954
+ }
7955
+ const uiLabels = node.ui_labels;
7956
+ if (uiLabels) {
7957
+ for (const lbl of uiLabels) {
7958
+ if (typeof lbl === "string" && lbl.toLowerCase().includes(q)) return true;
7959
+ }
7960
+ }
7961
+ const notes = node.notes;
7962
+ if (notes) {
7963
+ for (const n of notes) {
7964
+ if (n.kind && n.kind.toLowerCase().includes(q)) return true;
7965
+ if (n.text && n.text.toLowerCase().includes(q)) return true;
7966
+ }
7967
+ }
7197
7968
  return false;
7198
7969
  }
7199
7970
  function toMinimal(nodes) {
7200
7971
  return nodes.map((n) => {
7201
7972
  const out = { id: n.id, type: n.type, name: n.name };
7202
- if (n.tags != null) out.tags = n.tags;
7203
- if (n.route != null) out.route = n.route;
7204
- if (n.methods != null) out.methods = n.methods;
7973
+ for (const [k, v] of Object.entries(n)) {
7974
+ if (k === "id" || k === "type" || k === "name") continue;
7975
+ if (MINIMAL_STRIP_FIELDS.has(k)) continue;
7976
+ if (DEEP_FIELDS.has(k)) continue;
7977
+ if (v != null) out[k] = v;
7978
+ }
7205
7979
  return out;
7206
7980
  });
7207
7981
  }
@@ -7213,6 +7987,9 @@ function crossRefsAsEdges(graph) {
7213
7987
  target_layer: c.layer
7214
7988
  }));
7215
7989
  }
7990
+ function categorizeNoteKind(kind) {
7991
+ return NOTE_KIND_CATEGORY[kind] ?? "custom";
7992
+ }
7216
7993
  function toCompactNode(n) {
7217
7994
  const out = { i: n.id, t: n.type, n: n.name };
7218
7995
  const tags = n.tags;
@@ -7467,7 +8244,7 @@ function withFreshnessMeta(result, args) {
7467
8244
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return result;
7468
8245
  let rootDir;
7469
8246
  try {
7470
- rootDir = resolveProjectRoot(args.project, process.cwd());
8247
+ rootDir = resolveRequestRoot2(args, process.cwd());
7471
8248
  } catch {
7472
8249
  return result;
7473
8250
  }
@@ -7482,7 +8259,7 @@ function withFreshnessMeta(result, args) {
7482
8259
  }
7483
8260
  function resolveOrErr(args) {
7484
8261
  try {
7485
- return { rootDir: resolveProjectRoot(args.project, process.cwd()) };
8262
+ return { rootDir: resolveRequestRoot2(args, process.cwd()) };
7486
8263
  } catch (e) {
7487
8264
  return err(e.message);
7488
8265
  }
@@ -7491,6 +8268,8 @@ async function handleGenerateGraph(args) {
7491
8268
  const monorepoRoot = process.cwd();
7492
8269
  const layer = args.layer;
7493
8270
  const projectArg = typeof args.project === "string" ? args.project.trim() : "";
8271
+ const worktreeArg = typeof args.worktree === "string" ? args.worktree.trim() : "";
8272
+ const projectRootArg = typeof args.project_root === "string" ? args.project_root.trim() : "";
7494
8273
  function formatProjectResult(results2, relativeRoot) {
7495
8274
  return results2.map((r) => {
7496
8275
  const warnings = r.output.warnings.length;
@@ -7498,25 +8277,27 @@ async function handleGenerateGraph(args) {
7498
8277
  }).join("\n") + `
7499
8278
  \u2192 ${relativeRoot}/.launchsecure/graphs/`;
7500
8279
  }
7501
- if (projectArg) {
8280
+ if (projectArg || worktreeArg || projectRootArg) {
7502
8281
  let rootDir;
7503
8282
  try {
7504
- rootDir = resolveProjectRoot(projectArg, monorepoRoot);
8283
+ rootDir = resolveRequestRoot2(args, monorepoRoot);
7505
8284
  } catch (e) {
7506
8285
  return err(e.message);
7507
8286
  }
7508
8287
  const results2 = await generateGraph(rootDir, layer);
8288
+ const label = worktreeArg ? `worktree "${worktreeArg}"` : projectArg ? `project "${projectArg}"` : `root "${projectRootArg}"`;
8289
+ const queryHint = worktreeArg ? `read_graph (with worktree="${worktreeArg}")` : projectArg ? `read_graph (with project="${projectArg}")` : `read_graph (with project_root="${projectRootArg}")`;
7509
8290
  if (results2.length === 0) {
7510
8291
  return err(
7511
- layer ? `No parser detected for the "${layer}" layer in project "${projectArg}".` : `No parsers detected for project "${projectArg}". Check that the project root has the expected structure.`
8292
+ layer ? `No parser detected for the "${layer}" layer in ${label}.` : `No parsers detected for ${label}. Check that the root has the expected structure.`
7512
8293
  );
7513
8294
  }
7514
8295
  return ok(
7515
- `Graph generated successfully for project "${projectArg}".
8296
+ `Graph generated successfully for ${label}.
7516
8297
 
7517
- ${formatProjectResult(results2, projectArg)}
8298
+ ${formatProjectResult(results2, rootDir)}
7518
8299
 
7519
- Use read_graph (with project="${projectArg}") to query.`
8300
+ Use ${queryHint} to query.`
7520
8301
  );
7521
8302
  }
7522
8303
  const projects = listProjects(monorepoRoot);
@@ -7579,9 +8360,14 @@ function runReadGraphQueryRaw(rootDir, args) {
7579
8360
  const layerIsDb = args.layer === "db";
7580
8361
  const minimal = args.minimal ?? layerIsDb;
7581
8362
  const includeEdges = args.include_edges;
8363
+ const includeFindings = args.include_findings === true;
8364
+ const tagPredicates = Array.isArray(args.tag_predicates) ? args.tag_predicates : void 0;
8365
+ const includeCrossRefs = args.include_cross_refs === true;
8366
+ const crossRefType = args.cross_ref_type;
8367
+ const edgeConfidence = Array.isArray(args.edge_confidence) ? new Set(args.edge_confidence.map((s) => String(s).toLowerCase())) : void 0;
7582
8368
  const offset = args.offset ?? 0;
7583
8369
  const limit = args.limit;
7584
- const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue);
8370
+ const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue || tagPredicates && tagPredicates.length > 0);
7585
8371
  if (layer) {
7586
8372
  const available = getAvailableLayers(rootDir);
7587
8373
  if (available.length > 0 && !available.includes(layer)) {
@@ -7628,14 +8414,31 @@ function runReadGraphQueryRaw(rootDir, args) {
7628
8414
  result2.budget_exceeded = true;
7629
8415
  result2.hint = `Neighborhood truncated at hop ${nb.stoppedAtHop} (projected size exceeded budget). To explore further, call read_graph with node_id set to a specific neighbor from the returned nodes, or rerun with hops=${Math.max(1, nb.stoppedAtHop)} to confirm the partial view is what you wanted.`;
7630
8416
  }
8417
+ if (includeFindings && layer === "db") {
8418
+ result2.contradictions = graph.contradictions ?? [];
8419
+ result2.flagged_edges = graph.flagged_edges ?? [];
8420
+ }
8421
+ const neighborhoodIds = new Set(nb.nodes.map((n) => n.id));
8422
+ attachCrossRefsAndFlagged(result2, graph, neighborhoodIds, {
8423
+ includeCrossRefs,
8424
+ crossRefType,
8425
+ edgeConfidence,
8426
+ scopeToIds: true,
8427
+ layer
8428
+ });
7631
8429
  return result2;
7632
8430
  }
7633
8431
  if (!hasFilter) {
7634
- return {
8432
+ const summaryResult = {
7635
8433
  hint: "No filter specified \u2014 returning summary only. Use search/type/module/node_id to retrieve nodes.",
7636
8434
  layer,
7637
8435
  summary: layerSummary(graph)
7638
8436
  };
8437
+ if (includeFindings && layer === "db") {
8438
+ summaryResult.contradictions = graph.contradictions ?? [];
8439
+ summaryResult.flagged_edges = graph.flagged_edges ?? [];
8440
+ }
8441
+ return summaryResult;
7639
8442
  }
7640
8443
  const matched = graph.nodes.filter((n) => {
7641
8444
  if (search && !matchesSearch(n, search)) return false;
@@ -7643,6 +8446,11 @@ function runReadGraphQueryRaw(rootDir, args) {
7643
8446
  const nodeTags = n.tags;
7644
8447
  if (module_ && nodeTags?.module !== module_) return false;
7645
8448
  if (tagKey && tagValue && nodeTags?.[tagKey] !== tagValue) return false;
8449
+ if (tagPredicates && tagPredicates.length > 0) {
8450
+ for (const p of tagPredicates) {
8451
+ if (!evaluatePredicate(n, p)) return false;
8452
+ }
8453
+ }
7646
8454
  return true;
7647
8455
  });
7648
8456
  if (matched.length === 0) {
@@ -7686,8 +8494,93 @@ function runReadGraphQueryRaw(rootDir, args) {
7686
8494
  } else if (returnedEdges.length > 0) {
7687
8495
  result.edges_hint = `${returnedEdges.length} edges between matched nodes omitted. Pass include_edges:true to retrieve them (only do this when you actually need edge data).`;
7688
8496
  }
8497
+ if (includeFindings && layer === "db") {
8498
+ result.contradictions = graph.contradictions ?? [];
8499
+ result.flagged_edges = graph.flagged_edges ?? [];
8500
+ }
8501
+ attachCrossRefsAndFlagged(result, graph, returnedIds, {
8502
+ includeCrossRefs,
8503
+ crossRefType,
8504
+ edgeConfidence,
8505
+ scopeToIds: true,
8506
+ layer
8507
+ });
7689
8508
  return result;
7690
8509
  }
8510
+ function attachCrossRefsAndFlagged(result, graph, scopeIds, opts) {
8511
+ if (opts.includeCrossRefs) {
8512
+ const allCrossRefs = graph.cross_refs ?? [];
8513
+ const crossRefsByNode = {};
8514
+ for (const id of scopeIds) {
8515
+ crossRefsByNode[id] = { outgoing: [], incoming: [] };
8516
+ }
8517
+ for (const cr of allCrossRefs) {
8518
+ if (opts.crossRefType && cr.type !== opts.crossRefType) continue;
8519
+ if (crossRefsByNode[cr.source]) crossRefsByNode[cr.source].outgoing.push(cr);
8520
+ if (crossRefsByNode[cr.target]) crossRefsByNode[cr.target].incoming.push(cr);
8521
+ }
8522
+ result.cross_refs = crossRefsByNode;
8523
+ if (opts.crossRefType) result.cross_ref_type = opts.crossRefType;
8524
+ if (allCrossRefs.length === 0) {
8525
+ result.parser_warning = parserWarning([opts.layer], "cross_refs", "cross-layer references parser(s) (fetch-resolver / api-annotations / url-literal-scanner / static-ref-scanner)");
8526
+ }
8527
+ }
8528
+ if (opts.edgeConfidence && opts.edgeConfidence.size > 0) {
8529
+ const allFlagged = graph.flagged_edges ?? [];
8530
+ const useScope = opts.scopeToIds && scopeIds.size > 0;
8531
+ result.flagged_edges = allFlagged.filter((fe) => {
8532
+ if (!opts.edgeConfidence.has(String(fe.confidence).toLowerCase())) return false;
8533
+ if (useScope && !scopeIds.has(fe.source) && !scopeIds.has(fe.target)) return false;
8534
+ return true;
8535
+ });
8536
+ if (allFlagged.length === 0 && !result.parser_warning) {
8537
+ result.parser_warning = parserWarning([opts.layer], "flagged_edges", opts.layer === "db" ? "sql-migrations parser" : "cross-layer parsers");
8538
+ }
8539
+ }
8540
+ }
8541
+ function parserWarning(layers, capability, parserName) {
8542
+ return `The ${capability} data is empty in scanned layer(s): ${layers.join(", ")}. This likely means the ${parserName} did not produce output for this project \u2014 run detect_project_stack to verify parser configuration, then generate_graph to refresh. (If the parser IS configured and the source data is genuinely empty, ignore this warning.)`;
8543
+ }
8544
+ function evaluatePredicate(node, p) {
8545
+ if (!p.field || !p.op) return true;
8546
+ const tags = node.tags;
8547
+ const fromTag = tags?.[p.field];
8548
+ const fromField = node[p.field];
8549
+ const present = fromTag !== void 0 || fromField !== void 0;
8550
+ if (p.op === "exists") return present;
8551
+ if (p.op === "not_exists") return !present;
8552
+ if (!present) return false;
8553
+ const raw = fromTag !== void 0 ? coerceTagValue(fromTag, p.value) : fromField;
8554
+ switch (p.op) {
8555
+ case "eq":
8556
+ return raw === p.value;
8557
+ case "ne":
8558
+ return raw !== p.value;
8559
+ case "gt":
8560
+ return typeof raw === "number" && typeof p.value === "number" && raw > p.value;
8561
+ case "lt":
8562
+ return typeof raw === "number" && typeof p.value === "number" && raw < p.value;
8563
+ case "gte":
8564
+ return typeof raw === "number" && typeof p.value === "number" && raw >= p.value;
8565
+ case "lte":
8566
+ return typeof raw === "number" && typeof p.value === "number" && raw <= p.value;
8567
+ case "in":
8568
+ return Array.isArray(p.value) && p.value.includes(raw);
8569
+ default:
8570
+ return false;
8571
+ }
8572
+ }
8573
+ function coerceTagValue(raw, target) {
8574
+ if (typeof target === "boolean") {
8575
+ if (raw === "true") return true;
8576
+ if (raw === "false") return false;
8577
+ }
8578
+ if (typeof target === "number") {
8579
+ const n = Number(raw);
8580
+ if (!Number.isNaN(n)) return n;
8581
+ }
8582
+ return raw;
8583
+ }
7691
8584
  function runReadGraphQuery(rootDir, args) {
7692
8585
  const raw = runReadGraphQueryRaw(rootDir, args);
7693
8586
  return compactResult(raw);
@@ -7700,6 +8593,8 @@ function handleReadGraph(args) {
7700
8593
  return err("queries array is empty. Provide at least one query object.");
7701
8594
  }
7702
8595
  const inheritedProject = typeof args.project === "string" ? args.project : void 0;
8596
+ const inheritedWorktree = typeof args.worktree === "string" ? args.worktree : void 0;
8597
+ const inheritedProjectRoot = typeof args.project_root === "string" ? args.project_root : void 0;
7703
8598
  const results = [];
7704
8599
  let cumulativeChars = 0;
7705
8600
  let budgetHit = false;
@@ -7717,15 +8612,18 @@ function handleReadGraph(args) {
7717
8612
  });
7718
8613
  continue;
7719
8614
  }
7720
- const qWithProject = inheritedProject && !q.project ? { ...q, project: inheritedProject } : q;
8615
+ const qInherited = { ...q };
8616
+ if (inheritedProject && !qInherited.project) qInherited.project = inheritedProject;
8617
+ if (inheritedWorktree && !qInherited.worktree) qInherited.worktree = inheritedWorktree;
8618
+ if (inheritedProjectRoot && !qInherited.project_root) qInherited.project_root = inheritedProjectRoot;
7721
8619
  let perQueryRoot;
7722
8620
  try {
7723
- perQueryRoot = resolveProjectRoot(qWithProject.project, monorepoRoot);
8621
+ perQueryRoot = resolveRequestRoot2(qInherited, monorepoRoot);
7724
8622
  } catch (e) {
7725
8623
  results.push({ index: i, query: q, result: { error: e.message } });
7726
8624
  continue;
7727
8625
  }
7728
- const r = runReadGraphQuery(perQueryRoot, qWithProject);
8626
+ const r = runReadGraphQuery(perQueryRoot, qInherited);
7729
8627
  const entry = { index: i, query: q, result: r };
7730
8628
  const entrySize = JSON.stringify(entry, null, 2).length;
7731
8629
  if (cumulativeChars + entrySize > BATCH_BUDGET_CHARS && results.length > 0) {
@@ -7758,12 +8656,12 @@ function handleReadGraph(args) {
7758
8656
  return okJson(result);
7759
8657
  }
7760
8658
  function nodeToFilePath(rootDir, layer, nodeId) {
7761
- if (layer === "ui" || layer === "api") return (0, import_node_path28.join)(rootDir, "src", nodeId);
7762
- if (layer === "db") return (0, import_node_path28.join)(rootDir, "prisma", "schema.prisma");
7763
- const withSrc = (0, import_node_path28.join)(rootDir, "src", nodeId);
7764
- if ((0, import_node_fs24.existsSync)(withSrc)) return withSrc;
7765
- const direct = (0, import_node_path28.join)(rootDir, nodeId);
7766
- if ((0, import_node_fs24.existsSync)(direct)) return direct;
8659
+ if (layer === "ui" || layer === "api") return (0, import_node_path30.join)(rootDir, "src", nodeId);
8660
+ if (layer === "db") return (0, import_node_path30.join)(rootDir, "prisma", "schema.prisma");
8661
+ const withSrc = (0, import_node_path30.join)(rootDir, "src", nodeId);
8662
+ if ((0, import_node_fs25.existsSync)(withSrc)) return withSrc;
8663
+ const direct = (0, import_node_path30.join)(rootDir, nodeId);
8664
+ if ((0, import_node_fs25.existsSync)(direct)) return direct;
7767
8665
  return null;
7768
8666
  }
7769
8667
  function handleInspectNode(args) {
@@ -7793,8 +8691,23 @@ function handleInspectNode(args) {
7793
8691
  } else {
7794
8692
  matched = graph.nodes;
7795
8693
  }
7796
- const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params", "effects"];
8694
+ const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params", "effects", "ui_labels", "notes", "crossRefs"];
7797
8695
  const requestedFields = fields ?? allDeepFields;
8696
+ const crossRefTypeArg = args.cross_ref_type;
8697
+ const wantCrossRefs = requestedFields.includes("crossRefs");
8698
+ let crossRefsBySource = null;
8699
+ let crossRefsByTarget = null;
8700
+ if (wantCrossRefs) {
8701
+ crossRefsBySource = /* @__PURE__ */ new Map();
8702
+ crossRefsByTarget = /* @__PURE__ */ new Map();
8703
+ for (const cr of graph.cross_refs ?? []) {
8704
+ if (crossRefTypeArg && cr.type !== crossRefTypeArg) continue;
8705
+ if (!crossRefsBySource.has(cr.source)) crossRefsBySource.set(cr.source, []);
8706
+ crossRefsBySource.get(cr.source).push(cr);
8707
+ if (!crossRefsByTarget.has(cr.target)) crossRefsByTarget.set(cr.target, []);
8708
+ crossRefsByTarget.get(cr.target).push(cr);
8709
+ }
8710
+ }
7798
8711
  let filterRegex = null;
7799
8712
  if (filter) {
7800
8713
  try {
@@ -7817,7 +8730,14 @@ function handleInspectNode(args) {
7817
8730
  const deep = { id: node.id, name: node.name, type: node.type };
7818
8731
  let hasData = false;
7819
8732
  for (const field of requestedFields) {
7820
- if (allDeepFields.includes(field) && node[field] != null) {
8733
+ if (field === "crossRefs") {
8734
+ const outgoing = crossRefsBySource?.get(node.id) ?? [];
8735
+ const incoming = crossRefsByTarget?.get(node.id) ?? [];
8736
+ if (outgoing.length > 0 || incoming.length > 0) {
8737
+ deep.crossRefs = { outgoing, incoming };
8738
+ hasData = true;
8739
+ }
8740
+ } else if (allDeepFields.includes(field) && node[field] != null) {
7821
8741
  deep[field] = node[field];
7822
8742
  hasData = true;
7823
8743
  }
@@ -7841,13 +8761,112 @@ function handleInspectNode(args) {
7841
8761
  const hint = filter ? `No nodes with deep fields matching /${filter}/${caseInsensitive ? "i" : ""} in ${layer} layer.` : `No nodes matching "${search}" in ${layer} layer.`;
7842
8762
  return err(hint);
7843
8763
  }
8764
+ const wantedCrossRefs = wantCrossRefs;
8765
+ const graphHasNoCrossRefs = (graph.cross_refs ?? []).length === 0;
8766
+ const extraWarning = wantedCrossRefs && graphHasNoCrossRefs ? { parser_warning: parserWarning([layer], "cross_refs", "cross-layer references parser(s) (fetch-resolver / api-annotations / url-literal-scanner / static-ref-scanner)") } : {};
7844
8767
  return okJson({
7845
8768
  layer,
7846
8769
  matched: results.length,
7847
8770
  ...results.length >= maxResults ? { truncated: true, hint: `Showing first ${maxResults} matches. Narrow with search param.` } : {},
8771
+ ...extraWarning,
7848
8772
  nodes: results
7849
8773
  });
7850
8774
  }
8775
+ function handleListNotes(args) {
8776
+ const __resolved = resolveOrErr(args);
8777
+ if ("content" in __resolved) return __resolved;
8778
+ const { rootDir } = __resolved;
8779
+ const layerArg = args.layer;
8780
+ const kindArg = args.kind?.toUpperCase();
8781
+ const categoryArg = args.category ?? "actionable,warning";
8782
+ const moduleArg = args.module;
8783
+ const patternArg = args.pattern;
8784
+ const authorArg = args.author?.toLowerCase();
8785
+ const limit = args.limit ?? 100;
8786
+ const offset = args.offset ?? 0;
8787
+ let patternRegex = null;
8788
+ if (patternArg) {
8789
+ try {
8790
+ patternRegex = new RegExp(patternArg, "i");
8791
+ } catch {
8792
+ return err(`Invalid regex pattern: "${patternArg}"`);
8793
+ }
8794
+ }
8795
+ const wantedCategories = (() => {
8796
+ const trimmed = categoryArg.trim().toLowerCase();
8797
+ if (trimmed === "all") return "all";
8798
+ const out = /* @__PURE__ */ new Set();
8799
+ for (const part of trimmed.split(",").map((s) => s.trim()).filter(Boolean)) {
8800
+ if (part === "actionable" || part === "warning" || part === "doc" || part === "custom") {
8801
+ out.add(part);
8802
+ }
8803
+ }
8804
+ if (out.size === 0) {
8805
+ out.add("actionable");
8806
+ out.add("warning");
8807
+ }
8808
+ return out;
8809
+ })();
8810
+ const layers = layerArg ? [layerArg] : getAvailableLayers(rootDir);
8811
+ const items = [];
8812
+ for (const layer of layers) {
8813
+ const graph = readGraph(rootDir, layer);
8814
+ if (!graph) continue;
8815
+ for (const node of graph.nodes) {
8816
+ const nodeNotes = node.notes;
8817
+ if (!nodeNotes || nodeNotes.length === 0) continue;
8818
+ const tags = node.tags;
8819
+ const nodeModule = tags?.module;
8820
+ if (moduleArg && nodeModule !== moduleArg) continue;
8821
+ for (const n of nodeNotes) {
8822
+ if (!n.kind || !n.text || typeof n.line !== "number") continue;
8823
+ const category = categorizeNoteKind(n.kind);
8824
+ if (wantedCategories !== "all" && !wantedCategories.has(category)) continue;
8825
+ if (kindArg && n.kind.toUpperCase() !== kindArg) continue;
8826
+ if (authorArg && (!n.author || n.author.toLowerCase() !== authorArg)) continue;
8827
+ if (patternRegex && !patternRegex.test(n.text)) continue;
8828
+ const item = {
8829
+ file: node.id,
8830
+ line: n.line,
8831
+ kind: n.kind,
8832
+ category,
8833
+ text: n.text
8834
+ };
8835
+ if (n.author) item.author = n.author;
8836
+ if (nodeModule) item.module = nodeModule;
8837
+ items.push(item);
8838
+ }
8839
+ }
8840
+ }
8841
+ const catOrder = { actionable: 0, warning: 1, doc: 2, custom: 3 };
8842
+ items.sort((a, b) => {
8843
+ const c = catOrder[a.category] - catOrder[b.category];
8844
+ if (c !== 0) return c;
8845
+ const k = a.kind.localeCompare(b.kind);
8846
+ if (k !== 0) return k;
8847
+ const f = a.file.localeCompare(b.file);
8848
+ if (f !== 0) return f;
8849
+ return a.line - b.line;
8850
+ });
8851
+ const total = items.length;
8852
+ const paged = items.slice(offset, offset + limit);
8853
+ const hasMore = offset + paged.length < total;
8854
+ return okJson({
8855
+ total,
8856
+ returned: paged.length,
8857
+ has_more: hasMore,
8858
+ ...hasMore ? { next_offset: offset + paged.length } : {},
8859
+ filter: {
8860
+ layer: layerArg ?? "all",
8861
+ kind: kindArg ?? null,
8862
+ category: categoryArg,
8863
+ module: moduleArg ?? null,
8864
+ pattern: patternArg ?? null,
8865
+ author: authorArg ?? null
8866
+ },
8867
+ items: paged
8868
+ });
8869
+ }
7851
8870
  function handleGrepNodes(args) {
7852
8871
  const __resolved = resolveOrErr(args);
7853
8872
  if ("content" in __resolved) return __resolved;
@@ -7910,11 +8929,11 @@ function handleGrepNodes(args) {
7910
8929
  let filesSearched = 0;
7911
8930
  let truncated = false;
7912
8931
  for (const [filePath, nodeId] of filePaths) {
7913
- if (!(0, import_node_fs24.existsSync)(filePath)) continue;
8932
+ if (!(0, import_node_fs25.existsSync)(filePath)) continue;
7914
8933
  filesSearched++;
7915
8934
  let content;
7916
8935
  try {
7917
- content = (0, import_node_fs24.readFileSync)(filePath, "utf-8");
8936
+ content = (0, import_node_fs25.readFileSync)(filePath, "utf-8");
7918
8937
  } catch {
7919
8938
  continue;
7920
8939
  }
@@ -7955,13 +8974,19 @@ function handleEffectsIndex(args) {
7955
8974
  const __resolved = resolveOrErr(args);
7956
8975
  if ("content" in __resolved) return __resolved;
7957
8976
  const { rootDir } = __resolved;
8977
+ const kind = args.kind ?? "collisions";
8978
+ const key = args.key;
8979
+ const MAX_KEYS = 200;
8980
+ if (kind === "fetch_calls") {
8981
+ return okJson(buildFetchCallsIndex(rootDir, key, MAX_KEYS));
8982
+ }
8983
+ if (kind === "api_effects") {
8984
+ return okJson(buildApiEffectsIndex(rootDir, key, MAX_KEYS));
8985
+ }
7958
8986
  const idx = readEffectsIndex(rootDir);
7959
8987
  if (!idx) {
7960
8988
  return err("No effects-index.json found. Run generate_graph first (or wait for the file-watcher to fire).");
7961
8989
  }
7962
- const kind = args.kind ?? "collisions";
7963
- const key = args.key;
7964
- const MAX_KEYS = 200;
7965
8990
  if (kind === "singleton_risks") {
7966
8991
  return okJson({ kind, count: idx.singleton_risks.length, nodes: idx.singleton_risks });
7967
8992
  }
@@ -7975,7 +9000,7 @@ function handleEffectsIndex(args) {
7975
9000
  }
7976
9001
  const map = idx[kind];
7977
9002
  if (!map || typeof map !== "object") {
7978
- return err(`Unknown kind "${kind}". Valid: dom_ids, window_events, storage_keys, fetch_urls, timers, singleton_risks, collisions.`);
9003
+ return err(`Unknown kind "${kind}". Valid: dom_ids, window_events, storage_keys, fetch_urls, timers, singleton_risks, collisions, fetch_calls, api_effects.`);
7979
9004
  }
7980
9005
  if (key) {
7981
9006
  const nodes = map[key] ?? [];
@@ -7993,6 +9018,97 @@ function handleEffectsIndex(args) {
7993
9018
  results
7994
9019
  });
7995
9020
  }
9021
+ function buildFetchCallsIndex(rootDir, key, maxKeys) {
9022
+ const ui = readGraph(rootDir, "ui");
9023
+ if (!ui) return { kind: "fetch_calls", error: "No ui graph found. Run generate_graph first." };
9024
+ const inverted = /* @__PURE__ */ new Map();
9025
+ let callsApiCount = 0;
9026
+ for (const cr of ui.cross_refs ?? []) {
9027
+ if (cr.type !== "calls_api") continue;
9028
+ callsApiCount++;
9029
+ if (!inverted.has(cr.target)) inverted.set(cr.target, /* @__PURE__ */ new Set());
9030
+ inverted.get(cr.target).add(cr.source);
9031
+ }
9032
+ for (const n of ui.nodes) {
9033
+ const eff = n.effects;
9034
+ if (!eff?.fetches) continue;
9035
+ for (const f of eff.fetches) {
9036
+ if (!inverted.has(f)) inverted.set(f, /* @__PURE__ */ new Set());
9037
+ inverted.get(f).add(n.id);
9038
+ }
9039
+ }
9040
+ const allCrossRefs = ui.cross_refs ?? [];
9041
+ const fetchResolverAbsent = callsApiCount === 0 && allCrossRefs.length === 0;
9042
+ const partialResolverGap = callsApiCount === 0 && inverted.size > 0;
9043
+ if (key) {
9044
+ return {
9045
+ kind: "fetch_calls",
9046
+ key,
9047
+ callers: [...inverted.get(key) ?? []],
9048
+ ...fetchResolverAbsent ? { parser_warning: parserWarning(["ui"], "cross_refs", "fetch-resolver cross-layer parser") } : {},
9049
+ ...partialResolverGap ? { parser_warning: "No calls_api cross_refs found \u2014 fetch-resolver may not be enabled. Only raw fetch() strings from ui.node.effects.fetches are indexed; resolved-to-endpoint links are missing." } : {}
9050
+ };
9051
+ }
9052
+ const entries = [...inverted.entries()].sort((a, b) => b[1].size - a[1].size);
9053
+ const truncated = entries.length > maxKeys;
9054
+ const results = {};
9055
+ for (const [k, v] of entries.slice(0, maxKeys)) results[k] = [...v];
9056
+ return {
9057
+ kind: "fetch_calls",
9058
+ total_keys: entries.length,
9059
+ ...truncated ? { truncated: true, showing: maxKeys, hint: "Pass `key` (endpoint path or URL template) to look up callers." } : {},
9060
+ ...fetchResolverAbsent ? { parser_warning: parserWarning(["ui"], "cross_refs", "fetch-resolver cross-layer parser") } : {},
9061
+ ...partialResolverGap ? { parser_warning: "No calls_api cross_refs found \u2014 fetch-resolver may not be enabled. Only raw fetch() strings from ui.node.effects.fetches are indexed; resolved-to-endpoint links are missing." } : {},
9062
+ results
9063
+ };
9064
+ }
9065
+ function buildApiEffectsIndex(rootDir, key, maxKeys) {
9066
+ const api = readGraph(rootDir, "api");
9067
+ if (!api) return { kind: "api_effects", error: "No api graph found. Run generate_graph first." };
9068
+ const categories = ["calls", "fetches", "persists", "subscribes", "timers", "dom_writes", "globals"];
9069
+ const inverted = {};
9070
+ for (const c of categories) inverted[c] = /* @__PURE__ */ new Map();
9071
+ for (const n of api.nodes) {
9072
+ const eff = n.effects;
9073
+ if (!eff) continue;
9074
+ for (const c of categories) {
9075
+ const items = eff[c];
9076
+ if (!Array.isArray(items)) continue;
9077
+ for (const item of items) {
9078
+ if (!inverted[c].has(item)) inverted[c].set(item, /* @__PURE__ */ new Set());
9079
+ inverted[c].get(item).add(n.id);
9080
+ }
9081
+ }
9082
+ }
9083
+ if (key) {
9084
+ const hits = {};
9085
+ for (const c of categories) {
9086
+ const endpoints = inverted[c].get(key);
9087
+ if (endpoints && endpoints.size > 0) hits[c] = [...endpoints];
9088
+ }
9089
+ return { kind: "api_effects", key, hits };
9090
+ }
9091
+ const TOP_CATEGORIES = Math.min(maxKeys, 50);
9092
+ const TOP_CALLERS = 10;
9093
+ const summary = {};
9094
+ for (const c of categories) {
9095
+ const entries = [...inverted[c].entries()].sort((a, b) => b[1].size - a[1].size);
9096
+ const top = {};
9097
+ for (const [k, v] of entries.slice(0, TOP_CATEGORIES)) {
9098
+ const endpoints = [...v];
9099
+ top[k] = {
9100
+ count: endpoints.length,
9101
+ sample_endpoints: endpoints.slice(0, TOP_CALLERS)
9102
+ };
9103
+ }
9104
+ summary[c] = { total_items: entries.length, top };
9105
+ }
9106
+ return {
9107
+ kind: "api_effects",
9108
+ hint: `Summary mode: top ${TOP_CATEGORIES} items per category, top ${TOP_CALLERS} endpoints per item. Pass \`key\` (a specific call/url/event) to get the FULL caller list across all categories.`,
9109
+ summary
9110
+ };
9111
+ }
7996
9112
  function handleChartServerStatus() {
7997
9113
  const rootDir = process.cwd();
7998
9114
  const lock = getLiveLock(rootDir);
@@ -8036,11 +9152,11 @@ function handleStartChartServer(args) {
8036
9152
  });
8037
9153
  }
8038
9154
  const entryPath = process.argv[1];
8039
- const logDir = (0, import_node_path28.join)((0, import_node_os2.homedir)(), LAUNCHSECURE_DIR);
8040
- (0, import_node_fs24.mkdirSync)(logDir, { recursive: true });
8041
- const logPath = (0, import_node_path28.join)(logDir, "launch-chart.log");
8042
- const out = (0, import_node_fs24.openSync)(logPath, "a");
8043
- const err2 = (0, import_node_fs24.openSync)(logPath, "a");
9155
+ const logDir = (0, import_node_path30.join)((0, import_node_os3.homedir)(), LAUNCHSECURE_DIR);
9156
+ (0, import_node_fs25.mkdirSync)(logDir, { recursive: true });
9157
+ const logPath = (0, import_node_path30.join)(logDir, "launch-chart.log");
9158
+ const out = (0, import_node_fs25.openSync)(logPath, "a");
9159
+ const err2 = (0, import_node_fs25.openSync)(logPath, "a");
8044
9160
  const portArgs = args.port ? ["--port", String(args.port)] : [];
8045
9161
  const child = (0, import_node_child_process2.spawn)(process.execPath, [entryPath, "serve", ...portArgs], {
8046
9162
  detached: true,
@@ -8106,6 +9222,296 @@ function handleRemoveTag(args) {
8106
9222
  removeTag(rootDir, nodeId, key);
8107
9223
  return okJson({ ok: true, node_id: nodeId, removed_key: key });
8108
9224
  }
9225
+ function handleMigrationAudit(args) {
9226
+ const __resolved = resolveOrErr(args);
9227
+ if ("content" in __resolved) return __resolved;
9228
+ const { rootDir } = __resolved;
9229
+ const requireOrphan = args.require_orphan_check !== false;
9230
+ const requireBackup = args.require_sidecar_backup !== false;
9231
+ const requirePreFlight = args.require_pre_flight_notice !== false;
9232
+ const includeSafe = args.include_safe === true;
9233
+ const offset = args.offset ?? 0;
9234
+ const limit = args.limit ?? 10;
9235
+ const db = readGraph(rootDir, "db");
9236
+ if (!db) return err("No db graph found. Run generate_graph first.");
9237
+ const migrations = db.nodes.filter((n) => n.type === "migration");
9238
+ const audit = [];
9239
+ for (const m of migrations) {
9240
+ const isDestructive = m.is_destructive === true;
9241
+ if (!isDestructive && !includeSafe) continue;
9242
+ const violations = [];
9243
+ if (isDestructive) {
9244
+ if (requireOrphan && m.has_orphan_check !== true) violations.push("missing_orphan_check");
9245
+ if (requireBackup && m.has_sidecar_backup !== true) violations.push("missing_sidecar_backup");
9246
+ if (requirePreFlight && m.has_pre_flight_notice !== true) violations.push("missing_pre_flight_notice");
9247
+ }
9248
+ if (!isDestructive && includeSafe && violations.length === 0) {
9249
+ audit.push({ id: m.id, name: m.name, timestamp: m.timestamp, is_destructive: false, status: "safe" });
9250
+ continue;
9251
+ }
9252
+ if (violations.length === 0 && !includeSafe) continue;
9253
+ audit.push({
9254
+ id: m.id,
9255
+ name: m.name,
9256
+ timestamp: m.timestamp,
9257
+ filepath: m.filepath,
9258
+ is_destructive: isDestructive,
9259
+ contains_drop_column: m.contains_drop_column === true,
9260
+ contains_drop_table: m.contains_drop_table === true,
9261
+ contains_backfill: m.contains_backfill === true,
9262
+ has_orphan_check: m.has_orphan_check === true,
9263
+ has_sidecar_backup: m.has_sidecar_backup === true,
9264
+ has_pre_flight_notice: m.has_pre_flight_notice === true,
9265
+ statement_count: m.statement_count,
9266
+ violations,
9267
+ status: violations.length === 0 ? "safe" : "unsafe"
9268
+ });
9269
+ }
9270
+ audit.sort((a, b) => String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? "")));
9271
+ const total = audit.length;
9272
+ const page = audit.slice(offset, offset + limit);
9273
+ const hasMore = offset + page.length < total;
9274
+ return okJson({
9275
+ total,
9276
+ returned: page.length,
9277
+ offset,
9278
+ has_more: hasMore,
9279
+ ...hasMore ? { next_offset: offset + page.length } : {},
9280
+ items: page,
9281
+ discipline: "Layer 1 (expand-and-contract) + Layer 2 (pg_dump wrapper) + Layer 3 (in-migration SQL guards) per CLAUDE.md. This tool checks Layer 3 only."
9282
+ });
9283
+ }
9284
+ function handleDriftReport(args) {
9285
+ const __resolved = resolveOrErr(args);
9286
+ if ("content" in __resolved) return __resolved;
9287
+ const { rootDir } = __resolved;
9288
+ const layerArg = args.layer;
9289
+ const kind = args.kind ?? "all";
9290
+ const confidenceArg = Array.isArray(args.confidence) ? new Set(args.confidence.map((s) => String(s).toLowerCase())) : void 0;
9291
+ const offset = args.offset ?? 0;
9292
+ const limit = args.limit ?? 10;
9293
+ const layers = layerArg ? [layerArg] : getAvailableLayers(rootDir);
9294
+ const items = [];
9295
+ const layersWithAnyFinding = [];
9296
+ for (const layer of layers) {
9297
+ const g = readGraph(rootDir, layer);
9298
+ if (!g) continue;
9299
+ const cs = g.contradictions ?? [];
9300
+ const fes = g.flagged_edges ?? [];
9301
+ if (cs.length > 0 || fes.length > 0) layersWithAnyFinding.push(layer);
9302
+ if (kind === "all" || kind === "contradictions") {
9303
+ for (const c of cs) {
9304
+ items.push({ layer, kind: "contradiction", entity: c.entity, source_a: c.source_a, source_b: c.source_b, detail: c.detail });
9305
+ }
9306
+ }
9307
+ if (kind === "all" || kind === "flagged_edges") {
9308
+ for (const fe of fes) {
9309
+ if (confidenceArg && !confidenceArg.has(String(fe.confidence).toLowerCase())) continue;
9310
+ items.push({
9311
+ layer,
9312
+ kind: "flagged_edge",
9313
+ source: fe.source,
9314
+ target: fe.target,
9315
+ edge_type: fe.type,
9316
+ confidence: fe.confidence,
9317
+ label: fe.label
9318
+ });
9319
+ }
9320
+ }
9321
+ }
9322
+ const total = items.length;
9323
+ const page = items.slice(offset, offset + limit);
9324
+ const hasMore = offset + page.length < total;
9325
+ const parserAbsent = layers.length > 0 && layersWithAnyFinding.length === 0;
9326
+ return okJson({
9327
+ total,
9328
+ returned: page.length,
9329
+ offset,
9330
+ has_more: hasMore,
9331
+ ...hasMore ? { next_offset: offset + page.length } : {},
9332
+ layers_scanned: layers,
9333
+ ...parserAbsent ? { parser_warning: parserWarning(layers, "contradictions + flagged_edges", layers.includes("db") ? "sql-migrations parser and/or cross-layer parsers" : "cross-layer parsers") } : {},
9334
+ items: page
9335
+ });
9336
+ }
9337
+ function handleWhoUses(args) {
9338
+ const __resolved = resolveOrErr(args);
9339
+ if ("content" in __resolved) return __resolved;
9340
+ const { rootDir } = __resolved;
9341
+ const target = args.target;
9342
+ if (!target) return err("target is required");
9343
+ const filterType = args.cross_ref_type;
9344
+ const offset = args.offset ?? 0;
9345
+ const limit = args.limit ?? 10;
9346
+ const hits = [];
9347
+ const layersScanned = [];
9348
+ const layersWithCrossRefs = [];
9349
+ for (const layer of getAvailableLayers(rootDir)) {
9350
+ const g = readGraph(rootDir, layer);
9351
+ if (!g) continue;
9352
+ layersScanned.push(layer);
9353
+ const crs = g.cross_refs ?? [];
9354
+ if (crs.length > 0) layersWithCrossRefs.push(layer);
9355
+ for (const cr of crs) {
9356
+ if (cr.target !== target) continue;
9357
+ if (filterType && cr.type !== filterType) continue;
9358
+ const entry = {
9359
+ source_layer: layer,
9360
+ source: cr.source,
9361
+ cross_ref_type: cr.type,
9362
+ target_layer: cr.layer
9363
+ };
9364
+ const via = cr.via;
9365
+ if (Array.isArray(via)) entry.via = via;
9366
+ hits.push(entry);
9367
+ }
9368
+ }
9369
+ hits.sort((a, b) => String(a.source).localeCompare(String(b.source)));
9370
+ const total = hits.length;
9371
+ const page = hits.slice(offset, offset + limit);
9372
+ const hasMore = offset + page.length < total;
9373
+ const parserAbsent = layersScanned.length > 0 && layersWithCrossRefs.length === 0;
9374
+ return okJson({
9375
+ target,
9376
+ ...filterType ? { cross_ref_type: filterType } : {},
9377
+ total,
9378
+ returned: page.length,
9379
+ offset,
9380
+ has_more: hasMore,
9381
+ ...hasMore ? { next_offset: offset + page.length } : {},
9382
+ ...parserAbsent ? { parser_warning: parserWarning(layersScanned, "cross_refs", "cross-layer references parser(s) (fetch-resolver / api-annotations / url-literal-scanner / static-ref-scanner)") } : {},
9383
+ items: page
9384
+ });
9385
+ }
9386
+ function handleTracePath(args) {
9387
+ const __resolved = resolveOrErr(args);
9388
+ if ("content" in __resolved) return __resolved;
9389
+ const { rootDir } = __resolved;
9390
+ const from = args.from;
9391
+ const to = args.to;
9392
+ if (!from || !to) return err("from and to are required");
9393
+ const maxHops = args.max_hops ?? 5;
9394
+ const maxPaths = args.max_paths ?? 10;
9395
+ const offset = args.offset ?? 0;
9396
+ const limit = args.limit ?? 10;
9397
+ const outgoing = /* @__PURE__ */ new Map();
9398
+ const layersScanned = [];
9399
+ const layersWithCrossRefs = [];
9400
+ for (const layer of getAvailableLayers(rootDir)) {
9401
+ const g = readGraph(rootDir, layer);
9402
+ if (!g) continue;
9403
+ layersScanned.push(layer);
9404
+ if ((g.cross_refs ?? []).length > 0) layersWithCrossRefs.push(layer);
9405
+ for (const cr of g.cross_refs ?? []) {
9406
+ const via = cr.via;
9407
+ if (Array.isArray(via) && via.length > 0) {
9408
+ let prev = cr.source;
9409
+ for (const step of via) {
9410
+ pushEdge(outgoing, { from: prev, to: step, type: cr.type, layer: cr.layer });
9411
+ prev = step;
9412
+ }
9413
+ pushEdge(outgoing, { from: prev, to: cr.target, type: cr.type, layer: cr.layer });
9414
+ } else {
9415
+ pushEdge(outgoing, { from: cr.source, to: cr.target, type: cr.type, layer: cr.layer });
9416
+ }
9417
+ }
9418
+ for (const e of g.edges ?? []) {
9419
+ pushEdge(outgoing, { from: e.source, to: e.target, type: e.type, layer });
9420
+ }
9421
+ }
9422
+ const paths = [];
9423
+ const queue = [{ nodes: [from], edges: [] }];
9424
+ while (queue.length > 0 && paths.length < maxPaths) {
9425
+ const p = queue.shift();
9426
+ const tail = p.nodes[p.nodes.length - 1];
9427
+ if (tail === to && p.nodes.length > 1) {
9428
+ paths.push(p);
9429
+ continue;
9430
+ }
9431
+ if (p.nodes.length > maxHops) continue;
9432
+ const next = outgoing.get(tail) ?? [];
9433
+ for (const e of next) {
9434
+ if (p.nodes.includes(e.to)) continue;
9435
+ queue.push({ nodes: [...p.nodes, e.to], edges: [...p.edges, e] });
9436
+ }
9437
+ }
9438
+ paths.sort((a, b) => a.nodes.length - b.nodes.length);
9439
+ const total = paths.length;
9440
+ const page = paths.slice(offset, offset + limit);
9441
+ const hasMore = offset + page.length < total;
9442
+ const parserAbsent = layersScanned.length > 0 && layersWithCrossRefs.length === 0;
9443
+ return okJson({
9444
+ from,
9445
+ to,
9446
+ max_hops: maxHops,
9447
+ max_paths: maxPaths,
9448
+ total,
9449
+ returned: page.length,
9450
+ offset,
9451
+ has_more: hasMore,
9452
+ ...hasMore ? { next_offset: offset + page.length } : {},
9453
+ ...total === maxPaths ? { search_capped: true, hint: "Hit max_paths cap \u2014 more paths may exist. Raise max_paths or narrow from/to." } : {},
9454
+ ...parserAbsent ? { parser_warning: parserWarning(layersScanned, "cross_refs", "cross-layer references parser(s)") + " Cross-layer paths are unreachable without cross_refs; only same-layer paths (imports/renders/belongs_to) are visible." } : {},
9455
+ paths: page.map((p) => ({ hops: p.nodes.length - 1, nodes: p.nodes, edges: p.edges }))
9456
+ });
9457
+ }
9458
+ function pushEdge(map, e) {
9459
+ if (!map.has(e.from)) map.set(e.from, []);
9460
+ map.get(e.from).push(e);
9461
+ }
9462
+ function handleAuthCoverageReport(args) {
9463
+ const __resolved = resolveOrErr(args);
9464
+ if ("content" in __resolved) return __resolved;
9465
+ const { rootDir } = __resolved;
9466
+ const moduleFilter = args.module;
9467
+ const strategyFilter = args.strategy;
9468
+ const offset = args.offset ?? 0;
9469
+ const limit = args.limit ?? 10;
9470
+ const api = readGraph(rootDir, "api");
9471
+ if (!api) return err("No api graph found. Run generate_graph first.");
9472
+ const byStrategy = {};
9473
+ const byModule = {};
9474
+ const unauthenticated = [];
9475
+ const matchedEndpoints = [];
9476
+ for (const n of api.nodes) {
9477
+ const tags = n.tags;
9478
+ const moduleTag = tags?.module ?? "root";
9479
+ const auth = n.auth ?? [];
9480
+ const strategyKey = auth.length === 0 ? "none" : auth.slice().sort().join("+");
9481
+ byStrategy[strategyKey] = (byStrategy[strategyKey] ?? 0) + 1;
9482
+ if (!byModule[moduleTag]) byModule[moduleTag] = { total: 0, by_strategy: {} };
9483
+ byModule[moduleTag].total += 1;
9484
+ byModule[moduleTag].by_strategy[strategyKey] = (byModule[moduleTag].by_strategy[strategyKey] ?? 0) + 1;
9485
+ if (auth.length === 0) unauthenticated.push(n.id);
9486
+ if (moduleFilter && moduleTag !== moduleFilter) continue;
9487
+ if (strategyFilter && strategyKey !== strategyFilter) continue;
9488
+ matchedEndpoints.push({
9489
+ id: n.id,
9490
+ path: n.path,
9491
+ methods: n.methods,
9492
+ module: moduleTag,
9493
+ auth,
9494
+ mutates: n.mutates === true
9495
+ });
9496
+ }
9497
+ matchedEndpoints.sort((a, b) => String(a.path ?? a.id).localeCompare(String(b.path ?? b.id)));
9498
+ const total = matchedEndpoints.length;
9499
+ const page = matchedEndpoints.slice(offset, offset + limit);
9500
+ const hasMore = offset + page.length < total;
9501
+ return okJson({
9502
+ total_endpoints: api.nodes.length,
9503
+ by_strategy: byStrategy,
9504
+ unauthenticated_count: unauthenticated.length,
9505
+ by_module: byModule,
9506
+ filter: { module: moduleFilter, strategy: strategyFilter },
9507
+ total,
9508
+ returned: page.length,
9509
+ offset,
9510
+ has_more: hasMore,
9511
+ ...hasMore ? { next_offset: offset + page.length } : {},
9512
+ items: page
9513
+ });
9514
+ }
8109
9515
  function handleAuditLayer(args) {
8110
9516
  const __resolved = resolveOrErr(args);
8111
9517
  if ("content" in __resolved) return __resolved;
@@ -8169,20 +9575,20 @@ function handleDetectProjectStack() {
8169
9575
  if (ref.type === "references_api") stats.references_api++;
8170
9576
  }
8171
9577
  }
8172
- const srcDir = (0, import_node_path28.join)(rootDir, "src");
8173
- if ((0, import_node_fs24.existsSync)(srcDir)) {
9578
+ const srcDir = (0, import_node_path30.join)(rootDir, "src");
9579
+ if ((0, import_node_fs25.existsSync)(srcDir)) {
8174
9580
  const scanDir = (dir) => {
8175
- if (!(0, import_node_fs24.existsSync)(dir)) return;
8176
- for (const entry of (0, import_node_fs24.readdirSync)(dir, { withFileTypes: true })) {
9581
+ if (!(0, import_node_fs25.existsSync)(dir)) return;
9582
+ for (const entry of (0, import_node_fs25.readdirSync)(dir, { withFileTypes: true })) {
8177
9583
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
8178
- const full = (0, import_node_path28.join)(dir, entry.name);
9584
+ const full = (0, import_node_path30.join)(dir, entry.name);
8179
9585
  if (entry.isDirectory()) {
8180
9586
  scanDir(full);
8181
9587
  continue;
8182
9588
  }
8183
- if (![".ts", ".tsx"].includes((0, import_node_path28.extname)(entry.name))) continue;
9589
+ if (![".ts", ".tsx"].includes((0, import_node_path30.extname)(entry.name))) continue;
8184
9590
  try {
8185
- const content = (0, import_node_fs24.readFileSync)(full, "utf-8");
9591
+ const content = (0, import_node_fs25.readFileSync)(full, "utf-8");
8186
9592
  const matches = content.match(/@api\s+(GET|POST|PUT|DELETE|PATCH)\s+\/\S+/g);
8187
9593
  if (matches) stats.annotations += matches.length;
8188
9594
  } catch {
@@ -8201,7 +9607,7 @@ function handleDetectProjectStack() {
8201
9607
  name: p.name,
8202
9608
  root: p.root,
8203
9609
  absolute_root: p.absoluteRoot,
8204
- has_graph: (0, import_node_fs24.existsSync)((0, import_node_path28.join)(p.absoluteRoot, LAUNCHSECURE_DIR, "graphs"))
9610
+ has_graph: (0, import_node_fs25.existsSync)((0, import_node_path30.join)(p.absoluteRoot, LAUNCHSECURE_DIR, "graphs"))
8205
9611
  }));
8206
9612
  return okJson({
8207
9613
  languages,
@@ -8315,6 +9721,30 @@ async function handleMessage(msg) {
8315
9721
  respond(id ?? null, withFreshnessMeta(handleBlastPoints(args), args));
8316
9722
  return;
8317
9723
  }
9724
+ if (toolName === "list_notes") {
9725
+ respond(id ?? null, withFreshnessMeta(handleListNotes(args), args));
9726
+ return;
9727
+ }
9728
+ if (toolName === "migration_audit") {
9729
+ respond(id ?? null, withFreshnessMeta(handleMigrationAudit(args), args));
9730
+ return;
9731
+ }
9732
+ if (toolName === "drift_report") {
9733
+ respond(id ?? null, withFreshnessMeta(handleDriftReport(args), args));
9734
+ return;
9735
+ }
9736
+ if (toolName === "who_uses") {
9737
+ respond(id ?? null, withFreshnessMeta(handleWhoUses(args), args));
9738
+ return;
9739
+ }
9740
+ if (toolName === "trace_path") {
9741
+ respond(id ?? null, withFreshnessMeta(handleTracePath(args), args));
9742
+ return;
9743
+ }
9744
+ if (toolName === "auth_coverage_report") {
9745
+ respond(id ?? null, withFreshnessMeta(handleAuthCoverageReport(args), args));
9746
+ return;
9747
+ }
8318
9748
  respondError(id ?? null, -32601, `Unknown tool: ${toolName}`);
8319
9749
  return;
8320
9750
  }
@@ -8375,14 +9805,14 @@ function startGraphMcpServer() {
8375
9805
  process.stderr.write(`[launchsecure-graph] MCP server started (cwd: ${process.cwd()})
8376
9806
  `);
8377
9807
  }
8378
- var import_node_fs24, import_node_path28, import_node_child_process2, import_node_os2, SERVER_INFO, TOOLS, COMPACT_SCHEMA, COMPACT_NODE_KNOWN_KEYS, DEEP_FIELDS, EST_CHARS_PER_NODE_FULL, EST_CHARS_PER_NODE_MIN, EST_CHARS_PER_EDGE, DEFAULT_EST_NODE_FULL, DEFAULT_EST_NODE_MIN, DEFAULT_EST_EDGE, NEIGHBORHOOD_BUDGET_CHARS, MAX_FILTER_EDGES, BATCH_BUDGET_CHARS, watcherHandle;
9808
+ var import_node_fs25, import_node_path30, import_node_child_process2, import_node_os3, SERVER_INFO, TOOLS, MINIMAL_STRIP_FIELDS, COMPACT_SCHEMA, COMPACT_NODE_KNOWN_KEYS, DEEP_FIELDS, NOTE_KIND_CATEGORY, EST_CHARS_PER_NODE_FULL, EST_CHARS_PER_NODE_MIN, EST_CHARS_PER_EDGE, DEFAULT_EST_NODE_FULL, DEFAULT_EST_NODE_MIN, DEFAULT_EST_EDGE, NEIGHBORHOOD_BUDGET_CHARS, MAX_FILTER_EDGES, BATCH_BUDGET_CHARS, watcherHandle;
8379
9809
  var init_graph_mcp = __esm({
8380
9810
  "src/server/graph-mcp.ts"() {
8381
9811
  "use strict";
8382
- import_node_fs24 = require("node:fs");
8383
- import_node_path28 = require("node:path");
9812
+ import_node_fs25 = require("node:fs");
9813
+ import_node_path30 = require("node:path");
8384
9814
  import_node_child_process2 = require("node:child_process");
8385
- import_node_os2 = require("node:os");
9815
+ import_node_os3 = require("node:os");
8386
9816
  init_launch_kit_paths();
8387
9817
  init_graph();
8388
9818
  init_lockfile();
@@ -8410,13 +9840,21 @@ var init_graph_mcp = __esm({
8410
9840
  project: {
8411
9841
  type: "string",
8412
9842
  description: PROJECT_PARAM_DESCRIPTION + " Special: omit to regenerate ALL configured projects."
9843
+ },
9844
+ worktree: {
9845
+ type: "string",
9846
+ description: WORKTREE_PARAM_DESCRIPTION2
9847
+ },
9848
+ project_root: {
9849
+ type: "string",
9850
+ description: PROJECT_ROOT_PARAM_DESCRIPTION2
8413
9851
  }
8414
9852
  }
8415
9853
  }
8416
9854
  },
8417
9855
  {
8418
9856
  name: "read_graph",
8419
- description: 'Query the structural project graph \u2014 use INSTEAD of Glob and Grep for locating files, understanding structure, and navigating the codebase. Faster and more accurate than file-system search because it returns typed nodes with metadata and relationships. \n\nUSE THIS FOR: "where is X", "what files are in module Y", "what pages exist under /admin", "what components does Z render", "what tables relate to User", "list all hooks in auth module", "which endpoints touch the User table", "what auth strategy does this endpoint use". \n\nDO NOT USE FOR: understanding what\'s INSIDE a component (use inspect_node for elements, conditions, state, variables, responses), reading actual source code (use Read). \n\nQUERY PARAMS (at least one required for node data \u2014 unfiltered calls return summary only to stay in context):\n- search: substring match on node id, name, or route\n- type: filter by node type (ui layer: page, layout, component, ui, hook, context, config, util; api layer: endpoint; db layer: table, enum)\n- module: filter by module tag (computed from directory structure, e.g. "auth", "admin", "settings")\n- node_id: return this node + its neighborhood (incoming+outgoing edges within `hops`)\n- hops: neighborhood radius when node_id is set (default 1)\n- minimal: return only id/type/name/module/route per node (skip heavy fields like columns, exports)\n- include_edges: return the actual edge list. Default: TRUE for neighborhood queries (node_id), FALSE for filter queries (search/type/module). Filter responses always include `edge_count`; only pass include_edges:true when you actually need to inspect individual edges (e.g. "which components render X"). This default cuts typical filter responses in half.\n\nBATCH MODE: pass `queries` (array of query objects) to run multiple independent queries in a single call. Each query object uses the same params (layer/search/type/module/node_id/hops/minimal). Returns { batch: true, count, results: [{index, query, result}, ...] }. Use this when you need multiple graph views up-front (e.g. scoping a feature across ui+api+db layers) to save round-trips. When batch mode is used, top-level params are ignored.\n\nReturns: filtered nodes + edges between them. If no filter given, returns per-layer counts and type breakdown only.\n\nWIRE FORMAT (compact): responses that include nodes/edges use short keys and edge-by-index refs to cut payload ~40-60%. Every such response carries a `_schema` legend. Quick reference:\n nodes[]: { i: id, t: type, n: name, m: module, r: route, mt: methods, x: exports, c: columns }\n edges[]: { s: source_node_index, d: target_node_index, t: type, l: label }\nedges.s / edges.d are 0-based indices into THIS response\'s nodes array. If a referenced node is not in the response (boundary case), s/d may instead contain the full node id string \u2014 always check the type.\n\nPAGINATION (filter queries):\n- Use `offset` and `limit` to paginate through large result sets.\n- Response includes: `total` (matched), `returned` (in this page), `has_more`, `next_offset`.\n- If `has_more: true`, call again with `offset: next_offset` to get the next page.\n\nBUDGET GUARDS:\n- Neighborhood queries stop expanding when the projected response exceeds budget. The response then contains `budget_exceeded: true` plus `hops_traversed < hops_requested`. When this happens, drill into a specific neighbor with another node_id call rather than retrying with larger hops \u2014 it will just truncate again.\n- Batch mode caps total response size. Once the budget is hit, later queries return `{skipped: true, reason: "batch_budget_exhausted"}` and you must re-run them individually.\n\nMONOREPOS: pass `project: "<name>"` to query a sub-project graph (defined in .launchchart.json projects[]). Omitting `project` targets the monorepo root. In batch mode the top-level `project` is inherited by sub-queries that do not set their own. Run detect_project_stack to list configured projects.',
9857
+ description: 'Query the structural project graph \u2014 use INSTEAD of Glob and Grep for locating files, understanding structure, and navigating the codebase. Faster and more accurate than file-system search because it returns typed nodes with metadata and relationships. \n\nUSE THIS FOR: "where is X", "what files are in module Y", "what pages exist under /admin", "what components does Z render", "what tables relate to User", "list all hooks in auth module", "which endpoints touch the User table", "what auth strategy does this endpoint use". \n\nDO NOT USE FOR: understanding what\'s INSIDE a component (use inspect_node for elements, conditions, state, variables, responses), reading actual source code (use Read). \n\nQUERY PARAMS (at least one required for node data \u2014 unfiltered calls return summary only to stay in context):\n- search: substring match on node id, name, route, JSX element text/string-prop literals, ui_labels (strings from `const X = [{label:\'\u2026\'}]` data arrays), and notes (tagged comments). Lets queries like search:"Briefs" find a page where "Briefs" is a tab label inside an array, or search:"FIXME" find every file with a FIXME comment. For exhaustive note listings use list_notes.\n- type: filter by node type (ui layer: page, layout, component, ui, hook, context, config, util; api layer: endpoint; db layer: table, enum, migration). Migration nodes carry safety attributes \u2014 is_destructive, has_orphan_check, has_sidecar_backup, has_pre_flight_notice, contains_backfill, contains_drop_column, contains_drop_table, statement_count, timestamp \u2014 queryable via tag_key filters or by inspecting returned node fields.\n- include_findings: db layer only. When true, response includes `contradictions` and `flagged_edges` arrays surfacing SQL\u2194ORM schema drift detected by the SQL migrations parser. Use this to audit "schema vs migrations" disagreement (missing columns, type mismatches, nullability drift, FKs declared in SQL but not ORM).\n- module: filter by module tag (computed from directory structure, e.g. "auth", "admin", "settings")\n- node_id: return this node + its neighborhood (incoming+outgoing edges within `hops`)\n- hops: neighborhood radius when node_id is set (default 1)\n- minimal: return only id/type/name/module/route per node (skip heavy fields like columns, exports)\n- include_edges: return the actual edge list. Default: TRUE for neighborhood queries (node_id), FALSE for filter queries (search/type/module). Filter responses always include `edge_count`; only pass include_edges:true when you actually need to inspect individual edges (e.g. "which components render X"). This default cuts typical filter responses in half.\n\nBATCH MODE: pass `queries` (array of query objects) to run multiple independent queries in a single call. Each query object uses the same params (layer/search/type/module/node_id/hops/minimal). Returns { batch: true, count, results: [{index, query, result}, ...] }. Use this when you need multiple graph views up-front (e.g. scoping a feature across ui+api+db layers) to save round-trips. When batch mode is used, top-level params are ignored.\n\nReturns: filtered nodes + edges between them. If no filter given, returns per-layer counts and type breakdown only.\n\nWIRE FORMAT (compact): responses that include nodes/edges use short keys and edge-by-index refs to cut payload ~40-60%. Every such response carries a `_schema` legend. Quick reference:\n nodes[]: { i: id, t: type, n: name, m: module, r: route, mt: methods, x: exports, c: columns }\n edges[]: { s: source_node_index, d: target_node_index, t: type, l: label }\nedges.s / edges.d are 0-based indices into THIS response\'s nodes array. If a referenced node is not in the response (boundary case), s/d may instead contain the full node id string \u2014 always check the type.\n\nPAGINATION (filter queries):\n- Use `offset` and `limit` to paginate through large result sets.\n- Response includes: `total` (matched), `returned` (in this page), `has_more`, `next_offset`.\n- If `has_more: true`, call again with `offset: next_offset` to get the next page.\n\nBUDGET GUARDS:\n- Neighborhood queries stop expanding when the projected response exceeds budget. The response then contains `budget_exceeded: true` plus `hops_traversed < hops_requested`. When this happens, drill into a specific neighbor with another node_id call rather than retrying with larger hops \u2014 it will just truncate again.\n- Batch mode caps total response size. Once the budget is hit, later queries return `{skipped: true, reason: "batch_budget_exhausted"}` and you must re-run them individually.\n\nMONOREPOS: pass `project: "<name>"` to query a sub-project graph (defined in .launchchart.json projects[]). Omitting `project` targets the monorepo root. In batch mode the top-level `project` is inherited by sub-queries that do not set their own. Run detect_project_stack to list configured projects.',
8420
9858
  inputSchema: {
8421
9859
  type: "object",
8422
9860
  properties: {
@@ -8426,7 +9864,7 @@ var init_graph_mcp = __esm({
8426
9864
  },
8427
9865
  search: {
8428
9866
  type: "string",
8429
- description: "Case-insensitive substring match against node id, name, or route."
9867
+ description: "Case-insensitive substring match against node id, name, route, JSX element text/string-prop literals, and ui_labels (strings from `const X = [{label:'\u2026'}]` data arrays)."
8430
9868
  },
8431
9869
  type: {
8432
9870
  type: "string",
@@ -8460,6 +9898,36 @@ var init_graph_mcp = __esm({
8460
9898
  type: "boolean",
8461
9899
  description: "Include the edge list in the response. Default TRUE for neighborhood queries (node_id), FALSE for filter queries. Filter responses always include edge_count. Only set true on filter queries when you actually need edge data."
8462
9900
  },
9901
+ include_findings: {
9902
+ type: "boolean",
9903
+ description: "DB layer only. When true, response includes `contradictions[]` and `flagged_edges[]` arrays from the SQL migrations parser \u2014 SQL\u2194ORM schema drift findings (missing columns/tables, type mismatches, nullability drift, FKs in SQL but no ORM @relation). Default false."
9904
+ },
9905
+ tag_predicates: {
9906
+ type: "array",
9907
+ description: 'Multi-condition AND filter over node attributes. Each predicate is { field, op, value }. `field` matches either a top-level node attribute (e.g. is_destructive, mutates, statement_count) OR a tag key (tag values take precedence when both exist). `op` \u2208 {eq, ne, gt, lt, gte, lte, exists, not_exists, in}. Example: [{field:"is_destructive",op:"eq",value:true},{field:"has_orphan_check",op:"eq",value:false}] returns destructive migrations missing an orphan check.',
9908
+ items: {
9909
+ type: "object",
9910
+ properties: {
9911
+ field: { type: "string", description: "Node attribute or tag key." },
9912
+ op: { type: "string", enum: ["eq", "ne", "gt", "lt", "gte", "lte", "exists", "not_exists", "in"] },
9913
+ value: { description: "Comparison value. Required for all ops except exists/not_exists. Pass an array for `in`." }
9914
+ },
9915
+ required: ["field", "op"]
9916
+ }
9917
+ },
9918
+ include_cross_refs: {
9919
+ type: "boolean",
9920
+ description: "When true, attach `cross_refs` (incoming + outgoing) to each returned node. Unlocks the cross-layer reference data (references_static, calls_api, reads_via, mutates_via, reads, mutates, references_api) that is otherwise dropped from responses. Combine with `cross_ref_type` to narrow."
9921
+ },
9922
+ cross_ref_type: {
9923
+ type: "string",
9924
+ description: "Narrow attached cross_refs by kind. Common values: calls_api, references_static, reads_via, mutates_via, reads, mutates, references_api. Only applies when include_cross_refs is true."
9925
+ },
9926
+ edge_confidence: {
9927
+ type: "array",
9928
+ description: 'Filter to surface low/medium/high-confidence flagged_edges in the response. Pass e.g. ["medium","high"] to get DYNAMIC navigation targets, FK drift, etc. Scoped to matched nodes when a node filter is in play; otherwise returns all flagged_edges at the requested confidence levels. Layer-independent.',
9929
+ items: { type: "string", enum: ["low", "medium", "high"] }
9930
+ },
8463
9931
  offset: {
8464
9932
  type: "number",
8465
9933
  description: "Skip first N matched nodes (pagination). Default 0. Use next_offset from a previous response to get the next page."
@@ -8470,7 +9938,7 @@ var init_graph_mcp = __esm({
8470
9938
  },
8471
9939
  queries: {
8472
9940
  type: "array",
8473
- description: "Batch mode \u2014 array of query objects to run in a single call. Each uses the same param schema (including `project`). When set, top-level params are ignored. Subject to an aggregate size budget \u2014 later queries may return a skipped stub.",
9941
+ description: "Batch mode \u2014 array of query objects to run in a single call. Each uses the same param schema (including `project` / `worktree` / `project_root`). Top-level root args are inherited by sub-queries that don't specify their own. Subject to an aggregate size budget \u2014 later queries may return a skipped stub.",
8474
9942
  items: {
8475
9943
  type: "object",
8476
9944
  properties: {
@@ -8482,13 +9950,28 @@ var init_graph_mcp = __esm({
8482
9950
  hops: { type: "number" },
8483
9951
  minimal: { type: "boolean" },
8484
9952
  include_edges: { type: "boolean" },
8485
- project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
9953
+ include_findings: { type: "boolean" },
9954
+ tag_predicates: { type: "array" },
9955
+ include_cross_refs: { type: "boolean" },
9956
+ cross_ref_type: { type: "string" },
9957
+ edge_confidence: { type: "array" },
9958
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
9959
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
9960
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
8486
9961
  }
8487
9962
  }
8488
9963
  },
8489
9964
  project: {
8490
9965
  type: "string",
8491
9966
  description: PROJECT_PARAM_DESCRIPTION
9967
+ },
9968
+ worktree: {
9969
+ type: "string",
9970
+ description: WORKTREE_PARAM_DESCRIPTION2
9971
+ },
9972
+ project_root: {
9973
+ type: "string",
9974
+ description: PROJECT_ROOT_PARAM_DESCRIPTION2
8492
9975
  }
8493
9976
  }
8494
9977
  }
@@ -8536,7 +10019,9 @@ Returns: { pattern, filter, files_searched, total_matches, matches: [{file, line
8536
10019
  context: { type: "number", description: "Context lines around each match. Default 2." },
8537
10020
  max_matches: { type: "number", description: "Max matches to return total. Default 50." },
8538
10021
  max_files: { type: "number", description: "Max files to search. Default 50." },
8539
- project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
10022
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10023
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10024
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
8540
10025
  },
8541
10026
  required: ["layer", "pattern"]
8542
10027
  }
@@ -8568,17 +10053,23 @@ Returns deep fields only \u2014 not structural metadata (use read_graph for that
8568
10053
  fields: {
8569
10054
  type: "array",
8570
10055
  items: { type: "string" },
8571
- description: 'Specific deep fields to return. Options: elements, stateVars, conditions, variables, responses, params, effects. Omit for all. "effects" returns the file-level side-effect summary (calls, dom_writes, subscribes, timers, persists, fetches, globals); per-arrow-function effects are also attached to each entry in "variables".'
10056
+ description: `Specific deep fields to return. Options: elements, stateVars, conditions, variables, responses, params, effects, crossRefs. Omit for all. "effects" returns the file-level side-effect summary (calls, dom_writes, subscribes, timers, persists, fetches, globals); per-arrow-function effects are also attached to each entry in "variables". "crossRefs" returns this node's incoming + outgoing cross-layer references (calls_api, references_static, reads_via, mutates_via, reads, mutates, references_api) \u2014 the cross-layer linking data otherwise dropped from responses.`
8572
10057
  },
8573
10058
  filter: {
8574
10059
  type: "string",
8575
10060
  description: "Regex pattern to search WITHIN deep field values. Only returns nodes where at least one deep field matches. Searches across all string values in the requested fields (condition tests, variable inits, element tags/props, response bodies, etc.). When set, search becomes optional and node limit is raised to 50."
8576
10061
  },
10062
+ cross_ref_type: {
10063
+ type: "string",
10064
+ description: 'Narrow returned crossRefs by kind (e.g. calls_api, references_static, reads_via). Only applies when "crossRefs" is included in fields (or fields is omitted).'
10065
+ },
8577
10066
  case_insensitive: {
8578
10067
  type: "boolean",
8579
10068
  description: "Case-insensitive filter matching. Default true."
8580
10069
  },
8581
- project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
10070
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10071
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10072
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
8582
10073
  },
8583
10074
  required: ["layer"]
8584
10075
  }
@@ -8616,20 +10107,111 @@ Use this when the user asks "is the chart running", "show me the project graph U
8616
10107
  },
8617
10108
  {
8618
10109
  name: "effects_index",
8619
- description: 'Cross-layer inverted index over per-node side effects. Answers "who else touches X?" without re-walking every node. Built automatically when generate_graph runs (or whenever the watcher regenerates). \n\nUSE THIS FOR: "is mountFoo safe to instantiate twice?" (kind="singleton_risks"), "who writes DOM id moon-shadow-blur?" (kind="dom_ids", key="moon-shadow-blur"), "which files attach a window keydown listener?" (kind="window_events", key="window:keydown"), "who else writes localStorage key panchang.settings.v1?" (kind="storage_keys", key="..."), "any DOM-id collisions?" (kind="collisions"). \n\nReturns: { kind, results } where results is a {key: [nodeIds]} map for the chosen kind, or a list of multi-writer collisions when kind="collisions", or a flat node-id list when kind="singleton_risks".',
10110
+ description: 'Cross-layer inverted index over per-node side effects. Answers "who else touches X?" without re-walking every node. Most kinds load from a precomputed effects-index.json (built when generate_graph runs); fetch_calls and api_effects are computed on demand from the ui/api graphs so they always reflect current state.\n\nUSE THIS FOR: "is mountFoo safe to instantiate twice?" (kind="singleton_risks"), "who writes DOM id moon-shadow-blur?" (kind="dom_ids", key="moon-shadow-blur"), "which files attach a window keydown listener?" (kind="window_events", key="window:keydown"), "who else writes localStorage key panchang.settings.v1?" (kind="storage_keys", key="..."), "any DOM-id collisions?" (kind="collisions"), "who calls /api/work-items?" (kind="fetch_calls", key="app/api/.../work-items/route.ts"), "which endpoints call db.user.findUnique?" (kind="api_effects", key="db.user.findUnique"). \n\nReturns vary by kind: {key:[nodeIds]} map for standard kinds; {dom_ids, storage_keys, window_events} for collisions; flat node-id list for singleton_risks; {api_endpoint|url_template: [ui_files]} for fetch_calls; {category: {item: [endpoint_ids]}} summary for api_effects (or {category, key, hits} when key is set).',
8620
10111
  inputSchema: {
8621
10112
  type: "object",
8622
10113
  properties: {
8623
10114
  kind: {
8624
10115
  type: "string",
8625
- enum: ["dom_ids", "window_events", "storage_keys", "fetch_urls", "timers", "singleton_risks", "collisions"],
8626
- description: "Which inverted index to query. Default: collisions (most actionable signal)."
10116
+ enum: ["dom_ids", "window_events", "storage_keys", "fetch_urls", "timers", "singleton_risks", "collisions", "fetch_calls", "api_effects"],
10117
+ description: 'Which inverted index to query. Default: collisions (most actionable signal). fetch_calls = "UI\u2192API call inventory" (resolved via ui cross_refs + raw fetch strings). api_effects = "what does each endpoint touch" inverted across all effect categories (calls / fetches / persists / subscribes / timers / dom_writes / globals).'
8627
10118
  },
8628
10119
  key: {
8629
10120
  type: "string",
8630
- description: 'Optional specific key to look up within the chosen kind (e.g. "moon-shadow-blur"). When omitted, returns the full {key:nodes} map for the kind.'
10121
+ description: 'Optional specific key to look up within the chosen kind (e.g. "moon-shadow-blur" for dom_ids, "/api/work-items" for fetch_calls, "db.user.findUnique" for api_effects). When omitted, returns the full {key:nodes} map (or per-category summary for api_effects).'
8631
10122
  },
8632
- project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
10123
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10124
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10125
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
10126
+ }
10127
+ }
10128
+ },
10129
+ {
10130
+ name: "migration_audit",
10131
+ description: `List Prisma migrations that violate the migration-safety discipline: destructive migrations missing one or more guards (orphan check, sidecar backup, pre-flight notice). Codifies the three-layer migration safety rules \u2014 derived from each migration node's extracted attributes (is_destructive, has_orphan_check, has_sidecar_backup, has_pre_flight_notice, contains_backfill, contains_drop_column, contains_drop_table).
10132
+
10133
+ USE THIS FOR: "are there unsafe migrations on this branch", "audit the migration history before deploy", "find migrations that drop columns without backup". Paginated. Returns one entry per offending migration with the specific missing guards listed.`,
10134
+ inputSchema: {
10135
+ type: "object",
10136
+ properties: {
10137
+ require_orphan_check: { type: "boolean", description: "Flag destructive migrations missing an orphan-count guard. Default true." },
10138
+ require_sidecar_backup: { type: "boolean", description: "Flag destructive migrations missing a sidecar backup table. Default true." },
10139
+ require_pre_flight_notice: { type: "boolean", description: "Flag destructive migrations missing a RAISE NOTICE pre-flight. Default true." },
10140
+ include_safe: { type: "boolean", description: "Also return migrations that pass all checks (no violations). Default false \u2014 by default only offenders are returned." },
10141
+ offset: { type: "number", description: "Pagination offset. Default 0." },
10142
+ limit: { type: "number", description: "Max migrations to return. Default 10." },
10143
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10144
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10145
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
10146
+ }
10147
+ }
10148
+ },
10149
+ {
10150
+ name: "drift_report",
10151
+ description: 'Aggregate cross-layer drift findings \u2014 contradictions (schema disagreements) + flagged_edges (low-confidence resolutions, FK drift, dynamic routes) \u2014 into one report. Generalizes the db-only `read_graph include_findings` flag to all layers.\n\nUSE THIS FOR: "is the codebase drifting", "show me SQL vs ORM mismatches", "list endpoints/components with unresolved routes", "audit schema health before refactor". Paginated. Returns items with { layer, kind, source, target, detail, confidence }.',
10152
+ inputSchema: {
10153
+ type: "object",
10154
+ properties: {
10155
+ layer: { type: "string", description: "Restrict to one layer (e.g. db, ui). Omit for all layers." },
10156
+ kind: { type: "string", enum: ["contradictions", "flagged_edges", "all"], description: "Which finding type to return. Default all." },
10157
+ confidence: { type: "array", items: { type: "string", enum: ["low", "medium", "high"] }, description: "For flagged_edges: filter by confidence levels. Default all." },
10158
+ offset: { type: "number", description: "Pagination offset. Default 0." },
10159
+ limit: { type: "number", description: "Max items to return. Default 10." },
10160
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10161
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10162
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
10163
+ }
10164
+ }
10165
+ },
10166
+ {
10167
+ name: "who_uses",
10168
+ description: 'List everything that references a target node via cross_refs \u2014 the cross-layer references_static / calls_api / reads_via / mutates_via / reads / mutates / references_api edges that are otherwise dropped from responses.\n\nUSE THIS FOR: "where is permission `manage_billing` used", "which UI components reference enum `WorkItemPriority`", "which endpoints touch the User table", "which files call /api/work-items". Auto-scans all layers\' cross_refs for incoming edges to the target. Paginated.',
10169
+ inputSchema: {
10170
+ type: "object",
10171
+ properties: {
10172
+ target: { type: "string", description: 'Target node id to look up (e.g. "seed:permission:manage_billing", "User", "app/api/.../work-items/route.ts", "WorkItemPriority").' },
10173
+ cross_ref_type: { type: "string", description: "Narrow by cross-ref kind (calls_api / references_static / reads_via / mutates_via / reads / mutates / references_api). Omit for all kinds." },
10174
+ offset: { type: "number", description: "Pagination offset. Default 0." },
10175
+ limit: { type: "number", description: "Max references to return. Default 10." },
10176
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10177
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10178
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
10179
+ },
10180
+ required: ["target"]
10181
+ }
10182
+ },
10183
+ {
10184
+ name: "trace_path",
10185
+ description: 'Find shortest paths between two nodes across layers via cross_refs (including the via[] chains that capture transitive DB access through middleware).\n\nUSE THIS FOR: "how does AdminAnalyticsPage end up reading the User table" \u2014 returns the call chain page \u2192 fetch \u2192 endpoint \u2192 middleware \u2192 db. BFS-bounded by max_hops + max_paths to keep responses tractable on hub nodes. Paginated.',
10186
+ inputSchema: {
10187
+ type: "object",
10188
+ properties: {
10189
+ from: { type: "string", description: "Source node id." },
10190
+ to: { type: "string", description: "Target node id." },
10191
+ max_hops: { type: "number", description: "Hard cap on path length. Default 5." },
10192
+ max_paths: { type: "number", description: "Hard cap on total paths discovered. Default 10. Once hit, BFS stops." },
10193
+ offset: { type: "number", description: "Pagination offset over discovered paths. Default 0." },
10194
+ limit: { type: "number", description: "Max paths returned per response. Default 10." },
10195
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10196
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10197
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
10198
+ },
10199
+ required: ["from", "to"]
10200
+ }
10201
+ },
10202
+ {
10203
+ name: "auth_coverage_report",
10204
+ description: 'Aggregate every API endpoint by its auth[] wrapper(s) \u2014 surfaces endpoints with empty auth, groups by module, shows which auth strategies dominate. Computed from api.json endpoint auth field (100% populated).\n\nUSE THIS FOR: "are there unauthenticated endpoints", "which auth wrappers are in use", "audit auth strategy consistency across modules". Paginated. Returns { total, by_strategy: {strategy: count}, unauthenticated: [endpoint_ids], by_module: {module: {total, by_strategy}} } plus the paginated endpoint list.',
10205
+ inputSchema: {
10206
+ type: "object",
10207
+ properties: {
10208
+ module: { type: "string", description: 'Restrict to one module tag (e.g. "orgs", "admin").' },
10209
+ strategy: { type: "string", description: 'Restrict to endpoints using this auth wrapper (e.g. "withAuth"). Pass "none" to list unauthenticated endpoints.' },
10210
+ offset: { type: "number", description: "Pagination offset over the endpoint list. Default 0." },
10211
+ limit: { type: "number", description: "Max endpoints to return per response. Default 10." },
10212
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10213
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10214
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
8633
10215
  }
8634
10216
  }
8635
10217
  },
@@ -8659,7 +10241,9 @@ Use this when the user asks "is the chart running", "show me the project graph U
8659
10241
  type: "string",
8660
10242
  description: 'Tag value (e.g. "auth", "alice", "true").'
8661
10243
  },
8662
- project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
10244
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10245
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10246
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
8663
10247
  },
8664
10248
  required: ["node_id", "key", "value"]
8665
10249
  }
@@ -8678,7 +10262,9 @@ Use this when the user asks "is the chart running", "show me the project graph U
8678
10262
  type: "string",
8679
10263
  description: "Tag key to remove."
8680
10264
  },
8681
- project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
10265
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10266
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10267
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
8682
10268
  },
8683
10269
  required: ["node_id", "key"]
8684
10270
  }
@@ -8697,7 +10283,9 @@ Use this when the user asks "is the chart running", "show me the project graph U
8697
10283
  type: "string",
8698
10284
  description: "Specific check to run (e.g. 'schema_drift', 'unprotected_routes'). Omit to run all checks for the layer."
8699
10285
  },
8700
- project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
10286
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10287
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10288
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
8701
10289
  },
8702
10290
  required: ["layer"]
8703
10291
  }
@@ -8733,12 +10321,59 @@ Example: blast_points(node_id: "server/auth/middleware.ts", hops: 2) \u2192 retu
8733
10321
  enum: ["reverse", "both"],
8734
10322
  description: "'reverse' (default) = only what depends on this node. 'both' = full neighborhood."
8735
10323
  },
8736
- project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
10324
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10325
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10326
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
8737
10327
  },
8738
10328
  required: ["node_id"]
8739
10329
  }
10330
+ },
10331
+ {
10332
+ name: "list_notes",
10333
+ description: 'List tagged comments across the project graph \u2014 TODO, FIXME, HACK, NOTE, SECURITY, etc. Any `// KIND: body` or `/* KIND: body */` comment where KIND is an all-caps identifier (2\u201316 chars) is captured automatically; team conventions like `// CLAUDE:` are discovered without configuration.\n\nKinds are categorized at read time:\n actionable: TODO, FIXME, HACK, XXX, REFACTOR, OPTIMIZE, REVIEW\n warning: SECURITY, WARNING, IMPORTANT, CAUTION, DANGER, DEPRECATED\n doc: NOTE, TIP, SEE, NB, INFO, EXAMPLE\n custom: anything else (team-specific conventions)\n\nDefault category filter is `actionable,warning` \u2014 the "what should I act on" view. Pass category:"all" for everything including doc + custom.\n\nReturns: { total, returned, has_more, items: [{file, line, kind, category, text, author?, module?}] }.',
10334
+ inputSchema: {
10335
+ type: "object",
10336
+ properties: {
10337
+ layer: {
10338
+ type: "string",
10339
+ description: "Restrict to one layer ('ui', 'api', 'db'). Default: scan all layers that have notes."
10340
+ },
10341
+ kind: {
10342
+ type: "string",
10343
+ description: 'Exact kind match, case-insensitive (e.g. "TODO", "FIXME", "SECURITY").'
10344
+ },
10345
+ category: {
10346
+ type: "string",
10347
+ description: "'actionable' | 'warning' | 'doc' | 'custom' | 'all'. Comma-separated list also accepted. Default: 'actionable,warning'."
10348
+ },
10349
+ module: {
10350
+ type: "string",
10351
+ description: 'Restrict to nodes carrying this module tag (e.g. "auth", "work-items").'
10352
+ },
10353
+ pattern: {
10354
+ type: "string",
10355
+ description: 'Regex applied to note body text (case-insensitive). Use to grep within bodies, e.g. "race condition".'
10356
+ },
10357
+ author: {
10358
+ type: "string",
10359
+ description: "Match author from `// TODO(alice): \u2026` syntax, case-insensitive."
10360
+ },
10361
+ limit: {
10362
+ type: "number",
10363
+ description: "Max items returned. Default 100. Pair with offset for pagination."
10364
+ },
10365
+ offset: {
10366
+ type: "number",
10367
+ description: "Skip first N items. Default 0."
10368
+ },
10369
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
10370
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
10371
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
10372
+ }
10373
+ }
8740
10374
  }
8741
10375
  ];
10376
+ MINIMAL_STRIP_FIELDS = /* @__PURE__ */ new Set(["columns"]);
8742
10377
  COMPACT_SCHEMA = {
8743
10378
  nodes: {
8744
10379
  i: "id",
@@ -8778,8 +10413,31 @@ Example: blast_points(node_id: "server/auth/middleware.ts", hops: 2) \u2192 retu
8778
10413
  "variables",
8779
10414
  "responses",
8780
10415
  "params",
8781
- "effects"
10416
+ "effects",
10417
+ "ui_labels",
10418
+ "notes"
8782
10419
  ]);
10420
+ NOTE_KIND_CATEGORY = {
10421
+ TODO: "actionable",
10422
+ FIXME: "actionable",
10423
+ HACK: "actionable",
10424
+ XXX: "actionable",
10425
+ REFACTOR: "actionable",
10426
+ OPTIMIZE: "actionable",
10427
+ REVIEW: "actionable",
10428
+ SECURITY: "warning",
10429
+ WARNING: "warning",
10430
+ IMPORTANT: "warning",
10431
+ CAUTION: "warning",
10432
+ DANGER: "warning",
10433
+ DEPRECATED: "warning",
10434
+ NOTE: "doc",
10435
+ TIP: "doc",
10436
+ SEE: "doc",
10437
+ NB: "doc",
10438
+ INFO: "doc",
10439
+ EXAMPLE: "doc"
10440
+ };
8783
10441
  EST_CHARS_PER_NODE_FULL = {
8784
10442
  ui: 300,
8785
10443
  api: 300,