claudecode-rlm 1.0.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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -0
  3. package/dist/config.d.ts +176 -0
  4. package/dist/config.d.ts.map +1 -0
  5. package/dist/config.js +103 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/graph/index.d.ts +10 -0
  8. package/dist/graph/index.d.ts.map +1 -0
  9. package/dist/graph/index.js +10 -0
  10. package/dist/graph/index.js.map +1 -0
  11. package/dist/graph/ingestion.d.ts +68 -0
  12. package/dist/graph/ingestion.d.ts.map +1 -0
  13. package/dist/graph/ingestion.js +417 -0
  14. package/dist/graph/ingestion.js.map +1 -0
  15. package/dist/graph/storage.d.ts +51 -0
  16. package/dist/graph/storage.d.ts.map +1 -0
  17. package/dist/graph/storage.js +552 -0
  18. package/dist/graph/storage.js.map +1 -0
  19. package/dist/graph/traversal.d.ts +54 -0
  20. package/dist/graph/traversal.d.ts.map +1 -0
  21. package/dist/graph/traversal.js +255 -0
  22. package/dist/graph/traversal.js.map +1 -0
  23. package/dist/graph/types.d.ts +152 -0
  24. package/dist/graph/types.d.ts.map +1 -0
  25. package/dist/graph/types.js +94 -0
  26. package/dist/graph/types.js.map +1 -0
  27. package/dist/index.d.ts +30 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +190 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/plugin-types.d.ts +96 -0
  32. package/dist/plugin-types.d.ts.map +1 -0
  33. package/dist/plugin-types.js +17 -0
  34. package/dist/plugin-types.js.map +1 -0
  35. package/dist/search/enhanced.d.ts +95 -0
  36. package/dist/search/enhanced.d.ts.map +1 -0
  37. package/dist/search/enhanced.js +194 -0
  38. package/dist/search/enhanced.js.map +1 -0
  39. package/dist/search/index.d.ts +8 -0
  40. package/dist/search/index.d.ts.map +1 -0
  41. package/dist/search/index.js +8 -0
  42. package/dist/search/index.js.map +1 -0
  43. package/dist/search/patterns.d.ts +38 -0
  44. package/dist/search/patterns.d.ts.map +1 -0
  45. package/dist/search/patterns.js +124 -0
  46. package/dist/search/patterns.js.map +1 -0
  47. package/dist/tools/graph-query.d.ts +14 -0
  48. package/dist/tools/graph-query.d.ts.map +1 -0
  49. package/dist/tools/graph-query.js +203 -0
  50. package/dist/tools/graph-query.js.map +1 -0
  51. package/dist/tools/index.d.ts +8 -0
  52. package/dist/tools/index.d.ts.map +1 -0
  53. package/dist/tools/index.js +8 -0
  54. package/dist/tools/index.js.map +1 -0
  55. package/dist/tools/memory.d.ts +20 -0
  56. package/dist/tools/memory.d.ts.map +1 -0
  57. package/dist/tools/memory.js +181 -0
  58. package/dist/tools/memory.js.map +1 -0
  59. package/package.json +66 -0
  60. package/src/config.ts +111 -0
  61. package/src/graph/index.ts +10 -0
  62. package/src/graph/ingestion.ts +528 -0
  63. package/src/graph/storage.ts +639 -0
  64. package/src/graph/traversal.ts +348 -0
  65. package/src/graph/types.ts +144 -0
  66. package/src/index.ts +238 -0
  67. package/src/plugin-types.ts +107 -0
  68. package/src/search/enhanced.ts +264 -0
  69. package/src/search/index.ts +23 -0
  70. package/src/search/patterns.ts +139 -0
  71. package/src/tools/graph-query.ts +257 -0
  72. package/src/tools/index.ts +8 -0
  73. package/src/tools/memory.ts +208 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Reference patterns for detecting context-related queries.
