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.
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/dist/config.d.ts +176 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +103 -0
- package/dist/config.js.map +1 -0
- package/dist/graph/index.d.ts +10 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +10 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/ingestion.d.ts +68 -0
- package/dist/graph/ingestion.d.ts.map +1 -0
- package/dist/graph/ingestion.js +417 -0
- package/dist/graph/ingestion.js.map +1 -0
- package/dist/graph/storage.d.ts +51 -0
- package/dist/graph/storage.d.ts.map +1 -0
- package/dist/graph/storage.js +552 -0
- package/dist/graph/storage.js.map +1 -0
- package/dist/graph/traversal.d.ts +54 -0
- package/dist/graph/traversal.d.ts.map +1 -0
- package/dist/graph/traversal.js +255 -0
- package/dist/graph/traversal.js.map +1 -0
- package/dist/graph/types.d.ts +152 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +94 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +190 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-types.d.ts +96 -0
- package/dist/plugin-types.d.ts.map +1 -0
- package/dist/plugin-types.js +17 -0
- package/dist/plugin-types.js.map +1 -0
- package/dist/search/enhanced.d.ts +95 -0
- package/dist/search/enhanced.d.ts.map +1 -0
- package/dist/search/enhanced.js +194 -0
- package/dist/search/enhanced.js.map +1 -0
- package/dist/search/index.d.ts +8 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +8 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/patterns.d.ts +38 -0
- package/dist/search/patterns.d.ts.map +1 -0
- package/dist/search/patterns.js +124 -0
- package/dist/search/patterns.js.map +1 -0
- package/dist/tools/graph-query.d.ts +14 -0
- package/dist/tools/graph-query.d.ts.map +1 -0
- package/dist/tools/graph-query.js +203 -0
- package/dist/tools/graph-query.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/memory.d.ts +20 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/memory.js +181 -0
- package/dist/tools/memory.js.map +1 -0
- package/package.json +66 -0
- package/src/config.ts +111 -0
- package/src/graph/index.ts +10 -0
- package/src/graph/ingestion.ts +528 -0
- package/src/graph/storage.ts +639 -0
- package/src/graph/traversal.ts +348 -0
- package/src/graph/types.ts +144 -0
- package/src/index.ts +238 -0
- package/src/plugin-types.ts +107 -0
- package/src/search/enhanced.ts +264 -0
- package/src/search/index.ts +23 -0
- package/src/search/patterns.ts +139 -0
- package/src/tools/graph-query.ts +257 -0
- package/src/tools/index.ts +8 -0
- 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[]>
|