@launchsecure/launch-kit 0.0.30 → 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 +934 -865
- 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/screenshot.d.ts +19 -1
- package/dist/beacon/types/internal/screenshot.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-DdHaBFYO.js} +1 -1
- package/dist/deck-client/assets/{arc-h-ifqmNR.js → arc-D98e_18X.js} +1 -1
- package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-C9dITSPv.js → architectureDiagram-Q4EWVU46-DNFZzh-4.js} +1 -1
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BHuJT34t.js → blockDiagram-DXYQGD6D-DeQvGUdX.js} +1 -1
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-CpvMGtDG.js → c4Diagram-AHTNJAMY-B6ekZf1n.js} +1 -1
- package/dist/deck-client/assets/channel-DmR7Tyyt.js +1 -0
- package/dist/deck-client/assets/{chunk-4BX2VUAB-B6md1VIm.js → chunk-4BX2VUAB-9aDWymq2.js} +1 -1
- package/dist/deck-client/assets/{chunk-4TB4RGXK-BmEnX8ik.js → chunk-4TB4RGXK-DtKQqaI7.js} +1 -1
- package/dist/deck-client/assets/{chunk-55IACEB6-BZPUyZAZ.js → chunk-55IACEB6-COy9hEae.js} +1 -1
- package/dist/deck-client/assets/{chunk-EDXVE4YY-BWwNUK-l.js → chunk-EDXVE4YY-D_f861An.js} +1 -1
- package/dist/deck-client/assets/{chunk-FMBD7UC4-o7gSppGI.js → chunk-FMBD7UC4-CmuA5UKn.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-C4KoTL5p.js → chunk-OYMX7WX6-vT8z8D-0.js} +1 -1
- package/dist/deck-client/assets/{chunk-QZHKN3VN-jkf68sDs.js → chunk-QZHKN3VN-CTlwwg-R.js} +1 -1
- package/dist/deck-client/assets/{chunk-YZCP3GAM-Cd4yBE7o.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-DeGFUgAV.js → cose-bilkent-S5V4N54A-DBB2J2nL.js} +1 -1
- package/dist/deck-client/assets/{dagre-KV5264BT-ekcYJuUV.js → dagre-KV5264BT-DxDTYbKl.js} +1 -1
- package/dist/deck-client/assets/{diagram-5BDNPKRD-YHPk4rV2.js → diagram-5BDNPKRD-DByWrWd1.js} +1 -1
- package/dist/deck-client/assets/{diagram-G4DWMVQ6-DM-JCd_B.js → diagram-G4DWMVQ6-B8B6ddMq.js} +1 -1
- package/dist/deck-client/assets/{diagram-MMDJMWI5-l5FK1ybk.js → diagram-MMDJMWI5-BMUZ2PWK.js} +1 -1
- package/dist/deck-client/assets/{diagram-TYMM5635-CIN4_1-j.js → diagram-TYMM5635-Bk9e8BB-.js} +1 -1
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-MyinSkEl.js → erDiagram-SMLLAGMA-DcOSwSol.js} +1 -1
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-Dk8nn42x.js → flowDiagram-DWJPFMVM-DI-4BR0F.js} +1 -1
- package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-BU1ihicu.js → ganttDiagram-T4ZO3ILL-BeZuXBoU.js} +1 -1
- package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-BjsTL13C.js → gitGraphDiagram-UUTBAWPF-Bcki__f-.js} +1 -1
- package/dist/deck-client/assets/{graph-DJmh-xi7.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-Dxvy_RB4.js → infoDiagram-42DDH7IO-CReN1nFN.js} +1 -1
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DPOaNF1l.js → ishikawaDiagram-UXIWVN3A-CDF_VLN_.js} +1 -1
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-DMew3K5c.js → journeyDiagram-VCZTEJTY-DwgGrNVB.js} +1 -1
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-csciJFuk.js → kanban-definition-6JOO6SKY-DB_zohh5.js} +1 -1
- package/dist/deck-client/assets/{layout-Dg4yyms2.js → layout-DFfX1O3z.js} +1 -1
- package/dist/deck-client/assets/{linear-BA3zU6gq.js → linear-CtKb4EXj.js} +1 -1
- package/dist/deck-client/assets/{min-lz-Ird-p.js → min-DCRRwUZv.js} +1 -1
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-CCEN8OQV.js → mindmap-definition-QFDTVHPH-D0QBOiFe.js} +1 -1
- package/dist/deck-client/assets/{pieDiagram-DEJITSTG-DM6n1HY7.js → pieDiagram-DEJITSTG-CD-EV5WB.js} +1 -1
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-_ULoR66n.js → quadrantDiagram-34T5L4WZ-B-JXZ8xI.js} +1 -1
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-BuwJs7Tn.js → requirementDiagram-MS252O5E-D2_OK5Dp.js} +1 -1
- package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-BEsuzkW4.js → sankeyDiagram-XADWPNL6-BbBJqVSC.js} +1 -1
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-CP2H0YWf.js → sequenceDiagram-FGHM5R23-Db8A-Rkk.js} +1 -1
- package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-B5Gw_NNL.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-DsoYydQa.js → timeline-definition-GMOUNBTQ-BRkr6T4w.js} +1 -1
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-Dz8JT_ob.js → vennDiagram-DHZGUBPP-d0rsTqFo.js} +1 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-2t7cMqdS.js +162 -0
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-DN1LJMB1.js → wardleyDiagram-NUSXRM2D-DzboAsHh.js} +1 -1
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-nb0oSfrQ.js → xychartDiagram-5P7HB3ND-CgTP9u2V.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 +141 -21
- package/dist/server/deck-serve.js +141 -21
- package/dist/server/fb-wizard.js +0 -0
- package/dist/server/graph-mcp-entry.js +666 -12
- package/dist/server/init-entry.js +15 -9
- 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.md → diagram/SKILL.md} +27 -9
- 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/dist/deck-client/assets/wardley-RL74JXVD-DGHQ_Ijv.js +0 -162
- 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
package/dist/server/cli.js
CHANGED
|
@@ -33902,6 +33902,32 @@ var TOOLS = [
|
|
|
33902
33902
|
type: "boolean",
|
|
33903
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
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
|
+
},
|
|
33905
33931
|
offset: {
|
|
33906
33932
|
type: "number",
|
|
33907
33933
|
description: "Skip first N matched nodes (pagination). Default 0. Use next_offset from a previous response to get the next page."
|
|
@@ -33925,6 +33951,10 @@ var TOOLS = [
|
|
|
33925
33951
|
minimal: { type: "boolean" },
|
|
33926
33952
|
include_edges: { type: "boolean" },
|
|
33927
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" },
|
|
33928
33958
|
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
33929
33959
|
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
33930
33960
|
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
@@ -34023,12 +34053,16 @@ Returns deep fields only \u2014 not structural metadata (use read_graph for that
|
|
|
34023
34053
|
fields: {
|
|
34024
34054
|
type: "array",
|
|
34025
34055
|
items: { type: "string" },
|
|
34026
|
-
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.`
|
|
34027
34057
|
},
|
|
34028
34058
|
filter: {
|
|
34029
34059
|
type: "string",
|
|
34030
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."
|
|
34031
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
|
+
},
|
|
34032
34066
|
case_insensitive: {
|
|
34033
34067
|
type: "boolean",
|
|
34034
34068
|
description: "Case-insensitive filter matching. Default true."
|
|
@@ -34073,18 +34107,18 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
34073
34107
|
},
|
|
34074
34108
|
{
|
|
34075
34109
|
name: "effects_index",
|
|
34076
|
-
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).',
|
|
34077
34111
|
inputSchema: {
|
|
34078
34112
|
type: "object",
|
|
34079
34113
|
properties: {
|
|
34080
34114
|
kind: {
|
|
34081
34115
|
type: "string",
|
|
34082
|
-
enum: ["dom_ids", "window_events", "storage_keys", "fetch_urls", "timers", "singleton_risks", "collisions"],
|
|
34083
|
-
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).'
|
|
34084
34118
|
},
|
|
34085
34119
|
key: {
|
|
34086
34120
|
type: "string",
|
|
34087
|
-
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).'
|
|
34088
34122
|
},
|
|
34089
34123
|
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
34090
34124
|
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
@@ -34092,6 +34126,95 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
34092
34126
|
}
|
|
34093
34127
|
}
|
|
34094
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 }
|
|
34215
|
+
}
|
|
34216
|
+
}
|
|
34217
|
+
},
|
|
34095
34218
|
{
|
|
34096
34219
|
name: "detect_project_stack",
|
|
34097
34220
|
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.",
|
|
@@ -34764,9 +34887,13 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
34764
34887
|
const minimal = args.minimal ?? layerIsDb;
|
|
34765
34888
|
const includeEdges = args.include_edges;
|
|
34766
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;
|
|
34767
34894
|
const offset = args.offset ?? 0;
|
|
34768
34895
|
const limit = args.limit;
|
|
34769
|
-
const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue);
|
|
34896
|
+
const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue || tagPredicates && tagPredicates.length > 0);
|
|
34770
34897
|
if (layer) {
|
|
34771
34898
|
const available = getAvailableLayers(rootDir);
|
|
34772
34899
|
if (available.length > 0 && !available.includes(layer)) {
|
|
@@ -34817,6 +34944,14 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
34817
34944
|
result2.contradictions = graph.contradictions ?? [];
|
|
34818
34945
|
result2.flagged_edges = graph.flagged_edges ?? [];
|
|
34819
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
|
+
});
|
|
34820
34955
|
return result2;
|
|
34821
34956
|
}
|
|
34822
34957
|
if (!hasFilter) {
|
|
@@ -34837,6 +34972,11 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
34837
34972
|
const nodeTags = n.tags;
|
|
34838
34973
|
if (module_ && nodeTags?.module !== module_) return false;
|
|
34839
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
|
+
}
|
|
34840
34980
|
return true;
|
|
34841
34981
|
});
|
|
34842
34982
|
if (matched.length === 0) {
|
|
@@ -34884,8 +35024,89 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
34884
35024
|
result.contradictions = graph.contradictions ?? [];
|
|
34885
35025
|
result.flagged_edges = graph.flagged_edges ?? [];
|
|
34886
35026
|
}
|
|
35027
|
+
attachCrossRefsAndFlagged(result, graph, returnedIds, {
|
|
35028
|
+
includeCrossRefs,
|
|
35029
|
+
crossRefType,
|
|
35030
|
+
edgeConfidence,
|
|
35031
|
+
scopeToIds: true,
|
|
35032
|
+
layer
|
|
35033
|
+
});
|
|
34887
35034
|
return result;
|
|
34888
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
|
+
}
|
|
34889
35110
|
function runReadGraphQuery(rootDir, args) {
|
|
34890
35111
|
const raw = runReadGraphQueryRaw(rootDir, args);
|
|
34891
35112
|
return compactResult(raw);
|
|
@@ -34996,8 +35217,23 @@ function handleInspectNode(args) {
|
|
|
34996
35217
|
} else {
|
|
34997
35218
|
matched = graph.nodes;
|
|
34998
35219
|
}
|
|
34999
|
-
const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params", "effects", "ui_labels", "notes"];
|
|
35220
|
+
const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params", "effects", "ui_labels", "notes", "crossRefs"];
|
|
35000
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
|
+
}
|
|
35001
35237
|
let filterRegex = null;
|
|
35002
35238
|
if (filter) {
|
|
35003
35239
|
try {
|
|
@@ -35020,7 +35256,14 @@ function handleInspectNode(args) {
|
|
|
35020
35256
|
const deep = { id: node.id, name: node.name, type: node.type };
|
|
35021
35257
|
let hasData = false;
|
|
35022
35258
|
for (const field of requestedFields) {
|
|
35023
|
-
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) {
|
|
35024
35267
|
deep[field] = node[field];
|
|
35025
35268
|
hasData = true;
|
|
35026
35269
|
}
|
|
@@ -35044,10 +35287,14 @@ function handleInspectNode(args) {
|
|
|
35044
35287
|
const hint = filter ? `No nodes with deep fields matching /${filter}/${caseInsensitive ? "i" : ""} in ${layer} layer.` : `No nodes matching "${search}" in ${layer} layer.`;
|
|
35045
35288
|
return err(hint);
|
|
35046
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)") } : {};
|
|
35047
35293
|
return okJson({
|
|
35048
35294
|
layer,
|
|
35049
35295
|
matched: results.length,
|
|
35050
35296
|
...results.length >= maxResults ? { truncated: true, hint: `Showing first ${maxResults} matches. Narrow with search param.` } : {},
|
|
35297
|
+
...extraWarning,
|
|
35051
35298
|
nodes: results
|
|
35052
35299
|
});
|
|
35053
35300
|
}
|
|
@@ -35253,13 +35500,19 @@ function handleEffectsIndex(args) {
|
|
|
35253
35500
|
const __resolved = resolveOrErr(args);
|
|
35254
35501
|
if ("content" in __resolved) return __resolved;
|
|
35255
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
|
+
}
|
|
35256
35512
|
const idx = readEffectsIndex(rootDir);
|
|
35257
35513
|
if (!idx) {
|
|
35258
35514
|
return err("No effects-index.json found. Run generate_graph first (or wait for the file-watcher to fire).");
|
|
35259
35515
|
}
|
|
35260
|
-
const kind = args.kind ?? "collisions";
|
|
35261
|
-
const key = args.key;
|
|
35262
|
-
const MAX_KEYS = 200;
|
|
35263
35516
|
if (kind === "singleton_risks") {
|
|
35264
35517
|
return okJson({ kind, count: idx.singleton_risks.length, nodes: idx.singleton_risks });
|
|
35265
35518
|
}
|
|
@@ -35273,7 +35526,7 @@ function handleEffectsIndex(args) {
|
|
|
35273
35526
|
}
|
|
35274
35527
|
const map = idx[kind];
|
|
35275
35528
|
if (!map || typeof map !== "object") {
|
|
35276
|
-
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.`);
|
|
35277
35530
|
}
|
|
35278
35531
|
if (key) {
|
|
35279
35532
|
const nodes = map[key] ?? [];
|
|
@@ -35291,6 +35544,97 @@ function handleEffectsIndex(args) {
|
|
|
35291
35544
|
results
|
|
35292
35545
|
});
|
|
35293
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
|
+
}
|
|
35294
35638
|
function handleChartServerStatus() {
|
|
35295
35639
|
const rootDir = process.cwd();
|
|
35296
35640
|
const lock = getLiveLock(rootDir);
|
|
@@ -35404,6 +35748,296 @@ function handleRemoveTag(args) {
|
|
|
35404
35748
|
removeTag(rootDir, nodeId, key);
|
|
35405
35749
|
return okJson({ ok: true, node_id: nodeId, removed_key: key });
|
|
35406
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
|
+
}
|
|
35407
36041
|
function handleAuditLayer(args) {
|
|
35408
36042
|
const __resolved = resolveOrErr(args);
|
|
35409
36043
|
if ("content" in __resolved) return __resolved;
|
|
@@ -35617,6 +36251,26 @@ async function handleMessage(msg) {
|
|
|
35617
36251
|
respond(id ?? null, withFreshnessMeta(handleListNotes(args), args));
|
|
35618
36252
|
return;
|
|
35619
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
|
+
}
|
|
35620
36274
|
respondError(id ?? null, -32601, `Unknown tool: ${toolName}`);
|
|
35621
36275
|
return;
|
|
35622
36276
|
}
|