@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.
Files changed (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +690 -0
  3. package/dist/api/acp/acpServer.d.ts +23 -0
  4. package/dist/api/acp/acpServer.js +814 -0
  5. package/dist/api/acp/index.d.ts +2 -0
  6. package/dist/api/acp/index.js +2 -0
  7. package/dist/api/acp/irisClient.d.ts +58 -0
  8. package/dist/api/acp/irisClient.js +214 -0
  9. package/dist/api/agent.d.ts +14 -0
  10. package/dist/api/agent.js +4171 -0
  11. package/dist/api/backendService.d.ts +4 -0
  12. package/dist/api/backendService.js +14 -0
  13. package/dist/api/core/agent/host/AgentContract.d.ts +82 -0
  14. package/dist/api/core/agent/host/AgentContract.js +1 -0
  15. package/dist/api/core/agent/host/AgentRegistry.d.ts +24 -0
  16. package/dist/api/core/agent/host/AgentRegistry.js +125 -0
  17. package/dist/api/core/agent/host/defaultRegistry.d.ts +15 -0
  18. package/dist/api/core/agent/host/defaultRegistry.js +46 -0
  19. package/dist/api/core/agent/host/externalAgentLifecycle.d.ts +65 -0
  20. package/dist/api/core/agent/host/externalAgentLifecycle.js +117 -0
  21. package/dist/api/core/agent/host/hostSessionManager.d.ts +111 -0
  22. package/dist/api/core/agent/host/hostSessionManager.js +352 -0
  23. package/dist/api/core/agent/host/index.d.ts +5 -0
  24. package/dist/api/core/agent/host/index.js +5 -0
  25. package/dist/api/core/agent/index.d.ts +87 -0
  26. package/dist/api/core/agent/index.js +1024 -0
  27. package/dist/api/core/agent/publicContracts.d.ts +35 -0
  28. package/dist/api/core/agent/publicContracts.js +103 -0
  29. package/dist/api/core/agent/tools/applyDiff.d.ts +51 -0
  30. package/dist/api/core/agent/tools/applyDiff.js +131 -0
  31. package/dist/api/core/agent/tools/backgroundTasks.d.ts +240 -0
  32. package/dist/api/core/agent/tools/backgroundTasks.js +313 -0
  33. package/dist/api/core/agent/tools/createDirectory.d.ts +43 -0
  34. package/dist/api/core/agent/tools/createDirectory.js +77 -0
  35. package/dist/api/core/agent/tools/deleteFile.d.ts +37 -0
  36. package/dist/api/core/agent/tools/deleteFile.js +66 -0
  37. package/dist/api/core/agent/tools/editFile.d.ts +73 -0
  38. package/dist/api/core/agent/tools/editFile.js +173 -0
  39. package/dist/api/core/agent/tools/executeCommand.d.ts +59 -0
  40. package/dist/api/core/agent/tools/executeCommand.js +250 -0
  41. package/dist/api/core/agent/tools/fileContent.d.ts +54 -0
  42. package/dist/api/core/agent/tools/fileContent.js +227 -0
  43. package/dist/api/core/agent/tools/findDefinition.d.ts +72 -0
  44. package/dist/api/core/agent/tools/findDefinition.js +109 -0
  45. package/dist/api/core/agent/tools/findReferences.d.ts +86 -0
  46. package/dist/api/core/agent/tools/findReferences.js +135 -0
  47. package/dist/api/core/agent/tools/formatDocument.d.ts +67 -0
  48. package/dist/api/core/agent/tools/formatDocument.js +90 -0
  49. package/dist/api/core/agent/tools/getCodeActions.d.ts +77 -0
  50. package/dist/api/core/agent/tools/getCodeActions.js +97 -0
  51. package/dist/api/core/agent/tools/getCodeCompletion.d.ts +77 -0
  52. package/dist/api/core/agent/tools/getCodeCompletion.js +174 -0
  53. package/dist/api/core/agent/tools/getCodeContext.d.ts +14 -0
  54. package/dist/api/core/agent/tools/getCodeContext.js +282 -0
  55. package/dist/api/core/agent/tools/getSignatureHelp.d.ts +61 -0
  56. package/dist/api/core/agent/tools/getSignatureHelp.js +85 -0
  57. package/dist/api/core/agent/tools/getSymbols.d.ts +50 -0
  58. package/dist/api/core/agent/tools/getSymbols.js +164 -0
  59. package/dist/api/core/agent/tools/getSymbolsLSP.d.ts +58 -0
  60. package/dist/api/core/agent/tools/getSymbolsLSP.js +147 -0
  61. package/dist/api/core/agent/tools/getTypeInfo.d.ts +70 -0
  62. package/dist/api/core/agent/tools/getTypeInfo.js +84 -0
  63. package/dist/api/core/agent/tools/getWorkspaceInfo.d.ts +80 -0
  64. package/dist/api/core/agent/tools/getWorkspaceInfo.js +281 -0
  65. package/dist/api/core/agent/tools/getWorkspaceSymbols.d.ts +53 -0
  66. package/dist/api/core/agent/tools/getWorkspaceSymbols.js +97 -0
  67. package/dist/api/core/agent/tools/grepSearch.d.ts +100 -0
  68. package/dist/api/core/agent/tools/grepSearch.js +211 -0
  69. package/dist/api/core/agent/tools/languageModelToolsIntegration.d.ts +13 -0
  70. package/dist/api/core/agent/tools/languageModelToolsIntegration.js +22 -0
  71. package/dist/api/core/agent/tools/listDirectory.d.ts +66 -0
  72. package/dist/api/core/agent/tools/listDirectory.js +161 -0
  73. package/dist/api/core/agent/tools/mcpTools.d.ts +48 -0
  74. package/dist/api/core/agent/tools/mcpTools.js +442 -0
  75. package/dist/api/core/agent/tools/queryKnowledgeGraph.d.ts +120 -0
  76. package/dist/api/core/agent/tools/queryKnowledgeGraph.js +306 -0
  77. package/dist/api/core/agent/tools/readFile.d.ts +92 -0
  78. package/dist/api/core/agent/tools/readFile.js +337 -0
  79. package/dist/api/core/agent/tools/renameFile.d.ts +49 -0
  80. package/dist/api/core/agent/tools/renameFile.js +86 -0
  81. package/dist/api/core/agent/tools/renameSymbol.d.ts +70 -0
  82. package/dist/api/core/agent/tools/renameSymbol.js +106 -0
  83. package/dist/api/core/agent/tools/runTerminalCommand.d.ts +72 -0
  84. package/dist/api/core/agent/tools/runTerminalCommand.js +69 -0
  85. package/dist/api/core/agent/tools/searchFiles.d.ts +82 -0
  86. package/dist/api/core/agent/tools/searchFiles.js +280 -0
  87. package/dist/api/core/agent/tools/terminalAutoApprove.d.ts +3 -0
  88. package/dist/api/core/agent/tools/terminalAutoApprove.js +676 -0
  89. package/dist/api/core/agent/tools/treeTraversal.d.ts +60 -0
  90. package/dist/api/core/agent/tools/treeTraversal.js +217 -0
  91. package/dist/api/core/agent/tools/webSearch.d.ts +37 -0
  92. package/dist/api/core/agent/tools/webSearch.js +80 -0
  93. package/dist/api/core/agent/tools/writeFile.d.ts +59 -0
  94. package/dist/api/core/agent/tools/writeFile.js +97 -0
  95. package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.d.ts +12 -0
  96. package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.js +67 -0
  97. package/dist/api/core/agent/utils/diffUtils.d.ts +14 -0
  98. package/dist/api/core/agent/utils/diffUtils.js +39 -0
  99. package/dist/api/core/agent/utils/environmentSnapshot.d.ts +42 -0
  100. package/dist/api/core/agent/utils/environmentSnapshot.js +213 -0
  101. package/dist/api/core/agent/utils/errorRecovery.d.ts +49 -0
  102. package/dist/api/core/agent/utils/errorRecovery.js +229 -0
  103. package/dist/api/core/agent/utils/multimodalTokenLimiter.d.ts +8 -0
  104. package/dist/api/core/agent/utils/multimodalTokenLimiter.js +238 -0
  105. package/dist/api/core/agent/utils/openRouterModelSettings.d.ts +12 -0
  106. package/dist/api/core/agent/utils/openRouterModelSettings.js +34 -0
  107. package/dist/api/core/agent/utils/pathRecovery.d.ts +1 -0
  108. package/dist/api/core/agent/utils/pathRecovery.js +29 -0
  109. package/dist/api/core/agent/utils/repoMapIndex.d.ts +3 -0
  110. package/dist/api/core/agent/utils/repoMapIndex.js +197 -0
  111. package/dist/api/core/agent/utils/skillsDiscovery.d.ts +10 -0
  112. package/dist/api/core/agent/utils/skillsDiscovery.js +50 -0
  113. package/dist/api/core/agent/utils/toolCallBudget.d.ts +9 -0
  114. package/dist/api/core/agent/utils/toolCallBudget.js +55 -0
  115. package/dist/api/core/agent/utils/toolLifecycle.d.ts +30 -0
  116. package/dist/api/core/agent/utils/toolLifecycle.js +361 -0
  117. package/dist/api/core/agent/utils/toolResultSafetyProcessor.d.ts +12 -0
  118. package/dist/api/core/agent/utils/toolResultSafetyProcessor.js +57 -0
  119. package/dist/api/core/agent/utils/workspaceMutationBridge.d.ts +13 -0
  120. package/dist/api/core/agent/utils/workspaceMutationBridge.js +149 -0
  121. package/dist/api/core/agent/utils/workspacePathGuard.d.ts +7 -0
  122. package/dist/api/core/agent/utils/workspacePathGuard.js +79 -0
  123. package/dist/api/core/containers/chat/toolResultSerialization.d.ts +8 -0
  124. package/dist/api/core/containers/chat/toolResultSerialization.js +71 -0
  125. package/dist/api/core/library/BrowserManager.d.ts +11 -0
  126. package/dist/api/core/library/BrowserManager.js +66 -0
  127. package/dist/api/core/library/desktopWorkspace.d.ts +1 -0
  128. package/dist/api/core/library/desktopWorkspace.js +3 -0
  129. package/dist/api/core/library/extensionManager.d.ts +10 -0
  130. package/dist/api/core/library/extensionManager.js +27 -0
  131. package/dist/api/core/library/knowledgeGraph.d.ts +268 -0
  132. package/dist/api/core/library/knowledgeGraph.js +989 -0
  133. package/dist/api/core/library/languageModelTools.d.ts +141 -0
  134. package/dist/api/core/library/languageModelTools.js +207 -0
  135. package/dist/api/core/library/localRuntime.d.ts +23 -0
  136. package/dist/api/core/library/localRuntime.js +144 -0
  137. package/dist/api/core/library/lsp/coreLsp.d.ts +367 -0
  138. package/dist/api/core/library/lsp/coreLsp.js +2076 -0
  139. package/dist/api/core/library/lsp/protocol.d.ts +25 -0
  140. package/dist/api/core/library/lsp/protocol.js +1 -0
  141. package/dist/api/core/library/lsp/serverManager.d.ts +37 -0
  142. package/dist/api/core/library/lsp/serverManager.js +427 -0
  143. package/dist/api/core/library/mcpServerProvider.d.ts +69 -0
  144. package/dist/api/core/library/mcpServerProvider.js +152 -0
  145. package/dist/api/core/library/mcpSettings.d.ts +20 -0
  146. package/dist/api/core/library/mcpSettings.js +131 -0
  147. package/dist/api/core/library/patternMatcher.d.ts +66 -0
  148. package/dist/api/core/library/patternMatcher.js +284 -0
  149. package/dist/api/core/library/regexEscape.d.ts +4 -0
  150. package/dist/api/core/library/regexEscape.js +4 -0
  151. package/dist/api/core/library/runtimeEventBus.d.ts +27 -0
  152. package/dist/api/core/library/runtimeEventBus.js +78 -0
  153. package/dist/api/core/library/safetyMiddleware.d.ts +98 -0
  154. package/dist/api/core/library/safetyMiddleware.js +215 -0
  155. package/dist/api/core/library/tauri.d.ts +71 -0
  156. package/dist/api/core/library/tauri.js +222 -0
  157. package/dist/api/core/library/tauriImport.d.ts +5 -0
  158. package/dist/api/core/library/tauriImport.js +7 -0
  159. package/dist/api/core/library/terminalAutoApproveSettings.d.ts +32 -0
  160. package/dist/api/core/library/terminalAutoApproveSettings.js +529 -0
  161. package/dist/api/core/library/workspaceIdentity.d.ts +10 -0
  162. package/dist/api/core/library/workspaceIdentity.js +49 -0
  163. package/dist/api/core/library/workspaceSummary.d.ts +19 -0
  164. package/dist/api/core/library/workspaceSummary.js +35 -0
  165. package/dist/api/core/skills/agent-customization/SKILL.md +27 -0
  166. package/dist/api/core/skills/bug-fix/SKILL.md +21 -0
  167. package/dist/api/core/skills/create-pr/SKILL.md +32 -0
  168. package/dist/api/core/skills/dev-server/SKILL.md +20 -0
  169. package/dist/api/core/skills/fix-suggestions/SKILL.md +21 -0
  170. package/dist/api/core/skills/github-search/SKILL.md +21 -0
  171. package/dist/api/core/skills/github-summary/SKILL.md +25 -0
  172. package/dist/api/core/skills/integration-tests/SKILL.md +29 -0
  173. package/dist/api/core/skills/pr-comments/SKILL.md +23 -0
  174. package/dist/api/core/skills/project-setup/SKILL.md +20 -0
  175. package/dist/api/core/skills/search-integration/SKILL.md +19 -0
  176. package/dist/api/core/skills/search-results/SKILL.md +19 -0
  177. package/dist/api/core/skills/typescript-upgrade/SKILL.md +21 -0
  178. package/dist/api/data/runStore.d.ts +46 -0
  179. package/dist/api/data/runStore.js +244 -0
  180. package/dist/api/helpers/agentUtils.d.ts +11 -0
  181. package/dist/api/helpers/agentUtils.js +23 -0
  182. package/dist/api/helpers/modelTokenLimits.d.ts +2 -0
  183. package/dist/api/helpers/modelTokenLimits.js +158 -0
  184. package/dist/api/helpers/observationalMemory.d.ts +10 -0
  185. package/dist/api/helpers/observationalMemory.js +36 -0
  186. package/dist/api/helpers/promptBudget.d.ts +30 -0
  187. package/dist/api/helpers/promptBudget.js +216 -0
  188. package/dist/api/helpers/resolveImageMessageParts.d.ts +9 -0
  189. package/dist/api/helpers/resolveImageMessageParts.js +140 -0
  190. package/dist/api/helpers/slashCommands.d.ts +31 -0
  191. package/dist/api/helpers/slashCommands.js +129 -0
  192. package/dist/api/helpers/tokenUsage.d.ts +19 -0
  193. package/dist/api/helpers/tokenUsage.js +81 -0
  194. package/dist/api/routes/fileRoutes.d.ts +2 -0
  195. package/dist/api/routes/fileRoutes.js +108 -0
  196. package/dist/api/routes/lspDocumentRoutes.d.ts +4 -0
  197. package/dist/api/routes/lspDocumentRoutes.js +84 -0
  198. package/dist/api/routes/lspHierarchyRoutes.d.ts +4 -0
  199. package/dist/api/routes/lspHierarchyRoutes.js +122 -0
  200. package/dist/api/routes/lspPositionRoutes.d.ts +4 -0
  201. package/dist/api/routes/lspPositionRoutes.js +263 -0
  202. package/dist/api/routes/lspQueryRoutes.d.ts +4 -0
  203. package/dist/api/routes/lspQueryRoutes.js +679 -0
  204. package/dist/api/routes/lspResolveRoutes.d.ts +4 -0
  205. package/dist/api/routes/lspResolveRoutes.js +143 -0
  206. package/dist/api/routes/lspSemanticDocumentRoutes.d.ts +4 -0
  207. package/dist/api/routes/lspSemanticDocumentRoutes.js +98 -0
  208. package/dist/api/routes/semanticRoutes.d.ts +2 -0
  209. package/dist/api/routes/semanticRoutes.js +126 -0
  210. package/dist/cli.d.ts +8 -0
  211. package/dist/cli.js +160 -0
  212. package/dist/index.d.ts +1 -0
  213. package/dist/index.js +1 -0
  214. package/dist/server.d.ts +2 -0
  215. package/dist/server.js +38 -0
  216. 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;