@4onstudios/iris-agent 0.1.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/LICENSE +21 -0
- package/README.md +690 -0
- package/dist/api/acp/acpServer.d.ts +23 -0
- package/dist/api/acp/acpServer.js +814 -0
- package/dist/api/acp/index.d.ts +2 -0
- package/dist/api/acp/index.js +2 -0
- package/dist/api/acp/irisClient.d.ts +58 -0
- package/dist/api/acp/irisClient.js +214 -0
- package/dist/api/agent.d.ts +14 -0
- package/dist/api/agent.js +4171 -0
- package/dist/api/backendService.d.ts +4 -0
- package/dist/api/backendService.js +14 -0
- package/dist/api/core/agent/host/AgentContract.d.ts +82 -0
- package/dist/api/core/agent/host/AgentContract.js +1 -0
- package/dist/api/core/agent/host/AgentRegistry.d.ts +24 -0
- package/dist/api/core/agent/host/AgentRegistry.js +125 -0
- package/dist/api/core/agent/host/defaultRegistry.d.ts +15 -0
- package/dist/api/core/agent/host/defaultRegistry.js +46 -0
- package/dist/api/core/agent/host/externalAgentLifecycle.d.ts +65 -0
- package/dist/api/core/agent/host/externalAgentLifecycle.js +117 -0
- package/dist/api/core/agent/host/hostSessionManager.d.ts +111 -0
- package/dist/api/core/agent/host/hostSessionManager.js +352 -0
- package/dist/api/core/agent/host/index.d.ts +5 -0
- package/dist/api/core/agent/host/index.js +5 -0
- package/dist/api/core/agent/index.d.ts +87 -0
- package/dist/api/core/agent/index.js +1024 -0
- package/dist/api/core/agent/publicContracts.d.ts +35 -0
- package/dist/api/core/agent/publicContracts.js +103 -0
- package/dist/api/core/agent/tools/applyDiff.d.ts +51 -0
- package/dist/api/core/agent/tools/applyDiff.js +131 -0
- package/dist/api/core/agent/tools/backgroundTasks.d.ts +240 -0
- package/dist/api/core/agent/tools/backgroundTasks.js +313 -0
- package/dist/api/core/agent/tools/createDirectory.d.ts +43 -0
- package/dist/api/core/agent/tools/createDirectory.js +77 -0
- package/dist/api/core/agent/tools/deleteFile.d.ts +37 -0
- package/dist/api/core/agent/tools/deleteFile.js +66 -0
- package/dist/api/core/agent/tools/editFile.d.ts +73 -0
- package/dist/api/core/agent/tools/editFile.js +173 -0
- package/dist/api/core/agent/tools/executeCommand.d.ts +59 -0
- package/dist/api/core/agent/tools/executeCommand.js +250 -0
- package/dist/api/core/agent/tools/fileContent.d.ts +54 -0
- package/dist/api/core/agent/tools/fileContent.js +227 -0
- package/dist/api/core/agent/tools/findDefinition.d.ts +72 -0
- package/dist/api/core/agent/tools/findDefinition.js +109 -0
- package/dist/api/core/agent/tools/findReferences.d.ts +86 -0
- package/dist/api/core/agent/tools/findReferences.js +135 -0
- package/dist/api/core/agent/tools/formatDocument.d.ts +67 -0
- package/dist/api/core/agent/tools/formatDocument.js +90 -0
- package/dist/api/core/agent/tools/getCodeActions.d.ts +77 -0
- package/dist/api/core/agent/tools/getCodeActions.js +97 -0
- package/dist/api/core/agent/tools/getCodeCompletion.d.ts +77 -0
- package/dist/api/core/agent/tools/getCodeCompletion.js +174 -0
- package/dist/api/core/agent/tools/getCodeContext.d.ts +14 -0
- package/dist/api/core/agent/tools/getCodeContext.js +282 -0
- package/dist/api/core/agent/tools/getSignatureHelp.d.ts +61 -0
- package/dist/api/core/agent/tools/getSignatureHelp.js +85 -0
- package/dist/api/core/agent/tools/getSymbols.d.ts +50 -0
- package/dist/api/core/agent/tools/getSymbols.js +164 -0
- package/dist/api/core/agent/tools/getSymbolsLSP.d.ts +58 -0
- package/dist/api/core/agent/tools/getSymbolsLSP.js +147 -0
- package/dist/api/core/agent/tools/getTypeInfo.d.ts +70 -0
- package/dist/api/core/agent/tools/getTypeInfo.js +84 -0
- package/dist/api/core/agent/tools/getWorkspaceInfo.d.ts +80 -0
- package/dist/api/core/agent/tools/getWorkspaceInfo.js +281 -0
- package/dist/api/core/agent/tools/getWorkspaceSymbols.d.ts +53 -0
- package/dist/api/core/agent/tools/getWorkspaceSymbols.js +97 -0
- package/dist/api/core/agent/tools/grepSearch.d.ts +100 -0
- package/dist/api/core/agent/tools/grepSearch.js +211 -0
- package/dist/api/core/agent/tools/languageModelToolsIntegration.d.ts +13 -0
- package/dist/api/core/agent/tools/languageModelToolsIntegration.js +22 -0
- package/dist/api/core/agent/tools/listDirectory.d.ts +66 -0
- package/dist/api/core/agent/tools/listDirectory.js +161 -0
- package/dist/api/core/agent/tools/mcpTools.d.ts +48 -0
- package/dist/api/core/agent/tools/mcpTools.js +442 -0
- package/dist/api/core/agent/tools/queryKnowledgeGraph.d.ts +120 -0
- package/dist/api/core/agent/tools/queryKnowledgeGraph.js +306 -0
- package/dist/api/core/agent/tools/readFile.d.ts +92 -0
- package/dist/api/core/agent/tools/readFile.js +337 -0
- package/dist/api/core/agent/tools/renameFile.d.ts +49 -0
- package/dist/api/core/agent/tools/renameFile.js +86 -0
- package/dist/api/core/agent/tools/renameSymbol.d.ts +70 -0
- package/dist/api/core/agent/tools/renameSymbol.js +106 -0
- package/dist/api/core/agent/tools/runTerminalCommand.d.ts +72 -0
- package/dist/api/core/agent/tools/runTerminalCommand.js +69 -0
- package/dist/api/core/agent/tools/searchFiles.d.ts +82 -0
- package/dist/api/core/agent/tools/searchFiles.js +280 -0
- package/dist/api/core/agent/tools/terminalAutoApprove.d.ts +3 -0
- package/dist/api/core/agent/tools/terminalAutoApprove.js +676 -0
- package/dist/api/core/agent/tools/treeTraversal.d.ts +60 -0
- package/dist/api/core/agent/tools/treeTraversal.js +217 -0
- package/dist/api/core/agent/tools/webSearch.d.ts +37 -0
- package/dist/api/core/agent/tools/webSearch.js +80 -0
- package/dist/api/core/agent/tools/writeFile.d.ts +59 -0
- package/dist/api/core/agent/tools/writeFile.js +97 -0
- package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.d.ts +12 -0
- package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.js +67 -0
- package/dist/api/core/agent/utils/diffUtils.d.ts +14 -0
- package/dist/api/core/agent/utils/diffUtils.js +39 -0
- package/dist/api/core/agent/utils/environmentSnapshot.d.ts +42 -0
- package/dist/api/core/agent/utils/environmentSnapshot.js +213 -0
- package/dist/api/core/agent/utils/errorRecovery.d.ts +49 -0
- package/dist/api/core/agent/utils/errorRecovery.js +229 -0
- package/dist/api/core/agent/utils/multimodalTokenLimiter.d.ts +8 -0
- package/dist/api/core/agent/utils/multimodalTokenLimiter.js +238 -0
- package/dist/api/core/agent/utils/openRouterModelSettings.d.ts +12 -0
- package/dist/api/core/agent/utils/openRouterModelSettings.js +34 -0
- package/dist/api/core/agent/utils/pathRecovery.d.ts +1 -0
- package/dist/api/core/agent/utils/pathRecovery.js +29 -0
- package/dist/api/core/agent/utils/repoMapIndex.d.ts +3 -0
- package/dist/api/core/agent/utils/repoMapIndex.js +197 -0
- package/dist/api/core/agent/utils/skillsDiscovery.d.ts +10 -0
- package/dist/api/core/agent/utils/skillsDiscovery.js +50 -0
- package/dist/api/core/agent/utils/toolCallBudget.d.ts +9 -0
- package/dist/api/core/agent/utils/toolCallBudget.js +55 -0
- package/dist/api/core/agent/utils/toolLifecycle.d.ts +30 -0
- package/dist/api/core/agent/utils/toolLifecycle.js +361 -0
- package/dist/api/core/agent/utils/toolResultSafetyProcessor.d.ts +12 -0
- package/dist/api/core/agent/utils/toolResultSafetyProcessor.js +57 -0
- package/dist/api/core/agent/utils/workspaceMutationBridge.d.ts +13 -0
- package/dist/api/core/agent/utils/workspaceMutationBridge.js +149 -0
- package/dist/api/core/agent/utils/workspacePathGuard.d.ts +7 -0
- package/dist/api/core/agent/utils/workspacePathGuard.js +79 -0
- package/dist/api/core/containers/chat/toolResultSerialization.d.ts +8 -0
- package/dist/api/core/containers/chat/toolResultSerialization.js +71 -0
- package/dist/api/core/library/BrowserManager.d.ts +11 -0
- package/dist/api/core/library/BrowserManager.js +66 -0
- package/dist/api/core/library/desktopWorkspace.d.ts +1 -0
- package/dist/api/core/library/desktopWorkspace.js +3 -0
- package/dist/api/core/library/extensionManager.d.ts +10 -0
- package/dist/api/core/library/extensionManager.js +27 -0
- package/dist/api/core/library/knowledgeGraph.d.ts +268 -0
- package/dist/api/core/library/knowledgeGraph.js +989 -0
- package/dist/api/core/library/languageModelTools.d.ts +141 -0
- package/dist/api/core/library/languageModelTools.js +207 -0
- package/dist/api/core/library/localRuntime.d.ts +23 -0
- package/dist/api/core/library/localRuntime.js +144 -0
- package/dist/api/core/library/lsp/coreLsp.d.ts +367 -0
- package/dist/api/core/library/lsp/coreLsp.js +2076 -0
- package/dist/api/core/library/lsp/protocol.d.ts +25 -0
- package/dist/api/core/library/lsp/protocol.js +1 -0
- package/dist/api/core/library/lsp/serverManager.d.ts +37 -0
- package/dist/api/core/library/lsp/serverManager.js +427 -0
- package/dist/api/core/library/mcpServerProvider.d.ts +69 -0
- package/dist/api/core/library/mcpServerProvider.js +152 -0
- package/dist/api/core/library/mcpSettings.d.ts +20 -0
- package/dist/api/core/library/mcpSettings.js +131 -0
- package/dist/api/core/library/patternMatcher.d.ts +66 -0
- package/dist/api/core/library/patternMatcher.js +284 -0
- package/dist/api/core/library/regexEscape.d.ts +4 -0
- package/dist/api/core/library/regexEscape.js +4 -0
- package/dist/api/core/library/runtimeEventBus.d.ts +27 -0
- package/dist/api/core/library/runtimeEventBus.js +78 -0
- package/dist/api/core/library/safetyMiddleware.d.ts +98 -0
- package/dist/api/core/library/safetyMiddleware.js +215 -0
- package/dist/api/core/library/tauri.d.ts +71 -0
- package/dist/api/core/library/tauri.js +222 -0
- package/dist/api/core/library/tauriImport.d.ts +5 -0
- package/dist/api/core/library/tauriImport.js +7 -0
- package/dist/api/core/library/terminalAutoApproveSettings.d.ts +32 -0
- package/dist/api/core/library/terminalAutoApproveSettings.js +529 -0
- package/dist/api/core/library/workspaceIdentity.d.ts +10 -0
- package/dist/api/core/library/workspaceIdentity.js +49 -0
- package/dist/api/core/library/workspaceSummary.d.ts +19 -0
- package/dist/api/core/library/workspaceSummary.js +35 -0
- package/dist/api/core/skills/agent-customization/SKILL.md +27 -0
- package/dist/api/core/skills/bug-fix/SKILL.md +21 -0
- package/dist/api/core/skills/create-pr/SKILL.md +32 -0
- package/dist/api/core/skills/dev-server/SKILL.md +20 -0
- package/dist/api/core/skills/fix-suggestions/SKILL.md +21 -0
- package/dist/api/core/skills/github-search/SKILL.md +21 -0
- package/dist/api/core/skills/github-summary/SKILL.md +25 -0
- package/dist/api/core/skills/integration-tests/SKILL.md +29 -0
- package/dist/api/core/skills/pr-comments/SKILL.md +23 -0
- package/dist/api/core/skills/project-setup/SKILL.md +20 -0
- package/dist/api/core/skills/search-integration/SKILL.md +19 -0
- package/dist/api/core/skills/search-results/SKILL.md +19 -0
- package/dist/api/core/skills/typescript-upgrade/SKILL.md +21 -0
- package/dist/api/data/runStore.d.ts +46 -0
- package/dist/api/data/runStore.js +244 -0
- package/dist/api/helpers/agentUtils.d.ts +11 -0
- package/dist/api/helpers/agentUtils.js +23 -0
- package/dist/api/helpers/modelTokenLimits.d.ts +2 -0
- package/dist/api/helpers/modelTokenLimits.js +158 -0
- package/dist/api/helpers/observationalMemory.d.ts +10 -0
- package/dist/api/helpers/observationalMemory.js +36 -0
- package/dist/api/helpers/promptBudget.d.ts +30 -0
- package/dist/api/helpers/promptBudget.js +216 -0
- package/dist/api/helpers/resolveImageMessageParts.d.ts +9 -0
- package/dist/api/helpers/resolveImageMessageParts.js +140 -0
- package/dist/api/helpers/slashCommands.d.ts +31 -0
- package/dist/api/helpers/slashCommands.js +129 -0
- package/dist/api/helpers/tokenUsage.d.ts +19 -0
- package/dist/api/helpers/tokenUsage.js +81 -0
- package/dist/api/routes/fileRoutes.d.ts +2 -0
- package/dist/api/routes/fileRoutes.js +108 -0
- package/dist/api/routes/lspDocumentRoutes.d.ts +4 -0
- package/dist/api/routes/lspDocumentRoutes.js +84 -0
- package/dist/api/routes/lspHierarchyRoutes.d.ts +4 -0
- package/dist/api/routes/lspHierarchyRoutes.js +122 -0
- package/dist/api/routes/lspPositionRoutes.d.ts +4 -0
- package/dist/api/routes/lspPositionRoutes.js +263 -0
- package/dist/api/routes/lspQueryRoutes.d.ts +4 -0
- package/dist/api/routes/lspQueryRoutes.js +679 -0
- package/dist/api/routes/lspResolveRoutes.d.ts +4 -0
- package/dist/api/routes/lspResolveRoutes.js +143 -0
- package/dist/api/routes/lspSemanticDocumentRoutes.d.ts +4 -0
- package/dist/api/routes/lspSemanticDocumentRoutes.js +98 -0
- package/dist/api/routes/semanticRoutes.d.ts +2 -0
- package/dist/api/routes/semanticRoutes.js +126 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +160 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +38 -0
- package/package.json +100 -0
|
@@ -0,0 +1,989 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Graph System for Token Optimization
|
|
3
|
+
* Implements intelligent context caching and relationship tracking
|
|
4
|
+
*/
|
|
5
|
+
import { runtimeEventBus, RuntimeEvents } from "./runtimeEventBus.js";
|
|
6
|
+
import { createDefaultMatcher } from "./patternMatcher.js";
|
|
7
|
+
import { redact } from "./safetyMiddleware.js";
|
|
8
|
+
/**
|
|
9
|
+
* Knowledge Node - Represents a piece of code context
|
|
10
|
+
*/
|
|
11
|
+
export class KnowledgeNode {
|
|
12
|
+
id;
|
|
13
|
+
type;
|
|
14
|
+
content;
|
|
15
|
+
originalContent;
|
|
16
|
+
redacted;
|
|
17
|
+
redactionInfo;
|
|
18
|
+
metadata;
|
|
19
|
+
relationships;
|
|
20
|
+
accessCount;
|
|
21
|
+
lastAccessed;
|
|
22
|
+
tokenCount;
|
|
23
|
+
embedding;
|
|
24
|
+
constructor(id, type, content, metadata = {}) {
|
|
25
|
+
this.id = id;
|
|
26
|
+
this.type = type; // 'file', 'function', 'class', 'import', 'context'
|
|
27
|
+
// 🔒 SECURITY: Redact sensitive info before storing
|
|
28
|
+
const { output: safeContent, redactions } = redact(content);
|
|
29
|
+
this.content = safeContent;
|
|
30
|
+
this.originalContent = null; // Never store original if redacted
|
|
31
|
+
this.redacted = redactions.length > 0;
|
|
32
|
+
this.redactionInfo =
|
|
33
|
+
redactions.length > 0
|
|
34
|
+
? {
|
|
35
|
+
count: redactions.length,
|
|
36
|
+
types: [...new Set(redactions.map((r) => r.type))],
|
|
37
|
+
}
|
|
38
|
+
: null;
|
|
39
|
+
this.metadata = metadata;
|
|
40
|
+
this.relationships = new Set();
|
|
41
|
+
this.accessCount = 0;
|
|
42
|
+
this.lastAccessed = Date.now();
|
|
43
|
+
this.tokenCount = this.estimateTokens(safeContent);
|
|
44
|
+
this.embedding = null; // For semantic search (future)
|
|
45
|
+
}
|
|
46
|
+
estimateTokens(text) {
|
|
47
|
+
// Rough estimation: ~4 chars per token
|
|
48
|
+
return Math.ceil((text || "").length / 4);
|
|
49
|
+
}
|
|
50
|
+
addRelationship(nodeId, relationshipType, metadata = {}, confidence = 1) {
|
|
51
|
+
this.relationships.add({
|
|
52
|
+
nodeId,
|
|
53
|
+
type: relationshipType,
|
|
54
|
+
metadata,
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
confidence,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
hasRelationship(nodeId, relationshipType) {
|
|
60
|
+
return Array.from(this.relationships).some((rel) => rel.nodeId === nodeId && rel.type === relationshipType);
|
|
61
|
+
}
|
|
62
|
+
getRelationships(type = null) {
|
|
63
|
+
const rels = Array.from(this.relationships);
|
|
64
|
+
return type ? rels.filter((rel) => rel.type === type) : rels;
|
|
65
|
+
}
|
|
66
|
+
access() {
|
|
67
|
+
this.accessCount++;
|
|
68
|
+
this.lastAccessed = Date.now();
|
|
69
|
+
}
|
|
70
|
+
get score() {
|
|
71
|
+
// Recency + frequency scoring
|
|
72
|
+
const recencyScore = 1 / (Date.now() - this.lastAccessed + 1);
|
|
73
|
+
const frequencyScore = this.accessCount;
|
|
74
|
+
return recencyScore * 1000 + frequencyScore;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Knowledge Graph - Manages code context and relationships
|
|
79
|
+
*/
|
|
80
|
+
class KnowledgeGraph {
|
|
81
|
+
nodes;
|
|
82
|
+
cache;
|
|
83
|
+
maxCacheSize;
|
|
84
|
+
totalTokensSaved;
|
|
85
|
+
cacheHits;
|
|
86
|
+
cacheMisses;
|
|
87
|
+
patternMatcher;
|
|
88
|
+
constructor() {
|
|
89
|
+
this.nodes = new Map();
|
|
90
|
+
this.cache = new Map();
|
|
91
|
+
this.maxCacheSize = 50; // Max nodes in cache
|
|
92
|
+
this.totalTokensSaved = 0;
|
|
93
|
+
this.cacheHits = 0;
|
|
94
|
+
this.cacheMisses = 0;
|
|
95
|
+
// Pattern matcher for advanced code analysis
|
|
96
|
+
this.patternMatcher = createDefaultMatcher();
|
|
97
|
+
// Event subscriptions
|
|
98
|
+
this.initializeEventListeners();
|
|
99
|
+
}
|
|
100
|
+
initializeEventListeners() {
|
|
101
|
+
runtimeEventBus.subscribe(RuntimeEvents.FILE_OPEN, (file) => {
|
|
102
|
+
this.indexFile(file);
|
|
103
|
+
});
|
|
104
|
+
runtimeEventBus.subscribe(RuntimeEvents.CODE_CHANGE, (data) => {
|
|
105
|
+
this.updateContext(data);
|
|
106
|
+
});
|
|
107
|
+
runtimeEventBus.subscribe(RuntimeEvents.CONTEXT_QUERY, (query) => {
|
|
108
|
+
this.queryContext(query);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Create or update a node
|
|
113
|
+
*/
|
|
114
|
+
upsertNode(id, type, content, metadata = {}) {
|
|
115
|
+
let node = this.nodes.get(id);
|
|
116
|
+
if (node) {
|
|
117
|
+
// Update existing node with redaction
|
|
118
|
+
const { output: safeContent, redactions } = redact(content);
|
|
119
|
+
node.content = safeContent;
|
|
120
|
+
node.metadata = { ...node.metadata, ...metadata };
|
|
121
|
+
node.tokenCount = node.estimateTokens(safeContent);
|
|
122
|
+
node.redacted = redactions.length > 0;
|
|
123
|
+
if (redactions.length > 0) {
|
|
124
|
+
node.redactionInfo = {
|
|
125
|
+
count: redactions.length,
|
|
126
|
+
types: [...new Set(redactions.map((r) => r.type))],
|
|
127
|
+
};
|
|
128
|
+
// Log redaction event
|
|
129
|
+
console.warn("[Knowledge Graph] Redacted sensitive data in node:", {
|
|
130
|
+
nodeId: id,
|
|
131
|
+
type,
|
|
132
|
+
redactionCount: redactions.length,
|
|
133
|
+
types: node.redactionInfo?.types ?? [],
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Create new node (redaction happens in constructor)
|
|
139
|
+
node = new KnowledgeNode(id, type, content, metadata);
|
|
140
|
+
this.nodes.set(id, node);
|
|
141
|
+
if (node.redacted) {
|
|
142
|
+
const redactionInfo = node.redactionInfo;
|
|
143
|
+
console.warn("[Knowledge Graph] Created node with redacted content:", {
|
|
144
|
+
nodeId: id,
|
|
145
|
+
type,
|
|
146
|
+
redactionCount: redactionInfo?.count ?? 0,
|
|
147
|
+
types: redactionInfo?.types ?? [],
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return node;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Index a file and its components
|
|
155
|
+
*/
|
|
156
|
+
indexFile(file) {
|
|
157
|
+
const { path, name, content, language } = file;
|
|
158
|
+
const fileMetadata = {
|
|
159
|
+
name,
|
|
160
|
+
path,
|
|
161
|
+
};
|
|
162
|
+
if (typeof language === "string") {
|
|
163
|
+
fileMetadata.language = language;
|
|
164
|
+
}
|
|
165
|
+
// Create file node
|
|
166
|
+
const fileNode = this.upsertNode(path, "file", content, fileMetadata);
|
|
167
|
+
// Parse and index components
|
|
168
|
+
const components = this.parseComponents(content, language ?? undefined);
|
|
169
|
+
components.forEach((comp) => {
|
|
170
|
+
const compId = `${path}:${comp.type}:${comp.name}`;
|
|
171
|
+
const compNode = this.upsertNode(compId, comp.type, comp.content, {
|
|
172
|
+
filePath: path,
|
|
173
|
+
startLine: comp.startLine,
|
|
174
|
+
endLine: comp.endLine,
|
|
175
|
+
name: comp.name,
|
|
176
|
+
extends: comp.extends,
|
|
177
|
+
implements: comp.implements,
|
|
178
|
+
methods: comp.methods,
|
|
179
|
+
});
|
|
180
|
+
// Create basic relationships
|
|
181
|
+
fileNode.addRelationship(compId, "contains");
|
|
182
|
+
compNode.addRelationship(path, "belongsTo");
|
|
183
|
+
// Track imports/dependencies
|
|
184
|
+
if (comp.type === "import") {
|
|
185
|
+
comp.dependencies?.forEach((dep) => {
|
|
186
|
+
const resolvedImport = this.resolveImportPath(path, dep);
|
|
187
|
+
compNode.addRelationship(resolvedImport || dep, "imports", {
|
|
188
|
+
sourceFile: path,
|
|
189
|
+
importPath: dep,
|
|
190
|
+
resolved: Boolean(resolvedImport),
|
|
191
|
+
}, resolvedImport ? 0.95 : 0.4);
|
|
192
|
+
fileNode.addRelationship(resolvedImport || dep, "imports", {
|
|
193
|
+
sourceFile: path,
|
|
194
|
+
importPath: dep,
|
|
195
|
+
resolved: Boolean(resolvedImport),
|
|
196
|
+
}, resolvedImport ? 0.95 : 0.4);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
// Build semantic relationships (calls, extends, implements, etc.)
|
|
201
|
+
this.buildSemanticRelationships(path, components);
|
|
202
|
+
runtimeEventBus.emit(RuntimeEvents.CONTEXT_UPDATE, {
|
|
203
|
+
type: "file_indexed",
|
|
204
|
+
path,
|
|
205
|
+
nodeCount: components.length + 1,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
normalizePathLike(input) {
|
|
209
|
+
const rawParts = input.replace(/\\/g, "/").split("/");
|
|
210
|
+
const parts = [];
|
|
211
|
+
rawParts.forEach((part) => {
|
|
212
|
+
if (!part || part === ".") {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (part === "..") {
|
|
216
|
+
parts.pop();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
parts.push(part);
|
|
220
|
+
});
|
|
221
|
+
const hasLeadingSlash = input.startsWith("/");
|
|
222
|
+
return `${hasLeadingSlash ? "/" : ""}${parts.join("/")}`;
|
|
223
|
+
}
|
|
224
|
+
resolveImportPath(sourceFilePath, importPath) {
|
|
225
|
+
if (!importPath || !importPath.startsWith(".")) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const sourceDir = sourceFilePath.includes("/")
|
|
229
|
+
? sourceFilePath.slice(0, sourceFilePath.lastIndexOf("/"))
|
|
230
|
+
: "";
|
|
231
|
+
const base = this.normalizePathLike(`${sourceDir}/${importPath}`);
|
|
232
|
+
const candidates = [
|
|
233
|
+
base,
|
|
234
|
+
`${base}.ts`,
|
|
235
|
+
`${base}.tsx`,
|
|
236
|
+
`${base}.js`,
|
|
237
|
+
`${base}.jsx`,
|
|
238
|
+
`${base}/index.ts`,
|
|
239
|
+
`${base}/index.tsx`,
|
|
240
|
+
`${base}/index.js`,
|
|
241
|
+
`${base}/index.jsx`,
|
|
242
|
+
];
|
|
243
|
+
for (const candidate of candidates) {
|
|
244
|
+
if (this.nodes.has(candidate)) {
|
|
245
|
+
return candidate;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Parse code components (functions, classes, imports)
|
|
252
|
+
*/
|
|
253
|
+
parseComponents(content, language) {
|
|
254
|
+
const components = [];
|
|
255
|
+
if (language === "javascript" || language === "jsx") {
|
|
256
|
+
// Parse imports
|
|
257
|
+
const importRegex = /import\s+.*?\s+from\s+['"](.+?)['"]/g;
|
|
258
|
+
let match;
|
|
259
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
260
|
+
components.push({
|
|
261
|
+
type: "import",
|
|
262
|
+
name: match[1],
|
|
263
|
+
content: match[0],
|
|
264
|
+
dependencies: [match[1]],
|
|
265
|
+
startLine: content.substring(0, match.index).split("\n").length,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
// Parse functions
|
|
269
|
+
const functionRegex = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*\{/g;
|
|
270
|
+
while ((match = functionRegex.exec(content)) !== null) {
|
|
271
|
+
const startLine = content.substring(0, match.index).split("\n").length;
|
|
272
|
+
const funcContent = this.extractBlock(content, match.index);
|
|
273
|
+
components.push({
|
|
274
|
+
type: "function",
|
|
275
|
+
name: match[1],
|
|
276
|
+
content: funcContent,
|
|
277
|
+
startLine,
|
|
278
|
+
// Enhanced: Extract function calls
|
|
279
|
+
calls: this.extractFunctionCalls(funcContent),
|
|
280
|
+
// Enhanced: Extract variable usage
|
|
281
|
+
uses: this.extractVariableUsage(funcContent),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
// Parse arrow functions
|
|
285
|
+
const arrowRegex = /const\s+(\w+)\s*=\s*\([^)]*\)\s*=>/g;
|
|
286
|
+
while ((match = arrowRegex.exec(content)) !== null) {
|
|
287
|
+
const startLine = content.substring(0, match.index).split("\n").length;
|
|
288
|
+
const funcContent = this.extractStatement(content, match.index);
|
|
289
|
+
components.push({
|
|
290
|
+
type: "function",
|
|
291
|
+
name: match[1],
|
|
292
|
+
content: funcContent,
|
|
293
|
+
startLine,
|
|
294
|
+
// Enhanced: Extract function calls
|
|
295
|
+
calls: this.extractFunctionCalls(funcContent),
|
|
296
|
+
// Enhanced: Extract variable usage
|
|
297
|
+
uses: this.extractVariableUsage(funcContent),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
// Parse classes
|
|
301
|
+
const classRegex = /class\s+(\w+)\s*(?:extends\s+(\w+))?\s*(?:implements\s+([\w,\s]+))?\s*\{/g;
|
|
302
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
303
|
+
const startLine = content.substring(0, match.index).split("\n").length;
|
|
304
|
+
const classContent = this.extractBlock(content, match.index);
|
|
305
|
+
components.push({
|
|
306
|
+
type: "class",
|
|
307
|
+
name: match[1],
|
|
308
|
+
content: classContent,
|
|
309
|
+
startLine,
|
|
310
|
+
// Enhanced: Track inheritance
|
|
311
|
+
extends: match[2] || null,
|
|
312
|
+
// Enhanced: Track interfaces
|
|
313
|
+
implements: match[3] ? match[3].split(",").map((i) => i.trim()) : [],
|
|
314
|
+
// Enhanced: Extract methods
|
|
315
|
+
methods: this.extractClassMethods(classContent),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return components;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Enhanced: Extract function calls from code
|
|
323
|
+
*/
|
|
324
|
+
extractFunctionCalls(code) {
|
|
325
|
+
const calls = new Set();
|
|
326
|
+
// Match function calls: functionName(...) or object.method(...)
|
|
327
|
+
const callPatterns = [
|
|
328
|
+
/(\w+)\s*\(/g, // Simple calls: foo()
|
|
329
|
+
/(\w+)\.(\w+)\s*\(/g, // Method calls: obj.method()
|
|
330
|
+
/(\w+)\.(\w+)\.(\w+)\s*\(/g, // Chained calls: obj.prop.method()
|
|
331
|
+
];
|
|
332
|
+
callPatterns.forEach((pattern) => {
|
|
333
|
+
let match;
|
|
334
|
+
const regex = new RegExp(pattern);
|
|
335
|
+
while ((match = regex.exec(code)) !== null) {
|
|
336
|
+
const callName = match[0].replace(/\s*\($/, "");
|
|
337
|
+
if (!this.isKeyword(callName)) {
|
|
338
|
+
calls.add(callName);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
return Array.from(calls);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Enhanced: Extract variable usage from code
|
|
346
|
+
*/
|
|
347
|
+
extractVariableUsage(code) {
|
|
348
|
+
const variables = new Set();
|
|
349
|
+
// Match variable declarations
|
|
350
|
+
const varPatterns = [/(?:const|let|var)\s+(\w+)/g, /(\w+)\s*=\s*/g];
|
|
351
|
+
varPatterns.forEach((pattern) => {
|
|
352
|
+
let match;
|
|
353
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
354
|
+
if (!this.isKeyword(match[1])) {
|
|
355
|
+
variables.add(match[1]);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
return Array.from(variables);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Enhanced: Extract methods from class
|
|
363
|
+
*/
|
|
364
|
+
extractClassMethods(classCode) {
|
|
365
|
+
const methods = [];
|
|
366
|
+
// Match method definitions in class
|
|
367
|
+
const methodRegex = /(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/g;
|
|
368
|
+
let match;
|
|
369
|
+
while ((match = methodRegex.exec(classCode)) !== null) {
|
|
370
|
+
const methodName = match[1];
|
|
371
|
+
if (methodName !== "constructor" && !this.isKeyword(methodName)) {
|
|
372
|
+
methods.push({
|
|
373
|
+
name: methodName,
|
|
374
|
+
isAsync: match[0].includes("async"),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return methods;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Check if a word is a JavaScript keyword
|
|
382
|
+
*/
|
|
383
|
+
isKeyword(word) {
|
|
384
|
+
const keywords = new Set([
|
|
385
|
+
"if",
|
|
386
|
+
"else",
|
|
387
|
+
"for",
|
|
388
|
+
"while",
|
|
389
|
+
"do",
|
|
390
|
+
"switch",
|
|
391
|
+
"case",
|
|
392
|
+
"break",
|
|
393
|
+
"continue",
|
|
394
|
+
"return",
|
|
395
|
+
"function",
|
|
396
|
+
"class",
|
|
397
|
+
"const",
|
|
398
|
+
"let",
|
|
399
|
+
"var",
|
|
400
|
+
"new",
|
|
401
|
+
"this",
|
|
402
|
+
"super",
|
|
403
|
+
"import",
|
|
404
|
+
"export",
|
|
405
|
+
"from",
|
|
406
|
+
"default",
|
|
407
|
+
"async",
|
|
408
|
+
"await",
|
|
409
|
+
"try",
|
|
410
|
+
"catch",
|
|
411
|
+
"finally",
|
|
412
|
+
"throw",
|
|
413
|
+
]);
|
|
414
|
+
return keywords.has(word);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Enhanced: Build semantic relationships between components
|
|
418
|
+
*/
|
|
419
|
+
buildSemanticRelationships(path, components) {
|
|
420
|
+
const fileNode = this.nodes.get(path);
|
|
421
|
+
if (!fileNode)
|
|
422
|
+
return;
|
|
423
|
+
components.forEach((comp) => {
|
|
424
|
+
const compId = `${path}:${comp.type}:${comp.name}`;
|
|
425
|
+
const compNode = this.nodes.get(compId);
|
|
426
|
+
if (!compNode)
|
|
427
|
+
return;
|
|
428
|
+
// Track function calls
|
|
429
|
+
if (comp.calls) {
|
|
430
|
+
comp.calls.forEach((calledFunc) => {
|
|
431
|
+
// Find the called function node
|
|
432
|
+
const calledNodeId = this.findNodeByName(calledFunc, "function");
|
|
433
|
+
if (calledNodeId) {
|
|
434
|
+
compNode.addRelationship(calledNodeId, "calls", {
|
|
435
|
+
functionName: calledFunc,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
// Track class inheritance
|
|
441
|
+
if (comp.extends) {
|
|
442
|
+
const parentClassId = this.findNodeByName(comp.extends, "class");
|
|
443
|
+
if (parentClassId) {
|
|
444
|
+
compNode.addRelationship(parentClassId, "extends", {
|
|
445
|
+
parentClass: comp.extends,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Track interface implementation
|
|
450
|
+
if (comp.implements && comp.implements.length > 0) {
|
|
451
|
+
comp.implements.forEach((interfaceName) => {
|
|
452
|
+
const interfaceId = this.findNodeByName(interfaceName, "interface");
|
|
453
|
+
if (interfaceId) {
|
|
454
|
+
compNode.addRelationship(interfaceId, "implements", {
|
|
455
|
+
interface: interfaceName,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
// Track variable dependencies
|
|
461
|
+
if (comp.uses) {
|
|
462
|
+
comp.uses.forEach((varName) => {
|
|
463
|
+
// Check if variable is defined elsewhere
|
|
464
|
+
const varNodeId = this.findNodeByName(varName, "variable");
|
|
465
|
+
if (varNodeId) {
|
|
466
|
+
compNode.addRelationship(varNodeId, "uses", {
|
|
467
|
+
variable: varName,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
// Track method definitions in classes
|
|
473
|
+
if (comp.methods && comp.methods.length > 0) {
|
|
474
|
+
comp.methods.forEach((method) => {
|
|
475
|
+
const methodId = `${compId}:method:${method.name}`;
|
|
476
|
+
const methodNode = this.upsertNode(methodId, "method", "", {
|
|
477
|
+
className: comp.name,
|
|
478
|
+
methodName: method.name,
|
|
479
|
+
isAsync: method.isAsync,
|
|
480
|
+
});
|
|
481
|
+
compNode.addRelationship(methodId, "defines", {
|
|
482
|
+
methodName: method.name,
|
|
483
|
+
});
|
|
484
|
+
methodNode.addRelationship(compId, "definedBy", {
|
|
485
|
+
className: comp.name,
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Find a node by name and type
|
|
493
|
+
*/
|
|
494
|
+
findNodeByName(name, type) {
|
|
495
|
+
for (const [nodeId, node] of this.nodes.entries()) {
|
|
496
|
+
if (node.type === type &&
|
|
497
|
+
(node.metadata.name === name || nodeId.endsWith(`:${type}:${name}`))) {
|
|
498
|
+
return nodeId;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Extract code block from position
|
|
505
|
+
*/
|
|
506
|
+
extractBlock(content, startIndex) {
|
|
507
|
+
let depth = 0;
|
|
508
|
+
let started = false;
|
|
509
|
+
let endIndex = startIndex;
|
|
510
|
+
for (let i = startIndex; i < content.length; i++) {
|
|
511
|
+
if (content[i] === "{") {
|
|
512
|
+
depth++;
|
|
513
|
+
started = true;
|
|
514
|
+
}
|
|
515
|
+
else if (content[i] === "}") {
|
|
516
|
+
depth--;
|
|
517
|
+
if (started && depth === 0) {
|
|
518
|
+
endIndex = i + 1;
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return content.substring(startIndex, endIndex);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Extract statement (for arrow functions, etc.)
|
|
527
|
+
*/
|
|
528
|
+
extractStatement(content, startIndex) {
|
|
529
|
+
const endIndex = content.indexOf(";", startIndex);
|
|
530
|
+
return content.substring(startIndex, endIndex + 1);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Enhanced: Use pattern matching to analyze code structure
|
|
534
|
+
*/
|
|
535
|
+
analyzeWithPatterns(code) {
|
|
536
|
+
const matches = this.patternMatcher.match(code);
|
|
537
|
+
const analysis = {
|
|
538
|
+
patterns: {},
|
|
539
|
+
complexity: 0,
|
|
540
|
+
structures: [],
|
|
541
|
+
};
|
|
542
|
+
matches.forEach((match) => {
|
|
543
|
+
if (!analysis.patterns[match.pattern]) {
|
|
544
|
+
analysis.patterns[match.pattern] = [];
|
|
545
|
+
}
|
|
546
|
+
analysis.patterns[match.pattern].push(match);
|
|
547
|
+
analysis.structures.push(match.pattern);
|
|
548
|
+
});
|
|
549
|
+
// Calculate complexity based on patterns found
|
|
550
|
+
analysis.complexity = this.calculateComplexity(analysis.patterns);
|
|
551
|
+
return analysis;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Calculate code complexity score
|
|
555
|
+
*/
|
|
556
|
+
calculateComplexity(patterns) {
|
|
557
|
+
let score = 0;
|
|
558
|
+
// Weight different patterns
|
|
559
|
+
const weights = {
|
|
560
|
+
FUNCTION_DECLARATION: 2,
|
|
561
|
+
CLASS_DECLARATION: 3,
|
|
562
|
+
CLASS_INHERITANCE: 4,
|
|
563
|
+
ASYNC_FUNCTION: 3,
|
|
564
|
+
METHOD_CALL: 1,
|
|
565
|
+
ARROW_FUNCTION: 2,
|
|
566
|
+
};
|
|
567
|
+
Object.entries(patterns).forEach(([patternName, matches]) => {
|
|
568
|
+
const weight = weights[patternName] || 1;
|
|
569
|
+
score += matches.length * weight;
|
|
570
|
+
});
|
|
571
|
+
return score;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Query context with caching
|
|
575
|
+
*/
|
|
576
|
+
queryContext(query) {
|
|
577
|
+
const { type, id, relatedFiles, maxTokens = 4000 } = query;
|
|
578
|
+
if (type === "question") {
|
|
579
|
+
return this.queryByQuestion({
|
|
580
|
+
question: id,
|
|
581
|
+
relatedFiles,
|
|
582
|
+
maxTokens,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
const cacheKey = JSON.stringify(query);
|
|
586
|
+
// Check cache
|
|
587
|
+
if (this.cache.has(cacheKey)) {
|
|
588
|
+
const cached = this.cache.get(cacheKey);
|
|
589
|
+
if (!cached) {
|
|
590
|
+
return this.buildContext(type, id, relatedFiles, maxTokens);
|
|
591
|
+
}
|
|
592
|
+
cached.node?.access();
|
|
593
|
+
this.cacheHits++;
|
|
594
|
+
this.totalTokensSaved += cached.tokenCount;
|
|
595
|
+
runtimeEventBus.emit(RuntimeEvents.CACHE_HIT, {
|
|
596
|
+
query,
|
|
597
|
+
tokensSaved: cached.tokenCount,
|
|
598
|
+
totalSaved: this.totalTokensSaved,
|
|
599
|
+
});
|
|
600
|
+
return cached.context;
|
|
601
|
+
}
|
|
602
|
+
this.cacheMisses++;
|
|
603
|
+
// Build context
|
|
604
|
+
const context = this.buildContext(type, id, relatedFiles, maxTokens);
|
|
605
|
+
// Cache result
|
|
606
|
+
this.updateCache(cacheKey, context);
|
|
607
|
+
runtimeEventBus.emit(RuntimeEvents.CACHE_MISS, { query });
|
|
608
|
+
runtimeEventBus.emit(RuntimeEvents.TOKEN_USAGE, {
|
|
609
|
+
tokens: context.tokenCount,
|
|
610
|
+
cached: false,
|
|
611
|
+
});
|
|
612
|
+
return context;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Build context from knowledge graph
|
|
616
|
+
*/
|
|
617
|
+
buildContext(type, id, relatedFiles = [], maxTokens = 4000) {
|
|
618
|
+
const relevantNodes = [];
|
|
619
|
+
let tokenCount = 0;
|
|
620
|
+
void type;
|
|
621
|
+
// Get primary node
|
|
622
|
+
const primaryNode = this.nodes.get(id);
|
|
623
|
+
if (primaryNode) {
|
|
624
|
+
relevantNodes.push(primaryNode);
|
|
625
|
+
tokenCount += primaryNode.tokenCount;
|
|
626
|
+
primaryNode.access();
|
|
627
|
+
}
|
|
628
|
+
// Get related nodes by relationships
|
|
629
|
+
if (primaryNode) {
|
|
630
|
+
primaryNode.relationships.forEach(({ nodeId }) => {
|
|
631
|
+
const relNode = this.nodes.get(nodeId);
|
|
632
|
+
if (relNode && tokenCount + relNode.tokenCount <= maxTokens) {
|
|
633
|
+
relevantNodes.push(relNode);
|
|
634
|
+
tokenCount += relNode.tokenCount;
|
|
635
|
+
relNode.access();
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
// Get nodes from related files
|
|
640
|
+
relatedFiles.forEach((filePath) => {
|
|
641
|
+
const fileNode = this.nodes.get(filePath);
|
|
642
|
+
if (fileNode && tokenCount + fileNode.tokenCount <= maxTokens) {
|
|
643
|
+
relevantNodes.push(fileNode);
|
|
644
|
+
tokenCount += fileNode.tokenCount;
|
|
645
|
+
fileNode.access();
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
// Sort by relevance score
|
|
649
|
+
relevantNodes.sort((a, b) => b.score - a.score);
|
|
650
|
+
return {
|
|
651
|
+
nodes: relevantNodes,
|
|
652
|
+
tokenCount,
|
|
653
|
+
contextText: relevantNodes.map((n) => n.content).join("\n\n"),
|
|
654
|
+
retrieval: {
|
|
655
|
+
mode: "entity",
|
|
656
|
+
},
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
extractQuestionTerms(question) {
|
|
660
|
+
const stopWords = new Set([
|
|
661
|
+
"a",
|
|
662
|
+
"an",
|
|
663
|
+
"and",
|
|
664
|
+
"are",
|
|
665
|
+
"as",
|
|
666
|
+
"at",
|
|
667
|
+
"be",
|
|
668
|
+
"by",
|
|
669
|
+
"for",
|
|
670
|
+
"from",
|
|
671
|
+
"how",
|
|
672
|
+
"i",
|
|
673
|
+
"in",
|
|
674
|
+
"is",
|
|
675
|
+
"it",
|
|
676
|
+
"of",
|
|
677
|
+
"on",
|
|
678
|
+
"or",
|
|
679
|
+
"that",
|
|
680
|
+
"the",
|
|
681
|
+
"this",
|
|
682
|
+
"to",
|
|
683
|
+
"what",
|
|
684
|
+
"where",
|
|
685
|
+
"which",
|
|
686
|
+
"who",
|
|
687
|
+
"why",
|
|
688
|
+
"with",
|
|
689
|
+
"you",
|
|
690
|
+
]);
|
|
691
|
+
const normalized = (question || "").toLowerCase();
|
|
692
|
+
const rawTerms = normalized.match(/[a-z0-9_./-]+/g) || [];
|
|
693
|
+
return Array.from(new Set(rawTerms.filter((term) => term.length > 1 && !stopWords.has(term))));
|
|
694
|
+
}
|
|
695
|
+
scoreNodeForQuestion(node, terms, relatedFiles) {
|
|
696
|
+
if (terms.length === 0) {
|
|
697
|
+
return 0;
|
|
698
|
+
}
|
|
699
|
+
const metaName = String(node.metadata.name || "").toLowerCase();
|
|
700
|
+
const nodeId = node.id.toLowerCase();
|
|
701
|
+
const pathHint = String(node.metadata.filePath || node.metadata.path || "").toLowerCase();
|
|
702
|
+
let score = 0;
|
|
703
|
+
terms.forEach((term) => {
|
|
704
|
+
if (metaName === term) {
|
|
705
|
+
score += 8;
|
|
706
|
+
}
|
|
707
|
+
else if (metaName.includes(term)) {
|
|
708
|
+
score += 4;
|
|
709
|
+
}
|
|
710
|
+
if (nodeId.includes(term)) {
|
|
711
|
+
score += 2;
|
|
712
|
+
}
|
|
713
|
+
if (pathHint.includes(term)) {
|
|
714
|
+
score += 2;
|
|
715
|
+
}
|
|
716
|
+
if (node.content.toLowerCase().includes(term)) {
|
|
717
|
+
score += 1;
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
const typeBoost = {
|
|
721
|
+
file: 0.9,
|
|
722
|
+
function: 1.3,
|
|
723
|
+
class: 1.2,
|
|
724
|
+
import: 0.7,
|
|
725
|
+
context: 0.8,
|
|
726
|
+
method: 1.1,
|
|
727
|
+
interface: 1,
|
|
728
|
+
variable: 0.9,
|
|
729
|
+
};
|
|
730
|
+
score *= typeBoost[node.type] || 1;
|
|
731
|
+
if (relatedFiles.length > 0 &&
|
|
732
|
+
relatedFiles.some((filePath) => node.id === filePath || pathHint.includes(filePath))) {
|
|
733
|
+
score += 3;
|
|
734
|
+
}
|
|
735
|
+
return score;
|
|
736
|
+
}
|
|
737
|
+
findAnchorNodes(question, relatedFiles = [], limit = 8) {
|
|
738
|
+
const terms = this.extractQuestionTerms(question);
|
|
739
|
+
const scored = [];
|
|
740
|
+
this.nodes.forEach((node) => {
|
|
741
|
+
const score = this.scoreNodeForQuestion(node, terms, relatedFiles);
|
|
742
|
+
if (score > 0) {
|
|
743
|
+
scored.push({ node, score });
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
scored.sort((a, b) => b.score - a.score);
|
|
747
|
+
return {
|
|
748
|
+
terms,
|
|
749
|
+
anchors: scored.slice(0, limit),
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
getRelationshipWeight(type) {
|
|
753
|
+
const weights = {
|
|
754
|
+
contains: 1.2,
|
|
755
|
+
belongsTo: 1,
|
|
756
|
+
imports: 1.1,
|
|
757
|
+
calls: 1.3,
|
|
758
|
+
extends: 1.2,
|
|
759
|
+
implements: 1.1,
|
|
760
|
+
uses: 0.9,
|
|
761
|
+
defines: 1.1,
|
|
762
|
+
definedBy: 1,
|
|
763
|
+
};
|
|
764
|
+
return weights[type] || 0.8;
|
|
765
|
+
}
|
|
766
|
+
collectNeighbors(nodeId) {
|
|
767
|
+
const neighbors = [];
|
|
768
|
+
const currentNode = this.nodes.get(nodeId);
|
|
769
|
+
if (currentNode) {
|
|
770
|
+
currentNode.relationships.forEach((rel) => {
|
|
771
|
+
neighbors.push({
|
|
772
|
+
nodeId: rel.nodeId,
|
|
773
|
+
edgeWeight: this.getRelationshipWeight(rel.type) * (rel.confidence || 1),
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
this.nodes.forEach((node) => {
|
|
778
|
+
node.relationships.forEach((rel) => {
|
|
779
|
+
if (rel.nodeId === nodeId) {
|
|
780
|
+
neighbors.push({
|
|
781
|
+
nodeId: node.id,
|
|
782
|
+
edgeWeight: this.getRelationshipWeight(rel.type) * (rel.confidence || 1),
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
return neighbors;
|
|
788
|
+
}
|
|
789
|
+
queryByQuestion(query) {
|
|
790
|
+
const { question, anchorId, relatedFiles = [], maxTokens = 4000, maxDepth = 2, } = query;
|
|
791
|
+
const { terms, anchors } = this.findAnchorNodes(question, relatedFiles);
|
|
792
|
+
const candidates = new Map();
|
|
793
|
+
const queue = [];
|
|
794
|
+
const visited = new Set();
|
|
795
|
+
if (anchorId && this.nodes.has(anchorId)) {
|
|
796
|
+
queue.push({ nodeId: anchorId, score: 10, depth: 0 });
|
|
797
|
+
}
|
|
798
|
+
anchors.forEach(({ node, score }) => {
|
|
799
|
+
queue.push({ nodeId: node.id, score, depth: 0 });
|
|
800
|
+
});
|
|
801
|
+
while (queue.length > 0) {
|
|
802
|
+
const current = queue.shift();
|
|
803
|
+
if (!current) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
const dedupeKey = `${current.nodeId}:${current.depth}`;
|
|
807
|
+
if (visited.has(dedupeKey)) {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
visited.add(dedupeKey);
|
|
811
|
+
const node = this.nodes.get(current.nodeId);
|
|
812
|
+
if (!node) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
const pathScore = current.score / (current.depth + 1);
|
|
816
|
+
const recencyBonus = node.accessCount > 0 ? 0.2 : 0;
|
|
817
|
+
const totalScore = pathScore + recencyBonus;
|
|
818
|
+
const existing = candidates.get(node.id);
|
|
819
|
+
if (!existing || totalScore > existing.score) {
|
|
820
|
+
candidates.set(node.id, {
|
|
821
|
+
node,
|
|
822
|
+
score: totalScore,
|
|
823
|
+
depth: current.depth,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
if (current.depth >= maxDepth) {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
this.collectNeighbors(node.id).forEach((neighbor) => {
|
|
830
|
+
if (!this.nodes.has(neighbor.nodeId)) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
queue.push({
|
|
834
|
+
nodeId: neighbor.nodeId,
|
|
835
|
+
score: current.score * neighbor.edgeWeight,
|
|
836
|
+
depth: current.depth + 1,
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
const ranked = Array.from(candidates.values()).sort((a, b) => b.score - a.score);
|
|
841
|
+
const selectedNodes = [];
|
|
842
|
+
let tokenCount = 0;
|
|
843
|
+
ranked.forEach(({ node }) => {
|
|
844
|
+
if (tokenCount + node.tokenCount > maxTokens) {
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
selectedNodes.push(node);
|
|
848
|
+
tokenCount += node.tokenCount;
|
|
849
|
+
node.access();
|
|
850
|
+
});
|
|
851
|
+
return {
|
|
852
|
+
nodes: selectedNodes,
|
|
853
|
+
tokenCount,
|
|
854
|
+
contextText: selectedNodes.map((node) => node.content).join("\n\n"),
|
|
855
|
+
retrieval: {
|
|
856
|
+
mode: "question",
|
|
857
|
+
question,
|
|
858
|
+
anchorNodeIds: anchors.map(({ node }) => node.id).slice(0, 5),
|
|
859
|
+
hops: maxDepth,
|
|
860
|
+
matchedTerms: terms,
|
|
861
|
+
},
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Update cache with LRU eviction
|
|
866
|
+
*/
|
|
867
|
+
updateCache(key, context) {
|
|
868
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
869
|
+
// Evict least recently used
|
|
870
|
+
let lruKey = null;
|
|
871
|
+
let lruScore = Infinity;
|
|
872
|
+
this.cache.forEach((value, k) => {
|
|
873
|
+
const nodeScore = value.node?.score ?? Infinity;
|
|
874
|
+
if (nodeScore < lruScore) {
|
|
875
|
+
lruScore = nodeScore;
|
|
876
|
+
lruKey = k;
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
if (lruKey) {
|
|
880
|
+
this.cache.delete(lruKey);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
this.cache.set(key, {
|
|
884
|
+
context,
|
|
885
|
+
tokenCount: context.tokenCount,
|
|
886
|
+
timestamp: Date.now(),
|
|
887
|
+
node: context.nodes[0],
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Update context when code changes
|
|
892
|
+
*/
|
|
893
|
+
updateContext(data) {
|
|
894
|
+
const { filePath, content } = data;
|
|
895
|
+
// Re-index the file
|
|
896
|
+
this.indexFile({
|
|
897
|
+
path: filePath,
|
|
898
|
+
name: filePath.split("/").pop(),
|
|
899
|
+
content,
|
|
900
|
+
language: this.detectLanguage(filePath),
|
|
901
|
+
});
|
|
902
|
+
// Invalidate related cache entries
|
|
903
|
+
this.invalidateCache(filePath);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Invalidate cache entries related to a file
|
|
907
|
+
*/
|
|
908
|
+
invalidateCache(filePath) {
|
|
909
|
+
const keysToDelete = [];
|
|
910
|
+
this.cache.forEach((value, key) => {
|
|
911
|
+
if (key.includes(filePath)) {
|
|
912
|
+
keysToDelete.push(key);
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
keysToDelete.forEach((key) => this.cache.delete(key));
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Detect language from file extension
|
|
919
|
+
*/
|
|
920
|
+
detectLanguage(filePath) {
|
|
921
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
922
|
+
const langMap = {
|
|
923
|
+
js: "javascript",
|
|
924
|
+
jsx: "jsx",
|
|
925
|
+
ts: "typescript",
|
|
926
|
+
tsx: "tsx",
|
|
927
|
+
py: "python",
|
|
928
|
+
java: "java",
|
|
929
|
+
go: "go",
|
|
930
|
+
};
|
|
931
|
+
if (!ext) {
|
|
932
|
+
return "text";
|
|
933
|
+
}
|
|
934
|
+
return langMap[ext] || "text";
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Get statistics for telemetry
|
|
938
|
+
*/
|
|
939
|
+
getStatistics() {
|
|
940
|
+
const totalRequests = this.cacheHits + this.cacheMisses;
|
|
941
|
+
const hitRate = totalRequests > 0 ? this.cacheHits / totalRequests : 0;
|
|
942
|
+
return {
|
|
943
|
+
nodeCount: this.nodes.size,
|
|
944
|
+
cacheSize: this.cache.size,
|
|
945
|
+
cacheHits: this.cacheHits,
|
|
946
|
+
cacheMisses: this.cacheMisses,
|
|
947
|
+
hitRate: (hitRate * 100).toFixed(2) + "%",
|
|
948
|
+
totalTokensSaved: this.totalTokensSaved,
|
|
949
|
+
estimatedCostSaved: (this.totalTokensSaved * 0.00002).toFixed(4), // $0.00002 per token
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Export graph for visualization
|
|
954
|
+
*/
|
|
955
|
+
exportGraph() {
|
|
956
|
+
const nodes = [];
|
|
957
|
+
const edges = [];
|
|
958
|
+
this.nodes.forEach((node) => {
|
|
959
|
+
nodes.push({
|
|
960
|
+
id: node.id,
|
|
961
|
+
type: node.type,
|
|
962
|
+
label: node.metadata.name || node.id,
|
|
963
|
+
size: node.tokenCount,
|
|
964
|
+
accessCount: node.accessCount,
|
|
965
|
+
});
|
|
966
|
+
node.relationships.forEach(({ nodeId, type }) => {
|
|
967
|
+
edges.push({
|
|
968
|
+
source: node.id,
|
|
969
|
+
target: nodeId,
|
|
970
|
+
type,
|
|
971
|
+
});
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
return { nodes, edges };
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Clear all data
|
|
978
|
+
*/
|
|
979
|
+
clear() {
|
|
980
|
+
this.nodes.clear();
|
|
981
|
+
this.cache.clear();
|
|
982
|
+
this.totalTokensSaved = 0;
|
|
983
|
+
this.cacheHits = 0;
|
|
984
|
+
this.cacheMisses = 0;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
// Singleton instance
|
|
988
|
+
export const knowledgeGraph = new KnowledgeGraph();
|
|
989
|
+
export default KnowledgeGraph;
|