@sashabogi/foundation 0.1.5 → 0.1.7
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/dist/cli/setup-wizard.d.ts +30 -0
- package/dist/cli/setup-wizard.d.ts.map +1 -0
- package/dist/cli/setup-wizard.js +1645 -0
- package/dist/cli/setup-wizard.js.map +1 -0
- package/dist/cli/test-connection.d.ts +76 -0
- package/dist/cli/test-connection.d.ts.map +1 -0
- package/dist/cli/test-connection.js +697 -0
- package/dist/cli/test-connection.js.map +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +133 -5
- package/dist/cli.js.map +1 -1
- package/dist/providers/anthropic.d.ts +178 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +514 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/base.d.ts +154 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +227 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/deepseek.d.ts +23 -0
- package/dist/providers/deepseek.d.ts.map +1 -0
- package/dist/providers/deepseek.js +31 -0
- package/dist/providers/deepseek.js.map +1 -0
- package/dist/providers/fireworks.d.ts +23 -0
- package/dist/providers/fireworks.d.ts.map +1 -0
- package/dist/providers/fireworks.js +31 -0
- package/dist/providers/fireworks.js.map +1 -0
- package/dist/providers/gemini.d.ts +85 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +414 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/groq.d.ts +23 -0
- package/dist/providers/groq.d.ts.map +1 -0
- package/dist/providers/groq.js +31 -0
- package/dist/providers/groq.js.map +1 -0
- package/dist/providers/index.d.ts +23 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +27 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/kimi-code.d.ts +32 -0
- package/dist/providers/kimi-code.d.ts.map +1 -0
- package/dist/providers/kimi-code.js +46 -0
- package/dist/providers/kimi-code.js.map +1 -0
- package/dist/providers/kimi.d.ts +19 -0
- package/dist/providers/kimi.d.ts.map +1 -0
- package/dist/providers/kimi.js +27 -0
- package/dist/providers/kimi.js.map +1 -0
- package/dist/providers/manager.d.ts +144 -0
- package/dist/providers/manager.d.ts.map +1 -0
- package/dist/providers/manager.js +279 -0
- package/dist/providers/manager.js.map +1 -0
- package/dist/providers/ollama.d.ts +83 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +450 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +91 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +445 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +23 -0
- package/dist/providers/openrouter.d.ts.map +1 -0
- package/dist/providers/openrouter.js +31 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/perplexity.d.ts +34 -0
- package/dist/providers/perplexity.d.ts.map +1 -0
- package/dist/providers/perplexity.js +58 -0
- package/dist/providers/perplexity.js.map +1 -0
- package/dist/providers/together.d.ts +23 -0
- package/dist/providers/together.d.ts.map +1 -0
- package/dist/providers/together.js +31 -0
- package/dist/providers/together.js.map +1 -0
- package/dist/providers/types.d.ts +229 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +73 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/providers/zai.d.ts +19 -0
- package/dist/providers/zai.d.ts.map +1 -0
- package/dist/providers/zai.js +27 -0
- package/dist/providers/zai.js.map +1 -0
- package/dist/services/provider.service.d.ts +28 -0
- package/dist/services/provider.service.d.ts.map +1 -1
- package/dist/services/provider.service.js +137 -13
- package/dist/services/provider.service.js.map +1 -1
- package/dist/tools/demerzel/engine.d.ts +67 -0
- package/dist/tools/demerzel/engine.d.ts.map +1 -0
- package/dist/tools/demerzel/engine.js +401 -0
- package/dist/tools/demerzel/engine.js.map +1 -0
- package/dist/tools/demerzel/enhanced-snapshot.d.ts +67 -0
- package/dist/tools/demerzel/enhanced-snapshot.d.ts.map +1 -0
- package/dist/tools/demerzel/enhanced-snapshot.js +481 -0
- package/dist/tools/demerzel/enhanced-snapshot.js.map +1 -0
- package/dist/tools/demerzel/index.d.ts +11 -0
- package/dist/tools/demerzel/index.d.ts.map +1 -1
- package/dist/tools/demerzel/index.js +656 -85
- package/dist/tools/demerzel/index.js.map +1 -1
- package/dist/tools/demerzel/prompts.d.ts +26 -0
- package/dist/tools/demerzel/prompts.d.ts.map +1 -0
- package/dist/tools/demerzel/prompts.js +181 -0
- package/dist/tools/demerzel/prompts.js.map +1 -0
- package/dist/tools/demerzel/semantic-search.d.ts +54 -0
- package/dist/tools/demerzel/semantic-search.d.ts.map +1 -0
- package/dist/tools/demerzel/semantic-search.js +205 -0
- package/dist/tools/demerzel/semantic-search.js.map +1 -0
- package/dist/tools/demerzel/snapshot.d.ts +30 -0
- package/dist/tools/demerzel/snapshot.d.ts.map +1 -0
- package/dist/tools/demerzel/snapshot.js +169 -0
- package/dist/tools/demerzel/snapshot.js.map +1 -0
- package/package.json +2 -1
|
@@ -15,24 +15,241 @@
|
|
|
15
15
|
* - demerzel_get_context: Get lines around a location
|
|
16
16
|
* - demerzel_analyze: AI-powered deep analysis
|
|
17
17
|
* - demerzel_semantic_search: Natural language code search
|
|
18
|
+
*
|
|
19
|
+
* Ported from Argus to Foundation.
|
|
18
20
|
*/
|
|
19
21
|
import { z } from 'zod';
|
|
22
|
+
import { existsSync, readFileSync, statSync, mkdtempSync, unlinkSync } from 'fs';
|
|
23
|
+
import { join, resolve } from 'path';
|
|
24
|
+
import { tmpdir } from 'os';
|
|
25
|
+
import { createSnapshot, DEFAULT_EXTENSIONS, DEFAULT_EXCLUDE_PATTERNS } from './snapshot.js';
|
|
26
|
+
import { createEnhancedSnapshot } from './enhanced-snapshot.js';
|
|
27
|
+
import { analyze, searchDocument } from './engine.js';
|
|
28
|
+
import { SemanticIndex } from './semantic-search.js';
|
|
29
|
+
import { ConfigService } from '../../services/config.service.js';
|
|
30
|
+
import { ProviderService } from '../../services/provider.service.js';
|
|
31
|
+
import { isSessionDelegation } from '../../providers/types.js';
|
|
32
|
+
// Tool limits and defaults
|
|
33
|
+
const DEFAULT_FIND_FILES_LIMIT = 100;
|
|
34
|
+
const MAX_FIND_FILES_LIMIT = 500;
|
|
35
|
+
const DEFAULT_SEARCH_RESULTS = 50;
|
|
36
|
+
const MAX_SEARCH_RESULTS = 200;
|
|
37
|
+
const MAX_PATTERN_LENGTH = 500;
|
|
38
|
+
const MAX_WILDCARDS = 20;
|
|
39
|
+
/**
|
|
40
|
+
* Parse metadata section from an enhanced snapshot
|
|
41
|
+
*/
|
|
42
|
+
function parseSnapshotMetadata(content) {
|
|
43
|
+
// Check if this is an enhanced snapshot
|
|
44
|
+
if (!content.includes('METADATA: IMPORT GRAPH')) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const importGraph = {};
|
|
48
|
+
const exportGraph = {};
|
|
49
|
+
const symbolIndex = {};
|
|
50
|
+
const exports = [];
|
|
51
|
+
// Parse import graph
|
|
52
|
+
const importSection = content.match(/METADATA: IMPORT GRAPH\n=+\n([\s\S]*?)\n\n=+\nMETADATA:/)?.[1] || '';
|
|
53
|
+
for (const block of importSection.split('\n\n')) {
|
|
54
|
+
const lines = block.trim().split('\n');
|
|
55
|
+
if (lines.length > 0 && lines[0].endsWith(':')) {
|
|
56
|
+
const file = lines[0].slice(0, -1);
|
|
57
|
+
importGraph[file] = lines.slice(1).map(l => l.replace(/^\s*->\s*/, '').trim()).filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Parse export index (symbol -> files)
|
|
61
|
+
const exportSection = content.match(/METADATA: EXPORT INDEX\n=+\n([\s\S]*?)\n\n=+\nMETADATA:/)?.[1] || '';
|
|
62
|
+
for (const line of exportSection.split('\n')) {
|
|
63
|
+
const match = line.match(/^([\w$]+):\s*(.+)$/);
|
|
64
|
+
if (match) {
|
|
65
|
+
symbolIndex[match[1]] = match[2].split(',').map(s => s.trim());
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Parse who imports whom (reverse graph)
|
|
69
|
+
const whoImportsSection = content.match(/METADATA: WHO IMPORTS WHOM\n=+\n([\s\S]*)$/)?.[1] || '';
|
|
70
|
+
for (const block of whoImportsSection.split('\n\n')) {
|
|
71
|
+
const lines = block.trim().split('\n');
|
|
72
|
+
if (lines.length > 0 && lines[0].includes(' is imported by:')) {
|
|
73
|
+
const file = lines[0].replace(' is imported by:', '').trim();
|
|
74
|
+
exportGraph[file] = lines.slice(1).map(l => l.replace(/^\s*<-\s*/, '').trim()).filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Parse file exports
|
|
78
|
+
const fileExportsSection = content.match(/METADATA: FILE EXPORTS\n=+\n([\s\S]*?)\n\n=+\nMETADATA:/)?.[1] || '';
|
|
79
|
+
for (const line of fileExportsSection.split('\n')) {
|
|
80
|
+
const match = line.match(/^([^:]+):(\d+)\s*-\s*(\w+)\s+(.+)$/);
|
|
81
|
+
if (match) {
|
|
82
|
+
exports.push({
|
|
83
|
+
file: match[1],
|
|
84
|
+
line: parseInt(match[2]),
|
|
85
|
+
type: match[3],
|
|
86
|
+
symbol: match[4].split(' ')[0], // Take first word as symbol name
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { importGraph, exportGraph, symbolIndex, exports };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Create a stub provider for when no provider is configured
|
|
94
|
+
* Returns an error message indicating configuration is needed
|
|
95
|
+
*/
|
|
96
|
+
function createStubProvider() {
|
|
97
|
+
return {
|
|
98
|
+
name: 'stub',
|
|
99
|
+
async complete(_messages) {
|
|
100
|
+
return {
|
|
101
|
+
content: '<<<FINAL>>>Analysis requires a configured AI provider. Please configure Foundation with an API key.<<<END>>>',
|
|
102
|
+
finishReason: 'stop',
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Create a provider adapter that bridges Demerzel's AIProvider interface
|
|
109
|
+
* with Foundation's ProviderService.
|
|
110
|
+
*
|
|
111
|
+
* @param providerName - Name of the provider to use (e.g., 'ollama', 'anthropic')
|
|
112
|
+
* @param model - Model identifier to use for completions
|
|
113
|
+
* @returns AIProvider instance that delegates to Foundation's provider system
|
|
114
|
+
*/
|
|
115
|
+
function createProviderAdapter(providerName, model) {
|
|
116
|
+
return {
|
|
117
|
+
name: providerName,
|
|
118
|
+
async complete(messages, options) {
|
|
119
|
+
try {
|
|
120
|
+
// Get the provider from ProviderService
|
|
121
|
+
const providerManager = ProviderService.getInstance().getProviderManager();
|
|
122
|
+
// Check if provider is configured
|
|
123
|
+
if (!providerManager.isConfigured(providerName)) {
|
|
124
|
+
return {
|
|
125
|
+
content: `<<<FINAL>>>Provider '${providerName}' is not configured. Please configure it in ~/.foundation/config.yaml or use a different provider.<<<END>>>`,
|
|
126
|
+
finishReason: 'error',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const provider = providerManager.get(providerName);
|
|
130
|
+
// Extract system message if present (Demerzel uses system role, Foundation doesn't)
|
|
131
|
+
let systemContent = '';
|
|
132
|
+
const conversationMessages = [];
|
|
133
|
+
for (const msg of messages) {
|
|
134
|
+
if (msg.role === 'system') {
|
|
135
|
+
systemContent = msg.content;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
conversationMessages.push({
|
|
139
|
+
role: msg.role,
|
|
140
|
+
content: msg.content,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// If we have a system message, prepend it to the first user message
|
|
145
|
+
if (systemContent && conversationMessages.length > 0 && conversationMessages[0].role === 'user') {
|
|
146
|
+
conversationMessages[0] = {
|
|
147
|
+
role: 'user',
|
|
148
|
+
content: `${systemContent}\n\n${conversationMessages[0].content}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
else if (systemContent) {
|
|
152
|
+
// If no user message yet, add system as user message
|
|
153
|
+
conversationMessages.unshift({
|
|
154
|
+
role: 'user',
|
|
155
|
+
content: systemContent,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// Build the completion request
|
|
159
|
+
const request = {
|
|
160
|
+
model,
|
|
161
|
+
messages: conversationMessages,
|
|
162
|
+
temperature: options?.temperature ?? 0.7,
|
|
163
|
+
max_tokens: options?.maxTokens ?? 4096,
|
|
164
|
+
};
|
|
165
|
+
// Execute the completion
|
|
166
|
+
const response = await provider.complete(request);
|
|
167
|
+
// Handle session delegation response
|
|
168
|
+
if (isSessionDelegation(response)) {
|
|
169
|
+
// For session delegation, return the instructions as the content
|
|
170
|
+
return {
|
|
171
|
+
content: response.instructions,
|
|
172
|
+
finishReason: 'stop',
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Extract text content from the response
|
|
176
|
+
const textContent = response.content
|
|
177
|
+
.filter(block => block.type === 'text')
|
|
178
|
+
.map(block => block.text || '')
|
|
179
|
+
.join('\n');
|
|
180
|
+
// Convert usage information
|
|
181
|
+
const usage = response.usage ? {
|
|
182
|
+
promptTokens: response.usage.input_tokens,
|
|
183
|
+
completionTokens: response.usage.output_tokens,
|
|
184
|
+
totalTokens: response.usage.input_tokens + response.usage.output_tokens,
|
|
185
|
+
} : undefined;
|
|
186
|
+
return {
|
|
187
|
+
content: textContent,
|
|
188
|
+
finishReason: response.stop_reason === 'end_turn' ? 'stop' :
|
|
189
|
+
response.stop_reason === 'max_tokens' ? 'length' : 'stop',
|
|
190
|
+
usage,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
195
|
+
return {
|
|
196
|
+
content: `<<<FINAL>>>Error calling provider '${providerName}': ${errorMessage}<<<END>>>`,
|
|
197
|
+
finishReason: 'error',
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
20
203
|
export function registerDemerzelTools(server) {
|
|
21
204
|
// demerzel_snapshot
|
|
22
205
|
server.tool('demerzel_snapshot', 'Create a codebase snapshot for efficient searching and analysis. Enhanced snapshots include import graph and export index. "I have been watching for 20,000 years."', {
|
|
23
206
|
path: z.string().describe('Path to the codebase directory'),
|
|
24
207
|
enhanced: z.boolean().optional().describe('Create enhanced snapshot with import graph (default: true)'),
|
|
25
208
|
output: z.string().optional().describe('Output path for snapshot (default: .foundation/snapshot.txt)'),
|
|
26
|
-
}, async ({ path, enhanced = true, output
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
209
|
+
}, async ({ path, enhanced = true, output }) => {
|
|
210
|
+
const projectPath = resolve(path);
|
|
211
|
+
if (!existsSync(projectPath)) {
|
|
212
|
+
return {
|
|
213
|
+
content: [{ type: 'text', text: `Error: Path not found: ${projectPath}` }],
|
|
214
|
+
isError: true,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const outputPath = output ? resolve(output) : join(projectPath, '.foundation', 'snapshot.txt');
|
|
218
|
+
try {
|
|
219
|
+
const result = enhanced
|
|
220
|
+
? createEnhancedSnapshot(projectPath, outputPath, {
|
|
221
|
+
extensions: DEFAULT_EXTENSIONS,
|
|
222
|
+
excludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
|
223
|
+
})
|
|
224
|
+
: createSnapshot(projectPath, outputPath, {
|
|
225
|
+
extensions: DEFAULT_EXTENSIONS,
|
|
226
|
+
excludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
|
227
|
+
});
|
|
228
|
+
const response = {
|
|
229
|
+
outputPath: result.outputPath,
|
|
230
|
+
fileCount: result.fileCount,
|
|
231
|
+
totalLines: result.totalLines,
|
|
232
|
+
totalSize: result.totalSize,
|
|
233
|
+
enhanced,
|
|
234
|
+
};
|
|
235
|
+
if (enhanced && 'metadata' in result) {
|
|
236
|
+
const enhancedResult = result;
|
|
237
|
+
response.metadata = {
|
|
238
|
+
imports: enhancedResult.metadata.imports.length,
|
|
239
|
+
exports: enhancedResult.metadata.exports.length,
|
|
240
|
+
symbols: Object.keys(enhancedResult.metadata.symbolIndex).length,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
return {
|
|
249
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
250
|
+
isError: true,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
36
253
|
});
|
|
37
254
|
// demerzel_search
|
|
38
255
|
server.tool('demerzel_search', 'Fast regex search across codebase. Zero AI cost. Returns file:line:content matches.', {
|
|
@@ -40,77 +257,269 @@ export function registerDemerzelTools(server) {
|
|
|
40
257
|
pattern: z.string().describe('Regex pattern to search'),
|
|
41
258
|
maxResults: z.number().optional().describe('Maximum results (default: 50)'),
|
|
42
259
|
caseInsensitive: z.boolean().optional().describe('Case insensitive search (default: true)'),
|
|
43
|
-
}, async ({ path, pattern, maxResults
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
260
|
+
}, async ({ path, pattern, maxResults = DEFAULT_SEARCH_RESULTS, caseInsensitive = true }) => {
|
|
261
|
+
const snapshotPath = resolve(path);
|
|
262
|
+
if (!pattern || pattern.trim() === '') {
|
|
263
|
+
return {
|
|
264
|
+
content: [{ type: 'text', text: 'Error: Pattern cannot be empty' }],
|
|
265
|
+
isError: true,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (!existsSync(snapshotPath)) {
|
|
269
|
+
return {
|
|
270
|
+
content: [{ type: 'text', text: `Error: Snapshot not found: ${snapshotPath}. Run demerzel_snapshot first.` }],
|
|
271
|
+
isError: true,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const matches = searchDocument(snapshotPath, pattern, {
|
|
276
|
+
caseInsensitive,
|
|
277
|
+
maxResults: Math.min(maxResults, MAX_SEARCH_RESULTS),
|
|
278
|
+
});
|
|
279
|
+
const formattedMatches = matches.map(m => ({
|
|
280
|
+
lineNum: m.lineNum,
|
|
281
|
+
line: m.line.trim(),
|
|
282
|
+
match: m.match,
|
|
283
|
+
}));
|
|
284
|
+
return {
|
|
285
|
+
content: [{
|
|
286
|
+
type: 'text',
|
|
287
|
+
text: JSON.stringify({
|
|
288
|
+
count: formattedMatches.length,
|
|
289
|
+
matches: formattedMatches,
|
|
290
|
+
}, null, 2),
|
|
291
|
+
}],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
return {
|
|
296
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
297
|
+
isError: true,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
53
300
|
});
|
|
54
301
|
// demerzel_find_files
|
|
55
302
|
server.tool('demerzel_find_files', 'Find files matching a glob pattern. Zero AI cost.', {
|
|
56
303
|
path: z.string().describe('Path to snapshot file'),
|
|
57
304
|
pattern: z.string().describe('Glob pattern (e.g., "*.ts", "src/**/*.tsx")'),
|
|
58
305
|
limit: z.number().optional().describe('Maximum results (default: 100)'),
|
|
59
|
-
}, async ({ path, pattern, limit
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
306
|
+
}, async ({ path, pattern, limit = DEFAULT_FIND_FILES_LIMIT }) => {
|
|
307
|
+
const snapshotPath = resolve(path);
|
|
308
|
+
if (!pattern || pattern.trim() === '') {
|
|
309
|
+
return {
|
|
310
|
+
content: [{ type: 'text', text: 'Error: Pattern cannot be empty' }],
|
|
311
|
+
isError: true,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
315
|
+
return {
|
|
316
|
+
content: [{ type: 'text', text: `Error: Pattern too long (max ${MAX_PATTERN_LENGTH} characters)` }],
|
|
317
|
+
isError: true,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// ReDoS protection
|
|
321
|
+
const starCount = (pattern.match(/\*/g) || []).length;
|
|
322
|
+
if (starCount > MAX_WILDCARDS) {
|
|
323
|
+
return {
|
|
324
|
+
content: [{ type: 'text', text: `Error: Too many wildcards in pattern (max ${MAX_WILDCARDS})` }],
|
|
325
|
+
isError: true,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (!existsSync(snapshotPath)) {
|
|
329
|
+
return {
|
|
330
|
+
content: [{ type: 'text', text: `Error: Snapshot not found: ${snapshotPath}. Run demerzel_snapshot first.` }],
|
|
331
|
+
isError: true,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const content = readFileSync(snapshotPath, 'utf-8');
|
|
336
|
+
// Extract all FILE: markers
|
|
337
|
+
const fileRegex = /^FILE: \.\/(.+)$/gm;
|
|
338
|
+
const files = [];
|
|
339
|
+
let match;
|
|
340
|
+
while ((match = fileRegex.exec(content)) !== null) {
|
|
341
|
+
files.push(match[1]);
|
|
342
|
+
}
|
|
343
|
+
// Convert glob pattern to regex
|
|
344
|
+
let regexPattern = pattern
|
|
345
|
+
.replace(/[.+^${}()|[\]\\-]/g, '\\$&')
|
|
346
|
+
.replace(/\*\*/g, '<<<GLOBSTAR>>>')
|
|
347
|
+
.replace(/\*/g, '[^/]*?')
|
|
348
|
+
.replace(/<<<GLOBSTAR>>>/g, '.*?')
|
|
349
|
+
.replace(/\?/g, '.');
|
|
350
|
+
const regex = new RegExp(`^${regexPattern}$`, 'i');
|
|
351
|
+
const matching = files.filter(f => regex.test(f));
|
|
352
|
+
const limited = matching.slice(0, Math.min(limit, MAX_FIND_FILES_LIMIT)).sort();
|
|
353
|
+
return {
|
|
354
|
+
content: [{
|
|
355
|
+
type: 'text',
|
|
356
|
+
text: JSON.stringify({
|
|
357
|
+
pattern,
|
|
358
|
+
files: limited,
|
|
359
|
+
count: limited.length,
|
|
360
|
+
totalMatching: matching.length,
|
|
361
|
+
hasMore: matching.length > limit,
|
|
362
|
+
}, null, 2),
|
|
363
|
+
}],
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
return {
|
|
368
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
369
|
+
isError: true,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
69
372
|
});
|
|
70
373
|
// demerzel_find_symbol
|
|
71
374
|
server.tool('demerzel_find_symbol', 'Locate where a function, class, or type is exported. Zero AI cost. Requires enhanced snapshot.', {
|
|
72
375
|
path: z.string().describe('Path to snapshot file'),
|
|
73
376
|
symbol: z.string().describe('Symbol name to find (e.g., "AuthProvider", "useAuth")'),
|
|
74
377
|
}, async ({ path, symbol }) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
378
|
+
const snapshotPath = resolve(path);
|
|
379
|
+
if (!existsSync(snapshotPath)) {
|
|
380
|
+
return {
|
|
381
|
+
content: [{ type: 'text', text: `Error: Snapshot not found: ${snapshotPath}` }],
|
|
382
|
+
isError: true,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
const content = readFileSync(snapshotPath, 'utf-8');
|
|
387
|
+
const metadata = parseSnapshotMetadata(content);
|
|
388
|
+
if (!metadata) {
|
|
389
|
+
return {
|
|
390
|
+
content: [{ type: 'text', text: 'Error: This snapshot does not have metadata. Create with enhanced=true.' }],
|
|
391
|
+
isError: true,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const files = metadata.symbolIndex[symbol] || [];
|
|
395
|
+
const exportDetails = metadata.exports.filter(e => e.symbol === symbol);
|
|
396
|
+
return {
|
|
397
|
+
content: [{
|
|
398
|
+
type: 'text',
|
|
399
|
+
text: JSON.stringify({
|
|
400
|
+
symbol,
|
|
401
|
+
exportedFrom: files,
|
|
402
|
+
details: exportDetails,
|
|
403
|
+
count: files.length,
|
|
404
|
+
}, null, 2),
|
|
405
|
+
}],
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
return {
|
|
410
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
411
|
+
isError: true,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
84
414
|
});
|
|
85
415
|
// demerzel_find_importers
|
|
86
416
|
server.tool('demerzel_find_importers', 'Find all files that import a given module. Zero AI cost. Requires enhanced snapshot.', {
|
|
87
417
|
path: z.string().describe('Path to snapshot file'),
|
|
88
418
|
target: z.string().describe('File path to find importers of (e.g., "src/auth.ts")'),
|
|
89
419
|
}, async ({ path, target }) => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
420
|
+
const snapshotPath = resolve(path);
|
|
421
|
+
if (!existsSync(snapshotPath)) {
|
|
422
|
+
return {
|
|
423
|
+
content: [{ type: 'text', text: `Error: Snapshot not found: ${snapshotPath}` }],
|
|
424
|
+
isError: true,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
const content = readFileSync(snapshotPath, 'utf-8');
|
|
429
|
+
const metadata = parseSnapshotMetadata(content);
|
|
430
|
+
if (!metadata) {
|
|
431
|
+
return {
|
|
432
|
+
content: [{ type: 'text', text: 'Error: This snapshot does not have metadata. Create with enhanced=true.' }],
|
|
433
|
+
isError: true,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
// Normalize the target path
|
|
437
|
+
const normalizedTarget = target.startsWith('./') ? target.slice(2) : target;
|
|
438
|
+
const targetVariants = [normalizedTarget, './' + normalizedTarget, normalizedTarget.replace(/\.(ts|tsx|js|jsx)$/, '')];
|
|
439
|
+
// Find all files that import this target
|
|
440
|
+
const importers = [];
|
|
441
|
+
for (const [file, imports] of Object.entries(metadata.importGraph)) {
|
|
442
|
+
for (const imp of imports) {
|
|
443
|
+
if (targetVariants.some(v => imp === v || imp.endsWith('/' + v) || imp.includes(v))) {
|
|
444
|
+
importers.push(file);
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Also check the exportGraph (direct mapping)
|
|
450
|
+
for (const variant of targetVariants) {
|
|
451
|
+
if (metadata.exportGraph[variant]) {
|
|
452
|
+
importers.push(...metadata.exportGraph[variant]);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const unique = [...new Set(importers)];
|
|
456
|
+
return {
|
|
457
|
+
content: [{
|
|
458
|
+
type: 'text',
|
|
459
|
+
text: JSON.stringify({
|
|
460
|
+
target,
|
|
461
|
+
importedBy: unique,
|
|
462
|
+
count: unique.length,
|
|
463
|
+
}, null, 2),
|
|
464
|
+
}],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
return {
|
|
469
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
470
|
+
isError: true,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
99
473
|
});
|
|
100
474
|
// demerzel_get_deps
|
|
101
475
|
server.tool('demerzel_get_deps', 'Get all dependencies (imports) of a specific file. Zero AI cost. Requires enhanced snapshot.', {
|
|
102
476
|
path: z.string().describe('Path to snapshot file'),
|
|
103
477
|
file: z.string().describe('File path to get dependencies for'),
|
|
104
478
|
}, async ({ path, file }) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
479
|
+
const snapshotPath = resolve(path);
|
|
480
|
+
if (!existsSync(snapshotPath)) {
|
|
481
|
+
return {
|
|
482
|
+
content: [{ type: 'text', text: `Error: Snapshot not found: ${snapshotPath}` }],
|
|
483
|
+
isError: true,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
const content = readFileSync(snapshotPath, 'utf-8');
|
|
488
|
+
const metadata = parseSnapshotMetadata(content);
|
|
489
|
+
if (!metadata) {
|
|
490
|
+
return {
|
|
491
|
+
content: [{ type: 'text', text: 'Error: This snapshot does not have metadata. Create with enhanced=true.' }],
|
|
492
|
+
isError: true,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
// Normalize the file path
|
|
496
|
+
const normalizedFile = file.startsWith('./') ? file.slice(2) : file;
|
|
497
|
+
const fileVariants = [normalizedFile, './' + normalizedFile];
|
|
498
|
+
// Find imports for this file
|
|
499
|
+
let imports = [];
|
|
500
|
+
for (const variant of fileVariants) {
|
|
501
|
+
if (metadata.importGraph[variant]) {
|
|
502
|
+
imports = metadata.importGraph[variant];
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
content: [{
|
|
508
|
+
type: 'text',
|
|
509
|
+
text: JSON.stringify({
|
|
510
|
+
file,
|
|
511
|
+
imports,
|
|
512
|
+
count: imports.length,
|
|
513
|
+
}, null, 2),
|
|
514
|
+
}],
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
return {
|
|
519
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
520
|
+
isError: true,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
114
523
|
});
|
|
115
524
|
// demerzel_get_context
|
|
116
525
|
server.tool('demerzel_get_context', 'Get lines of code around a specific location. Zero AI cost. Use after search to get more context.', {
|
|
@@ -120,31 +529,145 @@ export function registerDemerzelTools(server) {
|
|
|
120
529
|
before: z.number().optional().describe('Lines before (default: 10)'),
|
|
121
530
|
after: z.number().optional().describe('Lines after (default: 10)'),
|
|
122
531
|
}, async ({ path, file, line, before = 10, after = 10 }) => {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
532
|
+
const snapshotPath = resolve(path);
|
|
533
|
+
if (!existsSync(snapshotPath)) {
|
|
534
|
+
return {
|
|
535
|
+
content: [{ type: 'text', text: `Error: Snapshot not found: ${snapshotPath}` }],
|
|
536
|
+
isError: true,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
const content = readFileSync(snapshotPath, 'utf-8');
|
|
541
|
+
// Find the file section in the snapshot
|
|
542
|
+
const normalizedTarget = file.replace(/^\.\//, '');
|
|
543
|
+
const fileMarkerVariants = [
|
|
544
|
+
`FILE: ./${normalizedTarget}`,
|
|
545
|
+
`FILE: ${normalizedTarget}`,
|
|
546
|
+
];
|
|
547
|
+
let fileStart = -1;
|
|
548
|
+
for (const marker of fileMarkerVariants) {
|
|
549
|
+
fileStart = content.indexOf(marker);
|
|
550
|
+
if (fileStart !== -1)
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
if (fileStart === -1) {
|
|
554
|
+
return {
|
|
555
|
+
content: [{ type: 'text', text: `Error: File not found in snapshot: ${file}` }],
|
|
556
|
+
isError: true,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
// Find the end of this file section
|
|
560
|
+
const nextFileStart = content.indexOf('\nFILE:', fileStart + 1);
|
|
561
|
+
const metadataStart = content.indexOf('\nMETADATA:', fileStart);
|
|
562
|
+
const fileEnd = Math.min(nextFileStart === -1 ? Infinity : nextFileStart, metadataStart === -1 ? Infinity : metadataStart);
|
|
563
|
+
// Extract file content
|
|
564
|
+
const fileContent = content.slice(fileStart, fileEnd === Infinity ? undefined : fileEnd);
|
|
565
|
+
const fileLines = fileContent.split('\n').slice(2); // Skip FILE: header and separator
|
|
566
|
+
// Calculate range
|
|
567
|
+
const startLine = Math.max(0, line - before - 1);
|
|
568
|
+
const endLine = Math.min(fileLines.length, line + after);
|
|
569
|
+
// Extract context with line numbers
|
|
570
|
+
const contextLines = fileLines.slice(startLine, endLine).map((lineContent, idx) => {
|
|
571
|
+
const lineNum = startLine + idx + 1;
|
|
572
|
+
const marker = lineNum === line ? '>>>' : ' ';
|
|
573
|
+
return `${marker} ${lineNum.toString().padStart(4)}: ${lineContent}`;
|
|
574
|
+
});
|
|
575
|
+
return {
|
|
576
|
+
content: [{
|
|
577
|
+
type: 'text',
|
|
578
|
+
text: JSON.stringify({
|
|
579
|
+
file,
|
|
580
|
+
targetLine: line,
|
|
581
|
+
range: { start: startLine + 1, end: endLine },
|
|
582
|
+
content: contextLines.join('\n'),
|
|
583
|
+
totalLines: fileLines.length,
|
|
584
|
+
}, null, 2),
|
|
585
|
+
}],
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
return {
|
|
590
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
591
|
+
isError: true,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
132
594
|
});
|
|
133
595
|
// demerzel_analyze
|
|
134
596
|
server.tool('demerzel_analyze', 'AI-powered deep analysis of codebase. Uses recursive reasoning across entire codebase. ~500 tokens cost.', {
|
|
135
|
-
path: z.string().describe('Path to snapshot file'),
|
|
597
|
+
path: z.string().describe('Path to snapshot file or codebase directory'),
|
|
136
598
|
query: z.string().describe('Question about the codebase (be specific for best results)'),
|
|
137
599
|
maxTurns: z.number().optional().describe('Maximum reasoning turns (default: 15)'),
|
|
138
600
|
}, async ({ path, query, maxTurns = 15 }) => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
601
|
+
const inputPath = resolve(path);
|
|
602
|
+
if (!existsSync(inputPath)) {
|
|
603
|
+
return {
|
|
604
|
+
content: [{ type: 'text', text: `Error: Path not found: ${inputPath}` }],
|
|
605
|
+
isError: true,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
let snapshotPath = inputPath;
|
|
609
|
+
let tempSnapshot = false;
|
|
610
|
+
try {
|
|
611
|
+
// If it's a directory, create a temporary enhanced snapshot
|
|
612
|
+
const stats = statSync(inputPath);
|
|
613
|
+
if (stats.isDirectory()) {
|
|
614
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'demerzel-'));
|
|
615
|
+
snapshotPath = join(tempDir, 'snapshot.txt');
|
|
616
|
+
createEnhancedSnapshot(inputPath, snapshotPath, {
|
|
617
|
+
extensions: DEFAULT_EXTENSIONS,
|
|
618
|
+
excludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
|
619
|
+
});
|
|
620
|
+
tempSnapshot = true;
|
|
621
|
+
}
|
|
622
|
+
// Get provider configuration from ConfigService
|
|
623
|
+
const argusConfig = ConfigService.getInstance().getArgusConfig();
|
|
624
|
+
const providerName = argusConfig.provider ?? 'ollama';
|
|
625
|
+
const model = argusConfig.model ?? 'qwen2.5-coder:7b';
|
|
626
|
+
// Create provider adapter, falling back to stub if not configured
|
|
627
|
+
let provider;
|
|
628
|
+
try {
|
|
629
|
+
const providerManager = ProviderService.getInstance().getProviderManager();
|
|
630
|
+
if (providerManager.isConfigured(providerName)) {
|
|
631
|
+
provider = createProviderAdapter(providerName, model);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
// Fallback to stub provider with informative message
|
|
635
|
+
provider = createStubProvider();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
catch {
|
|
639
|
+
// Fallback to stub if ProviderService is not initialized
|
|
640
|
+
provider = createStubProvider();
|
|
641
|
+
}
|
|
642
|
+
const result = await analyze(provider, snapshotPath, query, { maxTurns });
|
|
643
|
+
return {
|
|
644
|
+
content: [{
|
|
645
|
+
type: 'text',
|
|
646
|
+
text: JSON.stringify({
|
|
647
|
+
answer: result.answer,
|
|
648
|
+
success: result.success,
|
|
649
|
+
turns: result.turns,
|
|
650
|
+
commands: result.commands,
|
|
651
|
+
}, null, 2),
|
|
652
|
+
}],
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
catch (error) {
|
|
656
|
+
return {
|
|
657
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
658
|
+
isError: true,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
finally {
|
|
662
|
+
if (tempSnapshot && existsSync(snapshotPath)) {
|
|
663
|
+
try {
|
|
664
|
+
unlinkSync(snapshotPath);
|
|
665
|
+
}
|
|
666
|
+
catch {
|
|
667
|
+
// Ignore cleanup errors
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
148
671
|
});
|
|
149
672
|
// demerzel_semantic_search
|
|
150
673
|
server.tool('demerzel_semantic_search', 'Search code using natural language. Uses FTS5 full-text search. More flexible than regex.', {
|
|
@@ -152,15 +675,63 @@ export function registerDemerzelTools(server) {
|
|
|
152
675
|
query: z.string().describe('Natural language query or code terms'),
|
|
153
676
|
limit: z.number().optional().describe('Maximum results (default: 20)'),
|
|
154
677
|
}, async ({ path, query, limit = 20 }) => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
678
|
+
const projectPath = resolve(path);
|
|
679
|
+
if (!query || query.trim() === '') {
|
|
680
|
+
return {
|
|
681
|
+
content: [{ type: 'text', text: 'Error: Query cannot be empty' }],
|
|
682
|
+
isError: true,
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
const snapshotPath = join(projectPath, '.foundation', 'snapshot.txt');
|
|
686
|
+
const indexPath = join(projectPath, '.foundation', 'search.db');
|
|
687
|
+
if (!existsSync(snapshotPath)) {
|
|
688
|
+
return {
|
|
689
|
+
content: [{ type: 'text', text: `Error: Snapshot not found: ${snapshotPath}. Run demerzel_snapshot first.` }],
|
|
690
|
+
isError: true,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
let index = null;
|
|
694
|
+
try {
|
|
695
|
+
index = new SemanticIndex(indexPath);
|
|
696
|
+
// Check if index needs rebuilding
|
|
697
|
+
const stats = index.getStats();
|
|
698
|
+
const snapshotMtime = statSync(snapshotPath).mtimeMs;
|
|
699
|
+
const needsReindex = !stats.lastIndexed ||
|
|
700
|
+
new Date(stats.lastIndexed).getTime() < snapshotMtime ||
|
|
701
|
+
stats.snapshotPath !== snapshotPath;
|
|
702
|
+
if (needsReindex) {
|
|
703
|
+
index.indexFromSnapshot(snapshotPath);
|
|
704
|
+
}
|
|
705
|
+
const results = index.search(query, limit);
|
|
706
|
+
return {
|
|
707
|
+
content: [{
|
|
708
|
+
type: 'text',
|
|
709
|
+
text: JSON.stringify({
|
|
710
|
+
query,
|
|
711
|
+
count: results.length,
|
|
712
|
+
results: results.map(r => ({
|
|
713
|
+
file: r.file,
|
|
714
|
+
symbol: r.symbol,
|
|
715
|
+
type: r.type,
|
|
716
|
+
snippet: r.content.split('\n').slice(0, 5).join('\n'),
|
|
717
|
+
})),
|
|
718
|
+
}, null, 2),
|
|
719
|
+
}],
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
catch (error) {
|
|
723
|
+
return {
|
|
724
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
725
|
+
isError: true,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
finally {
|
|
729
|
+
if (index) {
|
|
730
|
+
index.close();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
164
733
|
});
|
|
165
734
|
}
|
|
735
|
+
// Re-export types and functions for external use
|
|
736
|
+
export { createSnapshot, createEnhancedSnapshot, analyze, searchDocument, SemanticIndex };
|
|
166
737
|
//# sourceMappingURL=index.js.map
|