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
package/src/index.ts ADDED
@@ -0,0 +1,238 @@
1
+ /**
2
+ * ClaudeCode RLM Plugin for Claude Code
3
+ *
4
+ * Enhances Claude Code's RLM system with:
5
+ * - Knowledge graph-based context storage (74x faster reads)
6
+ * - Extended reference patterns (40+ patterns)
7
+ * - Enhanced hybrid search with recency weighting
8
+ * - Entity extraction and linking
9
+ * - Custom tools for graph query and memory search
10
+ *
11
+ * @module claudecode-rlm
12
+ */
13
+
14
+ import type { Plugin, Hooks, PluginInput } from "./plugin-types.js"
15
+ import { GraphStorage, GraphIngester } from "./graph/index.js"
16
+ import { EnhancedSearch, detectsReference, detectsTaskCompletion } from "./search/index.js"
17
+ import {
18
+ graphQueryTool,
19
+ memoryEnhancedTool,
20
+ listEntitiesTool,
21
+ graphStatsTool,
22
+ } from "./tools/index.js"
23
+ import { parseConfig, type PluginConfig } from "./config.js"
24
+
25
+ // Re-export modules
26
+ export * from "./graph/index.js"
27
+ export * from "./search/index.js"
28
+ export * from "./tools/index.js"
29
+ export { parseConfig, type PluginConfig } from "./config.js"
30
+ export type { Plugin, Hooks, PluginInput } from "./plugin-types.js"
31
+
32
+ /**
33
+ * Plugin state.
34
+ */
35
+ interface PluginState {
36
+ config: PluginConfig
37
+ initialized: boolean
38
+ }
39
+
40
+ const state: PluginState = {
41
+ config: parseConfig({}),
42
+ initialized: false,
43
+ }
44
+
45
+ /**
46
+ * ClaudeCode RLM Plugin entry point.
47
+ *
48
+ * Registers hooks for:
49
+ * - experimental.chat.messages.transform: Inject graph-based context
50
+ * - experimental.session.compacting: Add entity summaries
51
+ * - event: Listen for rlm.archived to ingest into graph
52
+ * - tool: Register graph_query and memory_enhanced tools
53
+ */
54
+ export const ClaudeCodeRLMPlugin: Plugin = async (
55
+ input: PluginInput
56
+ ): Promise<Hooks> => {
57
+ const { worktree, client } = input
58
+
59
+ // Initialize graph storage with worktree path
60
+ GraphStorage.init(worktree)
61
+
62
+ // Load plugin config from Claude Code config
63
+ try {
64
+ // Try to get config from client if available
65
+ const claudeCodeConfig = await client?.config?.get?.()
66
+ const pluginConfig = (claudeCodeConfig as any)?.["claudecode-rlm"]
67
+ state.config = parseConfig(pluginConfig)
68
+ } catch {
69
+ // Use defaults if config not available
70
+ state.config = parseConfig({})
71
+ }
72
+
73
+ state.initialized = true
74
+
75
+ console.log("[claudecode-rlm] Plugin initialized (optimized storage: 74x faster reads)")
76
+ console.log("[claudecode-rlm] Graph enabled:", state.config.graph.enabled)
77
+ console.log(
78
+ "[claudecode-rlm] Extended patterns:",
79
+ state.config.enhanced_search.additional_patterns
80
+ )
81
+
82
+ return {
83
+ /**
84
+ * Register custom tools.
85
+ */
86
+ tool: {
87
+ graph_query: graphQueryTool,
88
+ memory_enhanced: memoryEnhancedTool,
89
+ list_entities: listEntitiesTool,
90
+ graph_stats: graphStatsTool,
91
+ },
92
+
93
+ /**
94
+ * Transform messages before sending to LLM.
95
+ * Injects graph-based context when reference patterns detected.
96
+ */
97
+ "experimental.chat.messages.transform": async (input, output) => {
98
+ if (!state.config.graph.enabled) return
99
+ if (!state.config.enhanced_search.additional_patterns) return
100
+
101
+ // Get the last user message
102
+ const lastMessage = output.messages[output.messages.length - 1]
103
+ if (!lastMessage || lastMessage.info.role !== "user") return
104
+
105
+ // Extract text from message parts
106
+ const textParts = lastMessage.parts
107
+ .filter((p) => p.type === "text")
108
+ .map((p) => (p as any).text || "")
109
+ const messageText = textParts.join(" ")
110
+
111
+ if (!messageText) return
112
+
113
+ // Get sessionID from message info
114
+ const sessionID = (lastMessage.info as any).sessionID || ""
115
+ if (!sessionID) return
116
+
117
+ // Check for reference patterns using enhanced detection
118
+ const analysis = EnhancedSearch.analyzeForInjection(
119
+ sessionID,
120
+ messageText,
121
+ {
122
+ additionalPatterns: state.config.enhanced_search.additional_patterns,
123
+ recencyWeight: state.config.enhanced_search.recency_weight,
124
+ entityBoost: state.config.enhanced_search.entity_boost,
125
+ minScoreThreshold: state.config.enhanced_search.min_score_threshold,
126
+ }
127
+ )
128
+
129
+ if (!analysis.shouldInject || analysis.results.length === 0) return
130
+
131
+ // Format context for injection
132
+ const contextText = EnhancedSearch.formatForInjection(analysis.results)
133
+
134
+ // Inject as a system message before the user message
135
+ const injectionMessage = {
136
+ info: {
137
+ id: `claudecode-inject-${Date.now()}`,
138
+ sessionID,
139
+ role: "user" as const,
140
+ time: Date.now(),
141
+ },
142
+ parts: [
143
+ {
144
+ type: "text" as const,
145
+ text: contextText,
146
+ },
147
+ ],
148
+ }
149
+
150
+ // Insert before the last user message
151
+ output.messages.splice(output.messages.length - 1, 0, injectionMessage)
152
+
153
+ console.log(
154
+ `[claudecode-rlm] Injected ${analysis.results.length} context blocks`
155
+ )
156
+ },
157
+
158
+ /**
159
+ * Add entity summary during session compaction.
160
+ */
161
+ "experimental.session.compacting": async (input, output) => {
162
+ if (!state.config.graph.enabled) return
163
+
164
+ const { sessionID } = input
165
+
166
+ // Get entity summary from graph
167
+ try {
168
+ const stats = GraphStorage.getStats(sessionID)
169
+
170
+ if (stats.nodeCount > 0) {
171
+ const entitySummary = [
172
+ "",
173
+ "Knowledge Graph Summary:",
174
+ `- ${stats.nodesByType["document"] || 0} documents archived`,
175
+ `- ${stats.nodesByType["chunk"] || 0} content chunks indexed`,
176
+ `- ${stats.nodesByType["entity"] || 0} entities tracked`,
177
+ "",
178
+ "Use graph_query or memory_enhanced tools to search archived context.",
179
+ ].join("\n")
180
+
181
+ output.context.push(entitySummary)
182
+ }
183
+ } catch {
184
+ // Ignore errors
185
+ }
186
+ },
187
+
188
+ /**
189
+ * Listen for RLM events to ingest into graph.
190
+ */
191
+ event: async ({ event }) => {
192
+ if (!state.config.graph.enabled) return
193
+ if (!state.config.graph.auto_ingest) return
194
+
195
+ // Handle RLM archived event
196
+ if (event.type === "rlm.archived") {
197
+ const { sessionID, blockID, content, summary, tokens } = event.properties as any
198
+
199
+ try {
200
+ // Ingest the archived context block into the graph
201
+ const block = {
202
+ id: blockID,
203
+ sessionID,
204
+ content: content || "",
205
+ summary: summary || "",
206
+ tokens: tokens || 0,
207
+ createdAt: Date.now(),
208
+ }
209
+
210
+ GraphIngester.ingestContextBlock(block)
211
+ // Flush to ensure data is persisted
212
+ GraphStorage.flush()
213
+ console.log(`[claudecode-rlm] Ingested block ${blockID} into graph`)
214
+ } catch (error) {
215
+ console.error("[claudecode-rlm] Failed to ingest block:", error)
216
+ }
217
+ }
218
+
219
+ // Handle task completion for auto-archive
220
+ if (
221
+ state.config.task_detection.auto_archive_on_completion &&
222
+ event.type === "todo.updated"
223
+ ) {
224
+ const { status, content } = event.properties as any
225
+ if (status === "completed") {
226
+ console.log(
227
+ `[claudecode-rlm] Task completed: ${content?.slice(0, 50)}...`
228
+ )
229
+ // Note: Actual archival is handled by Claude Code's RLM system
230
+ // We just log for visibility
231
+ }
232
+ }
233
+ },
234
+ }
235
+ }
236
+
237
+ // Default export for plugin loading
238
+ export default ClaudeCodeRLMPlugin
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Plugin types for Claude Code integration.
3
+ *
4
+ * These types mirror the @claudecode/plugin package interface.
5
+ * When @claudecode/plugin is available, its types take precedence.
6
+ */
7
+
8
+ import { z } from "zod"
9
+
10
+ /**
11
+ * Plugin input provided by Claude Code.
12
+ */
13
+ export interface PluginInput {
14
+ client: any
15
+ project: any
16
+ directory: string
17
+ worktree: string
18
+ serverUrl: URL
19
+ $: any
20
+ }
21
+
22
+ /**
23
+ * Event from Claude Code.
24
+ */
25
+ export interface Event {
26
+ type: string
27
+ properties: Record<string, any>
28
+ }
29
+
30
+ /**
31
+ * Message types.
32
+ */
33
+ export interface Message {
34
+ id: string
35
+ sessionID: string
36
+ role: "user" | "assistant" | "system"
37
+ time: number
38
+ agent?: string
39
+ model?: string
40
+ }
41
+
42
+ export interface Part {
43
+ type: string
44
+ [key: string]: any
45
+ }
46
+
47
+ /**
48
+ * Plugin hooks interface.
49
+ */
50
+ export interface Hooks {
51
+ event?: (input: { event: Event }) => Promise<void>
52
+ config?: (input: any) => Promise<void>
53
+ tool?: Record<string, ToolDefinition>
54
+ "chat.params"?: (input: any, output: any) => Promise<void>
55
+ "experimental.chat.messages.transform"?: (
56
+ input: {},
57
+ output: {
58
+ messages: Array<{
59
+ info: Message
60
+ parts: Part[]
61
+ }>
62
+ }
63
+ ) => Promise<void>
64
+ "experimental.session.compacting"?: (
65
+ input: { sessionID: string },
66
+ output: { context: string[]; prompt?: string }
67
+ ) => Promise<void>
68
+ }
69
+
70
+ /**
71
+ * Plugin function type.
72
+ */
73
+ export type Plugin = (input: PluginInput) => Promise<Hooks>
74
+
75
+ /**
76
+ * Tool definition interface.
77
+ */
78
+ export interface ToolDefinition {
79
+ description: string
80
+ args: Record<string, z.ZodType>
81
+ execute: (args: any, context: ToolContext) => Promise<ToolResult>
82
+ }
83
+
84
+ export interface ToolContext {
85
+ sessionID: string
86
+ abortSignal?: AbortSignal
87
+ }
88
+
89
+ export interface ToolResult {
90
+ title: string
91
+ output: string
92
+ metadata?: Record<string, any>
93
+ }
94
+
95
+ /**
96
+ * Helper to create tool definitions.
97
+ */
98
+ export const tool = {
99
+ schema: z,
100
+ define(def: {
101
+ description: string
102
+ args: Record<string, z.ZodType>
103
+ execute: (args: any, context: ToolContext) => Promise<ToolResult>
104
+ }): ToolDefinition {
105
+ return def
106
+ },
107
+ }
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Enhanced hybrid search with recency weighting and entity boost.
3
+ *
4
+ * Combines keyword search, entity matching, and graph traversal
5
+ * with configurable scoring weights.
6
+ */
7
+
8
+ import { z } from "zod"
9
+ import { GraphStorage, GraphSearcher, type GraphNode, type GraphSearchResult, NodeType } from "../graph/index.js"
10
+ import { detectsReference, getMatchingPatterns } from "./patterns.js"
11
+
12
+ /**
13
+ * Enhanced search configuration schema.
14
+ */
15
+ export const EnhancedSearchConfigSchema = z.object({
16
+ /** Use additional reference patterns */
17
+ additionalPatterns: z.boolean().default(true),
18
+ /** Weight for recency in scoring (0-1) */
19
+ recencyWeight: z.number().min(0).max(1).default(0.3),
20
+ /** Weight for entity matches in scoring (0-1) */
21
+ entityBoost: z.number().min(0).max(1).default(0.2),
22
+ /** Maximum age in days for recency scoring */
23
+ maxAgeDays: z.number().default(30),
24
+ /** Minimum score threshold for results */
25
+ minScoreThreshold: z.number().default(0.3),
26
+ /** Maximum results to return */
27
+ limit: z.number().default(10),
28
+ })
29
+
30
+ export type EnhancedSearchConfig = z.infer<typeof EnhancedSearchConfigSchema>
31
+
32
+ /**
33
+ * Enhanced search result with additional metadata.
34
+ */
35
+ export interface EnhancedSearchResult extends GraphSearchResult {
36
+ recencyScore: number
37
+ entityScore: number
38
+ finalScore: number
39
+ }
40
+
41
+ /**
42
+ * Injection analysis result.
43
+ */
44
+ export interface InjectionAnalysis {
45
+ shouldInject: boolean
46
+ matchedPatterns: RegExp[]
47
+ results: EnhancedSearchResult[]
48
+ query: string
49
+ }
50
+
51
+ /**
52
+ * Enhanced search namespace.
53
+ */
54
+ export namespace EnhancedSearch {
55
+ const DEFAULT_CONFIG: EnhancedSearchConfig = {
56
+ additionalPatterns: true,
57
+ recencyWeight: 0.3,
58
+ entityBoost: 0.2,
59
+ maxAgeDays: 30,
60
+ minScoreThreshold: 0.3,
61
+ limit: 10,
62
+ }
63
+
64
+ /**
65
+ * Check if context should be retrieved for a message.
66
+ */
67
+ export function shouldRetrieve(
68
+ message: string,
69
+ config: Partial<EnhancedSearchConfig> = {}
70
+ ): { shouldRetrieve: boolean; patterns: RegExp[] } {
71
+ const patterns = getMatchingPatterns(message)
72
+ return {
73
+ shouldRetrieve: patterns.length > 0,
74
+ patterns,
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Calculate recency score for a node.
80
+ * Score decreases as the node ages.
81
+ */
82
+ export function calculateRecencyScore(
83
+ nodeCreatedAt: number,
84
+ maxAgeDays: number = 30
85
+ ): number {
86
+ const now = Date.now()
87
+ const ageMs = now - nodeCreatedAt
88
+ const ageDays = ageMs / (1000 * 60 * 60 * 24)
89
+
90
+ if (ageDays <= 0) return 1.0
91
+ if (ageDays >= maxAgeDays) return 0.0
92
+
93
+ // Linear decay
94
+ return 1.0 - ageDays / maxAgeDays
95
+ }
96
+
97
+ /**
98
+ * Calculate keyword match score.
99
+ */
100
+ export function calculateKeywordScore(
101
+ content: string,
102
+ keywords: string[]
103
+ ): number {
104
+ if (keywords.length === 0) return 0
105
+
106
+ const contentLower = content.toLowerCase()
107
+ let matches = 0
108
+
109
+ for (const keyword of keywords) {
110
+ if (contentLower.includes(keyword.toLowerCase())) {
111
+ matches++
112
+ }
113
+ }
114
+
115
+ return matches / keywords.length
116
+ }
117
+
118
+ /**
119
+ * Search with enhanced scoring.
120
+ */
121
+ export function search(
122
+ sessionID: string,
123
+ query: string,
124
+ config: Partial<EnhancedSearchConfig> = {}
125
+ ): EnhancedSearchResult[] {
126
+ const cfg = { ...DEFAULT_CONFIG, ...config }
127
+
128
+ // Get base results from graph searcher
129
+ const baseResults = GraphSearcher.search(sessionID, query, {
130
+ limit: cfg.limit * 2, // Get extra for filtering
131
+ expandContext: true,
132
+ })
133
+
134
+ // Extract keywords from query
135
+ const keywords = query
136
+ .toLowerCase()
137
+ .split(/\s+/)
138
+ .filter((w) => w.length > 2)
139
+
140
+ // Enhance with additional scoring
141
+ const enhanced: EnhancedSearchResult[] = baseResults.map((result) => {
142
+ const recencyScore = calculateRecencyScore(
143
+ result.node.createdAt,
144
+ cfg.maxAgeDays
145
+ )
146
+
147
+ const entityScore =
148
+ result.matchedOn === "entity" ? cfg.entityBoost : 0
149
+
150
+ const keywordScore = calculateKeywordScore(result.node.content, keywords)
151
+
152
+ // Combine scores
153
+ const baseScore = result.score
154
+ const finalScore =
155
+ baseScore * (1 - cfg.recencyWeight) +
156
+ recencyScore * cfg.recencyWeight +
157
+ entityScore +
158
+ keywordScore * 0.1
159
+
160
+ return {
161
+ ...result,
162
+ recencyScore,
163
+ entityScore,
164
+ finalScore,
165
+ score: finalScore,
166
+ }
167
+ })
168
+
169
+ // Filter by threshold and sort
170
+ return enhanced
171
+ .filter((r) => r.finalScore >= cfg.minScoreThreshold)
172
+ .sort((a, b) => b.finalScore - a.finalScore)
173
+ .slice(0, cfg.limit)
174
+ }
175
+
176
+ /**
177
+ * Analyze a message and determine if context should be injected.
178
+ */
179
+ export function analyzeForInjection(
180
+ sessionID: string,
181
+ message: string,
182
+ config: Partial<EnhancedSearchConfig> = {}
183
+ ): InjectionAnalysis {
184
+ const { shouldRetrieve: should, patterns } = shouldRetrieve(message, config)
185
+
186
+ if (!should) {
187
+ return {
188
+ shouldInject: false,
189
+ matchedPatterns: [],
190
+ results: [],
191
+ query: message,
192
+ }
193
+ }
194
+
195
+ // Search for relevant context
196
+ const results = search(sessionID, message, config)
197
+
198
+ return {
199
+ shouldInject: results.length > 0,
200
+ matchedPatterns: patterns,
201
+ results,
202
+ query: message,
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Format search results for injection into context.
208
+ */
209
+ export function formatForInjection(results: EnhancedSearchResult[]): string {
210
+ if (results.length === 0) {
211
+ return ""
212
+ }
213
+
214
+ const sections: string[] = [
215
+ "<retrieved_context>",
216
+ "The following relevant context was retrieved from previous conversations:",
217
+ "",
218
+ ]
219
+
220
+ for (let i = 0; i < results.length; i++) {
221
+ const result = results[i]
222
+ const node = result.node
223
+
224
+ // Format based on node type
225
+ if (node.type === "chunk") {
226
+ sections.push(`[Context ${i + 1}]`)
227
+ sections.push(node.content)
228
+ sections.push("")
229
+ } else if (node.type === "document") {
230
+ sections.push(`[Document: ${node.content}]`)
231
+ if (node.metadata?.fullContent) {
232
+ sections.push(node.metadata.fullContent)
233
+ }
234
+ sections.push("")
235
+ } else if (node.type === "entity") {
236
+ sections.push(`[Entity: ${node.content}]`)
237
+ sections.push("")
238
+ }
239
+ }
240
+
241
+ sections.push("</retrieved_context>")
242
+
243
+ return sections.join("\n")
244
+ }
245
+
246
+ /**
247
+ * Get entity suggestions for a query.
248
+ */
249
+ export function suggestEntities(
250
+ sessionID: string,
251
+ query: string,
252
+ limit: number = 5
253
+ ): GraphNode[] {
254
+ // Search for entities matching the query
255
+ const entities = GraphStorage.searchNodes(
256
+ sessionID,
257
+ query,
258
+ NodeType.ENTITY,
259
+ limit
260
+ )
261
+
262
+ return entities
263
+ }
264
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Search module exports.
3
+ *
4
+ * Extended reference patterns and enhanced hybrid search.
5
+ */
6
+
7
+ export {
8
+ EXPLICIT_MEMORY_PATTERNS,
9
+ EXTENDED_PATTERNS,
10
+ TASK_COMPLETION_PATTERNS,
11
+ ALL_PATTERNS,
12
+ detectsReference,
13
+ getMatchingPatterns,
14
+ detectsTaskCompletion,
15
+ } from "./patterns.js"
16
+
17
+ export {
18
+ EnhancedSearch,
19
+ EnhancedSearchConfigSchema,
20
+ type EnhancedSearchConfig,
21
+ type EnhancedSearchResult,
22
+ type InjectionAnalysis,
23
+ } from "./enhanced.js"