@launchsecure/launch-kit 0.0.29 → 0.0.30
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.
- package/dist/beacon/beacon.mjs +2759 -1246
- package/dist/beacon/beacon.mjs.map +1 -1
- package/dist/beacon/beacon.umd.js +710 -95
- package/dist/beacon/beacon.umd.js.map +1 -1
- package/dist/beacon/types/core.d.ts +14 -0
- package/dist/beacon/types/core.d.ts.map +1 -0
- package/dist/beacon/types/ctx.d.ts +14 -0
- package/dist/beacon/types/ctx.d.ts.map +1 -0
- package/dist/beacon/types/element.d.ts +16 -48
- package/dist/beacon/types/element.d.ts.map +1 -1
- package/dist/beacon/types/index.d.ts +5 -4
- package/dist/beacon/types/index.d.ts.map +1 -1
- package/dist/beacon/types/internal/annotation-cache.d.ts +10 -0
- package/dist/beacon/types/internal/annotation-cache.d.ts.map +1 -0
- package/dist/beacon/types/internal/element-capture.d.ts +19 -0
- package/dist/beacon/types/internal/element-capture.d.ts.map +1 -0
- package/dist/beacon/types/internal/event-buffer.d.ts +16 -0
- package/dist/beacon/types/internal/event-buffer.d.ts.map +1 -0
- package/dist/beacon/types/internal/framework-detect.d.ts +6 -0
- package/dist/beacon/types/internal/framework-detect.d.ts.map +1 -0
- package/dist/beacon/types/internal/markers.d.ts +17 -0
- package/dist/beacon/types/internal/markers.d.ts.map +1 -0
- package/dist/beacon/types/internal/monitor/capture-dom.d.ts +14 -0
- package/dist/beacon/types/internal/monitor/capture-dom.d.ts.map +1 -0
- package/dist/beacon/types/internal/monitor/capture-network.d.ts +12 -0
- package/dist/beacon/types/internal/monitor/capture-network.d.ts.map +1 -0
- package/dist/beacon/types/internal/monitor/overlay.d.ts +16 -0
- package/dist/beacon/types/internal/monitor/overlay.d.ts.map +1 -0
- package/dist/beacon/types/internal/monitor/session.d.ts +41 -0
- package/dist/beacon/types/internal/monitor/session.d.ts.map +1 -0
- package/dist/beacon/types/{monitor → internal/monitor}/transport.d.ts +3 -3
- package/dist/beacon/types/internal/monitor/transport.d.ts.map +1 -0
- package/dist/beacon/types/{monitor/types.d.ts → internal/monitor/wire.d.ts} +69 -27
- package/dist/beacon/types/internal/monitor/wire.d.ts.map +1 -0
- package/dist/beacon/types/{ui → internal}/pick-mode-overlay.d.ts +4 -5
- package/dist/beacon/types/internal/pick-mode-overlay.d.ts.map +1 -0
- package/dist/beacon/types/{capture → internal}/picker.d.ts +0 -1
- package/dist/beacon/types/internal/picker.d.ts.map +1 -0
- package/dist/beacon/types/{ui → internal}/pin-popover.d.ts +1 -1
- package/dist/beacon/types/internal/pin-popover.d.ts.map +1 -0
- package/dist/beacon/types/{capture → internal}/screenshot.d.ts +1 -0
- package/dist/beacon/types/internal/screenshot.d.ts.map +1 -0
- package/dist/beacon/types/internal/selector.d.ts.map +1 -0
- package/dist/beacon/types/plugins/domEle.d.ts +14 -0
- package/dist/beacon/types/plugins/domEle.d.ts.map +1 -0
- package/dist/beacon/types/plugins/domSS.d.ts +8 -0
- package/dist/beacon/types/plugins/domSS.d.ts.map +1 -0
- package/dist/beacon/types/plugins/errors.d.ts +3 -0
- package/dist/beacon/types/plugins/errors.d.ts.map +1 -0
- package/dist/beacon/types/plugins/index.d.ts +8 -0
- package/dist/beacon/types/plugins/index.d.ts.map +1 -0
- package/dist/beacon/types/plugins/liveMonitor.d.ts +14 -0
- package/dist/beacon/types/plugins/liveMonitor.d.ts.map +1 -0
- package/dist/beacon/types/plugins/metadata.d.ts +3 -0
- package/dist/beacon/types/plugins/metadata.d.ts.map +1 -0
- package/dist/beacon/types/registry.d.ts +33 -0
- package/dist/beacon/types/registry.d.ts.map +1 -0
- package/dist/beacon/types/styles.d.ts +8 -0
- package/dist/beacon/types/styles.d.ts.map +1 -0
- package/dist/beacon/types/transport.d.ts +3 -0
- package/dist/beacon/types/transport.d.ts.map +1 -0
- package/dist/beacon/types/types.d.ts +152 -68
- package/dist/beacon/types/types.d.ts.map +1 -1
- package/dist/beacon/types/ui/dialog.d.ts +53 -0
- package/dist/beacon/types/ui/dialog.d.ts.map +1 -0
- package/dist/beacon/types/ui/form.d.ts +7 -0
- package/dist/beacon/types/ui/form.d.ts.map +1 -0
- package/dist/beacon/types/ui/overlay.d.ts +6 -0
- package/dist/beacon/types/ui/overlay.d.ts.map +1 -0
- package/dist/deck-client/assets/{_baseUniq-W2JQDmje.js → _baseUniq-DCt2IMRR.js} +1 -1
- package/dist/deck-client/assets/{arc-DIBWAId9.js → arc-h-ifqmNR.js} +1 -1
- package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-CAIRMvJK.js → architectureDiagram-Q4EWVU46-C9dITSPv.js} +1 -1
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BeNaNiOi.js → blockDiagram-DXYQGD6D-BHuJT34t.js} +1 -1
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-B9Ozi62h.js → c4Diagram-AHTNJAMY-CpvMGtDG.js} +1 -1
- package/dist/deck-client/assets/channel-2PZVMiXf.js +1 -0
- package/dist/deck-client/assets/{chunk-4BX2VUAB-D7AZ47dt.js → chunk-4BX2VUAB-B6md1VIm.js} +1 -1
- package/dist/deck-client/assets/{chunk-4TB4RGXK-DnVnNPcI.js → chunk-4TB4RGXK-BmEnX8ik.js} +1 -1
- package/dist/deck-client/assets/{chunk-55IACEB6-UKYs-YNd.js → chunk-55IACEB6-BZPUyZAZ.js} +1 -1
- package/dist/deck-client/assets/{chunk-EDXVE4YY-D43b-SKn.js → chunk-EDXVE4YY-BWwNUK-l.js} +1 -1
- package/dist/deck-client/assets/{chunk-FMBD7UC4-QzBAoyyW.js → chunk-FMBD7UC4-o7gSppGI.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-Cjif4r6W.js → chunk-OYMX7WX6-C4KoTL5p.js} +1 -1
- package/dist/deck-client/assets/{chunk-QZHKN3VN-CqLDirEI.js → chunk-QZHKN3VN-jkf68sDs.js} +1 -1
- package/dist/deck-client/assets/{chunk-YZCP3GAM-_FQvmMs4.js → chunk-YZCP3GAM-Cd4yBE7o.js} +1 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-Bt8xBAof.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-Bt8xBAof.js +1 -0
- package/dist/deck-client/assets/clone-BHQryoDl.js +1 -0
- package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-rfrocesE.js → cose-bilkent-S5V4N54A-DeGFUgAV.js} +1 -1
- package/dist/deck-client/assets/{dagre-KV5264BT-Bv_7DJat.js → dagre-KV5264BT-ekcYJuUV.js} +1 -1
- package/dist/deck-client/assets/{diagram-5BDNPKRD-4F1414G5.js → diagram-5BDNPKRD-YHPk4rV2.js} +1 -1
- package/dist/deck-client/assets/{diagram-G4DWMVQ6-C4-Pszqm.js → diagram-G4DWMVQ6-DM-JCd_B.js} +1 -1
- package/dist/deck-client/assets/{diagram-MMDJMWI5-B647TIx9.js → diagram-MMDJMWI5-l5FK1ybk.js} +1 -1
- package/dist/deck-client/assets/{diagram-TYMM5635-BFAqpezd.js → diagram-TYMM5635-CIN4_1-j.js} +1 -1
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-BfBfrJOC.js → erDiagram-SMLLAGMA-MyinSkEl.js} +1 -1
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-DX9YAYes.js → flowDiagram-DWJPFMVM-Dk8nn42x.js} +1 -1
- package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-DCuiy7wF.js → ganttDiagram-T4ZO3ILL-BU1ihicu.js} +1 -1
- package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-CGp1IXUh.js → gitGraphDiagram-UUTBAWPF-BjsTL13C.js} +1 -1
- package/dist/deck-client/assets/{graph-B7g8aoxv.js → graph-DJmh-xi7.js} +1 -1
- package/dist/deck-client/assets/{index-Dg1r-WSN.js → index-KsShfCV-.js} +3 -3
- package/dist/deck-client/assets/{infoDiagram-42DDH7IO-L3fahMkF.js → infoDiagram-42DDH7IO-Dxvy_RB4.js} +1 -1
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-aS_EjWBZ.js → ishikawaDiagram-UXIWVN3A-DPOaNF1l.js} +1 -1
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-djTSQZF9.js → journeyDiagram-VCZTEJTY-DMew3K5c.js} +1 -1
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CcTHo4CM.js → kanban-definition-6JOO6SKY-csciJFuk.js} +1 -1
- package/dist/deck-client/assets/{layout-mEJiadb7.js → layout-Dg4yyms2.js} +1 -1
- package/dist/deck-client/assets/{linear-XgTKqyRu.js → linear-BA3zU6gq.js} +1 -1
- package/dist/deck-client/assets/{min-Ct9jZdpd.js → min-lz-Ird-p.js} +1 -1
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-BaFxCGNU.js → mindmap-definition-QFDTVHPH-CCEN8OQV.js} +1 -1
- package/dist/deck-client/assets/{pieDiagram-DEJITSTG-CIbYYjtw.js → pieDiagram-DEJITSTG-DM6n1HY7.js} +1 -1
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-D9EtCOvh.js → quadrantDiagram-34T5L4WZ-_ULoR66n.js} +1 -1
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-xeni9eVG.js → requirementDiagram-MS252O5E-BuwJs7Tn.js} +1 -1
- package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-LYeknz9h.js → sankeyDiagram-XADWPNL6-BEsuzkW4.js} +1 -1
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-RDbsKFZf.js → sequenceDiagram-FGHM5R23-CP2H0YWf.js} +1 -1
- package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-BH1Zjglk.js → stateDiagram-FHFEXIEX-B5Gw_NNL.js} +1 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-4T4wMDXr.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-IFXxKptt.js → timeline-definition-GMOUNBTQ-DsoYydQa.js} +1 -1
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-D-sLkQs9.js → vennDiagram-DHZGUBPP-Dz8JT_ob.js} +1 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-DGHQ_Ijv.js +162 -0
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-BTjjuDU3.js → wardleyDiagram-NUSXRM2D-DN1LJMB1.js} +1 -1
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-AYbv92n-.js → xychartDiagram-5P7HB3ND-nb0oSfrQ.js} +1 -1
- package/dist/deck-client/index.html +1 -1
- package/dist/server/beacon-monitor-entry.js +548 -6
- package/dist/server/chart-serve.js +917 -248
- package/dist/server/cli.js +1368 -374
- package/dist/server/council-entry.js +0 -0
- package/dist/server/fb-wizard.js +0 -0
- package/dist/server/graph-mcp-entry.js +1326 -322
- package/dist/server/init-entry.js +16 -11
- package/dist/server/orbit-entry.js +135 -7
- package/dist/server/parse-worker-entry.js +918 -247
- package/package.json +22 -22
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-array.md +107 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-clear.md +94 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-pulse.md +82 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-scan.md +66 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/blast-radius.md +101 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief.md +112 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/course.md +84 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/debug.md +92 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/deploy-check.md +160 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/diagram.md +134 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/orbit.md +87 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/prototype.md +90 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/recall.md +83 -0
- package/scaffolds/ls-marketplace/plugins/kit/{commands → skills}/show-mcp-status.md +4 -4
- package/scaffolds/ls-marketplace/plugins/kit/skills/wireframe.md +70 -0
- package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +0 -0
- package/scaffolds/recall-hook/scripts/ensure-recall.sh +0 -0
- package/scaffolds/statusline/statusline-mcp.sh +21 -9
- package/dist/beacon/types/capture/element.d.ts +0 -3
- package/dist/beacon/types/capture/element.d.ts.map +0 -1
- package/dist/beacon/types/capture/events.d.ts +0 -20
- package/dist/beacon/types/capture/events.d.ts.map +0 -1
- package/dist/beacon/types/capture/framework.d.ts +0 -3
- package/dist/beacon/types/capture/framework.d.ts.map +0 -1
- package/dist/beacon/types/capture/metadata.d.ts +0 -3
- package/dist/beacon/types/capture/metadata.d.ts.map +0 -1
- package/dist/beacon/types/capture/overlay.d.ts +0 -7
- package/dist/beacon/types/capture/overlay.d.ts.map +0 -1
- package/dist/beacon/types/capture/picker.d.ts.map +0 -1
- package/dist/beacon/types/capture/screenshot.d.ts.map +0 -1
- package/dist/beacon/types/capture/selector.d.ts.map +0 -1
- package/dist/beacon/types/monitor/dom.d.ts +0 -13
- package/dist/beacon/types/monitor/dom.d.ts.map +0 -1
- package/dist/beacon/types/monitor/index.d.ts +0 -19
- package/dist/beacon/types/monitor/index.d.ts.map +0 -1
- package/dist/beacon/types/monitor/network.d.ts +0 -12
- package/dist/beacon/types/monitor/network.d.ts.map +0 -1
- package/dist/beacon/types/monitor/transport.d.ts.map +0 -1
- package/dist/beacon/types/monitor/types.d.ts.map +0 -1
- package/dist/beacon/types/transport/submit.d.ts +0 -3
- package/dist/beacon/types/transport/submit.d.ts.map +0 -1
- package/dist/beacon/types/ui/button.d.ts +0 -2
- package/dist/beacon/types/ui/button.d.ts.map +0 -1
- package/dist/beacon/types/ui/drawer.d.ts +0 -33
- package/dist/beacon/types/ui/drawer.d.ts.map +0 -1
- package/dist/beacon/types/ui/icons.d.ts +0 -9
- package/dist/beacon/types/ui/icons.d.ts.map +0 -1
- package/dist/beacon/types/ui/monitor-panel.d.ts +0 -19
- package/dist/beacon/types/ui/monitor-panel.d.ts.map +0 -1
- package/dist/beacon/types/ui/pick-mode-overlay.d.ts.map +0 -1
- package/dist/beacon/types/ui/pin-popover.d.ts.map +0 -1
- package/dist/deck-client/assets/channel-CRdozqbp.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-lIZMp57W.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-lIZMp57W.js +0 -1
- package/dist/deck-client/assets/clone-BtWeSTyJ.js +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BrV78NDR.js +0 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-C010F8l4.js +0 -162
- package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-array.md +0 -92
- package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-clear.md +0 -68
- package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-pulse.md +0 -80
- package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-scan.md +0 -62
- /package/dist/beacon/types/{capture → internal}/selector.d.ts +0 -0
|
@@ -653,7 +653,7 @@ function resolveWorkerPath() {
|
|
|
653
653
|
);
|
|
654
654
|
}
|
|
655
655
|
function runParseInWorker(req) {
|
|
656
|
-
return new Promise((
|
|
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(() =>
|
|
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
|
-
|
|
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
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
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,
|
|
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
|
-
|
|
4095
|
-
|
|
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((
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
7638
|
+
if (!(0, import_node_fs23.existsSync)(dir)) return;
|
|
6894
7639
|
let entries;
|
|
6895
7640
|
try {
|
|
6896
|
-
entries = (0,
|
|
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,
|
|
7649
|
+
walkForExtensions((0, import_node_path28.join)(dir, entry.name), extCounts, depth + 1);
|
|
6905
7650
|
} else {
|
|
6906
|
-
const ext = (0,
|
|
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
|
|
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
|
-
|
|
6950
|
-
|
|
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(
|
|
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,
|
|
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
|
|
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
|
-
|
|
7152
|
-
|
|
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,
|
|
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
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
8296
|
+
`Graph generated successfully for ${label}.
|
|
7516
8297
|
|
|
7517
|
-
${formatProjectResult(results2,
|
|
8298
|
+
${formatProjectResult(results2, rootDir)}
|
|
7518
8299
|
|
|
7519
|
-
Use
|
|
8300
|
+
Use ${queryHint} to query.`
|
|
7520
8301
|
);
|
|
7521
8302
|
}
|
|
7522
8303
|
const projects = listProjects(monorepoRoot);
|
|
@@ -7579,6 +8360,7 @@ 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;
|
|
7582
8364
|
const offset = args.offset ?? 0;
|
|
7583
8365
|
const limit = args.limit;
|
|
7584
8366
|
const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue);
|
|
@@ -7628,14 +8410,23 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
7628
8410
|
result2.budget_exceeded = true;
|
|
7629
8411
|
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
8412
|
}
|
|
8413
|
+
if (includeFindings && layer === "db") {
|
|
8414
|
+
result2.contradictions = graph.contradictions ?? [];
|
|
8415
|
+
result2.flagged_edges = graph.flagged_edges ?? [];
|
|
8416
|
+
}
|
|
7631
8417
|
return result2;
|
|
7632
8418
|
}
|
|
7633
8419
|
if (!hasFilter) {
|
|
7634
|
-
|
|
8420
|
+
const summaryResult = {
|
|
7635
8421
|
hint: "No filter specified \u2014 returning summary only. Use search/type/module/node_id to retrieve nodes.",
|
|
7636
8422
|
layer,
|
|
7637
8423
|
summary: layerSummary(graph)
|
|
7638
8424
|
};
|
|
8425
|
+
if (includeFindings && layer === "db") {
|
|
8426
|
+
summaryResult.contradictions = graph.contradictions ?? [];
|
|
8427
|
+
summaryResult.flagged_edges = graph.flagged_edges ?? [];
|
|
8428
|
+
}
|
|
8429
|
+
return summaryResult;
|
|
7639
8430
|
}
|
|
7640
8431
|
const matched = graph.nodes.filter((n) => {
|
|
7641
8432
|
if (search && !matchesSearch(n, search)) return false;
|
|
@@ -7686,6 +8477,10 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
7686
8477
|
} else if (returnedEdges.length > 0) {
|
|
7687
8478
|
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
8479
|
}
|
|
8480
|
+
if (includeFindings && layer === "db") {
|
|
8481
|
+
result.contradictions = graph.contradictions ?? [];
|
|
8482
|
+
result.flagged_edges = graph.flagged_edges ?? [];
|
|
8483
|
+
}
|
|
7689
8484
|
return result;
|
|
7690
8485
|
}
|
|
7691
8486
|
function runReadGraphQuery(rootDir, args) {
|
|
@@ -7700,6 +8495,8 @@ function handleReadGraph(args) {
|
|
|
7700
8495
|
return err("queries array is empty. Provide at least one query object.");
|
|
7701
8496
|
}
|
|
7702
8497
|
const inheritedProject = typeof args.project === "string" ? args.project : void 0;
|
|
8498
|
+
const inheritedWorktree = typeof args.worktree === "string" ? args.worktree : void 0;
|
|
8499
|
+
const inheritedProjectRoot = typeof args.project_root === "string" ? args.project_root : void 0;
|
|
7703
8500
|
const results = [];
|
|
7704
8501
|
let cumulativeChars = 0;
|
|
7705
8502
|
let budgetHit = false;
|
|
@@ -7717,15 +8514,18 @@ function handleReadGraph(args) {
|
|
|
7717
8514
|
});
|
|
7718
8515
|
continue;
|
|
7719
8516
|
}
|
|
7720
|
-
const
|
|
8517
|
+
const qInherited = { ...q };
|
|
8518
|
+
if (inheritedProject && !qInherited.project) qInherited.project = inheritedProject;
|
|
8519
|
+
if (inheritedWorktree && !qInherited.worktree) qInherited.worktree = inheritedWorktree;
|
|
8520
|
+
if (inheritedProjectRoot && !qInherited.project_root) qInherited.project_root = inheritedProjectRoot;
|
|
7721
8521
|
let perQueryRoot;
|
|
7722
8522
|
try {
|
|
7723
|
-
perQueryRoot =
|
|
8523
|
+
perQueryRoot = resolveRequestRoot2(qInherited, monorepoRoot);
|
|
7724
8524
|
} catch (e) {
|
|
7725
8525
|
results.push({ index: i, query: q, result: { error: e.message } });
|
|
7726
8526
|
continue;
|
|
7727
8527
|
}
|
|
7728
|
-
const r = runReadGraphQuery(perQueryRoot,
|
|
8528
|
+
const r = runReadGraphQuery(perQueryRoot, qInherited);
|
|
7729
8529
|
const entry = { index: i, query: q, result: r };
|
|
7730
8530
|
const entrySize = JSON.stringify(entry, null, 2).length;
|
|
7731
8531
|
if (cumulativeChars + entrySize > BATCH_BUDGET_CHARS && results.length > 0) {
|
|
@@ -7758,12 +8558,12 @@ function handleReadGraph(args) {
|
|
|
7758
8558
|
return okJson(result);
|
|
7759
8559
|
}
|
|
7760
8560
|
function nodeToFilePath(rootDir, layer, nodeId) {
|
|
7761
|
-
if (layer === "ui" || layer === "api") return (0,
|
|
7762
|
-
if (layer === "db") return (0,
|
|
7763
|
-
const withSrc = (0,
|
|
7764
|
-
if ((0,
|
|
7765
|
-
const direct = (0,
|
|
7766
|
-
if ((0,
|
|
8561
|
+
if (layer === "ui" || layer === "api") return (0, import_node_path30.join)(rootDir, "src", nodeId);
|
|
8562
|
+
if (layer === "db") return (0, import_node_path30.join)(rootDir, "prisma", "schema.prisma");
|
|
8563
|
+
const withSrc = (0, import_node_path30.join)(rootDir, "src", nodeId);
|
|
8564
|
+
if ((0, import_node_fs25.existsSync)(withSrc)) return withSrc;
|
|
8565
|
+
const direct = (0, import_node_path30.join)(rootDir, nodeId);
|
|
8566
|
+
if ((0, import_node_fs25.existsSync)(direct)) return direct;
|
|
7767
8567
|
return null;
|
|
7768
8568
|
}
|
|
7769
8569
|
function handleInspectNode(args) {
|
|
@@ -7793,7 +8593,7 @@ function handleInspectNode(args) {
|
|
|
7793
8593
|
} else {
|
|
7794
8594
|
matched = graph.nodes;
|
|
7795
8595
|
}
|
|
7796
|
-
const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params", "effects"];
|
|
8596
|
+
const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params", "effects", "ui_labels", "notes"];
|
|
7797
8597
|
const requestedFields = fields ?? allDeepFields;
|
|
7798
8598
|
let filterRegex = null;
|
|
7799
8599
|
if (filter) {
|
|
@@ -7848,6 +8648,101 @@ function handleInspectNode(args) {
|
|
|
7848
8648
|
nodes: results
|
|
7849
8649
|
});
|
|
7850
8650
|
}
|
|
8651
|
+
function handleListNotes(args) {
|
|
8652
|
+
const __resolved = resolveOrErr(args);
|
|
8653
|
+
if ("content" in __resolved) return __resolved;
|
|
8654
|
+
const { rootDir } = __resolved;
|
|
8655
|
+
const layerArg = args.layer;
|
|
8656
|
+
const kindArg = args.kind?.toUpperCase();
|
|
8657
|
+
const categoryArg = args.category ?? "actionable,warning";
|
|
8658
|
+
const moduleArg = args.module;
|
|
8659
|
+
const patternArg = args.pattern;
|
|
8660
|
+
const authorArg = args.author?.toLowerCase();
|
|
8661
|
+
const limit = args.limit ?? 100;
|
|
8662
|
+
const offset = args.offset ?? 0;
|
|
8663
|
+
let patternRegex = null;
|
|
8664
|
+
if (patternArg) {
|
|
8665
|
+
try {
|
|
8666
|
+
patternRegex = new RegExp(patternArg, "i");
|
|
8667
|
+
} catch {
|
|
8668
|
+
return err(`Invalid regex pattern: "${patternArg}"`);
|
|
8669
|
+
}
|
|
8670
|
+
}
|
|
8671
|
+
const wantedCategories = (() => {
|
|
8672
|
+
const trimmed = categoryArg.trim().toLowerCase();
|
|
8673
|
+
if (trimmed === "all") return "all";
|
|
8674
|
+
const out = /* @__PURE__ */ new Set();
|
|
8675
|
+
for (const part of trimmed.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
8676
|
+
if (part === "actionable" || part === "warning" || part === "doc" || part === "custom") {
|
|
8677
|
+
out.add(part);
|
|
8678
|
+
}
|
|
8679
|
+
}
|
|
8680
|
+
if (out.size === 0) {
|
|
8681
|
+
out.add("actionable");
|
|
8682
|
+
out.add("warning");
|
|
8683
|
+
}
|
|
8684
|
+
return out;
|
|
8685
|
+
})();
|
|
8686
|
+
const layers = layerArg ? [layerArg] : getAvailableLayers(rootDir);
|
|
8687
|
+
const items = [];
|
|
8688
|
+
for (const layer of layers) {
|
|
8689
|
+
const graph = readGraph(rootDir, layer);
|
|
8690
|
+
if (!graph) continue;
|
|
8691
|
+
for (const node of graph.nodes) {
|
|
8692
|
+
const nodeNotes = node.notes;
|
|
8693
|
+
if (!nodeNotes || nodeNotes.length === 0) continue;
|
|
8694
|
+
const tags = node.tags;
|
|
8695
|
+
const nodeModule = tags?.module;
|
|
8696
|
+
if (moduleArg && nodeModule !== moduleArg) continue;
|
|
8697
|
+
for (const n of nodeNotes) {
|
|
8698
|
+
if (!n.kind || !n.text || typeof n.line !== "number") continue;
|
|
8699
|
+
const category = categorizeNoteKind(n.kind);
|
|
8700
|
+
if (wantedCategories !== "all" && !wantedCategories.has(category)) continue;
|
|
8701
|
+
if (kindArg && n.kind.toUpperCase() !== kindArg) continue;
|
|
8702
|
+
if (authorArg && (!n.author || n.author.toLowerCase() !== authorArg)) continue;
|
|
8703
|
+
if (patternRegex && !patternRegex.test(n.text)) continue;
|
|
8704
|
+
const item = {
|
|
8705
|
+
file: node.id,
|
|
8706
|
+
line: n.line,
|
|
8707
|
+
kind: n.kind,
|
|
8708
|
+
category,
|
|
8709
|
+
text: n.text
|
|
8710
|
+
};
|
|
8711
|
+
if (n.author) item.author = n.author;
|
|
8712
|
+
if (nodeModule) item.module = nodeModule;
|
|
8713
|
+
items.push(item);
|
|
8714
|
+
}
|
|
8715
|
+
}
|
|
8716
|
+
}
|
|
8717
|
+
const catOrder = { actionable: 0, warning: 1, doc: 2, custom: 3 };
|
|
8718
|
+
items.sort((a, b) => {
|
|
8719
|
+
const c = catOrder[a.category] - catOrder[b.category];
|
|
8720
|
+
if (c !== 0) return c;
|
|
8721
|
+
const k = a.kind.localeCompare(b.kind);
|
|
8722
|
+
if (k !== 0) return k;
|
|
8723
|
+
const f = a.file.localeCompare(b.file);
|
|
8724
|
+
if (f !== 0) return f;
|
|
8725
|
+
return a.line - b.line;
|
|
8726
|
+
});
|
|
8727
|
+
const total = items.length;
|
|
8728
|
+
const paged = items.slice(offset, offset + limit);
|
|
8729
|
+
const hasMore = offset + paged.length < total;
|
|
8730
|
+
return okJson({
|
|
8731
|
+
total,
|
|
8732
|
+
returned: paged.length,
|
|
8733
|
+
has_more: hasMore,
|
|
8734
|
+
...hasMore ? { next_offset: offset + paged.length } : {},
|
|
8735
|
+
filter: {
|
|
8736
|
+
layer: layerArg ?? "all",
|
|
8737
|
+
kind: kindArg ?? null,
|
|
8738
|
+
category: categoryArg,
|
|
8739
|
+
module: moduleArg ?? null,
|
|
8740
|
+
pattern: patternArg ?? null,
|
|
8741
|
+
author: authorArg ?? null
|
|
8742
|
+
},
|
|
8743
|
+
items: paged
|
|
8744
|
+
});
|
|
8745
|
+
}
|
|
7851
8746
|
function handleGrepNodes(args) {
|
|
7852
8747
|
const __resolved = resolveOrErr(args);
|
|
7853
8748
|
if ("content" in __resolved) return __resolved;
|
|
@@ -7910,11 +8805,11 @@ function handleGrepNodes(args) {
|
|
|
7910
8805
|
let filesSearched = 0;
|
|
7911
8806
|
let truncated = false;
|
|
7912
8807
|
for (const [filePath, nodeId] of filePaths) {
|
|
7913
|
-
if (!(0,
|
|
8808
|
+
if (!(0, import_node_fs25.existsSync)(filePath)) continue;
|
|
7914
8809
|
filesSearched++;
|
|
7915
8810
|
let content;
|
|
7916
8811
|
try {
|
|
7917
|
-
content = (0,
|
|
8812
|
+
content = (0, import_node_fs25.readFileSync)(filePath, "utf-8");
|
|
7918
8813
|
} catch {
|
|
7919
8814
|
continue;
|
|
7920
8815
|
}
|
|
@@ -8036,11 +8931,11 @@ function handleStartChartServer(args) {
|
|
|
8036
8931
|
});
|
|
8037
8932
|
}
|
|
8038
8933
|
const entryPath = process.argv[1];
|
|
8039
|
-
const logDir = (0,
|
|
8040
|
-
(0,
|
|
8041
|
-
const logPath = (0,
|
|
8042
|
-
const out = (0,
|
|
8043
|
-
const err2 = (0,
|
|
8934
|
+
const logDir = (0, import_node_path30.join)((0, import_node_os3.homedir)(), LAUNCHSECURE_DIR);
|
|
8935
|
+
(0, import_node_fs25.mkdirSync)(logDir, { recursive: true });
|
|
8936
|
+
const logPath = (0, import_node_path30.join)(logDir, "launch-chart.log");
|
|
8937
|
+
const out = (0, import_node_fs25.openSync)(logPath, "a");
|
|
8938
|
+
const err2 = (0, import_node_fs25.openSync)(logPath, "a");
|
|
8044
8939
|
const portArgs = args.port ? ["--port", String(args.port)] : [];
|
|
8045
8940
|
const child = (0, import_node_child_process2.spawn)(process.execPath, [entryPath, "serve", ...portArgs], {
|
|
8046
8941
|
detached: true,
|
|
@@ -8169,20 +9064,20 @@ function handleDetectProjectStack() {
|
|
|
8169
9064
|
if (ref.type === "references_api") stats.references_api++;
|
|
8170
9065
|
}
|
|
8171
9066
|
}
|
|
8172
|
-
const srcDir = (0,
|
|
8173
|
-
if ((0,
|
|
9067
|
+
const srcDir = (0, import_node_path30.join)(rootDir, "src");
|
|
9068
|
+
if ((0, import_node_fs25.existsSync)(srcDir)) {
|
|
8174
9069
|
const scanDir = (dir) => {
|
|
8175
|
-
if (!(0,
|
|
8176
|
-
for (const entry of (0,
|
|
9070
|
+
if (!(0, import_node_fs25.existsSync)(dir)) return;
|
|
9071
|
+
for (const entry of (0, import_node_fs25.readdirSync)(dir, { withFileTypes: true })) {
|
|
8177
9072
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
8178
|
-
const full = (0,
|
|
9073
|
+
const full = (0, import_node_path30.join)(dir, entry.name);
|
|
8179
9074
|
if (entry.isDirectory()) {
|
|
8180
9075
|
scanDir(full);
|
|
8181
9076
|
continue;
|
|
8182
9077
|
}
|
|
8183
|
-
if (![".ts", ".tsx"].includes((0,
|
|
9078
|
+
if (![".ts", ".tsx"].includes((0, import_node_path30.extname)(entry.name))) continue;
|
|
8184
9079
|
try {
|
|
8185
|
-
const content = (0,
|
|
9080
|
+
const content = (0, import_node_fs25.readFileSync)(full, "utf-8");
|
|
8186
9081
|
const matches = content.match(/@api\s+(GET|POST|PUT|DELETE|PATCH)\s+\/\S+/g);
|
|
8187
9082
|
if (matches) stats.annotations += matches.length;
|
|
8188
9083
|
} catch {
|
|
@@ -8201,7 +9096,7 @@ function handleDetectProjectStack() {
|
|
|
8201
9096
|
name: p.name,
|
|
8202
9097
|
root: p.root,
|
|
8203
9098
|
absolute_root: p.absoluteRoot,
|
|
8204
|
-
has_graph: (0,
|
|
9099
|
+
has_graph: (0, import_node_fs25.existsSync)((0, import_node_path30.join)(p.absoluteRoot, LAUNCHSECURE_DIR, "graphs"))
|
|
8205
9100
|
}));
|
|
8206
9101
|
return okJson({
|
|
8207
9102
|
languages,
|
|
@@ -8315,6 +9210,10 @@ async function handleMessage(msg) {
|
|
|
8315
9210
|
respond(id ?? null, withFreshnessMeta(handleBlastPoints(args), args));
|
|
8316
9211
|
return;
|
|
8317
9212
|
}
|
|
9213
|
+
if (toolName === "list_notes") {
|
|
9214
|
+
respond(id ?? null, withFreshnessMeta(handleListNotes(args), args));
|
|
9215
|
+
return;
|
|
9216
|
+
}
|
|
8318
9217
|
respondError(id ?? null, -32601, `Unknown tool: ${toolName}`);
|
|
8319
9218
|
return;
|
|
8320
9219
|
}
|
|
@@ -8375,14 +9274,14 @@ function startGraphMcpServer() {
|
|
|
8375
9274
|
process.stderr.write(`[launchsecure-graph] MCP server started (cwd: ${process.cwd()})
|
|
8376
9275
|
`);
|
|
8377
9276
|
}
|
|
8378
|
-
var
|
|
9277
|
+
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
9278
|
var init_graph_mcp = __esm({
|
|
8380
9279
|
"src/server/graph-mcp.ts"() {
|
|
8381
9280
|
"use strict";
|
|
8382
|
-
|
|
8383
|
-
|
|
9281
|
+
import_node_fs25 = require("node:fs");
|
|
9282
|
+
import_node_path30 = require("node:path");
|
|
8384
9283
|
import_node_child_process2 = require("node:child_process");
|
|
8385
|
-
|
|
9284
|
+
import_node_os3 = require("node:os");
|
|
8386
9285
|
init_launch_kit_paths();
|
|
8387
9286
|
init_graph();
|
|
8388
9287
|
init_lockfile();
|
|
@@ -8410,13 +9309,21 @@ var init_graph_mcp = __esm({
|
|
|
8410
9309
|
project: {
|
|
8411
9310
|
type: "string",
|
|
8412
9311
|
description: PROJECT_PARAM_DESCRIPTION + " Special: omit to regenerate ALL configured projects."
|
|
9312
|
+
},
|
|
9313
|
+
worktree: {
|
|
9314
|
+
type: "string",
|
|
9315
|
+
description: WORKTREE_PARAM_DESCRIPTION2
|
|
9316
|
+
},
|
|
9317
|
+
project_root: {
|
|
9318
|
+
type: "string",
|
|
9319
|
+
description: PROJECT_ROOT_PARAM_DESCRIPTION2
|
|
8413
9320
|
}
|
|
8414
9321
|
}
|
|
8415
9322
|
}
|
|
8416
9323
|
},
|
|
8417
9324
|
{
|
|
8418
9325
|
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
|
|
9326
|
+
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
9327
|
inputSchema: {
|
|
8421
9328
|
type: "object",
|
|
8422
9329
|
properties: {
|
|
@@ -8426,7 +9333,7 @@ var init_graph_mcp = __esm({
|
|
|
8426
9333
|
},
|
|
8427
9334
|
search: {
|
|
8428
9335
|
type: "string",
|
|
8429
|
-
description: "Case-insensitive substring match against node id, name,
|
|
9336
|
+
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
9337
|
},
|
|
8431
9338
|
type: {
|
|
8432
9339
|
type: "string",
|
|
@@ -8460,6 +9367,10 @@ var init_graph_mcp = __esm({
|
|
|
8460
9367
|
type: "boolean",
|
|
8461
9368
|
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
9369
|
},
|
|
9370
|
+
include_findings: {
|
|
9371
|
+
type: "boolean",
|
|
9372
|
+
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."
|
|
9373
|
+
},
|
|
8463
9374
|
offset: {
|
|
8464
9375
|
type: "number",
|
|
8465
9376
|
description: "Skip first N matched nodes (pagination). Default 0. Use next_offset from a previous response to get the next page."
|
|
@@ -8470,7 +9381,7 @@ var init_graph_mcp = __esm({
|
|
|
8470
9381
|
},
|
|
8471
9382
|
queries: {
|
|
8472
9383
|
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`).
|
|
9384
|
+
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
9385
|
items: {
|
|
8475
9386
|
type: "object",
|
|
8476
9387
|
properties: {
|
|
@@ -8482,13 +9393,24 @@ var init_graph_mcp = __esm({
|
|
|
8482
9393
|
hops: { type: "number" },
|
|
8483
9394
|
minimal: { type: "boolean" },
|
|
8484
9395
|
include_edges: { type: "boolean" },
|
|
8485
|
-
|
|
9396
|
+
include_findings: { type: "boolean" },
|
|
9397
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9398
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
9399
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
8486
9400
|
}
|
|
8487
9401
|
}
|
|
8488
9402
|
},
|
|
8489
9403
|
project: {
|
|
8490
9404
|
type: "string",
|
|
8491
9405
|
description: PROJECT_PARAM_DESCRIPTION
|
|
9406
|
+
},
|
|
9407
|
+
worktree: {
|
|
9408
|
+
type: "string",
|
|
9409
|
+
description: WORKTREE_PARAM_DESCRIPTION2
|
|
9410
|
+
},
|
|
9411
|
+
project_root: {
|
|
9412
|
+
type: "string",
|
|
9413
|
+
description: PROJECT_ROOT_PARAM_DESCRIPTION2
|
|
8492
9414
|
}
|
|
8493
9415
|
}
|
|
8494
9416
|
}
|
|
@@ -8536,7 +9458,9 @@ Returns: { pattern, filter, files_searched, total_matches, matches: [{file, line
|
|
|
8536
9458
|
context: { type: "number", description: "Context lines around each match. Default 2." },
|
|
8537
9459
|
max_matches: { type: "number", description: "Max matches to return total. Default 50." },
|
|
8538
9460
|
max_files: { type: "number", description: "Max files to search. Default 50." },
|
|
8539
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
9461
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9462
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
9463
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
8540
9464
|
},
|
|
8541
9465
|
required: ["layer", "pattern"]
|
|
8542
9466
|
}
|
|
@@ -8578,7 +9502,9 @@ Returns deep fields only \u2014 not structural metadata (use read_graph for that
|
|
|
8578
9502
|
type: "boolean",
|
|
8579
9503
|
description: "Case-insensitive filter matching. Default true."
|
|
8580
9504
|
},
|
|
8581
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
9505
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9506
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
9507
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
8582
9508
|
},
|
|
8583
9509
|
required: ["layer"]
|
|
8584
9510
|
}
|
|
@@ -8629,7 +9555,9 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
8629
9555
|
type: "string",
|
|
8630
9556
|
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.'
|
|
8631
9557
|
},
|
|
8632
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
9558
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9559
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
9560
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
8633
9561
|
}
|
|
8634
9562
|
}
|
|
8635
9563
|
},
|
|
@@ -8659,7 +9587,9 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
8659
9587
|
type: "string",
|
|
8660
9588
|
description: 'Tag value (e.g. "auth", "alice", "true").'
|
|
8661
9589
|
},
|
|
8662
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
9590
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9591
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
9592
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
8663
9593
|
},
|
|
8664
9594
|
required: ["node_id", "key", "value"]
|
|
8665
9595
|
}
|
|
@@ -8678,7 +9608,9 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
8678
9608
|
type: "string",
|
|
8679
9609
|
description: "Tag key to remove."
|
|
8680
9610
|
},
|
|
8681
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
9611
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9612
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
9613
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
8682
9614
|
},
|
|
8683
9615
|
required: ["node_id", "key"]
|
|
8684
9616
|
}
|
|
@@ -8697,7 +9629,9 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
8697
9629
|
type: "string",
|
|
8698
9630
|
description: "Specific check to run (e.g. 'schema_drift', 'unprotected_routes'). Omit to run all checks for the layer."
|
|
8699
9631
|
},
|
|
8700
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
9632
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9633
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
9634
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
8701
9635
|
},
|
|
8702
9636
|
required: ["layer"]
|
|
8703
9637
|
}
|
|
@@ -8733,12 +9667,59 @@ Example: blast_points(node_id: "server/auth/middleware.ts", hops: 2) \u2192 retu
|
|
|
8733
9667
|
enum: ["reverse", "both"],
|
|
8734
9668
|
description: "'reverse' (default) = only what depends on this node. 'both' = full neighborhood."
|
|
8735
9669
|
},
|
|
8736
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
9670
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9671
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
9672
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
8737
9673
|
},
|
|
8738
9674
|
required: ["node_id"]
|
|
8739
9675
|
}
|
|
9676
|
+
},
|
|
9677
|
+
{
|
|
9678
|
+
name: "list_notes",
|
|
9679
|
+
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?}] }.',
|
|
9680
|
+
inputSchema: {
|
|
9681
|
+
type: "object",
|
|
9682
|
+
properties: {
|
|
9683
|
+
layer: {
|
|
9684
|
+
type: "string",
|
|
9685
|
+
description: "Restrict to one layer ('ui', 'api', 'db'). Default: scan all layers that have notes."
|
|
9686
|
+
},
|
|
9687
|
+
kind: {
|
|
9688
|
+
type: "string",
|
|
9689
|
+
description: 'Exact kind match, case-insensitive (e.g. "TODO", "FIXME", "SECURITY").'
|
|
9690
|
+
},
|
|
9691
|
+
category: {
|
|
9692
|
+
type: "string",
|
|
9693
|
+
description: "'actionable' | 'warning' | 'doc' | 'custom' | 'all'. Comma-separated list also accepted. Default: 'actionable,warning'."
|
|
9694
|
+
},
|
|
9695
|
+
module: {
|
|
9696
|
+
type: "string",
|
|
9697
|
+
description: 'Restrict to nodes carrying this module tag (e.g. "auth", "work-items").'
|
|
9698
|
+
},
|
|
9699
|
+
pattern: {
|
|
9700
|
+
type: "string",
|
|
9701
|
+
description: 'Regex applied to note body text (case-insensitive). Use to grep within bodies, e.g. "race condition".'
|
|
9702
|
+
},
|
|
9703
|
+
author: {
|
|
9704
|
+
type: "string",
|
|
9705
|
+
description: "Match author from `// TODO(alice): \u2026` syntax, case-insensitive."
|
|
9706
|
+
},
|
|
9707
|
+
limit: {
|
|
9708
|
+
type: "number",
|
|
9709
|
+
description: "Max items returned. Default 100. Pair with offset for pagination."
|
|
9710
|
+
},
|
|
9711
|
+
offset: {
|
|
9712
|
+
type: "number",
|
|
9713
|
+
description: "Skip first N items. Default 0."
|
|
9714
|
+
},
|
|
9715
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9716
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
9717
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
9718
|
+
}
|
|
9719
|
+
}
|
|
8740
9720
|
}
|
|
8741
9721
|
];
|
|
9722
|
+
MINIMAL_STRIP_FIELDS = /* @__PURE__ */ new Set(["columns"]);
|
|
8742
9723
|
COMPACT_SCHEMA = {
|
|
8743
9724
|
nodes: {
|
|
8744
9725
|
i: "id",
|
|
@@ -8778,8 +9759,31 @@ Example: blast_points(node_id: "server/auth/middleware.ts", hops: 2) \u2192 retu
|
|
|
8778
9759
|
"variables",
|
|
8779
9760
|
"responses",
|
|
8780
9761
|
"params",
|
|
8781
|
-
"effects"
|
|
9762
|
+
"effects",
|
|
9763
|
+
"ui_labels",
|
|
9764
|
+
"notes"
|
|
8782
9765
|
]);
|
|
9766
|
+
NOTE_KIND_CATEGORY = {
|
|
9767
|
+
TODO: "actionable",
|
|
9768
|
+
FIXME: "actionable",
|
|
9769
|
+
HACK: "actionable",
|
|
9770
|
+
XXX: "actionable",
|
|
9771
|
+
REFACTOR: "actionable",
|
|
9772
|
+
OPTIMIZE: "actionable",
|
|
9773
|
+
REVIEW: "actionable",
|
|
9774
|
+
SECURITY: "warning",
|
|
9775
|
+
WARNING: "warning",
|
|
9776
|
+
IMPORTANT: "warning",
|
|
9777
|
+
CAUTION: "warning",
|
|
9778
|
+
DANGER: "warning",
|
|
9779
|
+
DEPRECATED: "warning",
|
|
9780
|
+
NOTE: "doc",
|
|
9781
|
+
TIP: "doc",
|
|
9782
|
+
SEE: "doc",
|
|
9783
|
+
NB: "doc",
|
|
9784
|
+
INFO: "doc",
|
|
9785
|
+
EXAMPLE: "doc"
|
|
9786
|
+
};
|
|
8783
9787
|
EST_CHARS_PER_NODE_FULL = {
|
|
8784
9788
|
ui: 300,
|
|
8785
9789
|
api: 300,
|