codemap-ai 3.2.0 → 3.4.0
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/cli.js +1191 -31
- package/dist/cli.js.map +1 -1
- package/dist/{flow-server-UQEOUP2C.js → flow-server-4XSR5P4N.js} +2 -2
- package/dist/{flow-server-UQEOUP2C.js.map → flow-server-4XSR5P4N.js.map} +1 -1
- package/dist/index.js +424 -17
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
|
@@ -29,7 +29,7 @@ function getStorage() {
|
|
|
29
29
|
var server = new Server(
|
|
30
30
|
{
|
|
31
31
|
name: "codemap-flow",
|
|
32
|
-
version: "3.
|
|
32
|
+
version: "3.4.0"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
capabilities: {
|
|
@@ -243,4 +243,4 @@ server.connect(transport).catch((error) => {
|
|
|
243
243
|
console.error("Failed to start MCP server:", error);
|
|
244
244
|
process.exit(1);
|
|
245
245
|
});
|
|
246
|
-
//# sourceMappingURL=flow-server-
|
|
246
|
+
//# sourceMappingURL=flow-server-4XSR5P4N.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp/flow-server.ts"],"sourcesContent":["/**\n * MCP Server for CodeMap Flow Edition\n * Provides 3 focused tools: impact analysis, flow tracing, and caller lookup\n */\n\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { FlowStorage } from \"../flow/storage.js\";\nimport { resolve } from \"path\";\nimport { existsSync } from \"fs\";\n\n// Get config from environment\nconst DB_PATH = process.env.CODEMAP_DB_PATH || \".codemap/graph.db\";\nconst PROJECT_ROOT = process.env.CODEMAP_PROJECT_ROOT || process.cwd();\n\nlet storage: FlowStorage | null = null;\n\nfunction getStorage(): FlowStorage {\n if (!storage) {\n const dbPath = resolve(PROJECT_ROOT, DB_PATH);\n if (!existsSync(dbPath)) {\n throw new Error(`CodeMap database not found at ${dbPath}. Run 'codemap index' first.`);\n }\n storage = new FlowStorage(dbPath);\n }\n return storage;\n}\n\nconst server = new Server(\n {\n name: \"codemap-flow\",\n version: \"3.2.0\",\n },\n {\n capabilities: {\n tools: {},\n },\n }\n);\n\n// ============ Tools ============\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: [\n {\n name: \"codemap_impact\",\n description: \"Analyze what would break if you change a function/class. Shows all callers and affected files with risk assessment.\",\n inputSchema: {\n type: \"object\",\n properties: {\n target: {\n type: \"string\",\n description: \"Function or class name to analyze\",\n },\n depth: {\n type: \"number\",\n description: \"How many levels deep to analyze (default: 3)\",\n default: 3,\n },\n },\n required: [\"target\"],\n },\n },\n {\n name: \"codemap_trace_flow\",\n description: \"Trace the execution flow between two functions. Shows the complete call path from source to target.\",\n inputSchema: {\n type: \"object\",\n properties: {\n from: {\n type: \"string\",\n description: \"Starting function name\",\n },\n to: {\n type: \"string\",\n description: \"Target function name\",\n },\n },\n required: [\"from\", \"to\"],\n },\n },\n {\n name: \"codemap_callers\",\n description: \"Find all functions that call a specific function. Shows where and how a function is used.\",\n inputSchema: {\n type: \"object\",\n properties: {\n function: {\n type: \"string\",\n description: \"Function name to find callers for\",\n },\n },\n required: [\"function\"],\n },\n },\n ],\n };\n});\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n const db = getStorage();\n\n try {\n switch (name) {\n case \"codemap_impact\": {\n const target = args?.target as string;\n const depth = (args?.depth as number) || 3;\n\n // Find the target node(s)\n const nodes = db.searchNodesByName(target);\n\n if (nodes.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No function or class found with name \"${target}\"`,\n },\n ],\n };\n }\n\n const targetNode = nodes[0];\n\n // Get impacted nodes\n const impacted = db.getImpactedNodes(targetNode.id, depth);\n\n // Get unique files\n const affectedFiles = new Set(impacted.map((n) => n.filePath));\n\n // Assess risk\n const directCallers = db.getResolvedCallers(targetNode.id);\n let risk = \"LOW\";\n if (directCallers.length > 10) risk = \"HIGH\";\n else if (directCallers.length > 5) risk = \"MEDIUM\";\n\n const response = `# Impact Analysis: ${targetNode.name}\n\n**Location:** ${targetNode.filePath}:${targetNode.startLine}\n**Type:** ${targetNode.type}\n**Risk Level:** ${risk}\n\n## Direct Callers (${directCallers.length})\n${directCallers.slice(0, 10).map((c) => `- ${c.name} in ${c.filePath}:${c.startLine}`).join(\"\\n\")}\n${directCallers.length > 10 ? `\\n... and ${directCallers.length - 10} more` : \"\"}\n\n## Total Impact\n- **Affected functions:** ${impacted.length}\n- **Affected files:** ${affectedFiles.size}\n\n## Affected Files\n${[...affectedFiles].slice(0, 20).map((f) => `- ${f}`).join(\"\\n\")}\n${affectedFiles.size > 20 ? `\\n... and ${affectedFiles.size - 20} more` : \"\"}\n\n⚠️ **Recommendation:** ${risk === \"HIGH\" ? \"High-risk change. Review all callers before modifying.\" : risk === \"MEDIUM\" ? \"Moderate risk. Test affected files after changes.\" : \"Low risk. Changes unlikely to cause wide impact.\"}\n`;\n\n return {\n content: [{ type: \"text\", text: response }],\n };\n }\n\n case \"codemap_trace_flow\": {\n const from = args?.from as string;\n const to = args?.to as string;\n\n // Find source and target nodes\n const sourceNodes = db.searchNodesByName(from);\n const targetNodes = db.searchNodesByName(to);\n\n if (sourceNodes.length === 0) {\n return {\n content: [{ type: \"text\", text: `Source function \"${from}\" not found` }],\n };\n }\n\n if (targetNodes.length === 0) {\n return {\n content: [{ type: \"text\", text: `Target function \"${to}\" not found` }],\n };\n }\n\n const sourceNode = sourceNodes[0];\n const targetNode = targetNodes[0];\n\n // Trace path\n const path = db.traceCallPath(sourceNode.id, targetNode.id);\n\n if (!path) {\n return {\n content: [\n {\n type: \"text\",\n text: `No call path found from \"${from}\" to \"${to}\". They may not be connected.`,\n },\n ],\n };\n }\n\n const response = `# Execution Flow: ${from} → ${to}\n\n${path.nodes\n .map(\n (n, i) =>\n `${i + 1}. **${n.name}**\\n File: ${n.file}:${n.line}${i < path.nodes.length - 1 ? \"\\n ↓ calls\" : \"\"}`\n )\n .join(\"\\n\\n\")}\n\n**Total steps:** ${path.nodes.length}\n`;\n\n return {\n content: [{ type: \"text\", text: response }],\n };\n }\n\n case \"codemap_callers\": {\n const functionName = args?.function as string;\n\n // Find the function\n const nodes = db.searchNodesByName(functionName);\n\n if (nodes.length === 0) {\n return {\n content: [{ type: \"text\", text: `Function \"${functionName}\" not found` }],\n };\n }\n\n const targetNode = nodes[0];\n\n // Get all callers\n const callers = db.getResolvedCallers(targetNode.id);\n\n if (callers.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No callers found for \"${functionName}\". It may be unused or only called externally.`,\n },\n ],\n };\n }\n\n const response = `# Callers of ${functionName}\n\n**Function:** ${targetNode.name} (${targetNode.type})\n**Location:** ${targetNode.filePath}:${targetNode.startLine}\n**Total callers:** ${callers.length}\n\n## Who Calls This Function\n\n${callers\n .map(\n (c) => `- **${c.name}** (${c.type})\n File: ${c.filePath}:${c.startLine}`\n )\n .join(\"\\n\\n\")}\n`;\n\n return {\n content: [{ type: \"text\", text: response }],\n };\n }\n\n default:\n return {\n content: [{ type: \"text\", text: `Unknown tool: ${name}` }],\n isError: true,\n };\n }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: \"text\", text: `Error: ${errorMessage}` }],\n isError: true,\n };\n }\n});\n\n// Start server\nconst transport = new StdioServerTransport();\nserver.connect(transport).catch((error) => {\n console.error(\"Failed to start MCP server:\", error);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/mcp/flow-server.ts"],"sourcesContent":["/**\n * MCP Server for CodeMap Flow Edition\n * Provides 3 focused tools: impact analysis, flow tracing, and caller lookup\n */\n\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { FlowStorage } from \"../flow/storage.js\";\nimport { resolve } from \"path\";\nimport { existsSync } from \"fs\";\n\n// Get config from environment\nconst DB_PATH = process.env.CODEMAP_DB_PATH || \".codemap/graph.db\";\nconst PROJECT_ROOT = process.env.CODEMAP_PROJECT_ROOT || process.cwd();\n\nlet storage: FlowStorage | null = null;\n\nfunction getStorage(): FlowStorage {\n if (!storage) {\n const dbPath = resolve(PROJECT_ROOT, DB_PATH);\n if (!existsSync(dbPath)) {\n throw new Error(`CodeMap database not found at ${dbPath}. Run 'codemap index' first.`);\n }\n storage = new FlowStorage(dbPath);\n }\n return storage;\n}\n\nconst server = new Server(\n {\n name: \"codemap-flow\",\n version: \"3.4.0\",\n },\n {\n capabilities: {\n tools: {},\n },\n }\n);\n\n// ============ Tools ============\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: [\n {\n name: \"codemap_impact\",\n description: \"Analyze what would break if you change a function/class. Shows all callers and affected files with risk assessment.\",\n inputSchema: {\n type: \"object\",\n properties: {\n target: {\n type: \"string\",\n description: \"Function or class name to analyze\",\n },\n depth: {\n type: \"number\",\n description: \"How many levels deep to analyze (default: 3)\",\n default: 3,\n },\n },\n required: [\"target\"],\n },\n },\n {\n name: \"codemap_trace_flow\",\n description: \"Trace the execution flow between two functions. Shows the complete call path from source to target.\",\n inputSchema: {\n type: \"object\",\n properties: {\n from: {\n type: \"string\",\n description: \"Starting function name\",\n },\n to: {\n type: \"string\",\n description: \"Target function name\",\n },\n },\n required: [\"from\", \"to\"],\n },\n },\n {\n name: \"codemap_callers\",\n description: \"Find all functions that call a specific function. Shows where and how a function is used.\",\n inputSchema: {\n type: \"object\",\n properties: {\n function: {\n type: \"string\",\n description: \"Function name to find callers for\",\n },\n },\n required: [\"function\"],\n },\n },\n ],\n };\n});\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n const db = getStorage();\n\n try {\n switch (name) {\n case \"codemap_impact\": {\n const target = args?.target as string;\n const depth = (args?.depth as number) || 3;\n\n // Find the target node(s)\n const nodes = db.searchNodesByName(target);\n\n if (nodes.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No function or class found with name \"${target}\"`,\n },\n ],\n };\n }\n\n const targetNode = nodes[0];\n\n // Get impacted nodes\n const impacted = db.getImpactedNodes(targetNode.id, depth);\n\n // Get unique files\n const affectedFiles = new Set(impacted.map((n) => n.filePath));\n\n // Assess risk\n const directCallers = db.getResolvedCallers(targetNode.id);\n let risk = \"LOW\";\n if (directCallers.length > 10) risk = \"HIGH\";\n else if (directCallers.length > 5) risk = \"MEDIUM\";\n\n const response = `# Impact Analysis: ${targetNode.name}\n\n**Location:** ${targetNode.filePath}:${targetNode.startLine}\n**Type:** ${targetNode.type}\n**Risk Level:** ${risk}\n\n## Direct Callers (${directCallers.length})\n${directCallers.slice(0, 10).map((c) => `- ${c.name} in ${c.filePath}:${c.startLine}`).join(\"\\n\")}\n${directCallers.length > 10 ? `\\n... and ${directCallers.length - 10} more` : \"\"}\n\n## Total Impact\n- **Affected functions:** ${impacted.length}\n- **Affected files:** ${affectedFiles.size}\n\n## Affected Files\n${[...affectedFiles].slice(0, 20).map((f) => `- ${f}`).join(\"\\n\")}\n${affectedFiles.size > 20 ? `\\n... and ${affectedFiles.size - 20} more` : \"\"}\n\n⚠️ **Recommendation:** ${risk === \"HIGH\" ? \"High-risk change. Review all callers before modifying.\" : risk === \"MEDIUM\" ? \"Moderate risk. Test affected files after changes.\" : \"Low risk. Changes unlikely to cause wide impact.\"}\n`;\n\n return {\n content: [{ type: \"text\", text: response }],\n };\n }\n\n case \"codemap_trace_flow\": {\n const from = args?.from as string;\n const to = args?.to as string;\n\n // Find source and target nodes\n const sourceNodes = db.searchNodesByName(from);\n const targetNodes = db.searchNodesByName(to);\n\n if (sourceNodes.length === 0) {\n return {\n content: [{ type: \"text\", text: `Source function \"${from}\" not found` }],\n };\n }\n\n if (targetNodes.length === 0) {\n return {\n content: [{ type: \"text\", text: `Target function \"${to}\" not found` }],\n };\n }\n\n const sourceNode = sourceNodes[0];\n const targetNode = targetNodes[0];\n\n // Trace path\n const path = db.traceCallPath(sourceNode.id, targetNode.id);\n\n if (!path) {\n return {\n content: [\n {\n type: \"text\",\n text: `No call path found from \"${from}\" to \"${to}\". They may not be connected.`,\n },\n ],\n };\n }\n\n const response = `# Execution Flow: ${from} → ${to}\n\n${path.nodes\n .map(\n (n, i) =>\n `${i + 1}. **${n.name}**\\n File: ${n.file}:${n.line}${i < path.nodes.length - 1 ? \"\\n ↓ calls\" : \"\"}`\n )\n .join(\"\\n\\n\")}\n\n**Total steps:** ${path.nodes.length}\n`;\n\n return {\n content: [{ type: \"text\", text: response }],\n };\n }\n\n case \"codemap_callers\": {\n const functionName = args?.function as string;\n\n // Find the function\n const nodes = db.searchNodesByName(functionName);\n\n if (nodes.length === 0) {\n return {\n content: [{ type: \"text\", text: `Function \"${functionName}\" not found` }],\n };\n }\n\n const targetNode = nodes[0];\n\n // Get all callers\n const callers = db.getResolvedCallers(targetNode.id);\n\n if (callers.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No callers found for \"${functionName}\". It may be unused or only called externally.`,\n },\n ],\n };\n }\n\n const response = `# Callers of ${functionName}\n\n**Function:** ${targetNode.name} (${targetNode.type})\n**Location:** ${targetNode.filePath}:${targetNode.startLine}\n**Total callers:** ${callers.length}\n\n## Who Calls This Function\n\n${callers\n .map(\n (c) => `- **${c.name}** (${c.type})\n File: ${c.filePath}:${c.startLine}`\n )\n .join(\"\\n\\n\")}\n`;\n\n return {\n content: [{ type: \"text\", text: response }],\n };\n }\n\n default:\n return {\n content: [{ type: \"text\", text: `Unknown tool: ${name}` }],\n isError: true,\n };\n }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: \"text\", text: `Error: ${errorMessage}` }],\n isError: true,\n };\n }\n});\n\n// Start server\nconst transport = new StdioServerTransport();\nserver.connect(transport).catch((error) => {\n console.error(\"Failed to start MCP server:\", error);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,392 @@ import { createHash } from "crypto";
|
|
|
8
8
|
import { glob } from "glob";
|
|
9
9
|
import { resolve, relative, extname } from "path";
|
|
10
10
|
|
|
11
|
+
// src/analysis/class-hierarchy.ts
|
|
12
|
+
function buildClassHierarchy(nodes) {
|
|
13
|
+
const classes = /* @__PURE__ */ new Map();
|
|
14
|
+
const children = /* @__PURE__ */ new Map();
|
|
15
|
+
const parents = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const node of nodes) {
|
|
17
|
+
if (node.type === "class") {
|
|
18
|
+
const classNode = {
|
|
19
|
+
id: node.id,
|
|
20
|
+
name: node.name,
|
|
21
|
+
file: node.filePath,
|
|
22
|
+
line: node.startLine,
|
|
23
|
+
extends: node.extends || [],
|
|
24
|
+
methods: []
|
|
25
|
+
// Will populate in next step
|
|
26
|
+
};
|
|
27
|
+
classes.set(node.name, classNode);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const node of nodes) {
|
|
31
|
+
if (node.type === "method" && node.parentClass) {
|
|
32
|
+
const classNode = classes.get(node.parentClass);
|
|
33
|
+
if (classNode) {
|
|
34
|
+
classNode.methods.push(node.name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const [className, classNode] of classes) {
|
|
39
|
+
for (const parentName of classNode.extends) {
|
|
40
|
+
if (!children.has(parentName)) {
|
|
41
|
+
children.set(parentName, []);
|
|
42
|
+
}
|
|
43
|
+
children.get(parentName).push(className);
|
|
44
|
+
if (!parents.has(className)) {
|
|
45
|
+
parents.set(className, []);
|
|
46
|
+
}
|
|
47
|
+
parents.get(className).push(parentName);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
classes,
|
|
52
|
+
children,
|
|
53
|
+
parents
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function findMethodDefinition(className, methodName, hierarchy) {
|
|
57
|
+
const visited = /* @__PURE__ */ new Set();
|
|
58
|
+
const queue = [className];
|
|
59
|
+
while (queue.length > 0) {
|
|
60
|
+
const current = queue.shift();
|
|
61
|
+
if (visited.has(current)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
visited.add(current);
|
|
65
|
+
const classNode = hierarchy.classes.get(current);
|
|
66
|
+
if (classNode) {
|
|
67
|
+
if (classNode.methods.includes(methodName)) {
|
|
68
|
+
return classNode;
|
|
69
|
+
}
|
|
70
|
+
const parentNames = hierarchy.parents.get(current) || [];
|
|
71
|
+
for (const parent of parentNames) {
|
|
72
|
+
queue.push(parent);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function resolveMethodCall(instanceType, methodName, hierarchy) {
|
|
79
|
+
return findMethodDefinition(instanceType, methodName, hierarchy);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/flow/enhanced-resolver.ts
|
|
83
|
+
var EnhancedResolver = class {
|
|
84
|
+
constructor(storage) {
|
|
85
|
+
this.storage = storage;
|
|
86
|
+
}
|
|
87
|
+
hierarchy = null;
|
|
88
|
+
variableTypes = /* @__PURE__ */ new Map();
|
|
89
|
+
/**
|
|
90
|
+
* Build class hierarchy from all nodes in storage
|
|
91
|
+
*/
|
|
92
|
+
buildHierarchy() {
|
|
93
|
+
const nodes = this.getAllNodes();
|
|
94
|
+
this.hierarchy = buildClassHierarchy(nodes);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Load variable type information from storage
|
|
98
|
+
*/
|
|
99
|
+
loadVariableTypes() {
|
|
100
|
+
const stmt = this.storage.db.prepare(`
|
|
101
|
+
SELECT * FROM variable_types
|
|
102
|
+
`);
|
|
103
|
+
const rows = stmt.all();
|
|
104
|
+
for (const row of rows) {
|
|
105
|
+
const scopeId = row.scope_node_id;
|
|
106
|
+
if (!this.variableTypes.has(scopeId)) {
|
|
107
|
+
this.variableTypes.set(scopeId, []);
|
|
108
|
+
}
|
|
109
|
+
this.variableTypes.get(scopeId).push({
|
|
110
|
+
name: row.variable_name,
|
|
111
|
+
type: row.type_name,
|
|
112
|
+
scope: scopeId,
|
|
113
|
+
line: row.line
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Resolve all unresolved edges using type information
|
|
119
|
+
*/
|
|
120
|
+
resolveAllCalls() {
|
|
121
|
+
if (!this.hierarchy) {
|
|
122
|
+
this.buildHierarchy();
|
|
123
|
+
}
|
|
124
|
+
this.loadVariableTypes();
|
|
125
|
+
const resolvedCalls = [];
|
|
126
|
+
const stmt = this.storage.db.prepare(`
|
|
127
|
+
SELECT * FROM edges
|
|
128
|
+
WHERE target_id LIKE 'ref:%'
|
|
129
|
+
AND type = 'calls'
|
|
130
|
+
`);
|
|
131
|
+
const edges = stmt.all();
|
|
132
|
+
for (const edgeRow of edges) {
|
|
133
|
+
const edge = {
|
|
134
|
+
id: edgeRow.id,
|
|
135
|
+
type: edgeRow.type,
|
|
136
|
+
sourceId: edgeRow.source_id,
|
|
137
|
+
targetId: edgeRow.target_id,
|
|
138
|
+
metadata: edgeRow.metadata ? JSON.parse(edgeRow.metadata) : void 0
|
|
139
|
+
};
|
|
140
|
+
const resolved = this.resolveCall(edge);
|
|
141
|
+
if (resolved.targetNode) {
|
|
142
|
+
resolvedCalls.push(resolved);
|
|
143
|
+
this.updateEdgeTarget(edge.id, resolved.targetNode.id);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return resolvedCalls;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Resolve a single call edge
|
|
150
|
+
*/
|
|
151
|
+
resolveCall(edge) {
|
|
152
|
+
const unresolvedName = edge.metadata?.unresolvedName;
|
|
153
|
+
if (!unresolvedName) {
|
|
154
|
+
return {
|
|
155
|
+
originalEdge: edge,
|
|
156
|
+
targetNode: null,
|
|
157
|
+
confidence: "LOW",
|
|
158
|
+
reason: "No unresolved name in metadata"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const sourceNode = this.storage.getNode(edge.sourceId);
|
|
162
|
+
if (!sourceNode) {
|
|
163
|
+
return {
|
|
164
|
+
originalEdge: edge,
|
|
165
|
+
targetNode: null,
|
|
166
|
+
confidence: "LOW",
|
|
167
|
+
reason: "Source node not found"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (!unresolvedName.includes(".")) {
|
|
171
|
+
return this.resolveSimpleCall(unresolvedName, sourceNode, edge);
|
|
172
|
+
}
|
|
173
|
+
return this.resolveMethodCall(unresolvedName, sourceNode, edge);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Resolve simple function call (no dot notation)
|
|
177
|
+
*/
|
|
178
|
+
resolveSimpleCall(functionName, sourceNode, edge) {
|
|
179
|
+
const sameFileNodes = this.storage.getNodesByFile(sourceNode.filePath);
|
|
180
|
+
const localMatch = sameFileNodes.find(
|
|
181
|
+
(n) => (n.type === "function" || n.type === "method") && n.name === functionName
|
|
182
|
+
);
|
|
183
|
+
if (localMatch) {
|
|
184
|
+
return {
|
|
185
|
+
originalEdge: edge,
|
|
186
|
+
targetNode: localMatch,
|
|
187
|
+
confidence: "HIGH",
|
|
188
|
+
reason: "Found in same file"
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const globalMatches = this.storage.searchNodesByName(functionName);
|
|
192
|
+
if (globalMatches.length === 1) {
|
|
193
|
+
return {
|
|
194
|
+
originalEdge: edge,
|
|
195
|
+
targetNode: globalMatches[0],
|
|
196
|
+
confidence: "MEDIUM",
|
|
197
|
+
reason: "Single global match"
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (globalMatches.length > 1) {
|
|
201
|
+
const sourceDir = sourceNode.filePath.split("/").slice(0, -1).join("/");
|
|
202
|
+
const sameDirMatch = globalMatches.find((n) => n.filePath.startsWith(sourceDir));
|
|
203
|
+
if (sameDirMatch) {
|
|
204
|
+
return {
|
|
205
|
+
originalEdge: edge,
|
|
206
|
+
targetNode: sameDirMatch,
|
|
207
|
+
confidence: "MEDIUM",
|
|
208
|
+
reason: "Multiple matches, picked same directory"
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
originalEdge: edge,
|
|
213
|
+
targetNode: globalMatches[0],
|
|
214
|
+
confidence: "LOW",
|
|
215
|
+
reason: `Multiple matches (${globalMatches.length}), picked first`
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
originalEdge: edge,
|
|
220
|
+
targetNode: null,
|
|
221
|
+
confidence: "LOW",
|
|
222
|
+
reason: "No matches found"
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Resolve method call using type information
|
|
227
|
+
* Pattern: obj.method() or self.method()
|
|
228
|
+
*/
|
|
229
|
+
resolveMethodCall(fullName, sourceNode, edge) {
|
|
230
|
+
const parts = fullName.split(".");
|
|
231
|
+
const varName = parts[0];
|
|
232
|
+
const methodName = parts.slice(1).join(".");
|
|
233
|
+
if (varName === "self" || varName === "this") {
|
|
234
|
+
return this.resolveSelfMethodCall(methodName, sourceNode, edge);
|
|
235
|
+
}
|
|
236
|
+
const scopeTypes = this.variableTypes.get(sourceNode.id) || [];
|
|
237
|
+
const varType = scopeTypes.find((v) => v.name === varName);
|
|
238
|
+
if (!varType) {
|
|
239
|
+
return this.resolveSimpleCall(methodName, sourceNode, edge);
|
|
240
|
+
}
|
|
241
|
+
if (!this.hierarchy) {
|
|
242
|
+
return {
|
|
243
|
+
originalEdge: edge,
|
|
244
|
+
targetNode: null,
|
|
245
|
+
confidence: "LOW",
|
|
246
|
+
reason: "Class hierarchy not built"
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const classNode = resolveMethodCall(varType.type, methodName, this.hierarchy);
|
|
250
|
+
if (classNode) {
|
|
251
|
+
const methodNode = this.findMethodInClass(classNode.name, methodName);
|
|
252
|
+
if (methodNode) {
|
|
253
|
+
return {
|
|
254
|
+
originalEdge: edge,
|
|
255
|
+
targetNode: methodNode,
|
|
256
|
+
confidence: "HIGH",
|
|
257
|
+
reason: `Resolved via type tracking: ${varName}: ${varType.type}`
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
originalEdge: edge,
|
|
263
|
+
targetNode: null,
|
|
264
|
+
confidence: "LOW",
|
|
265
|
+
reason: `Type found (${varType.type}) but method not resolved`
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Resolve self.method() or this.method()
|
|
270
|
+
*/
|
|
271
|
+
resolveSelfMethodCall(methodName, sourceNode, edge) {
|
|
272
|
+
const parentClass = sourceNode.metadata?.parentClass;
|
|
273
|
+
if (!parentClass) {
|
|
274
|
+
return {
|
|
275
|
+
originalEdge: edge,
|
|
276
|
+
targetNode: null,
|
|
277
|
+
confidence: "LOW",
|
|
278
|
+
reason: "self/this call but no parent class"
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (!this.hierarchy) {
|
|
282
|
+
return {
|
|
283
|
+
originalEdge: edge,
|
|
284
|
+
targetNode: null,
|
|
285
|
+
confidence: "LOW",
|
|
286
|
+
reason: "Class hierarchy not built"
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const classNode = resolveMethodCall(parentClass, methodName, this.hierarchy);
|
|
290
|
+
if (classNode) {
|
|
291
|
+
const methodNode2 = this.findMethodInClass(classNode.name, methodName);
|
|
292
|
+
if (methodNode2) {
|
|
293
|
+
return {
|
|
294
|
+
originalEdge: edge,
|
|
295
|
+
targetNode: methodNode2,
|
|
296
|
+
confidence: "HIGH",
|
|
297
|
+
reason: `Resolved self.${methodName} in ${parentClass}`
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const sameFileNodes = this.storage.getNodesByFile(sourceNode.filePath);
|
|
302
|
+
const methodNode = sameFileNodes.find(
|
|
303
|
+
(n) => n.type === "method" && n.name === methodName
|
|
304
|
+
);
|
|
305
|
+
if (methodNode) {
|
|
306
|
+
return {
|
|
307
|
+
originalEdge: edge,
|
|
308
|
+
targetNode: methodNode,
|
|
309
|
+
confidence: "MEDIUM",
|
|
310
|
+
reason: "Found method in same file"
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
originalEdge: edge,
|
|
315
|
+
targetNode: null,
|
|
316
|
+
confidence: "LOW",
|
|
317
|
+
reason: `Method ${methodName} not found in ${parentClass}`
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Find method node in a class
|
|
322
|
+
*/
|
|
323
|
+
findMethodInClass(className, methodName) {
|
|
324
|
+
const stmt = this.storage.db.prepare(`
|
|
325
|
+
SELECT * FROM nodes
|
|
326
|
+
WHERE type = 'method'
|
|
327
|
+
AND name = ?
|
|
328
|
+
AND json_extract(metadata, '$.parentClass') = ?
|
|
329
|
+
`);
|
|
330
|
+
const row = stmt.get(methodName, className);
|
|
331
|
+
if (!row) return null;
|
|
332
|
+
return {
|
|
333
|
+
id: row.id,
|
|
334
|
+
type: row.type,
|
|
335
|
+
name: row.name,
|
|
336
|
+
filePath: row.file_path,
|
|
337
|
+
startLine: row.start_line,
|
|
338
|
+
endLine: row.end_line,
|
|
339
|
+
language: row.language,
|
|
340
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Get all nodes from storage
|
|
345
|
+
*/
|
|
346
|
+
getAllNodes() {
|
|
347
|
+
const stmt = this.storage.db.prepare("SELECT * FROM nodes");
|
|
348
|
+
const rows = stmt.all();
|
|
349
|
+
return rows.map((row) => ({
|
|
350
|
+
id: row.id,
|
|
351
|
+
type: row.type,
|
|
352
|
+
name: row.name,
|
|
353
|
+
filePath: row.file_path,
|
|
354
|
+
startLine: row.start_line,
|
|
355
|
+
endLine: row.end_line,
|
|
356
|
+
language: row.language,
|
|
357
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
358
|
+
}));
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Update edge target in database
|
|
362
|
+
*/
|
|
363
|
+
updateEdgeTarget(edgeId, newTargetId) {
|
|
364
|
+
const stmt = this.storage.db.prepare(`
|
|
365
|
+
UPDATE edges
|
|
366
|
+
SET target_id = ?
|
|
367
|
+
WHERE id = ?
|
|
368
|
+
`);
|
|
369
|
+
stmt.run(newTargetId, edgeId);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Get resolution statistics
|
|
373
|
+
*/
|
|
374
|
+
getStats() {
|
|
375
|
+
const totalStmt = this.storage.db.prepare(`
|
|
376
|
+
SELECT COUNT(*) as count FROM edges WHERE type = 'calls'
|
|
377
|
+
`);
|
|
378
|
+
const totalRow = totalStmt.get();
|
|
379
|
+
const totalCalls = totalRow.count;
|
|
380
|
+
const resolvedStmt = this.storage.db.prepare(`
|
|
381
|
+
SELECT COUNT(*) as count FROM edges
|
|
382
|
+
WHERE type = 'calls' AND target_id NOT LIKE 'ref:%'
|
|
383
|
+
`);
|
|
384
|
+
const resolvedRow = resolvedStmt.get();
|
|
385
|
+
const resolvedCalls = resolvedRow.count;
|
|
386
|
+
const unresolvedCalls = totalCalls - resolvedCalls;
|
|
387
|
+
const resolutionRate = totalCalls > 0 ? resolvedCalls / totalCalls * 100 : 0;
|
|
388
|
+
return {
|
|
389
|
+
totalCalls,
|
|
390
|
+
resolvedCalls,
|
|
391
|
+
unresolvedCalls,
|
|
392
|
+
resolutionRate
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
11
397
|
// src/parsers/base.ts
|
|
12
398
|
var BaseParser = class {
|
|
13
399
|
nodeIdCounter = 0;
|
|
@@ -262,31 +648,38 @@ var TypeScriptParser = class extends BaseParser {
|
|
|
262
648
|
handleClass(node, filePath, analysis, parentId) {
|
|
263
649
|
const nameNode = node.childForFieldName("name");
|
|
264
650
|
if (!nameNode) return;
|
|
651
|
+
const extends_ = [];
|
|
652
|
+
for (const child of node.children) {
|
|
653
|
+
if (child.type === "class_heritage") {
|
|
654
|
+
const extendsClause = child.children.find((c) => c.type === "extends_clause");
|
|
655
|
+
if (extendsClause) {
|
|
656
|
+
const baseClass = extendsClause.children.find((c) => c.type === "identifier");
|
|
657
|
+
if (baseClass) {
|
|
658
|
+
extends_.push(baseClass.text);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
265
663
|
const classNode = this.createNode(
|
|
266
664
|
"class",
|
|
267
665
|
nameNode.text,
|
|
268
666
|
filePath,
|
|
269
667
|
node.startPosition.row + 1,
|
|
270
|
-
node.endPosition.row + 1
|
|
668
|
+
node.endPosition.row + 1,
|
|
669
|
+
{
|
|
670
|
+
extends: extends_
|
|
671
|
+
}
|
|
271
672
|
);
|
|
272
673
|
analysis.nodes.push(classNode);
|
|
273
674
|
analysis.edges.push(
|
|
274
675
|
this.createEdge("contains", parentId, classNode.id)
|
|
275
676
|
);
|
|
276
|
-
for (const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
analysis.edges.push(
|
|
283
|
-
this.createEdge("extends", classNode.id, `ref:${baseClass.text}`, {
|
|
284
|
-
unresolvedName: baseClass.text
|
|
285
|
-
})
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
677
|
+
for (const baseClass of extends_) {
|
|
678
|
+
analysis.edges.push(
|
|
679
|
+
this.createEdge("extends", classNode.id, `ref:${baseClass}`, {
|
|
680
|
+
unresolvedName: baseClass
|
|
681
|
+
})
|
|
682
|
+
);
|
|
290
683
|
}
|
|
291
684
|
const body = node.childForFieldName("body");
|
|
292
685
|
if (body) {
|
|
@@ -300,6 +693,8 @@ var TypeScriptParser = class extends BaseParser {
|
|
|
300
693
|
handleMethod(node, filePath, analysis, parentId) {
|
|
301
694
|
const nameNode = node.childForFieldName("name");
|
|
302
695
|
if (!nameNode) return;
|
|
696
|
+
const parentNode = analysis.nodes.find((n) => n.id === parentId);
|
|
697
|
+
const parentClass = parentNode?.type === "class" ? parentNode.name : void 0;
|
|
303
698
|
const methodNode = this.createNode(
|
|
304
699
|
"method",
|
|
305
700
|
nameNode.text,
|
|
@@ -308,7 +703,8 @@ var TypeScriptParser = class extends BaseParser {
|
|
|
308
703
|
node.endPosition.row + 1,
|
|
309
704
|
{
|
|
310
705
|
static: node.children.some((c) => c.type === "static"),
|
|
311
|
-
async: node.children.some((c) => c.type === "async")
|
|
706
|
+
async: node.children.some((c) => c.type === "async"),
|
|
707
|
+
parentClass
|
|
312
708
|
}
|
|
313
709
|
);
|
|
314
710
|
analysis.nodes.push(methodNode);
|
|
@@ -743,6 +1139,8 @@ var PythonParser = class extends BaseParser {
|
|
|
743
1139
|
node.endPosition.row + 1,
|
|
744
1140
|
{
|
|
745
1141
|
bases,
|
|
1142
|
+
extends: bases,
|
|
1143
|
+
// Add extends for consistency with TypeScript
|
|
746
1144
|
isPrivate: name.startsWith("_")
|
|
747
1145
|
}
|
|
748
1146
|
);
|
|
@@ -780,6 +1178,8 @@ var PythonParser = class extends BaseParser {
|
|
|
780
1178
|
const nameNode = node.childForFieldName("name");
|
|
781
1179
|
if (!nameNode) return;
|
|
782
1180
|
const name = nameNode.text;
|
|
1181
|
+
const parentNode = analysis.nodes.find((n) => n.id === classId);
|
|
1182
|
+
const parentClass = parentNode?.type === "class" ? parentNode.name : void 0;
|
|
783
1183
|
const decorators = [];
|
|
784
1184
|
if (node.parent?.type === "decorated_definition") {
|
|
785
1185
|
for (const sibling of node.parent.children) {
|
|
@@ -808,7 +1208,8 @@ var PythonParser = class extends BaseParser {
|
|
|
808
1208
|
classmethod: isClassMethod,
|
|
809
1209
|
property: isProperty,
|
|
810
1210
|
isPrivate: name.startsWith("_"),
|
|
811
|
-
isDunder: name.startsWith("__") && name.endsWith("__")
|
|
1211
|
+
isDunder: name.startsWith("__") && name.endsWith("__"),
|
|
1212
|
+
parentClass
|
|
812
1213
|
}
|
|
813
1214
|
);
|
|
814
1215
|
analysis.nodes.push(methodNode);
|
|
@@ -1011,6 +1412,12 @@ var FlowBuilder = class {
|
|
|
1011
1412
|
currentFile: ""
|
|
1012
1413
|
});
|
|
1013
1414
|
this.resolveAllCalls();
|
|
1415
|
+
const enhancedResolver = new EnhancedResolver(this.storage);
|
|
1416
|
+
enhancedResolver.buildHierarchy();
|
|
1417
|
+
const enhancedResolved = enhancedResolver.resolveAllCalls();
|
|
1418
|
+
if (enhancedResolved.length > 0) {
|
|
1419
|
+
console.log(`\u2728 Enhanced resolver: resolved ${enhancedResolved.length} additional calls`);
|
|
1420
|
+
}
|
|
1014
1421
|
const stats = this.storage.getStats();
|
|
1015
1422
|
this.emitProgress({
|
|
1016
1423
|
phase: "complete",
|