@kage-core/kage-graph-mcp 1.1.27 → 1.1.29
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/README.md +1 -1
- package/dist/kernel.js +138 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Restart your agent once after setup so MCP tools reload.
|
|
|
37
37
|
- repo-local memory for decisions, runbooks, bug fixes, gotchas, conventions,
|
|
38
38
|
and code explanations
|
|
39
39
|
- a code graph for files, symbols, imports, calls, routes, tests, and packages,
|
|
40
|
-
including generic call/test signals
|
|
40
|
+
including generic call/test signals and mixed-language framework routes
|
|
41
41
|
- memory-code links so project knowledge points at the code it affects
|
|
42
42
|
- decision intelligence for why-memory coverage, stale/weak packets, and
|
|
43
43
|
important files that still lack linked repo knowledge
|
package/dist/kernel.js
CHANGED
|
@@ -2830,11 +2830,62 @@ function extractGenericCalls(path, text, symbols, symbolByName) {
|
|
|
2830
2830
|
}
|
|
2831
2831
|
return calls.sort((a, b) => a.line - b.line || a.to_symbol.localeCompare(b.to_symbol));
|
|
2832
2832
|
}
|
|
2833
|
+
function offsetForLine(text, oneBasedLine) {
|
|
2834
|
+
if (oneBasedLine <= 1)
|
|
2835
|
+
return 0;
|
|
2836
|
+
const lines = text.split(/\r?\n/).slice(0, oneBasedLine - 1);
|
|
2837
|
+
return lines.join("\n").length + (lines.length ? 1 : 0);
|
|
2838
|
+
}
|
|
2839
|
+
function normalizeWebRoutePath(routePath) {
|
|
2840
|
+
let cleaned = routePath
|
|
2841
|
+
.trim()
|
|
2842
|
+
.replace(/^r(["'`])|(["'`])$/g, "")
|
|
2843
|
+
.replace(/^['"`]|['"`]$/g, "")
|
|
2844
|
+
.replace(/\\/g, "")
|
|
2845
|
+
.replace(/^\^/, "")
|
|
2846
|
+
.replace(/\$$/, "")
|
|
2847
|
+
.replace(/\{([A-Za-z_][\w]*)\}/g, ":$1")
|
|
2848
|
+
.replace(/<(?:(?:int|str|slug|uuid|path):)?([A-Za-z_][\w]*)>/g, ":$1")
|
|
2849
|
+
.replace(/\/+/g, "/");
|
|
2850
|
+
if (!cleaned.startsWith("/"))
|
|
2851
|
+
cleaned = `/${cleaned}`;
|
|
2852
|
+
if (cleaned.length > 1)
|
|
2853
|
+
cleaned = cleaned.replace(/\/$/, "");
|
|
2854
|
+
return cleaned || "/";
|
|
2855
|
+
}
|
|
2856
|
+
function pythonRouteFramework(text) {
|
|
2857
|
+
return /\bfrom\s+flask\s+import\b|\bimport\s+flask\b|\bFlask\s*\(/.test(text) ? "flask" : "fastapi";
|
|
2858
|
+
}
|
|
2859
|
+
function parsePythonMethodList(value) {
|
|
2860
|
+
if (!value)
|
|
2861
|
+
return ["GET"];
|
|
2862
|
+
const methods = [...value.matchAll(/["']([A-Za-z]+)["']/g)].map((match) => match[1].toUpperCase());
|
|
2863
|
+
return methods.length ? unique(methods) : ["GET"];
|
|
2864
|
+
}
|
|
2865
|
+
const SPRING_ROUTE_METHODS = {
|
|
2866
|
+
GetMapping: "GET",
|
|
2867
|
+
PostMapping: "POST",
|
|
2868
|
+
PutMapping: "PUT",
|
|
2869
|
+
PatchMapping: "PATCH",
|
|
2870
|
+
DeleteMapping: "DELETE",
|
|
2871
|
+
RequestMapping: "ANY",
|
|
2872
|
+
};
|
|
2873
|
+
const ASPNET_ROUTE_METHODS = {
|
|
2874
|
+
Get: "GET",
|
|
2875
|
+
Post: "POST",
|
|
2876
|
+
Put: "PUT",
|
|
2877
|
+
Patch: "PATCH",
|
|
2878
|
+
Delete: "DELETE",
|
|
2879
|
+
};
|
|
2880
|
+
function routeHandlerNearLine(lines, startIndex, pattern) {
|
|
2881
|
+
const handlerLine = lines.slice(startIndex + 1, Math.min(lines.length, startIndex + 8)).find((candidate) => pattern.test(candidate));
|
|
2882
|
+
return handlerLine?.match(pattern)?.[1] ?? null;
|
|
2883
|
+
}
|
|
2833
2884
|
function extractRoutes(path, text, symbols) {
|
|
2834
2885
|
const routes = [];
|
|
2835
2886
|
const addRoute = (method, routePath, offset, framework, handler = null) => {
|
|
2836
2887
|
const line = lineForOffset(text, offset);
|
|
2837
|
-
const cleanRoutePath = routePath
|
|
2888
|
+
const cleanRoutePath = normalizeWebRoutePath(routePath);
|
|
2838
2889
|
const containing = handler ? symbols.find((symbol) => symbol.path === path && symbol.name === handler) : symbolAtLine(symbols, path, line);
|
|
2839
2890
|
routes.push({
|
|
2840
2891
|
id: routeId(path, method, cleanRoutePath, line),
|
|
@@ -2856,6 +2907,92 @@ function extractRoutes(path, text, symbols) {
|
|
|
2856
2907
|
const routeMatch = text.match(new RegExp(`const\\s+${match[2]}Match\\s*=\\s*url\\.pathname\\.match\\(\\s*/\\^\\\\/([^/]+)[^/]*`));
|
|
2857
2908
|
addRoute(match[1], routeMatch ? `/${routeMatch[1]}/:id` : "/:dynamic", match.index ?? 0, "node-http");
|
|
2858
2909
|
}
|
|
2910
|
+
if (extensionOf(path) === ".py") {
|
|
2911
|
+
const lines = text.split(/\r?\n/);
|
|
2912
|
+
const framework = pythonRouteFramework(text);
|
|
2913
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2914
|
+
const line = lines[index];
|
|
2915
|
+
const decorator = line.match(/^\s*@(?:\w+\.)?(get|post|put|patch|delete|options|head)\s*\(\s*["']([^"']+)["']/i);
|
|
2916
|
+
if (decorator) {
|
|
2917
|
+
const handlerLine = lines.slice(index + 1, Math.min(lines.length, index + 7)).find((candidate) => /^\s*(?:async\s+)?def\s+[A-Za-z_][\w]*\s*\(/.test(candidate));
|
|
2918
|
+
const handler = handlerLine?.match(/^\s*(?:async\s+)?def\s+([A-Za-z_][\w]*)\s*\(/)?.[1] ?? null;
|
|
2919
|
+
addRoute(decorator[1].toUpperCase(), decorator[2], offsetForLine(text, index + 1), framework, handler);
|
|
2920
|
+
continue;
|
|
2921
|
+
}
|
|
2922
|
+
const flaskRoute = line.match(/^\s*@(?:\w+\.)?route\s*\(\s*["']([^"']+)["']/i);
|
|
2923
|
+
if (flaskRoute) {
|
|
2924
|
+
const handlerLine = lines.slice(index + 1, Math.min(lines.length, index + 7)).find((candidate) => /^\s*(?:async\s+)?def\s+[A-Za-z_][\w]*\s*\(/.test(candidate));
|
|
2925
|
+
const handler = handlerLine?.match(/^\s*(?:async\s+)?def\s+([A-Za-z_][\w]*)\s*\(/)?.[1] ?? null;
|
|
2926
|
+
const methods = line.match(/methods\s*=\s*\[([^\]]+)\]/i)?.[1];
|
|
2927
|
+
for (const method of parsePythonMethodList(methods))
|
|
2928
|
+
addRoute(method, flaskRoute[1], offsetForLine(text, index + 1), "flask", handler);
|
|
2929
|
+
continue;
|
|
2930
|
+
}
|
|
2931
|
+
const djangoPath = line.match(/\b(?:path|re_path)\s*\(\s*r?["']([^"']+)["']\s*,\s*([A-Za-z_][\w.]+)/);
|
|
2932
|
+
if (djangoPath) {
|
|
2933
|
+
const handler = djangoPath[2].split(".").pop() ?? null;
|
|
2934
|
+
addRoute("ANY", djangoPath[1], offsetForLine(text, index + 1), "django", handler);
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
if (extensionOf(path) === ".rb") {
|
|
2939
|
+
for (const match of text.matchAll(/\b(get|post|put|patch|delete)\s+["']([^"']+)["']/gi)) {
|
|
2940
|
+
addRoute(match[1].toUpperCase(), match[2], match.index ?? 0, "rails");
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
if (extensionOf(path) === ".php") {
|
|
2944
|
+
for (const match of text.matchAll(/\bRoute::(get|post|put|patch|delete|options|any)\s*\(\s*["']([^"']+)["']/gi)) {
|
|
2945
|
+
addRoute(match[1].toUpperCase(), match[2], match.index ?? 0, "laravel");
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
if ([".java", ".kt"].includes(extensionOf(path))) {
|
|
2949
|
+
const lines = text.split(/\r?\n/);
|
|
2950
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2951
|
+
const line = lines[index];
|
|
2952
|
+
const mapping = line.match(/@(GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping|RequestMapping)\s*(?:\(\s*(?:value\s*=\s*)?["']([^"']+)["'])?/);
|
|
2953
|
+
if (!mapping || !mapping[2])
|
|
2954
|
+
continue;
|
|
2955
|
+
let method = SPRING_ROUTE_METHODS[mapping[1]] ?? "ANY";
|
|
2956
|
+
if (mapping[1] === "RequestMapping") {
|
|
2957
|
+
const explicit = line.match(/RequestMethod\.(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)/);
|
|
2958
|
+
if (explicit)
|
|
2959
|
+
method = explicit[1];
|
|
2960
|
+
}
|
|
2961
|
+
const handler = routeHandlerNearLine(lines, index, /^\s*(?:public|private|protected)?\s*[\w<>\[\], ?]+\s+([A-Za-z_][\w]*)\s*\(/);
|
|
2962
|
+
addRoute(method, mapping[2], offsetForLine(text, index + 1), "spring", handler);
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
if (extensionOf(path) === ".go") {
|
|
2966
|
+
for (const match of text.matchAll(/\b[A-Za-z_][\w]*\.(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*\(\s*["`]([^"`]+)["`]\s*,\s*([A-Za-z_][\w.]*)?/g)) {
|
|
2967
|
+
addRoute(match[1], match[2], match.index ?? 0, "go-router", match[3]?.split(".").pop() ?? null);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
if (extensionOf(path) === ".rs") {
|
|
2971
|
+
for (const match of text.matchAll(/\.route\s*\(\s*["']([^"']+)["']\s*,\s*(get|post|put|patch|delete|options|head)\s*\(\s*([A-Za-z_][\w:]*)?/gi)) {
|
|
2972
|
+
addRoute(match[2].toUpperCase(), match[1], match.index ?? 0, "rust-router", match[3]?.split("::").pop() ?? null);
|
|
2973
|
+
}
|
|
2974
|
+
const lines = text.split(/\r?\n/);
|
|
2975
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2976
|
+
const attr = lines[index].match(/#\[(get|post|put|patch|delete|options|head)\(\s*["']([^"']+)["']\s*\)\]/i);
|
|
2977
|
+
if (!attr)
|
|
2978
|
+
continue;
|
|
2979
|
+
const handler = routeHandlerNearLine(lines, index, /^\s*(?:pub\s+)?(?:async\s+)?fn\s+([A-Za-z_][\w]*)\s*\(/);
|
|
2980
|
+
addRoute(attr[1].toUpperCase(), attr[2], offsetForLine(text, index + 1), "rust-router", handler);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
if (extensionOf(path) === ".cs") {
|
|
2984
|
+
for (const match of text.matchAll(/\bMap(Get|Post|Put|Patch|Delete)\s*\(\s*["']([^"']+)["']\s*,\s*([A-Za-z_][\w.]*)?/g)) {
|
|
2985
|
+
addRoute(ASPNET_ROUTE_METHODS[match[1]] ?? match[1].toUpperCase(), match[2], match.index ?? 0, "aspnet", match[3]?.split(".").pop() ?? null);
|
|
2986
|
+
}
|
|
2987
|
+
const lines = text.split(/\r?\n/);
|
|
2988
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2989
|
+
const attr = lines[index].match(/\[\s*Http(Get|Post|Put|Patch|Delete)?\s*\(\s*["']([^"']+)["']\s*\)\s*\]/);
|
|
2990
|
+
if (!attr)
|
|
2991
|
+
continue;
|
|
2992
|
+
const handler = routeHandlerNearLine(lines, index, /^\s*(?:public|private|protected|internal)?\s*(?:async\s+)?[\w<>\[\], ?]+\s+([A-Za-z_][\w]*)\s*\(/);
|
|
2993
|
+
addRoute(attr[1] ? ASPNET_ROUTE_METHODS[attr[1]] ?? attr[1].toUpperCase() : "ANY", attr[2], offsetForLine(text, index + 1), "aspnet", handler);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2859
2996
|
if (/app\/api\//.test(path)) {
|
|
2860
2997
|
for (const symbol of symbols.filter((symbol) => symbol.path === path && symbol.export && ["GET", "POST", "PUT", "PATCH", "DELETE"].includes(symbol.name))) {
|
|
2861
2998
|
const apiPath = `/${path.replace(/^.*app\/api\//, "").replace(/\/route\.[cm]?[jt]sx?$/, "").replace(/\[([^\]]+)\]/g, ":$1")}`;
|