@launchsecure/launch-kit 0.0.29 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/beacon/beacon.mjs +2825 -1243
- 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/internal/screenshot.d.ts +26 -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/chart-client/assets/{index-CJ4mgRRF.css → index-CDIhdgWg.css} +1 -1
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/{index-DI5qSR_w.css → index-CfW4n40I.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/council-client/assets/{index-C_-vAM9L.css → index-CZim6x1u.css} +1 -1
- package/dist/council-client/index.html +2 -2
- package/dist/deck-client/assets/{_baseUniq-W2JQDmje.js → _baseUniq-DdHaBFYO.js} +1 -1
- package/dist/deck-client/assets/{arc-DIBWAId9.js → arc-D98e_18X.js} +1 -1
- package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-CAIRMvJK.js → architectureDiagram-Q4EWVU46-DNFZzh-4.js} +1 -1
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BeNaNiOi.js → blockDiagram-DXYQGD6D-DeQvGUdX.js} +1 -1
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-B9Ozi62h.js → c4Diagram-AHTNJAMY-B6ekZf1n.js} +1 -1
- package/dist/deck-client/assets/channel-DmR7Tyyt.js +1 -0
- package/dist/deck-client/assets/{chunk-4BX2VUAB-D7AZ47dt.js → chunk-4BX2VUAB-9aDWymq2.js} +1 -1
- package/dist/deck-client/assets/{chunk-4TB4RGXK-DnVnNPcI.js → chunk-4TB4RGXK-DtKQqaI7.js} +1 -1
- package/dist/deck-client/assets/{chunk-55IACEB6-UKYs-YNd.js → chunk-55IACEB6-COy9hEae.js} +1 -1
- package/dist/deck-client/assets/{chunk-EDXVE4YY-D43b-SKn.js → chunk-EDXVE4YY-D_f861An.js} +1 -1
- package/dist/deck-client/assets/{chunk-FMBD7UC4-QzBAoyyW.js → chunk-FMBD7UC4-CmuA5UKn.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-Cjif4r6W.js → chunk-OYMX7WX6-vT8z8D-0.js} +1 -1
- package/dist/deck-client/assets/{chunk-QZHKN3VN-CqLDirEI.js → chunk-QZHKN3VN-CTlwwg-R.js} +1 -1
- package/dist/deck-client/assets/{chunk-YZCP3GAM-_FQvmMs4.js → chunk-YZCP3GAM-C44yr620.js} +1 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-Bl4ozQWs.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-Bl4ozQWs.js +1 -0
- package/dist/deck-client/assets/clone-BAy58j24.js +1 -0
- package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-rfrocesE.js → cose-bilkent-S5V4N54A-DBB2J2nL.js} +1 -1
- package/dist/deck-client/assets/{dagre-KV5264BT-Bv_7DJat.js → dagre-KV5264BT-DxDTYbKl.js} +1 -1
- package/dist/deck-client/assets/{diagram-5BDNPKRD-4F1414G5.js → diagram-5BDNPKRD-DByWrWd1.js} +1 -1
- package/dist/deck-client/assets/{diagram-G4DWMVQ6-C4-Pszqm.js → diagram-G4DWMVQ6-B8B6ddMq.js} +1 -1
- package/dist/deck-client/assets/{diagram-MMDJMWI5-B647TIx9.js → diagram-MMDJMWI5-BMUZ2PWK.js} +1 -1
- package/dist/deck-client/assets/{diagram-TYMM5635-BFAqpezd.js → diagram-TYMM5635-Bk9e8BB-.js} +1 -1
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-BfBfrJOC.js → erDiagram-SMLLAGMA-DcOSwSol.js} +1 -1
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-DX9YAYes.js → flowDiagram-DWJPFMVM-DI-4BR0F.js} +1 -1
- package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-DCuiy7wF.js → ganttDiagram-T4ZO3ILL-BeZuXBoU.js} +1 -1
- package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-CGp1IXUh.js → gitGraphDiagram-UUTBAWPF-Bcki__f-.js} +1 -1
- package/dist/deck-client/assets/{graph-B7g8aoxv.js → graph-CifKx6G1.js} +1 -1
- package/dist/deck-client/assets/index-6sdqbm2o.js +2 -0
- package/dist/deck-client/assets/{index-DsIZ3LqL.css → index-BlTlhxFW.css} +1 -1
- package/dist/deck-client/assets/index-CB-qlwRT.js +1195 -0
- package/dist/deck-client/assets/{infoDiagram-42DDH7IO-L3fahMkF.js → infoDiagram-42DDH7IO-CReN1nFN.js} +1 -1
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-aS_EjWBZ.js → ishikawaDiagram-UXIWVN3A-CDF_VLN_.js} +1 -1
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-djTSQZF9.js → journeyDiagram-VCZTEJTY-DwgGrNVB.js} +1 -1
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CcTHo4CM.js → kanban-definition-6JOO6SKY-DB_zohh5.js} +1 -1
- package/dist/deck-client/assets/{layout-mEJiadb7.js → layout-DFfX1O3z.js} +1 -1
- package/dist/deck-client/assets/{linear-XgTKqyRu.js → linear-CtKb4EXj.js} +1 -1
- package/dist/deck-client/assets/{min-Ct9jZdpd.js → min-DCRRwUZv.js} +1 -1
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-BaFxCGNU.js → mindmap-definition-QFDTVHPH-D0QBOiFe.js} +1 -1
- package/dist/deck-client/assets/{pieDiagram-DEJITSTG-CIbYYjtw.js → pieDiagram-DEJITSTG-CD-EV5WB.js} +1 -1
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-D9EtCOvh.js → quadrantDiagram-34T5L4WZ-B-JXZ8xI.js} +1 -1
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-xeni9eVG.js → requirementDiagram-MS252O5E-D2_OK5Dp.js} +1 -1
- package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-LYeknz9h.js → sankeyDiagram-XADWPNL6-BbBJqVSC.js} +1 -1
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-RDbsKFZf.js → sequenceDiagram-FGHM5R23-Db8A-Rkk.js} +1 -1
- package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-BH1Zjglk.js → stateDiagram-FHFEXIEX-DGJnanjS.js} +1 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CR7riiab.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-IFXxKptt.js → timeline-definition-GMOUNBTQ-BRkr6T4w.js} +1 -1
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-D-sLkQs9.js → vennDiagram-DHZGUBPP-d0rsTqFo.js} +1 -1
- package/dist/deck-client/assets/{wardley-RL74JXVD-C010F8l4.js → wardley-RL74JXVD-2t7cMqdS.js} +1 -1
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-BTjjuDU3.js → wardleyDiagram-NUSXRM2D-DzboAsHh.js} +1 -1
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-AYbv92n-.js → xychartDiagram-5P7HB3ND-CgTP9u2V.js} +1 -1
- package/dist/deck-client/index.html +2 -2
- package/dist/server/beacon-monitor-entry.js +548 -6
- package/dist/server/chart-serve.js +917 -248
- package/dist/server/cli.js +2033 -385
- package/dist/server/deck-mcp-entry.js +141 -21
- package/dist/server/deck-serve.js +141 -21
- package/dist/server/graph-mcp-entry.js +1991 -333
- package/dist/server/init-entry.js +24 -13
- package/dist/server/orbit-entry.js +135 -7
- package/dist/server/parse-worker-entry.js +918 -247
- package/package.json +4 -2
- package/scaffolds/ls-marketplace/plugins/kit/skills/analyse/SKILL.md +180 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-array/SKILL.md +107 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-clear/SKILL.md +94 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-pulse/SKILL.md +82 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-scan/SKILL.md +66 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/blast-radius/SKILL.md +117 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief/SKILL.md +112 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/course/SKILL.md +84 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/debug/SKILL.md +85 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/deploy-check/SKILL.md +160 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/diagram/SKILL.md +152 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/orbit/SKILL.md +87 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/prototype/SKILL.md +110 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/recovery/SKILL.md +95 -0
- package/scaffolds/ls-marketplace/plugins/kit/{commands/show-mcp-status.md → skills/show-mcp-status/SKILL.md} +4 -4
- package/scaffolds/ls-marketplace/plugins/kit/skills/wireframe/SKILL.md +90 -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 +0 -7
- 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/index-Dg1r-WSN.js +0 -476
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BrV78NDR.js +0 -1
- 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
- /package/dist/chart-client/assets/{index-Ccy-DpI-.js → index-B__ARB8k.js} +0 -0
- /package/dist/client/assets/{index-Dp0_okva.js → index-h8kMzVtG.js} +0 -0
- /package/dist/council-client/assets/{index-Dt4zWKSj.js → index-CWaDcsFR.js} +0 -0
package/dist/server/cli.js
CHANGED
|
@@ -1752,7 +1752,7 @@ var require_usage_reader = __commonJS({
|
|
|
1752
1752
|
async readJsonlFile(filePath, cutoffTime) {
|
|
1753
1753
|
const entries = [];
|
|
1754
1754
|
const fileProcessedEntries = /* @__PURE__ */ new Set();
|
|
1755
|
-
return new Promise((
|
|
1755
|
+
return new Promise((resolve6) => {
|
|
1756
1756
|
const rl = readline.createInterface({
|
|
1757
1757
|
input: createReadStream(filePath),
|
|
1758
1758
|
crlfDelay: Infinity
|
|
@@ -1810,11 +1810,11 @@ var require_usage_reader = __commonJS({
|
|
|
1810
1810
|
}
|
|
1811
1811
|
});
|
|
1812
1812
|
rl.on("close", () => {
|
|
1813
|
-
|
|
1813
|
+
resolve6(entries);
|
|
1814
1814
|
});
|
|
1815
1815
|
rl.on("error", (error) => {
|
|
1816
1816
|
console.error("Error reading file:", filePath, error);
|
|
1817
|
-
|
|
1817
|
+
resolve6(entries);
|
|
1818
1818
|
});
|
|
1819
1819
|
});
|
|
1820
1820
|
}
|
|
@@ -3623,7 +3623,7 @@ var require_src = __commonJS({
|
|
|
3623
3623
|
if (session.active) throw new Error(`Agent already running in session ${sessionId}`);
|
|
3624
3624
|
const { command, args = [], env = {} } = options;
|
|
3625
3625
|
if (!command) throw new Error("startScriptInSession requires a command");
|
|
3626
|
-
return new Promise((
|
|
3626
|
+
return new Promise((resolve6, reject) => {
|
|
3627
3627
|
this.scriptBridge.startSession(sessionId, {
|
|
3628
3628
|
command,
|
|
3629
3629
|
args,
|
|
@@ -3645,7 +3645,7 @@ var require_src = __commonJS({
|
|
|
3645
3645
|
session.lastActivity = /* @__PURE__ */ new Date();
|
|
3646
3646
|
this.broadcastToSession(sessionId, { type: "script_stopped", sessionId });
|
|
3647
3647
|
if (exitCode === 0) {
|
|
3648
|
-
|
|
3648
|
+
resolve6({ code: exitCode, signal });
|
|
3649
3649
|
} else {
|
|
3650
3650
|
reject(new Error(`Script exited with code ${exitCode}`));
|
|
3651
3651
|
}
|
|
@@ -5567,9 +5567,9 @@ var require_dispatcher_base = __commonJS({
|
|
|
5567
5567
|
}
|
|
5568
5568
|
close(callback) {
|
|
5569
5569
|
if (callback === void 0) {
|
|
5570
|
-
return new Promise((
|
|
5570
|
+
return new Promise((resolve6, reject) => {
|
|
5571
5571
|
this.close((err2, data) => {
|
|
5572
|
-
return err2 ? reject(err2) :
|
|
5572
|
+
return err2 ? reject(err2) : resolve6(data);
|
|
5573
5573
|
});
|
|
5574
5574
|
});
|
|
5575
5575
|
}
|
|
@@ -5607,12 +5607,12 @@ var require_dispatcher_base = __commonJS({
|
|
|
5607
5607
|
err2 = null;
|
|
5608
5608
|
}
|
|
5609
5609
|
if (callback === void 0) {
|
|
5610
|
-
return new Promise((
|
|
5610
|
+
return new Promise((resolve6, reject) => {
|
|
5611
5611
|
this.destroy(err2, (err3, data) => {
|
|
5612
5612
|
return err3 ? (
|
|
5613
5613
|
/* istanbul ignore next: should never error */
|
|
5614
5614
|
reject(err3)
|
|
5615
|
-
) :
|
|
5615
|
+
) : resolve6(data);
|
|
5616
5616
|
});
|
|
5617
5617
|
});
|
|
5618
5618
|
}
|
|
@@ -7879,8 +7879,8 @@ var require_util2 = __commonJS({
|
|
|
7879
7879
|
function createDeferredPromise() {
|
|
7880
7880
|
let res;
|
|
7881
7881
|
let rej;
|
|
7882
|
-
const promise = new Promise((
|
|
7883
|
-
res =
|
|
7882
|
+
const promise = new Promise((resolve6, reject) => {
|
|
7883
|
+
res = resolve6;
|
|
7884
7884
|
rej = reject;
|
|
7885
7885
|
});
|
|
7886
7886
|
return { promise, resolve: res, reject: rej };
|
|
@@ -10021,12 +10021,12 @@ upgrade: ${upgrade}\r
|
|
|
10021
10021
|
cb();
|
|
10022
10022
|
}
|
|
10023
10023
|
}
|
|
10024
|
-
const waitForDrain = () => new Promise((
|
|
10024
|
+
const waitForDrain = () => new Promise((resolve6, reject) => {
|
|
10025
10025
|
assert(callback === null);
|
|
10026
10026
|
if (socket[kError]) {
|
|
10027
10027
|
reject(socket[kError]);
|
|
10028
10028
|
} else {
|
|
10029
|
-
callback =
|
|
10029
|
+
callback = resolve6;
|
|
10030
10030
|
}
|
|
10031
10031
|
});
|
|
10032
10032
|
socket.on("close", onDrain).on("drain", onDrain);
|
|
@@ -10663,12 +10663,12 @@ var require_client_h2 = __commonJS({
|
|
|
10663
10663
|
cb();
|
|
10664
10664
|
}
|
|
10665
10665
|
}
|
|
10666
|
-
const waitForDrain = () => new Promise((
|
|
10666
|
+
const waitForDrain = () => new Promise((resolve6, reject) => {
|
|
10667
10667
|
assert(callback === null);
|
|
10668
10668
|
if (socket[kError]) {
|
|
10669
10669
|
reject(socket[kError]);
|
|
10670
10670
|
} else {
|
|
10671
|
-
callback =
|
|
10671
|
+
callback = resolve6;
|
|
10672
10672
|
}
|
|
10673
10673
|
});
|
|
10674
10674
|
h2stream.on("close", onDrain).on("drain", onDrain);
|
|
@@ -11146,16 +11146,16 @@ var require_client = __commonJS({
|
|
|
11146
11146
|
return this[kNeedDrain] < 2;
|
|
11147
11147
|
}
|
|
11148
11148
|
async [kClose]() {
|
|
11149
|
-
return new Promise((
|
|
11149
|
+
return new Promise((resolve6) => {
|
|
11150
11150
|
if (this[kSize]) {
|
|
11151
|
-
this[kClosedResolve] =
|
|
11151
|
+
this[kClosedResolve] = resolve6;
|
|
11152
11152
|
} else {
|
|
11153
|
-
|
|
11153
|
+
resolve6(null);
|
|
11154
11154
|
}
|
|
11155
11155
|
});
|
|
11156
11156
|
}
|
|
11157
11157
|
async [kDestroy](err2) {
|
|
11158
|
-
return new Promise((
|
|
11158
|
+
return new Promise((resolve6) => {
|
|
11159
11159
|
const requests = this[kQueue].splice(this[kPendingIdx]);
|
|
11160
11160
|
for (let i = 0; i < requests.length; i++) {
|
|
11161
11161
|
const request = requests[i];
|
|
@@ -11166,7 +11166,7 @@ var require_client = __commonJS({
|
|
|
11166
11166
|
this[kClosedResolve]();
|
|
11167
11167
|
this[kClosedResolve] = null;
|
|
11168
11168
|
}
|
|
11169
|
-
|
|
11169
|
+
resolve6(null);
|
|
11170
11170
|
};
|
|
11171
11171
|
if (this[kHTTPContext]) {
|
|
11172
11172
|
this[kHTTPContext].destroy(err2, callback);
|
|
@@ -11217,7 +11217,7 @@ var require_client = __commonJS({
|
|
|
11217
11217
|
});
|
|
11218
11218
|
}
|
|
11219
11219
|
try {
|
|
11220
|
-
const socket = await new Promise((
|
|
11220
|
+
const socket = await new Promise((resolve6, reject) => {
|
|
11221
11221
|
client[kConnector]({
|
|
11222
11222
|
host,
|
|
11223
11223
|
hostname,
|
|
@@ -11229,7 +11229,7 @@ var require_client = __commonJS({
|
|
|
11229
11229
|
if (err2) {
|
|
11230
11230
|
reject(err2);
|
|
11231
11231
|
} else {
|
|
11232
|
-
|
|
11232
|
+
resolve6(socket2);
|
|
11233
11233
|
}
|
|
11234
11234
|
});
|
|
11235
11235
|
});
|
|
@@ -11565,8 +11565,8 @@ var require_pool_base = __commonJS({
|
|
|
11565
11565
|
if (this[kQueue].isEmpty()) {
|
|
11566
11566
|
await Promise.all(this[kClients].map((c) => c.close()));
|
|
11567
11567
|
} else {
|
|
11568
|
-
await new Promise((
|
|
11569
|
-
this[kClosedResolve] =
|
|
11568
|
+
await new Promise((resolve6) => {
|
|
11569
|
+
this[kClosedResolve] = resolve6;
|
|
11570
11570
|
});
|
|
11571
11571
|
}
|
|
11572
11572
|
}
|
|
@@ -12781,7 +12781,7 @@ var require_readable = __commonJS({
|
|
|
12781
12781
|
if (this._readableState.closeEmitted) {
|
|
12782
12782
|
return null;
|
|
12783
12783
|
}
|
|
12784
|
-
return await new Promise((
|
|
12784
|
+
return await new Promise((resolve6, reject) => {
|
|
12785
12785
|
if (this[kContentLength] > limit) {
|
|
12786
12786
|
this.destroy(new AbortError());
|
|
12787
12787
|
}
|
|
@@ -12794,7 +12794,7 @@ var require_readable = __commonJS({
|
|
|
12794
12794
|
if (signal?.aborted) {
|
|
12795
12795
|
reject(signal.reason ?? new AbortError());
|
|
12796
12796
|
} else {
|
|
12797
|
-
|
|
12797
|
+
resolve6(null);
|
|
12798
12798
|
}
|
|
12799
12799
|
}).on("error", noop).on("data", function(chunk) {
|
|
12800
12800
|
limit -= chunk.length;
|
|
@@ -12813,7 +12813,7 @@ var require_readable = __commonJS({
|
|
|
12813
12813
|
}
|
|
12814
12814
|
async function consume(stream, type) {
|
|
12815
12815
|
assert(!stream[kConsume]);
|
|
12816
|
-
return new Promise((
|
|
12816
|
+
return new Promise((resolve6, reject) => {
|
|
12817
12817
|
if (isUnusable(stream)) {
|
|
12818
12818
|
const rState = stream._readableState;
|
|
12819
12819
|
if (rState.destroyed && rState.closeEmitted === false) {
|
|
@@ -12830,7 +12830,7 @@ var require_readable = __commonJS({
|
|
|
12830
12830
|
stream[kConsume] = {
|
|
12831
12831
|
type,
|
|
12832
12832
|
stream,
|
|
12833
|
-
resolve:
|
|
12833
|
+
resolve: resolve6,
|
|
12834
12834
|
reject,
|
|
12835
12835
|
length: 0,
|
|
12836
12836
|
body: []
|
|
@@ -12900,18 +12900,18 @@ var require_readable = __commonJS({
|
|
|
12900
12900
|
return buffer;
|
|
12901
12901
|
}
|
|
12902
12902
|
function consumeEnd(consume2) {
|
|
12903
|
-
const { type, body, resolve:
|
|
12903
|
+
const { type, body, resolve: resolve6, stream, length } = consume2;
|
|
12904
12904
|
try {
|
|
12905
12905
|
if (type === "text") {
|
|
12906
|
-
|
|
12906
|
+
resolve6(chunksDecode(body, length));
|
|
12907
12907
|
} else if (type === "json") {
|
|
12908
|
-
|
|
12908
|
+
resolve6(JSON.parse(chunksDecode(body, length)));
|
|
12909
12909
|
} else if (type === "arrayBuffer") {
|
|
12910
|
-
|
|
12910
|
+
resolve6(chunksConcat(body, length).buffer);
|
|
12911
12911
|
} else if (type === "blob") {
|
|
12912
|
-
|
|
12912
|
+
resolve6(new Blob(body, { type: stream[kContentType] }));
|
|
12913
12913
|
} else if (type === "bytes") {
|
|
12914
|
-
|
|
12914
|
+
resolve6(chunksConcat(body, length));
|
|
12915
12915
|
}
|
|
12916
12916
|
consumeFinish(consume2);
|
|
12917
12917
|
} catch (err2) {
|
|
@@ -13168,9 +13168,9 @@ var require_api_request = __commonJS({
|
|
|
13168
13168
|
};
|
|
13169
13169
|
function request(opts, callback) {
|
|
13170
13170
|
if (callback === void 0) {
|
|
13171
|
-
return new Promise((
|
|
13171
|
+
return new Promise((resolve6, reject) => {
|
|
13172
13172
|
request.call(this, opts, (err2, data) => {
|
|
13173
|
-
return err2 ? reject(err2) :
|
|
13173
|
+
return err2 ? reject(err2) : resolve6(data);
|
|
13174
13174
|
});
|
|
13175
13175
|
});
|
|
13176
13176
|
}
|
|
@@ -13393,9 +13393,9 @@ var require_api_stream = __commonJS({
|
|
|
13393
13393
|
};
|
|
13394
13394
|
function stream(opts, factory, callback) {
|
|
13395
13395
|
if (callback === void 0) {
|
|
13396
|
-
return new Promise((
|
|
13396
|
+
return new Promise((resolve6, reject) => {
|
|
13397
13397
|
stream.call(this, opts, factory, (err2, data) => {
|
|
13398
|
-
return err2 ? reject(err2) :
|
|
13398
|
+
return err2 ? reject(err2) : resolve6(data);
|
|
13399
13399
|
});
|
|
13400
13400
|
});
|
|
13401
13401
|
}
|
|
@@ -13680,9 +13680,9 @@ var require_api_upgrade = __commonJS({
|
|
|
13680
13680
|
};
|
|
13681
13681
|
function upgrade(opts, callback) {
|
|
13682
13682
|
if (callback === void 0) {
|
|
13683
|
-
return new Promise((
|
|
13683
|
+
return new Promise((resolve6, reject) => {
|
|
13684
13684
|
upgrade.call(this, opts, (err2, data) => {
|
|
13685
|
-
return err2 ? reject(err2) :
|
|
13685
|
+
return err2 ? reject(err2) : resolve6(data);
|
|
13686
13686
|
});
|
|
13687
13687
|
});
|
|
13688
13688
|
}
|
|
@@ -13774,9 +13774,9 @@ var require_api_connect = __commonJS({
|
|
|
13774
13774
|
};
|
|
13775
13775
|
function connect(opts, callback) {
|
|
13776
13776
|
if (callback === void 0) {
|
|
13777
|
-
return new Promise((
|
|
13777
|
+
return new Promise((resolve6, reject) => {
|
|
13778
13778
|
connect.call(this, opts, (err2, data) => {
|
|
13779
|
-
return err2 ? reject(err2) :
|
|
13779
|
+
return err2 ? reject(err2) : resolve6(data);
|
|
13780
13780
|
});
|
|
13781
13781
|
});
|
|
13782
13782
|
}
|
|
@@ -17638,7 +17638,7 @@ var require_fetch = __commonJS({
|
|
|
17638
17638
|
function dispatch({ body }) {
|
|
17639
17639
|
const url = requestCurrentURL(request);
|
|
17640
17640
|
const agent = fetchParams.controller.dispatcher;
|
|
17641
|
-
return new Promise((
|
|
17641
|
+
return new Promise((resolve6, reject) => agent.dispatch(
|
|
17642
17642
|
{
|
|
17643
17643
|
path: url.pathname + url.search,
|
|
17644
17644
|
origin: url.origin,
|
|
@@ -17714,7 +17714,7 @@ var require_fetch = __commonJS({
|
|
|
17714
17714
|
}
|
|
17715
17715
|
}
|
|
17716
17716
|
const onError = this.onError.bind(this);
|
|
17717
|
-
|
|
17717
|
+
resolve6({
|
|
17718
17718
|
status,
|
|
17719
17719
|
statusText,
|
|
17720
17720
|
headersList,
|
|
@@ -17760,7 +17760,7 @@ var require_fetch = __commonJS({
|
|
|
17760
17760
|
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
17761
17761
|
headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString("latin1"), true);
|
|
17762
17762
|
}
|
|
17763
|
-
|
|
17763
|
+
resolve6({
|
|
17764
17764
|
status,
|
|
17765
17765
|
statusText: STATUS_CODES[status],
|
|
17766
17766
|
headersList,
|
|
@@ -21436,8 +21436,8 @@ var require_util8 = __commonJS({
|
|
|
21436
21436
|
return true;
|
|
21437
21437
|
}
|
|
21438
21438
|
function delay(ms) {
|
|
21439
|
-
return new Promise((
|
|
21440
|
-
setTimeout(
|
|
21439
|
+
return new Promise((resolve6) => {
|
|
21440
|
+
setTimeout(resolve6, ms).unref();
|
|
21441
21441
|
});
|
|
21442
21442
|
}
|
|
21443
21443
|
module2.exports = {
|
|
@@ -22628,7 +22628,7 @@ function resolveWorkerPath() {
|
|
|
22628
22628
|
);
|
|
22629
22629
|
}
|
|
22630
22630
|
function runParseInWorker(req) {
|
|
22631
|
-
return new Promise((
|
|
22631
|
+
return new Promise((resolve6, reject) => {
|
|
22632
22632
|
let workerPath;
|
|
22633
22633
|
try {
|
|
22634
22634
|
workerPath = resolveWorkerPath();
|
|
@@ -22647,7 +22647,7 @@ function runParseInWorker(req) {
|
|
|
22647
22647
|
};
|
|
22648
22648
|
worker.on("message", (reply) => {
|
|
22649
22649
|
if (reply.ok) {
|
|
22650
|
-
finish(() =>
|
|
22650
|
+
finish(() => resolve6({ results: reply.results, failedFiles: reply.failedFiles }));
|
|
22651
22651
|
} else {
|
|
22652
22652
|
const err2 = new Error(reply.error.message);
|
|
22653
22653
|
err2.name = reply.error.name;
|
|
@@ -22891,7 +22891,7 @@ var init_freshness = __esm({
|
|
|
22891
22891
|
function getAvailableLayers(rootDir) {
|
|
22892
22892
|
const dir = (0, import_node_path13.join)(rootDir, GRAPHS_DIR2);
|
|
22893
22893
|
if (!(0, import_node_fs11.existsSync)(dir)) return [];
|
|
22894
|
-
return (0, import_node_fs11.readdirSync)(dir).filter((f) => f.endsWith(".json") && !NON_LAYER_GRAPH_FILES.has(f)).map((f) => f.replace(".json", ""));
|
|
22894
|
+
return (0, import_node_fs11.readdirSync)(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".") && !NON_LAYER_GRAPH_FILES.has(f)).map((f) => f.replace(".json", ""));
|
|
22895
22895
|
}
|
|
22896
22896
|
function graphsDir(rootDir) {
|
|
22897
22897
|
return (0, import_node_path13.join)(rootDir, GRAPHS_DIR2);
|
|
@@ -24089,6 +24089,8 @@ function extractDeep(absPath) {
|
|
|
24089
24089
|
false
|
|
24090
24090
|
);
|
|
24091
24091
|
const hasEffects = Object.keys(fileEffects).length > 0;
|
|
24092
|
+
const uiLabels = collectUiLabels(root);
|
|
24093
|
+
const notes = collectNotes(root);
|
|
24092
24094
|
return {
|
|
24093
24095
|
elements,
|
|
24094
24096
|
stateVars,
|
|
@@ -24096,10 +24098,77 @@ function extractDeep(absPath) {
|
|
|
24096
24098
|
variables,
|
|
24097
24099
|
responses,
|
|
24098
24100
|
params,
|
|
24099
|
-
...hasEffects ? { effects: fileEffects } : {}
|
|
24101
|
+
...hasEffects ? { effects: fileEffects } : {},
|
|
24102
|
+
...uiLabels.length > 0 ? { ui_labels: uiLabels } : {},
|
|
24103
|
+
...notes.length > 0 ? { notes } : {}
|
|
24100
24104
|
};
|
|
24101
24105
|
}
|
|
24102
|
-
|
|
24106
|
+
function collectNotes(root) {
|
|
24107
|
+
const out = [];
|
|
24108
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24109
|
+
function visit(node) {
|
|
24110
|
+
if (out.length >= NOTES_MAX) return;
|
|
24111
|
+
if (node.type === "comment") {
|
|
24112
|
+
const text = node.text;
|
|
24113
|
+
const startRow = node.startPosition.row;
|
|
24114
|
+
NOTE_REGEX.lastIndex = 0;
|
|
24115
|
+
let m;
|
|
24116
|
+
while ((m = NOTE_REGEX.exec(text)) !== null) {
|
|
24117
|
+
const kind = m[1];
|
|
24118
|
+
const author = m[2];
|
|
24119
|
+
const body = m[3];
|
|
24120
|
+
if (!kind || !body) continue;
|
|
24121
|
+
const newlinesBefore = (text.slice(0, m.index).match(/\n/g) ?? []).length;
|
|
24122
|
+
const line = startRow + 1 + newlinesBefore;
|
|
24123
|
+
const key = `${line}:${kind}`;
|
|
24124
|
+
if (seen.has(key)) continue;
|
|
24125
|
+
seen.add(key);
|
|
24126
|
+
const note = {
|
|
24127
|
+
kind,
|
|
24128
|
+
text: body.length <= 200 ? body : body.slice(0, 200) + "...",
|
|
24129
|
+
line
|
|
24130
|
+
};
|
|
24131
|
+
if (author) note.author = author;
|
|
24132
|
+
out.push(note);
|
|
24133
|
+
if (out.length >= NOTES_MAX) return;
|
|
24134
|
+
}
|
|
24135
|
+
}
|
|
24136
|
+
for (const child of node.namedChildren) {
|
|
24137
|
+
visit(child);
|
|
24138
|
+
if (out.length >= NOTES_MAX) return;
|
|
24139
|
+
}
|
|
24140
|
+
}
|
|
24141
|
+
visit(root);
|
|
24142
|
+
return out;
|
|
24143
|
+
}
|
|
24144
|
+
function collectUiLabels(root) {
|
|
24145
|
+
const out = [];
|
|
24146
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24147
|
+
function visit(node) {
|
|
24148
|
+
if (out.length >= UI_LABELS_MAX) return;
|
|
24149
|
+
if (node.type === "pair") {
|
|
24150
|
+
const key = node.childForFieldName("key");
|
|
24151
|
+
const val = node.childForFieldName("value");
|
|
24152
|
+
if (key && val) {
|
|
24153
|
+
const keyText = key.type === "property_identifier" ? key.text : stringLiteralValue(key) ?? key.text;
|
|
24154
|
+
if (UI_LABEL_KEYS.has(keyText)) {
|
|
24155
|
+
const strVal = stringLiteralValue(val);
|
|
24156
|
+
if (strVal && strVal.length > 0 && strVal.length <= 200 && !seen.has(strVal)) {
|
|
24157
|
+
seen.add(strVal);
|
|
24158
|
+
out.push(strVal);
|
|
24159
|
+
}
|
|
24160
|
+
}
|
|
24161
|
+
}
|
|
24162
|
+
}
|
|
24163
|
+
for (const child of node.namedChildren) {
|
|
24164
|
+
visit(child);
|
|
24165
|
+
if (out.length >= UI_LABELS_MAX) return;
|
|
24166
|
+
}
|
|
24167
|
+
}
|
|
24168
|
+
visit(root);
|
|
24169
|
+
return out;
|
|
24170
|
+
}
|
|
24171
|
+
var import_node_fs15, import_node_path17, 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;
|
|
24103
24172
|
var init_ts_extractor = __esm({
|
|
24104
24173
|
"src/server/graph/core/ts-extractor.ts"() {
|
|
24105
24174
|
"use strict";
|
|
@@ -24218,6 +24287,27 @@ var init_ts_extractor = __esm({
|
|
|
24218
24287
|
"selected",
|
|
24219
24288
|
"disabled"
|
|
24220
24289
|
]);
|
|
24290
|
+
UI_LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
24291
|
+
"label",
|
|
24292
|
+
"title",
|
|
24293
|
+
"name",
|
|
24294
|
+
"text",
|
|
24295
|
+
"description",
|
|
24296
|
+
"placeholder",
|
|
24297
|
+
"tooltip",
|
|
24298
|
+
"heading",
|
|
24299
|
+
"subheading",
|
|
24300
|
+
"sheetTitle",
|
|
24301
|
+
"sheetDescription",
|
|
24302
|
+
"caption",
|
|
24303
|
+
"cta",
|
|
24304
|
+
"buttonText",
|
|
24305
|
+
"emptyText",
|
|
24306
|
+
"subtitle"
|
|
24307
|
+
]);
|
|
24308
|
+
UI_LABELS_MAX = 200;
|
|
24309
|
+
NOTE_REGEX = /^\s*(?:\/\/|\/\*+|\*)\s*([A-Z][A-Z0-9_]{1,15})(?:\(([^)]+)\))?:\s*(\S.*?)\s*(?:\*\/)?$/gm;
|
|
24310
|
+
NOTES_MAX = 100;
|
|
24221
24311
|
}
|
|
24222
24312
|
});
|
|
24223
24313
|
|
|
@@ -24229,7 +24319,7 @@ __export(watcher_exports, {
|
|
|
24229
24319
|
function isIgnoredPath(rel) {
|
|
24230
24320
|
if (rel.startsWith(GRAPHS_RELATIVE)) return true;
|
|
24231
24321
|
if (rel.endsWith(".lock") || rel.endsWith(".log")) return true;
|
|
24232
|
-
for (const part of rel.split(
|
|
24322
|
+
for (const part of rel.split(import_node_path32.sep)) {
|
|
24233
24323
|
if (IGNORE_SEGMENTS.has(part)) return true;
|
|
24234
24324
|
}
|
|
24235
24325
|
return false;
|
|
@@ -24271,7 +24361,7 @@ function startGraphWatcher(rootDir, opts = {}) {
|
|
|
24271
24361
|
regenerating = false;
|
|
24272
24362
|
}
|
|
24273
24363
|
}
|
|
24274
|
-
const watcher = (0,
|
|
24364
|
+
const watcher = (0, import_node_fs27.watch)(rootDir, { recursive: true }, (event, filename) => {
|
|
24275
24365
|
if (!filename) return;
|
|
24276
24366
|
const rel = filename.toString();
|
|
24277
24367
|
if (process.env.LAUNCH_CHART_WATCH_TRACE === "1") {
|
|
@@ -24304,12 +24394,12 @@ function startGraphWatcher(rootDir, opts = {}) {
|
|
|
24304
24394
|
freshness: () => getFreshnessTracker(rootDir).get()
|
|
24305
24395
|
};
|
|
24306
24396
|
}
|
|
24307
|
-
var
|
|
24397
|
+
var import_node_fs27, import_node_path32, IGNORE_SEGMENTS, TRIGGER_EXTENSIONS, GRAPHS_RELATIVE;
|
|
24308
24398
|
var init_watcher = __esm({
|
|
24309
24399
|
"src/server/graph/core/watcher.ts"() {
|
|
24310
24400
|
"use strict";
|
|
24311
|
-
|
|
24312
|
-
|
|
24401
|
+
import_node_fs27 = require("node:fs");
|
|
24402
|
+
import_node_path32 = require("node:path");
|
|
24313
24403
|
init_launch_kit_paths();
|
|
24314
24404
|
init_graph();
|
|
24315
24405
|
init_freshness();
|
|
@@ -24338,7 +24428,7 @@ var init_watcher = __esm({
|
|
|
24338
24428
|
".prisma",
|
|
24339
24429
|
".sql"
|
|
24340
24430
|
]);
|
|
24341
|
-
GRAPHS_RELATIVE = (0,
|
|
24431
|
+
GRAPHS_RELATIVE = (0, import_node_path32.join)(LAUNCHSECURE_DIR, "graphs");
|
|
24342
24432
|
}
|
|
24343
24433
|
});
|
|
24344
24434
|
|
|
@@ -26015,7 +26105,7 @@ var PostImplLaunchExecutor = class {
|
|
|
26015
26105
|
return 3001;
|
|
26016
26106
|
}
|
|
26017
26107
|
startDevServer(port, databaseUrl) {
|
|
26018
|
-
return new Promise((
|
|
26108
|
+
return new Promise((resolve6) => {
|
|
26019
26109
|
const env = { ...process.env, PORT: String(port), ...databaseUrl ? { DATABASE_URL: databaseUrl } : {} };
|
|
26020
26110
|
this.devProcess = (0, import_child_process3.spawn)("npm", ["run", "dev"], {
|
|
26021
26111
|
cwd: this.workingDir,
|
|
@@ -26027,7 +26117,7 @@ var PostImplLaunchExecutor = class {
|
|
|
26027
26117
|
const timeout = setTimeout(() => {
|
|
26028
26118
|
if (!resolved) {
|
|
26029
26119
|
resolved = true;
|
|
26030
|
-
this.healthCheck(port).then(
|
|
26120
|
+
this.healthCheck(port).then(resolve6);
|
|
26031
26121
|
}
|
|
26032
26122
|
}, 15e3);
|
|
26033
26123
|
const onData = (data) => {
|
|
@@ -26036,7 +26126,7 @@ var PostImplLaunchExecutor = class {
|
|
|
26036
26126
|
if (!resolved) {
|
|
26037
26127
|
resolved = true;
|
|
26038
26128
|
clearTimeout(timeout);
|
|
26039
|
-
|
|
26129
|
+
resolve6(true);
|
|
26040
26130
|
}
|
|
26041
26131
|
}
|
|
26042
26132
|
};
|
|
@@ -26047,7 +26137,7 @@ var PostImplLaunchExecutor = class {
|
|
|
26047
26137
|
if (!resolved) {
|
|
26048
26138
|
resolved = true;
|
|
26049
26139
|
clearTimeout(timeout);
|
|
26050
|
-
|
|
26140
|
+
resolve6(false);
|
|
26051
26141
|
}
|
|
26052
26142
|
});
|
|
26053
26143
|
this.devProcess.unref();
|
|
@@ -27392,7 +27482,7 @@ var ProjectMcpClient = class {
|
|
|
27392
27482
|
this.initialized = true;
|
|
27393
27483
|
}
|
|
27394
27484
|
send(body) {
|
|
27395
|
-
return new Promise((
|
|
27485
|
+
return new Promise((resolve6, reject) => {
|
|
27396
27486
|
const headers = {
|
|
27397
27487
|
...this.headers,
|
|
27398
27488
|
"Content-Length": String(Buffer.byteLength(body))
|
|
@@ -27417,7 +27507,7 @@ var ProjectMcpClient = class {
|
|
|
27417
27507
|
return;
|
|
27418
27508
|
}
|
|
27419
27509
|
const sid = res.headers["mcp-session-id"];
|
|
27420
|
-
|
|
27510
|
+
resolve6({ body: text, sessionId: typeof sid === "string" ? sid : void 0 });
|
|
27421
27511
|
});
|
|
27422
27512
|
}
|
|
27423
27513
|
);
|
|
@@ -28344,16 +28434,16 @@ var CloudflaredTunnel = class extends import_node_events.EventEmitter {
|
|
|
28344
28434
|
const tun = this.tunnel;
|
|
28345
28435
|
this.tunnel = null;
|
|
28346
28436
|
if (!tun) return;
|
|
28347
|
-
return new Promise((
|
|
28437
|
+
return new Promise((resolve6) => {
|
|
28348
28438
|
tun.once("exit", () => {
|
|
28349
28439
|
this.emit("stopped");
|
|
28350
|
-
|
|
28440
|
+
resolve6();
|
|
28351
28441
|
});
|
|
28352
28442
|
try {
|
|
28353
28443
|
tun.stop();
|
|
28354
28444
|
} catch {
|
|
28355
28445
|
}
|
|
28356
|
-
setTimeout(() =>
|
|
28446
|
+
setTimeout(() => resolve6(), 6e3);
|
|
28357
28447
|
});
|
|
28358
28448
|
}
|
|
28359
28449
|
spawnOnce() {
|
|
@@ -29007,10 +29097,10 @@ function streamTranscript(opts) {
|
|
|
29007
29097
|
}
|
|
29008
29098
|
|
|
29009
29099
|
// src/server/graph-mcp.ts
|
|
29010
|
-
var
|
|
29011
|
-
var
|
|
29100
|
+
var import_node_fs28 = require("node:fs");
|
|
29101
|
+
var import_node_path33 = require("node:path");
|
|
29012
29102
|
var import_node_child_process3 = require("node:child_process");
|
|
29013
|
-
var
|
|
29103
|
+
var import_node_os6 = require("node:os");
|
|
29014
29104
|
init_launch_kit_paths();
|
|
29015
29105
|
init_graph();
|
|
29016
29106
|
|
|
@@ -29753,6 +29843,7 @@ function generate(rootDir) {
|
|
|
29753
29843
|
responses: deep.responses,
|
|
29754
29844
|
params: deep.params,
|
|
29755
29845
|
...deep.effects ? { effects: deep.effects } : {},
|
|
29846
|
+
...deep.notes ? { notes: deep.notes } : {},
|
|
29756
29847
|
_dbCalls: dbCalls
|
|
29757
29848
|
// temp: used for cross-ref building below
|
|
29758
29849
|
});
|
|
@@ -29773,6 +29864,8 @@ function generate(rootDir) {
|
|
|
29773
29864
|
conditions: deep.conditions,
|
|
29774
29865
|
variables: deep.variables,
|
|
29775
29866
|
...deep.effects ? { effects: deep.effects } : {},
|
|
29867
|
+
...deep.ui_labels ? { ui_labels: deep.ui_labels } : {},
|
|
29868
|
+
...deep.notes ? { notes: deep.notes } : {},
|
|
29776
29869
|
...authWrappers.length > 0 ? { auth: authWrappers } : {},
|
|
29777
29870
|
...dbCalls.length > 0 ? { _dbCalls: dbCalls } : {}
|
|
29778
29871
|
});
|
|
@@ -30374,6 +30467,7 @@ var prismaSchemaParser = {
|
|
|
30374
30467
|
// src/server/graph/parsers/db/sql-migrations.ts
|
|
30375
30468
|
var import_node_fs18 = require("node:fs");
|
|
30376
30469
|
var import_node_path19 = require("node:path");
|
|
30470
|
+
var import_pgsql_parser = require("pgsql-parser");
|
|
30377
30471
|
init_config();
|
|
30378
30472
|
var PG_TO_PRISMA = {
|
|
30379
30473
|
"TEXT": "String",
|
|
@@ -30405,243 +30499,6 @@ function pgTypeToPrisma(pgType) {
|
|
|
30405
30499
|
const upper = pgType.toUpperCase().trim();
|
|
30406
30500
|
return PG_TO_PRISMA[upper] ?? upper;
|
|
30407
30501
|
}
|
|
30408
|
-
var ID = `(?:"[\\w$]+"|[\\w$]+)`;
|
|
30409
|
-
var QID = `(?:${ID}\\.)?${ID}`;
|
|
30410
|
-
function bareName(captured) {
|
|
30411
|
-
const parts = captured.split(".");
|
|
30412
|
-
const last = parts[parts.length - 1];
|
|
30413
|
-
return last.replace(/^"(.*)"$/, "$1").trim();
|
|
30414
|
-
}
|
|
30415
|
-
function parseCreateTable(sql, state) {
|
|
30416
|
-
const re = new RegExp(
|
|
30417
|
-
`CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${QID})\\s*\\(([\\s\\S]*?)\\);`,
|
|
30418
|
-
"gi"
|
|
30419
|
-
);
|
|
30420
|
-
let m;
|
|
30421
|
-
while ((m = re.exec(sql)) !== null) {
|
|
30422
|
-
const tableName = bareName(m[1]);
|
|
30423
|
-
const body = m[2];
|
|
30424
|
-
const columns = /* @__PURE__ */ new Map();
|
|
30425
|
-
let primaryCol = null;
|
|
30426
|
-
const inlineFks = [];
|
|
30427
|
-
const lines = splitTopLevelCommas(body);
|
|
30428
|
-
for (const raw of lines) {
|
|
30429
|
-
const trimmed = raw.trim().replace(/,\s*$/, "");
|
|
30430
|
-
if (!trimmed || trimmed.startsWith("--")) continue;
|
|
30431
|
-
const namedPk = trimmed.match(new RegExp(`^CONSTRAINT\\s+${ID}\\s+PRIMARY\\s+KEY\\s*\\(\\s*(${QID})`, "i"));
|
|
30432
|
-
if (namedPk) {
|
|
30433
|
-
primaryCol = bareName(namedPk[1]);
|
|
30434
|
-
continue;
|
|
30435
|
-
}
|
|
30436
|
-
const tablePk = trimmed.match(new RegExp(`^PRIMARY\\s+KEY\\s*\\(\\s*(${QID})`, "i"));
|
|
30437
|
-
if (tablePk) {
|
|
30438
|
-
primaryCol = bareName(tablePk[1]);
|
|
30439
|
-
continue;
|
|
30440
|
-
}
|
|
30441
|
-
if (/^UNIQUE\s*\(/i.test(trimmed)) continue;
|
|
30442
|
-
const namedFk = trimmed.match(new RegExp(
|
|
30443
|
-
`^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+)?))?`,
|
|
30444
|
-
"i"
|
|
30445
|
-
));
|
|
30446
|
-
if (namedFk) {
|
|
30447
|
-
inlineFks.push({
|
|
30448
|
-
constraintName: bareName(namedFk[1]),
|
|
30449
|
-
sourceTable: tableName,
|
|
30450
|
-
sourceColumn: bareName(namedFk[2]),
|
|
30451
|
-
targetTable: bareName(namedFk[3]),
|
|
30452
|
-
targetColumn: bareName(namedFk[4]),
|
|
30453
|
-
onDelete: namedFk[5] ?? null
|
|
30454
|
-
});
|
|
30455
|
-
continue;
|
|
30456
|
-
}
|
|
30457
|
-
const bareFk = trimmed.match(new RegExp(
|
|
30458
|
-
`^FOREIGN\\s+KEY\\s*\\(\\s*(${QID})\\s*\\)\\s+REFERENCES\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?`,
|
|
30459
|
-
"i"
|
|
30460
|
-
));
|
|
30461
|
-
if (bareFk) {
|
|
30462
|
-
inlineFks.push({
|
|
30463
|
-
constraintName: `${tableName}_${bareName(bareFk[1])}_fkey`,
|
|
30464
|
-
sourceTable: tableName,
|
|
30465
|
-
sourceColumn: bareName(bareFk[1]),
|
|
30466
|
-
targetTable: bareName(bareFk[2]),
|
|
30467
|
-
targetColumn: bareName(bareFk[3]),
|
|
30468
|
-
onDelete: bareFk[4] ?? null
|
|
30469
|
-
});
|
|
30470
|
-
continue;
|
|
30471
|
-
}
|
|
30472
|
-
if (/^CONSTRAINT\s/i.test(trimmed)) continue;
|
|
30473
|
-
const colMatch = trimmed.match(new RegExp(`^(${ID})\\s+(.+)`, "i"));
|
|
30474
|
-
if (!colMatch) continue;
|
|
30475
|
-
const colName = bareName(colMatch[1]);
|
|
30476
|
-
let rest = colMatch[2];
|
|
30477
|
-
const inlineRefMatch = rest.match(new RegExp(
|
|
30478
|
-
`\\bREFERENCES\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?`,
|
|
30479
|
-
"i"
|
|
30480
|
-
));
|
|
30481
|
-
if (inlineRefMatch) {
|
|
30482
|
-
inlineFks.push({
|
|
30483
|
-
constraintName: `${tableName}_${colName}_fkey`,
|
|
30484
|
-
sourceTable: tableName,
|
|
30485
|
-
sourceColumn: colName,
|
|
30486
|
-
targetTable: bareName(inlineRefMatch[1]),
|
|
30487
|
-
targetColumn: bareName(inlineRefMatch[2]),
|
|
30488
|
-
onDelete: inlineRefMatch[3] ?? null
|
|
30489
|
-
});
|
|
30490
|
-
rest = rest.replace(inlineRefMatch[0], "").trim();
|
|
30491
|
-
}
|
|
30492
|
-
const isNotNull = /\bNOT\s+NULL\b/i.test(rest);
|
|
30493
|
-
const isPrimaryKey = /\bPRIMARY\s+KEY\b/i.test(rest);
|
|
30494
|
-
const isUnique = /\bUNIQUE\b/i.test(rest);
|
|
30495
|
-
const defaultMatch = rest.match(/\bDEFAULT\s+(.+?)(?:\s*,?\s*$)/i);
|
|
30496
|
-
const defaultVal = defaultMatch ? defaultMatch[1].trim() : null;
|
|
30497
|
-
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();
|
|
30498
|
-
columns.set(colName, {
|
|
30499
|
-
name: colName,
|
|
30500
|
-
type: colType,
|
|
30501
|
-
nullable: !isNotNull && !isPrimaryKey,
|
|
30502
|
-
primary: isPrimaryKey,
|
|
30503
|
-
unique: isUnique,
|
|
30504
|
-
default: defaultVal
|
|
30505
|
-
});
|
|
30506
|
-
if (isPrimaryKey) primaryCol = colName;
|
|
30507
|
-
}
|
|
30508
|
-
if (primaryCol && columns.has(primaryCol)) {
|
|
30509
|
-
columns.get(primaryCol).primary = true;
|
|
30510
|
-
}
|
|
30511
|
-
state.tables.set(tableName, { name: tableName, columns });
|
|
30512
|
-
state.fks.push(...inlineFks);
|
|
30513
|
-
}
|
|
30514
|
-
}
|
|
30515
|
-
function splitTopLevelCommas(body) {
|
|
30516
|
-
const out = [];
|
|
30517
|
-
let depth = 0;
|
|
30518
|
-
let buf = "";
|
|
30519
|
-
let inString = null;
|
|
30520
|
-
for (const ch of body) {
|
|
30521
|
-
if (inString) {
|
|
30522
|
-
buf += ch;
|
|
30523
|
-
if (ch === inString) inString = null;
|
|
30524
|
-
continue;
|
|
30525
|
-
}
|
|
30526
|
-
if (ch === "'" || ch === '"') {
|
|
30527
|
-
inString = ch;
|
|
30528
|
-
buf += ch;
|
|
30529
|
-
continue;
|
|
30530
|
-
}
|
|
30531
|
-
if (ch === "(") depth++;
|
|
30532
|
-
else if (ch === ")") depth--;
|
|
30533
|
-
if (ch === "," && depth === 0) {
|
|
30534
|
-
out.push(buf);
|
|
30535
|
-
buf = "";
|
|
30536
|
-
continue;
|
|
30537
|
-
}
|
|
30538
|
-
buf += ch;
|
|
30539
|
-
}
|
|
30540
|
-
if (buf.trim()) out.push(buf);
|
|
30541
|
-
return out;
|
|
30542
|
-
}
|
|
30543
|
-
function parseCreateEnum(sql, state) {
|
|
30544
|
-
const re = new RegExp(
|
|
30545
|
-
`CREATE\\s+TYPE\\s+(${QID})\\s+AS\\s+ENUM\\s*\\(([^)]+)\\)`,
|
|
30546
|
-
"gi"
|
|
30547
|
-
);
|
|
30548
|
-
let m;
|
|
30549
|
-
while ((m = re.exec(sql)) !== null) {
|
|
30550
|
-
const enumName = bareName(m[1]);
|
|
30551
|
-
const valuesStr = m[2];
|
|
30552
|
-
const values = new Set(
|
|
30553
|
-
valuesStr.split(",").map((v) => v.trim().replace(/^'(.*)'$/, "$1")).filter(Boolean)
|
|
30554
|
-
);
|
|
30555
|
-
state.enums.set(enumName, { name: enumName, values });
|
|
30556
|
-
}
|
|
30557
|
-
}
|
|
30558
|
-
function parseAlterTable(sql, state) {
|
|
30559
|
-
const addColRe = new RegExp(
|
|
30560
|
-
`ALTER\\s+TABLE\\s+(${QID})\\s+ADD\\s+COLUMN\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${QID})\\s+(.+?);`,
|
|
30561
|
-
"gi"
|
|
30562
|
-
);
|
|
30563
|
-
let m;
|
|
30564
|
-
while ((m = addColRe.exec(sql)) !== null) {
|
|
30565
|
-
const tableName = bareName(m[1]);
|
|
30566
|
-
const colName = bareName(m[2]);
|
|
30567
|
-
let rest = m[3];
|
|
30568
|
-
const table = state.tables.get(tableName);
|
|
30569
|
-
if (!table) continue;
|
|
30570
|
-
const isNotNull = /\bNOT\s+NULL\b/i.test(rest);
|
|
30571
|
-
const defaultMatch = rest.match(/\bDEFAULT\s+(.+?)$/i);
|
|
30572
|
-
const defaultVal = defaultMatch ? defaultMatch[1].trim() : null;
|
|
30573
|
-
let colType = rest.replace(/\bNOT\s+NULL\b/gi, "").replace(/\bDEFAULT\s+.*/gi, "").trim();
|
|
30574
|
-
table.columns.set(colName, {
|
|
30575
|
-
name: colName,
|
|
30576
|
-
type: colType,
|
|
30577
|
-
nullable: !isNotNull,
|
|
30578
|
-
primary: false,
|
|
30579
|
-
unique: false,
|
|
30580
|
-
default: defaultVal
|
|
30581
|
-
});
|
|
30582
|
-
}
|
|
30583
|
-
const dropColRe = new RegExp(
|
|
30584
|
-
`ALTER\\s+TABLE\\s+(${QID})\\s+DROP\\s+COLUMN\\s+(?:IF\\s+EXISTS\\s+)?(${QID})`,
|
|
30585
|
-
"gi"
|
|
30586
|
-
);
|
|
30587
|
-
while ((m = dropColRe.exec(sql)) !== null) {
|
|
30588
|
-
const table = state.tables.get(bareName(m[1]));
|
|
30589
|
-
if (table) table.columns.delete(bareName(m[2]));
|
|
30590
|
-
}
|
|
30591
|
-
const fkRe = new RegExp(
|
|
30592
|
-
`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+)?))?`,
|
|
30593
|
-
"gi"
|
|
30594
|
-
);
|
|
30595
|
-
while ((m = fkRe.exec(sql)) !== null) {
|
|
30596
|
-
state.fks.push({
|
|
30597
|
-
constraintName: bareName(m[2]),
|
|
30598
|
-
sourceTable: bareName(m[1]),
|
|
30599
|
-
sourceColumn: bareName(m[3]),
|
|
30600
|
-
targetTable: bareName(m[4]),
|
|
30601
|
-
targetColumn: bareName(m[5]),
|
|
30602
|
-
onDelete: m[6] ?? null
|
|
30603
|
-
});
|
|
30604
|
-
}
|
|
30605
|
-
}
|
|
30606
|
-
function parseAlterEnum(sql, state) {
|
|
30607
|
-
const re = new RegExp(
|
|
30608
|
-
`ALTER\\s+TYPE\\s+(${QID})\\s+ADD\\s+VALUE\\s+'([^']+)'`,
|
|
30609
|
-
"gi"
|
|
30610
|
-
);
|
|
30611
|
-
let m;
|
|
30612
|
-
while ((m = re.exec(sql)) !== null) {
|
|
30613
|
-
const en = state.enums.get(bareName(m[1]));
|
|
30614
|
-
if (en) en.values.add(m[2]);
|
|
30615
|
-
}
|
|
30616
|
-
}
|
|
30617
|
-
function parseDropTable(sql, state) {
|
|
30618
|
-
const re = new RegExp(
|
|
30619
|
-
`DROP\\s+TABLE\\s+(?:IF\\s+EXISTS\\s+)?(${QID})`,
|
|
30620
|
-
"gi"
|
|
30621
|
-
);
|
|
30622
|
-
let m;
|
|
30623
|
-
while ((m = re.exec(sql)) !== null) {
|
|
30624
|
-
const dropped = bareName(m[1]);
|
|
30625
|
-
state.tables.delete(dropped);
|
|
30626
|
-
state.fks = state.fks.filter((fk) => fk.sourceTable !== dropped && fk.targetTable !== dropped);
|
|
30627
|
-
}
|
|
30628
|
-
}
|
|
30629
|
-
function parseUniqueIndex(sql, state) {
|
|
30630
|
-
const re = new RegExp(
|
|
30631
|
-
`CREATE\\s+UNIQUE\\s+INDEX\\s+(?:(?:IF\\s+NOT\\s+EXISTS\\s+)?(?:${ID}\\s+)?)?ON\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)`,
|
|
30632
|
-
"gi"
|
|
30633
|
-
);
|
|
30634
|
-
let m;
|
|
30635
|
-
while ((m = re.exec(sql)) !== null) {
|
|
30636
|
-
const tableName = bareName(m[1]);
|
|
30637
|
-
const colName = bareName(m[2]);
|
|
30638
|
-
const table = state.tables.get(tableName);
|
|
30639
|
-
const col = table?.columns.get(colName);
|
|
30640
|
-
if (col) col.unique = true;
|
|
30641
|
-
if (!state.uniqueIndexes.has(tableName)) state.uniqueIndexes.set(tableName, /* @__PURE__ */ new Set());
|
|
30642
|
-
state.uniqueIndexes.get(tableName).add(colName);
|
|
30643
|
-
}
|
|
30644
|
-
}
|
|
30645
30502
|
function discoverMigrationFiles(migrationsDir) {
|
|
30646
30503
|
if (!(0, import_node_fs18.existsSync)(migrationsDir)) return [];
|
|
30647
30504
|
const out = [];
|
|
@@ -30656,25 +30513,614 @@ function discoverMigrationFiles(migrationsDir) {
|
|
|
30656
30513
|
}
|
|
30657
30514
|
return out;
|
|
30658
30515
|
}
|
|
30659
|
-
|
|
30516
|
+
var postgresDialect = {
|
|
30517
|
+
parse(sql) {
|
|
30518
|
+
return (0, import_pgsql_parser.parseSync)(sql);
|
|
30519
|
+
},
|
|
30520
|
+
applyAll(ast, state, filepath) {
|
|
30521
|
+
const stmts = ast.stmts ?? [];
|
|
30522
|
+
extractTablesFromStmts(stmts, state);
|
|
30523
|
+
extractEnumsFromStmts(stmts, state);
|
|
30524
|
+
extractIndexesFromStmts(stmts, state, filepath);
|
|
30525
|
+
extractPoliciesFromStmts(stmts, state, filepath);
|
|
30526
|
+
extractExtensionsFromStmts(stmts, state, filepath);
|
|
30527
|
+
extractTriggersFromStmts(stmts, state, filepath);
|
|
30528
|
+
extractFunctionsFromStmts(stmts, state, filepath);
|
|
30529
|
+
extractViewsFromStmts(stmts, state, filepath);
|
|
30530
|
+
applyDropIndexes(stmts, state);
|
|
30531
|
+
applyDropPolicies(stmts, state);
|
|
30532
|
+
applyDropsForSchemaObjects(stmts, state);
|
|
30533
|
+
applyAstAlterEnums(stmts, state);
|
|
30534
|
+
applyAstAlterations(stmts, state);
|
|
30535
|
+
},
|
|
30536
|
+
extractMigrationInfo(ast, name, filepath) {
|
|
30537
|
+
const stmts = ast.stmts ?? [];
|
|
30538
|
+
return extractMigrationInfoFromStmts(stmts, name, filepath);
|
|
30539
|
+
}
|
|
30540
|
+
};
|
|
30541
|
+
function parseMigrations(migrationsDir, dialect = postgresDialect) {
|
|
30660
30542
|
const state = {
|
|
30661
30543
|
tables: /* @__PURE__ */ new Map(),
|
|
30662
30544
|
enums: /* @__PURE__ */ new Map(),
|
|
30663
30545
|
fks: [],
|
|
30664
|
-
uniqueIndexes: /* @__PURE__ */ new Map()
|
|
30546
|
+
uniqueIndexes: /* @__PURE__ */ new Map(),
|
|
30547
|
+
indexes: [],
|
|
30548
|
+
policies: [],
|
|
30549
|
+
extensions: [],
|
|
30550
|
+
triggers: [],
|
|
30551
|
+
functions: [],
|
|
30552
|
+
views: []
|
|
30665
30553
|
};
|
|
30666
30554
|
if (!migrationsDir) return state;
|
|
30667
30555
|
for (const sqlPath of discoverMigrationFiles(migrationsDir)) {
|
|
30668
30556
|
const sql = (0, import_node_fs18.readFileSync)(sqlPath, "utf-8");
|
|
30669
|
-
|
|
30670
|
-
|
|
30671
|
-
|
|
30672
|
-
|
|
30673
|
-
|
|
30674
|
-
|
|
30557
|
+
let ast;
|
|
30558
|
+
try {
|
|
30559
|
+
ast = dialect.parse(sql);
|
|
30560
|
+
} catch {
|
|
30561
|
+
continue;
|
|
30562
|
+
}
|
|
30563
|
+
dialect.applyAll(ast, state, sqlPath);
|
|
30675
30564
|
}
|
|
30676
30565
|
return state;
|
|
30677
30566
|
}
|
|
30567
|
+
function extractIndexesFromStmts(stmts, state, filepath) {
|
|
30568
|
+
for (const wrap of stmts) {
|
|
30569
|
+
const stmt = wrap.stmt ?? {};
|
|
30570
|
+
const ix = stmt.IndexStmt;
|
|
30571
|
+
if (!ix) continue;
|
|
30572
|
+
const name = ix.idxname ?? "";
|
|
30573
|
+
if (!name) continue;
|
|
30574
|
+
const table = ix.relation?.relname ?? "";
|
|
30575
|
+
const unique = !!ix.unique;
|
|
30576
|
+
const method = String(ix.accessMethod ?? "btree").toLowerCase();
|
|
30577
|
+
const params = ix.indexParams ?? [];
|
|
30578
|
+
const columns = [];
|
|
30579
|
+
let hasExpressions = false;
|
|
30580
|
+
for (const p of params) {
|
|
30581
|
+
const elem = p.IndexElem;
|
|
30582
|
+
if (!elem) continue;
|
|
30583
|
+
if (elem.name) columns.push(elem.name);
|
|
30584
|
+
else if (elem.expr) hasExpressions = true;
|
|
30585
|
+
}
|
|
30586
|
+
const hasPredicate = !!ix.whereClause;
|
|
30587
|
+
const existing = state.indexes.findIndex((i) => i.name === name);
|
|
30588
|
+
const next = { name, table, unique, method, columns, hasExpressions, hasPredicate, filepath };
|
|
30589
|
+
if (existing >= 0) state.indexes[existing] = next;
|
|
30590
|
+
else state.indexes.push(next);
|
|
30591
|
+
if (unique && columns.length === 1 && !hasPredicate && !hasExpressions) {
|
|
30592
|
+
const t = state.tables.get(table);
|
|
30593
|
+
const col = t?.columns.get(columns[0]);
|
|
30594
|
+
if (col) col.unique = true;
|
|
30595
|
+
if (!state.uniqueIndexes.has(table)) state.uniqueIndexes.set(table, /* @__PURE__ */ new Set());
|
|
30596
|
+
state.uniqueIndexes.get(table).add(columns[0]);
|
|
30597
|
+
}
|
|
30598
|
+
}
|
|
30599
|
+
}
|
|
30600
|
+
function applyDropIndexes(stmts, state) {
|
|
30601
|
+
for (const wrap of stmts) {
|
|
30602
|
+
const drop = wrap.stmt?.DropStmt;
|
|
30603
|
+
if (!drop || drop.removeType !== "OBJECT_INDEX") continue;
|
|
30604
|
+
const objects = drop.objects ?? [];
|
|
30605
|
+
const droppedNames = /* @__PURE__ */ new Set();
|
|
30606
|
+
for (const obj of objects) {
|
|
30607
|
+
const items = obj.List?.items ?? [];
|
|
30608
|
+
const last = items[items.length - 1]?.String?.sval;
|
|
30609
|
+
if (last) droppedNames.add(last);
|
|
30610
|
+
}
|
|
30611
|
+
if (droppedNames.size > 0) {
|
|
30612
|
+
state.indexes = state.indexes.filter((i) => !droppedNames.has(i.name));
|
|
30613
|
+
}
|
|
30614
|
+
}
|
|
30615
|
+
}
|
|
30616
|
+
function formatPgTypeName(typeName) {
|
|
30617
|
+
const names = (typeName?.names ?? []).map((n) => n.String?.sval ?? "").filter(Boolean);
|
|
30618
|
+
const base = (names[names.length - 1] ?? "").toLowerCase();
|
|
30619
|
+
const PG_INTERNAL_MAP = {
|
|
30620
|
+
int4: "INTEGER",
|
|
30621
|
+
int8: "BIGINT",
|
|
30622
|
+
int2: "SMALLINT",
|
|
30623
|
+
float8: "DOUBLE PRECISION",
|
|
30624
|
+
float4: "REAL",
|
|
30625
|
+
bool: "BOOLEAN",
|
|
30626
|
+
bpchar: "CHAR",
|
|
30627
|
+
timestamptz: "TIMESTAMPTZ",
|
|
30628
|
+
timestamp: "TIMESTAMP",
|
|
30629
|
+
numeric: "NUMERIC",
|
|
30630
|
+
text: "TEXT",
|
|
30631
|
+
varchar: "VARCHAR",
|
|
30632
|
+
jsonb: "JSONB",
|
|
30633
|
+
json: "JSON",
|
|
30634
|
+
uuid: "UUID",
|
|
30635
|
+
date: "DATE",
|
|
30636
|
+
bytea: "BYTEA"
|
|
30637
|
+
};
|
|
30638
|
+
return PG_INTERNAL_MAP[base] ?? base.toUpperCase();
|
|
30639
|
+
}
|
|
30640
|
+
function applyAstAlterations(stmts, state) {
|
|
30641
|
+
for (const wrap of stmts) {
|
|
30642
|
+
const stmt = wrap.stmt ?? {};
|
|
30643
|
+
const kind = Object.keys(stmt)[0];
|
|
30644
|
+
if (!kind) continue;
|
|
30645
|
+
if (kind === "AlterTableStmt") {
|
|
30646
|
+
const body = stmt.AlterTableStmt;
|
|
30647
|
+
const tableName = body.relation?.relname ?? "";
|
|
30648
|
+
const table = state.tables.get(tableName);
|
|
30649
|
+
if (!table) continue;
|
|
30650
|
+
const cmds = body.cmds ?? [];
|
|
30651
|
+
for (const c of cmds) {
|
|
30652
|
+
const cmd = c.AlterTableCmd;
|
|
30653
|
+
if (!cmd) continue;
|
|
30654
|
+
const subtype = cmd.subtype ?? "";
|
|
30655
|
+
const colName = cmd.name ?? "";
|
|
30656
|
+
const col = colName ? table.columns.get(colName) : void 0;
|
|
30657
|
+
switch (subtype) {
|
|
30658
|
+
case "AT_AlterColumnType": {
|
|
30659
|
+
if (!col) break;
|
|
30660
|
+
const typeName = cmd.def?.ColumnDef?.typeName ?? cmd.def?.typeName;
|
|
30661
|
+
if (typeName) col.type = formatPgTypeNameWithMods(typeName);
|
|
30662
|
+
break;
|
|
30663
|
+
}
|
|
30664
|
+
case "AT_SetNotNull":
|
|
30665
|
+
if (col) col.nullable = false;
|
|
30666
|
+
break;
|
|
30667
|
+
case "AT_DropNotNull":
|
|
30668
|
+
if (col) col.nullable = true;
|
|
30669
|
+
break;
|
|
30670
|
+
case "AT_AddColumn": {
|
|
30671
|
+
const cd = cmd.def?.ColumnDef;
|
|
30672
|
+
if (!cd) break;
|
|
30673
|
+
const newColName = cd.colname ?? "";
|
|
30674
|
+
if (!newColName) break;
|
|
30675
|
+
if (table.columns.has(newColName)) break;
|
|
30676
|
+
let nullable = true;
|
|
30677
|
+
let primary = false;
|
|
30678
|
+
let unique = false;
|
|
30679
|
+
let defaultVal = null;
|
|
30680
|
+
for (const c2 of cd.constraints ?? []) {
|
|
30681
|
+
const ct = c2.Constraint;
|
|
30682
|
+
if (!ct) continue;
|
|
30683
|
+
if (ct.contype === "CONSTR_NOTNULL") nullable = false;
|
|
30684
|
+
else if (ct.contype === "CONSTR_PRIMARY") {
|
|
30685
|
+
primary = true;
|
|
30686
|
+
nullable = false;
|
|
30687
|
+
} else if (ct.contype === "CONSTR_UNIQUE") unique = true;
|
|
30688
|
+
else if (ct.contype === "CONSTR_DEFAULT") defaultVal = "<expr>";
|
|
30689
|
+
}
|
|
30690
|
+
table.columns.set(newColName, {
|
|
30691
|
+
name: newColName,
|
|
30692
|
+
type: formatPgTypeNameWithMods(cd.typeName),
|
|
30693
|
+
nullable,
|
|
30694
|
+
primary,
|
|
30695
|
+
unique,
|
|
30696
|
+
default: defaultVal
|
|
30697
|
+
});
|
|
30698
|
+
break;
|
|
30699
|
+
}
|
|
30700
|
+
case "AT_DropColumn":
|
|
30701
|
+
if (colName) table.columns.delete(colName);
|
|
30702
|
+
break;
|
|
30703
|
+
case "AT_AddConstraint": {
|
|
30704
|
+
const ct = cmd.def?.Constraint;
|
|
30705
|
+
if (!ct) break;
|
|
30706
|
+
if (ct.contype !== "CONSTR_FOREIGN") break;
|
|
30707
|
+
const fkCols = (ct.fk_attrs ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
|
|
30708
|
+
const pkCols = (ct.pk_attrs ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
|
|
30709
|
+
const pkTable = ct.pktable?.relname ?? "";
|
|
30710
|
+
if (fkCols.length && pkCols.length && pkTable) {
|
|
30711
|
+
state.fks.push({
|
|
30712
|
+
constraintName: ct.conname || `${tableName}_${fkCols[0]}_fkey`,
|
|
30713
|
+
sourceTable: tableName,
|
|
30714
|
+
sourceColumn: fkCols[0],
|
|
30715
|
+
targetTable: pkTable,
|
|
30716
|
+
targetColumn: pkCols[0],
|
|
30717
|
+
onDelete: mapFkAction(ct.fk_del_action)
|
|
30718
|
+
});
|
|
30719
|
+
}
|
|
30720
|
+
break;
|
|
30721
|
+
}
|
|
30722
|
+
}
|
|
30723
|
+
}
|
|
30724
|
+
} else if (kind === "RenameStmt") {
|
|
30725
|
+
const body = stmt.RenameStmt;
|
|
30726
|
+
const renameType = body.renameType ?? "";
|
|
30727
|
+
const newName = body.newname ?? "";
|
|
30728
|
+
if (renameType === "OBJECT_COLUMN") {
|
|
30729
|
+
const tableName = body.relation?.relname ?? "";
|
|
30730
|
+
const oldName = body.subname ?? "";
|
|
30731
|
+
const table = state.tables.get(tableName);
|
|
30732
|
+
if (!table || !oldName || !newName) continue;
|
|
30733
|
+
const col = table.columns.get(oldName);
|
|
30734
|
+
if (col) {
|
|
30735
|
+
col.name = newName;
|
|
30736
|
+
table.columns.delete(oldName);
|
|
30737
|
+
table.columns.set(newName, col);
|
|
30738
|
+
}
|
|
30739
|
+
} else if (renameType === "OBJECT_TABLE") {
|
|
30740
|
+
const oldName = body.relation?.relname ?? "";
|
|
30741
|
+
if (!oldName || !newName) continue;
|
|
30742
|
+
const t = state.tables.get(oldName);
|
|
30743
|
+
if (!t) continue;
|
|
30744
|
+
state.tables.delete(oldName);
|
|
30745
|
+
t.name = newName;
|
|
30746
|
+
state.tables.set(newName, t);
|
|
30747
|
+
for (const fk of state.fks) {
|
|
30748
|
+
if (fk.sourceTable === oldName) fk.sourceTable = newName;
|
|
30749
|
+
if (fk.targetTable === oldName) fk.targetTable = newName;
|
|
30750
|
+
}
|
|
30751
|
+
}
|
|
30752
|
+
}
|
|
30753
|
+
}
|
|
30754
|
+
}
|
|
30755
|
+
function extractPoliciesFromStmts(stmts, state, filepath) {
|
|
30756
|
+
for (const wrap of stmts) {
|
|
30757
|
+
const body = wrap.stmt?.CreatePolicyStmt;
|
|
30758
|
+
if (!body) continue;
|
|
30759
|
+
const name = body.policy_name ?? "";
|
|
30760
|
+
if (!name) continue;
|
|
30761
|
+
const table = body.table?.relname ?? "";
|
|
30762
|
+
const cmdRaw = String(body.cmd_name ?? "all").toUpperCase();
|
|
30763
|
+
const command = ["SELECT", "INSERT", "UPDATE", "DELETE", "ALL"].includes(cmdRaw) ? cmdRaw : "ALL";
|
|
30764
|
+
const permissive = body.permissive === true;
|
|
30765
|
+
const roles = (body.roles ?? []).map((r) => {
|
|
30766
|
+
const rs = r.RoleSpec;
|
|
30767
|
+
if (!rs) return "";
|
|
30768
|
+
if (rs.roletype === "ROLESPEC_PUBLIC") return "public";
|
|
30769
|
+
if (rs.roletype === "ROLESPEC_CURRENT_USER") return "current_user";
|
|
30770
|
+
if (rs.roletype === "ROLESPEC_CSTRING" && rs.rolename) return rs.rolename;
|
|
30771
|
+
return "";
|
|
30772
|
+
}).filter(Boolean);
|
|
30773
|
+
const hasUsing = !!body.qual;
|
|
30774
|
+
const hasWithCheck = !!body.with_check;
|
|
30775
|
+
const existing = state.policies.findIndex((p) => p.table === table && p.name === name);
|
|
30776
|
+
const next = { name, table, command, permissive, roles, hasUsing, hasWithCheck, filepath };
|
|
30777
|
+
if (existing >= 0) state.policies[existing] = next;
|
|
30778
|
+
else state.policies.push(next);
|
|
30779
|
+
}
|
|
30780
|
+
}
|
|
30781
|
+
function applyDropPolicies(stmts, state) {
|
|
30782
|
+
for (const wrap of stmts) {
|
|
30783
|
+
const drop = wrap.stmt?.DropStmt;
|
|
30784
|
+
if (!drop || drop.removeType !== "OBJECT_POLICY") continue;
|
|
30785
|
+
const objects = drop.objects ?? [];
|
|
30786
|
+
for (const obj of objects) {
|
|
30787
|
+
const items = obj.List?.items ?? [];
|
|
30788
|
+
if (items.length < 2) continue;
|
|
30789
|
+
const table = items[0]?.String?.sval ?? "";
|
|
30790
|
+
const policyName = items[items.length - 1]?.String?.sval ?? "";
|
|
30791
|
+
if (!table || !policyName) continue;
|
|
30792
|
+
state.policies = state.policies.filter((p) => !(p.table === table && p.name === policyName));
|
|
30793
|
+
}
|
|
30794
|
+
}
|
|
30795
|
+
}
|
|
30796
|
+
function mapFkAction(action) {
|
|
30797
|
+
if (!action) return null;
|
|
30798
|
+
const m = {
|
|
30799
|
+
r: "RESTRICT",
|
|
30800
|
+
c: "CASCADE",
|
|
30801
|
+
s: "SET NULL",
|
|
30802
|
+
d: "SET DEFAULT",
|
|
30803
|
+
a: "NO ACTION",
|
|
30804
|
+
// pgsql-parser may also emit FKCONSTR_ACTION_* enum strings:
|
|
30805
|
+
FKCONSTR_ACTION_RESTRICT: "RESTRICT",
|
|
30806
|
+
FKCONSTR_ACTION_CASCADE: "CASCADE",
|
|
30807
|
+
FKCONSTR_ACTION_SETNULL: "SET NULL",
|
|
30808
|
+
FKCONSTR_ACTION_SETDEFAULT: "SET DEFAULT",
|
|
30809
|
+
FKCONSTR_ACTION_NOACTION: "NO ACTION"
|
|
30810
|
+
};
|
|
30811
|
+
return m[action] ?? null;
|
|
30812
|
+
}
|
|
30813
|
+
function formatPgTypeNameWithMods(typeName) {
|
|
30814
|
+
const base = formatPgTypeName(typeName);
|
|
30815
|
+
if (base === "String" || base === "unknown") return base;
|
|
30816
|
+
const typmods = [];
|
|
30817
|
+
for (const m of typeName?.typmods ?? []) {
|
|
30818
|
+
const v = m.A_Const?.ival?.ival;
|
|
30819
|
+
if (typeof v === "number") typmods.push(v);
|
|
30820
|
+
}
|
|
30821
|
+
return typmods.length ? `${base}(${typmods.join(",")})` : base;
|
|
30822
|
+
}
|
|
30823
|
+
function extractTablesFromStmts(stmts, state) {
|
|
30824
|
+
for (const wrap of stmts) {
|
|
30825
|
+
const body = wrap.stmt?.CreateStmt;
|
|
30826
|
+
if (!body) continue;
|
|
30827
|
+
const tableName = body.relation?.relname ?? "";
|
|
30828
|
+
if (!tableName) continue;
|
|
30829
|
+
const columns = /* @__PURE__ */ new Map();
|
|
30830
|
+
const fks = [];
|
|
30831
|
+
let primaryCol = null;
|
|
30832
|
+
for (const elt of body.tableElts ?? []) {
|
|
30833
|
+
if (elt.ColumnDef) {
|
|
30834
|
+
const cd = elt.ColumnDef;
|
|
30835
|
+
const colName = cd.colname ?? "";
|
|
30836
|
+
if (!colName) continue;
|
|
30837
|
+
const colType = formatPgTypeNameWithMods(cd.typeName);
|
|
30838
|
+
let nullable = true;
|
|
30839
|
+
let primary = false;
|
|
30840
|
+
let unique = false;
|
|
30841
|
+
let defaultVal = null;
|
|
30842
|
+
for (const c of cd.constraints ?? []) {
|
|
30843
|
+
const ct = c.Constraint;
|
|
30844
|
+
if (!ct) continue;
|
|
30845
|
+
switch (ct.contype) {
|
|
30846
|
+
case "CONSTR_NOTNULL":
|
|
30847
|
+
nullable = false;
|
|
30848
|
+
break;
|
|
30849
|
+
case "CONSTR_PRIMARY":
|
|
30850
|
+
primary = true;
|
|
30851
|
+
nullable = false;
|
|
30852
|
+
primaryCol = colName;
|
|
30853
|
+
break;
|
|
30854
|
+
case "CONSTR_UNIQUE":
|
|
30855
|
+
unique = true;
|
|
30856
|
+
break;
|
|
30857
|
+
case "CONSTR_DEFAULT":
|
|
30858
|
+
defaultVal = "<expr>";
|
|
30859
|
+
break;
|
|
30860
|
+
case "CONSTR_FOREIGN": {
|
|
30861
|
+
const pkTable = ct.pktable?.relname ?? "";
|
|
30862
|
+
const pkCols = (ct.pk_attrs ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
|
|
30863
|
+
if (pkTable && pkCols.length) {
|
|
30864
|
+
fks.push({
|
|
30865
|
+
constraintName: ct.conname || `${tableName}_${colName}_fkey`,
|
|
30866
|
+
sourceTable: tableName,
|
|
30867
|
+
sourceColumn: colName,
|
|
30868
|
+
targetTable: pkTable,
|
|
30869
|
+
targetColumn: pkCols[0],
|
|
30870
|
+
onDelete: mapFkAction(ct.fk_del_action)
|
|
30871
|
+
});
|
|
30872
|
+
}
|
|
30873
|
+
break;
|
|
30874
|
+
}
|
|
30875
|
+
}
|
|
30876
|
+
}
|
|
30877
|
+
columns.set(colName, {
|
|
30878
|
+
name: colName,
|
|
30879
|
+
type: colType,
|
|
30880
|
+
nullable,
|
|
30881
|
+
primary,
|
|
30882
|
+
unique,
|
|
30883
|
+
default: defaultVal
|
|
30884
|
+
});
|
|
30885
|
+
} else if (elt.Constraint) {
|
|
30886
|
+
const ct = elt.Constraint;
|
|
30887
|
+
switch (ct.contype) {
|
|
30888
|
+
case "CONSTR_PRIMARY": {
|
|
30889
|
+
const keys = (ct.keys ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
|
|
30890
|
+
if (keys.length) primaryCol = keys[0];
|
|
30891
|
+
break;
|
|
30892
|
+
}
|
|
30893
|
+
case "CONSTR_FOREIGN": {
|
|
30894
|
+
const fkCols = (ct.fk_attrs ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
|
|
30895
|
+
const pkCols = (ct.pk_attrs ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
|
|
30896
|
+
const pkTable = ct.pktable?.relname ?? "";
|
|
30897
|
+
if (fkCols.length && pkCols.length && pkTable) {
|
|
30898
|
+
fks.push({
|
|
30899
|
+
constraintName: ct.conname || `${tableName}_${fkCols[0]}_fkey`,
|
|
30900
|
+
sourceTable: tableName,
|
|
30901
|
+
sourceColumn: fkCols[0],
|
|
30902
|
+
targetTable: pkTable,
|
|
30903
|
+
targetColumn: pkCols[0],
|
|
30904
|
+
onDelete: mapFkAction(ct.fk_del_action)
|
|
30905
|
+
});
|
|
30906
|
+
}
|
|
30907
|
+
break;
|
|
30908
|
+
}
|
|
30909
|
+
}
|
|
30910
|
+
}
|
|
30911
|
+
}
|
|
30912
|
+
if (primaryCol && columns.has(primaryCol)) {
|
|
30913
|
+
columns.get(primaryCol).primary = true;
|
|
30914
|
+
columns.get(primaryCol).nullable = false;
|
|
30915
|
+
}
|
|
30916
|
+
state.tables.set(tableName, { name: tableName, columns });
|
|
30917
|
+
state.fks.push(...fks);
|
|
30918
|
+
}
|
|
30919
|
+
}
|
|
30920
|
+
function extractEnumsFromStmts(stmts, state) {
|
|
30921
|
+
for (const wrap of stmts) {
|
|
30922
|
+
const body = wrap.stmt?.CreateEnumStmt;
|
|
30923
|
+
if (!body) continue;
|
|
30924
|
+
const names = (body.typeName ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
|
|
30925
|
+
const enumName = names[names.length - 1] ?? "";
|
|
30926
|
+
if (!enumName) continue;
|
|
30927
|
+
const vals = new Set(
|
|
30928
|
+
(body.vals ?? []).map((s) => s.String?.sval ?? "").filter(Boolean)
|
|
30929
|
+
);
|
|
30930
|
+
state.enums.set(enumName, { name: enumName, values: vals });
|
|
30931
|
+
}
|
|
30932
|
+
}
|
|
30933
|
+
function applyAstAlterEnums(stmts, state) {
|
|
30934
|
+
for (const wrap of stmts) {
|
|
30935
|
+
const body = wrap.stmt?.AlterEnumStmt;
|
|
30936
|
+
if (!body) continue;
|
|
30937
|
+
const names = (body.typeName ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
|
|
30938
|
+
const enumName = names[names.length - 1] ?? "";
|
|
30939
|
+
const en = state.enums.get(enumName);
|
|
30940
|
+
if (!en) continue;
|
|
30941
|
+
if (body.newVal) en.values.add(String(body.newVal));
|
|
30942
|
+
}
|
|
30943
|
+
}
|
|
30944
|
+
function extractExtensionsFromStmts(stmts, state, filepath) {
|
|
30945
|
+
for (const wrap of stmts) {
|
|
30946
|
+
const body = wrap.stmt?.CreateExtensionStmt;
|
|
30947
|
+
if (!body) continue;
|
|
30948
|
+
const name = body.extname ?? "";
|
|
30949
|
+
if (!name) continue;
|
|
30950
|
+
let schema = null;
|
|
30951
|
+
let version2 = null;
|
|
30952
|
+
for (const opt of body.options ?? []) {
|
|
30953
|
+
const de = opt.DefElem;
|
|
30954
|
+
if (!de) continue;
|
|
30955
|
+
if (de.defname === "schema" && de.arg?.String?.sval) schema = de.arg.String.sval;
|
|
30956
|
+
else if (de.defname === "new_version" && de.arg?.String?.sval) version2 = de.arg.String.sval;
|
|
30957
|
+
}
|
|
30958
|
+
const next = { name, schema, version: version2, filepath };
|
|
30959
|
+
const existing = state.extensions.findIndex((e) => e.name === name);
|
|
30960
|
+
if (existing >= 0) state.extensions[existing] = next;
|
|
30961
|
+
else state.extensions.push(next);
|
|
30962
|
+
}
|
|
30963
|
+
}
|
|
30964
|
+
function extractTriggersFromStmts(stmts, state, filepath) {
|
|
30965
|
+
for (const wrap of stmts) {
|
|
30966
|
+
const body = wrap.stmt?.CreateTrigStmt;
|
|
30967
|
+
if (!body) continue;
|
|
30968
|
+
const name = body.trigname ?? "";
|
|
30969
|
+
if (!name) continue;
|
|
30970
|
+
const table = body.relation?.relname ?? "";
|
|
30971
|
+
const timingVal = body.timing ?? 0;
|
|
30972
|
+
const eventsVal = body.events ?? 0;
|
|
30973
|
+
const timing = timingVal & 2 ? "BEFORE" : timingVal & 64 ? "INSTEAD OF" : "AFTER";
|
|
30974
|
+
const events = [];
|
|
30975
|
+
if (eventsVal & 4) events.push("INSERT");
|
|
30976
|
+
if (eventsVal & 8) events.push("DELETE");
|
|
30977
|
+
if (eventsVal & 16) events.push("UPDATE");
|
|
30978
|
+
if (eventsVal & 32) events.push("TRUNCATE");
|
|
30979
|
+
const funcname = body.funcname ?? [];
|
|
30980
|
+
const funcCall = funcname[funcname.length - 1]?.String?.sval ?? "";
|
|
30981
|
+
const forEach = body.row ? "ROW" : "STATEMENT";
|
|
30982
|
+
const hasWhen = !!body.whenClause;
|
|
30983
|
+
const next = { name, table, timing, events, function: funcCall, hasWhen, forEach, filepath };
|
|
30984
|
+
const existing = state.triggers.findIndex((t) => t.table === table && t.name === name);
|
|
30985
|
+
if (existing >= 0) state.triggers[existing] = next;
|
|
30986
|
+
else state.triggers.push(next);
|
|
30987
|
+
}
|
|
30988
|
+
}
|
|
30989
|
+
function functionIdFor(name, schema) {
|
|
30990
|
+
return schema ? `${schema}.${name}` : name;
|
|
30991
|
+
}
|
|
30992
|
+
function extractFunctionsFromStmts(stmts, state, filepath) {
|
|
30993
|
+
for (const wrap of stmts) {
|
|
30994
|
+
const body = wrap.stmt?.CreateFunctionStmt;
|
|
30995
|
+
if (!body) continue;
|
|
30996
|
+
const fn = body.funcname ?? [];
|
|
30997
|
+
if (fn.length === 0) continue;
|
|
30998
|
+
const name = fn[fn.length - 1]?.String?.sval ?? "";
|
|
30999
|
+
if (!name) continue;
|
|
31000
|
+
const schema = fn.length > 1 ? fn[fn.length - 2]?.String?.sval ?? null : null;
|
|
31001
|
+
let language = "sql";
|
|
31002
|
+
for (const opt of body.options ?? []) {
|
|
31003
|
+
const de = opt.DefElem;
|
|
31004
|
+
if (de?.defname === "language" && de.arg?.String?.sval) language = de.arg.String.sval;
|
|
31005
|
+
}
|
|
31006
|
+
const returnType = body.returnType ? formatPgTypeName(body.returnType) : "";
|
|
31007
|
+
const isProcedure = !!body.is_procedure;
|
|
31008
|
+
const next = { name, schema, language, returnType, isProcedure, filepath };
|
|
31009
|
+
const id = functionIdFor(name, schema);
|
|
31010
|
+
const existing = state.functions.findIndex((f) => functionIdFor(f.name, f.schema) === id);
|
|
31011
|
+
if (existing >= 0) state.functions[existing] = next;
|
|
31012
|
+
else state.functions.push(next);
|
|
31013
|
+
}
|
|
31014
|
+
}
|
|
31015
|
+
function extractViewsFromStmts(stmts, state, filepath) {
|
|
31016
|
+
for (const wrap of stmts) {
|
|
31017
|
+
const stmt = wrap.stmt ?? {};
|
|
31018
|
+
const view = stmt.ViewStmt;
|
|
31019
|
+
if (view) {
|
|
31020
|
+
const name = view.view?.relname ?? "";
|
|
31021
|
+
if (!name) continue;
|
|
31022
|
+
const schema = view.view?.schemaname ?? null;
|
|
31023
|
+
const next = {
|
|
31024
|
+
name,
|
|
31025
|
+
schema,
|
|
31026
|
+
isMaterialized: false,
|
|
31027
|
+
withCheckOption: String(view.withCheckOption ?? "NO_CHECK_OPTION"),
|
|
31028
|
+
filepath
|
|
31029
|
+
};
|
|
31030
|
+
const id = functionIdFor(name, schema);
|
|
31031
|
+
const existing = state.views.findIndex((v) => functionIdFor(v.name, v.schema) === id);
|
|
31032
|
+
if (existing >= 0) state.views[existing] = next;
|
|
31033
|
+
else state.views.push(next);
|
|
31034
|
+
}
|
|
31035
|
+
const ctas = stmt.CreateTableAsStmt;
|
|
31036
|
+
if (ctas && ctas.objtype === "OBJECT_MATVIEW") {
|
|
31037
|
+
const name = ctas.into?.rel?.relname ?? "";
|
|
31038
|
+
if (!name) continue;
|
|
31039
|
+
const schema = ctas.into?.rel?.schemaname ?? null;
|
|
31040
|
+
const next = {
|
|
31041
|
+
name,
|
|
31042
|
+
schema,
|
|
31043
|
+
isMaterialized: true,
|
|
31044
|
+
withCheckOption: "N/A",
|
|
31045
|
+
filepath
|
|
31046
|
+
};
|
|
31047
|
+
const id = functionIdFor(name, schema);
|
|
31048
|
+
const existing = state.views.findIndex((v) => functionIdFor(v.name, v.schema) === id);
|
|
31049
|
+
if (existing >= 0) state.views[existing] = next;
|
|
31050
|
+
else state.views.push(next);
|
|
31051
|
+
}
|
|
31052
|
+
}
|
|
31053
|
+
}
|
|
31054
|
+
function applyDropsForSchemaObjects(stmts, state) {
|
|
31055
|
+
for (const wrap of stmts) {
|
|
31056
|
+
const drop = wrap.stmt?.DropStmt;
|
|
31057
|
+
if (!drop) continue;
|
|
31058
|
+
const removeType = drop.removeType ?? "";
|
|
31059
|
+
const objects = drop.objects ?? [];
|
|
31060
|
+
if (removeType === "OBJECT_TABLE") {
|
|
31061
|
+
const droppedTables = /* @__PURE__ */ new Set();
|
|
31062
|
+
for (const obj of objects) {
|
|
31063
|
+
const items = obj.List?.items ?? [];
|
|
31064
|
+
const last = items[items.length - 1]?.String?.sval;
|
|
31065
|
+
if (last) droppedTables.add(last);
|
|
31066
|
+
}
|
|
31067
|
+
if (droppedTables.size > 0) {
|
|
31068
|
+
for (const t of droppedTables) state.tables.delete(t);
|
|
31069
|
+
state.fks = state.fks.filter((fk) => !droppedTables.has(fk.sourceTable) && !droppedTables.has(fk.targetTable));
|
|
31070
|
+
}
|
|
31071
|
+
} else if (removeType === "OBJECT_TYPE") {
|
|
31072
|
+
const droppedEnums = /* @__PURE__ */ new Set();
|
|
31073
|
+
for (const obj of objects) {
|
|
31074
|
+
const tn = obj.TypeName;
|
|
31075
|
+
if (!tn) continue;
|
|
31076
|
+
const names = (tn.names ?? []).map((s) => s.String?.sval ?? "").filter(Boolean);
|
|
31077
|
+
const last = names[names.length - 1];
|
|
31078
|
+
if (last) droppedEnums.add(last);
|
|
31079
|
+
}
|
|
31080
|
+
for (const e of droppedEnums) state.enums.delete(e);
|
|
31081
|
+
} else if (removeType === "OBJECT_EXTENSION") {
|
|
31082
|
+
const names = /* @__PURE__ */ new Set();
|
|
31083
|
+
for (const obj of objects) {
|
|
31084
|
+
const sval = obj.String?.sval;
|
|
31085
|
+
if (sval) names.add(sval);
|
|
31086
|
+
}
|
|
31087
|
+
if (names.size > 0) state.extensions = state.extensions.filter((e) => !names.has(e.name));
|
|
31088
|
+
} else if (removeType === "OBJECT_TRIGGER") {
|
|
31089
|
+
for (const obj of objects) {
|
|
31090
|
+
const items = obj.List?.items ?? [];
|
|
31091
|
+
if (items.length < 2) continue;
|
|
31092
|
+
const table = items[0]?.String?.sval ?? "";
|
|
31093
|
+
const trigName = items[items.length - 1]?.String?.sval ?? "";
|
|
31094
|
+
if (!table || !trigName) continue;
|
|
31095
|
+
state.triggers = state.triggers.filter((t) => !(t.table === table && t.name === trigName));
|
|
31096
|
+
}
|
|
31097
|
+
} else if (removeType === "OBJECT_FUNCTION" || removeType === "OBJECT_PROCEDURE") {
|
|
31098
|
+
for (const obj of objects) {
|
|
31099
|
+
const items = obj.ObjectWithArgs?.objname?.items ?? obj.ObjectWithArgs?.objname ?? obj.List?.items ?? [];
|
|
31100
|
+
if (!items.length) continue;
|
|
31101
|
+
const segs = items.map((s) => s.String?.sval ?? "").filter(Boolean);
|
|
31102
|
+
if (!segs.length) continue;
|
|
31103
|
+
const name = segs[segs.length - 1];
|
|
31104
|
+
const schema = segs.length > 1 ? segs[segs.length - 2] : null;
|
|
31105
|
+
const id = functionIdFor(name, schema);
|
|
31106
|
+
state.functions = state.functions.filter((f) => functionIdFor(f.name, f.schema) !== id);
|
|
31107
|
+
}
|
|
31108
|
+
} else if (removeType === "OBJECT_VIEW" || removeType === "OBJECT_MATVIEW") {
|
|
31109
|
+
for (const obj of objects) {
|
|
31110
|
+
const items = obj.List?.items ?? [];
|
|
31111
|
+
if (!items.length) continue;
|
|
31112
|
+
const name = items[items.length - 1]?.String?.sval ?? "";
|
|
31113
|
+
const schema = items.length > 1 ? items[items.length - 2]?.String?.sval ?? null : null;
|
|
31114
|
+
if (!name) continue;
|
|
31115
|
+
const id = functionIdFor(name, schema);
|
|
31116
|
+
state.views = state.views.filter((v) => functionIdFor(v.name, v.schema) !== id);
|
|
31117
|
+
}
|
|
31118
|
+
}
|
|
31119
|
+
}
|
|
31120
|
+
}
|
|
31121
|
+
function indexIsPrismaUncoverable(idx) {
|
|
31122
|
+
return idx.hasPredicate || idx.hasExpressions || idx.method !== "btree";
|
|
31123
|
+
}
|
|
30678
31124
|
function loadPrismaState(schemaPath) {
|
|
30679
31125
|
if (!schemaPath || !(0, import_node_fs18.existsSync)(schemaPath)) return null;
|
|
30680
31126
|
const content = (0, import_node_fs18.readFileSync)(schemaPath, "utf-8");
|
|
@@ -30841,6 +31287,96 @@ function verify(sqlState, prisma) {
|
|
|
30841
31287
|
}
|
|
30842
31288
|
return { contradictions, flaggedEdges };
|
|
30843
31289
|
}
|
|
31290
|
+
function deriveMigrationName(sqlPath) {
|
|
31291
|
+
const segments = sqlPath.split(/[\\/]/);
|
|
31292
|
+
const last = segments[segments.length - 1];
|
|
31293
|
+
if (last === "migration.sql" && segments.length >= 2) {
|
|
31294
|
+
return segments[segments.length - 2];
|
|
31295
|
+
}
|
|
31296
|
+
return last.replace(/\.sql$/, "");
|
|
31297
|
+
}
|
|
31298
|
+
function extractMigrationInfoFromStmts(stmts, name, filepath) {
|
|
31299
|
+
let isDestructive = false;
|
|
31300
|
+
let hasOrphanCheck = false;
|
|
31301
|
+
let hasSidecarBackup = false;
|
|
31302
|
+
let hasPreFlightNotice = false;
|
|
31303
|
+
let containsBackfill = false;
|
|
31304
|
+
let containsDropColumn = false;
|
|
31305
|
+
let containsDropTable = false;
|
|
31306
|
+
for (const wrap of stmts) {
|
|
31307
|
+
const stmt = wrap.stmt ?? {};
|
|
31308
|
+
const kind = Object.keys(stmt)[0];
|
|
31309
|
+
if (!kind) continue;
|
|
31310
|
+
const body = stmt[kind] ?? {};
|
|
31311
|
+
switch (kind) {
|
|
31312
|
+
case "AlterTableStmt": {
|
|
31313
|
+
const cmds = body.cmds ?? [];
|
|
31314
|
+
for (const c of cmds) {
|
|
31315
|
+
const subtype = c.AlterTableCmd?.subtype;
|
|
31316
|
+
if (subtype === "AT_DropColumn") {
|
|
31317
|
+
containsDropColumn = true;
|
|
31318
|
+
isDestructive = true;
|
|
31319
|
+
} else if (subtype === "AT_AlterColumnType" || subtype === "AT_DropNotNull" || subtype === "AT_DropConstraint") {
|
|
31320
|
+
isDestructive = true;
|
|
31321
|
+
}
|
|
31322
|
+
}
|
|
31323
|
+
break;
|
|
31324
|
+
}
|
|
31325
|
+
case "DropStmt": {
|
|
31326
|
+
const removeType = body.removeType ?? "";
|
|
31327
|
+
if (removeType === "OBJECT_TABLE") {
|
|
31328
|
+
containsDropTable = true;
|
|
31329
|
+
isDestructive = true;
|
|
31330
|
+
} else if (removeType === "OBJECT_TYPE" || removeType === "OBJECT_COLUMN" || removeType === "OBJECT_INDEX" || removeType === "OBJECT_POLICY") {
|
|
31331
|
+
isDestructive = true;
|
|
31332
|
+
}
|
|
31333
|
+
break;
|
|
31334
|
+
}
|
|
31335
|
+
case "CreateStmt": {
|
|
31336
|
+
const relname = body.relation?.relname ?? "";
|
|
31337
|
+
if (relname.startsWith("_backup_")) hasSidecarBackup = true;
|
|
31338
|
+
break;
|
|
31339
|
+
}
|
|
31340
|
+
case "CreateTableAsStmt": {
|
|
31341
|
+
const relname = body.into?.rel?.relname ?? "";
|
|
31342
|
+
if (relname.startsWith("_backup_")) hasSidecarBackup = true;
|
|
31343
|
+
break;
|
|
31344
|
+
}
|
|
31345
|
+
case "UpdateStmt":
|
|
31346
|
+
case "InsertStmt":
|
|
31347
|
+
case "DeleteStmt": {
|
|
31348
|
+
containsBackfill = true;
|
|
31349
|
+
break;
|
|
31350
|
+
}
|
|
31351
|
+
case "DoStmt": {
|
|
31352
|
+
const args = body.args ?? [];
|
|
31353
|
+
for (const arg of args) {
|
|
31354
|
+
const def = arg.DefElem;
|
|
31355
|
+
if (!def || def.defname !== "as") continue;
|
|
31356
|
+
const code = def.arg?.String?.sval ?? "";
|
|
31357
|
+
if (/\bRAISE\s+EXCEPTION\b/i.test(code)) hasOrphanCheck = true;
|
|
31358
|
+
if (/\bRAISE\s+NOTICE\b/i.test(code)) hasPreFlightNotice = true;
|
|
31359
|
+
}
|
|
31360
|
+
break;
|
|
31361
|
+
}
|
|
31362
|
+
}
|
|
31363
|
+
}
|
|
31364
|
+
const tsMatch = name.match(/^(\d{8,14})/);
|
|
31365
|
+
const timestamp = tsMatch ? tsMatch[1] : null;
|
|
31366
|
+
return {
|
|
31367
|
+
name,
|
|
31368
|
+
filepath,
|
|
31369
|
+
timestamp,
|
|
31370
|
+
isDestructive,
|
|
31371
|
+
hasOrphanCheck,
|
|
31372
|
+
hasSidecarBackup,
|
|
31373
|
+
hasPreFlightNotice,
|
|
31374
|
+
containsBackfill,
|
|
31375
|
+
containsDropColumn,
|
|
31376
|
+
containsDropTable,
|
|
31377
|
+
statementCount: stmts.length
|
|
31378
|
+
};
|
|
31379
|
+
}
|
|
30844
31380
|
function migrationsDirFor(rootDir) {
|
|
30845
31381
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
30846
31382
|
if (!paths) return null;
|
|
@@ -30895,6 +31431,132 @@ function generate3(rootDir) {
|
|
|
30895
31431
|
values: [...sqlEnum.values]
|
|
30896
31432
|
});
|
|
30897
31433
|
}
|
|
31434
|
+
let indexNodeCount = 0;
|
|
31435
|
+
for (const idx of sqlState.indexes) {
|
|
31436
|
+
if (!indexIsPrismaUncoverable(idx)) continue;
|
|
31437
|
+
nodes.push({
|
|
31438
|
+
id: `index:${idx.name}`,
|
|
31439
|
+
type: "index",
|
|
31440
|
+
name: idx.name,
|
|
31441
|
+
source: "sql",
|
|
31442
|
+
table: idx.table,
|
|
31443
|
+
unique: idx.unique,
|
|
31444
|
+
method: idx.method,
|
|
31445
|
+
columns: idx.columns,
|
|
31446
|
+
has_expressions: idx.hasExpressions,
|
|
31447
|
+
has_predicate: idx.hasPredicate,
|
|
31448
|
+
filepath: idx.filepath
|
|
31449
|
+
});
|
|
31450
|
+
indexNodeCount++;
|
|
31451
|
+
}
|
|
31452
|
+
let extensionNodeCount = 0;
|
|
31453
|
+
for (const ext of sqlState.extensions) {
|
|
31454
|
+
nodes.push({
|
|
31455
|
+
id: `extension:${ext.name}`,
|
|
31456
|
+
type: "extension",
|
|
31457
|
+
name: ext.name,
|
|
31458
|
+
source: "sql",
|
|
31459
|
+
schema: ext.schema,
|
|
31460
|
+
version: ext.version,
|
|
31461
|
+
filepath: ext.filepath
|
|
31462
|
+
});
|
|
31463
|
+
extensionNodeCount++;
|
|
31464
|
+
}
|
|
31465
|
+
let triggerNodeCount = 0;
|
|
31466
|
+
for (const trg of sqlState.triggers) {
|
|
31467
|
+
nodes.push({
|
|
31468
|
+
id: `trigger:${trg.table}:${trg.name}`,
|
|
31469
|
+
type: "trigger",
|
|
31470
|
+
name: trg.name,
|
|
31471
|
+
source: "sql",
|
|
31472
|
+
table: trg.table,
|
|
31473
|
+
timing: trg.timing,
|
|
31474
|
+
events: trg.events,
|
|
31475
|
+
function: trg.function,
|
|
31476
|
+
has_when: trg.hasWhen,
|
|
31477
|
+
for_each: trg.forEach,
|
|
31478
|
+
filepath: trg.filepath
|
|
31479
|
+
});
|
|
31480
|
+
triggerNodeCount++;
|
|
31481
|
+
}
|
|
31482
|
+
let functionNodeCount = 0;
|
|
31483
|
+
for (const fn of sqlState.functions) {
|
|
31484
|
+
const qualified = fn.schema ? `${fn.schema}.${fn.name}` : fn.name;
|
|
31485
|
+
nodes.push({
|
|
31486
|
+
id: `function:${qualified}`,
|
|
31487
|
+
type: "function",
|
|
31488
|
+
name: fn.name,
|
|
31489
|
+
source: "sql",
|
|
31490
|
+
schema: fn.schema,
|
|
31491
|
+
language: fn.language,
|
|
31492
|
+
return_type: fn.returnType,
|
|
31493
|
+
is_procedure: fn.isProcedure,
|
|
31494
|
+
filepath: fn.filepath
|
|
31495
|
+
});
|
|
31496
|
+
functionNodeCount++;
|
|
31497
|
+
}
|
|
31498
|
+
let viewNodeCount = 0;
|
|
31499
|
+
for (const vw of sqlState.views) {
|
|
31500
|
+
const qualified = vw.schema ? `${vw.schema}.${vw.name}` : vw.name;
|
|
31501
|
+
nodes.push({
|
|
31502
|
+
id: `${vw.isMaterialized ? "matview" : "view"}:${qualified}`,
|
|
31503
|
+
type: vw.isMaterialized ? "materialized_view" : "view",
|
|
31504
|
+
name: vw.name,
|
|
31505
|
+
source: "sql",
|
|
31506
|
+
schema: vw.schema,
|
|
31507
|
+
is_materialized: vw.isMaterialized,
|
|
31508
|
+
with_check_option: vw.withCheckOption,
|
|
31509
|
+
filepath: vw.filepath
|
|
31510
|
+
});
|
|
31511
|
+
viewNodeCount++;
|
|
31512
|
+
}
|
|
31513
|
+
let policyNodeCount = 0;
|
|
31514
|
+
for (const pol of sqlState.policies) {
|
|
31515
|
+
nodes.push({
|
|
31516
|
+
id: `policy:${pol.table}:${pol.name}`,
|
|
31517
|
+
type: "policy",
|
|
31518
|
+
name: pol.name,
|
|
31519
|
+
source: "sql",
|
|
31520
|
+
table: pol.table,
|
|
31521
|
+
command: pol.command,
|
|
31522
|
+
permissive: pol.permissive,
|
|
31523
|
+
roles: pol.roles,
|
|
31524
|
+
has_using: pol.hasUsing,
|
|
31525
|
+
has_with_check: pol.hasWithCheck,
|
|
31526
|
+
filepath: pol.filepath
|
|
31527
|
+
});
|
|
31528
|
+
policyNodeCount++;
|
|
31529
|
+
}
|
|
31530
|
+
const migrationFiles = migrationsDir ? discoverMigrationFiles(migrationsDir) : [];
|
|
31531
|
+
let migrationNodeCount = 0;
|
|
31532
|
+
for (const sqlPath of migrationFiles) {
|
|
31533
|
+
const sql = (0, import_node_fs18.readFileSync)(sqlPath, "utf-8");
|
|
31534
|
+
const name = deriveMigrationName(sqlPath);
|
|
31535
|
+
let ast;
|
|
31536
|
+
try {
|
|
31537
|
+
ast = postgresDialect.parse(sql);
|
|
31538
|
+
} catch {
|
|
31539
|
+
ast = { stmts: [] };
|
|
31540
|
+
}
|
|
31541
|
+
const info = postgresDialect.extractMigrationInfo(ast, name, sqlPath);
|
|
31542
|
+
nodes.push({
|
|
31543
|
+
id: `migration:${name}`,
|
|
31544
|
+
type: "migration",
|
|
31545
|
+
name,
|
|
31546
|
+
source: "sql",
|
|
31547
|
+
filepath: info.filepath,
|
|
31548
|
+
timestamp: info.timestamp,
|
|
31549
|
+
is_destructive: info.isDestructive,
|
|
31550
|
+
has_orphan_check: info.hasOrphanCheck,
|
|
31551
|
+
has_sidecar_backup: info.hasSidecarBackup,
|
|
31552
|
+
has_pre_flight_notice: info.hasPreFlightNotice,
|
|
31553
|
+
contains_backfill: info.containsBackfill,
|
|
31554
|
+
contains_drop_column: info.containsDropColumn,
|
|
31555
|
+
contains_drop_table: info.containsDropTable,
|
|
31556
|
+
statement_count: info.statementCount
|
|
31557
|
+
});
|
|
31558
|
+
migrationNodeCount++;
|
|
31559
|
+
}
|
|
30898
31560
|
const sqlOnlyTables = new Set(nodes.filter((n) => n.type === "table").map((n) => n.id));
|
|
30899
31561
|
const edges = sqlState.fks.filter((fk) => sqlOnlyTables.has(fk.sourceTable)).map((fk) => ({
|
|
30900
31562
|
source: fk.sourceTable,
|
|
@@ -30913,6 +31575,13 @@ function generate3(rootDir) {
|
|
|
30913
31575
|
sql_tables: sqlState.tables.size,
|
|
30914
31576
|
sql_enums: sqlState.enums.size,
|
|
30915
31577
|
sql_fks: sqlState.fks.length,
|
|
31578
|
+
sql_index_nodes: indexNodeCount,
|
|
31579
|
+
sql_policy_nodes: policyNodeCount,
|
|
31580
|
+
sql_extension_nodes: extensionNodeCount,
|
|
31581
|
+
sql_trigger_nodes: triggerNodeCount,
|
|
31582
|
+
sql_function_nodes: functionNodeCount,
|
|
31583
|
+
sql_view_nodes: viewNodeCount,
|
|
31584
|
+
sql_migration_nodes: migrationNodeCount,
|
|
30916
31585
|
additive_nodes: nodes.length,
|
|
30917
31586
|
contradictions_found: contradictions.length,
|
|
30918
31587
|
flagged_edges_found: flaggedEdges.length
|
|
@@ -33053,15 +33722,75 @@ function runAudit(rootDir, layer, check) {
|
|
|
33053
33722
|
}
|
|
33054
33723
|
|
|
33055
33724
|
// src/server/graph/core/projects.ts
|
|
33725
|
+
var import_node_path31 = require("node:path");
|
|
33726
|
+
|
|
33727
|
+
// src/server/lib/worktree.ts
|
|
33728
|
+
var import_node_path30 = require("node:path");
|
|
33729
|
+
|
|
33730
|
+
// src/server/orbit/registry.ts
|
|
33731
|
+
var import_node_fs26 = require("node:fs");
|
|
33732
|
+
var import_node_os5 = require("node:os");
|
|
33056
33733
|
var import_node_path29 = require("node:path");
|
|
33734
|
+
init_launch_kit_paths();
|
|
33735
|
+
var REGISTRY_DIR = (0, import_node_path29.join)((0, import_node_os5.homedir)(), LAUNCHSECURE_DIR, "orbit");
|
|
33736
|
+
var REGISTRY_PATH = (0, import_node_path29.join)(REGISTRY_DIR, "state.json");
|
|
33737
|
+
var LOCK_PATH = (0, import_node_path29.join)(REGISTRY_DIR, "state.json.lock");
|
|
33738
|
+
function emptyRegistry() {
|
|
33739
|
+
return { version: 1, worktrees: {} };
|
|
33740
|
+
}
|
|
33741
|
+
function readRegistry() {
|
|
33742
|
+
if (!(0, import_node_fs26.existsSync)(REGISTRY_PATH)) return emptyRegistry();
|
|
33743
|
+
try {
|
|
33744
|
+
const parsed = JSON.parse((0, import_node_fs26.readFileSync)(REGISTRY_PATH, "utf-8"));
|
|
33745
|
+
if (parsed?.version === 1 && parsed.worktrees && typeof parsed.worktrees === "object") {
|
|
33746
|
+
return parsed;
|
|
33747
|
+
}
|
|
33748
|
+
} catch {
|
|
33749
|
+
}
|
|
33750
|
+
return emptyRegistry();
|
|
33751
|
+
}
|
|
33752
|
+
function listWorktrees() {
|
|
33753
|
+
return Object.values(readRegistry().worktrees);
|
|
33754
|
+
}
|
|
33755
|
+
|
|
33756
|
+
// src/server/lib/worktree.ts
|
|
33757
|
+
var 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`.";
|
|
33758
|
+
var 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.";
|
|
33759
|
+
function resolveWorktreeRoot(slug, monorepoRoot) {
|
|
33760
|
+
const local = listWorktrees().filter((w) => w.projectRoot === monorepoRoot);
|
|
33761
|
+
const match = local.find((w) => w.slug === slug);
|
|
33762
|
+
if (match) return match.path;
|
|
33763
|
+
if (local.length === 0) {
|
|
33764
|
+
throw new Error(
|
|
33765
|
+
`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.`
|
|
33766
|
+
);
|
|
33767
|
+
}
|
|
33768
|
+
const available = local.map((w) => `"${w.slug}" (${w.branch})`).join(", ");
|
|
33769
|
+
throw new Error(`Unknown worktree "${slug}". Available: ${available}.`);
|
|
33770
|
+
}
|
|
33771
|
+
function resolveWorktreeOrProjectRoot(args, monorepoRoot) {
|
|
33772
|
+
const projectRoot = typeof args.project_root === "string" ? args.project_root.trim() : "";
|
|
33773
|
+
if (projectRoot) {
|
|
33774
|
+
return (0, import_node_path30.isAbsolute)(projectRoot) ? projectRoot : (0, import_node_path30.resolve)(monorepoRoot, projectRoot);
|
|
33775
|
+
}
|
|
33776
|
+
const worktree = typeof args.worktree === "string" ? args.worktree.trim() : "";
|
|
33777
|
+
if (worktree) {
|
|
33778
|
+
return resolveWorktreeRoot(worktree, monorepoRoot);
|
|
33779
|
+
}
|
|
33780
|
+
return null;
|
|
33781
|
+
}
|
|
33782
|
+
|
|
33783
|
+
// src/server/graph/core/projects.ts
|
|
33057
33784
|
init_config();
|
|
33785
|
+
var WORKTREE_PARAM_DESCRIPTION2 = WORKTREE_PARAM_DESCRIPTION;
|
|
33786
|
+
var PROJECT_ROOT_PARAM_DESCRIPTION2 = PROJECT_ROOT_PARAM_DESCRIPTION;
|
|
33058
33787
|
function listProjects(monorepoRoot) {
|
|
33059
33788
|
const cfg = loadConfig(monorepoRoot);
|
|
33060
33789
|
const entries = cfg.projects ?? [];
|
|
33061
33790
|
return entries.map((p) => ({
|
|
33062
33791
|
name: p.name,
|
|
33063
33792
|
root: p.root,
|
|
33064
|
-
absoluteRoot: (0,
|
|
33793
|
+
absoluteRoot: (0, import_node_path31.resolve)(monorepoRoot, p.root)
|
|
33065
33794
|
}));
|
|
33066
33795
|
}
|
|
33067
33796
|
function resolveProject(name, projects) {
|
|
@@ -33084,6 +33813,12 @@ function resolveProjectRoot(project, monorepoRoot) {
|
|
|
33084
33813
|
return resolveProject(raw, projects).absoluteRoot;
|
|
33085
33814
|
}
|
|
33086
33815
|
var 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.";
|
|
33816
|
+
function resolveRequestRoot(args, monorepoRoot) {
|
|
33817
|
+
const fromArgs = resolveWorktreeOrProjectRoot(args, monorepoRoot);
|
|
33818
|
+
if (fromArgs) return fromArgs;
|
|
33819
|
+
const project = typeof args.project === "string" ? args.project : void 0;
|
|
33820
|
+
return resolveProjectRoot(project, monorepoRoot);
|
|
33821
|
+
}
|
|
33087
33822
|
|
|
33088
33823
|
// src/server/graph-mcp.ts
|
|
33089
33824
|
init_freshness();
|
|
@@ -33105,13 +33840,21 @@ var TOOLS = [
|
|
|
33105
33840
|
project: {
|
|
33106
33841
|
type: "string",
|
|
33107
33842
|
description: PROJECT_PARAM_DESCRIPTION + " Special: omit to regenerate ALL configured projects."
|
|
33843
|
+
},
|
|
33844
|
+
worktree: {
|
|
33845
|
+
type: "string",
|
|
33846
|
+
description: WORKTREE_PARAM_DESCRIPTION2
|
|
33847
|
+
},
|
|
33848
|
+
project_root: {
|
|
33849
|
+
type: "string",
|
|
33850
|
+
description: PROJECT_ROOT_PARAM_DESCRIPTION2
|
|
33108
33851
|
}
|
|
33109
33852
|
}
|
|
33110
33853
|
}
|
|
33111
33854
|
},
|
|
33112
33855
|
{
|
|
33113
33856
|
name: "read_graph",
|
|
33114
|
-
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
|
|
33857
|
+
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.',
|
|
33115
33858
|
inputSchema: {
|
|
33116
33859
|
type: "object",
|
|
33117
33860
|
properties: {
|
|
@@ -33121,7 +33864,7 @@ var TOOLS = [
|
|
|
33121
33864
|
},
|
|
33122
33865
|
search: {
|
|
33123
33866
|
type: "string",
|
|
33124
|
-
description: "Case-insensitive substring match against node id, name,
|
|
33867
|
+
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)."
|
|
33125
33868
|
},
|
|
33126
33869
|
type: {
|
|
33127
33870
|
type: "string",
|
|
@@ -33155,6 +33898,36 @@ var TOOLS = [
|
|
|
33155
33898
|
type: "boolean",
|
|
33156
33899
|
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."
|
|
33157
33900
|
},
|
|
33901
|
+
include_findings: {
|
|
33902
|
+
type: "boolean",
|
|
33903
|
+
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."
|
|
33904
|
+
},
|
|
33905
|
+
tag_predicates: {
|
|
33906
|
+
type: "array",
|
|
33907
|
+
description: 'Multi-condition AND filter over node attributes. Each predicate is { field, op, value }. `field` matches either a top-level node attribute (e.g. is_destructive, mutates, statement_count) OR a tag key (tag values take precedence when both exist). `op` \u2208 {eq, ne, gt, lt, gte, lte, exists, not_exists, in}. Example: [{field:"is_destructive",op:"eq",value:true},{field:"has_orphan_check",op:"eq",value:false}] returns destructive migrations missing an orphan check.',
|
|
33908
|
+
items: {
|
|
33909
|
+
type: "object",
|
|
33910
|
+
properties: {
|
|
33911
|
+
field: { type: "string", description: "Node attribute or tag key." },
|
|
33912
|
+
op: { type: "string", enum: ["eq", "ne", "gt", "lt", "gte", "lte", "exists", "not_exists", "in"] },
|
|
33913
|
+
value: { description: "Comparison value. Required for all ops except exists/not_exists. Pass an array for `in`." }
|
|
33914
|
+
},
|
|
33915
|
+
required: ["field", "op"]
|
|
33916
|
+
}
|
|
33917
|
+
},
|
|
33918
|
+
include_cross_refs: {
|
|
33919
|
+
type: "boolean",
|
|
33920
|
+
description: "When true, attach `cross_refs` (incoming + outgoing) to each returned node. Unlocks the cross-layer reference data (references_static, calls_api, reads_via, mutates_via, reads, mutates, references_api) that is otherwise dropped from responses. Combine with `cross_ref_type` to narrow."
|
|
33921
|
+
},
|
|
33922
|
+
cross_ref_type: {
|
|
33923
|
+
type: "string",
|
|
33924
|
+
description: "Narrow attached cross_refs by kind. Common values: calls_api, references_static, reads_via, mutates_via, reads, mutates, references_api. Only applies when include_cross_refs is true."
|
|
33925
|
+
},
|
|
33926
|
+
edge_confidence: {
|
|
33927
|
+
type: "array",
|
|
33928
|
+
description: 'Filter to surface low/medium/high-confidence flagged_edges in the response. Pass e.g. ["medium","high"] to get DYNAMIC navigation targets, FK drift, etc. Scoped to matched nodes when a node filter is in play; otherwise returns all flagged_edges at the requested confidence levels. Layer-independent.',
|
|
33929
|
+
items: { type: "string", enum: ["low", "medium", "high"] }
|
|
33930
|
+
},
|
|
33158
33931
|
offset: {
|
|
33159
33932
|
type: "number",
|
|
33160
33933
|
description: "Skip first N matched nodes (pagination). Default 0. Use next_offset from a previous response to get the next page."
|
|
@@ -33165,7 +33938,7 @@ var TOOLS = [
|
|
|
33165
33938
|
},
|
|
33166
33939
|
queries: {
|
|
33167
33940
|
type: "array",
|
|
33168
|
-
description: "Batch mode \u2014 array of query objects to run in a single call. Each uses the same param schema (including `project`).
|
|
33941
|
+
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.",
|
|
33169
33942
|
items: {
|
|
33170
33943
|
type: "object",
|
|
33171
33944
|
properties: {
|
|
@@ -33177,13 +33950,28 @@ var TOOLS = [
|
|
|
33177
33950
|
hops: { type: "number" },
|
|
33178
33951
|
minimal: { type: "boolean" },
|
|
33179
33952
|
include_edges: { type: "boolean" },
|
|
33180
|
-
|
|
33953
|
+
include_findings: { type: "boolean" },
|
|
33954
|
+
tag_predicates: { type: "array" },
|
|
33955
|
+
include_cross_refs: { type: "boolean" },
|
|
33956
|
+
cross_ref_type: { type: "string" },
|
|
33957
|
+
edge_confidence: { type: "array" },
|
|
33958
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
33959
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
33960
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
33181
33961
|
}
|
|
33182
33962
|
}
|
|
33183
33963
|
},
|
|
33184
33964
|
project: {
|
|
33185
33965
|
type: "string",
|
|
33186
33966
|
description: PROJECT_PARAM_DESCRIPTION
|
|
33967
|
+
},
|
|
33968
|
+
worktree: {
|
|
33969
|
+
type: "string",
|
|
33970
|
+
description: WORKTREE_PARAM_DESCRIPTION2
|
|
33971
|
+
},
|
|
33972
|
+
project_root: {
|
|
33973
|
+
type: "string",
|
|
33974
|
+
description: PROJECT_ROOT_PARAM_DESCRIPTION2
|
|
33187
33975
|
}
|
|
33188
33976
|
}
|
|
33189
33977
|
}
|
|
@@ -33231,7 +34019,9 @@ Returns: { pattern, filter, files_searched, total_matches, matches: [{file, line
|
|
|
33231
34019
|
context: { type: "number", description: "Context lines around each match. Default 2." },
|
|
33232
34020
|
max_matches: { type: "number", description: "Max matches to return total. Default 50." },
|
|
33233
34021
|
max_files: { type: "number", description: "Max files to search. Default 50." },
|
|
33234
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
34022
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34023
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34024
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
33235
34025
|
},
|
|
33236
34026
|
required: ["layer", "pattern"]
|
|
33237
34027
|
}
|
|
@@ -33263,17 +34053,23 @@ Returns deep fields only \u2014 not structural metadata (use read_graph for that
|
|
|
33263
34053
|
fields: {
|
|
33264
34054
|
type: "array",
|
|
33265
34055
|
items: { type: "string" },
|
|
33266
|
-
description:
|
|
34056
|
+
description: `Specific deep fields to return. Options: elements, stateVars, conditions, variables, responses, params, effects, crossRefs. Omit for all. "effects" returns the file-level side-effect summary (calls, dom_writes, subscribes, timers, persists, fetches, globals); per-arrow-function effects are also attached to each entry in "variables". "crossRefs" returns this node's incoming + outgoing cross-layer references (calls_api, references_static, reads_via, mutates_via, reads, mutates, references_api) \u2014 the cross-layer linking data otherwise dropped from responses.`
|
|
33267
34057
|
},
|
|
33268
34058
|
filter: {
|
|
33269
34059
|
type: "string",
|
|
33270
34060
|
description: "Regex pattern to search WITHIN deep field values. Only returns nodes where at least one deep field matches. Searches across all string values in the requested fields (condition tests, variable inits, element tags/props, response bodies, etc.). When set, search becomes optional and node limit is raised to 50."
|
|
33271
34061
|
},
|
|
34062
|
+
cross_ref_type: {
|
|
34063
|
+
type: "string",
|
|
34064
|
+
description: 'Narrow returned crossRefs by kind (e.g. calls_api, references_static, reads_via). Only applies when "crossRefs" is included in fields (or fields is omitted).'
|
|
34065
|
+
},
|
|
33272
34066
|
case_insensitive: {
|
|
33273
34067
|
type: "boolean",
|
|
33274
34068
|
description: "Case-insensitive filter matching. Default true."
|
|
33275
34069
|
},
|
|
33276
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
34070
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34071
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34072
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
33277
34073
|
},
|
|
33278
34074
|
required: ["layer"]
|
|
33279
34075
|
}
|
|
@@ -33311,20 +34107,111 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
33311
34107
|
},
|
|
33312
34108
|
{
|
|
33313
34109
|
name: "effects_index",
|
|
33314
|
-
description: 'Cross-layer inverted index over per-node side effects. Answers "who else touches X?" without re-walking every node.
|
|
34110
|
+
description: 'Cross-layer inverted index over per-node side effects. Answers "who else touches X?" without re-walking every node. Most kinds load from a precomputed effects-index.json (built when generate_graph runs); fetch_calls and api_effects are computed on demand from the ui/api graphs so they always reflect current state.\n\nUSE THIS FOR: "is mountFoo safe to instantiate twice?" (kind="singleton_risks"), "who writes DOM id moon-shadow-blur?" (kind="dom_ids", key="moon-shadow-blur"), "which files attach a window keydown listener?" (kind="window_events", key="window:keydown"), "who else writes localStorage key panchang.settings.v1?" (kind="storage_keys", key="..."), "any DOM-id collisions?" (kind="collisions"), "who calls /api/work-items?" (kind="fetch_calls", key="app/api/.../work-items/route.ts"), "which endpoints call db.user.findUnique?" (kind="api_effects", key="db.user.findUnique"). \n\nReturns vary by kind: {key:[nodeIds]} map for standard kinds; {dom_ids, storage_keys, window_events} for collisions; flat node-id list for singleton_risks; {api_endpoint|url_template: [ui_files]} for fetch_calls; {category: {item: [endpoint_ids]}} summary for api_effects (or {category, key, hits} when key is set).',
|
|
33315
34111
|
inputSchema: {
|
|
33316
34112
|
type: "object",
|
|
33317
34113
|
properties: {
|
|
33318
34114
|
kind: {
|
|
33319
34115
|
type: "string",
|
|
33320
|
-
enum: ["dom_ids", "window_events", "storage_keys", "fetch_urls", "timers", "singleton_risks", "collisions"],
|
|
33321
|
-
description:
|
|
34116
|
+
enum: ["dom_ids", "window_events", "storage_keys", "fetch_urls", "timers", "singleton_risks", "collisions", "fetch_calls", "api_effects"],
|
|
34117
|
+
description: 'Which inverted index to query. Default: collisions (most actionable signal). fetch_calls = "UI\u2192API call inventory" (resolved via ui cross_refs + raw fetch strings). api_effects = "what does each endpoint touch" inverted across all effect categories (calls / fetches / persists / subscribes / timers / dom_writes / globals).'
|
|
33322
34118
|
},
|
|
33323
34119
|
key: {
|
|
33324
34120
|
type: "string",
|
|
33325
|
-
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
|
|
34121
|
+
description: 'Optional specific key to look up within the chosen kind (e.g. "moon-shadow-blur" for dom_ids, "/api/work-items" for fetch_calls, "db.user.findUnique" for api_effects). When omitted, returns the full {key:nodes} map (or per-category summary for api_effects).'
|
|
33326
34122
|
},
|
|
33327
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
34123
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34124
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34125
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
34126
|
+
}
|
|
34127
|
+
}
|
|
34128
|
+
},
|
|
34129
|
+
{
|
|
34130
|
+
name: "migration_audit",
|
|
34131
|
+
description: `List Prisma migrations that violate the migration-safety discipline: destructive migrations missing one or more guards (orphan check, sidecar backup, pre-flight notice). Codifies the three-layer migration safety rules \u2014 derived from each migration node's extracted attributes (is_destructive, has_orphan_check, has_sidecar_backup, has_pre_flight_notice, contains_backfill, contains_drop_column, contains_drop_table).
|
|
34132
|
+
|
|
34133
|
+
USE THIS FOR: "are there unsafe migrations on this branch", "audit the migration history before deploy", "find migrations that drop columns without backup". Paginated. Returns one entry per offending migration with the specific missing guards listed.`,
|
|
34134
|
+
inputSchema: {
|
|
34135
|
+
type: "object",
|
|
34136
|
+
properties: {
|
|
34137
|
+
require_orphan_check: { type: "boolean", description: "Flag destructive migrations missing an orphan-count guard. Default true." },
|
|
34138
|
+
require_sidecar_backup: { type: "boolean", description: "Flag destructive migrations missing a sidecar backup table. Default true." },
|
|
34139
|
+
require_pre_flight_notice: { type: "boolean", description: "Flag destructive migrations missing a RAISE NOTICE pre-flight. Default true." },
|
|
34140
|
+
include_safe: { type: "boolean", description: "Also return migrations that pass all checks (no violations). Default false \u2014 by default only offenders are returned." },
|
|
34141
|
+
offset: { type: "number", description: "Pagination offset. Default 0." },
|
|
34142
|
+
limit: { type: "number", description: "Max migrations to return. Default 10." },
|
|
34143
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34144
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34145
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
34146
|
+
}
|
|
34147
|
+
}
|
|
34148
|
+
},
|
|
34149
|
+
{
|
|
34150
|
+
name: "drift_report",
|
|
34151
|
+
description: 'Aggregate cross-layer drift findings \u2014 contradictions (schema disagreements) + flagged_edges (low-confidence resolutions, FK drift, dynamic routes) \u2014 into one report. Generalizes the db-only `read_graph include_findings` flag to all layers.\n\nUSE THIS FOR: "is the codebase drifting", "show me SQL vs ORM mismatches", "list endpoints/components with unresolved routes", "audit schema health before refactor". Paginated. Returns items with { layer, kind, source, target, detail, confidence }.',
|
|
34152
|
+
inputSchema: {
|
|
34153
|
+
type: "object",
|
|
34154
|
+
properties: {
|
|
34155
|
+
layer: { type: "string", description: "Restrict to one layer (e.g. db, ui). Omit for all layers." },
|
|
34156
|
+
kind: { type: "string", enum: ["contradictions", "flagged_edges", "all"], description: "Which finding type to return. Default all." },
|
|
34157
|
+
confidence: { type: "array", items: { type: "string", enum: ["low", "medium", "high"] }, description: "For flagged_edges: filter by confidence levels. Default all." },
|
|
34158
|
+
offset: { type: "number", description: "Pagination offset. Default 0." },
|
|
34159
|
+
limit: { type: "number", description: "Max items to return. Default 10." },
|
|
34160
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34161
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34162
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
34163
|
+
}
|
|
34164
|
+
}
|
|
34165
|
+
},
|
|
34166
|
+
{
|
|
34167
|
+
name: "who_uses",
|
|
34168
|
+
description: 'List everything that references a target node via cross_refs \u2014 the cross-layer references_static / calls_api / reads_via / mutates_via / reads / mutates / references_api edges that are otherwise dropped from responses.\n\nUSE THIS FOR: "where is permission `manage_billing` used", "which UI components reference enum `WorkItemPriority`", "which endpoints touch the User table", "which files call /api/work-items". Auto-scans all layers\' cross_refs for incoming edges to the target. Paginated.',
|
|
34169
|
+
inputSchema: {
|
|
34170
|
+
type: "object",
|
|
34171
|
+
properties: {
|
|
34172
|
+
target: { type: "string", description: 'Target node id to look up (e.g. "seed:permission:manage_billing", "User", "app/api/.../work-items/route.ts", "WorkItemPriority").' },
|
|
34173
|
+
cross_ref_type: { type: "string", description: "Narrow by cross-ref kind (calls_api / references_static / reads_via / mutates_via / reads / mutates / references_api). Omit for all kinds." },
|
|
34174
|
+
offset: { type: "number", description: "Pagination offset. Default 0." },
|
|
34175
|
+
limit: { type: "number", description: "Max references to return. Default 10." },
|
|
34176
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34177
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34178
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
34179
|
+
},
|
|
34180
|
+
required: ["target"]
|
|
34181
|
+
}
|
|
34182
|
+
},
|
|
34183
|
+
{
|
|
34184
|
+
name: "trace_path",
|
|
34185
|
+
description: 'Find shortest paths between two nodes across layers via cross_refs (including the via[] chains that capture transitive DB access through middleware).\n\nUSE THIS FOR: "how does AdminAnalyticsPage end up reading the User table" \u2014 returns the call chain page \u2192 fetch \u2192 endpoint \u2192 middleware \u2192 db. BFS-bounded by max_hops + max_paths to keep responses tractable on hub nodes. Paginated.',
|
|
34186
|
+
inputSchema: {
|
|
34187
|
+
type: "object",
|
|
34188
|
+
properties: {
|
|
34189
|
+
from: { type: "string", description: "Source node id." },
|
|
34190
|
+
to: { type: "string", description: "Target node id." },
|
|
34191
|
+
max_hops: { type: "number", description: "Hard cap on path length. Default 5." },
|
|
34192
|
+
max_paths: { type: "number", description: "Hard cap on total paths discovered. Default 10. Once hit, BFS stops." },
|
|
34193
|
+
offset: { type: "number", description: "Pagination offset over discovered paths. Default 0." },
|
|
34194
|
+
limit: { type: "number", description: "Max paths returned per response. Default 10." },
|
|
34195
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34196
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34197
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
34198
|
+
},
|
|
34199
|
+
required: ["from", "to"]
|
|
34200
|
+
}
|
|
34201
|
+
},
|
|
34202
|
+
{
|
|
34203
|
+
name: "auth_coverage_report",
|
|
34204
|
+
description: 'Aggregate every API endpoint by its auth[] wrapper(s) \u2014 surfaces endpoints with empty auth, groups by module, shows which auth strategies dominate. Computed from api.json endpoint auth field (100% populated).\n\nUSE THIS FOR: "are there unauthenticated endpoints", "which auth wrappers are in use", "audit auth strategy consistency across modules". Paginated. Returns { total, by_strategy: {strategy: count}, unauthenticated: [endpoint_ids], by_module: {module: {total, by_strategy}} } plus the paginated endpoint list.',
|
|
34205
|
+
inputSchema: {
|
|
34206
|
+
type: "object",
|
|
34207
|
+
properties: {
|
|
34208
|
+
module: { type: "string", description: 'Restrict to one module tag (e.g. "orgs", "admin").' },
|
|
34209
|
+
strategy: { type: "string", description: 'Restrict to endpoints using this auth wrapper (e.g. "withAuth"). Pass "none" to list unauthenticated endpoints.' },
|
|
34210
|
+
offset: { type: "number", description: "Pagination offset over the endpoint list. Default 0." },
|
|
34211
|
+
limit: { type: "number", description: "Max endpoints to return per response. Default 10." },
|
|
34212
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34213
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34214
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
33328
34215
|
}
|
|
33329
34216
|
}
|
|
33330
34217
|
},
|
|
@@ -33354,7 +34241,9 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
33354
34241
|
type: "string",
|
|
33355
34242
|
description: 'Tag value (e.g. "auth", "alice", "true").'
|
|
33356
34243
|
},
|
|
33357
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
34244
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34245
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34246
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
33358
34247
|
},
|
|
33359
34248
|
required: ["node_id", "key", "value"]
|
|
33360
34249
|
}
|
|
@@ -33373,7 +34262,9 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
33373
34262
|
type: "string",
|
|
33374
34263
|
description: "Tag key to remove."
|
|
33375
34264
|
},
|
|
33376
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
34265
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34266
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34267
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
33377
34268
|
},
|
|
33378
34269
|
required: ["node_id", "key"]
|
|
33379
34270
|
}
|
|
@@ -33392,7 +34283,9 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
33392
34283
|
type: "string",
|
|
33393
34284
|
description: "Specific check to run (e.g. 'schema_drift', 'unprotected_routes'). Omit to run all checks for the layer."
|
|
33394
34285
|
},
|
|
33395
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
34286
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34287
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34288
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
33396
34289
|
},
|
|
33397
34290
|
required: ["layer"]
|
|
33398
34291
|
}
|
|
@@ -33428,10 +34321,56 @@ Example: blast_points(node_id: "server/auth/middleware.ts", hops: 2) \u2192 retu
|
|
|
33428
34321
|
enum: ["reverse", "both"],
|
|
33429
34322
|
description: "'reverse' (default) = only what depends on this node. 'both' = full neighborhood."
|
|
33430
34323
|
},
|
|
33431
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION }
|
|
34324
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34325
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34326
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
33432
34327
|
},
|
|
33433
34328
|
required: ["node_id"]
|
|
33434
34329
|
}
|
|
34330
|
+
},
|
|
34331
|
+
{
|
|
34332
|
+
name: "list_notes",
|
|
34333
|
+
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?}] }.',
|
|
34334
|
+
inputSchema: {
|
|
34335
|
+
type: "object",
|
|
34336
|
+
properties: {
|
|
34337
|
+
layer: {
|
|
34338
|
+
type: "string",
|
|
34339
|
+
description: "Restrict to one layer ('ui', 'api', 'db'). Default: scan all layers that have notes."
|
|
34340
|
+
},
|
|
34341
|
+
kind: {
|
|
34342
|
+
type: "string",
|
|
34343
|
+
description: 'Exact kind match, case-insensitive (e.g. "TODO", "FIXME", "SECURITY").'
|
|
34344
|
+
},
|
|
34345
|
+
category: {
|
|
34346
|
+
type: "string",
|
|
34347
|
+
description: "'actionable' | 'warning' | 'doc' | 'custom' | 'all'. Comma-separated list also accepted. Default: 'actionable,warning'."
|
|
34348
|
+
},
|
|
34349
|
+
module: {
|
|
34350
|
+
type: "string",
|
|
34351
|
+
description: 'Restrict to nodes carrying this module tag (e.g. "auth", "work-items").'
|
|
34352
|
+
},
|
|
34353
|
+
pattern: {
|
|
34354
|
+
type: "string",
|
|
34355
|
+
description: 'Regex applied to note body text (case-insensitive). Use to grep within bodies, e.g. "race condition".'
|
|
34356
|
+
},
|
|
34357
|
+
author: {
|
|
34358
|
+
type: "string",
|
|
34359
|
+
description: "Match author from `// TODO(alice): \u2026` syntax, case-insensitive."
|
|
34360
|
+
},
|
|
34361
|
+
limit: {
|
|
34362
|
+
type: "number",
|
|
34363
|
+
description: "Max items returned. Default 100. Pair with offset for pagination."
|
|
34364
|
+
},
|
|
34365
|
+
offset: {
|
|
34366
|
+
type: "number",
|
|
34367
|
+
description: "Skip first N items. Default 0."
|
|
34368
|
+
},
|
|
34369
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34370
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
34371
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
34372
|
+
}
|
|
34373
|
+
}
|
|
33435
34374
|
}
|
|
33436
34375
|
];
|
|
33437
34376
|
function matchesSearch(node, query) {
|
|
@@ -33440,14 +34379,44 @@ function matchesSearch(node, query) {
|
|
|
33440
34379
|
if (node.name.toLowerCase().includes(q)) return true;
|
|
33441
34380
|
const route = node.route;
|
|
33442
34381
|
if (route && route.toLowerCase().includes(q)) return true;
|
|
34382
|
+
const elements = node.elements;
|
|
34383
|
+
if (elements) {
|
|
34384
|
+
for (const el of elements) {
|
|
34385
|
+
if (el.text && el.text.toLowerCase().includes(q)) return true;
|
|
34386
|
+
if (el.props) {
|
|
34387
|
+
for (const v of Object.values(el.props)) {
|
|
34388
|
+
if (typeof v !== "string") continue;
|
|
34389
|
+
if (v.includes("=>") || v.startsWith("(") || v.startsWith("{")) continue;
|
|
34390
|
+
if (v.toLowerCase().includes(q)) return true;
|
|
34391
|
+
}
|
|
34392
|
+
}
|
|
34393
|
+
}
|
|
34394
|
+
}
|
|
34395
|
+
const uiLabels = node.ui_labels;
|
|
34396
|
+
if (uiLabels) {
|
|
34397
|
+
for (const lbl of uiLabels) {
|
|
34398
|
+
if (typeof lbl === "string" && lbl.toLowerCase().includes(q)) return true;
|
|
34399
|
+
}
|
|
34400
|
+
}
|
|
34401
|
+
const notes = node.notes;
|
|
34402
|
+
if (notes) {
|
|
34403
|
+
for (const n of notes) {
|
|
34404
|
+
if (n.kind && n.kind.toLowerCase().includes(q)) return true;
|
|
34405
|
+
if (n.text && n.text.toLowerCase().includes(q)) return true;
|
|
34406
|
+
}
|
|
34407
|
+
}
|
|
33443
34408
|
return false;
|
|
33444
34409
|
}
|
|
34410
|
+
var MINIMAL_STRIP_FIELDS = /* @__PURE__ */ new Set(["columns"]);
|
|
33445
34411
|
function toMinimal(nodes) {
|
|
33446
34412
|
return nodes.map((n) => {
|
|
33447
34413
|
const out = { id: n.id, type: n.type, name: n.name };
|
|
33448
|
-
|
|
33449
|
-
|
|
33450
|
-
|
|
34414
|
+
for (const [k, v] of Object.entries(n)) {
|
|
34415
|
+
if (k === "id" || k === "type" || k === "name") continue;
|
|
34416
|
+
if (MINIMAL_STRIP_FIELDS.has(k)) continue;
|
|
34417
|
+
if (DEEP_FIELDS.has(k)) continue;
|
|
34418
|
+
if (v != null) out[k] = v;
|
|
34419
|
+
}
|
|
33451
34420
|
return out;
|
|
33452
34421
|
});
|
|
33453
34422
|
}
|
|
@@ -33498,8 +34467,34 @@ var DEEP_FIELDS = /* @__PURE__ */ new Set([
|
|
|
33498
34467
|
"variables",
|
|
33499
34468
|
"responses",
|
|
33500
34469
|
"params",
|
|
33501
|
-
"effects"
|
|
34470
|
+
"effects",
|
|
34471
|
+
"ui_labels",
|
|
34472
|
+
"notes"
|
|
33502
34473
|
]);
|
|
34474
|
+
var NOTE_KIND_CATEGORY = {
|
|
34475
|
+
TODO: "actionable",
|
|
34476
|
+
FIXME: "actionable",
|
|
34477
|
+
HACK: "actionable",
|
|
34478
|
+
XXX: "actionable",
|
|
34479
|
+
REFACTOR: "actionable",
|
|
34480
|
+
OPTIMIZE: "actionable",
|
|
34481
|
+
REVIEW: "actionable",
|
|
34482
|
+
SECURITY: "warning",
|
|
34483
|
+
WARNING: "warning",
|
|
34484
|
+
IMPORTANT: "warning",
|
|
34485
|
+
CAUTION: "warning",
|
|
34486
|
+
DANGER: "warning",
|
|
34487
|
+
DEPRECATED: "warning",
|
|
34488
|
+
NOTE: "doc",
|
|
34489
|
+
TIP: "doc",
|
|
34490
|
+
SEE: "doc",
|
|
34491
|
+
NB: "doc",
|
|
34492
|
+
INFO: "doc",
|
|
34493
|
+
EXAMPLE: "doc"
|
|
34494
|
+
};
|
|
34495
|
+
function categorizeNoteKind(kind) {
|
|
34496
|
+
return NOTE_KIND_CATEGORY[kind] ?? "custom";
|
|
34497
|
+
}
|
|
33503
34498
|
var EST_CHARS_PER_NODE_FULL = {
|
|
33504
34499
|
ui: 300,
|
|
33505
34500
|
api: 300,
|
|
@@ -33775,7 +34770,7 @@ function withFreshnessMeta(result, args) {
|
|
|
33775
34770
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return result;
|
|
33776
34771
|
let rootDir;
|
|
33777
34772
|
try {
|
|
33778
|
-
rootDir =
|
|
34773
|
+
rootDir = resolveRequestRoot(args, process.cwd());
|
|
33779
34774
|
} catch {
|
|
33780
34775
|
return result;
|
|
33781
34776
|
}
|
|
@@ -33790,7 +34785,7 @@ function withFreshnessMeta(result, args) {
|
|
|
33790
34785
|
}
|
|
33791
34786
|
function resolveOrErr(args) {
|
|
33792
34787
|
try {
|
|
33793
|
-
return { rootDir:
|
|
34788
|
+
return { rootDir: resolveRequestRoot(args, process.cwd()) };
|
|
33794
34789
|
} catch (e) {
|
|
33795
34790
|
return err(e.message);
|
|
33796
34791
|
}
|
|
@@ -33799,6 +34794,8 @@ async function handleGenerateGraph(args) {
|
|
|
33799
34794
|
const monorepoRoot = process.cwd();
|
|
33800
34795
|
const layer = args.layer;
|
|
33801
34796
|
const projectArg = typeof args.project === "string" ? args.project.trim() : "";
|
|
34797
|
+
const worktreeArg = typeof args.worktree === "string" ? args.worktree.trim() : "";
|
|
34798
|
+
const projectRootArg = typeof args.project_root === "string" ? args.project_root.trim() : "";
|
|
33802
34799
|
function formatProjectResult(results2, relativeRoot) {
|
|
33803
34800
|
return results2.map((r) => {
|
|
33804
34801
|
const warnings = r.output.warnings.length;
|
|
@@ -33806,25 +34803,27 @@ async function handleGenerateGraph(args) {
|
|
|
33806
34803
|
}).join("\n") + `
|
|
33807
34804
|
\u2192 ${relativeRoot}/.launchsecure/graphs/`;
|
|
33808
34805
|
}
|
|
33809
|
-
if (projectArg) {
|
|
34806
|
+
if (projectArg || worktreeArg || projectRootArg) {
|
|
33810
34807
|
let rootDir;
|
|
33811
34808
|
try {
|
|
33812
|
-
rootDir =
|
|
34809
|
+
rootDir = resolveRequestRoot(args, monorepoRoot);
|
|
33813
34810
|
} catch (e) {
|
|
33814
34811
|
return err(e.message);
|
|
33815
34812
|
}
|
|
33816
34813
|
const results2 = await generateGraph(rootDir, layer);
|
|
34814
|
+
const label = worktreeArg ? `worktree "${worktreeArg}"` : projectArg ? `project "${projectArg}"` : `root "${projectRootArg}"`;
|
|
34815
|
+
const queryHint = worktreeArg ? `read_graph (with worktree="${worktreeArg}")` : projectArg ? `read_graph (with project="${projectArg}")` : `read_graph (with project_root="${projectRootArg}")`;
|
|
33817
34816
|
if (results2.length === 0) {
|
|
33818
34817
|
return err(
|
|
33819
|
-
layer ? `No parser detected for the "${layer}" layer in
|
|
34818
|
+
layer ? `No parser detected for the "${layer}" layer in ${label}.` : `No parsers detected for ${label}. Check that the root has the expected structure.`
|
|
33820
34819
|
);
|
|
33821
34820
|
}
|
|
33822
34821
|
return ok(
|
|
33823
|
-
`Graph generated successfully for
|
|
34822
|
+
`Graph generated successfully for ${label}.
|
|
33824
34823
|
|
|
33825
|
-
${formatProjectResult(results2,
|
|
34824
|
+
${formatProjectResult(results2, rootDir)}
|
|
33826
34825
|
|
|
33827
|
-
Use
|
|
34826
|
+
Use ${queryHint} to query.`
|
|
33828
34827
|
);
|
|
33829
34828
|
}
|
|
33830
34829
|
const projects = listProjects(monorepoRoot);
|
|
@@ -33887,9 +34886,14 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
33887
34886
|
const layerIsDb = args.layer === "db";
|
|
33888
34887
|
const minimal = args.minimal ?? layerIsDb;
|
|
33889
34888
|
const includeEdges = args.include_edges;
|
|
34889
|
+
const includeFindings = args.include_findings === true;
|
|
34890
|
+
const tagPredicates = Array.isArray(args.tag_predicates) ? args.tag_predicates : void 0;
|
|
34891
|
+
const includeCrossRefs = args.include_cross_refs === true;
|
|
34892
|
+
const crossRefType = args.cross_ref_type;
|
|
34893
|
+
const edgeConfidence = Array.isArray(args.edge_confidence) ? new Set(args.edge_confidence.map((s) => String(s).toLowerCase())) : void 0;
|
|
33890
34894
|
const offset = args.offset ?? 0;
|
|
33891
34895
|
const limit = args.limit;
|
|
33892
|
-
const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue);
|
|
34896
|
+
const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue || tagPredicates && tagPredicates.length > 0);
|
|
33893
34897
|
if (layer) {
|
|
33894
34898
|
const available = getAvailableLayers(rootDir);
|
|
33895
34899
|
if (available.length > 0 && !available.includes(layer)) {
|
|
@@ -33936,14 +34940,31 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
33936
34940
|
result2.budget_exceeded = true;
|
|
33937
34941
|
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.`;
|
|
33938
34942
|
}
|
|
34943
|
+
if (includeFindings && layer === "db") {
|
|
34944
|
+
result2.contradictions = graph.contradictions ?? [];
|
|
34945
|
+
result2.flagged_edges = graph.flagged_edges ?? [];
|
|
34946
|
+
}
|
|
34947
|
+
const neighborhoodIds = new Set(nb.nodes.map((n) => n.id));
|
|
34948
|
+
attachCrossRefsAndFlagged(result2, graph, neighborhoodIds, {
|
|
34949
|
+
includeCrossRefs,
|
|
34950
|
+
crossRefType,
|
|
34951
|
+
edgeConfidence,
|
|
34952
|
+
scopeToIds: true,
|
|
34953
|
+
layer
|
|
34954
|
+
});
|
|
33939
34955
|
return result2;
|
|
33940
34956
|
}
|
|
33941
34957
|
if (!hasFilter) {
|
|
33942
|
-
|
|
34958
|
+
const summaryResult = {
|
|
33943
34959
|
hint: "No filter specified \u2014 returning summary only. Use search/type/module/node_id to retrieve nodes.",
|
|
33944
34960
|
layer,
|
|
33945
34961
|
summary: layerSummary(graph)
|
|
33946
34962
|
};
|
|
34963
|
+
if (includeFindings && layer === "db") {
|
|
34964
|
+
summaryResult.contradictions = graph.contradictions ?? [];
|
|
34965
|
+
summaryResult.flagged_edges = graph.flagged_edges ?? [];
|
|
34966
|
+
}
|
|
34967
|
+
return summaryResult;
|
|
33947
34968
|
}
|
|
33948
34969
|
const matched = graph.nodes.filter((n) => {
|
|
33949
34970
|
if (search && !matchesSearch(n, search)) return false;
|
|
@@ -33951,6 +34972,11 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
33951
34972
|
const nodeTags = n.tags;
|
|
33952
34973
|
if (module_ && nodeTags?.module !== module_) return false;
|
|
33953
34974
|
if (tagKey && tagValue && nodeTags?.[tagKey] !== tagValue) return false;
|
|
34975
|
+
if (tagPredicates && tagPredicates.length > 0) {
|
|
34976
|
+
for (const p of tagPredicates) {
|
|
34977
|
+
if (!evaluatePredicate(n, p)) return false;
|
|
34978
|
+
}
|
|
34979
|
+
}
|
|
33954
34980
|
return true;
|
|
33955
34981
|
});
|
|
33956
34982
|
if (matched.length === 0) {
|
|
@@ -33994,8 +35020,93 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
33994
35020
|
} else if (returnedEdges.length > 0) {
|
|
33995
35021
|
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).`;
|
|
33996
35022
|
}
|
|
35023
|
+
if (includeFindings && layer === "db") {
|
|
35024
|
+
result.contradictions = graph.contradictions ?? [];
|
|
35025
|
+
result.flagged_edges = graph.flagged_edges ?? [];
|
|
35026
|
+
}
|
|
35027
|
+
attachCrossRefsAndFlagged(result, graph, returnedIds, {
|
|
35028
|
+
includeCrossRefs,
|
|
35029
|
+
crossRefType,
|
|
35030
|
+
edgeConfidence,
|
|
35031
|
+
scopeToIds: true,
|
|
35032
|
+
layer
|
|
35033
|
+
});
|
|
33997
35034
|
return result;
|
|
33998
35035
|
}
|
|
35036
|
+
function attachCrossRefsAndFlagged(result, graph, scopeIds, opts) {
|
|
35037
|
+
if (opts.includeCrossRefs) {
|
|
35038
|
+
const allCrossRefs = graph.cross_refs ?? [];
|
|
35039
|
+
const crossRefsByNode = {};
|
|
35040
|
+
for (const id of scopeIds) {
|
|
35041
|
+
crossRefsByNode[id] = { outgoing: [], incoming: [] };
|
|
35042
|
+
}
|
|
35043
|
+
for (const cr of allCrossRefs) {
|
|
35044
|
+
if (opts.crossRefType && cr.type !== opts.crossRefType) continue;
|
|
35045
|
+
if (crossRefsByNode[cr.source]) crossRefsByNode[cr.source].outgoing.push(cr);
|
|
35046
|
+
if (crossRefsByNode[cr.target]) crossRefsByNode[cr.target].incoming.push(cr);
|
|
35047
|
+
}
|
|
35048
|
+
result.cross_refs = crossRefsByNode;
|
|
35049
|
+
if (opts.crossRefType) result.cross_ref_type = opts.crossRefType;
|
|
35050
|
+
if (allCrossRefs.length === 0) {
|
|
35051
|
+
result.parser_warning = parserWarning([opts.layer], "cross_refs", "cross-layer references parser(s) (fetch-resolver / api-annotations / url-literal-scanner / static-ref-scanner)");
|
|
35052
|
+
}
|
|
35053
|
+
}
|
|
35054
|
+
if (opts.edgeConfidence && opts.edgeConfidence.size > 0) {
|
|
35055
|
+
const allFlagged = graph.flagged_edges ?? [];
|
|
35056
|
+
const useScope = opts.scopeToIds && scopeIds.size > 0;
|
|
35057
|
+
result.flagged_edges = allFlagged.filter((fe) => {
|
|
35058
|
+
if (!opts.edgeConfidence.has(String(fe.confidence).toLowerCase())) return false;
|
|
35059
|
+
if (useScope && !scopeIds.has(fe.source) && !scopeIds.has(fe.target)) return false;
|
|
35060
|
+
return true;
|
|
35061
|
+
});
|
|
35062
|
+
if (allFlagged.length === 0 && !result.parser_warning) {
|
|
35063
|
+
result.parser_warning = parserWarning([opts.layer], "flagged_edges", opts.layer === "db" ? "sql-migrations parser" : "cross-layer parsers");
|
|
35064
|
+
}
|
|
35065
|
+
}
|
|
35066
|
+
}
|
|
35067
|
+
function parserWarning(layers, capability, parserName) {
|
|
35068
|
+
return `The ${capability} data is empty in scanned layer(s): ${layers.join(", ")}. This likely means the ${parserName} did not produce output for this project \u2014 run detect_project_stack to verify parser configuration, then generate_graph to refresh. (If the parser IS configured and the source data is genuinely empty, ignore this warning.)`;
|
|
35069
|
+
}
|
|
35070
|
+
function evaluatePredicate(node, p) {
|
|
35071
|
+
if (!p.field || !p.op) return true;
|
|
35072
|
+
const tags = node.tags;
|
|
35073
|
+
const fromTag = tags?.[p.field];
|
|
35074
|
+
const fromField = node[p.field];
|
|
35075
|
+
const present = fromTag !== void 0 || fromField !== void 0;
|
|
35076
|
+
if (p.op === "exists") return present;
|
|
35077
|
+
if (p.op === "not_exists") return !present;
|
|
35078
|
+
if (!present) return false;
|
|
35079
|
+
const raw = fromTag !== void 0 ? coerceTagValue(fromTag, p.value) : fromField;
|
|
35080
|
+
switch (p.op) {
|
|
35081
|
+
case "eq":
|
|
35082
|
+
return raw === p.value;
|
|
35083
|
+
case "ne":
|
|
35084
|
+
return raw !== p.value;
|
|
35085
|
+
case "gt":
|
|
35086
|
+
return typeof raw === "number" && typeof p.value === "number" && raw > p.value;
|
|
35087
|
+
case "lt":
|
|
35088
|
+
return typeof raw === "number" && typeof p.value === "number" && raw < p.value;
|
|
35089
|
+
case "gte":
|
|
35090
|
+
return typeof raw === "number" && typeof p.value === "number" && raw >= p.value;
|
|
35091
|
+
case "lte":
|
|
35092
|
+
return typeof raw === "number" && typeof p.value === "number" && raw <= p.value;
|
|
35093
|
+
case "in":
|
|
35094
|
+
return Array.isArray(p.value) && p.value.includes(raw);
|
|
35095
|
+
default:
|
|
35096
|
+
return false;
|
|
35097
|
+
}
|
|
35098
|
+
}
|
|
35099
|
+
function coerceTagValue(raw, target) {
|
|
35100
|
+
if (typeof target === "boolean") {
|
|
35101
|
+
if (raw === "true") return true;
|
|
35102
|
+
if (raw === "false") return false;
|
|
35103
|
+
}
|
|
35104
|
+
if (typeof target === "number") {
|
|
35105
|
+
const n = Number(raw);
|
|
35106
|
+
if (!Number.isNaN(n)) return n;
|
|
35107
|
+
}
|
|
35108
|
+
return raw;
|
|
35109
|
+
}
|
|
33999
35110
|
function runReadGraphQuery(rootDir, args) {
|
|
34000
35111
|
const raw = runReadGraphQueryRaw(rootDir, args);
|
|
34001
35112
|
return compactResult(raw);
|
|
@@ -34008,6 +35119,8 @@ function handleReadGraph(args) {
|
|
|
34008
35119
|
return err("queries array is empty. Provide at least one query object.");
|
|
34009
35120
|
}
|
|
34010
35121
|
const inheritedProject = typeof args.project === "string" ? args.project : void 0;
|
|
35122
|
+
const inheritedWorktree = typeof args.worktree === "string" ? args.worktree : void 0;
|
|
35123
|
+
const inheritedProjectRoot = typeof args.project_root === "string" ? args.project_root : void 0;
|
|
34011
35124
|
const results = [];
|
|
34012
35125
|
let cumulativeChars = 0;
|
|
34013
35126
|
let budgetHit = false;
|
|
@@ -34025,15 +35138,18 @@ function handleReadGraph(args) {
|
|
|
34025
35138
|
});
|
|
34026
35139
|
continue;
|
|
34027
35140
|
}
|
|
34028
|
-
const
|
|
35141
|
+
const qInherited = { ...q };
|
|
35142
|
+
if (inheritedProject && !qInherited.project) qInherited.project = inheritedProject;
|
|
35143
|
+
if (inheritedWorktree && !qInherited.worktree) qInherited.worktree = inheritedWorktree;
|
|
35144
|
+
if (inheritedProjectRoot && !qInherited.project_root) qInherited.project_root = inheritedProjectRoot;
|
|
34029
35145
|
let perQueryRoot;
|
|
34030
35146
|
try {
|
|
34031
|
-
perQueryRoot =
|
|
35147
|
+
perQueryRoot = resolveRequestRoot(qInherited, monorepoRoot);
|
|
34032
35148
|
} catch (e) {
|
|
34033
35149
|
results.push({ index: i, query: q, result: { error: e.message } });
|
|
34034
35150
|
continue;
|
|
34035
35151
|
}
|
|
34036
|
-
const r = runReadGraphQuery(perQueryRoot,
|
|
35152
|
+
const r = runReadGraphQuery(perQueryRoot, qInherited);
|
|
34037
35153
|
const entry = { index: i, query: q, result: r };
|
|
34038
35154
|
const entrySize = JSON.stringify(entry, null, 2).length;
|
|
34039
35155
|
if (cumulativeChars + entrySize > BATCH_BUDGET_CHARS && results.length > 0) {
|
|
@@ -34066,12 +35182,12 @@ function handleReadGraph(args) {
|
|
|
34066
35182
|
return okJson(result);
|
|
34067
35183
|
}
|
|
34068
35184
|
function nodeToFilePath(rootDir, layer, nodeId) {
|
|
34069
|
-
if (layer === "ui" || layer === "api") return (0,
|
|
34070
|
-
if (layer === "db") return (0,
|
|
34071
|
-
const withSrc = (0,
|
|
34072
|
-
if ((0,
|
|
34073
|
-
const direct = (0,
|
|
34074
|
-
if ((0,
|
|
35185
|
+
if (layer === "ui" || layer === "api") return (0, import_node_path33.join)(rootDir, "src", nodeId);
|
|
35186
|
+
if (layer === "db") return (0, import_node_path33.join)(rootDir, "prisma", "schema.prisma");
|
|
35187
|
+
const withSrc = (0, import_node_path33.join)(rootDir, "src", nodeId);
|
|
35188
|
+
if ((0, import_node_fs28.existsSync)(withSrc)) return withSrc;
|
|
35189
|
+
const direct = (0, import_node_path33.join)(rootDir, nodeId);
|
|
35190
|
+
if ((0, import_node_fs28.existsSync)(direct)) return direct;
|
|
34075
35191
|
return null;
|
|
34076
35192
|
}
|
|
34077
35193
|
function handleInspectNode(args) {
|
|
@@ -34101,8 +35217,23 @@ function handleInspectNode(args) {
|
|
|
34101
35217
|
} else {
|
|
34102
35218
|
matched = graph.nodes;
|
|
34103
35219
|
}
|
|
34104
|
-
const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params", "effects"];
|
|
35220
|
+
const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params", "effects", "ui_labels", "notes", "crossRefs"];
|
|
34105
35221
|
const requestedFields = fields ?? allDeepFields;
|
|
35222
|
+
const crossRefTypeArg = args.cross_ref_type;
|
|
35223
|
+
const wantCrossRefs = requestedFields.includes("crossRefs");
|
|
35224
|
+
let crossRefsBySource = null;
|
|
35225
|
+
let crossRefsByTarget = null;
|
|
35226
|
+
if (wantCrossRefs) {
|
|
35227
|
+
crossRefsBySource = /* @__PURE__ */ new Map();
|
|
35228
|
+
crossRefsByTarget = /* @__PURE__ */ new Map();
|
|
35229
|
+
for (const cr of graph.cross_refs ?? []) {
|
|
35230
|
+
if (crossRefTypeArg && cr.type !== crossRefTypeArg) continue;
|
|
35231
|
+
if (!crossRefsBySource.has(cr.source)) crossRefsBySource.set(cr.source, []);
|
|
35232
|
+
crossRefsBySource.get(cr.source).push(cr);
|
|
35233
|
+
if (!crossRefsByTarget.has(cr.target)) crossRefsByTarget.set(cr.target, []);
|
|
35234
|
+
crossRefsByTarget.get(cr.target).push(cr);
|
|
35235
|
+
}
|
|
35236
|
+
}
|
|
34106
35237
|
let filterRegex = null;
|
|
34107
35238
|
if (filter) {
|
|
34108
35239
|
try {
|
|
@@ -34125,7 +35256,14 @@ function handleInspectNode(args) {
|
|
|
34125
35256
|
const deep = { id: node.id, name: node.name, type: node.type };
|
|
34126
35257
|
let hasData = false;
|
|
34127
35258
|
for (const field of requestedFields) {
|
|
34128
|
-
if (
|
|
35259
|
+
if (field === "crossRefs") {
|
|
35260
|
+
const outgoing = crossRefsBySource?.get(node.id) ?? [];
|
|
35261
|
+
const incoming = crossRefsByTarget?.get(node.id) ?? [];
|
|
35262
|
+
if (outgoing.length > 0 || incoming.length > 0) {
|
|
35263
|
+
deep.crossRefs = { outgoing, incoming };
|
|
35264
|
+
hasData = true;
|
|
35265
|
+
}
|
|
35266
|
+
} else if (allDeepFields.includes(field) && node[field] != null) {
|
|
34129
35267
|
deep[field] = node[field];
|
|
34130
35268
|
hasData = true;
|
|
34131
35269
|
}
|
|
@@ -34149,13 +35287,112 @@ function handleInspectNode(args) {
|
|
|
34149
35287
|
const hint = filter ? `No nodes with deep fields matching /${filter}/${caseInsensitive ? "i" : ""} in ${layer} layer.` : `No nodes matching "${search}" in ${layer} layer.`;
|
|
34150
35288
|
return err(hint);
|
|
34151
35289
|
}
|
|
35290
|
+
const wantedCrossRefs = wantCrossRefs;
|
|
35291
|
+
const graphHasNoCrossRefs = (graph.cross_refs ?? []).length === 0;
|
|
35292
|
+
const extraWarning = wantedCrossRefs && graphHasNoCrossRefs ? { parser_warning: parserWarning([layer], "cross_refs", "cross-layer references parser(s) (fetch-resolver / api-annotations / url-literal-scanner / static-ref-scanner)") } : {};
|
|
34152
35293
|
return okJson({
|
|
34153
35294
|
layer,
|
|
34154
35295
|
matched: results.length,
|
|
34155
35296
|
...results.length >= maxResults ? { truncated: true, hint: `Showing first ${maxResults} matches. Narrow with search param.` } : {},
|
|
35297
|
+
...extraWarning,
|
|
34156
35298
|
nodes: results
|
|
34157
35299
|
});
|
|
34158
35300
|
}
|
|
35301
|
+
function handleListNotes(args) {
|
|
35302
|
+
const __resolved = resolveOrErr(args);
|
|
35303
|
+
if ("content" in __resolved) return __resolved;
|
|
35304
|
+
const { rootDir } = __resolved;
|
|
35305
|
+
const layerArg = args.layer;
|
|
35306
|
+
const kindArg = args.kind?.toUpperCase();
|
|
35307
|
+
const categoryArg = args.category ?? "actionable,warning";
|
|
35308
|
+
const moduleArg = args.module;
|
|
35309
|
+
const patternArg = args.pattern;
|
|
35310
|
+
const authorArg = args.author?.toLowerCase();
|
|
35311
|
+
const limit = args.limit ?? 100;
|
|
35312
|
+
const offset = args.offset ?? 0;
|
|
35313
|
+
let patternRegex = null;
|
|
35314
|
+
if (patternArg) {
|
|
35315
|
+
try {
|
|
35316
|
+
patternRegex = new RegExp(patternArg, "i");
|
|
35317
|
+
} catch {
|
|
35318
|
+
return err(`Invalid regex pattern: "${patternArg}"`);
|
|
35319
|
+
}
|
|
35320
|
+
}
|
|
35321
|
+
const wantedCategories = (() => {
|
|
35322
|
+
const trimmed = categoryArg.trim().toLowerCase();
|
|
35323
|
+
if (trimmed === "all") return "all";
|
|
35324
|
+
const out = /* @__PURE__ */ new Set();
|
|
35325
|
+
for (const part of trimmed.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
35326
|
+
if (part === "actionable" || part === "warning" || part === "doc" || part === "custom") {
|
|
35327
|
+
out.add(part);
|
|
35328
|
+
}
|
|
35329
|
+
}
|
|
35330
|
+
if (out.size === 0) {
|
|
35331
|
+
out.add("actionable");
|
|
35332
|
+
out.add("warning");
|
|
35333
|
+
}
|
|
35334
|
+
return out;
|
|
35335
|
+
})();
|
|
35336
|
+
const layers = layerArg ? [layerArg] : getAvailableLayers(rootDir);
|
|
35337
|
+
const items = [];
|
|
35338
|
+
for (const layer of layers) {
|
|
35339
|
+
const graph = readGraph(rootDir, layer);
|
|
35340
|
+
if (!graph) continue;
|
|
35341
|
+
for (const node of graph.nodes) {
|
|
35342
|
+
const nodeNotes = node.notes;
|
|
35343
|
+
if (!nodeNotes || nodeNotes.length === 0) continue;
|
|
35344
|
+
const tags = node.tags;
|
|
35345
|
+
const nodeModule = tags?.module;
|
|
35346
|
+
if (moduleArg && nodeModule !== moduleArg) continue;
|
|
35347
|
+
for (const n of nodeNotes) {
|
|
35348
|
+
if (!n.kind || !n.text || typeof n.line !== "number") continue;
|
|
35349
|
+
const category = categorizeNoteKind(n.kind);
|
|
35350
|
+
if (wantedCategories !== "all" && !wantedCategories.has(category)) continue;
|
|
35351
|
+
if (kindArg && n.kind.toUpperCase() !== kindArg) continue;
|
|
35352
|
+
if (authorArg && (!n.author || n.author.toLowerCase() !== authorArg)) continue;
|
|
35353
|
+
if (patternRegex && !patternRegex.test(n.text)) continue;
|
|
35354
|
+
const item = {
|
|
35355
|
+
file: node.id,
|
|
35356
|
+
line: n.line,
|
|
35357
|
+
kind: n.kind,
|
|
35358
|
+
category,
|
|
35359
|
+
text: n.text
|
|
35360
|
+
};
|
|
35361
|
+
if (n.author) item.author = n.author;
|
|
35362
|
+
if (nodeModule) item.module = nodeModule;
|
|
35363
|
+
items.push(item);
|
|
35364
|
+
}
|
|
35365
|
+
}
|
|
35366
|
+
}
|
|
35367
|
+
const catOrder = { actionable: 0, warning: 1, doc: 2, custom: 3 };
|
|
35368
|
+
items.sort((a, b) => {
|
|
35369
|
+
const c = catOrder[a.category] - catOrder[b.category];
|
|
35370
|
+
if (c !== 0) return c;
|
|
35371
|
+
const k = a.kind.localeCompare(b.kind);
|
|
35372
|
+
if (k !== 0) return k;
|
|
35373
|
+
const f = a.file.localeCompare(b.file);
|
|
35374
|
+
if (f !== 0) return f;
|
|
35375
|
+
return a.line - b.line;
|
|
35376
|
+
});
|
|
35377
|
+
const total = items.length;
|
|
35378
|
+
const paged = items.slice(offset, offset + limit);
|
|
35379
|
+
const hasMore = offset + paged.length < total;
|
|
35380
|
+
return okJson({
|
|
35381
|
+
total,
|
|
35382
|
+
returned: paged.length,
|
|
35383
|
+
has_more: hasMore,
|
|
35384
|
+
...hasMore ? { next_offset: offset + paged.length } : {},
|
|
35385
|
+
filter: {
|
|
35386
|
+
layer: layerArg ?? "all",
|
|
35387
|
+
kind: kindArg ?? null,
|
|
35388
|
+
category: categoryArg,
|
|
35389
|
+
module: moduleArg ?? null,
|
|
35390
|
+
pattern: patternArg ?? null,
|
|
35391
|
+
author: authorArg ?? null
|
|
35392
|
+
},
|
|
35393
|
+
items: paged
|
|
35394
|
+
});
|
|
35395
|
+
}
|
|
34159
35396
|
function handleGrepNodes(args) {
|
|
34160
35397
|
const __resolved = resolveOrErr(args);
|
|
34161
35398
|
if ("content" in __resolved) return __resolved;
|
|
@@ -34218,11 +35455,11 @@ function handleGrepNodes(args) {
|
|
|
34218
35455
|
let filesSearched = 0;
|
|
34219
35456
|
let truncated = false;
|
|
34220
35457
|
for (const [filePath, nodeId] of filePaths) {
|
|
34221
|
-
if (!(0,
|
|
35458
|
+
if (!(0, import_node_fs28.existsSync)(filePath)) continue;
|
|
34222
35459
|
filesSearched++;
|
|
34223
35460
|
let content;
|
|
34224
35461
|
try {
|
|
34225
|
-
content = (0,
|
|
35462
|
+
content = (0, import_node_fs28.readFileSync)(filePath, "utf-8");
|
|
34226
35463
|
} catch {
|
|
34227
35464
|
continue;
|
|
34228
35465
|
}
|
|
@@ -34263,13 +35500,19 @@ function handleEffectsIndex(args) {
|
|
|
34263
35500
|
const __resolved = resolveOrErr(args);
|
|
34264
35501
|
if ("content" in __resolved) return __resolved;
|
|
34265
35502
|
const { rootDir } = __resolved;
|
|
35503
|
+
const kind = args.kind ?? "collisions";
|
|
35504
|
+
const key = args.key;
|
|
35505
|
+
const MAX_KEYS = 200;
|
|
35506
|
+
if (kind === "fetch_calls") {
|
|
35507
|
+
return okJson(buildFetchCallsIndex(rootDir, key, MAX_KEYS));
|
|
35508
|
+
}
|
|
35509
|
+
if (kind === "api_effects") {
|
|
35510
|
+
return okJson(buildApiEffectsIndex(rootDir, key, MAX_KEYS));
|
|
35511
|
+
}
|
|
34266
35512
|
const idx = readEffectsIndex(rootDir);
|
|
34267
35513
|
if (!idx) {
|
|
34268
35514
|
return err("No effects-index.json found. Run generate_graph first (or wait for the file-watcher to fire).");
|
|
34269
35515
|
}
|
|
34270
|
-
const kind = args.kind ?? "collisions";
|
|
34271
|
-
const key = args.key;
|
|
34272
|
-
const MAX_KEYS = 200;
|
|
34273
35516
|
if (kind === "singleton_risks") {
|
|
34274
35517
|
return okJson({ kind, count: idx.singleton_risks.length, nodes: idx.singleton_risks });
|
|
34275
35518
|
}
|
|
@@ -34283,7 +35526,7 @@ function handleEffectsIndex(args) {
|
|
|
34283
35526
|
}
|
|
34284
35527
|
const map = idx[kind];
|
|
34285
35528
|
if (!map || typeof map !== "object") {
|
|
34286
|
-
return err(`Unknown kind "${kind}". Valid: dom_ids, window_events, storage_keys, fetch_urls, timers, singleton_risks, collisions.`);
|
|
35529
|
+
return err(`Unknown kind "${kind}". Valid: dom_ids, window_events, storage_keys, fetch_urls, timers, singleton_risks, collisions, fetch_calls, api_effects.`);
|
|
34287
35530
|
}
|
|
34288
35531
|
if (key) {
|
|
34289
35532
|
const nodes = map[key] ?? [];
|
|
@@ -34301,6 +35544,97 @@ function handleEffectsIndex(args) {
|
|
|
34301
35544
|
results
|
|
34302
35545
|
});
|
|
34303
35546
|
}
|
|
35547
|
+
function buildFetchCallsIndex(rootDir, key, maxKeys) {
|
|
35548
|
+
const ui = readGraph(rootDir, "ui");
|
|
35549
|
+
if (!ui) return { kind: "fetch_calls", error: "No ui graph found. Run generate_graph first." };
|
|
35550
|
+
const inverted = /* @__PURE__ */ new Map();
|
|
35551
|
+
let callsApiCount = 0;
|
|
35552
|
+
for (const cr of ui.cross_refs ?? []) {
|
|
35553
|
+
if (cr.type !== "calls_api") continue;
|
|
35554
|
+
callsApiCount++;
|
|
35555
|
+
if (!inverted.has(cr.target)) inverted.set(cr.target, /* @__PURE__ */ new Set());
|
|
35556
|
+
inverted.get(cr.target).add(cr.source);
|
|
35557
|
+
}
|
|
35558
|
+
for (const n of ui.nodes) {
|
|
35559
|
+
const eff = n.effects;
|
|
35560
|
+
if (!eff?.fetches) continue;
|
|
35561
|
+
for (const f of eff.fetches) {
|
|
35562
|
+
if (!inverted.has(f)) inverted.set(f, /* @__PURE__ */ new Set());
|
|
35563
|
+
inverted.get(f).add(n.id);
|
|
35564
|
+
}
|
|
35565
|
+
}
|
|
35566
|
+
const allCrossRefs = ui.cross_refs ?? [];
|
|
35567
|
+
const fetchResolverAbsent = callsApiCount === 0 && allCrossRefs.length === 0;
|
|
35568
|
+
const partialResolverGap = callsApiCount === 0 && inverted.size > 0;
|
|
35569
|
+
if (key) {
|
|
35570
|
+
return {
|
|
35571
|
+
kind: "fetch_calls",
|
|
35572
|
+
key,
|
|
35573
|
+
callers: [...inverted.get(key) ?? []],
|
|
35574
|
+
...fetchResolverAbsent ? { parser_warning: parserWarning(["ui"], "cross_refs", "fetch-resolver cross-layer parser") } : {},
|
|
35575
|
+
...partialResolverGap ? { parser_warning: "No calls_api cross_refs found \u2014 fetch-resolver may not be enabled. Only raw fetch() strings from ui.node.effects.fetches are indexed; resolved-to-endpoint links are missing." } : {}
|
|
35576
|
+
};
|
|
35577
|
+
}
|
|
35578
|
+
const entries = [...inverted.entries()].sort((a, b) => b[1].size - a[1].size);
|
|
35579
|
+
const truncated = entries.length > maxKeys;
|
|
35580
|
+
const results = {};
|
|
35581
|
+
for (const [k, v] of entries.slice(0, maxKeys)) results[k] = [...v];
|
|
35582
|
+
return {
|
|
35583
|
+
kind: "fetch_calls",
|
|
35584
|
+
total_keys: entries.length,
|
|
35585
|
+
...truncated ? { truncated: true, showing: maxKeys, hint: "Pass `key` (endpoint path or URL template) to look up callers." } : {},
|
|
35586
|
+
...fetchResolverAbsent ? { parser_warning: parserWarning(["ui"], "cross_refs", "fetch-resolver cross-layer parser") } : {},
|
|
35587
|
+
...partialResolverGap ? { parser_warning: "No calls_api cross_refs found \u2014 fetch-resolver may not be enabled. Only raw fetch() strings from ui.node.effects.fetches are indexed; resolved-to-endpoint links are missing." } : {},
|
|
35588
|
+
results
|
|
35589
|
+
};
|
|
35590
|
+
}
|
|
35591
|
+
function buildApiEffectsIndex(rootDir, key, maxKeys) {
|
|
35592
|
+
const api = readGraph(rootDir, "api");
|
|
35593
|
+
if (!api) return { kind: "api_effects", error: "No api graph found. Run generate_graph first." };
|
|
35594
|
+
const categories = ["calls", "fetches", "persists", "subscribes", "timers", "dom_writes", "globals"];
|
|
35595
|
+
const inverted = {};
|
|
35596
|
+
for (const c of categories) inverted[c] = /* @__PURE__ */ new Map();
|
|
35597
|
+
for (const n of api.nodes) {
|
|
35598
|
+
const eff = n.effects;
|
|
35599
|
+
if (!eff) continue;
|
|
35600
|
+
for (const c of categories) {
|
|
35601
|
+
const items = eff[c];
|
|
35602
|
+
if (!Array.isArray(items)) continue;
|
|
35603
|
+
for (const item of items) {
|
|
35604
|
+
if (!inverted[c].has(item)) inverted[c].set(item, /* @__PURE__ */ new Set());
|
|
35605
|
+
inverted[c].get(item).add(n.id);
|
|
35606
|
+
}
|
|
35607
|
+
}
|
|
35608
|
+
}
|
|
35609
|
+
if (key) {
|
|
35610
|
+
const hits = {};
|
|
35611
|
+
for (const c of categories) {
|
|
35612
|
+
const endpoints = inverted[c].get(key);
|
|
35613
|
+
if (endpoints && endpoints.size > 0) hits[c] = [...endpoints];
|
|
35614
|
+
}
|
|
35615
|
+
return { kind: "api_effects", key, hits };
|
|
35616
|
+
}
|
|
35617
|
+
const TOP_CATEGORIES = Math.min(maxKeys, 50);
|
|
35618
|
+
const TOP_CALLERS = 10;
|
|
35619
|
+
const summary = {};
|
|
35620
|
+
for (const c of categories) {
|
|
35621
|
+
const entries = [...inverted[c].entries()].sort((a, b) => b[1].size - a[1].size);
|
|
35622
|
+
const top = {};
|
|
35623
|
+
for (const [k, v] of entries.slice(0, TOP_CATEGORIES)) {
|
|
35624
|
+
const endpoints = [...v];
|
|
35625
|
+
top[k] = {
|
|
35626
|
+
count: endpoints.length,
|
|
35627
|
+
sample_endpoints: endpoints.slice(0, TOP_CALLERS)
|
|
35628
|
+
};
|
|
35629
|
+
}
|
|
35630
|
+
summary[c] = { total_items: entries.length, top };
|
|
35631
|
+
}
|
|
35632
|
+
return {
|
|
35633
|
+
kind: "api_effects",
|
|
35634
|
+
hint: `Summary mode: top ${TOP_CATEGORIES} items per category, top ${TOP_CALLERS} endpoints per item. Pass \`key\` (a specific call/url/event) to get the FULL caller list across all categories.`,
|
|
35635
|
+
summary
|
|
35636
|
+
};
|
|
35637
|
+
}
|
|
34304
35638
|
function handleChartServerStatus() {
|
|
34305
35639
|
const rootDir = process.cwd();
|
|
34306
35640
|
const lock = getLiveLock(rootDir);
|
|
@@ -34344,11 +35678,11 @@ function handleStartChartServer(args) {
|
|
|
34344
35678
|
});
|
|
34345
35679
|
}
|
|
34346
35680
|
const entryPath = process.argv[1];
|
|
34347
|
-
const logDir = (0,
|
|
34348
|
-
(0,
|
|
34349
|
-
const logPath = (0,
|
|
34350
|
-
const out = (0,
|
|
34351
|
-
const err2 = (0,
|
|
35681
|
+
const logDir = (0, import_node_path33.join)((0, import_node_os6.homedir)(), LAUNCHSECURE_DIR);
|
|
35682
|
+
(0, import_node_fs28.mkdirSync)(logDir, { recursive: true });
|
|
35683
|
+
const logPath = (0, import_node_path33.join)(logDir, "launch-chart.log");
|
|
35684
|
+
const out = (0, import_node_fs28.openSync)(logPath, "a");
|
|
35685
|
+
const err2 = (0, import_node_fs28.openSync)(logPath, "a");
|
|
34352
35686
|
const portArgs = args.port ? ["--port", String(args.port)] : [];
|
|
34353
35687
|
const child = (0, import_node_child_process3.spawn)(process.execPath, [entryPath, "serve", ...portArgs], {
|
|
34354
35688
|
detached: true,
|
|
@@ -34414,6 +35748,296 @@ function handleRemoveTag(args) {
|
|
|
34414
35748
|
removeTag(rootDir, nodeId, key);
|
|
34415
35749
|
return okJson({ ok: true, node_id: nodeId, removed_key: key });
|
|
34416
35750
|
}
|
|
35751
|
+
function handleMigrationAudit(args) {
|
|
35752
|
+
const __resolved = resolveOrErr(args);
|
|
35753
|
+
if ("content" in __resolved) return __resolved;
|
|
35754
|
+
const { rootDir } = __resolved;
|
|
35755
|
+
const requireOrphan = args.require_orphan_check !== false;
|
|
35756
|
+
const requireBackup = args.require_sidecar_backup !== false;
|
|
35757
|
+
const requirePreFlight = args.require_pre_flight_notice !== false;
|
|
35758
|
+
const includeSafe = args.include_safe === true;
|
|
35759
|
+
const offset = args.offset ?? 0;
|
|
35760
|
+
const limit = args.limit ?? 10;
|
|
35761
|
+
const db = readGraph(rootDir, "db");
|
|
35762
|
+
if (!db) return err("No db graph found. Run generate_graph first.");
|
|
35763
|
+
const migrations = db.nodes.filter((n) => n.type === "migration");
|
|
35764
|
+
const audit = [];
|
|
35765
|
+
for (const m of migrations) {
|
|
35766
|
+
const isDestructive = m.is_destructive === true;
|
|
35767
|
+
if (!isDestructive && !includeSafe) continue;
|
|
35768
|
+
const violations = [];
|
|
35769
|
+
if (isDestructive) {
|
|
35770
|
+
if (requireOrphan && m.has_orphan_check !== true) violations.push("missing_orphan_check");
|
|
35771
|
+
if (requireBackup && m.has_sidecar_backup !== true) violations.push("missing_sidecar_backup");
|
|
35772
|
+
if (requirePreFlight && m.has_pre_flight_notice !== true) violations.push("missing_pre_flight_notice");
|
|
35773
|
+
}
|
|
35774
|
+
if (!isDestructive && includeSafe && violations.length === 0) {
|
|
35775
|
+
audit.push({ id: m.id, name: m.name, timestamp: m.timestamp, is_destructive: false, status: "safe" });
|
|
35776
|
+
continue;
|
|
35777
|
+
}
|
|
35778
|
+
if (violations.length === 0 && !includeSafe) continue;
|
|
35779
|
+
audit.push({
|
|
35780
|
+
id: m.id,
|
|
35781
|
+
name: m.name,
|
|
35782
|
+
timestamp: m.timestamp,
|
|
35783
|
+
filepath: m.filepath,
|
|
35784
|
+
is_destructive: isDestructive,
|
|
35785
|
+
contains_drop_column: m.contains_drop_column === true,
|
|
35786
|
+
contains_drop_table: m.contains_drop_table === true,
|
|
35787
|
+
contains_backfill: m.contains_backfill === true,
|
|
35788
|
+
has_orphan_check: m.has_orphan_check === true,
|
|
35789
|
+
has_sidecar_backup: m.has_sidecar_backup === true,
|
|
35790
|
+
has_pre_flight_notice: m.has_pre_flight_notice === true,
|
|
35791
|
+
statement_count: m.statement_count,
|
|
35792
|
+
violations,
|
|
35793
|
+
status: violations.length === 0 ? "safe" : "unsafe"
|
|
35794
|
+
});
|
|
35795
|
+
}
|
|
35796
|
+
audit.sort((a, b) => String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? "")));
|
|
35797
|
+
const total = audit.length;
|
|
35798
|
+
const page = audit.slice(offset, offset + limit);
|
|
35799
|
+
const hasMore = offset + page.length < total;
|
|
35800
|
+
return okJson({
|
|
35801
|
+
total,
|
|
35802
|
+
returned: page.length,
|
|
35803
|
+
offset,
|
|
35804
|
+
has_more: hasMore,
|
|
35805
|
+
...hasMore ? { next_offset: offset + page.length } : {},
|
|
35806
|
+
items: page,
|
|
35807
|
+
discipline: "Layer 1 (expand-and-contract) + Layer 2 (pg_dump wrapper) + Layer 3 (in-migration SQL guards) per CLAUDE.md. This tool checks Layer 3 only."
|
|
35808
|
+
});
|
|
35809
|
+
}
|
|
35810
|
+
function handleDriftReport(args) {
|
|
35811
|
+
const __resolved = resolveOrErr(args);
|
|
35812
|
+
if ("content" in __resolved) return __resolved;
|
|
35813
|
+
const { rootDir } = __resolved;
|
|
35814
|
+
const layerArg = args.layer;
|
|
35815
|
+
const kind = args.kind ?? "all";
|
|
35816
|
+
const confidenceArg = Array.isArray(args.confidence) ? new Set(args.confidence.map((s) => String(s).toLowerCase())) : void 0;
|
|
35817
|
+
const offset = args.offset ?? 0;
|
|
35818
|
+
const limit = args.limit ?? 10;
|
|
35819
|
+
const layers = layerArg ? [layerArg] : getAvailableLayers(rootDir);
|
|
35820
|
+
const items = [];
|
|
35821
|
+
const layersWithAnyFinding = [];
|
|
35822
|
+
for (const layer of layers) {
|
|
35823
|
+
const g = readGraph(rootDir, layer);
|
|
35824
|
+
if (!g) continue;
|
|
35825
|
+
const cs = g.contradictions ?? [];
|
|
35826
|
+
const fes = g.flagged_edges ?? [];
|
|
35827
|
+
if (cs.length > 0 || fes.length > 0) layersWithAnyFinding.push(layer);
|
|
35828
|
+
if (kind === "all" || kind === "contradictions") {
|
|
35829
|
+
for (const c of cs) {
|
|
35830
|
+
items.push({ layer, kind: "contradiction", entity: c.entity, source_a: c.source_a, source_b: c.source_b, detail: c.detail });
|
|
35831
|
+
}
|
|
35832
|
+
}
|
|
35833
|
+
if (kind === "all" || kind === "flagged_edges") {
|
|
35834
|
+
for (const fe of fes) {
|
|
35835
|
+
if (confidenceArg && !confidenceArg.has(String(fe.confidence).toLowerCase())) continue;
|
|
35836
|
+
items.push({
|
|
35837
|
+
layer,
|
|
35838
|
+
kind: "flagged_edge",
|
|
35839
|
+
source: fe.source,
|
|
35840
|
+
target: fe.target,
|
|
35841
|
+
edge_type: fe.type,
|
|
35842
|
+
confidence: fe.confidence,
|
|
35843
|
+
label: fe.label
|
|
35844
|
+
});
|
|
35845
|
+
}
|
|
35846
|
+
}
|
|
35847
|
+
}
|
|
35848
|
+
const total = items.length;
|
|
35849
|
+
const page = items.slice(offset, offset + limit);
|
|
35850
|
+
const hasMore = offset + page.length < total;
|
|
35851
|
+
const parserAbsent = layers.length > 0 && layersWithAnyFinding.length === 0;
|
|
35852
|
+
return okJson({
|
|
35853
|
+
total,
|
|
35854
|
+
returned: page.length,
|
|
35855
|
+
offset,
|
|
35856
|
+
has_more: hasMore,
|
|
35857
|
+
...hasMore ? { next_offset: offset + page.length } : {},
|
|
35858
|
+
layers_scanned: layers,
|
|
35859
|
+
...parserAbsent ? { parser_warning: parserWarning(layers, "contradictions + flagged_edges", layers.includes("db") ? "sql-migrations parser and/or cross-layer parsers" : "cross-layer parsers") } : {},
|
|
35860
|
+
items: page
|
|
35861
|
+
});
|
|
35862
|
+
}
|
|
35863
|
+
function handleWhoUses(args) {
|
|
35864
|
+
const __resolved = resolveOrErr(args);
|
|
35865
|
+
if ("content" in __resolved) return __resolved;
|
|
35866
|
+
const { rootDir } = __resolved;
|
|
35867
|
+
const target = args.target;
|
|
35868
|
+
if (!target) return err("target is required");
|
|
35869
|
+
const filterType = args.cross_ref_type;
|
|
35870
|
+
const offset = args.offset ?? 0;
|
|
35871
|
+
const limit = args.limit ?? 10;
|
|
35872
|
+
const hits = [];
|
|
35873
|
+
const layersScanned = [];
|
|
35874
|
+
const layersWithCrossRefs = [];
|
|
35875
|
+
for (const layer of getAvailableLayers(rootDir)) {
|
|
35876
|
+
const g = readGraph(rootDir, layer);
|
|
35877
|
+
if (!g) continue;
|
|
35878
|
+
layersScanned.push(layer);
|
|
35879
|
+
const crs = g.cross_refs ?? [];
|
|
35880
|
+
if (crs.length > 0) layersWithCrossRefs.push(layer);
|
|
35881
|
+
for (const cr of crs) {
|
|
35882
|
+
if (cr.target !== target) continue;
|
|
35883
|
+
if (filterType && cr.type !== filterType) continue;
|
|
35884
|
+
const entry = {
|
|
35885
|
+
source_layer: layer,
|
|
35886
|
+
source: cr.source,
|
|
35887
|
+
cross_ref_type: cr.type,
|
|
35888
|
+
target_layer: cr.layer
|
|
35889
|
+
};
|
|
35890
|
+
const via = cr.via;
|
|
35891
|
+
if (Array.isArray(via)) entry.via = via;
|
|
35892
|
+
hits.push(entry);
|
|
35893
|
+
}
|
|
35894
|
+
}
|
|
35895
|
+
hits.sort((a, b) => String(a.source).localeCompare(String(b.source)));
|
|
35896
|
+
const total = hits.length;
|
|
35897
|
+
const page = hits.slice(offset, offset + limit);
|
|
35898
|
+
const hasMore = offset + page.length < total;
|
|
35899
|
+
const parserAbsent = layersScanned.length > 0 && layersWithCrossRefs.length === 0;
|
|
35900
|
+
return okJson({
|
|
35901
|
+
target,
|
|
35902
|
+
...filterType ? { cross_ref_type: filterType } : {},
|
|
35903
|
+
total,
|
|
35904
|
+
returned: page.length,
|
|
35905
|
+
offset,
|
|
35906
|
+
has_more: hasMore,
|
|
35907
|
+
...hasMore ? { next_offset: offset + page.length } : {},
|
|
35908
|
+
...parserAbsent ? { parser_warning: parserWarning(layersScanned, "cross_refs", "cross-layer references parser(s) (fetch-resolver / api-annotations / url-literal-scanner / static-ref-scanner)") } : {},
|
|
35909
|
+
items: page
|
|
35910
|
+
});
|
|
35911
|
+
}
|
|
35912
|
+
function handleTracePath(args) {
|
|
35913
|
+
const __resolved = resolveOrErr(args);
|
|
35914
|
+
if ("content" in __resolved) return __resolved;
|
|
35915
|
+
const { rootDir } = __resolved;
|
|
35916
|
+
const from = args.from;
|
|
35917
|
+
const to = args.to;
|
|
35918
|
+
if (!from || !to) return err("from and to are required");
|
|
35919
|
+
const maxHops = args.max_hops ?? 5;
|
|
35920
|
+
const maxPaths = args.max_paths ?? 10;
|
|
35921
|
+
const offset = args.offset ?? 0;
|
|
35922
|
+
const limit = args.limit ?? 10;
|
|
35923
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
35924
|
+
const layersScanned = [];
|
|
35925
|
+
const layersWithCrossRefs = [];
|
|
35926
|
+
for (const layer of getAvailableLayers(rootDir)) {
|
|
35927
|
+
const g = readGraph(rootDir, layer);
|
|
35928
|
+
if (!g) continue;
|
|
35929
|
+
layersScanned.push(layer);
|
|
35930
|
+
if ((g.cross_refs ?? []).length > 0) layersWithCrossRefs.push(layer);
|
|
35931
|
+
for (const cr of g.cross_refs ?? []) {
|
|
35932
|
+
const via = cr.via;
|
|
35933
|
+
if (Array.isArray(via) && via.length > 0) {
|
|
35934
|
+
let prev = cr.source;
|
|
35935
|
+
for (const step of via) {
|
|
35936
|
+
pushEdge(outgoing, { from: prev, to: step, type: cr.type, layer: cr.layer });
|
|
35937
|
+
prev = step;
|
|
35938
|
+
}
|
|
35939
|
+
pushEdge(outgoing, { from: prev, to: cr.target, type: cr.type, layer: cr.layer });
|
|
35940
|
+
} else {
|
|
35941
|
+
pushEdge(outgoing, { from: cr.source, to: cr.target, type: cr.type, layer: cr.layer });
|
|
35942
|
+
}
|
|
35943
|
+
}
|
|
35944
|
+
for (const e of g.edges ?? []) {
|
|
35945
|
+
pushEdge(outgoing, { from: e.source, to: e.target, type: e.type, layer });
|
|
35946
|
+
}
|
|
35947
|
+
}
|
|
35948
|
+
const paths = [];
|
|
35949
|
+
const queue = [{ nodes: [from], edges: [] }];
|
|
35950
|
+
while (queue.length > 0 && paths.length < maxPaths) {
|
|
35951
|
+
const p = queue.shift();
|
|
35952
|
+
const tail = p.nodes[p.nodes.length - 1];
|
|
35953
|
+
if (tail === to && p.nodes.length > 1) {
|
|
35954
|
+
paths.push(p);
|
|
35955
|
+
continue;
|
|
35956
|
+
}
|
|
35957
|
+
if (p.nodes.length > maxHops) continue;
|
|
35958
|
+
const next = outgoing.get(tail) ?? [];
|
|
35959
|
+
for (const e of next) {
|
|
35960
|
+
if (p.nodes.includes(e.to)) continue;
|
|
35961
|
+
queue.push({ nodes: [...p.nodes, e.to], edges: [...p.edges, e] });
|
|
35962
|
+
}
|
|
35963
|
+
}
|
|
35964
|
+
paths.sort((a, b) => a.nodes.length - b.nodes.length);
|
|
35965
|
+
const total = paths.length;
|
|
35966
|
+
const page = paths.slice(offset, offset + limit);
|
|
35967
|
+
const hasMore = offset + page.length < total;
|
|
35968
|
+
const parserAbsent = layersScanned.length > 0 && layersWithCrossRefs.length === 0;
|
|
35969
|
+
return okJson({
|
|
35970
|
+
from,
|
|
35971
|
+
to,
|
|
35972
|
+
max_hops: maxHops,
|
|
35973
|
+
max_paths: maxPaths,
|
|
35974
|
+
total,
|
|
35975
|
+
returned: page.length,
|
|
35976
|
+
offset,
|
|
35977
|
+
has_more: hasMore,
|
|
35978
|
+
...hasMore ? { next_offset: offset + page.length } : {},
|
|
35979
|
+
...total === maxPaths ? { search_capped: true, hint: "Hit max_paths cap \u2014 more paths may exist. Raise max_paths or narrow from/to." } : {},
|
|
35980
|
+
...parserAbsent ? { parser_warning: parserWarning(layersScanned, "cross_refs", "cross-layer references parser(s)") + " Cross-layer paths are unreachable without cross_refs; only same-layer paths (imports/renders/belongs_to) are visible." } : {},
|
|
35981
|
+
paths: page.map((p) => ({ hops: p.nodes.length - 1, nodes: p.nodes, edges: p.edges }))
|
|
35982
|
+
});
|
|
35983
|
+
}
|
|
35984
|
+
function pushEdge(map, e) {
|
|
35985
|
+
if (!map.has(e.from)) map.set(e.from, []);
|
|
35986
|
+
map.get(e.from).push(e);
|
|
35987
|
+
}
|
|
35988
|
+
function handleAuthCoverageReport(args) {
|
|
35989
|
+
const __resolved = resolveOrErr(args);
|
|
35990
|
+
if ("content" in __resolved) return __resolved;
|
|
35991
|
+
const { rootDir } = __resolved;
|
|
35992
|
+
const moduleFilter = args.module;
|
|
35993
|
+
const strategyFilter = args.strategy;
|
|
35994
|
+
const offset = args.offset ?? 0;
|
|
35995
|
+
const limit = args.limit ?? 10;
|
|
35996
|
+
const api = readGraph(rootDir, "api");
|
|
35997
|
+
if (!api) return err("No api graph found. Run generate_graph first.");
|
|
35998
|
+
const byStrategy = {};
|
|
35999
|
+
const byModule = {};
|
|
36000
|
+
const unauthenticated = [];
|
|
36001
|
+
const matchedEndpoints = [];
|
|
36002
|
+
for (const n of api.nodes) {
|
|
36003
|
+
const tags = n.tags;
|
|
36004
|
+
const moduleTag = tags?.module ?? "root";
|
|
36005
|
+
const auth = n.auth ?? [];
|
|
36006
|
+
const strategyKey = auth.length === 0 ? "none" : auth.slice().sort().join("+");
|
|
36007
|
+
byStrategy[strategyKey] = (byStrategy[strategyKey] ?? 0) + 1;
|
|
36008
|
+
if (!byModule[moduleTag]) byModule[moduleTag] = { total: 0, by_strategy: {} };
|
|
36009
|
+
byModule[moduleTag].total += 1;
|
|
36010
|
+
byModule[moduleTag].by_strategy[strategyKey] = (byModule[moduleTag].by_strategy[strategyKey] ?? 0) + 1;
|
|
36011
|
+
if (auth.length === 0) unauthenticated.push(n.id);
|
|
36012
|
+
if (moduleFilter && moduleTag !== moduleFilter) continue;
|
|
36013
|
+
if (strategyFilter && strategyKey !== strategyFilter) continue;
|
|
36014
|
+
matchedEndpoints.push({
|
|
36015
|
+
id: n.id,
|
|
36016
|
+
path: n.path,
|
|
36017
|
+
methods: n.methods,
|
|
36018
|
+
module: moduleTag,
|
|
36019
|
+
auth,
|
|
36020
|
+
mutates: n.mutates === true
|
|
36021
|
+
});
|
|
36022
|
+
}
|
|
36023
|
+
matchedEndpoints.sort((a, b) => String(a.path ?? a.id).localeCompare(String(b.path ?? b.id)));
|
|
36024
|
+
const total = matchedEndpoints.length;
|
|
36025
|
+
const page = matchedEndpoints.slice(offset, offset + limit);
|
|
36026
|
+
const hasMore = offset + page.length < total;
|
|
36027
|
+
return okJson({
|
|
36028
|
+
total_endpoints: api.nodes.length,
|
|
36029
|
+
by_strategy: byStrategy,
|
|
36030
|
+
unauthenticated_count: unauthenticated.length,
|
|
36031
|
+
by_module: byModule,
|
|
36032
|
+
filter: { module: moduleFilter, strategy: strategyFilter },
|
|
36033
|
+
total,
|
|
36034
|
+
returned: page.length,
|
|
36035
|
+
offset,
|
|
36036
|
+
has_more: hasMore,
|
|
36037
|
+
...hasMore ? { next_offset: offset + page.length } : {},
|
|
36038
|
+
items: page
|
|
36039
|
+
});
|
|
36040
|
+
}
|
|
34417
36041
|
function handleAuditLayer(args) {
|
|
34418
36042
|
const __resolved = resolveOrErr(args);
|
|
34419
36043
|
if ("content" in __resolved) return __resolved;
|
|
@@ -34477,20 +36101,20 @@ function handleDetectProjectStack() {
|
|
|
34477
36101
|
if (ref.type === "references_api") stats.references_api++;
|
|
34478
36102
|
}
|
|
34479
36103
|
}
|
|
34480
|
-
const srcDir = (0,
|
|
34481
|
-
if ((0,
|
|
36104
|
+
const srcDir = (0, import_node_path33.join)(rootDir, "src");
|
|
36105
|
+
if ((0, import_node_fs28.existsSync)(srcDir)) {
|
|
34482
36106
|
const scanDir = (dir) => {
|
|
34483
|
-
if (!(0,
|
|
34484
|
-
for (const entry of (0,
|
|
36107
|
+
if (!(0, import_node_fs28.existsSync)(dir)) return;
|
|
36108
|
+
for (const entry of (0, import_node_fs28.readdirSync)(dir, { withFileTypes: true })) {
|
|
34485
36109
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
34486
|
-
const full = (0,
|
|
36110
|
+
const full = (0, import_node_path33.join)(dir, entry.name);
|
|
34487
36111
|
if (entry.isDirectory()) {
|
|
34488
36112
|
scanDir(full);
|
|
34489
36113
|
continue;
|
|
34490
36114
|
}
|
|
34491
|
-
if (![".ts", ".tsx"].includes((0,
|
|
36115
|
+
if (![".ts", ".tsx"].includes((0, import_node_path33.extname)(entry.name))) continue;
|
|
34492
36116
|
try {
|
|
34493
|
-
const content = (0,
|
|
36117
|
+
const content = (0, import_node_fs28.readFileSync)(full, "utf-8");
|
|
34494
36118
|
const matches = content.match(/@api\s+(GET|POST|PUT|DELETE|PATCH)\s+\/\S+/g);
|
|
34495
36119
|
if (matches) stats.annotations += matches.length;
|
|
34496
36120
|
} catch {
|
|
@@ -34509,7 +36133,7 @@ function handleDetectProjectStack() {
|
|
|
34509
36133
|
name: p.name,
|
|
34510
36134
|
root: p.root,
|
|
34511
36135
|
absolute_root: p.absoluteRoot,
|
|
34512
|
-
has_graph: (0,
|
|
36136
|
+
has_graph: (0, import_node_fs28.existsSync)((0, import_node_path33.join)(p.absoluteRoot, LAUNCHSECURE_DIR, "graphs"))
|
|
34513
36137
|
}));
|
|
34514
36138
|
return okJson({
|
|
34515
36139
|
languages,
|
|
@@ -34623,6 +36247,30 @@ async function handleMessage(msg) {
|
|
|
34623
36247
|
respond(id ?? null, withFreshnessMeta(handleBlastPoints(args), args));
|
|
34624
36248
|
return;
|
|
34625
36249
|
}
|
|
36250
|
+
if (toolName === "list_notes") {
|
|
36251
|
+
respond(id ?? null, withFreshnessMeta(handleListNotes(args), args));
|
|
36252
|
+
return;
|
|
36253
|
+
}
|
|
36254
|
+
if (toolName === "migration_audit") {
|
|
36255
|
+
respond(id ?? null, withFreshnessMeta(handleMigrationAudit(args), args));
|
|
36256
|
+
return;
|
|
36257
|
+
}
|
|
36258
|
+
if (toolName === "drift_report") {
|
|
36259
|
+
respond(id ?? null, withFreshnessMeta(handleDriftReport(args), args));
|
|
36260
|
+
return;
|
|
36261
|
+
}
|
|
36262
|
+
if (toolName === "who_uses") {
|
|
36263
|
+
respond(id ?? null, withFreshnessMeta(handleWhoUses(args), args));
|
|
36264
|
+
return;
|
|
36265
|
+
}
|
|
36266
|
+
if (toolName === "trace_path") {
|
|
36267
|
+
respond(id ?? null, withFreshnessMeta(handleTracePath(args), args));
|
|
36268
|
+
return;
|
|
36269
|
+
}
|
|
36270
|
+
if (toolName === "auth_coverage_report") {
|
|
36271
|
+
respond(id ?? null, withFreshnessMeta(handleAuthCoverageReport(args), args));
|
|
36272
|
+
return;
|
|
36273
|
+
}
|
|
34626
36274
|
respondError(id ?? null, -32601, `Unknown tool: ${toolName}`);
|
|
34627
36275
|
return;
|
|
34628
36276
|
}
|
|
@@ -34797,7 +36445,7 @@ function parseArgs() {
|
|
|
34797
36445
|
return { port, token, serverUrl: LAUNCHSECURE_URL, subcommand };
|
|
34798
36446
|
}
|
|
34799
36447
|
function tryListen(server, port, maxRetries = 10) {
|
|
34800
|
-
return new Promise((
|
|
36448
|
+
return new Promise((resolve6, reject) => {
|
|
34801
36449
|
let attempts = 0;
|
|
34802
36450
|
function attempt(p) {
|
|
34803
36451
|
server.once("error", (err2) => {
|
|
@@ -34808,7 +36456,7 @@ function tryListen(server, port, maxRetries = 10) {
|
|
|
34808
36456
|
reject(err2);
|
|
34809
36457
|
}
|
|
34810
36458
|
});
|
|
34811
|
-
server.listen(p, "127.0.0.1", () =>
|
|
36459
|
+
server.listen(p, "127.0.0.1", () => resolve6(p));
|
|
34812
36460
|
}
|
|
34813
36461
|
attempt(port);
|
|
34814
36462
|
});
|
|
@@ -34829,7 +36477,7 @@ function saveCredentials(creds) {
|
|
|
34829
36477
|
});
|
|
34830
36478
|
}
|
|
34831
36479
|
function verifyToken(serverUrl, token) {
|
|
34832
|
-
return new Promise((
|
|
36480
|
+
return new Promise((resolve6) => {
|
|
34833
36481
|
const url = new URL("/api/mcp/verify", serverUrl);
|
|
34834
36482
|
const body = JSON.stringify({ token });
|
|
34835
36483
|
const mod = url.protocol === "https:" ? import_https.default : import_http.default;
|
|
@@ -34844,30 +36492,30 @@ function verifyToken(serverUrl, token) {
|
|
|
34844
36492
|
res.on("data", (chunk) => data += chunk);
|
|
34845
36493
|
res.on("end", () => {
|
|
34846
36494
|
try {
|
|
34847
|
-
|
|
36495
|
+
resolve6(JSON.parse(data));
|
|
34848
36496
|
} catch {
|
|
34849
|
-
|
|
36497
|
+
resolve6({ valid: false, error: "Invalid response from server" });
|
|
34850
36498
|
}
|
|
34851
36499
|
});
|
|
34852
36500
|
});
|
|
34853
36501
|
req.on("error", (err2) => {
|
|
34854
|
-
|
|
36502
|
+
resolve6({ valid: false, error: `Cannot reach server: ${err2.message}` });
|
|
34855
36503
|
});
|
|
34856
36504
|
req.setTimeout(1e4, () => {
|
|
34857
36505
|
req.destroy();
|
|
34858
|
-
|
|
36506
|
+
resolve6({ valid: false, error: "Connection timed out" });
|
|
34859
36507
|
});
|
|
34860
36508
|
req.write(body);
|
|
34861
36509
|
req.end();
|
|
34862
36510
|
});
|
|
34863
36511
|
}
|
|
34864
36512
|
function httpRequest2(reqUrl, options, body, timeout = 3e4) {
|
|
34865
|
-
return new Promise((
|
|
36513
|
+
return new Promise((resolve6, reject) => {
|
|
34866
36514
|
const mod = reqUrl.protocol === "https:" ? import_https.default : import_http.default;
|
|
34867
36515
|
const r = mod.request(reqUrl, options, (resp) => {
|
|
34868
36516
|
let data = "";
|
|
34869
36517
|
resp.on("data", (chunk) => data += chunk);
|
|
34870
|
-
resp.on("end", () =>
|
|
36518
|
+
resp.on("end", () => resolve6({ status: resp.statusCode || 0, headers: resp.headers, body: data }));
|
|
34871
36519
|
});
|
|
34872
36520
|
r.on("error", reject);
|
|
34873
36521
|
r.setTimeout(timeout, () => {
|