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,348 @@
1
+ /**
2
+ * Graph traversal and search for RLM-Graph.
3
+ *
4
+ * Provides utilities for navigating and searching the knowledge graph:
5
+ * - GraphTraverser: Breadth-first and path-based traversal
6
+ * - GraphSearcher: Combined keyword and semantic search
7
+ */
8
+
9
+ import {
10
+ type GraphNode,
11
+ type GraphSearchResult,
12
+ NodeType,
13
+ RelationType,
14
+ } from "./types.js"
15
+ import { GraphStorage } from "./storage.js"
16
+
17
+ /**
18
+ * Traverses the knowledge graph to find related context.
19
+ */
20
+ export namespace GraphTraverser {
21
+ /**
22
+ * Get neighboring nodes.
23
+ */
24
+ export function getNeighbors(
25
+ sessionID: string,
26
+ nodeID: string,
27
+ direction: "outgoing" | "incoming" | "both" = "outgoing",
28
+ relationship?: RelationType
29
+ ): GraphNode[] {
30
+ return GraphStorage.getNeighbors(sessionID, nodeID, direction, relationship)
31
+ }
32
+
33
+ /**
34
+ * Expand context by traversing from given nodes using BFS.
35
+ */
36
+ export function expandContext(
37
+ sessionID: string,
38
+ nodeIDs: string[],
39
+ maxDepth: number = 2,
40
+ maxNodes: number = 20
41
+ ): GraphNode[] {
42
+ const visited = new Set<string>()
43
+ const result: GraphNode[] = []
44
+ const queue: Array<{ nodeID: string; depth: number }> = nodeIDs.map(
45
+ (nodeID) => ({ nodeID, depth: 0 })
46
+ )
47
+
48
+ while (queue.length > 0 && result.length < maxNodes) {
49
+ const { nodeID, depth } = queue.shift()!
50
+
51
+ if (visited.has(nodeID)) {
52
+ continue
53
+ }
54
+
55
+ visited.add(nodeID)
56
+
57
+ const node = GraphStorage.getNode(sessionID, nodeID)
58
+ if (node) {
59
+ result.push(node)
60
+ }
61
+
62
+ if (depth < maxDepth) {
63
+ // Get neighbors from edges
64
+ const edges = GraphStorage.getEdges(sessionID, nodeID, "both")
65
+ for (const edge of edges) {
66
+ const neighborID =
67
+ edge.sourceID === nodeID ? edge.targetID : edge.sourceID
68
+ if (!visited.has(neighborID)) {
69
+ queue.push({ nodeID: neighborID, depth: depth + 1 })
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ return result
76
+ }
77
+
78
+ /**
79
+ * Find a path between two nodes using BFS.
80
+ */
81
+ export function findPath(
82
+ sessionID: string,
83
+ startID: string,
84
+ endID: string,
85
+ maxDepth: number = 5
86
+ ): GraphNode[] | null {
87
+ const visited = new Set<string>()
88
+ const queue: Array<{ nodeID: string; path: string[] }> = [
89
+ { nodeID: startID, path: [startID] },
90
+ ]
91
+
92
+ while (queue.length > 0) {
93
+ const { nodeID, path } = queue.shift()!
94
+
95
+ if (nodeID === endID) {
96
+ // Build node list from path
97
+ const nodes: GraphNode[] = []
98
+ for (const id of path) {
99
+ const node = GraphStorage.getNode(sessionID, id)
100
+ if (node) {
101
+ nodes.push(node)
102
+ }
103
+ }
104
+ return nodes
105
+ }
106
+
107
+ if (path.length >= maxDepth) {
108
+ continue
109
+ }
110
+
111
+ visited.add(nodeID)
112
+
113
+ const edges = GraphStorage.getEdges(sessionID, nodeID, "both")
114
+ for (const edge of edges) {
115
+ const neighborID =
116
+ edge.sourceID === nodeID ? edge.targetID : edge.sourceID
117
+ if (!visited.has(neighborID)) {
118
+ queue.push({ nodeID: neighborID, path: [...path, neighborID] })
119
+ }
120
+ }
121
+ }
122
+
123
+ return null
124
+ }
125
+
126
+ /**
127
+ * Get all chunks that mention a specific entity.
128
+ */
129
+ export function getEntityContext(
130
+ sessionID: string,
131
+ entityName: string,
132
+ maxChunks: number = 10
133
+ ): GraphNode[] {
134
+ // Find entity nodes matching the name
135
+ const entities = GraphStorage.searchNodes(
136
+ sessionID,
137
+ entityName,
138
+ NodeType.ENTITY,
139
+ 5
140
+ )
141
+
142
+ const chunkIDs = new Set<string>()
143
+
144
+ for (const entity of entities) {
145
+ // Find chunks that mention this entity (incoming MENTIONS edges)
146
+ const edges = GraphStorage.getEdges(sessionID, entity.id, "incoming")
147
+ for (const edge of edges) {
148
+ if (edge.relationship === RelationType.MENTIONS) {
149
+ chunkIDs.add(edge.sourceID)
150
+ }
151
+ }
152
+ }
153
+
154
+ // Get chunk nodes
155
+ const chunks: GraphNode[] = []
156
+ for (const chunkID of Array.from(chunkIDs).slice(0, maxChunks)) {
157
+ const node = GraphStorage.getNode(sessionID, chunkID)
158
+ if (node) {
159
+ chunks.push(node)
160
+ }
161
+ }
162
+
163
+ return chunks
164
+ }
165
+
166
+ /**
167
+ * Get the parent document for a chunk.
168
+ */
169
+ export function getDocumentForChunk(
170
+ sessionID: string,
171
+ chunkID: string
172
+ ): GraphNode | null {
173
+ // Navigate up: chunk -> section -> document
174
+ const chunkEdges = GraphStorage.getEdges(sessionID, chunkID, "incoming")
175
+ for (const edge of chunkEdges) {
176
+ if (edge.relationship === RelationType.HAS_CHUNK) {
177
+ // Found the section
178
+ const sectionID = edge.sourceID
179
+
180
+ // Now find the document
181
+ const sectionEdges = GraphStorage.getEdges(
182
+ sessionID,
183
+ sectionID,
184
+ "incoming"
185
+ )
186
+ for (const sectionEdge of sectionEdges) {
187
+ if (sectionEdge.relationship === RelationType.HAS_SECTION) {
188
+ return GraphStorage.getNode(sessionID, sectionEdge.sourceID)
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ return null
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Search the knowledge graph using combined strategies.
200
+ */
201
+ export namespace GraphSearcher {
202
+ /**
203
+ * Search the graph for relevant nodes.
204
+ */
205
+ export function search(
206
+ sessionID: string,
207
+ query: string,
208
+ options: {
209
+ limit?: number
210
+ expandContext?: boolean
211
+ nodeType?: NodeType
212
+ } = {}
213
+ ): GraphSearchResult[] {
214
+ const { limit = 10, expandContext = true, nodeType = NodeType.CHUNK } = options
215
+ const results = new Map<string, GraphSearchResult>()
216
+
217
+ // 1. Keyword search
218
+ const keywordMatches = GraphStorage.searchNodes(
219
+ sessionID,
220
+ query,
221
+ nodeType,
222
+ limit * 2
223
+ )
224
+
225
+ const queryTerms = new Set(query.toLowerCase().split(/\s+/))
226
+
227
+ for (const node of keywordMatches) {
228
+ const contentTerms = new Set(node.content.toLowerCase().split(/\s+/))
229
+ const overlap = [...queryTerms].filter((t) => contentTerms.has(t)).length
230
+ const score = overlap / Math.max(queryTerms.size, 1)
231
+
232
+ results.set(node.id, {
233
+ node,
234
+ score: score * 0.4, // Keyword weight: 0.4
235
+ matchedOn: "content",
236
+ })
237
+ }
238
+
239
+ // 2. Entity-based search
240
+ const entities = extractQueryEntities(query)
241
+ for (const entityName of entities) {
242
+ const chunks = GraphTraverser.getEntityContext(sessionID, entityName, 5)
243
+ for (const chunk of chunks) {
244
+ if (results.has(chunk.id)) {
245
+ // Boost existing result
246
+ const existing = results.get(chunk.id)!
247
+ results.set(chunk.id, {
248
+ ...existing,
249
+ score: existing.score + 0.3,
250
+ })
251
+ } else {
252
+ results.set(chunk.id, {
253
+ node: chunk,
254
+ score: 0.3,
255
+ matchedOn: "entity",
256
+ })
257
+ }
258
+ }
259
+ }
260
+
261
+ // 3. Expand context via graph traversal
262
+ if (expandContext && results.size > 0) {
263
+ const topIDs = [...results.entries()]
264
+ .sort((a, b) => b[1].score - a[1].score)
265
+ .slice(0, 3)
266
+ .map(([id]) => id)
267
+
268
+ const expanded = GraphTraverser.expandContext(
269
+ sessionID,
270
+ topIDs,
271
+ 1,
272
+ 5
273
+ )
274
+
275
+ for (const node of expanded) {
276
+ if (!results.has(node.id)) {
277
+ results.set(node.id, {
278
+ node,
279
+ score: 0.2,
280
+ matchedOn: "traversal",
281
+ })
282
+ }
283
+ }
284
+ }
285
+
286
+ // Sort by score and return
287
+ return [...results.values()]
288
+ .sort((a, b) => b.score - a.score)
289
+ .slice(0, limit)
290
+ }
291
+
292
+ /**
293
+ * Search across all sessions.
294
+ */
295
+ export function searchAllSessions(
296
+ query: string,
297
+ options: {
298
+ limit?: number
299
+ expandContext?: boolean
300
+ } = {}
301
+ ): GraphSearchResult[] {
302
+ const { limit = 10 } = options
303
+ const allResults: GraphSearchResult[] = []
304
+
305
+ for (const sessionID of GraphStorage.getSessions()) {
306
+ const sessionResults = search(sessionID, query, {
307
+ ...options,
308
+ limit: Math.ceil(limit / 2),
309
+ })
310
+ allResults.push(...sessionResults)
311
+ }
312
+
313
+ // Sort by score and deduplicate
314
+ const seen = new Set<string>()
315
+ return allResults
316
+ .sort((a, b) => b.score - a.score)
317
+ .filter((r) => {
318
+ if (seen.has(r.node.id)) return false
319
+ seen.add(r.node.id)
320
+ return true
321
+ })
322
+ .slice(0, limit)
323
+ }
324
+
325
+ /**
326
+ * Extract potential entity names from a query.
327
+ */
328
+ function extractQueryEntities(query: string): string[] {
329
+ const entities: string[] = []
330
+
331
+ // Capitalized terms
332
+ const capitalizedPattern = /\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)\b/g
333
+ let match: RegExpExecArray | null
334
+ while ((match = capitalizedPattern.exec(query)) !== null) {
335
+ entities.push(match[1])
336
+ }
337
+
338
+ // Code elements (PascalCase, snake_case)
339
+ const codePattern = /\b([A-Z][a-zA-Z0-9]*|[a-z_][a-z0-9_]+)\b/g
340
+ while ((match = codePattern.exec(query)) !== null) {
341
+ if (match[1].length > 3) {
342
+ entities.push(match[1])
343
+ }
344
+ }
345
+
346
+ return [...new Set(entities)]
347
+ }
348
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Graph types for RLM-Graph knowledge storage.
3
+ *
4
+ * Defines the node and edge types for the knowledge graph that stores
5
+ * archived conversation context.
6
+ */
7
+
8
+ import { z } from "zod"
9
+
10
+ /**
11
+ * Node types in the knowledge graph.
12
+ */
13
+ export const NodeType = {
14
+ /** Top-level document representing an archived context block */
15
+ DOCUMENT: "document",
16
+ /** Section within a document (e.g., markdown header section) */
17
+ SECTION: "section",
18
+ /** Individual content chunk for retrieval */
19
+ CHUNK: "chunk",
20
+ /** Named entity (code element, file path, concept) */
21
+ ENTITY: "entity",
22
+ } as const
23
+
24
+ export type NodeType = (typeof NodeType)[keyof typeof NodeType]
25
+
26
+ /**
27
+ * Relationship types between nodes.
28
+ */
29
+ export const RelationType = {
30
+ /** Document contains section */
31
+ HAS_SECTION: "has_section",
32
+ /** Section/Document contains chunk */
33
+ HAS_CHUNK: "has_chunk",
34
+ /** Chunk mentions entity */
35
+ MENTIONS: "mentions",
36
+ /** Related concepts */
37
+ RELATED_TO: "related_to",
38
+ /** Temporal ordering (chunk follows chunk) */
39
+ FOLLOWS: "follows",
40
+ /** Parent-child relationship */
41
+ PARENT_OF: "parent_of",
42
+ } as const
43
+
44
+ export type RelationType = (typeof RelationType)[keyof typeof RelationType]
45
+
46
+ /**
47
+ * Entity types for extracted entities.
48
+ */
49
+ export const EntityType = {
50
+ /** Code element (class, function, variable) */
51
+ CODE_ELEMENT: "code_element",
52
+ /** File path */
53
+ FILE_PATH: "file_path",
54
+ /** Proper noun / named concept */
55
+ PROPER_NOUN: "proper_noun",
56
+ /** Reference (link, citation) */
57
+ REFERENCE: "reference",
58
+ } as const
59
+
60
+ export type EntityType = (typeof EntityType)[keyof typeof EntityType]
61
+
62
+ /**
63
+ * Graph node schema.
64
+ */
65
+ export const GraphNodeSchema = z.object({
66
+ /** Unique node identifier */
67
+ id: z.string(),
68
+ /** Session this node belongs to */
69
+ sessionID: z.string(),
70
+ /** Node type */
71
+ type: z.enum(["document", "section", "chunk", "entity"]),
72
+ /** Node content (text, entity name, etc.) */
73
+ content: z.string(),
74
+ /** Additional metadata */
75
+ metadata: z.record(z.any()).default({}),
76
+ /** Optional embedding vector */
77
+ embedding: z.array(z.number()).optional(),
78
+ /** Creation timestamp */
79
+ createdAt: z.number(),
80
+ })
81
+
82
+ export type GraphNode = z.infer<typeof GraphNodeSchema>
83
+
84
+ /**
85
+ * Graph edge schema.
86
+ */
87
+ export const GraphEdgeSchema = z.object({
88
+ /** Source node ID */
89
+ sourceID: z.string(),
90
+ /** Target node ID */
91
+ targetID: z.string(),
92
+ /** Relationship type */
93
+ relationship: z.enum([
94
+ "has_section",
95
+ "has_chunk",
96
+ "mentions",
97
+ "related_to",
98
+ "follows",
99
+ "parent_of",
100
+ ]),
101
+ /** Edge weight (0-1) */
102
+ weight: z.number().min(0).max(1).default(1.0),
103
+ /** Additional metadata */
104
+ metadata: z.record(z.any()).default({}),
105
+ /** Creation timestamp */
106
+ createdAt: z.number(),
107
+ })
108
+
109
+ export type GraphEdge = z.infer<typeof GraphEdgeSchema>
110
+
111
+ /**
112
+ * Entity extracted from text.
113
+ */
114
+ export interface Entity {
115
+ name: string
116
+ type: EntityType
117
+ }
118
+
119
+ /**
120
+ * Search result with relevance score.
121
+ */
122
+ export interface GraphSearchResult {
123
+ node: GraphNode
124
+ score: number
125
+ matchedOn: "content" | "entity" | "traversal" | "embedding"
126
+ }
127
+
128
+ /**
129
+ * Edge adjacency list for storage.
130
+ */
131
+ export interface EdgeAdjacencyList {
132
+ outgoing: GraphEdge[]
133
+ incoming: GraphEdge[]
134
+ }
135
+
136
+ /**
137
+ * Type index mapping node types to node IDs.
138
+ */
139
+ export type TypeIndex = Record<string, string[]>
140
+
141
+ /**
142
+ * Entity index mapping entity names to node IDs.
143
+ */
144
+ export type EntityIndex = Record<string, string[]>