@launchsecure/launch-kit 0.0.30 → 0.0.32
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 +1027 -929
- package/dist/beacon/beacon.mjs.map +1 -1
- package/dist/beacon/beacon.umd.js +9 -9
- package/dist/beacon/beacon.umd.js.map +1 -1
- package/dist/beacon/types/internal/pick-mode-overlay.d.ts.map +1 -1
- package/dist/beacon/types/internal/picker.d.ts.map +1 -1
- package/dist/beacon/types/internal/pin-popover.d.ts.map +1 -1
- package/dist/beacon/types/internal/screenshot.d.ts +19 -1
- package/dist/beacon/types/internal/screenshot.d.ts.map +1 -1
- package/dist/beacon/types/internal/selector.d.ts.map +1 -1
- package/dist/beacon/types/plugins/domEle.d.ts.map +1 -1
- 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-DCt2IMRR.js → _baseUniq-C7GsHvgg.js} +1 -1
- package/dist/deck-client/assets/{arc-h-ifqmNR.js → arc-CSrZRINY.js} +1 -1
- package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-C9dITSPv.js → architectureDiagram-Q4EWVU46-zoB-G17J.js} +1 -1
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BHuJT34t.js → blockDiagram-DXYQGD6D-BRjjtYH6.js} +1 -1
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-CpvMGtDG.js → c4Diagram-AHTNJAMY-C3D3sd2U.js} +1 -1
- package/dist/deck-client/assets/channel-8ReQnQfH.js +1 -0
- package/dist/deck-client/assets/{chunk-4BX2VUAB-B6md1VIm.js → chunk-4BX2VUAB-DhpDMOPO.js} +1 -1
- package/dist/deck-client/assets/{chunk-4TB4RGXK-BmEnX8ik.js → chunk-4TB4RGXK-BIRgPXRl.js} +1 -1
- package/dist/deck-client/assets/{chunk-55IACEB6-BZPUyZAZ.js → chunk-55IACEB6-BF24dwDZ.js} +1 -1
- package/dist/deck-client/assets/{chunk-EDXVE4YY-BWwNUK-l.js → chunk-EDXVE4YY-CW75Y61B.js} +1 -1
- package/dist/deck-client/assets/{chunk-FMBD7UC4-o7gSppGI.js → chunk-FMBD7UC4-B5-oyL79.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-C4KoTL5p.js → chunk-OYMX7WX6-BB2bHe_Q.js} +1 -1
- package/dist/deck-client/assets/{chunk-QZHKN3VN-jkf68sDs.js → chunk-QZHKN3VN-D80eZO4B.js} +1 -1
- package/dist/deck-client/assets/{chunk-YZCP3GAM-Cd4yBE7o.js → chunk-YZCP3GAM-Dz9787p_.js} +1 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-cRxTeGkK.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-cRxTeGkK.js +1 -0
- package/dist/deck-client/assets/clone-LSHZ3K6R.js +1 -0
- package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-DeGFUgAV.js → cose-bilkent-S5V4N54A-MQjiZLcL.js} +1 -1
- package/dist/deck-client/assets/{dagre-KV5264BT-ekcYJuUV.js → dagre-KV5264BT-DG4EcLpJ.js} +1 -1
- package/dist/deck-client/assets/{diagram-5BDNPKRD-YHPk4rV2.js → diagram-5BDNPKRD-1n7hM3Gc.js} +1 -1
- package/dist/deck-client/assets/{diagram-G4DWMVQ6-DM-JCd_B.js → diagram-G4DWMVQ6-CYMarncV.js} +1 -1
- package/dist/deck-client/assets/{diagram-MMDJMWI5-l5FK1ybk.js → diagram-MMDJMWI5-DSisoipe.js} +1 -1
- package/dist/deck-client/assets/{diagram-TYMM5635-CIN4_1-j.js → diagram-TYMM5635-Btnq49OJ.js} +1 -1
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-MyinSkEl.js → erDiagram-SMLLAGMA-Cu2Hb_Tz.js} +1 -1
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-Dk8nn42x.js → flowDiagram-DWJPFMVM-CGJzUzsO.js} +1 -1
- package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-BU1ihicu.js → ganttDiagram-T4ZO3ILL-D9sqGUBT.js} +1 -1
- package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-BjsTL13C.js → gitGraphDiagram-UUTBAWPF-C0QwX2od.js} +1 -1
- package/dist/deck-client/assets/{graph-DJmh-xi7.js → graph-CcBjOQCl.js} +1 -1
- package/dist/deck-client/assets/index-0arwoc0z.js +1195 -0
- 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/{infoDiagram-42DDH7IO-Dxvy_RB4.js → infoDiagram-42DDH7IO-DTimhhhS.js} +1 -1
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DPOaNF1l.js → ishikawaDiagram-UXIWVN3A-DxOxg_B4.js} +1 -1
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-DMew3K5c.js → journeyDiagram-VCZTEJTY-Bpq0qa4j.js} +1 -1
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-csciJFuk.js → kanban-definition-6JOO6SKY-aTIrpcVO.js} +1 -1
- package/dist/deck-client/assets/{layout-Dg4yyms2.js → layout-DqglLR2E.js} +1 -1
- package/dist/deck-client/assets/{linear-BA3zU6gq.js → linear-D5GxehPc.js} +1 -1
- package/dist/deck-client/assets/{min-lz-Ird-p.js → min-DXLfSREq.js} +1 -1
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-CCEN8OQV.js → mindmap-definition-QFDTVHPH-mO5Vys7I.js} +1 -1
- package/dist/deck-client/assets/{pieDiagram-DEJITSTG-DM6n1HY7.js → pieDiagram-DEJITSTG-Dm0gzdAr.js} +1 -1
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-_ULoR66n.js → quadrantDiagram-34T5L4WZ-Daq7j3qD.js} +1 -1
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-BuwJs7Tn.js → requirementDiagram-MS252O5E-CmwV95um.js} +1 -1
- package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-BEsuzkW4.js → sankeyDiagram-XADWPNL6-BOYl3Nkf.js} +1 -1
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-CP2H0YWf.js → sequenceDiagram-FGHM5R23-BuUjhIcW.js} +1 -1
- package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-B5Gw_NNL.js → stateDiagram-FHFEXIEX-LUZ_uwio.js} +1 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CnnRwE5D.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DsoYydQa.js → timeline-definition-GMOUNBTQ-CDUxCCAW.js} +1 -1
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-Dz8JT_ob.js → vennDiagram-DHZGUBPP-BRb24Tf7.js} +1 -1
- package/dist/deck-client/assets/{wardley-RL74JXVD-DGHQ_Ijv.js → wardley-RL74JXVD-B0BYyVBY.js} +1 -1
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-DN1LJMB1.js → wardleyDiagram-NUSXRM2D-BLGlYrQz.js} +1 -1
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-nb0oSfrQ.js → xychartDiagram-5P7HB3ND-De31MSnk.js} +1 -1
- package/dist/deck-client/index.html +2 -2
- package/dist/server/cli.js +666 -12
- package/dist/server/council-entry.js +0 -0
- package/dist/server/deck-mcp-entry.js +224 -61
- package/dist/server/deck-serve.js +195 -41
- package/dist/server/fb-wizard.js +0 -0
- package/dist/server/graph-mcp-entry.js +666 -12
- package/dist/server/init-entry.js +231 -82
- package/package.json +23 -21
- package/scaffolds/ls-marketplace/plugins/kit/skills/analyse/SKILL.md +180 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/{blast-radius.md → blast-radius/SKILL.md} +28 -12
- package/scaffolds/ls-marketplace/plugins/kit/skills/{debug.md → debug/SKILL.md} +2 -9
- package/scaffolds/ls-marketplace/plugins/kit/skills/diagram/SKILL.md +174 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/{prototype.md → prototype/SKILL.md} +21 -1
- package/scaffolds/ls-marketplace/plugins/kit/skills/recovery/SKILL.md +95 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/{wireframe.md → wireframe/SKILL.md} +21 -1
- package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +0 -0
- package/scaffolds/recall-hook/scripts/ensure-recall.sh +0 -0
- package/dist/deck-client/assets/channel-2PZVMiXf.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-Bt8xBAof.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-Bt8xBAof.js +0 -1
- package/dist/deck-client/assets/clone-BHQryoDl.js +0 -1
- package/dist/deck-client/assets/index-KsShfCV-.js +0 -476
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-4T4wMDXr.js +0 -1
- package/scaffolds/ls-marketplace/plugins/kit/skills/diagram.md +0 -134
- package/scaffolds/ls-marketplace/plugins/kit/skills/recall.md +0 -83
- /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/scaffolds/ls-marketplace/plugins/kit/skills/{beacon-array.md → beacon-array/SKILL.md} +0 -0
- /package/scaffolds/ls-marketplace/plugins/kit/skills/{beacon-clear.md → beacon-clear/SKILL.md} +0 -0
- /package/scaffolds/ls-marketplace/plugins/kit/skills/{beacon-pulse.md → beacon-pulse/SKILL.md} +0 -0
- /package/scaffolds/ls-marketplace/plugins/kit/skills/{beacon-scan.md → beacon-scan/SKILL.md} +0 -0
- /package/scaffolds/ls-marketplace/plugins/kit/skills/{brief.md → brief/SKILL.md} +0 -0
- /package/scaffolds/ls-marketplace/plugins/kit/skills/{course.md → course/SKILL.md} +0 -0
- /package/scaffolds/ls-marketplace/plugins/kit/skills/{deploy-check.md → deploy-check/SKILL.md} +0 -0
- /package/scaffolds/ls-marketplace/plugins/kit/skills/{orbit.md → orbit/SKILL.md} +0 -0
- /package/scaffolds/ls-marketplace/plugins/kit/skills/{show-mcp-status.md → show-mcp-status/SKILL.md} +0 -0
|
@@ -8361,9 +8361,13 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
8361
8361
|
const minimal = args.minimal ?? layerIsDb;
|
|
8362
8362
|
const includeEdges = args.include_edges;
|
|
8363
8363
|
const includeFindings = args.include_findings === true;
|
|
8364
|
+
const tagPredicates = Array.isArray(args.tag_predicates) ? args.tag_predicates : void 0;
|
|
8365
|
+
const includeCrossRefs = args.include_cross_refs === true;
|
|
8366
|
+
const crossRefType = args.cross_ref_type;
|
|
8367
|
+
const edgeConfidence = Array.isArray(args.edge_confidence) ? new Set(args.edge_confidence.map((s) => String(s).toLowerCase())) : void 0;
|
|
8364
8368
|
const offset = args.offset ?? 0;
|
|
8365
8369
|
const limit = args.limit;
|
|
8366
|
-
const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue);
|
|
8370
|
+
const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue || tagPredicates && tagPredicates.length > 0);
|
|
8367
8371
|
if (layer) {
|
|
8368
8372
|
const available = getAvailableLayers(rootDir);
|
|
8369
8373
|
if (available.length > 0 && !available.includes(layer)) {
|
|
@@ -8414,6 +8418,14 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
8414
8418
|
result2.contradictions = graph.contradictions ?? [];
|
|
8415
8419
|
result2.flagged_edges = graph.flagged_edges ?? [];
|
|
8416
8420
|
}
|
|
8421
|
+
const neighborhoodIds = new Set(nb.nodes.map((n) => n.id));
|
|
8422
|
+
attachCrossRefsAndFlagged(result2, graph, neighborhoodIds, {
|
|
8423
|
+
includeCrossRefs,
|
|
8424
|
+
crossRefType,
|
|
8425
|
+
edgeConfidence,
|
|
8426
|
+
scopeToIds: true,
|
|
8427
|
+
layer
|
|
8428
|
+
});
|
|
8417
8429
|
return result2;
|
|
8418
8430
|
}
|
|
8419
8431
|
if (!hasFilter) {
|
|
@@ -8434,6 +8446,11 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
8434
8446
|
const nodeTags = n.tags;
|
|
8435
8447
|
if (module_ && nodeTags?.module !== module_) return false;
|
|
8436
8448
|
if (tagKey && tagValue && nodeTags?.[tagKey] !== tagValue) return false;
|
|
8449
|
+
if (tagPredicates && tagPredicates.length > 0) {
|
|
8450
|
+
for (const p of tagPredicates) {
|
|
8451
|
+
if (!evaluatePredicate(n, p)) return false;
|
|
8452
|
+
}
|
|
8453
|
+
}
|
|
8437
8454
|
return true;
|
|
8438
8455
|
});
|
|
8439
8456
|
if (matched.length === 0) {
|
|
@@ -8481,8 +8498,89 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
8481
8498
|
result.contradictions = graph.contradictions ?? [];
|
|
8482
8499
|
result.flagged_edges = graph.flagged_edges ?? [];
|
|
8483
8500
|
}
|
|
8501
|
+
attachCrossRefsAndFlagged(result, graph, returnedIds, {
|
|
8502
|
+
includeCrossRefs,
|
|
8503
|
+
crossRefType,
|
|
8504
|
+
edgeConfidence,
|
|
8505
|
+
scopeToIds: true,
|
|
8506
|
+
layer
|
|
8507
|
+
});
|
|
8484
8508
|
return result;
|
|
8485
8509
|
}
|
|
8510
|
+
function attachCrossRefsAndFlagged(result, graph, scopeIds, opts) {
|
|
8511
|
+
if (opts.includeCrossRefs) {
|
|
8512
|
+
const allCrossRefs = graph.cross_refs ?? [];
|
|
8513
|
+
const crossRefsByNode = {};
|
|
8514
|
+
for (const id of scopeIds) {
|
|
8515
|
+
crossRefsByNode[id] = { outgoing: [], incoming: [] };
|
|
8516
|
+
}
|
|
8517
|
+
for (const cr of allCrossRefs) {
|
|
8518
|
+
if (opts.crossRefType && cr.type !== opts.crossRefType) continue;
|
|
8519
|
+
if (crossRefsByNode[cr.source]) crossRefsByNode[cr.source].outgoing.push(cr);
|
|
8520
|
+
if (crossRefsByNode[cr.target]) crossRefsByNode[cr.target].incoming.push(cr);
|
|
8521
|
+
}
|
|
8522
|
+
result.cross_refs = crossRefsByNode;
|
|
8523
|
+
if (opts.crossRefType) result.cross_ref_type = opts.crossRefType;
|
|
8524
|
+
if (allCrossRefs.length === 0) {
|
|
8525
|
+
result.parser_warning = parserWarning([opts.layer], "cross_refs", "cross-layer references parser(s) (fetch-resolver / api-annotations / url-literal-scanner / static-ref-scanner)");
|
|
8526
|
+
}
|
|
8527
|
+
}
|
|
8528
|
+
if (opts.edgeConfidence && opts.edgeConfidence.size > 0) {
|
|
8529
|
+
const allFlagged = graph.flagged_edges ?? [];
|
|
8530
|
+
const useScope = opts.scopeToIds && scopeIds.size > 0;
|
|
8531
|
+
result.flagged_edges = allFlagged.filter((fe) => {
|
|
8532
|
+
if (!opts.edgeConfidence.has(String(fe.confidence).toLowerCase())) return false;
|
|
8533
|
+
if (useScope && !scopeIds.has(fe.source) && !scopeIds.has(fe.target)) return false;
|
|
8534
|
+
return true;
|
|
8535
|
+
});
|
|
8536
|
+
if (allFlagged.length === 0 && !result.parser_warning) {
|
|
8537
|
+
result.parser_warning = parserWarning([opts.layer], "flagged_edges", opts.layer === "db" ? "sql-migrations parser" : "cross-layer parsers");
|
|
8538
|
+
}
|
|
8539
|
+
}
|
|
8540
|
+
}
|
|
8541
|
+
function parserWarning(layers, capability, parserName) {
|
|
8542
|
+
return `The ${capability} data is empty in scanned layer(s): ${layers.join(", ")}. This likely means the ${parserName} did not produce output for this project \u2014 run detect_project_stack to verify parser configuration, then generate_graph to refresh. (If the parser IS configured and the source data is genuinely empty, ignore this warning.)`;
|
|
8543
|
+
}
|
|
8544
|
+
function evaluatePredicate(node, p) {
|
|
8545
|
+
if (!p.field || !p.op) return true;
|
|
8546
|
+
const tags = node.tags;
|
|
8547
|
+
const fromTag = tags?.[p.field];
|
|
8548
|
+
const fromField = node[p.field];
|
|
8549
|
+
const present = fromTag !== void 0 || fromField !== void 0;
|
|
8550
|
+
if (p.op === "exists") return present;
|
|
8551
|
+
if (p.op === "not_exists") return !present;
|
|
8552
|
+
if (!present) return false;
|
|
8553
|
+
const raw = fromTag !== void 0 ? coerceTagValue(fromTag, p.value) : fromField;
|
|
8554
|
+
switch (p.op) {
|
|
8555
|
+
case "eq":
|
|
8556
|
+
return raw === p.value;
|
|
8557
|
+
case "ne":
|
|
8558
|
+
return raw !== p.value;
|
|
8559
|
+
case "gt":
|
|
8560
|
+
return typeof raw === "number" && typeof p.value === "number" && raw > p.value;
|
|
8561
|
+
case "lt":
|
|
8562
|
+
return typeof raw === "number" && typeof p.value === "number" && raw < p.value;
|
|
8563
|
+
case "gte":
|
|
8564
|
+
return typeof raw === "number" && typeof p.value === "number" && raw >= p.value;
|
|
8565
|
+
case "lte":
|
|
8566
|
+
return typeof raw === "number" && typeof p.value === "number" && raw <= p.value;
|
|
8567
|
+
case "in":
|
|
8568
|
+
return Array.isArray(p.value) && p.value.includes(raw);
|
|
8569
|
+
default:
|
|
8570
|
+
return false;
|
|
8571
|
+
}
|
|
8572
|
+
}
|
|
8573
|
+
function coerceTagValue(raw, target) {
|
|
8574
|
+
if (typeof target === "boolean") {
|
|
8575
|
+
if (raw === "true") return true;
|
|
8576
|
+
if (raw === "false") return false;
|
|
8577
|
+
}
|
|
8578
|
+
if (typeof target === "number") {
|
|
8579
|
+
const n = Number(raw);
|
|
8580
|
+
if (!Number.isNaN(n)) return n;
|
|
8581
|
+
}
|
|
8582
|
+
return raw;
|
|
8583
|
+
}
|
|
8486
8584
|
function runReadGraphQuery(rootDir, args) {
|
|
8487
8585
|
const raw = runReadGraphQueryRaw(rootDir, args);
|
|
8488
8586
|
return compactResult(raw);
|
|
@@ -8593,8 +8691,23 @@ function handleInspectNode(args) {
|
|
|
8593
8691
|
} else {
|
|
8594
8692
|
matched = graph.nodes;
|
|
8595
8693
|
}
|
|
8596
|
-
const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params", "effects", "ui_labels", "notes"];
|
|
8694
|
+
const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params", "effects", "ui_labels", "notes", "crossRefs"];
|
|
8597
8695
|
const requestedFields = fields ?? allDeepFields;
|
|
8696
|
+
const crossRefTypeArg = args.cross_ref_type;
|
|
8697
|
+
const wantCrossRefs = requestedFields.includes("crossRefs");
|
|
8698
|
+
let crossRefsBySource = null;
|
|
8699
|
+
let crossRefsByTarget = null;
|
|
8700
|
+
if (wantCrossRefs) {
|
|
8701
|
+
crossRefsBySource = /* @__PURE__ */ new Map();
|
|
8702
|
+
crossRefsByTarget = /* @__PURE__ */ new Map();
|
|
8703
|
+
for (const cr of graph.cross_refs ?? []) {
|
|
8704
|
+
if (crossRefTypeArg && cr.type !== crossRefTypeArg) continue;
|
|
8705
|
+
if (!crossRefsBySource.has(cr.source)) crossRefsBySource.set(cr.source, []);
|
|
8706
|
+
crossRefsBySource.get(cr.source).push(cr);
|
|
8707
|
+
if (!crossRefsByTarget.has(cr.target)) crossRefsByTarget.set(cr.target, []);
|
|
8708
|
+
crossRefsByTarget.get(cr.target).push(cr);
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8598
8711
|
let filterRegex = null;
|
|
8599
8712
|
if (filter) {
|
|
8600
8713
|
try {
|
|
@@ -8617,7 +8730,14 @@ function handleInspectNode(args) {
|
|
|
8617
8730
|
const deep = { id: node.id, name: node.name, type: node.type };
|
|
8618
8731
|
let hasData = false;
|
|
8619
8732
|
for (const field of requestedFields) {
|
|
8620
|
-
if (
|
|
8733
|
+
if (field === "crossRefs") {
|
|
8734
|
+
const outgoing = crossRefsBySource?.get(node.id) ?? [];
|
|
8735
|
+
const incoming = crossRefsByTarget?.get(node.id) ?? [];
|
|
8736
|
+
if (outgoing.length > 0 || incoming.length > 0) {
|
|
8737
|
+
deep.crossRefs = { outgoing, incoming };
|
|
8738
|
+
hasData = true;
|
|
8739
|
+
}
|
|
8740
|
+
} else if (allDeepFields.includes(field) && node[field] != null) {
|
|
8621
8741
|
deep[field] = node[field];
|
|
8622
8742
|
hasData = true;
|
|
8623
8743
|
}
|
|
@@ -8641,10 +8761,14 @@ function handleInspectNode(args) {
|
|
|
8641
8761
|
const hint = filter ? `No nodes with deep fields matching /${filter}/${caseInsensitive ? "i" : ""} in ${layer} layer.` : `No nodes matching "${search}" in ${layer} layer.`;
|
|
8642
8762
|
return err(hint);
|
|
8643
8763
|
}
|
|
8764
|
+
const wantedCrossRefs = wantCrossRefs;
|
|
8765
|
+
const graphHasNoCrossRefs = (graph.cross_refs ?? []).length === 0;
|
|
8766
|
+
const extraWarning = wantedCrossRefs && graphHasNoCrossRefs ? { parser_warning: parserWarning([layer], "cross_refs", "cross-layer references parser(s) (fetch-resolver / api-annotations / url-literal-scanner / static-ref-scanner)") } : {};
|
|
8644
8767
|
return okJson({
|
|
8645
8768
|
layer,
|
|
8646
8769
|
matched: results.length,
|
|
8647
8770
|
...results.length >= maxResults ? { truncated: true, hint: `Showing first ${maxResults} matches. Narrow with search param.` } : {},
|
|
8771
|
+
...extraWarning,
|
|
8648
8772
|
nodes: results
|
|
8649
8773
|
});
|
|
8650
8774
|
}
|
|
@@ -8850,13 +8974,19 @@ function handleEffectsIndex(args) {
|
|
|
8850
8974
|
const __resolved = resolveOrErr(args);
|
|
8851
8975
|
if ("content" in __resolved) return __resolved;
|
|
8852
8976
|
const { rootDir } = __resolved;
|
|
8977
|
+
const kind = args.kind ?? "collisions";
|
|
8978
|
+
const key = args.key;
|
|
8979
|
+
const MAX_KEYS = 200;
|
|
8980
|
+
if (kind === "fetch_calls") {
|
|
8981
|
+
return okJson(buildFetchCallsIndex(rootDir, key, MAX_KEYS));
|
|
8982
|
+
}
|
|
8983
|
+
if (kind === "api_effects") {
|
|
8984
|
+
return okJson(buildApiEffectsIndex(rootDir, key, MAX_KEYS));
|
|
8985
|
+
}
|
|
8853
8986
|
const idx = readEffectsIndex(rootDir);
|
|
8854
8987
|
if (!idx) {
|
|
8855
8988
|
return err("No effects-index.json found. Run generate_graph first (or wait for the file-watcher to fire).");
|
|
8856
8989
|
}
|
|
8857
|
-
const kind = args.kind ?? "collisions";
|
|
8858
|
-
const key = args.key;
|
|
8859
|
-
const MAX_KEYS = 200;
|
|
8860
8990
|
if (kind === "singleton_risks") {
|
|
8861
8991
|
return okJson({ kind, count: idx.singleton_risks.length, nodes: idx.singleton_risks });
|
|
8862
8992
|
}
|
|
@@ -8870,7 +9000,7 @@ function handleEffectsIndex(args) {
|
|
|
8870
9000
|
}
|
|
8871
9001
|
const map = idx[kind];
|
|
8872
9002
|
if (!map || typeof map !== "object") {
|
|
8873
|
-
return err(`Unknown kind "${kind}". Valid: dom_ids, window_events, storage_keys, fetch_urls, timers, singleton_risks, collisions.`);
|
|
9003
|
+
return err(`Unknown kind "${kind}". Valid: dom_ids, window_events, storage_keys, fetch_urls, timers, singleton_risks, collisions, fetch_calls, api_effects.`);
|
|
8874
9004
|
}
|
|
8875
9005
|
if (key) {
|
|
8876
9006
|
const nodes = map[key] ?? [];
|
|
@@ -8888,6 +9018,97 @@ function handleEffectsIndex(args) {
|
|
|
8888
9018
|
results
|
|
8889
9019
|
});
|
|
8890
9020
|
}
|
|
9021
|
+
function buildFetchCallsIndex(rootDir, key, maxKeys) {
|
|
9022
|
+
const ui = readGraph(rootDir, "ui");
|
|
9023
|
+
if (!ui) return { kind: "fetch_calls", error: "No ui graph found. Run generate_graph first." };
|
|
9024
|
+
const inverted = /* @__PURE__ */ new Map();
|
|
9025
|
+
let callsApiCount = 0;
|
|
9026
|
+
for (const cr of ui.cross_refs ?? []) {
|
|
9027
|
+
if (cr.type !== "calls_api") continue;
|
|
9028
|
+
callsApiCount++;
|
|
9029
|
+
if (!inverted.has(cr.target)) inverted.set(cr.target, /* @__PURE__ */ new Set());
|
|
9030
|
+
inverted.get(cr.target).add(cr.source);
|
|
9031
|
+
}
|
|
9032
|
+
for (const n of ui.nodes) {
|
|
9033
|
+
const eff = n.effects;
|
|
9034
|
+
if (!eff?.fetches) continue;
|
|
9035
|
+
for (const f of eff.fetches) {
|
|
9036
|
+
if (!inverted.has(f)) inverted.set(f, /* @__PURE__ */ new Set());
|
|
9037
|
+
inverted.get(f).add(n.id);
|
|
9038
|
+
}
|
|
9039
|
+
}
|
|
9040
|
+
const allCrossRefs = ui.cross_refs ?? [];
|
|
9041
|
+
const fetchResolverAbsent = callsApiCount === 0 && allCrossRefs.length === 0;
|
|
9042
|
+
const partialResolverGap = callsApiCount === 0 && inverted.size > 0;
|
|
9043
|
+
if (key) {
|
|
9044
|
+
return {
|
|
9045
|
+
kind: "fetch_calls",
|
|
9046
|
+
key,
|
|
9047
|
+
callers: [...inverted.get(key) ?? []],
|
|
9048
|
+
...fetchResolverAbsent ? { parser_warning: parserWarning(["ui"], "cross_refs", "fetch-resolver cross-layer parser") } : {},
|
|
9049
|
+
...partialResolverGap ? { parser_warning: "No calls_api cross_refs found \u2014 fetch-resolver may not be enabled. Only raw fetch() strings from ui.node.effects.fetches are indexed; resolved-to-endpoint links are missing." } : {}
|
|
9050
|
+
};
|
|
9051
|
+
}
|
|
9052
|
+
const entries = [...inverted.entries()].sort((a, b) => b[1].size - a[1].size);
|
|
9053
|
+
const truncated = entries.length > maxKeys;
|
|
9054
|
+
const results = {};
|
|
9055
|
+
for (const [k, v] of entries.slice(0, maxKeys)) results[k] = [...v];
|
|
9056
|
+
return {
|
|
9057
|
+
kind: "fetch_calls",
|
|
9058
|
+
total_keys: entries.length,
|
|
9059
|
+
...truncated ? { truncated: true, showing: maxKeys, hint: "Pass `key` (endpoint path or URL template) to look up callers." } : {},
|
|
9060
|
+
...fetchResolverAbsent ? { parser_warning: parserWarning(["ui"], "cross_refs", "fetch-resolver cross-layer parser") } : {},
|
|
9061
|
+
...partialResolverGap ? { parser_warning: "No calls_api cross_refs found \u2014 fetch-resolver may not be enabled. Only raw fetch() strings from ui.node.effects.fetches are indexed; resolved-to-endpoint links are missing." } : {},
|
|
9062
|
+
results
|
|
9063
|
+
};
|
|
9064
|
+
}
|
|
9065
|
+
function buildApiEffectsIndex(rootDir, key, maxKeys) {
|
|
9066
|
+
const api = readGraph(rootDir, "api");
|
|
9067
|
+
if (!api) return { kind: "api_effects", error: "No api graph found. Run generate_graph first." };
|
|
9068
|
+
const categories = ["calls", "fetches", "persists", "subscribes", "timers", "dom_writes", "globals"];
|
|
9069
|
+
const inverted = {};
|
|
9070
|
+
for (const c of categories) inverted[c] = /* @__PURE__ */ new Map();
|
|
9071
|
+
for (const n of api.nodes) {
|
|
9072
|
+
const eff = n.effects;
|
|
9073
|
+
if (!eff) continue;
|
|
9074
|
+
for (const c of categories) {
|
|
9075
|
+
const items = eff[c];
|
|
9076
|
+
if (!Array.isArray(items)) continue;
|
|
9077
|
+
for (const item of items) {
|
|
9078
|
+
if (!inverted[c].has(item)) inverted[c].set(item, /* @__PURE__ */ new Set());
|
|
9079
|
+
inverted[c].get(item).add(n.id);
|
|
9080
|
+
}
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
9083
|
+
if (key) {
|
|
9084
|
+
const hits = {};
|
|
9085
|
+
for (const c of categories) {
|
|
9086
|
+
const endpoints = inverted[c].get(key);
|
|
9087
|
+
if (endpoints && endpoints.size > 0) hits[c] = [...endpoints];
|
|
9088
|
+
}
|
|
9089
|
+
return { kind: "api_effects", key, hits };
|
|
9090
|
+
}
|
|
9091
|
+
const TOP_CATEGORIES = Math.min(maxKeys, 50);
|
|
9092
|
+
const TOP_CALLERS = 10;
|
|
9093
|
+
const summary = {};
|
|
9094
|
+
for (const c of categories) {
|
|
9095
|
+
const entries = [...inverted[c].entries()].sort((a, b) => b[1].size - a[1].size);
|
|
9096
|
+
const top = {};
|
|
9097
|
+
for (const [k, v] of entries.slice(0, TOP_CATEGORIES)) {
|
|
9098
|
+
const endpoints = [...v];
|
|
9099
|
+
top[k] = {
|
|
9100
|
+
count: endpoints.length,
|
|
9101
|
+
sample_endpoints: endpoints.slice(0, TOP_CALLERS)
|
|
9102
|
+
};
|
|
9103
|
+
}
|
|
9104
|
+
summary[c] = { total_items: entries.length, top };
|
|
9105
|
+
}
|
|
9106
|
+
return {
|
|
9107
|
+
kind: "api_effects",
|
|
9108
|
+
hint: `Summary mode: top ${TOP_CATEGORIES} items per category, top ${TOP_CALLERS} endpoints per item. Pass \`key\` (a specific call/url/event) to get the FULL caller list across all categories.`,
|
|
9109
|
+
summary
|
|
9110
|
+
};
|
|
9111
|
+
}
|
|
8891
9112
|
function handleChartServerStatus() {
|
|
8892
9113
|
const rootDir = process.cwd();
|
|
8893
9114
|
const lock = getLiveLock(rootDir);
|
|
@@ -9001,6 +9222,296 @@ function handleRemoveTag(args) {
|
|
|
9001
9222
|
removeTag(rootDir, nodeId, key);
|
|
9002
9223
|
return okJson({ ok: true, node_id: nodeId, removed_key: key });
|
|
9003
9224
|
}
|
|
9225
|
+
function handleMigrationAudit(args) {
|
|
9226
|
+
const __resolved = resolveOrErr(args);
|
|
9227
|
+
if ("content" in __resolved) return __resolved;
|
|
9228
|
+
const { rootDir } = __resolved;
|
|
9229
|
+
const requireOrphan = args.require_orphan_check !== false;
|
|
9230
|
+
const requireBackup = args.require_sidecar_backup !== false;
|
|
9231
|
+
const requirePreFlight = args.require_pre_flight_notice !== false;
|
|
9232
|
+
const includeSafe = args.include_safe === true;
|
|
9233
|
+
const offset = args.offset ?? 0;
|
|
9234
|
+
const limit = args.limit ?? 10;
|
|
9235
|
+
const db = readGraph(rootDir, "db");
|
|
9236
|
+
if (!db) return err("No db graph found. Run generate_graph first.");
|
|
9237
|
+
const migrations = db.nodes.filter((n) => n.type === "migration");
|
|
9238
|
+
const audit = [];
|
|
9239
|
+
for (const m of migrations) {
|
|
9240
|
+
const isDestructive = m.is_destructive === true;
|
|
9241
|
+
if (!isDestructive && !includeSafe) continue;
|
|
9242
|
+
const violations = [];
|
|
9243
|
+
if (isDestructive) {
|
|
9244
|
+
if (requireOrphan && m.has_orphan_check !== true) violations.push("missing_orphan_check");
|
|
9245
|
+
if (requireBackup && m.has_sidecar_backup !== true) violations.push("missing_sidecar_backup");
|
|
9246
|
+
if (requirePreFlight && m.has_pre_flight_notice !== true) violations.push("missing_pre_flight_notice");
|
|
9247
|
+
}
|
|
9248
|
+
if (!isDestructive && includeSafe && violations.length === 0) {
|
|
9249
|
+
audit.push({ id: m.id, name: m.name, timestamp: m.timestamp, is_destructive: false, status: "safe" });
|
|
9250
|
+
continue;
|
|
9251
|
+
}
|
|
9252
|
+
if (violations.length === 0 && !includeSafe) continue;
|
|
9253
|
+
audit.push({
|
|
9254
|
+
id: m.id,
|
|
9255
|
+
name: m.name,
|
|
9256
|
+
timestamp: m.timestamp,
|
|
9257
|
+
filepath: m.filepath,
|
|
9258
|
+
is_destructive: isDestructive,
|
|
9259
|
+
contains_drop_column: m.contains_drop_column === true,
|
|
9260
|
+
contains_drop_table: m.contains_drop_table === true,
|
|
9261
|
+
contains_backfill: m.contains_backfill === true,
|
|
9262
|
+
has_orphan_check: m.has_orphan_check === true,
|
|
9263
|
+
has_sidecar_backup: m.has_sidecar_backup === true,
|
|
9264
|
+
has_pre_flight_notice: m.has_pre_flight_notice === true,
|
|
9265
|
+
statement_count: m.statement_count,
|
|
9266
|
+
violations,
|
|
9267
|
+
status: violations.length === 0 ? "safe" : "unsafe"
|
|
9268
|
+
});
|
|
9269
|
+
}
|
|
9270
|
+
audit.sort((a, b) => String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? "")));
|
|
9271
|
+
const total = audit.length;
|
|
9272
|
+
const page = audit.slice(offset, offset + limit);
|
|
9273
|
+
const hasMore = offset + page.length < total;
|
|
9274
|
+
return okJson({
|
|
9275
|
+
total,
|
|
9276
|
+
returned: page.length,
|
|
9277
|
+
offset,
|
|
9278
|
+
has_more: hasMore,
|
|
9279
|
+
...hasMore ? { next_offset: offset + page.length } : {},
|
|
9280
|
+
items: page,
|
|
9281
|
+
discipline: "Layer 1 (expand-and-contract) + Layer 2 (pg_dump wrapper) + Layer 3 (in-migration SQL guards) per CLAUDE.md. This tool checks Layer 3 only."
|
|
9282
|
+
});
|
|
9283
|
+
}
|
|
9284
|
+
function handleDriftReport(args) {
|
|
9285
|
+
const __resolved = resolveOrErr(args);
|
|
9286
|
+
if ("content" in __resolved) return __resolved;
|
|
9287
|
+
const { rootDir } = __resolved;
|
|
9288
|
+
const layerArg = args.layer;
|
|
9289
|
+
const kind = args.kind ?? "all";
|
|
9290
|
+
const confidenceArg = Array.isArray(args.confidence) ? new Set(args.confidence.map((s) => String(s).toLowerCase())) : void 0;
|
|
9291
|
+
const offset = args.offset ?? 0;
|
|
9292
|
+
const limit = args.limit ?? 10;
|
|
9293
|
+
const layers = layerArg ? [layerArg] : getAvailableLayers(rootDir);
|
|
9294
|
+
const items = [];
|
|
9295
|
+
const layersWithAnyFinding = [];
|
|
9296
|
+
for (const layer of layers) {
|
|
9297
|
+
const g = readGraph(rootDir, layer);
|
|
9298
|
+
if (!g) continue;
|
|
9299
|
+
const cs = g.contradictions ?? [];
|
|
9300
|
+
const fes = g.flagged_edges ?? [];
|
|
9301
|
+
if (cs.length > 0 || fes.length > 0) layersWithAnyFinding.push(layer);
|
|
9302
|
+
if (kind === "all" || kind === "contradictions") {
|
|
9303
|
+
for (const c of cs) {
|
|
9304
|
+
items.push({ layer, kind: "contradiction", entity: c.entity, source_a: c.source_a, source_b: c.source_b, detail: c.detail });
|
|
9305
|
+
}
|
|
9306
|
+
}
|
|
9307
|
+
if (kind === "all" || kind === "flagged_edges") {
|
|
9308
|
+
for (const fe of fes) {
|
|
9309
|
+
if (confidenceArg && !confidenceArg.has(String(fe.confidence).toLowerCase())) continue;
|
|
9310
|
+
items.push({
|
|
9311
|
+
layer,
|
|
9312
|
+
kind: "flagged_edge",
|
|
9313
|
+
source: fe.source,
|
|
9314
|
+
target: fe.target,
|
|
9315
|
+
edge_type: fe.type,
|
|
9316
|
+
confidence: fe.confidence,
|
|
9317
|
+
label: fe.label
|
|
9318
|
+
});
|
|
9319
|
+
}
|
|
9320
|
+
}
|
|
9321
|
+
}
|
|
9322
|
+
const total = items.length;
|
|
9323
|
+
const page = items.slice(offset, offset + limit);
|
|
9324
|
+
const hasMore = offset + page.length < total;
|
|
9325
|
+
const parserAbsent = layers.length > 0 && layersWithAnyFinding.length === 0;
|
|
9326
|
+
return okJson({
|
|
9327
|
+
total,
|
|
9328
|
+
returned: page.length,
|
|
9329
|
+
offset,
|
|
9330
|
+
has_more: hasMore,
|
|
9331
|
+
...hasMore ? { next_offset: offset + page.length } : {},
|
|
9332
|
+
layers_scanned: layers,
|
|
9333
|
+
...parserAbsent ? { parser_warning: parserWarning(layers, "contradictions + flagged_edges", layers.includes("db") ? "sql-migrations parser and/or cross-layer parsers" : "cross-layer parsers") } : {},
|
|
9334
|
+
items: page
|
|
9335
|
+
});
|
|
9336
|
+
}
|
|
9337
|
+
function handleWhoUses(args) {
|
|
9338
|
+
const __resolved = resolveOrErr(args);
|
|
9339
|
+
if ("content" in __resolved) return __resolved;
|
|
9340
|
+
const { rootDir } = __resolved;
|
|
9341
|
+
const target = args.target;
|
|
9342
|
+
if (!target) return err("target is required");
|
|
9343
|
+
const filterType = args.cross_ref_type;
|
|
9344
|
+
const offset = args.offset ?? 0;
|
|
9345
|
+
const limit = args.limit ?? 10;
|
|
9346
|
+
const hits = [];
|
|
9347
|
+
const layersScanned = [];
|
|
9348
|
+
const layersWithCrossRefs = [];
|
|
9349
|
+
for (const layer of getAvailableLayers(rootDir)) {
|
|
9350
|
+
const g = readGraph(rootDir, layer);
|
|
9351
|
+
if (!g) continue;
|
|
9352
|
+
layersScanned.push(layer);
|
|
9353
|
+
const crs = g.cross_refs ?? [];
|
|
9354
|
+
if (crs.length > 0) layersWithCrossRefs.push(layer);
|
|
9355
|
+
for (const cr of crs) {
|
|
9356
|
+
if (cr.target !== target) continue;
|
|
9357
|
+
if (filterType && cr.type !== filterType) continue;
|
|
9358
|
+
const entry = {
|
|
9359
|
+
source_layer: layer,
|
|
9360
|
+
source: cr.source,
|
|
9361
|
+
cross_ref_type: cr.type,
|
|
9362
|
+
target_layer: cr.layer
|
|
9363
|
+
};
|
|
9364
|
+
const via = cr.via;
|
|
9365
|
+
if (Array.isArray(via)) entry.via = via;
|
|
9366
|
+
hits.push(entry);
|
|
9367
|
+
}
|
|
9368
|
+
}
|
|
9369
|
+
hits.sort((a, b) => String(a.source).localeCompare(String(b.source)));
|
|
9370
|
+
const total = hits.length;
|
|
9371
|
+
const page = hits.slice(offset, offset + limit);
|
|
9372
|
+
const hasMore = offset + page.length < total;
|
|
9373
|
+
const parserAbsent = layersScanned.length > 0 && layersWithCrossRefs.length === 0;
|
|
9374
|
+
return okJson({
|
|
9375
|
+
target,
|
|
9376
|
+
...filterType ? { cross_ref_type: filterType } : {},
|
|
9377
|
+
total,
|
|
9378
|
+
returned: page.length,
|
|
9379
|
+
offset,
|
|
9380
|
+
has_more: hasMore,
|
|
9381
|
+
...hasMore ? { next_offset: offset + page.length } : {},
|
|
9382
|
+
...parserAbsent ? { parser_warning: parserWarning(layersScanned, "cross_refs", "cross-layer references parser(s) (fetch-resolver / api-annotations / url-literal-scanner / static-ref-scanner)") } : {},
|
|
9383
|
+
items: page
|
|
9384
|
+
});
|
|
9385
|
+
}
|
|
9386
|
+
function handleTracePath(args) {
|
|
9387
|
+
const __resolved = resolveOrErr(args);
|
|
9388
|
+
if ("content" in __resolved) return __resolved;
|
|
9389
|
+
const { rootDir } = __resolved;
|
|
9390
|
+
const from = args.from;
|
|
9391
|
+
const to = args.to;
|
|
9392
|
+
if (!from || !to) return err("from and to are required");
|
|
9393
|
+
const maxHops = args.max_hops ?? 5;
|
|
9394
|
+
const maxPaths = args.max_paths ?? 10;
|
|
9395
|
+
const offset = args.offset ?? 0;
|
|
9396
|
+
const limit = args.limit ?? 10;
|
|
9397
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
9398
|
+
const layersScanned = [];
|
|
9399
|
+
const layersWithCrossRefs = [];
|
|
9400
|
+
for (const layer of getAvailableLayers(rootDir)) {
|
|
9401
|
+
const g = readGraph(rootDir, layer);
|
|
9402
|
+
if (!g) continue;
|
|
9403
|
+
layersScanned.push(layer);
|
|
9404
|
+
if ((g.cross_refs ?? []).length > 0) layersWithCrossRefs.push(layer);
|
|
9405
|
+
for (const cr of g.cross_refs ?? []) {
|
|
9406
|
+
const via = cr.via;
|
|
9407
|
+
if (Array.isArray(via) && via.length > 0) {
|
|
9408
|
+
let prev = cr.source;
|
|
9409
|
+
for (const step of via) {
|
|
9410
|
+
pushEdge(outgoing, { from: prev, to: step, type: cr.type, layer: cr.layer });
|
|
9411
|
+
prev = step;
|
|
9412
|
+
}
|
|
9413
|
+
pushEdge(outgoing, { from: prev, to: cr.target, type: cr.type, layer: cr.layer });
|
|
9414
|
+
} else {
|
|
9415
|
+
pushEdge(outgoing, { from: cr.source, to: cr.target, type: cr.type, layer: cr.layer });
|
|
9416
|
+
}
|
|
9417
|
+
}
|
|
9418
|
+
for (const e of g.edges ?? []) {
|
|
9419
|
+
pushEdge(outgoing, { from: e.source, to: e.target, type: e.type, layer });
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
9422
|
+
const paths = [];
|
|
9423
|
+
const queue = [{ nodes: [from], edges: [] }];
|
|
9424
|
+
while (queue.length > 0 && paths.length < maxPaths) {
|
|
9425
|
+
const p = queue.shift();
|
|
9426
|
+
const tail = p.nodes[p.nodes.length - 1];
|
|
9427
|
+
if (tail === to && p.nodes.length > 1) {
|
|
9428
|
+
paths.push(p);
|
|
9429
|
+
continue;
|
|
9430
|
+
}
|
|
9431
|
+
if (p.nodes.length > maxHops) continue;
|
|
9432
|
+
const next = outgoing.get(tail) ?? [];
|
|
9433
|
+
for (const e of next) {
|
|
9434
|
+
if (p.nodes.includes(e.to)) continue;
|
|
9435
|
+
queue.push({ nodes: [...p.nodes, e.to], edges: [...p.edges, e] });
|
|
9436
|
+
}
|
|
9437
|
+
}
|
|
9438
|
+
paths.sort((a, b) => a.nodes.length - b.nodes.length);
|
|
9439
|
+
const total = paths.length;
|
|
9440
|
+
const page = paths.slice(offset, offset + limit);
|
|
9441
|
+
const hasMore = offset + page.length < total;
|
|
9442
|
+
const parserAbsent = layersScanned.length > 0 && layersWithCrossRefs.length === 0;
|
|
9443
|
+
return okJson({
|
|
9444
|
+
from,
|
|
9445
|
+
to,
|
|
9446
|
+
max_hops: maxHops,
|
|
9447
|
+
max_paths: maxPaths,
|
|
9448
|
+
total,
|
|
9449
|
+
returned: page.length,
|
|
9450
|
+
offset,
|
|
9451
|
+
has_more: hasMore,
|
|
9452
|
+
...hasMore ? { next_offset: offset + page.length } : {},
|
|
9453
|
+
...total === maxPaths ? { search_capped: true, hint: "Hit max_paths cap \u2014 more paths may exist. Raise max_paths or narrow from/to." } : {},
|
|
9454
|
+
...parserAbsent ? { parser_warning: parserWarning(layersScanned, "cross_refs", "cross-layer references parser(s)") + " Cross-layer paths are unreachable without cross_refs; only same-layer paths (imports/renders/belongs_to) are visible." } : {},
|
|
9455
|
+
paths: page.map((p) => ({ hops: p.nodes.length - 1, nodes: p.nodes, edges: p.edges }))
|
|
9456
|
+
});
|
|
9457
|
+
}
|
|
9458
|
+
function pushEdge(map, e) {
|
|
9459
|
+
if (!map.has(e.from)) map.set(e.from, []);
|
|
9460
|
+
map.get(e.from).push(e);
|
|
9461
|
+
}
|
|
9462
|
+
function handleAuthCoverageReport(args) {
|
|
9463
|
+
const __resolved = resolveOrErr(args);
|
|
9464
|
+
if ("content" in __resolved) return __resolved;
|
|
9465
|
+
const { rootDir } = __resolved;
|
|
9466
|
+
const moduleFilter = args.module;
|
|
9467
|
+
const strategyFilter = args.strategy;
|
|
9468
|
+
const offset = args.offset ?? 0;
|
|
9469
|
+
const limit = args.limit ?? 10;
|
|
9470
|
+
const api = readGraph(rootDir, "api");
|
|
9471
|
+
if (!api) return err("No api graph found. Run generate_graph first.");
|
|
9472
|
+
const byStrategy = {};
|
|
9473
|
+
const byModule = {};
|
|
9474
|
+
const unauthenticated = [];
|
|
9475
|
+
const matchedEndpoints = [];
|
|
9476
|
+
for (const n of api.nodes) {
|
|
9477
|
+
const tags = n.tags;
|
|
9478
|
+
const moduleTag = tags?.module ?? "root";
|
|
9479
|
+
const auth = n.auth ?? [];
|
|
9480
|
+
const strategyKey = auth.length === 0 ? "none" : auth.slice().sort().join("+");
|
|
9481
|
+
byStrategy[strategyKey] = (byStrategy[strategyKey] ?? 0) + 1;
|
|
9482
|
+
if (!byModule[moduleTag]) byModule[moduleTag] = { total: 0, by_strategy: {} };
|
|
9483
|
+
byModule[moduleTag].total += 1;
|
|
9484
|
+
byModule[moduleTag].by_strategy[strategyKey] = (byModule[moduleTag].by_strategy[strategyKey] ?? 0) + 1;
|
|
9485
|
+
if (auth.length === 0) unauthenticated.push(n.id);
|
|
9486
|
+
if (moduleFilter && moduleTag !== moduleFilter) continue;
|
|
9487
|
+
if (strategyFilter && strategyKey !== strategyFilter) continue;
|
|
9488
|
+
matchedEndpoints.push({
|
|
9489
|
+
id: n.id,
|
|
9490
|
+
path: n.path,
|
|
9491
|
+
methods: n.methods,
|
|
9492
|
+
module: moduleTag,
|
|
9493
|
+
auth,
|
|
9494
|
+
mutates: n.mutates === true
|
|
9495
|
+
});
|
|
9496
|
+
}
|
|
9497
|
+
matchedEndpoints.sort((a, b) => String(a.path ?? a.id).localeCompare(String(b.path ?? b.id)));
|
|
9498
|
+
const total = matchedEndpoints.length;
|
|
9499
|
+
const page = matchedEndpoints.slice(offset, offset + limit);
|
|
9500
|
+
const hasMore = offset + page.length < total;
|
|
9501
|
+
return okJson({
|
|
9502
|
+
total_endpoints: api.nodes.length,
|
|
9503
|
+
by_strategy: byStrategy,
|
|
9504
|
+
unauthenticated_count: unauthenticated.length,
|
|
9505
|
+
by_module: byModule,
|
|
9506
|
+
filter: { module: moduleFilter, strategy: strategyFilter },
|
|
9507
|
+
total,
|
|
9508
|
+
returned: page.length,
|
|
9509
|
+
offset,
|
|
9510
|
+
has_more: hasMore,
|
|
9511
|
+
...hasMore ? { next_offset: offset + page.length } : {},
|
|
9512
|
+
items: page
|
|
9513
|
+
});
|
|
9514
|
+
}
|
|
9004
9515
|
function handleAuditLayer(args) {
|
|
9005
9516
|
const __resolved = resolveOrErr(args);
|
|
9006
9517
|
if ("content" in __resolved) return __resolved;
|
|
@@ -9214,6 +9725,26 @@ async function handleMessage(msg) {
|
|
|
9214
9725
|
respond(id ?? null, withFreshnessMeta(handleListNotes(args), args));
|
|
9215
9726
|
return;
|
|
9216
9727
|
}
|
|
9728
|
+
if (toolName === "migration_audit") {
|
|
9729
|
+
respond(id ?? null, withFreshnessMeta(handleMigrationAudit(args), args));
|
|
9730
|
+
return;
|
|
9731
|
+
}
|
|
9732
|
+
if (toolName === "drift_report") {
|
|
9733
|
+
respond(id ?? null, withFreshnessMeta(handleDriftReport(args), args));
|
|
9734
|
+
return;
|
|
9735
|
+
}
|
|
9736
|
+
if (toolName === "who_uses") {
|
|
9737
|
+
respond(id ?? null, withFreshnessMeta(handleWhoUses(args), args));
|
|
9738
|
+
return;
|
|
9739
|
+
}
|
|
9740
|
+
if (toolName === "trace_path") {
|
|
9741
|
+
respond(id ?? null, withFreshnessMeta(handleTracePath(args), args));
|
|
9742
|
+
return;
|
|
9743
|
+
}
|
|
9744
|
+
if (toolName === "auth_coverage_report") {
|
|
9745
|
+
respond(id ?? null, withFreshnessMeta(handleAuthCoverageReport(args), args));
|
|
9746
|
+
return;
|
|
9747
|
+
}
|
|
9217
9748
|
respondError(id ?? null, -32601, `Unknown tool: ${toolName}`);
|
|
9218
9749
|
return;
|
|
9219
9750
|
}
|
|
@@ -9371,6 +9902,32 @@ var init_graph_mcp = __esm({
|
|
|
9371
9902
|
type: "boolean",
|
|
9372
9903
|
description: "DB layer only. When true, response includes `contradictions[]` and `flagged_edges[]` arrays from the SQL migrations parser \u2014 SQL\u2194ORM schema drift findings (missing columns/tables, type mismatches, nullability drift, FKs in SQL but no ORM @relation). Default false."
|
|
9373
9904
|
},
|
|
9905
|
+
tag_predicates: {
|
|
9906
|
+
type: "array",
|
|
9907
|
+
description: 'Multi-condition AND filter over node attributes. Each predicate is { field, op, value }. `field` matches either a top-level node attribute (e.g. is_destructive, mutates, statement_count) OR a tag key (tag values take precedence when both exist). `op` \u2208 {eq, ne, gt, lt, gte, lte, exists, not_exists, in}. Example: [{field:"is_destructive",op:"eq",value:true},{field:"has_orphan_check",op:"eq",value:false}] returns destructive migrations missing an orphan check.',
|
|
9908
|
+
items: {
|
|
9909
|
+
type: "object",
|
|
9910
|
+
properties: {
|
|
9911
|
+
field: { type: "string", description: "Node attribute or tag key." },
|
|
9912
|
+
op: { type: "string", enum: ["eq", "ne", "gt", "lt", "gte", "lte", "exists", "not_exists", "in"] },
|
|
9913
|
+
value: { description: "Comparison value. Required for all ops except exists/not_exists. Pass an array for `in`." }
|
|
9914
|
+
},
|
|
9915
|
+
required: ["field", "op"]
|
|
9916
|
+
}
|
|
9917
|
+
},
|
|
9918
|
+
include_cross_refs: {
|
|
9919
|
+
type: "boolean",
|
|
9920
|
+
description: "When true, attach `cross_refs` (incoming + outgoing) to each returned node. Unlocks the cross-layer reference data (references_static, calls_api, reads_via, mutates_via, reads, mutates, references_api) that is otherwise dropped from responses. Combine with `cross_ref_type` to narrow."
|
|
9921
|
+
},
|
|
9922
|
+
cross_ref_type: {
|
|
9923
|
+
type: "string",
|
|
9924
|
+
description: "Narrow attached cross_refs by kind. Common values: calls_api, references_static, reads_via, mutates_via, reads, mutates, references_api. Only applies when include_cross_refs is true."
|
|
9925
|
+
},
|
|
9926
|
+
edge_confidence: {
|
|
9927
|
+
type: "array",
|
|
9928
|
+
description: 'Filter to surface low/medium/high-confidence flagged_edges in the response. Pass e.g. ["medium","high"] to get DYNAMIC navigation targets, FK drift, etc. Scoped to matched nodes when a node filter is in play; otherwise returns all flagged_edges at the requested confidence levels. Layer-independent.',
|
|
9929
|
+
items: { type: "string", enum: ["low", "medium", "high"] }
|
|
9930
|
+
},
|
|
9374
9931
|
offset: {
|
|
9375
9932
|
type: "number",
|
|
9376
9933
|
description: "Skip first N matched nodes (pagination). Default 0. Use next_offset from a previous response to get the next page."
|
|
@@ -9394,6 +9951,10 @@ var init_graph_mcp = __esm({
|
|
|
9394
9951
|
minimal: { type: "boolean" },
|
|
9395
9952
|
include_edges: { type: "boolean" },
|
|
9396
9953
|
include_findings: { type: "boolean" },
|
|
9954
|
+
tag_predicates: { type: "array" },
|
|
9955
|
+
include_cross_refs: { type: "boolean" },
|
|
9956
|
+
cross_ref_type: { type: "string" },
|
|
9957
|
+
edge_confidence: { type: "array" },
|
|
9397
9958
|
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9398
9959
|
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
9399
9960
|
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
@@ -9492,12 +10053,16 @@ Returns deep fields only \u2014 not structural metadata (use read_graph for that
|
|
|
9492
10053
|
fields: {
|
|
9493
10054
|
type: "array",
|
|
9494
10055
|
items: { type: "string" },
|
|
9495
|
-
description:
|
|
10056
|
+
description: `Specific deep fields to return. Options: elements, stateVars, conditions, variables, responses, params, effects, crossRefs. Omit for all. "effects" returns the file-level side-effect summary (calls, dom_writes, subscribes, timers, persists, fetches, globals); per-arrow-function effects are also attached to each entry in "variables". "crossRefs" returns this node's incoming + outgoing cross-layer references (calls_api, references_static, reads_via, mutates_via, reads, mutates, references_api) \u2014 the cross-layer linking data otherwise dropped from responses.`
|
|
9496
10057
|
},
|
|
9497
10058
|
filter: {
|
|
9498
10059
|
type: "string",
|
|
9499
10060
|
description: "Regex pattern to search WITHIN deep field values. Only returns nodes where at least one deep field matches. Searches across all string values in the requested fields (condition tests, variable inits, element tags/props, response bodies, etc.). When set, search becomes optional and node limit is raised to 50."
|
|
9500
10061
|
},
|
|
10062
|
+
cross_ref_type: {
|
|
10063
|
+
type: "string",
|
|
10064
|
+
description: 'Narrow returned crossRefs by kind (e.g. calls_api, references_static, reads_via). Only applies when "crossRefs" is included in fields (or fields is omitted).'
|
|
10065
|
+
},
|
|
9501
10066
|
case_insensitive: {
|
|
9502
10067
|
type: "boolean",
|
|
9503
10068
|
description: "Case-insensitive filter matching. Default true."
|
|
@@ -9542,18 +10107,18 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
9542
10107
|
},
|
|
9543
10108
|
{
|
|
9544
10109
|
name: "effects_index",
|
|
9545
|
-
description: 'Cross-layer inverted index over per-node side effects. Answers "who else touches X?" without re-walking every node.
|
|
10110
|
+
description: 'Cross-layer inverted index over per-node side effects. Answers "who else touches X?" without re-walking every node. Most kinds load from a precomputed effects-index.json (built when generate_graph runs); fetch_calls and api_effects are computed on demand from the ui/api graphs so they always reflect current state.\n\nUSE THIS FOR: "is mountFoo safe to instantiate twice?" (kind="singleton_risks"), "who writes DOM id moon-shadow-blur?" (kind="dom_ids", key="moon-shadow-blur"), "which files attach a window keydown listener?" (kind="window_events", key="window:keydown"), "who else writes localStorage key panchang.settings.v1?" (kind="storage_keys", key="..."), "any DOM-id collisions?" (kind="collisions"), "who calls /api/work-items?" (kind="fetch_calls", key="app/api/.../work-items/route.ts"), "which endpoints call db.user.findUnique?" (kind="api_effects", key="db.user.findUnique"). \n\nReturns vary by kind: {key:[nodeIds]} map for standard kinds; {dom_ids, storage_keys, window_events} for collisions; flat node-id list for singleton_risks; {api_endpoint|url_template: [ui_files]} for fetch_calls; {category: {item: [endpoint_ids]}} summary for api_effects (or {category, key, hits} when key is set).',
|
|
9546
10111
|
inputSchema: {
|
|
9547
10112
|
type: "object",
|
|
9548
10113
|
properties: {
|
|
9549
10114
|
kind: {
|
|
9550
10115
|
type: "string",
|
|
9551
|
-
enum: ["dom_ids", "window_events", "storage_keys", "fetch_urls", "timers", "singleton_risks", "collisions"],
|
|
9552
|
-
description:
|
|
10116
|
+
enum: ["dom_ids", "window_events", "storage_keys", "fetch_urls", "timers", "singleton_risks", "collisions", "fetch_calls", "api_effects"],
|
|
10117
|
+
description: 'Which inverted index to query. Default: collisions (most actionable signal). fetch_calls = "UI\u2192API call inventory" (resolved via ui cross_refs + raw fetch strings). api_effects = "what does each endpoint touch" inverted across all effect categories (calls / fetches / persists / subscribes / timers / dom_writes / globals).'
|
|
9553
10118
|
},
|
|
9554
10119
|
key: {
|
|
9555
10120
|
type: "string",
|
|
9556
|
-
description: 'Optional specific key to look up within the chosen kind (e.g. "moon-shadow-blur"). When omitted, returns the full {key:nodes} map for
|
|
10121
|
+
description: 'Optional specific key to look up within the chosen kind (e.g. "moon-shadow-blur" for dom_ids, "/api/work-items" for fetch_calls, "db.user.findUnique" for api_effects). When omitted, returns the full {key:nodes} map (or per-category summary for api_effects).'
|
|
9557
10122
|
},
|
|
9558
10123
|
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
9559
10124
|
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
@@ -9561,6 +10126,95 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
9561
10126
|
}
|
|
9562
10127
|
}
|
|
9563
10128
|
},
|
|
10129
|
+
{
|
|
10130
|
+
name: "migration_audit",
|
|
10131
|
+
description: `List Prisma migrations that violate the migration-safety discipline: destructive migrations missing one or more guards (orphan check, sidecar backup, pre-flight notice). Codifies the three-layer migration safety rules \u2014 derived from each migration node's extracted attributes (is_destructive, has_orphan_check, has_sidecar_backup, has_pre_flight_notice, contains_backfill, contains_drop_column, contains_drop_table).
|
|
10132
|
+
|
|
10133
|
+
USE THIS FOR: "are there unsafe migrations on this branch", "audit the migration history before deploy", "find migrations that drop columns without backup". Paginated. Returns one entry per offending migration with the specific missing guards listed.`,
|
|
10134
|
+
inputSchema: {
|
|
10135
|
+
type: "object",
|
|
10136
|
+
properties: {
|
|
10137
|
+
require_orphan_check: { type: "boolean", description: "Flag destructive migrations missing an orphan-count guard. Default true." },
|
|
10138
|
+
require_sidecar_backup: { type: "boolean", description: "Flag destructive migrations missing a sidecar backup table. Default true." },
|
|
10139
|
+
require_pre_flight_notice: { type: "boolean", description: "Flag destructive migrations missing a RAISE NOTICE pre-flight. Default true." },
|
|
10140
|
+
include_safe: { type: "boolean", description: "Also return migrations that pass all checks (no violations). Default false \u2014 by default only offenders are returned." },
|
|
10141
|
+
offset: { type: "number", description: "Pagination offset. Default 0." },
|
|
10142
|
+
limit: { type: "number", description: "Max migrations to return. Default 10." },
|
|
10143
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
10144
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
10145
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
10146
|
+
}
|
|
10147
|
+
}
|
|
10148
|
+
},
|
|
10149
|
+
{
|
|
10150
|
+
name: "drift_report",
|
|
10151
|
+
description: 'Aggregate cross-layer drift findings \u2014 contradictions (schema disagreements) + flagged_edges (low-confidence resolutions, FK drift, dynamic routes) \u2014 into one report. Generalizes the db-only `read_graph include_findings` flag to all layers.\n\nUSE THIS FOR: "is the codebase drifting", "show me SQL vs ORM mismatches", "list endpoints/components with unresolved routes", "audit schema health before refactor". Paginated. Returns items with { layer, kind, source, target, detail, confidence }.',
|
|
10152
|
+
inputSchema: {
|
|
10153
|
+
type: "object",
|
|
10154
|
+
properties: {
|
|
10155
|
+
layer: { type: "string", description: "Restrict to one layer (e.g. db, ui). Omit for all layers." },
|
|
10156
|
+
kind: { type: "string", enum: ["contradictions", "flagged_edges", "all"], description: "Which finding type to return. Default all." },
|
|
10157
|
+
confidence: { type: "array", items: { type: "string", enum: ["low", "medium", "high"] }, description: "For flagged_edges: filter by confidence levels. Default all." },
|
|
10158
|
+
offset: { type: "number", description: "Pagination offset. Default 0." },
|
|
10159
|
+
limit: { type: "number", description: "Max items to return. Default 10." },
|
|
10160
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
10161
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
10162
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
10163
|
+
}
|
|
10164
|
+
}
|
|
10165
|
+
},
|
|
10166
|
+
{
|
|
10167
|
+
name: "who_uses",
|
|
10168
|
+
description: 'List everything that references a target node via cross_refs \u2014 the cross-layer references_static / calls_api / reads_via / mutates_via / reads / mutates / references_api edges that are otherwise dropped from responses.\n\nUSE THIS FOR: "where is permission `manage_billing` used", "which UI components reference enum `WorkItemPriority`", "which endpoints touch the User table", "which files call /api/work-items". Auto-scans all layers\' cross_refs for incoming edges to the target. Paginated.',
|
|
10169
|
+
inputSchema: {
|
|
10170
|
+
type: "object",
|
|
10171
|
+
properties: {
|
|
10172
|
+
target: { type: "string", description: 'Target node id to look up (e.g. "seed:permission:manage_billing", "User", "app/api/.../work-items/route.ts", "WorkItemPriority").' },
|
|
10173
|
+
cross_ref_type: { type: "string", description: "Narrow by cross-ref kind (calls_api / references_static / reads_via / mutates_via / reads / mutates / references_api). Omit for all kinds." },
|
|
10174
|
+
offset: { type: "number", description: "Pagination offset. Default 0." },
|
|
10175
|
+
limit: { type: "number", description: "Max references to return. Default 10." },
|
|
10176
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
10177
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
10178
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
10179
|
+
},
|
|
10180
|
+
required: ["target"]
|
|
10181
|
+
}
|
|
10182
|
+
},
|
|
10183
|
+
{
|
|
10184
|
+
name: "trace_path",
|
|
10185
|
+
description: 'Find shortest paths between two nodes across layers via cross_refs (including the via[] chains that capture transitive DB access through middleware).\n\nUSE THIS FOR: "how does AdminAnalyticsPage end up reading the User table" \u2014 returns the call chain page \u2192 fetch \u2192 endpoint \u2192 middleware \u2192 db. BFS-bounded by max_hops + max_paths to keep responses tractable on hub nodes. Paginated.',
|
|
10186
|
+
inputSchema: {
|
|
10187
|
+
type: "object",
|
|
10188
|
+
properties: {
|
|
10189
|
+
from: { type: "string", description: "Source node id." },
|
|
10190
|
+
to: { type: "string", description: "Target node id." },
|
|
10191
|
+
max_hops: { type: "number", description: "Hard cap on path length. Default 5." },
|
|
10192
|
+
max_paths: { type: "number", description: "Hard cap on total paths discovered. Default 10. Once hit, BFS stops." },
|
|
10193
|
+
offset: { type: "number", description: "Pagination offset over discovered paths. Default 0." },
|
|
10194
|
+
limit: { type: "number", description: "Max paths returned per response. Default 10." },
|
|
10195
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
10196
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
10197
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
10198
|
+
},
|
|
10199
|
+
required: ["from", "to"]
|
|
10200
|
+
}
|
|
10201
|
+
},
|
|
10202
|
+
{
|
|
10203
|
+
name: "auth_coverage_report",
|
|
10204
|
+
description: 'Aggregate every API endpoint by its auth[] wrapper(s) \u2014 surfaces endpoints with empty auth, groups by module, shows which auth strategies dominate. Computed from api.json endpoint auth field (100% populated).\n\nUSE THIS FOR: "are there unauthenticated endpoints", "which auth wrappers are in use", "audit auth strategy consistency across modules". Paginated. Returns { total, by_strategy: {strategy: count}, unauthenticated: [endpoint_ids], by_module: {module: {total, by_strategy}} } plus the paginated endpoint list.',
|
|
10205
|
+
inputSchema: {
|
|
10206
|
+
type: "object",
|
|
10207
|
+
properties: {
|
|
10208
|
+
module: { type: "string", description: 'Restrict to one module tag (e.g. "orgs", "admin").' },
|
|
10209
|
+
strategy: { type: "string", description: 'Restrict to endpoints using this auth wrapper (e.g. "withAuth"). Pass "none" to list unauthenticated endpoints.' },
|
|
10210
|
+
offset: { type: "number", description: "Pagination offset over the endpoint list. Default 0." },
|
|
10211
|
+
limit: { type: "number", description: "Max endpoints to return per response. Default 10." },
|
|
10212
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
10213
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
10214
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
10215
|
+
}
|
|
10216
|
+
}
|
|
10217
|
+
},
|
|
9564
10218
|
{
|
|
9565
10219
|
name: "detect_project_stack",
|
|
9566
10220
|
description: "Detect project languages, frameworks, available parsers, and recommend parser configuration. Scans the project to identify all languages present (TypeScript, Python, Go, etc.) and reports which are supported by registered parsers vs detected-but-unsupported. Also detects frameworks (Next.js, Prisma, React, etc.), provides cross-layer detection stats (fetch calls, @api annotations, URL literals), and returns available graph layers. \n\nUse this when setting up launch-chart for a new project, reviewing parser configuration, or checking what languages are in the project.",
|