3
+ *
4
+ * These patterns trigger proactive context retrieval from the
5
+ * knowledge graph when detected in user messages.
6
+ */
7
+
8
+ /**
9
+ * Explicit memory reference patterns.
10
+ * High confidence that user is referring to past context.
11
+ */
12
+ export const EXPLICIT_MEMORY_PATTERNS: RegExp[] = [
13
+ // Direct memory references
14
+ /\bremember\s+(when|that|the|what|how)/i,
15
+ /\bdo\s+you\s+remember/i,
16
+ /\bcan\s+you\s+recall/i,
17
+ /\brecall\s+(when|that|the|what|how)/i,
18
+ /\byou\s+mentioned/i,
19
+ /\byou\s+said/i,
20
+ /\byou\s+told\s+me/i,
21
+ /\bwe\s+(discussed|talked\s+about)/i,
22
+
23
+ // Temporal references
24
+ /\bearlier\s+(you|we|I)/i,
25
+ /\bpreviously\s+(you|we|I)/i,
26
+ /\bbefore\s+(you|we|I)/i,
27
+ /\blast\s+time/i,
28
+ /\bthe\s+other\s+day/i,
29
+ /\ba\s+while\s+ago/i,
30
+ /\bsome\s+time\s+ago/i,
31
+
32
+ // Context continuation
33
+ /\bpick\s+up\s+where/i,
34
+ /\bcontinue\s+(from|where|with)/i,
35
+ /\bresume\s+(from|where)/i,
36
+ /\bback\s+to\s+(what|where)/i,
37
+ /\bgoing\s+back\s+to/i,
38
+ ]
39
+
40
+ /**
41
+ * Extended patterns from PinkyClawd.
42
+ * Broader context references and conversational cues.
43
+ */
44
+ export const EXTENDED_PATTERNS: RegExp[] = [
45
+ // Historical task references
46
+ /\bwhat\s+did\s+we\s+(do|try|use|build|create|implement|fix)/i,
47
+ /\bwhat\s+have\s+we\s+(done|tried|built|created|implemented|fixed)/i,
48
+ /\bwhere\s+were\s+we/i,
49
+ /\bwhere\s+did\s+we\s+(leave\s+off|stop)/i,
50
+ /\bhow\s+did\s+we\s+(do|handle|implement|fix|solve)/i,
51
+ /\bwhy\s+did\s+we\s+(decide|choose|use)/i,
52
+
53
+ // Continuation cues
54
+ /\blet's\s+(continue|resume|pick\s+up)/i,
55
+ /\blet's\s+go\s+back\s+to/i,
56
+ /\bcan\s+we\s+(continue|resume)/i,
57
+ /\bshall\s+we\s+(continue|resume)/i,
58
+
59
+ // Decision references
60
+ /\bthe\s+approach\s+we\s+(chose|decided|used)/i,
61
+ /\bthe\s+solution\s+we/i,
62
+ /\bthe\s+method\s+we/i,
63
+ /\bthe\s+way\s+we/i,
64
+ /\bour\s+(approach|solution|implementation)/i,
65
+
66
+ // Error/fix references
67
+ /\bthat\s+error\s+(we|you)/i,
68
+ /\bthe\s+bug\s+(we|you)/i,
69
+ /\bthe\s+issue\s+(we|you)/i,
70
+ /\bthe\s+problem\s+(we|you)/i,
71
+ /\bwhen\s+(it|that)\s+(broke|failed|crashed)/i,
72
+
73
+ // Feature references
74
+ /\bthe\s+feature\s+(we|you)/i,
75
+ /\bthat\s+functionality/i,
76
+ /\bthe\s+component\s+(we|you)/i,
77
+ /\bthe\s+module\s+(we|you)/i,
78
+
79
+ // Code references
80
+ /\bthat\s+function\s+(we|you)/i,
81
+ /\bthe\s+class\s+(we|you)/i,
82
+ /\bthat\s+file\s+(we|you)/i,
83
+ /\bthe\s+code\s+(we|you)/i,
84
+
85
+ // Discussion references
86
+ /\bas\s+we\s+(discussed|mentioned|said)/i,
87
+ /\blike\s+(we|you)\s+said/i,
88
+ /\baccording\s+to\s+(our|what\s+we)/i,
89
+ /\bbased\s+on\s+(our|what\s+we)/i,
90
+
91
+ // Session references
92
+ /\bin\s+our\s+(last|previous)\s+(session|conversation|chat)/i,
93
+ /\bearlier\s+in\s+this\s+(session|conversation)/i,
94
+ /\bfrom\s+our\s+(last|previous)/i,
95
+ ]
96
+
97
+ /**
98
+ * Task completion patterns.
99
+ * Indicates a task may have been completed.
100
+ */
101
+ export const TASK_COMPLETION_PATTERNS: RegExp[] = [
102
+ /\b(done|finished|completed|fixed|resolved|implemented|working\s+now)/i,
103
+ /\bthat\s+(should|seems\s+to)\s+(work|fix|solve)/i,
104
+ /\blet\s+me\s+know\s+if/i,
105
+ /\btry\s+(it|that)\s+(now|again)/i,
106
+ /\bshould\s+be\s+(good|fixed|working)/i,
107
+ /\bI've\s+(finished|completed|done|fixed)/i,
108
+ /\bthat's\s+(it|all|done)/i,
109
+ /\ball\s+(done|set|good)/i,
110
+ ]
111
+
112
+ /**
113
+ * All patterns combined.
114
+ */
115
+ export const ALL_PATTERNS: RegExp[] = [
116
+ ...EXPLICIT_MEMORY_PATTERNS,
117
+ ...EXTENDED_PATTERNS,
118
+ ]
119
+
120
+ /**
121
+ * Check if a message contains reference patterns.
122
+ */
123
+ export function detectsReference(message: string): boolean {
124
+ return ALL_PATTERNS.some((pattern) => pattern.test(message))
125
+ }
126
+
127
+ /**
128
+ * Get all matching patterns in a message.
129
+ */
130
+ export function getMatchingPatterns(message: string): RegExp[] {
131
+ return ALL_PATTERNS.filter((pattern) => pattern.test(message))
132
+ }
133
+
134
+ /**
135
+ * Check if a message indicates task completion.
136
+ */
137
+ export function detectsTaskCompletion(message: string): boolean {
138
+ return TASK_COMPLETION_PATTERNS.some((pattern) => pattern.test(message))
139
+ }
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Graph query tool for searching the knowledge graph.
3
+ *
4
+ * Allows the LLM to query the graph structure for:
5
+ * - Entity relationships
6
+ * - Chunk context expansion
7
+ * - Path finding between concepts
8
+ */
9
+
10
+ import { z } from "zod"
11
+ import type { ToolDefinition, ToolResult } from "../plugin-types.js"
12
+ import {
13
+ GraphStorage,
14
+ GraphSearcher,
15
+ GraphTraverser,
16
+ NodeType,
17
+ type GraphNode,
18
+ } from "../graph/index.js"
19
+
20
+ /**
21
+ * Format a node for display.
22
+ */
23
+ function formatNode(node: GraphNode, index: number): string {
24
+ const lines = [`[${index + 1}] ${node.type.toUpperCase()}: ${node.id}`]
25
+ lines.push(` Content: ${node.content.slice(0, 200)}${node.content.length > 200 ? "..." : ""}`)
26
+ if (node.metadata && Object.keys(node.metadata).length > 0) {
27
+ lines.push(` Metadata: ${JSON.stringify(node.metadata)}`)
28
+ }
29
+ return lines.join("\n")
30
+ }
31
+
32
+ /**
33
+ * Graph query tool definition.
34
+ */
35
+ export const graphQueryTool: ToolDefinition = {
36
+ description: `Query the PinkyClawd knowledge graph to find related context and entities.
37
+
38
+ Operations:
39
+ - search: Find chunks matching a query (uses hybrid keyword + entity matching)
40
+ - entity: Find all chunks mentioning a specific entity (code element, file, concept)
41
+ - expand: Get related context around a specific node
42
+ - path: Find connection path between two entities
43
+
44
+ Use this tool when you need to:
45
+ - Find where something was discussed or defined
46
+ - Understand relationships between concepts
47
+ - Get historical context about a topic
48
+ - Navigate the conversation history structurally`,
49
+
50
+ args: {
51
+ operation: z
52
+ .enum(["search", "entity", "expand", "path"])
53
+ .describe("The operation to perform"),
54
+ query: z
55
+ .string()
56
+ .optional()
57
+ .describe("Search query or entity name (for search/entity operations)"),
58
+ nodeId: z
59
+ .string()
60
+ .optional()
61
+ .describe("Node ID to expand from (for expand operation)"),
62
+ startEntity: z
63
+ .string()
64
+ .optional()
65
+ .describe("Starting entity name (for path operation)"),
66
+ endEntity: z
67
+ .string()
68
+ .optional()
69
+ .describe("Ending entity name (for path operation)"),
70
+ limit: z
71
+ .number()
72
+ .optional()
73
+ .default(10)
74
+ .describe("Maximum results to return"),
75
+ },
76
+
77
+ execute: async (args, context): Promise<ToolResult> => {
78
+ const { operation, query, nodeId, startEntity, endEntity, limit = 10 } = args
79
+ const sessionID = context.sessionID
80
+
81
+ try {
82
+ switch (operation) {
83
+ case "search": {
84
+ if (!query) {
85
+ return {
86
+ title: "Graph Query - Search",
87
+ output: "Error: 'query' parameter is required for search operation",
88
+ }
89
+ }
90
+
91
+ const results = GraphSearcher.search(sessionID, query, {
92
+ limit,
93
+ expandContext: true,
94
+ })
95
+
96
+ if (results.length === 0) {
97
+ return {
98
+ title: "Graph Query - Search",
99
+ output: `No results found for query: "${query}"`,
100
+ }
101
+ }
102
+
103
+ const output = [
104
+ `Found ${results.length} results for "${query}":`,
105
+ "",
106
+ ...results.map((r, i) => formatNode(r.node, i)),
107
+ ].join("\n")
108
+
109
+ return {
110
+ title: "Graph Query - Search",
111
+ output,
112
+ metadata: { resultCount: results.length },
113
+ }
114
+ }
115
+
116
+ case "entity": {
117
+ if (!query) {
118
+ return {
119
+ title: "Graph Query - Entity",
120
+ output: "Error: 'query' parameter is required for entity operation",
121
+ }
122
+ }
123
+
124
+ const chunks = GraphTraverser.getEntityContext(sessionID, query, limit)
125
+
126
+ if (chunks.length === 0) {
127
+ return {
128
+ title: "Graph Query - Entity",
129
+ output: `No chunks found mentioning entity: "${query}"`,
130
+ }
131
+ }
132
+
133
+ const output = [
134
+ `Found ${chunks.length} chunks mentioning "${query}":`,
135
+ "",
136
+ ...chunks.map((node, i) => formatNode(node, i)),
137
+ ].join("\n")
138
+
139
+ return {
140
+ title: "Graph Query - Entity",
141
+ output,
142
+ metadata: { resultCount: chunks.length },
143
+ }
144
+ }
145
+
146
+ case "expand": {
147
+ if (!nodeId) {
148
+ return {
149
+ title: "Graph Query - Expand",
150
+ output: "Error: 'nodeId' parameter is required for expand operation",
151
+ }
152
+ }
153
+
154
+ const expanded = GraphTraverser.expandContext(
155
+ sessionID,
156
+ [nodeId],
157
+ 2,
158
+ limit
159
+ )
160
+
161
+ if (expanded.length === 0) {
162
+ return {
163
+ title: "Graph Query - Expand",
164
+ output: `No context found around node: "${nodeId}"`,
165
+ }
166
+ }
167
+
168
+ const output = [
169
+ `Expanded context around "${nodeId}" (${expanded.length} nodes):`,
170
+ "",
171
+ ...expanded.map((node, i) => formatNode(node, i)),
172
+ ].join("\n")
173
+
174
+ return {
175
+ title: "Graph Query - Expand",
176
+ output,
177
+ metadata: { resultCount: expanded.length },
178
+ }
179
+ }
180
+
181
+ case "path": {
182
+ if (!startEntity || !endEntity) {
183
+ return {
184
+ title: "Graph Query - Path",
185
+ output: "Error: 'startEntity' and 'endEntity' parameters are required for path operation",
186
+ }
187
+ }
188
+
189
+ // Find entity nodes
190
+ const startNodes = GraphStorage.searchNodes(
191
+ sessionID,
192
+ startEntity,
193
+ NodeType.ENTITY,
194
+ 1
195
+ )
196
+ const endNodes = GraphStorage.searchNodes(
197
+ sessionID,
198
+ endEntity,
199
+ NodeType.ENTITY,
200
+ 1
201
+ )
202
+
203
+ if (startNodes.length === 0) {
204
+ return {
205
+ title: "Graph Query - Path",
206
+ output: `Start entity not found: "${startEntity}"`,
207
+ }
208
+ }
209
+
210
+ if (endNodes.length === 0) {
211
+ return {
212
+ title: "Graph Query - Path",
213
+ output: `End entity not found: "${endEntity}"`,
214
+ }
215
+ }
216
+
217
+ const path = GraphTraverser.findPath(
218
+ sessionID,
219
+ startNodes[0].id,
220
+ endNodes[0].id,
221
+ 5
222
+ )
223
+
224
+ if (!path) {
225
+ return {
226
+ title: "Graph Query - Path",
227
+ output: `No path found between "${startEntity}" and "${endEntity}"`,
228
+ }
229
+ }
230
+
231
+ const output = [
232
+ `Path from "${startEntity}" to "${endEntity}" (${path.length} nodes):`,
233
+ "",
234
+ ...path.map((node, i) => formatNode(node, i)),
235
+ ].join("\n")
236
+
237
+ return {
238
+ title: "Graph Query - Path",
239
+ output,
240
+ metadata: { pathLength: path.length },
241
+ }
242
+ }
243
+
244
+ default:
245
+ return {
246
+ title: "Graph Query",
247
+ output: `Unknown operation: ${operation}`,
248
+ }
249
+ }
250
+ } catch (error) {
251
+ return {
252
+ title: "Graph Query - Error",
253
+ output: `Error executing graph query: ${error instanceof Error ? error.message : String(error)}`,
254
+ }
255
+ }
256
+ },
257
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tools module exports.
3
+ *
4
+ * Custom tools for graph query and memory search.
5
+ */
6
+
7
+ export { graphQueryTool } from "./graph-query.js"
8
+ export { memoryEnhancedTool, listEntitiesTool, graphStatsTool } from "./memory.js"
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Memory-enhanced tools for context retrieval.
3
+ *
4
+ * Provides tools for searching archived context with
5
+ * enhanced scoring and entity awareness.
6
+ */
7
+
8
+ import { z } from "zod"
9
+ import type { ToolDefinition, ToolResult } from "../plugin-types.js"
10
+ import { GraphStorage, GraphSearcher, NodeType, type GraphNode } from "../graph/index.js"
11
+ import { EnhancedSearch } from "../search/index.js"
12
+
13
+ /**
14
+ * Format a node for display.
15
+ */
16
+ function formatNode(node: GraphNode, index: number): string {
17
+ const lines = [`[${index + 1}] ${node.type.toUpperCase()}`]
18
+ lines.push(` ${node.content.slice(0, 300)}${node.content.length > 300 ? "..." : ""}`)
19
+ return lines.join("\n")
20
+ }
21
+
22
+ /**
23
+ * Memory enhanced search tool.
24
+ */
25
+ export const memoryEnhancedTool: ToolDefinition = {
26
+ description: `Search archived conversation context with enhanced scoring.
27
+
28
+ Features:
29
+ - Keyword matching with recency weighting
30
+ - Entity-aware boosting
31
+ - Graph traversal for related context
32
+
33
+ Use this tool when you need to:
34
+ - Find past discussions about a topic
35
+ - Recall specific decisions or implementations
36
+ - Get context about entities (classes, files, concepts)`,
37
+
38
+ args: {
39
+ query: z.string().describe("Search query"),
40
+ limit: z.number().optional().default(5).describe("Maximum results"),
41
+ recencyWeight: z
42
+ .number()
43
+ .optional()
44
+ .default(0.3)
45
+ .describe("Weight for recency (0-1)"),
46
+ },
47
+
48
+ execute: async (args, context): Promise<ToolResult> => {
49
+ const { query, limit = 5, recencyWeight = 0.3 } = args
50
+ const sessionID = context.sessionID
51
+
52
+ try {
53
+ const results = EnhancedSearch.search(sessionID, query, {
54
+ limit,
55
+ recencyWeight,
56
+ })
57
+
58
+ if (results.length === 0) {
59
+ return {
60
+ title: "Memory Search",
61
+ output: `No archived context found for: "${query}"`,
62
+ }
63
+ }
64
+
65
+ const output = [
66
+ `Found ${results.length} relevant context blocks for "${query}":`,
67
+ "",
68
+ ...results.map((r, i) => {
69
+ const node = r.node
70
+ return [
71
+ `[${i + 1}] Score: ${r.finalScore.toFixed(2)} (recency: ${r.recencyScore.toFixed(2)})`,
72
+ ` ${node.content.slice(0, 300)}${node.content.length > 300 ? "..." : ""}`,
73
+ ].join("\n")
74
+ }),
75
+ ].join("\n")
76
+
77
+ return {
78
+ title: "Memory Search",
79
+ output,
80
+ metadata: { resultCount: results.length },
81
+ }
82
+ } catch (error) {
83
+ return {
84
+ title: "Memory Search - Error",
85
+ output: `Error searching memory: ${error instanceof Error ? error.message : String(error)}`,
86
+ }
87
+ }
88
+ },
89
+ }
90
+
91
+ /**
92
+ * List entities tool.
93
+ */
94
+ export const listEntitiesTool: ToolDefinition = {
95
+ description: `List entities (code elements, files, concepts) tracked in the knowledge graph.
96
+
97
+ Use this to discover what entities have been discussed and can be queried.`,
98
+
99
+ args: {
100
+ filter: z.string().optional().describe("Filter entities by name"),
101
+ limit: z.number().optional().default(20).describe("Maximum entities"),
102
+ },
103
+
104
+ execute: async (args, context): Promise<ToolResult> => {
105
+ const { filter, limit = 20 } = args
106
+ const sessionID = context.sessionID
107
+
108
+ try {
109
+ let entities: GraphNode[]
110
+
111
+ if (filter) {
112
+ entities = GraphStorage.searchNodes(
113
+ sessionID,
114
+ filter,
115
+ NodeType.ENTITY,
116
+ limit
117
+ )
118
+ } else {
119
+ entities = GraphStorage.getNodesByType(sessionID, NodeType.ENTITY, limit)
120
+ }
121
+
122
+ if (entities.length === 0) {
123
+ return {
124
+ title: "List Entities",
125
+ output: filter
126
+ ? `No entities found matching: "${filter}"`
127
+ : "No entities tracked in the knowledge graph yet.",
128
+ }
129
+ }
130
+
131
+ // Group by entity type
132
+ const byType = new Map<string, GraphNode[]>()
133
+ for (const entity of entities) {
134
+ const type = (entity.metadata?.entityType as string) || "unknown"
135
+ if (!byType.has(type)) byType.set(type, [])
136
+ byType.get(type)!.push(entity)
137
+ }
138
+
139
+ const sections: string[] = [`Found ${entities.length} entities:`]
140
+
141
+ for (const [type, typeEntities] of byType) {
142
+ sections.push("")
143
+ sections.push(`${type.toUpperCase()}:`)
144
+ for (const entity of typeEntities) {
145
+ sections.push(` - ${entity.content}`)
146
+ }
147
+ }
148
+
149
+ return {
150
+ title: "List Entities",
151
+ output: sections.join("\n"),
152
+ metadata: { entityCount: entities.length },
153
+ }
154
+ } catch (error) {
155
+ return {
156
+ title: "List Entities - Error",
157
+ output: `Error listing entities: ${error instanceof Error ? error.message : String(error)}`,
158
+ }
159
+ }
160
+ },
161
+ }
162
+
163
+ /**
164
+ * Graph stats tool.
165
+ */
166
+ export const graphStatsTool: ToolDefinition = {
167
+ description: `Get statistics about the knowledge graph.
168
+
169
+ Shows counts of nodes, edges, and cache performance.`,
170
+
171
+ args: {},
172
+
173
+ execute: async (args, context): Promise<ToolResult> => {
174
+ const sessionID = context.sessionID
175
+
176
+ try {
177
+ const stats = GraphStorage.getStats(sessionID)
178
+
179
+ const output = [
180
+ "Knowledge Graph Statistics:",
181
+ "",
182
+ `Total nodes: ${stats.nodeCount}`,
183
+ `Total edges: ${stats.edgeCount}`,
184
+ "",
185
+ "Nodes by type:",
186
+ ...Object.entries(stats.nodesByType).map(
187
+ ([type, count]) => ` - ${type}: ${count}`
188
+ ),
189
+ "",
190
+ "Cache performance:",
191
+ ` - Hits: ${stats.cacheStats.hits}`,
192
+ ` - Misses: ${stats.cacheStats.misses}`,
193
+ ` - Hit rate: ${stats.cacheStats.hitRate}`,
194
+ ].join("\n")
195
+
196
+ return {
197
+ title: "Graph Stats",
198
+ output,
199
+ metadata: stats,
200
+ }
201
+ } catch (error) {
202
+ return {
203
+ title: "Graph Stats - Error",
204
+ output: `Error getting stats: ${error instanceof Error ? error.message : String(error)}`,
205
+ }
206
+ }
207
+ },
208
+ }