directory-indexer 0.0.15 → 0.0.19
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.js +206 -19
- package/dist/cli.js.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/indexing.js +20 -0
- package/dist/indexing.js.map +1 -1
- package/dist/search.js +53 -8
- package/dist/search.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { fileURLToPath } from "url";
|
|
|
4
4
|
import { readFileSync } from "fs";
|
|
5
5
|
import { dirname, join } from "path";
|
|
6
6
|
import { indexDirectories } from "./indexing.js";
|
|
7
|
-
import { getFileContent, findSimilarFiles, searchContent } from "./search.js";
|
|
7
|
+
import { getChunkContent, getFileContent, findSimilarFiles, searchContent } from "./search.js";
|
|
8
8
|
import { loadConfig } from "./config.js";
|
|
9
9
|
import { getIndexStatus } from "./storage.js";
|
|
10
10
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -17,13 +17,31 @@ const VERSION$1 = packageJson$1.version;
|
|
|
17
17
|
const MCP_TOOLS = [
|
|
18
18
|
{
|
|
19
19
|
name: "index",
|
|
20
|
-
description:
|
|
20
|
+
description: `Index directories for AI-powered semantic search. This tool processes files in specified directories, extracts text content, generates vector embeddings, and stores them for semantic search capabilities.
|
|
21
|
+
|
|
22
|
+
When to use this tool:
|
|
23
|
+
- Before performing any search operations on new directories
|
|
24
|
+
- When you want to add new code repositories, documentation, or text files to the searchable knowledge base
|
|
25
|
+
- To update the index when files have been modified (the tool automatically detects and reprocesses changed files)
|
|
26
|
+
- When setting up semantic search for a project or workspace
|
|
27
|
+
|
|
28
|
+
What this tool does:
|
|
29
|
+
- Recursively scans directories for supported file types (code, markdown, text, config files)
|
|
30
|
+
- Chunks large files into smaller segments for better search precision
|
|
31
|
+
- Generates vector embeddings using the configured embedding model
|
|
32
|
+
- Stores file metadata and embeddings in a local database
|
|
33
|
+
- Skips unchanged files on re-indexing for efficiency
|
|
34
|
+
- Supports overlapping directory paths (files are deduplicated automatically)
|
|
35
|
+
|
|
36
|
+
Supported file types: .md, .txt, .py, .js, .ts, .go, .rs, .java, .json, .yaml, .toml, .env, .conf, and many others
|
|
37
|
+
|
|
38
|
+
Performance note: Initial indexing may take time for large directories, but subsequent re-indexing is much faster as only changed files are reprocessed.`,
|
|
21
39
|
inputSchema: {
|
|
22
40
|
type: "object",
|
|
23
41
|
properties: {
|
|
24
42
|
directory_path: {
|
|
25
43
|
type: "string",
|
|
26
|
-
description:
|
|
44
|
+
description: 'Comma-separated list of absolute or relative directory paths to index. Examples: "/home/user/projects" or "./src,./docs,./tests"'
|
|
27
45
|
}
|
|
28
46
|
},
|
|
29
47
|
required: ["directory_path"]
|
|
@@ -31,17 +49,41 @@ const MCP_TOOLS = [
|
|
|
31
49
|
},
|
|
32
50
|
{
|
|
33
51
|
name: "search",
|
|
34
|
-
description:
|
|
52
|
+
description: `Perform semantic search across indexed files using natural language queries. This tool uses vector similarity to find the most relevant content, going beyond simple keyword matching to understand intent and context.
|
|
53
|
+
|
|
54
|
+
When to use this tool:
|
|
55
|
+
- Finding code examples, functions, or patterns ("error handling in Python", "JWT authentication implementation")
|
|
56
|
+
- Locating documentation or explanations ("how to configure Redis", "API rate limiting guide")
|
|
57
|
+
- Discovering similar functionality across files ("database connection patterns", "logging utilities")
|
|
58
|
+
- Research and exploration of codebases ("machine learning models", "test utilities")
|
|
59
|
+
- Finding files related to specific features or topics
|
|
60
|
+
|
|
61
|
+
How semantic search works:
|
|
62
|
+
- Searches by meaning and context, not just exact keywords
|
|
63
|
+
- Finds conceptually related content even with different terminology
|
|
64
|
+
- Returns files ranked by relevance with similarity scores
|
|
65
|
+
- Groups results by file to avoid duplicates from multiple matching sections
|
|
66
|
+
|
|
67
|
+
Response format:
|
|
68
|
+
- Returns lightweight metadata including file paths, relevance scores, and chunk IDs
|
|
69
|
+
- Use 'get_chunk' or 'get_content' tools to fetch actual content from search results
|
|
70
|
+
- Chunks are sorted by relevance score within each file
|
|
71
|
+
- Average similarity score calculated across all matching chunks per file
|
|
72
|
+
|
|
73
|
+
Example queries:
|
|
74
|
+
- "error handling patterns" (finds try/catch, error classes, logging)
|
|
75
|
+
- "database migration scripts" (finds SQL, schema changes, migration files)
|
|
76
|
+
- "authentication middleware" (finds auth logic, JWT handling, middleware functions)`,
|
|
35
77
|
inputSchema: {
|
|
36
78
|
type: "object",
|
|
37
79
|
properties: {
|
|
38
80
|
query: {
|
|
39
81
|
type: "string",
|
|
40
|
-
description: "
|
|
82
|
+
description: "Natural language search query describing what you are looking for. Can be concepts, functionality, or specific technical terms."
|
|
41
83
|
},
|
|
42
84
|
limit: {
|
|
43
85
|
type: "number",
|
|
44
|
-
description: "Maximum number of
|
|
86
|
+
description: "Maximum number of files to return (default: 10). Each file may contain multiple matching chunks.",
|
|
45
87
|
default: 10
|
|
46
88
|
}
|
|
47
89
|
},
|
|
@@ -50,17 +92,40 @@ const MCP_TOOLS = [
|
|
|
50
92
|
},
|
|
51
93
|
{
|
|
52
94
|
name: "similar_files",
|
|
53
|
-
description:
|
|
95
|
+
description: `Find files that are semantically similar to a given reference file. This tool analyzes the content and context of a file to discover other files with related functionality, similar patterns, or comparable content.
|
|
96
|
+
|
|
97
|
+
When to use this tool:
|
|
98
|
+
- Discovering related implementations across a codebase ("find files similar to this authentication module")
|
|
99
|
+
- Locating alternative approaches or patterns ("find other components like this React component")
|
|
100
|
+
- Finding documentation or examples related to a specific file
|
|
101
|
+
- Identifying code duplication or similar functionality that could be refactored
|
|
102
|
+
- Exploring unfamiliar codebases by finding files similar to known examples
|
|
103
|
+
- Locating test files, configuration files, or documentation related to a source file
|
|
104
|
+
|
|
105
|
+
How similarity detection works:
|
|
106
|
+
- Analyzes the semantic content of the reference file
|
|
107
|
+
- Compares against all indexed files using vector similarity
|
|
108
|
+
- Considers code patterns, function signatures, imports, and documentation
|
|
109
|
+
- Returns files ranked by content similarity, not just filename or location similarity
|
|
110
|
+
- Works across different file types and programming languages
|
|
111
|
+
|
|
112
|
+
Use cases:
|
|
113
|
+
- Code analysis: "Find files similar to this database model to understand the schema patterns"
|
|
114
|
+
- Learning: "Show me other API controllers similar to this one"
|
|
115
|
+
- Maintenance: "Find files with similar error handling patterns"
|
|
116
|
+
- Architecture: "Locate other services that follow this microservice pattern"
|
|
117
|
+
|
|
118
|
+
Note: The reference file must be indexed for this tool to work. If the file is not found in the index, an error will be returned.`,
|
|
54
119
|
inputSchema: {
|
|
55
120
|
type: "object",
|
|
56
121
|
properties: {
|
|
57
122
|
file_path: {
|
|
58
123
|
type: "string",
|
|
59
|
-
description: "
|
|
124
|
+
description: "Absolute or relative path to the reference file. This file must have been previously indexed."
|
|
60
125
|
},
|
|
61
126
|
limit: {
|
|
62
127
|
type: "number",
|
|
63
|
-
description: "Maximum number of
|
|
128
|
+
description: "Maximum number of similar files to return (default: 10). Results are sorted by similarity score.",
|
|
64
129
|
default: 10
|
|
65
130
|
}
|
|
66
131
|
},
|
|
@@ -69,28 +134,133 @@ const MCP_TOOLS = [
|
|
|
69
134
|
},
|
|
70
135
|
{
|
|
71
136
|
name: "get_content",
|
|
72
|
-
description:
|
|
137
|
+
description: `Retrieve the full content of a file or specific chunks within a file. This tool reads files directly from the filesystem and can optionally return only specific portions of indexed files.
|
|
138
|
+
|
|
139
|
+
When to use this tool:
|
|
140
|
+
- After performing a search, to retrieve the actual content of relevant files
|
|
141
|
+
- Reading complete files that were identified through semantic search
|
|
142
|
+
- Extracting specific sections of large files using chunk ranges
|
|
143
|
+
- Accessing source code, documentation, or configuration files for analysis
|
|
144
|
+
- Following up on search results with detailed content examination
|
|
145
|
+
|
|
146
|
+
How chunk selection works:
|
|
147
|
+
- If no chunks parameter is provided, returns the entire file content
|
|
148
|
+
- Chunk ranges allow selective reading of large files (e.g., "2-5" returns chunks 2, 3, 4, and 5)
|
|
149
|
+
- Single chunks can be specified (e.g., "3" returns only chunk 3)
|
|
150
|
+
- Chunks are the same segments created during indexing for semantic search
|
|
151
|
+
- Useful for large files where you only need specific sections identified by search
|
|
152
|
+
|
|
153
|
+
File access:
|
|
154
|
+
- Reads files directly from the filesystem (not from the search index)
|
|
155
|
+
- Works with any readable file, whether indexed or not
|
|
156
|
+
- Supports all text-based file formats
|
|
157
|
+
- Preserves original formatting and content exactly as stored
|
|
158
|
+
|
|
159
|
+
Workflow integration:
|
|
160
|
+
1. Use 'search' to find relevant files and identify interesting chunk IDs
|
|
161
|
+
2. Use 'get_content' to retrieve full file content or specific chunks
|
|
162
|
+
3. Analyze the content to understand context and implementation details
|
|
163
|
+
|
|
164
|
+
Performance note: For large files, using chunk ranges can be more efficient than reading entire files.`,
|
|
73
165
|
inputSchema: {
|
|
74
166
|
type: "object",
|
|
75
167
|
properties: {
|
|
76
168
|
file_path: {
|
|
77
169
|
type: "string",
|
|
78
|
-
description: "
|
|
170
|
+
description: "Absolute or relative path to the file to retrieve. File must be readable and text-based."
|
|
79
171
|
},
|
|
80
172
|
chunks: {
|
|
81
173
|
type: "string",
|
|
82
|
-
description: 'Optional chunk range
|
|
174
|
+
description: 'Optional chunk range specification. Examples: "3" (single chunk), "2-5" (chunks 2 through 5), "1-3" (first three chunks). Only works for indexed files.'
|
|
83
175
|
}
|
|
84
176
|
},
|
|
85
177
|
required: ["file_path"]
|
|
86
178
|
}
|
|
87
179
|
},
|
|
180
|
+
{
|
|
181
|
+
name: "get_chunk",
|
|
182
|
+
description: `Retrieve the content of a specific chunk from an indexed file. This tool provides precise access to individual text segments that were identified during semantic search, allowing efficient retrieval of only the most relevant content.
|
|
183
|
+
|
|
184
|
+
When to use this tool:
|
|
185
|
+
- After performing a 'search' operation, to fetch the actual content of specific chunks that matched your query
|
|
186
|
+
- When you want to examine only the most relevant sections of a file rather than reading the entire file
|
|
187
|
+
- For targeted content analysis where you need specific text segments identified by their chunk IDs
|
|
188
|
+
- To build contextual responses using only the most semantically relevant portions of files
|
|
189
|
+
- When working with large files and you only need particular sections
|
|
190
|
+
|
|
191
|
+
How chunks work:
|
|
192
|
+
- Files are divided into overlapping text segments during indexing for better search granularity
|
|
193
|
+
- Each chunk represents a coherent section of text (typically 512 characters with overlap)
|
|
194
|
+
- Chunk IDs are sequential strings ("0", "1", "2", etc.) within each file
|
|
195
|
+
- Search results include chunk IDs for the most relevant sections
|
|
196
|
+
- This tool retrieves the exact content that was semantically matched
|
|
197
|
+
|
|
198
|
+
Typical workflow:
|
|
199
|
+
1. Use 'search' to find files and get chunk IDs with high relevance scores
|
|
200
|
+
2. Use 'get_chunk' to retrieve the specific content of the most relevant chunks
|
|
201
|
+
3. Analyze or process only the most pertinent text segments
|
|
202
|
+
|
|
203
|
+
Efficiency benefits:
|
|
204
|
+
- Avoids transferring unnecessary content from large files
|
|
205
|
+
- Provides precise access to semantically relevant text
|
|
206
|
+
- Reduces token usage by fetching only needed sections
|
|
207
|
+
- Enables focused analysis on the most important content
|
|
208
|
+
|
|
209
|
+
Note: Both the file and the specific chunk must exist in the search index for this tool to work.`,
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
file_path: {
|
|
214
|
+
type: "string",
|
|
215
|
+
description: "Absolute or relative path to the indexed file containing the desired chunk."
|
|
216
|
+
},
|
|
217
|
+
chunk_id: {
|
|
218
|
+
type: "string",
|
|
219
|
+
description: 'ID of the specific chunk to retrieve. This is typically obtained from search results and is a sequential string like "0", "1", "2", etc.'
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
required: ["file_path", "chunk_id"]
|
|
223
|
+
}
|
|
224
|
+
},
|
|
88
225
|
{
|
|
89
226
|
name: "server_info",
|
|
90
|
-
description:
|
|
227
|
+
description: `Get comprehensive information about the directory indexer server status, configuration, and indexed content. This tool provides a complete overview of the current state of the semantic search system.
|
|
228
|
+
|
|
229
|
+
When to use this tool:
|
|
230
|
+
- To check if the indexer is properly set up and operational
|
|
231
|
+
- Before starting work to understand what content is already indexed
|
|
232
|
+
- To verify indexing operations completed successfully
|
|
233
|
+
- When debugging search issues or unexpected results
|
|
234
|
+
- To get an overview of available content for semantic search
|
|
235
|
+
- To check system health and identify any configuration problems
|
|
236
|
+
|
|
237
|
+
Information provided:
|
|
238
|
+
- Server version and operational status
|
|
239
|
+
- Total count of indexed directories, files, and searchable chunks
|
|
240
|
+
- Database size and storage information
|
|
241
|
+
- Most recent indexing timestamp
|
|
242
|
+
- List of all indexed directories with individual statistics
|
|
243
|
+
- File counts and chunk counts per directory
|
|
244
|
+
- Indexing status for each directory (completed, failed, in progress)
|
|
245
|
+
- Error reports and processing issues
|
|
246
|
+
- System consistency checks between database components
|
|
247
|
+
|
|
248
|
+
Status indicators:
|
|
249
|
+
- Operational status of vector database (Qdrant) connection
|
|
250
|
+
- Embedding service availability
|
|
251
|
+
- Data consistency between SQLite metadata and vector storage
|
|
252
|
+
- Recent errors or warnings that may affect search quality
|
|
253
|
+
|
|
254
|
+
Use this tool to:
|
|
255
|
+
- Verify setup before performing search operations
|
|
256
|
+
- Understand the scope of available content
|
|
257
|
+
- Troubleshoot search or indexing issues
|
|
258
|
+
- Plan additional indexing operations
|
|
259
|
+
- Monitor system health and performance`,
|
|
91
260
|
inputSchema: {
|
|
92
261
|
type: "object",
|
|
93
|
-
properties: {}
|
|
262
|
+
properties: {},
|
|
263
|
+
additionalProperties: false
|
|
94
264
|
}
|
|
95
265
|
}
|
|
96
266
|
];
|
|
@@ -172,6 +342,20 @@ async function startMcpServer(config) {
|
|
|
172
342
|
]
|
|
173
343
|
};
|
|
174
344
|
}
|
|
345
|
+
case "get_chunk": {
|
|
346
|
+
if (!args || typeof args.file_path !== "string" || typeof args.chunk_id !== "string") {
|
|
347
|
+
throw new Error("file_path and chunk_id are required");
|
|
348
|
+
}
|
|
349
|
+
const content = await getChunkContent(args.file_path, args.chunk_id);
|
|
350
|
+
return {
|
|
351
|
+
content: [
|
|
352
|
+
{
|
|
353
|
+
type: "text",
|
|
354
|
+
text: content
|
|
355
|
+
}
|
|
356
|
+
]
|
|
357
|
+
};
|
|
358
|
+
}
|
|
175
359
|
case "server_info": {
|
|
176
360
|
const status = await getIndexStatus();
|
|
177
361
|
return {
|
|
@@ -219,7 +403,7 @@ async function main() {
|
|
|
219
403
|
program.command("index").description("Index directories for semantic search").argument("<paths...>", "Directory paths to index").option("-v, --verbose", "Enable verbose logging").action(async (paths, options) => {
|
|
220
404
|
try {
|
|
221
405
|
const config = await loadConfig({ verbose: options.verbose });
|
|
222
|
-
console.log(`Indexing ${paths.length} directories
|
|
406
|
+
console.log(`Indexing ${paths.length} ${paths.length === 1 ? "directory" : "directories"}: ${paths.join(", ")}`);
|
|
223
407
|
const result = await indexDirectories(paths, config);
|
|
224
408
|
console.log(`Indexed ${result.indexed} files, skipped ${result.skipped} files, ${result.errors.length} errors`);
|
|
225
409
|
if (result.errors.length > 0 && config.verbose) {
|
|
@@ -230,7 +414,7 @@ async function main() {
|
|
|
230
414
|
process.exit(1);
|
|
231
415
|
}
|
|
232
416
|
});
|
|
233
|
-
program.command("search").description("Search indexed content semantically").argument("<query>", "Search query").option("-l, --limit <number>", "Maximum number of results", "10").option("-v, --verbose", "Enable verbose logging").action(async (query, options) => {
|
|
417
|
+
program.command("search").description("Search indexed content semantically").argument("<query>", "Search query").option("-l, --limit <number>", "Maximum number of results", "10").option("-c, --show-chunks", "Show individual chunk scores and IDs").option("-v, --verbose", "Enable verbose logging").action(async (query, options) => {
|
|
234
418
|
try {
|
|
235
419
|
await loadConfig({ verbose: options.verbose });
|
|
236
420
|
const results = await searchContent(query, { limit: parseInt(options.limit) });
|
|
@@ -242,9 +426,12 @@ async function main() {
|
|
|
242
426
|
`);
|
|
243
427
|
results.forEach((result, index) => {
|
|
244
428
|
console.log(`${index + 1}. ${result.filePath}`);
|
|
245
|
-
console.log(` Score: ${result.score.toFixed(3)}`);
|
|
246
|
-
if (result.
|
|
247
|
-
console.log(`
|
|
429
|
+
console.log(` Score: ${result.score.toFixed(3)} (${result.matchingChunks} chunks)`);
|
|
430
|
+
if (options.showChunks && result.chunks.length > 0) {
|
|
431
|
+
console.log(` Chunks:`);
|
|
432
|
+
result.chunks.forEach((chunk) => {
|
|
433
|
+
console.log(` - Chunk ${chunk.chunkId}: ${chunk.score.toFixed(3)}`);
|
|
434
|
+
});
|
|
248
435
|
}
|
|
249
436
|
console.log();
|
|
250
437
|
});
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sources":["../src/mcp.ts","../src/cli.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { \n CallToolRequestSchema, \n ListToolsRequestSchema,\n Tool\n} from '@modelcontextprotocol/sdk/types.js';\nimport { readFileSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { Config } from './config.js';\nimport { indexDirectories } from './indexing.js';\nimport { searchContent, findSimilarFiles, getFileContent } from './search.js';\nimport { getIndexStatus } from './storage.js';\n\n// Read version from package.json\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJsonPath = join(__dirname, '../package.json');\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\nconst VERSION = packageJson.version;\n\nconst MCP_TOOLS: Tool[] = [\n {\n name: 'index',\n description: 'Index directories for semantic search',\n inputSchema: {\n type: 'object',\n properties: {\n directory_path: {\n type: 'string',\n description: 'Comma-separated list of directory paths to index'\n }\n },\n required: ['directory_path']\n }\n },\n {\n name: 'search',\n description: 'Search indexed content semantically',\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Search query'\n },\n limit: {\n type: 'number',\n description: 'Maximum number of results (default: 10)',\n default: 10\n }\n },\n required: ['query']\n }\n },\n {\n name: 'similar_files',\n description: 'Find files similar to a given file',\n inputSchema: {\n type: 'object',\n properties: {\n file_path: {\n type: 'string',\n description: 'Path to the file to find similar files for'\n },\n limit: {\n type: 'number',\n description: 'Maximum number of results (default: 10)',\n default: 10\n }\n },\n required: ['file_path']\n }\n },\n {\n name: 'get_content',\n description: 'Get file content',\n inputSchema: {\n type: 'object',\n properties: {\n file_path: {\n type: 'string',\n description: 'Path to the file to retrieve'\n },\n chunks: {\n type: 'string',\n description: 'Optional chunk range (e.g., \"2-5\")'\n }\n },\n required: ['file_path']\n }\n },\n {\n name: 'server_info',\n description: 'Get server information and status',\n inputSchema: {\n type: 'object',\n properties: {}\n }\n }\n];\n\nexport async function startMcpServer(config: Config): Promise<void> {\n const server = new Server(\n {\n name: 'directory-indexer',\n version: VERSION\n },\n {\n capabilities: {\n tools: {}\n }\n }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: MCP_TOOLS\n };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n switch (name) {\n case 'index': {\n if (!args || typeof args.directory_path !== 'string') {\n throw new Error('directory_path is required');\n }\n const paths = args.directory_path.split(',').map((p: string) => p.trim());\n const result = await indexDirectories(paths, config);\n return {\n content: [\n {\n type: 'text',\n text: `Indexed ${result.indexed} files, skipped ${result.skipped} files, ${result.errors.length} errors`\n }\n ]\n };\n }\n\n case 'search': {\n if (!args || typeof args.query !== 'string') {\n throw new Error('query is required');\n }\n const results = await searchContent(args.query, { limit: (args.limit as number) || 10 });\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(results, null, 2)\n }\n ]\n };\n }\n\n case 'similar_files': {\n if (!args || typeof args.file_path !== 'string') {\n throw new Error('file_path is required');\n }\n const results = await findSimilarFiles(args.file_path, (args.limit as number) || 10);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(results, null, 2)\n }\n ]\n };\n }\n\n case 'get_content': {\n if (!args || typeof args.file_path !== 'string') {\n throw new Error('file_path is required');\n }\n const content = await getFileContent(args.file_path, args.chunks as string);\n return {\n content: [\n {\n type: 'text',\n text: content\n }\n ]\n };\n }\n\n case 'server_info': {\n const status = await getIndexStatus();\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n name: 'directory-indexer',\n version: VERSION,\n status: status\n }, null, 2)\n }\n ]\n };\n }\n\n default:\n throw new Error(`Unknown tool: ${name}`);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${errorMessage}`\n }\n ],\n isError: true\n };\n }\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n \n if (config.verbose) {\n console.error('MCP server started successfully');\n }\n}","#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { fileURLToPath } from 'url';\nimport { readFileSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { indexDirectories } from './indexing.js';\nimport { searchContent, findSimilarFiles, getFileContent } from './search.js';\nimport { loadConfig } from './config.js';\nimport { getIndexStatus } from './storage.js';\nimport { startMcpServer } from './mcp.js';\n\n// Read version from package.json\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJsonPath = join(__dirname, '../package.json');\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\nconst VERSION = packageJson.version;\n\nexport async function main() {\n const program = new Command();\n \n program\n .name('directory-indexer')\n .description('AI-powered directory indexing with semantic search')\n .version(VERSION);\n\n program\n .command('index')\n .description('Index directories for semantic search')\n .argument('<paths...>', 'Directory paths to index')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (paths: string[], options) => {\n try {\n const config = await loadConfig({ verbose: options.verbose });\n console.log(`Indexing ${paths.length} directories...`);\n const result = await indexDirectories(paths, config);\n console.log(`Indexed ${result.indexed} files, skipped ${result.skipped} files, ${result.errors.length} errors`);\n if (result.errors.length > 0 && config.verbose) {\n console.log('Errors:', result.errors);\n }\n } catch (error) {\n console.error('Error indexing directories:', error);\n process.exit(1);\n }\n });\n\n program\n .command('search')\n .description('Search indexed content semantically')\n .argument('<query>', 'Search query')\n .option('-l, --limit <number>', 'Maximum number of results', '10')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (query: string, options) => {\n try {\n await loadConfig({ verbose: options.verbose });\n const results = await searchContent(query, { limit: parseInt(options.limit) });\n \n if (results.length === 0) {\n console.log('No results found');\n return;\n }\n\n console.log(`Found ${results.length} results:\\n`);\n results.forEach((result, index) => {\n console.log(`${index + 1}. ${result.filePath}`);\n console.log(` Score: ${result.score.toFixed(3)}`);\n if (result.content) {\n console.log(` Content: ${result.content.substring(0, 150)}...`);\n }\n console.log();\n });\n } catch (error) {\n console.error('Error searching content:', error);\n process.exit(1);\n }\n });\n\n program\n .command('similar')\n .description('Find files similar to a given file')\n .argument('<file>', 'File path to find similar files for')\n .option('-l, --limit <number>', 'Maximum number of results', '10')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (filePath: string, options) => {\n try {\n await loadConfig({ verbose: options.verbose });\n const results = await findSimilarFiles(filePath, parseInt(options.limit));\n \n if (results.length === 0) {\n console.log('No similar files found');\n return;\n }\n\n console.log(`Found ${results.length} similar files:\\n`);\n results.forEach((result, index) => {\n console.log(`${index + 1}. ${result.filePath}`);\n console.log(` Similarity: ${result.score.toFixed(3)}`);\n console.log();\n });\n } catch (error) {\n console.error('Error finding similar files:', error);\n process.exit(1);\n }\n });\n\n program\n .command('get')\n .description('Get file content')\n .argument('<file>', 'File path to retrieve')\n .option('-c, --chunks <range>', 'Chunk range (e.g., \"2-5\")')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (filePath: string, options) => {\n try {\n await loadConfig({ verbose: options.verbose });\n const content = await getFileContent(filePath, options.chunks);\n console.log(content);\n } catch (error) {\n console.error('Error getting file content:', error);\n process.exit(1);\n }\n });\n\n program\n .command('serve')\n .description('Start MCP server')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (options) => {\n try {\n const config = await loadConfig({ verbose: options.verbose });\n await startMcpServer(config);\n } catch (error) {\n console.error('Error starting MCP server:', error);\n process.exit(1);\n }\n });\n\n program\n .command('status')\n .description('Show indexing status')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (options) => {\n try {\n await loadConfig({ verbose: options.verbose });\n const status = await getIndexStatus();\n \n console.log('Directory Indexer Status Report');\n console.log('=====================================');\n console.log('');\n console.log('OVERVIEW:');\n console.log(` • ${status.directoriesIndexed} directories have been indexed`);\n console.log(` • ${status.filesIndexed} files processed for semantic search`);\n console.log(` • ${status.chunksIndexed} text chunks available for AI search`);\n console.log(` • Database storage: ${status.databaseSize}`);\n console.log(` • Most recent indexing: ${status.lastIndexed || 'No indexing performed yet'}`);\n \n if (status.errors.length > 0) {\n console.log(` • Processing errors encountered: ${status.errors.length}`);\n if (options.verbose) {\n console.log('');\n console.log('RECENT ERRORS:');\n status.errors.slice(0, 5).forEach(error => {\n console.log(` - ${error}`);\n });\n }\n }\n \n console.log('');\n console.log('INDEXED DIRECTORIES:');\n if (status.directories.length === 0) {\n console.log(' No directories have been indexed yet.');\n console.log(' Run \"directory-indexer index <path>\" to start indexing.');\n } else {\n status.directories.forEach(dir => {\n console.log('');\n console.log(` Directory: ${dir.path}`);\n console.log(` • Indexing status: ${dir.status}`);\n console.log(` • Files processed: ${dir.filesCount}`);\n console.log(` • Searchable chunks: ${dir.chunksCount}`);\n console.log(` • Last indexed: ${dir.lastIndexed || 'Never completed'}`);\n if (dir.errors.length > 0) {\n console.log(` • Files with errors: ${dir.errors.length}`);\n if (options.verbose) {\n console.log(' • Recent errors:');\n dir.errors.slice(0, 3).forEach(error => {\n console.log(` - ${error}`);\n });\n }\n }\n });\n }\n \n if (!status.qdrantConsistency.isConsistent) {\n console.log('');\n console.log('SYSTEM STATUS:');\n status.qdrantConsistency.issues.forEach(issue => {\n console.log(` • ${issue}`);\n });\n console.log('');\n console.log('ℹ️ Note: Status messages above may be normal during setup or active indexing.');\n } else {\n console.log('');\n console.log('SYSTEM STATUS:');\n console.log(' • All systems operational - ready for AI-powered search');\n }\n } catch (error) {\n console.error('Error getting status:', error);\n process.exit(1);\n }\n });\n\n await program.parseAsync();\n}\n\n// Main function is already exported above"],"names":["__dirname","packageJsonPath","packageJson","VERSION"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","sources":["../src/mcp.ts","../src/cli.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { \n CallToolRequestSchema, \n ListToolsRequestSchema,\n Tool\n} from '@modelcontextprotocol/sdk/types.js';\nimport { readFileSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { Config } from './config.js';\nimport { indexDirectories } from './indexing.js';\nimport { searchContent, findSimilarFiles, getFileContent, getChunkContent } from './search.js';\nimport { getIndexStatus } from './storage.js';\n\n// Read version from package.json\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJsonPath = join(__dirname, '../package.json');\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\nconst VERSION = packageJson.version;\n\nconst MCP_TOOLS: Tool[] = [\n {\n name: 'index',\n description: `Index directories for AI-powered semantic search. This tool processes files in specified directories, extracts text content, generates vector embeddings, and stores them for semantic search capabilities.\n\nWhen to use this tool:\n- Before performing any search operations on new directories\n- When you want to add new code repositories, documentation, or text files to the searchable knowledge base\n- To update the index when files have been modified (the tool automatically detects and reprocesses changed files)\n- When setting up semantic search for a project or workspace\n\nWhat this tool does:\n- Recursively scans directories for supported file types (code, markdown, text, config files)\n- Chunks large files into smaller segments for better search precision\n- Generates vector embeddings using the configured embedding model\n- Stores file metadata and embeddings in a local database\n- Skips unchanged files on re-indexing for efficiency\n- Supports overlapping directory paths (files are deduplicated automatically)\n\nSupported file types: .md, .txt, .py, .js, .ts, .go, .rs, .java, .json, .yaml, .toml, .env, .conf, and many others\n\nPerformance note: Initial indexing may take time for large directories, but subsequent re-indexing is much faster as only changed files are reprocessed.`,\n inputSchema: {\n type: 'object',\n properties: {\n directory_path: {\n type: 'string',\n description: 'Comma-separated list of absolute or relative directory paths to index. Examples: \"/home/user/projects\" or \"./src,./docs,./tests\"'\n }\n },\n required: ['directory_path']\n }\n },\n {\n name: 'search',\n description: `Perform semantic search across indexed files using natural language queries. This tool uses vector similarity to find the most relevant content, going beyond simple keyword matching to understand intent and context.\n\nWhen to use this tool:\n- Finding code examples, functions, or patterns (\"error handling in Python\", \"JWT authentication implementation\")\n- Locating documentation or explanations (\"how to configure Redis\", \"API rate limiting guide\")\n- Discovering similar functionality across files (\"database connection patterns\", \"logging utilities\")\n- Research and exploration of codebases (\"machine learning models\", \"test utilities\")\n- Finding files related to specific features or topics\n\nHow semantic search works:\n- Searches by meaning and context, not just exact keywords\n- Finds conceptually related content even with different terminology\n- Returns files ranked by relevance with similarity scores\n- Groups results by file to avoid duplicates from multiple matching sections\n\nResponse format:\n- Returns lightweight metadata including file paths, relevance scores, and chunk IDs\n- Use 'get_chunk' or 'get_content' tools to fetch actual content from search results\n- Chunks are sorted by relevance score within each file\n- Average similarity score calculated across all matching chunks per file\n\nExample queries:\n- \"error handling patterns\" (finds try/catch, error classes, logging)\n- \"database migration scripts\" (finds SQL, schema changes, migration files)\n- \"authentication middleware\" (finds auth logic, JWT handling, middleware functions)`,\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Natural language search query describing what you are looking for. Can be concepts, functionality, or specific technical terms.'\n },\n limit: {\n type: 'number',\n description: 'Maximum number of files to return (default: 10). Each file may contain multiple matching chunks.',\n default: 10\n }\n },\n required: ['query']\n }\n },\n {\n name: 'similar_files',\n description: `Find files that are semantically similar to a given reference file. This tool analyzes the content and context of a file to discover other files with related functionality, similar patterns, or comparable content.\n\nWhen to use this tool:\n- Discovering related implementations across a codebase (\"find files similar to this authentication module\")\n- Locating alternative approaches or patterns (\"find other components like this React component\")\n- Finding documentation or examples related to a specific file\n- Identifying code duplication or similar functionality that could be refactored\n- Exploring unfamiliar codebases by finding files similar to known examples\n- Locating test files, configuration files, or documentation related to a source file\n\nHow similarity detection works:\n- Analyzes the semantic content of the reference file\n- Compares against all indexed files using vector similarity\n- Considers code patterns, function signatures, imports, and documentation\n- Returns files ranked by content similarity, not just filename or location similarity\n- Works across different file types and programming languages\n\nUse cases:\n- Code analysis: \"Find files similar to this database model to understand the schema patterns\"\n- Learning: \"Show me other API controllers similar to this one\"\n- Maintenance: \"Find files with similar error handling patterns\"\n- Architecture: \"Locate other services that follow this microservice pattern\"\n\nNote: The reference file must be indexed for this tool to work. If the file is not found in the index, an error will be returned.`,\n inputSchema: {\n type: 'object',\n properties: {\n file_path: {\n type: 'string',\n description: 'Absolute or relative path to the reference file. This file must have been previously indexed.'\n },\n limit: {\n type: 'number',\n description: 'Maximum number of similar files to return (default: 10). Results are sorted by similarity score.',\n default: 10\n }\n },\n required: ['file_path']\n }\n },\n {\n name: 'get_content',\n description: `Retrieve the full content of a file or specific chunks within a file. This tool reads files directly from the filesystem and can optionally return only specific portions of indexed files.\n\nWhen to use this tool:\n- After performing a search, to retrieve the actual content of relevant files\n- Reading complete files that were identified through semantic search\n- Extracting specific sections of large files using chunk ranges\n- Accessing source code, documentation, or configuration files for analysis\n- Following up on search results with detailed content examination\n\nHow chunk selection works:\n- If no chunks parameter is provided, returns the entire file content\n- Chunk ranges allow selective reading of large files (e.g., \"2-5\" returns chunks 2, 3, 4, and 5)\n- Single chunks can be specified (e.g., \"3\" returns only chunk 3)\n- Chunks are the same segments created during indexing for semantic search\n- Useful for large files where you only need specific sections identified by search\n\nFile access:\n- Reads files directly from the filesystem (not from the search index)\n- Works with any readable file, whether indexed or not\n- Supports all text-based file formats\n- Preserves original formatting and content exactly as stored\n\nWorkflow integration:\n1. Use 'search' to find relevant files and identify interesting chunk IDs\n2. Use 'get_content' to retrieve full file content or specific chunks\n3. Analyze the content to understand context and implementation details\n\nPerformance note: For large files, using chunk ranges can be more efficient than reading entire files.`,\n inputSchema: {\n type: 'object',\n properties: {\n file_path: {\n type: 'string',\n description: 'Absolute or relative path to the file to retrieve. File must be readable and text-based.'\n },\n chunks: {\n type: 'string',\n description: 'Optional chunk range specification. Examples: \"3\" (single chunk), \"2-5\" (chunks 2 through 5), \"1-3\" (first three chunks). Only works for indexed files.'\n }\n },\n required: ['file_path']\n }\n },\n {\n name: 'get_chunk',\n description: `Retrieve the content of a specific chunk from an indexed file. This tool provides precise access to individual text segments that were identified during semantic search, allowing efficient retrieval of only the most relevant content.\n\nWhen to use this tool:\n- After performing a 'search' operation, to fetch the actual content of specific chunks that matched your query\n- When you want to examine only the most relevant sections of a file rather than reading the entire file\n- For targeted content analysis where you need specific text segments identified by their chunk IDs\n- To build contextual responses using only the most semantically relevant portions of files\n- When working with large files and you only need particular sections\n\nHow chunks work:\n- Files are divided into overlapping text segments during indexing for better search granularity\n- Each chunk represents a coherent section of text (typically 512 characters with overlap)\n- Chunk IDs are sequential strings (\"0\", \"1\", \"2\", etc.) within each file\n- Search results include chunk IDs for the most relevant sections\n- This tool retrieves the exact content that was semantically matched\n\nTypical workflow:\n1. Use 'search' to find files and get chunk IDs with high relevance scores\n2. Use 'get_chunk' to retrieve the specific content of the most relevant chunks\n3. Analyze or process only the most pertinent text segments\n\nEfficiency benefits:\n- Avoids transferring unnecessary content from large files\n- Provides precise access to semantically relevant text\n- Reduces token usage by fetching only needed sections\n- Enables focused analysis on the most important content\n\nNote: Both the file and the specific chunk must exist in the search index for this tool to work.`,\n inputSchema: {\n type: 'object',\n properties: {\n file_path: {\n type: 'string',\n description: 'Absolute or relative path to the indexed file containing the desired chunk.'\n },\n chunk_id: {\n type: 'string',\n description: 'ID of the specific chunk to retrieve. This is typically obtained from search results and is a sequential string like \"0\", \"1\", \"2\", etc.'\n }\n },\n required: ['file_path', 'chunk_id']\n }\n },\n {\n name: 'server_info',\n description: `Get comprehensive information about the directory indexer server status, configuration, and indexed content. This tool provides a complete overview of the current state of the semantic search system.\n\nWhen to use this tool:\n- To check if the indexer is properly set up and operational\n- Before starting work to understand what content is already indexed\n- To verify indexing operations completed successfully\n- When debugging search issues or unexpected results\n- To get an overview of available content for semantic search\n- To check system health and identify any configuration problems\n\nInformation provided:\n- Server version and operational status\n- Total count of indexed directories, files, and searchable chunks\n- Database size and storage information\n- Most recent indexing timestamp\n- List of all indexed directories with individual statistics\n- File counts and chunk counts per directory\n- Indexing status for each directory (completed, failed, in progress)\n- Error reports and processing issues\n- System consistency checks between database components\n\nStatus indicators:\n- Operational status of vector database (Qdrant) connection\n- Embedding service availability\n- Data consistency between SQLite metadata and vector storage\n- Recent errors or warnings that may affect search quality\n\nUse this tool to:\n- Verify setup before performing search operations\n- Understand the scope of available content\n- Troubleshoot search or indexing issues\n- Plan additional indexing operations\n- Monitor system health and performance`,\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false\n }\n }\n];\n\nexport async function startMcpServer(config: Config): Promise<void> {\n const server = new Server(\n {\n name: 'directory-indexer',\n version: VERSION\n },\n {\n capabilities: {\n tools: {}\n }\n }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: MCP_TOOLS\n };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n switch (name) {\n case 'index': {\n if (!args || typeof args.directory_path !== 'string') {\n throw new Error('directory_path is required');\n }\n const paths = args.directory_path.split(',').map((p: string) => p.trim());\n const result = await indexDirectories(paths, config);\n return {\n content: [\n {\n type: 'text',\n text: `Indexed ${result.indexed} files, skipped ${result.skipped} files, ${result.errors.length} errors`\n }\n ]\n };\n }\n\n case 'search': {\n if (!args || typeof args.query !== 'string') {\n throw new Error('query is required');\n }\n const results = await searchContent(args.query, { limit: (args.limit as number) || 10 });\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(results, null, 2)\n }\n ]\n };\n }\n\n case 'similar_files': {\n if (!args || typeof args.file_path !== 'string') {\n throw new Error('file_path is required');\n }\n const results = await findSimilarFiles(args.file_path, (args.limit as number) || 10);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(results, null, 2)\n }\n ]\n };\n }\n\n case 'get_content': {\n if (!args || typeof args.file_path !== 'string') {\n throw new Error('file_path is required');\n }\n const content = await getFileContent(args.file_path, args.chunks as string);\n return {\n content: [\n {\n type: 'text',\n text: content\n }\n ]\n };\n }\n\n case 'get_chunk': {\n if (!args || typeof args.file_path !== 'string' || typeof args.chunk_id !== 'string') {\n throw new Error('file_path and chunk_id are required');\n }\n const content = await getChunkContent(args.file_path, args.chunk_id);\n return {\n content: [\n {\n type: 'text',\n text: content\n }\n ]\n };\n }\n\n case 'server_info': {\n const status = await getIndexStatus();\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n name: 'directory-indexer',\n version: VERSION,\n status: status\n }, null, 2)\n }\n ]\n };\n }\n\n default:\n throw new Error(`Unknown tool: ${name}`);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${errorMessage}`\n }\n ],\n isError: true\n };\n }\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n \n if (config.verbose) {\n console.error('MCP server started successfully');\n }\n}","#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { fileURLToPath } from 'url';\nimport { readFileSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { indexDirectories } from './indexing.js';\nimport { searchContent, findSimilarFiles, getFileContent } from './search.js';\nimport { loadConfig } from './config.js';\nimport { getIndexStatus } from './storage.js';\nimport { startMcpServer } from './mcp.js';\n\n// Read version from package.json\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJsonPath = join(__dirname, '../package.json');\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\nconst VERSION = packageJson.version;\n\nexport async function main() {\n const program = new Command();\n \n program\n .name('directory-indexer')\n .description('AI-powered directory indexing with semantic search')\n .version(VERSION);\n\n program\n .command('index')\n .description('Index directories for semantic search')\n .argument('<paths...>', 'Directory paths to index')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (paths: string[], options) => {\n try {\n const config = await loadConfig({ verbose: options.verbose });\n console.log(`Indexing ${paths.length} ${paths.length === 1 ? 'directory' : 'directories'}: ${paths.join(', ')}`);\n const result = await indexDirectories(paths, config);\n console.log(`Indexed ${result.indexed} files, skipped ${result.skipped} files, ${result.errors.length} errors`);\n if (result.errors.length > 0 && config.verbose) {\n console.log('Errors:', result.errors);\n }\n } catch (error) {\n console.error('Error indexing directories:', error);\n process.exit(1);\n }\n });\n\n program\n .command('search')\n .description('Search indexed content semantically')\n .argument('<query>', 'Search query')\n .option('-l, --limit <number>', 'Maximum number of results', '10')\n .option('-c, --show-chunks', 'Show individual chunk scores and IDs')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (query: string, options) => {\n try {\n await loadConfig({ verbose: options.verbose });\n const results = await searchContent(query, { limit: parseInt(options.limit) });\n \n if (results.length === 0) {\n console.log('No results found');\n return;\n }\n\n console.log(`Found ${results.length} results:\\n`);\n results.forEach((result, index) => {\n console.log(`${index + 1}. ${result.filePath}`);\n console.log(` Score: ${result.score.toFixed(3)} (${result.matchingChunks} chunks)`);\n \n if (options.showChunks && result.chunks.length > 0) {\n console.log(` Chunks:`);\n result.chunks.forEach(chunk => {\n console.log(` - Chunk ${chunk.chunkId}: ${chunk.score.toFixed(3)}`);\n });\n }\n \n console.log();\n });\n } catch (error) {\n console.error('Error searching content:', error);\n process.exit(1);\n }\n });\n\n program\n .command('similar')\n .description('Find files similar to a given file')\n .argument('<file>', 'File path to find similar files for')\n .option('-l, --limit <number>', 'Maximum number of results', '10')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (filePath: string, options) => {\n try {\n await loadConfig({ verbose: options.verbose });\n const results = await findSimilarFiles(filePath, parseInt(options.limit));\n \n if (results.length === 0) {\n console.log('No similar files found');\n return;\n }\n\n console.log(`Found ${results.length} similar files:\\n`);\n results.forEach((result, index) => {\n console.log(`${index + 1}. ${result.filePath}`);\n console.log(` Similarity: ${result.score.toFixed(3)}`);\n console.log();\n });\n } catch (error) {\n console.error('Error finding similar files:', error);\n process.exit(1);\n }\n });\n\n program\n .command('get')\n .description('Get file content')\n .argument('<file>', 'File path to retrieve')\n .option('-c, --chunks <range>', 'Chunk range (e.g., \"2-5\")')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (filePath: string, options) => {\n try {\n await loadConfig({ verbose: options.verbose });\n const content = await getFileContent(filePath, options.chunks);\n console.log(content);\n } catch (error) {\n console.error('Error getting file content:', error);\n process.exit(1);\n }\n });\n\n program\n .command('serve')\n .description('Start MCP server')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (options) => {\n try {\n const config = await loadConfig({ verbose: options.verbose });\n await startMcpServer(config);\n } catch (error) {\n console.error('Error starting MCP server:', error);\n process.exit(1);\n }\n });\n\n program\n .command('status')\n .description('Show indexing status')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (options) => {\n try {\n await loadConfig({ verbose: options.verbose });\n const status = await getIndexStatus();\n \n console.log('Directory Indexer Status Report');\n console.log('=====================================');\n console.log('');\n console.log('OVERVIEW:');\n console.log(` • ${status.directoriesIndexed} directories have been indexed`);\n console.log(` • ${status.filesIndexed} files processed for semantic search`);\n console.log(` • ${status.chunksIndexed} text chunks available for AI search`);\n console.log(` • Database storage: ${status.databaseSize}`);\n console.log(` • Most recent indexing: ${status.lastIndexed || 'No indexing performed yet'}`);\n \n if (status.errors.length > 0) {\n console.log(` • Processing errors encountered: ${status.errors.length}`);\n if (options.verbose) {\n console.log('');\n console.log('RECENT ERRORS:');\n status.errors.slice(0, 5).forEach(error => {\n console.log(` - ${error}`);\n });\n }\n }\n \n console.log('');\n console.log('INDEXED DIRECTORIES:');\n if (status.directories.length === 0) {\n console.log(' No directories have been indexed yet.');\n console.log(' Run \"directory-indexer index <path>\" to start indexing.');\n } else {\n status.directories.forEach(dir => {\n console.log('');\n console.log(` Directory: ${dir.path}`);\n console.log(` • Indexing status: ${dir.status}`);\n console.log(` • Files processed: ${dir.filesCount}`);\n console.log(` • Searchable chunks: ${dir.chunksCount}`);\n console.log(` • Last indexed: ${dir.lastIndexed || 'Never completed'}`);\n if (dir.errors.length > 0) {\n console.log(` • Files with errors: ${dir.errors.length}`);\n if (options.verbose) {\n console.log(' • Recent errors:');\n dir.errors.slice(0, 3).forEach(error => {\n console.log(` - ${error}`);\n });\n }\n }\n });\n }\n \n if (!status.qdrantConsistency.isConsistent) {\n console.log('');\n console.log('SYSTEM STATUS:');\n status.qdrantConsistency.issues.forEach(issue => {\n console.log(` • ${issue}`);\n });\n console.log('');\n console.log('ℹ️ Note: Status messages above may be normal during setup or active indexing.');\n } else {\n console.log('');\n console.log('SYSTEM STATUS:');\n console.log(' • All systems operational - ready for AI-powered search');\n }\n } catch (error) {\n console.error('Error getting status:', error);\n process.exit(1);\n }\n });\n\n await program.parseAsync();\n}\n\n// Main function is already exported above"],"names":["__dirname","packageJsonPath","packageJson","VERSION"],"mappings":";;;;;;;;;;;;AAgBA,MAAMA,cAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,MAAMC,oBAAkB,KAAKD,aAAW,iBAAiB;AACzD,MAAME,gBAAc,KAAK,MAAM,aAAaD,mBAAiB,OAAO,CAAC;AACrE,MAAME,YAAUD,cAAY;AAE5B,MAAM,YAAoB;AAAA,EACxB;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAmBb,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,gBAAgB;AAAA,UACd,MAAM;AAAA,UACN,aAAa;AAAA,QAAA;AAAA,MACf;AAAA,MAEF,UAAU,CAAC,gBAAgB;AAAA,IAAA;AAAA,EAC7B;AAAA,EAEF;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAyBb,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,QAAA;AAAA,QAEf,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,UACb,SAAS;AAAA,QAAA;AAAA,MACX;AAAA,MAEF,UAAU,CAAC,OAAO;AAAA,IAAA;AAAA,EACpB;AAAA,EAEF;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAwBb,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,WAAW;AAAA,UACT,MAAM;AAAA,UACN,aAAa;AAAA,QAAA;AAAA,QAEf,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,UACb,SAAS;AAAA,QAAA;AAAA,MACX;AAAA,MAEF,UAAU,CAAC,WAAW;AAAA,IAAA;AAAA,EACxB;AAAA,EAEF;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA4Bb,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,WAAW;AAAA,UACT,MAAM;AAAA,UACN,aAAa;AAAA,QAAA;AAAA,QAEf,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,aAAa;AAAA,QAAA;AAAA,MACf;AAAA,MAEF,UAAU,CAAC,WAAW;AAAA,IAAA;AAAA,EACxB;AAAA,EAEF;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA4Bb,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,WAAW;AAAA,UACT,MAAM;AAAA,UACN,aAAa;AAAA,QAAA;AAAA,QAEf,UAAU;AAAA,UACR,MAAM;AAAA,UACN,aAAa;AAAA,QAAA;AAAA,MACf;AAAA,MAEF,UAAU,CAAC,aAAa,UAAU;AAAA,IAAA;AAAA,EACpC;AAAA,EAEF;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAiCb,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY,CAAA;AAAA,MACZ,sBAAsB;AAAA,IAAA;AAAA,EACxB;AAEJ;AAEA,eAAsB,eAAe,QAA+B;AAClE,QAAM,SAAS,IAAI;AAAA,IACjB;AAAA,MACE,MAAM;AAAA,MACN,SAASC;AAAAA,IAAA;AAAA,IAEX;AAAA,MACE,cAAc;AAAA,QACZ,OAAO,CAAA;AAAA,MAAC;AAAA,IACV;AAAA,EACF;AAGF,SAAO,kBAAkB,wBAAwB,YAAY;AAC3D,WAAO;AAAA,MACL,OAAO;AAAA,IAAA;AAAA,EAEX,CAAC;AAED,SAAO,kBAAkB,uBAAuB,OAAO,YAAY;AACjE,UAAM,EAAE,MAAM,WAAW,KAAA,IAAS,QAAQ;AAE1C,QAAI;AACF,cAAQ,MAAA;AAAA,QACN,KAAK,SAAS;AACZ,cAAI,CAAC,QAAQ,OAAO,KAAK,mBAAmB,UAAU;AACpD,kBAAM,IAAI,MAAM,4BAA4B;AAAA,UAC9C;AACA,gBAAM,QAAQ,KAAK,eAAe,MAAM,GAAG,EAAE,IAAI,CAAC,MAAc,EAAE,KAAA,CAAM;AACxE,gBAAM,SAAS,MAAM,iBAAiB,OAAO,MAAM;AACnD,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,WAAW,OAAO,OAAO,mBAAmB,OAAO,OAAO,WAAW,OAAO,OAAO,MAAM;AAAA,cAAA;AAAA,YACjG;AAAA,UACF;AAAA,QAEJ;AAAA,QAEA,KAAK,UAAU;AACb,cAAI,CAAC,QAAQ,OAAO,KAAK,UAAU,UAAU;AAC3C,kBAAM,IAAI,MAAM,mBAAmB;AAAA,UACrC;AACA,gBAAM,UAAU,MAAM,cAAc,KAAK,OAAO,EAAE,OAAQ,KAAK,SAAoB,IAAI;AACvF,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC;AAAA,cAAA;AAAA,YACvC;AAAA,UACF;AAAA,QAEJ;AAAA,QAEA,KAAK,iBAAiB;AACpB,cAAI,CAAC,QAAQ,OAAO,KAAK,cAAc,UAAU;AAC/C,kBAAM,IAAI,MAAM,uBAAuB;AAAA,UACzC;AACA,gBAAM,UAAU,MAAM,iBAAiB,KAAK,WAAY,KAAK,SAAoB,EAAE;AACnF,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC;AAAA,cAAA;AAAA,YACvC;AAAA,UACF;AAAA,QAEJ;AAAA,QAEA,KAAK,eAAe;AAClB,cAAI,CAAC,QAAQ,OAAO,KAAK,cAAc,UAAU;AAC/C,kBAAM,IAAI,MAAM,uBAAuB;AAAA,UACzC;AACA,gBAAM,UAAU,MAAM,eAAe,KAAK,WAAW,KAAK,MAAgB;AAC1E,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM;AAAA,cAAA;AAAA,YACR;AAAA,UACF;AAAA,QAEJ;AAAA,QAEA,KAAK,aAAa;AAChB,cAAI,CAAC,QAAQ,OAAO,KAAK,cAAc,YAAY,OAAO,KAAK,aAAa,UAAU;AACpF,kBAAM,IAAI,MAAM,qCAAqC;AAAA,UACvD;AACA,gBAAM,UAAU,MAAM,gBAAgB,KAAK,WAAW,KAAK,QAAQ;AACnE,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM;AAAA,cAAA;AAAA,YACR;AAAA,UACF;AAAA,QAEJ;AAAA,QAEA,KAAK,eAAe;AAClB,gBAAM,SAAS,MAAM,eAAA;AACrB,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU;AAAA,kBACnB,MAAM;AAAA,kBACN,SAASA;AAAAA,kBACT;AAAA,gBAAA,GACC,MAAM,CAAC;AAAA,cAAA;AAAA,YACZ;AAAA,UACF;AAAA,QAEJ;AAAA,QAEA;AACE,gBAAM,IAAI,MAAM,iBAAiB,IAAI,EAAE;AAAA,MAAA;AAAA,IAE7C,SAAS,OAAO;AACd,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAC9D,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,UAAU,YAAY;AAAA,UAAA;AAAA,QAC9B;AAAA,QAEF,SAAS;AAAA,MAAA;AAAA,IAEb;AAAA,EACF,CAAC;AAED,QAAM,YAAY,IAAI,qBAAA;AACtB,QAAM,OAAO,QAAQ,SAAS;AAE9B,MAAI,OAAO,SAAS;AAClB,YAAQ,MAAM,iCAAiC;AAAA,EACjD;AACF;AC9YA,MAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,MAAM,kBAAkB,KAAK,WAAW,iBAAiB;AACzD,MAAM,cAAc,KAAK,MAAM,aAAa,iBAAiB,OAAO,CAAC;AACrE,MAAM,UAAU,YAAY;AAE5B,eAAsB,OAAO;AAC3B,QAAM,UAAU,IAAI,QAAA;AAEpB,UACG,KAAK,mBAAmB,EACxB,YAAY,oDAAoD,EAChE,QAAQ,OAAO;AAElB,UACG,QAAQ,OAAO,EACf,YAAY,uCAAuC,EACnD,SAAS,cAAc,0BAA0B,EACjD,OAAO,iBAAiB,wBAAwB,EAChD,OAAO,OAAO,OAAiB,YAAY;AAC1C,QAAI;AACF,YAAM,SAAS,MAAM,WAAW,EAAE,SAAS,QAAQ,SAAS;AAC5D,cAAQ,IAAI,YAAY,MAAM,MAAM,IAAI,MAAM,WAAW,IAAI,cAAc,aAAa,KAAK,MAAM,KAAK,IAAI,CAAC,EAAE;AAC/G,YAAM,SAAS,MAAM,iBAAiB,OAAO,MAAM;AACnD,cAAQ,IAAI,WAAW,OAAO,OAAO,mBAAmB,OAAO,OAAO,WAAW,OAAO,OAAO,MAAM,SAAS;AAC9G,UAAI,OAAO,OAAO,SAAS,KAAK,OAAO,SAAS;AAC9C,gBAAQ,IAAI,WAAW,OAAO,MAAM;AAAA,MACtC;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,+BAA+B,KAAK;AAClD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,QAAQ,EAChB,YAAY,qCAAqC,EACjD,SAAS,WAAW,cAAc,EAClC,OAAO,wBAAwB,6BAA6B,IAAI,EAChE,OAAO,qBAAqB,sCAAsC,EAClE,OAAO,iBAAiB,wBAAwB,EAChD,OAAO,OAAO,OAAe,YAAY;AACxC,QAAI;AACF,YAAM,WAAW,EAAE,SAAS,QAAQ,SAAS;AAC7C,YAAM,UAAU,MAAM,cAAc,OAAO,EAAE,OAAO,SAAS,QAAQ,KAAK,GAAG;AAE7E,UAAI,QAAQ,WAAW,GAAG;AACxB,gBAAQ,IAAI,kBAAkB;AAC9B;AAAA,MACF;AAEA,cAAQ,IAAI,SAAS,QAAQ,MAAM;AAAA,CAAa;AAChD,cAAQ,QAAQ,CAAC,QAAQ,UAAU;AACjC,gBAAQ,IAAI,GAAG,QAAQ,CAAC,KAAK,OAAO,QAAQ,EAAE;AAC9C,gBAAQ,IAAI,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK,OAAO,cAAc,UAAU;AAEpF,YAAI,QAAQ,cAAc,OAAO,OAAO,SAAS,GAAG;AAClD,kBAAQ,IAAI,YAAY;AACxB,iBAAO,OAAO,QAAQ,CAAA,UAAS;AAC7B,oBAAQ,IAAI,gBAAgB,MAAM,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE;AAAA,UACxE,CAAC;AAAA,QACH;AAEA,gBAAQ,IAAA;AAAA,MACV,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,MAAM,4BAA4B,KAAK;AAC/C,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,SAAS,EACjB,YAAY,oCAAoC,EAChD,SAAS,UAAU,qCAAqC,EACxD,OAAO,wBAAwB,6BAA6B,IAAI,EAChE,OAAO,iBAAiB,wBAAwB,EAChD,OAAO,OAAO,UAAkB,YAAY;AAC3C,QAAI;AACF,YAAM,WAAW,EAAE,SAAS,QAAQ,SAAS;AAC7C,YAAM,UAAU,MAAM,iBAAiB,UAAU,SAAS,QAAQ,KAAK,CAAC;AAExE,UAAI,QAAQ,WAAW,GAAG;AACxB,gBAAQ,IAAI,wBAAwB;AACpC;AAAA,MACF;AAEA,cAAQ,IAAI,SAAS,QAAQ,MAAM;AAAA,CAAmB;AACtD,cAAQ,QAAQ,CAAC,QAAQ,UAAU;AACjC,gBAAQ,IAAI,GAAG,QAAQ,CAAC,KAAK,OAAO,QAAQ,EAAE;AAC9C,gBAAQ,IAAI,kBAAkB,OAAO,MAAM,QAAQ,CAAC,CAAC,EAAE;AACvD,gBAAQ,IAAA;AAAA,MACV,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,MAAM,gCAAgC,KAAK;AACnD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,KAAK,EACb,YAAY,kBAAkB,EAC9B,SAAS,UAAU,uBAAuB,EAC1C,OAAO,wBAAwB,2BAA2B,EAC1D,OAAO,iBAAiB,wBAAwB,EAChD,OAAO,OAAO,UAAkB,YAAY;AAC3C,QAAI;AACF,YAAM,WAAW,EAAE,SAAS,QAAQ,SAAS;AAC7C,YAAM,UAAU,MAAM,eAAe,UAAU,QAAQ,MAAM;AAC7D,cAAQ,IAAI,OAAO;AAAA,IACrB,SAAS,OAAO;AACd,cAAQ,MAAM,+BAA+B,KAAK;AAClD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,OAAO,EACf,YAAY,kBAAkB,EAC9B,OAAO,iBAAiB,wBAAwB,EAChD,OAAO,OAAO,YAAY;AACzB,QAAI;AACF,YAAM,SAAS,MAAM,WAAW,EAAE,SAAS,QAAQ,SAAS;AAC5D,YAAM,eAAe,MAAM;AAAA,IAC7B,SAAS,OAAO;AACd,cAAQ,MAAM,8BAA8B,KAAK;AACjD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,QAAQ,EAChB,YAAY,sBAAsB,EAClC,OAAO,iBAAiB,wBAAwB,EAChD,OAAO,OAAO,YAAY;AACzB,QAAI;AACF,YAAM,WAAW,EAAE,SAAS,QAAQ,SAAS;AAC7C,YAAM,SAAS,MAAM,eAAA;AAErB,cAAQ,IAAI,iCAAiC;AAC7C,cAAQ,IAAI,uCAAuC;AACnD,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,WAAW;AACvB,cAAQ,IAAI,OAAO,OAAO,kBAAkB,gCAAgC;AAC5E,cAAQ,IAAI,OAAO,OAAO,YAAY,sCAAsC;AAC5E,cAAQ,IAAI,OAAO,OAAO,aAAa,sCAAsC;AAC7E,cAAQ,IAAI,yBAAyB,OAAO,YAAY,EAAE;AAC1D,cAAQ,IAAI,6BAA6B,OAAO,eAAe,2BAA2B,EAAE;AAE5F,UAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,gBAAQ,IAAI,sCAAsC,OAAO,OAAO,MAAM,EAAE;AACxE,YAAI,QAAQ,SAAS;AACnB,kBAAQ,IAAI,EAAE;AACd,kBAAQ,IAAI,gBAAgB;AAC5B,iBAAO,OAAO,MAAM,GAAG,CAAC,EAAE,QAAQ,CAAA,UAAS;AACzC,oBAAQ,IAAI,OAAO,KAAK,EAAE;AAAA,UAC5B,CAAC;AAAA,QACH;AAAA,MACF;AAEA,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,sBAAsB;AAClC,UAAI,OAAO,YAAY,WAAW,GAAG;AACnC,gBAAQ,IAAI,yCAAyC;AACrD,gBAAQ,IAAI,2DAA2D;AAAA,MACzE,OAAO;AACL,eAAO,YAAY,QAAQ,CAAA,QAAO;AAChC,kBAAQ,IAAI,EAAE;AACd,kBAAQ,IAAI,gBAAgB,IAAI,IAAI,EAAE;AACtC,kBAAQ,IAAI,0BAA0B,IAAI,MAAM,EAAE;AAClD,kBAAQ,IAAI,0BAA0B,IAAI,UAAU,EAAE;AACtD,kBAAQ,IAAI,4BAA4B,IAAI,WAAW,EAAE;AACzD,kBAAQ,IAAI,uBAAuB,IAAI,eAAe,iBAAiB,EAAE;AACzE,cAAI,IAAI,OAAO,SAAS,GAAG;AACzB,oBAAQ,IAAI,4BAA4B,IAAI,OAAO,MAAM,EAAE;AAC3D,gBAAI,QAAQ,SAAS;AACnB,sBAAQ,IAAI,sBAAsB;AAClC,kBAAI,OAAO,MAAM,GAAG,CAAC,EAAE,QAAQ,CAAA,UAAS;AACtC,wBAAQ,IAAI,WAAW,KAAK,EAAE;AAAA,cAChC,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AAEA,UAAI,CAAC,OAAO,kBAAkB,cAAc;AAC1C,gBAAQ,IAAI,EAAE;AACd,gBAAQ,IAAI,gBAAgB;AAC5B,eAAO,kBAAkB,OAAO,QAAQ,CAAA,UAAS;AAC/C,kBAAQ,IAAI,OAAO,KAAK,EAAE;AAAA,QAC5B,CAAC;AACD,gBAAQ,IAAI,EAAE;AACd,gBAAQ,IAAI,gFAAgF;AAAA,MAC9F,OAAO;AACL,gBAAQ,IAAI,EAAE;AACd,gBAAQ,IAAI,gBAAgB;AAC5B,gBAAQ,IAAI,2DAA2D;AAAA,MACzE;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,yBAAyB,KAAK;AAC5C,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,QAAM,QAAQ,WAAA;AAChB;"}
|
package/dist/config.js
CHANGED
|
@@ -36,13 +36,13 @@ function loadConfig(options = {}) {
|
|
|
36
36
|
const config = {
|
|
37
37
|
storage: {
|
|
38
38
|
sqlitePath: join(dataDir, dbFileName),
|
|
39
|
-
qdrantEndpoint: process.env.QDRANT_ENDPOINT || "http://
|
|
39
|
+
qdrantEndpoint: process.env.QDRANT_ENDPOINT || "http://127.0.0.1:6333",
|
|
40
40
|
qdrantCollection: process.env.DIRECTORY_INDEXER_QDRANT_COLLECTION || defaultCollection
|
|
41
41
|
},
|
|
42
42
|
embedding: {
|
|
43
43
|
provider: process.env.EMBEDDING_PROVIDER || "ollama",
|
|
44
44
|
model: process.env.EMBEDDING_MODEL || "nomic-embed-text",
|
|
45
|
-
endpoint: process.env.OLLAMA_ENDPOINT || "http://
|
|
45
|
+
endpoint: process.env.OLLAMA_ENDPOINT || "http://127.0.0.1:11434"
|
|
46
46
|
},
|
|
47
47
|
indexing: {
|
|
48
48
|
chunkSize: parseInt(process.env.CHUNK_SIZE || "512"),
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sources":["../src/config.ts"],"sourcesContent":["import { homedir } from 'os';\nimport { join } from 'path';\nimport { z } from 'zod';\n\nconst ConfigSchema = z.object({\n storage: z.object({\n sqlitePath: z.string(),\n qdrantEndpoint: z.string().url(),\n qdrantCollection: z.string(),\n }),\n embedding: z.object({\n provider: z.enum(['ollama', 'openai', 'mock']),\n model: z.string(),\n endpoint: z.string().url(),\n }),\n indexing: z.object({\n chunkSize: z.number().positive(),\n chunkOverlap: z.number().nonnegative(),\n maxFileSize: z.number().positive(),\n ignorePatterns: z.array(z.string()),\n }),\n dataDir: z.string(),\n verbose: z.boolean(),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport class ConfigError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\nexport function loadConfig(options: { verbose?: boolean } = {}): Config {\n const dataDir = process.env.DIRECTORY_INDEXER_DATA_DIR || join(homedir(), '.directory-indexer');\n \n // Use separate database file and collection for tests to avoid contaminating main data\n const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST === 'true';\n const dbFileName = isTest ? 'test-data.db' : 'data.db';\n const defaultCollection = isTest ? 'directory-indexer-test' : 'directory-indexer';\n \n const config = {\n storage: {\n sqlitePath: join(dataDir, dbFileName),\n qdrantEndpoint: process.env.QDRANT_ENDPOINT || 'http://
|
|
1
|
+
{"version":3,"file":"config.js","sources":["../src/config.ts"],"sourcesContent":["import { homedir } from 'os';\nimport { join } from 'path';\nimport { z } from 'zod';\n\nconst ConfigSchema = z.object({\n storage: z.object({\n sqlitePath: z.string(),\n qdrantEndpoint: z.string().url(),\n qdrantCollection: z.string(),\n }),\n embedding: z.object({\n provider: z.enum(['ollama', 'openai', 'mock']),\n model: z.string(),\n endpoint: z.string().url(),\n }),\n indexing: z.object({\n chunkSize: z.number().positive(),\n chunkOverlap: z.number().nonnegative(),\n maxFileSize: z.number().positive(),\n ignorePatterns: z.array(z.string()),\n }),\n dataDir: z.string(),\n verbose: z.boolean(),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport class ConfigError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\nexport function loadConfig(options: { verbose?: boolean } = {}): Config {\n const dataDir = process.env.DIRECTORY_INDEXER_DATA_DIR || join(homedir(), '.directory-indexer');\n \n // Use separate database file and collection for tests to avoid contaminating main data\n const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST === 'true';\n const dbFileName = isTest ? 'test-data.db' : 'data.db';\n const defaultCollection = isTest ? 'directory-indexer-test' : 'directory-indexer';\n \n const config = {\n storage: {\n sqlitePath: join(dataDir, dbFileName),\n qdrantEndpoint: process.env.QDRANT_ENDPOINT || 'http://127.0.0.1:6333',\n qdrantCollection: process.env.DIRECTORY_INDEXER_QDRANT_COLLECTION || defaultCollection,\n },\n embedding: {\n provider: (process.env.EMBEDDING_PROVIDER as Config['embedding']['provider']) || 'ollama',\n model: process.env.EMBEDDING_MODEL || 'nomic-embed-text',\n endpoint: process.env.OLLAMA_ENDPOINT || 'http://127.0.0.1:11434',\n },\n indexing: {\n chunkSize: parseInt(process.env.CHUNK_SIZE || '512'),\n chunkOverlap: parseInt(process.env.CHUNK_OVERLAP || '50'),\n maxFileSize: parseInt(process.env.MAX_FILE_SIZE || '10485760'),\n ignorePatterns: ['.git', 'node_modules', 'target', '.DS_Store'],\n },\n dataDir,\n verbose: options.verbose ?? (process.env.VERBOSE === 'true'),\n };\n\n try {\n return ConfigSchema.parse(config);\n } catch (error) {\n if (error instanceof z.ZodError) {\n const messages = error.errors.map(e => `${e.path.join('.')}: ${e.message}`);\n throw new ConfigError(`Configuration validation failed: ${messages.join(', ')}`, error);\n }\n throw new ConfigError('Failed to load configuration', error as Error);\n }\n}"],"names":[],"mappings":";;;AAIA,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,SAAS,EAAE,OAAO;AAAA,IAChB,YAAY,EAAE,OAAA;AAAA,IACd,gBAAgB,EAAE,OAAA,EAAS,IAAA;AAAA,IAC3B,kBAAkB,EAAE,OAAA;AAAA,EAAO,CAC5B;AAAA,EACD,WAAW,EAAE,OAAO;AAAA,IAClB,UAAU,EAAE,KAAK,CAAC,UAAU,UAAU,MAAM,CAAC;AAAA,IAC7C,OAAO,EAAE,OAAA;AAAA,IACT,UAAU,EAAE,OAAA,EAAS,IAAA;AAAA,EAAI,CAC1B;AAAA,EACD,UAAU,EAAE,OAAO;AAAA,IACjB,WAAW,EAAE,OAAA,EAAS,SAAA;AAAA,IACtB,cAAc,EAAE,OAAA,EAAS,YAAA;AAAA,IACzB,aAAa,EAAE,OAAA,EAAS,SAAA;AAAA,IACxB,gBAAgB,EAAE,MAAM,EAAE,QAAQ;AAAA,EAAA,CACnC;AAAA,EACD,SAAS,EAAE,OAAA;AAAA,EACX,SAAS,EAAE,QAAA;AACb,CAAC;AAIM,MAAM,oBAAoB,MAAM;AAAA,EACrC,YAAY,SAAiC,OAAe;AAC1D,UAAM,OAAO;AAD8B,SAAA,QAAA;AAE3C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,WAAW,UAAiC,IAAY;AACtE,QAAM,UAAU,QAAQ,IAAI,8BAA8B,KAAK,QAAA,GAAW,oBAAoB;AAG9F,QAAM,SAAS,QAAQ,IAAI,aAAa,UAAU,QAAQ,IAAI,WAAW;AACzE,QAAM,aAAa,SAAS,iBAAiB;AAC7C,QAAM,oBAAoB,SAAS,2BAA2B;AAE9D,QAAM,SAAS;AAAA,IACb,SAAS;AAAA,MACP,YAAY,KAAK,SAAS,UAAU;AAAA,MACpC,gBAAgB,QAAQ,IAAI,mBAAmB;AAAA,MAC/C,kBAAkB,QAAQ,IAAI,uCAAuC;AAAA,IAAA;AAAA,IAEvE,WAAW;AAAA,MACT,UAAW,QAAQ,IAAI,sBAA0D;AAAA,MACjF,OAAO,QAAQ,IAAI,mBAAmB;AAAA,MACtC,UAAU,QAAQ,IAAI,mBAAmB;AAAA,IAAA;AAAA,IAE3C,UAAU;AAAA,MACR,WAAW,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA,MACnD,cAAc,SAAS,QAAQ,IAAI,iBAAiB,IAAI;AAAA,MACxD,aAAa,SAAS,QAAQ,IAAI,iBAAiB,UAAU;AAAA,MAC7D,gBAAgB,CAAC,QAAQ,gBAAgB,UAAU,WAAW;AAAA,IAAA;AAAA,IAEhE;AAAA,IACA,SAAS,QAAQ,WAAY,QAAQ,IAAI,YAAY;AAAA,EAAA;AAGvD,MAAI;AACF,WAAO,aAAa,MAAM,MAAM;AAAA,EAClC,SAAS,OAAO;AACd,QAAI,iBAAiB,EAAE,UAAU;AAC/B,YAAM,WAAW,MAAM,OAAO,IAAI,OAAK,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE;AAC1E,YAAM,IAAI,YAAY,oCAAoC,SAAS,KAAK,IAAI,CAAC,IAAI,KAAK;AAAA,IACxF;AACA,UAAM,IAAI,YAAY,gCAAgC,KAAc;AAAA,EACtE;AACF;"}
|
package/dist/indexing.js
CHANGED
|
@@ -120,6 +120,23 @@ async function indexDirectories(paths, config) {
|
|
|
120
120
|
maxFileSize: config.indexing.maxFileSize
|
|
121
121
|
};
|
|
122
122
|
const { sqlite, qdrant } = await initializeStorage(config);
|
|
123
|
+
let totalFiles = 0;
|
|
124
|
+
for (const path of paths) {
|
|
125
|
+
try {
|
|
126
|
+
if (config.verbose) {
|
|
127
|
+
console.log(`Scanning directory: ${path}`);
|
|
128
|
+
}
|
|
129
|
+
const files = await scanDirectory(path, scanOptions);
|
|
130
|
+
totalFiles += files.length;
|
|
131
|
+
if (config.verbose) {
|
|
132
|
+
console.log(`Found ${files.length} files to process in ${path}`);
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!config.verbose && totalFiles > 0) {
|
|
138
|
+
console.log(`Processing ${totalFiles} files...`);
|
|
139
|
+
}
|
|
123
140
|
for (const path of paths) {
|
|
124
141
|
try {
|
|
125
142
|
const normalizedPath = normalizePath(path);
|
|
@@ -158,6 +175,9 @@ async function indexDirectories(paths, config) {
|
|
|
158
175
|
await qdrant.upsertPoints([point]);
|
|
159
176
|
}
|
|
160
177
|
indexed++;
|
|
178
|
+
if (config.verbose) {
|
|
179
|
+
console.log(` Indexed: ${file.path} (${chunks.length} chunks)`);
|
|
180
|
+
}
|
|
161
181
|
} catch (error) {
|
|
162
182
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
163
183
|
const causeMessage = error instanceof Error && error.cause ? `: ${error.cause.message}` : "";
|
package/dist/indexing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexing.js","sources":["../src/indexing.ts"],"sourcesContent":["import { promises as fs } from 'fs';\nimport { join } from 'path';\nimport { Config } from './config.js';\nimport { \n FileInfo, \n ChunkInfo, \n normalizePath, \n getFileInfo, \n shouldIgnoreFile, \n isSupportedFileType,\n isDirectory,\n isFile\n} from './utils.js';\nimport { generateEmbedding } from './embedding.js';\nimport { initializeStorage, FileRecord } from './storage.js';\n\nexport interface ScanOptions {\n ignorePatterns: string[];\n maxFileSize: number;\n}\n\nexport interface IndexResult {\n indexed: number;\n skipped: number;\n errors: string[];\n}\n\nexport class IndexingError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'IndexingError';\n }\n}\n\nexport function chunkText(content: string, chunkSize: number, overlap: number): ChunkInfo[] {\n if (content.length <= chunkSize) {\n return [{\n id: '0',\n content,\n startIndex: 0,\n endIndex: content.length\n }];\n }\n \n const chunks: ChunkInfo[] = [];\n let startIndex = 0;\n let chunkId = 0;\n \n while (startIndex < content.length) {\n const endIndex = Math.min(startIndex + chunkSize, content.length);\n const chunkContent = content.slice(startIndex, endIndex);\n \n chunks.push({\n id: chunkId.toString(),\n content: chunkContent,\n startIndex,\n endIndex\n });\n \n chunkId++;\n const nextStart = endIndex - overlap;\n \n if (nextStart <= startIndex) {\n startIndex = startIndex + Math.max(1, chunkSize - overlap);\n } else {\n startIndex = nextStart;\n }\n \n if (startIndex >= content.length) break;\n }\n \n return chunks;\n}\n\nexport async function scanDirectory(dirPath: string, options: ScanOptions): Promise<FileInfo[]> {\n const files: FileInfo[] = [];\n const visited = new Set<string>();\n \n async function walkDirectory(currentPath: string): Promise<void> {\n const normalizedPath = normalizePath(currentPath);\n \n if (visited.has(normalizedPath)) {\n return;\n }\n visited.add(normalizedPath);\n \n try {\n if (shouldIgnoreFile(normalizedPath, options.ignorePatterns)) {\n return;\n }\n \n if (await isDirectory(normalizedPath)) {\n const entries = await fs.readdir(normalizedPath);\n \n for (const entry of entries) {\n const fullPath = join(normalizedPath, entry);\n await walkDirectory(fullPath);\n }\n } else if (await isFile(normalizedPath)) {\n if (!isSupportedFileType(normalizedPath)) {\n return;\n }\n \n const stats = await fs.stat(normalizedPath);\n if (stats.size > options.maxFileSize) {\n return;\n }\n \n const fileInfo = await getFileInfo(normalizedPath);\n files.push(fileInfo);\n }\n } catch (error) {\n throw new IndexingError(`Failed to scan directory: ${normalizedPath}`, error as Error);\n }\n }\n \n await walkDirectory(dirPath);\n return files;\n}\n\nexport async function getFileMetadata(filePath: string): Promise<FileInfo> {\n try {\n return await getFileInfo(filePath);\n } catch (error) {\n throw new IndexingError(`Failed to get file metadata`, error as Error);\n }\n}\n\nasync function shouldReprocessFile(filePath: string, existingRecord: FileRecord, config: Config): Promise<boolean> {\n try {\n const fs = await import('fs/promises');\n \n // Try modtime check first (fast path)\n const currentStats = await fs.stat(filePath);\n const existingModTime = new Date(existingRecord.modifiedTime);\n \n // If modtime is clearly older, likely unchanged\n if (currentStats.mtime <= existingModTime) {\n return false; // Skip processing\n }\n \n // If modtime suggests change, verify with hash\n const currentFileInfo = await getFileInfo(filePath);\n return currentFileInfo.hash !== existingRecord.hash;\n \n } catch (modtimeError) {\n // Graceful fallback: skip modtime, use hash only\n if (config.verbose) {\n console.log(`Warning: Could not check modification time for ${filePath}:`, modtimeError);\n }\n try {\n const currentFileInfo = await getFileInfo(filePath);\n return currentFileInfo.hash !== existingRecord.hash;\n } catch (hashError) {\n // If we can't hash either, assume changed to be safe\n if (config.verbose) {\n console.log(`Warning: Could not compute hash for ${filePath}:`, hashError);\n }\n return true;\n }\n }\n}\n\nexport async function indexDirectories(paths: string[], config: Config): Promise<IndexResult> {\n let indexed = 0;\n let skipped = 0;\n const errors: string[] = [];\n \n const scanOptions: ScanOptions = {\n ignorePatterns: config.indexing.ignorePatterns,\n maxFileSize: config.indexing.maxFileSize\n };\n \n // Initialize storage\n const { sqlite, qdrant } = await initializeStorage(config);\n \n for (const path of paths) {\n try {\n // Mark directory as indexing\n const normalizedPath = normalizePath(path);\n await sqlite.upsertDirectory(normalizedPath, 'indexing');\n \n const files = await scanDirectory(path, scanOptions);\n \n for (const file of files) {\n try {\n // Check if file already exists and needs reprocessing\n const existingFile = await sqlite.getFile(file.path);\n \n if (existingFile) {\n const needsReprocessing = await shouldReprocessFile(file.path, existingFile, config);\n if (!needsReprocessing) {\n skipped++;\n continue; // Skip unchanged file\n }\n \n // File changed - clean up old vectors first\n await qdrant.deletePointsByFileHash(existingFile.hash);\n }\n \n const content = await fs.readFile(file.path, 'utf-8');\n const chunks = chunkText(content, config.indexing.chunkSize, config.indexing.chunkOverlap);\n \n // Store file metadata in SQLite\n await sqlite.upsertFile(file, chunks);\n \n // Generate embeddings and store in Qdrant\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i];\n const embedding = await generateEmbedding(chunk.content, config);\n // Generate a unique integer ID by combining hash and chunk index\n const hashNum = parseInt(file.hash.slice(0, 8), 16);\n const pointId = (hashNum % 1000000) * 1000 + parseInt(chunk.id);\n const point = {\n id: pointId,\n vector: embedding,\n payload: {\n filePath: file.path,\n chunkId: chunk.id,\n fileHash: file.hash,\n content: chunk.content,\n parentDirectories: file.parentDirs\n }\n };\n await qdrant.upsertPoints([point]);\n }\n \n indexed++;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const causeMessage = error instanceof Error && error.cause ? `: ${(error.cause as Error).message}` : '';\n errors.push(`Failed to process ${file.path}: ${errorMessage}${causeMessage}`);\n }\n }\n \n // Mark directory as completed if no errors for this directory\n const directoryErrors = errors.filter(err => err.includes(path));\n const directoryStatus = directoryErrors.length > 0 ? 'failed' : 'completed';\n await sqlite.upsertDirectory(normalizedPath, directoryStatus);\n \n } catch (error) {\n const normalizedPath = normalizePath(path);\n await sqlite.upsertDirectory(normalizedPath, 'failed');\n errors.push(`Failed to scan directory ${path}: ${(error as Error).message}`);\n }\n }\n \n return { indexed, skipped, errors };\n}"],"names":["fs"],"mappings":";;;;;AA2BO,MAAM,sBAAsB,MAAM;AAAA,EACvC,YAAY,SAAiC,OAAe;AAC1D,UAAM,OAAO;AAD8B,SAAA,QAAA;AAE3C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,UAAU,SAAiB,WAAmB,SAA8B;AAC1F,MAAI,QAAQ,UAAU,WAAW;AAC/B,WAAO,CAAC;AAAA,MACN,IAAI;AAAA,MACJ;AAAA,MACA,YAAY;AAAA,MACZ,UAAU,QAAQ;AAAA,IAAA,CACnB;AAAA,EACH;AAEA,QAAM,SAAsB,CAAA;AAC5B,MAAI,aAAa;AACjB,MAAI,UAAU;AAEd,SAAO,aAAa,QAAQ,QAAQ;AAClC,UAAM,WAAW,KAAK,IAAI,aAAa,WAAW,QAAQ,MAAM;AAChE,UAAM,eAAe,QAAQ,MAAM,YAAY,QAAQ;AAEvD,WAAO,KAAK;AAAA,MACV,IAAI,QAAQ,SAAA;AAAA,MACZ,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IAAA,CACD;AAED;AACA,UAAM,YAAY,WAAW;AAE7B,QAAI,aAAa,YAAY;AAC3B,mBAAa,aAAa,KAAK,IAAI,GAAG,YAAY,OAAO;AAAA,IAC3D,OAAO;AACL,mBAAa;AAAA,IACf;AAEA,QAAI,cAAc,QAAQ,OAAQ;AAAA,EACpC;AAEA,SAAO;AACT;AAEA,eAAsB,cAAc,SAAiB,SAA2C;AAC9F,QAAM,QAAoB,CAAA;AAC1B,QAAM,8BAAc,IAAA;AAEpB,iBAAe,cAAc,aAAoC;AAC/D,UAAM,iBAAiB,cAAc,WAAW;AAEhD,QAAI,QAAQ,IAAI,cAAc,GAAG;AAC/B;AAAA,IACF;AACA,YAAQ,IAAI,cAAc;AAE1B,QAAI;AACF,UAAI,iBAAiB,gBAAgB,QAAQ,cAAc,GAAG;AAC5D;AAAA,MACF;AAEA,UAAI,MAAM,YAAY,cAAc,GAAG;AACrC,cAAM,UAAU,MAAMA,SAAG,QAAQ,cAAc;AAE/C,mBAAW,SAAS,SAAS;AAC3B,gBAAM,WAAW,KAAK,gBAAgB,KAAK;AAC3C,gBAAM,cAAc,QAAQ;AAAA,QAC9B;AAAA,MACF,WAAW,MAAM,OAAO,cAAc,GAAG;AACvC,YAAI,CAAC,oBAAoB,cAAc,GAAG;AACxC;AAAA,QACF;AAEA,cAAM,QAAQ,MAAMA,SAAG,KAAK,cAAc;AAC1C,YAAI,MAAM,OAAO,QAAQ,aAAa;AACpC;AAAA,QACF;AAEA,cAAM,WAAW,MAAM,YAAY,cAAc;AACjD,cAAM,KAAK,QAAQ;AAAA,MACrB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,IAAI,cAAc,6BAA6B,cAAc,IAAI,KAAc;AAAA,IACvF;AAAA,EACF;AAEA,QAAM,cAAc,OAAO;AAC3B,SAAO;AACT;AAEA,eAAsB,gBAAgB,UAAqC;AACzE,MAAI;AACF,WAAO,MAAM,YAAY,QAAQ;AAAA,EACnC,SAAS,OAAO;AACd,UAAM,IAAI,cAAc,+BAA+B,KAAc;AAAA,EACvE;AACF;AAEA,eAAe,oBAAoB,UAAkB,gBAA4B,QAAkC;AACjH,MAAI;AACF,UAAMA,MAAK,MAAM,OAAO,aAAa;AAGrC,UAAM,eAAe,MAAMA,IAAG,KAAK,QAAQ;AAC3C,UAAM,kBAAkB,IAAI,KAAK,eAAe,YAAY;AAG5D,QAAI,aAAa,SAAS,iBAAiB;AACzC,aAAO;AAAA,IACT;AAGA,UAAM,kBAAkB,MAAM,YAAY,QAAQ;AAClD,WAAO,gBAAgB,SAAS,eAAe;AAAA,EAEjD,SAAS,cAAc;AAErB,QAAI,OAAO,SAAS;AAClB,cAAQ,IAAI,kDAAkD,QAAQ,KAAK,YAAY;AAAA,IACzF;AACA,QAAI;AACF,YAAM,kBAAkB,MAAM,YAAY,QAAQ;AAClD,aAAO,gBAAgB,SAAS,eAAe;AAAA,IACjD,SAAS,WAAW;AAElB,UAAI,OAAO,SAAS;AAClB,gBAAQ,IAAI,uCAAuC,QAAQ,KAAK,SAAS;AAAA,MAC3E;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,eAAsB,iBAAiB,OAAiB,QAAsC;AAC5F,MAAI,UAAU;AACd,MAAI,UAAU;AACd,QAAM,SAAmB,CAAA;AAEzB,QAAM,cAA2B;AAAA,IAC/B,gBAAgB,OAAO,SAAS;AAAA,IAChC,aAAa,OAAO,SAAS;AAAA,EAAA;AAI/B,QAAM,EAAE,QAAQ,OAAA,IAAW,MAAM,kBAAkB,MAAM;AAEzD,aAAW,QAAQ,OAAO;AACxB,QAAI;AAEF,YAAM,iBAAiB,cAAc,IAAI;AACzC,YAAM,OAAO,gBAAgB,gBAAgB,UAAU;AAEvD,YAAM,QAAQ,MAAM,cAAc,MAAM,WAAW;AAEnD,iBAAW,QAAQ,OAAO;AACxB,YAAI;AAEF,gBAAM,eAAe,MAAM,OAAO,QAAQ,KAAK,IAAI;AAEnD,cAAI,cAAc;AAChB,kBAAM,oBAAoB,MAAM,oBAAoB,KAAK,MAAM,cAAc,MAAM;AACnF,gBAAI,CAAC,mBAAmB;AACtB;AACA;AAAA,YACF;AAGA,kBAAM,OAAO,uBAAuB,aAAa,IAAI;AAAA,UACvD;AAEA,gBAAM,UAAU,MAAMA,SAAG,SAAS,KAAK,MAAM,OAAO;AACpD,gBAAM,SAAS,UAAU,SAAS,OAAO,SAAS,WAAW,OAAO,SAAS,YAAY;AAGzF,gBAAM,OAAO,WAAW,MAAM,MAAM;AAGpC,mBAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,kBAAM,QAAQ,OAAO,CAAC;AACtB,kBAAM,YAAY,MAAM,kBAAkB,MAAM,SAAS,MAAM;AAE/D,kBAAM,UAAU,SAAS,KAAK,KAAK,MAAM,GAAG,CAAC,GAAG,EAAE;AAClD,kBAAM,UAAW,UAAU,MAAW,MAAO,SAAS,MAAM,EAAE;AAC9D,kBAAM,QAAQ;AAAA,cACZ,IAAI;AAAA,cACJ,QAAQ;AAAA,cACR,SAAS;AAAA,gBACP,UAAU,KAAK;AAAA,gBACf,SAAS,MAAM;AAAA,gBACf,UAAU,KAAK;AAAA,gBACf,SAAS,MAAM;AAAA,gBACf,mBAAmB,KAAK;AAAA,cAAA;AAAA,YAC1B;AAEF,kBAAM,OAAO,aAAa,CAAC,KAAK,CAAC;AAAA,UACnC;AAEA;AAAA,QACF,SAAS,OAAO;AACd,gBAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,gBAAM,eAAe,iBAAiB,SAAS,MAAM,QAAQ,KAAM,MAAM,MAAgB,OAAO,KAAK;AACrG,iBAAO,KAAK,qBAAqB,KAAK,IAAI,KAAK,YAAY,GAAG,YAAY,EAAE;AAAA,QAC9E;AAAA,MACF;AAGA,YAAM,kBAAkB,OAAO,OAAO,SAAO,IAAI,SAAS,IAAI,CAAC;AAC/D,YAAM,kBAAkB,gBAAgB,SAAS,IAAI,WAAW;AAChE,YAAM,OAAO,gBAAgB,gBAAgB,eAAe;AAAA,IAE9D,SAAS,OAAO;AACd,YAAM,iBAAiB,cAAc,IAAI;AACzC,YAAM,OAAO,gBAAgB,gBAAgB,QAAQ;AACrD,aAAO,KAAK,4BAA4B,IAAI,KAAM,MAAgB,OAAO,EAAE;AAAA,IAC7E;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,SAAS,OAAA;AAC7B;"}
|
|
1
|
+
{"version":3,"file":"indexing.js","sources":["../src/indexing.ts"],"sourcesContent":["import { promises as fs } from 'fs';\nimport { join } from 'path';\nimport { Config } from './config.js';\nimport { \n FileInfo, \n ChunkInfo, \n normalizePath, \n getFileInfo, \n shouldIgnoreFile, \n isSupportedFileType,\n isDirectory,\n isFile\n} from './utils.js';\nimport { generateEmbedding } from './embedding.js';\nimport { initializeStorage, FileRecord } from './storage.js';\n\nexport interface ScanOptions {\n ignorePatterns: string[];\n maxFileSize: number;\n}\n\nexport interface IndexResult {\n indexed: number;\n skipped: number;\n errors: string[];\n}\n\nexport class IndexingError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'IndexingError';\n }\n}\n\nexport function chunkText(content: string, chunkSize: number, overlap: number): ChunkInfo[] {\n if (content.length <= chunkSize) {\n return [{\n id: '0',\n content,\n startIndex: 0,\n endIndex: content.length\n }];\n }\n \n const chunks: ChunkInfo[] = [];\n let startIndex = 0;\n let chunkId = 0;\n \n while (startIndex < content.length) {\n const endIndex = Math.min(startIndex + chunkSize, content.length);\n const chunkContent = content.slice(startIndex, endIndex);\n \n chunks.push({\n id: chunkId.toString(),\n content: chunkContent,\n startIndex,\n endIndex\n });\n \n chunkId++;\n const nextStart = endIndex - overlap;\n \n if (nextStart <= startIndex) {\n startIndex = startIndex + Math.max(1, chunkSize - overlap);\n } else {\n startIndex = nextStart;\n }\n \n if (startIndex >= content.length) break;\n }\n \n return chunks;\n}\n\nexport async function scanDirectory(dirPath: string, options: ScanOptions): Promise<FileInfo[]> {\n const files: FileInfo[] = [];\n const visited = new Set<string>();\n \n async function walkDirectory(currentPath: string): Promise<void> {\n const normalizedPath = normalizePath(currentPath);\n \n if (visited.has(normalizedPath)) {\n return;\n }\n visited.add(normalizedPath);\n \n try {\n if (shouldIgnoreFile(normalizedPath, options.ignorePatterns)) {\n return;\n }\n \n if (await isDirectory(normalizedPath)) {\n const entries = await fs.readdir(normalizedPath);\n \n for (const entry of entries) {\n const fullPath = join(normalizedPath, entry);\n await walkDirectory(fullPath);\n }\n } else if (await isFile(normalizedPath)) {\n if (!isSupportedFileType(normalizedPath)) {\n return;\n }\n \n const stats = await fs.stat(normalizedPath);\n if (stats.size > options.maxFileSize) {\n return;\n }\n \n const fileInfo = await getFileInfo(normalizedPath);\n files.push(fileInfo);\n }\n } catch (error) {\n throw new IndexingError(`Failed to scan directory: ${normalizedPath}`, error as Error);\n }\n }\n \n await walkDirectory(dirPath);\n return files;\n}\n\nexport async function getFileMetadata(filePath: string): Promise<FileInfo> {\n try {\n return await getFileInfo(filePath);\n } catch (error) {\n throw new IndexingError(`Failed to get file metadata`, error as Error);\n }\n}\n\nasync function shouldReprocessFile(filePath: string, existingRecord: FileRecord, config: Config): Promise<boolean> {\n try {\n const fs = await import('fs/promises');\n \n // Try modtime check first (fast path)\n const currentStats = await fs.stat(filePath);\n const existingModTime = new Date(existingRecord.modifiedTime);\n \n // If modtime is clearly older, likely unchanged\n if (currentStats.mtime <= existingModTime) {\n return false; // Skip processing\n }\n \n // If modtime suggests change, verify with hash\n const currentFileInfo = await getFileInfo(filePath);\n return currentFileInfo.hash !== existingRecord.hash;\n \n } catch (modtimeError) {\n // Graceful fallback: skip modtime, use hash only\n if (config.verbose) {\n console.log(`Warning: Could not check modification time for ${filePath}:`, modtimeError);\n }\n try {\n const currentFileInfo = await getFileInfo(filePath);\n return currentFileInfo.hash !== existingRecord.hash;\n } catch (hashError) {\n // If we can't hash either, assume changed to be safe\n if (config.verbose) {\n console.log(`Warning: Could not compute hash for ${filePath}:`, hashError);\n }\n return true;\n }\n }\n}\n\nexport async function indexDirectories(paths: string[], config: Config): Promise<IndexResult> {\n let indexed = 0;\n let skipped = 0;\n const errors: string[] = [];\n \n const scanOptions: ScanOptions = {\n ignorePatterns: config.indexing.ignorePatterns,\n maxFileSize: config.indexing.maxFileSize\n };\n \n // Initialize storage\n const { sqlite, qdrant } = await initializeStorage(config);\n \n // First pass: scan all directories to get total file count\n let totalFiles = 0;\n for (const path of paths) {\n try {\n if (config.verbose) {\n console.log(`Scanning directory: ${path}`);\n }\n const files = await scanDirectory(path, scanOptions);\n totalFiles += files.length;\n if (config.verbose) {\n console.log(`Found ${files.length} files to process in ${path}`);\n }\n } catch {\n // Continue with other directories even if one fails to scan\n }\n }\n \n if (!config.verbose && totalFiles > 0) {\n console.log(`Processing ${totalFiles} files...`);\n }\n \n for (const path of paths) {\n try {\n // Mark directory as indexing\n const normalizedPath = normalizePath(path);\n await sqlite.upsertDirectory(normalizedPath, 'indexing');\n \n const files = await scanDirectory(path, scanOptions);\n \n for (const file of files) {\n try {\n // Check if file already exists and needs reprocessing\n const existingFile = await sqlite.getFile(file.path);\n \n if (existingFile) {\n const needsReprocessing = await shouldReprocessFile(file.path, existingFile, config);\n if (!needsReprocessing) {\n skipped++;\n continue; // Skip unchanged file\n }\n \n // File changed - clean up old vectors first\n await qdrant.deletePointsByFileHash(existingFile.hash);\n }\n \n const content = await fs.readFile(file.path, 'utf-8');\n const chunks = chunkText(content, config.indexing.chunkSize, config.indexing.chunkOverlap);\n \n // Store file metadata in SQLite\n await sqlite.upsertFile(file, chunks);\n \n // Generate embeddings and store in Qdrant\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i];\n const embedding = await generateEmbedding(chunk.content, config);\n // Generate a unique integer ID by combining hash and chunk index\n const hashNum = parseInt(file.hash.slice(0, 8), 16);\n const pointId = (hashNum % 1000000) * 1000 + parseInt(chunk.id);\n const point = {\n id: pointId,\n vector: embedding,\n payload: {\n filePath: file.path,\n chunkId: chunk.id,\n fileHash: file.hash,\n content: chunk.content,\n parentDirectories: file.parentDirs\n }\n };\n await qdrant.upsertPoints([point]);\n }\n \n indexed++;\n if (config.verbose) {\n console.log(` Indexed: ${file.path} (${chunks.length} chunks)`);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const causeMessage = error instanceof Error && error.cause ? `: ${(error.cause as Error).message}` : '';\n errors.push(`Failed to process ${file.path}: ${errorMessage}${causeMessage}`);\n }\n }\n \n // Mark directory as completed if no errors for this directory\n const directoryErrors = errors.filter(err => err.includes(path));\n const directoryStatus = directoryErrors.length > 0 ? 'failed' : 'completed';\n await sqlite.upsertDirectory(normalizedPath, directoryStatus);\n \n } catch (error) {\n const normalizedPath = normalizePath(path);\n await sqlite.upsertDirectory(normalizedPath, 'failed');\n errors.push(`Failed to scan directory ${path}: ${(error as Error).message}`);\n }\n }\n \n return { indexed, skipped, errors };\n}"],"names":["fs"],"mappings":"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"}
|
package/dist/search.js
CHANGED
|
@@ -15,14 +15,38 @@ async function searchContent(query, options = {}) {
|
|
|
15
15
|
const config = (await import("./config.js")).loadConfig();
|
|
16
16
|
const { qdrant } = await initializeStorage(config);
|
|
17
17
|
const queryEmbedding = await generateEmbedding(query, config);
|
|
18
|
-
const points = await qdrant.searchPoints(queryEmbedding, limit);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
const points = await qdrant.searchPoints(queryEmbedding, limit * 5);
|
|
19
|
+
const fileGroups = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const point of points) {
|
|
21
|
+
const score = point.score ?? 0;
|
|
22
|
+
if (score < threshold) continue;
|
|
23
|
+
const filePath = point.payload.filePath;
|
|
24
|
+
if (!fileGroups.has(filePath)) {
|
|
25
|
+
fileGroups.set(filePath, []);
|
|
26
|
+
}
|
|
27
|
+
fileGroups.get(filePath).push({
|
|
28
|
+
score,
|
|
29
|
+
chunkId: point.payload.chunkId,
|
|
30
|
+
parentDirectories: point.payload.parentDirectories
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const results = [];
|
|
34
|
+
for (const [filePath, chunks] of fileGroups.entries()) {
|
|
35
|
+
const avgScore = chunks.reduce((sum, chunk) => sum + chunk.score, 0) / chunks.length;
|
|
36
|
+
const sortedChunks = chunks.sort((a, b) => b.score - a.score);
|
|
37
|
+
const chunkMatches = sortedChunks.map((chunk) => ({
|
|
38
|
+
chunkId: chunk.chunkId,
|
|
39
|
+
score: chunk.score
|
|
40
|
+
}));
|
|
41
|
+
results.push({
|
|
42
|
+
filePath,
|
|
43
|
+
score: avgScore,
|
|
44
|
+
matchingChunks: chunks.length,
|
|
45
|
+
parentDirectories: sortedChunks[0].parentDirectories,
|
|
46
|
+
chunks: chunkMatches
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
26
50
|
} catch (error) {
|
|
27
51
|
throw new SearchError(`Failed to search content`, error);
|
|
28
52
|
}
|
|
@@ -56,6 +80,26 @@ async function findSimilarFiles(filePath, limit = 5) {
|
|
|
56
80
|
throw new SearchError(`Failed to find similar files`, error);
|
|
57
81
|
}
|
|
58
82
|
}
|
|
83
|
+
async function getChunkContent(filePath, chunkId) {
|
|
84
|
+
try {
|
|
85
|
+
if (!await fileExists(filePath)) {
|
|
86
|
+
throw new Error(`File not found: ${filePath}`);
|
|
87
|
+
}
|
|
88
|
+
const config = (await import("./config.js")).loadConfig();
|
|
89
|
+
const { sqlite } = await initializeStorage(config);
|
|
90
|
+
const fileRecord = await sqlite.getFile(filePath);
|
|
91
|
+
if (!fileRecord || fileRecord.chunks.length === 0) {
|
|
92
|
+
throw new Error(`File not indexed: ${filePath}`);
|
|
93
|
+
}
|
|
94
|
+
const chunk = fileRecord.chunks.find((c) => c.id === chunkId);
|
|
95
|
+
if (!chunk) {
|
|
96
|
+
throw new Error(`Chunk ${chunkId} not found in file: ${filePath}`);
|
|
97
|
+
}
|
|
98
|
+
return chunk.content;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new SearchError(`Failed to get chunk content`, error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
59
103
|
async function getFileContent(filePath, chunks) {
|
|
60
104
|
try {
|
|
61
105
|
if (!await fileExists(filePath)) {
|
|
@@ -91,6 +135,7 @@ function parseChunkRange(chunks) {
|
|
|
91
135
|
export {
|
|
92
136
|
SearchError,
|
|
93
137
|
findSimilarFiles,
|
|
138
|
+
getChunkContent,
|
|
94
139
|
getFileContent,
|
|
95
140
|
searchContent
|
|
96
141
|
};
|
package/dist/search.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.js","sources":["../src/search.ts"],"sourcesContent":["import { promises as fs } from 'fs';\nimport { generateEmbedding } from './embedding.js';\nimport { initializeStorage } from './storage.js';\nimport { fileExists } from './utils.js';\n\nexport interface SearchOptions {\n limit?: number;\n threshold?: number;\n directoryPath?: string;\n}\n\nexport interface SearchResult {\n filePath: string;\n chunkId: string;\n content: string;\n score: number;\n parentDirectories: string[];\n}\n\nexport interface SimilarFile {\n filePath: string;\n score: number;\n parentDirectories: string[];\n}\n\nexport class SearchError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'SearchError';\n }\n}\n\nexport async function searchContent(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n const { limit = 10, threshold = 0.0 } = options;\n \n try {\n const config = (await import('./config.js')).loadConfig();\n const { qdrant } = await initializeStorage(config);\n \n const queryEmbedding = await generateEmbedding(query, config);\n const points = await qdrant.searchPoints(queryEmbedding, limit);\n \n return points\n .filter(point => (point.score ?? 0) >= threshold)\n .map(point => ({\n filePath: point.payload.filePath,\n chunkId: point.payload.chunkId,\n content: '',\n score: point.score ?? 0,\n parentDirectories: point.payload.parentDirectories\n }));\n } catch (error) {\n throw new SearchError(`Failed to search content`, error as Error);\n }\n}\n\nexport async function findSimilarFiles(filePath: string, limit: number = 5): Promise<SimilarFile[]> {\n try {\n if (!await fileExists(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n \n const config = (await import('./config.js')).loadConfig();\n const { sqlite, qdrant } = await initializeStorage(config);\n \n const fileRecord = await sqlite.getFile(filePath);\n if (!fileRecord || fileRecord.chunks.length === 0) {\n const content = await fs.readFile(filePath, 'utf-8');\n const embedding = await generateEmbedding(content, config);\n const points = await qdrant.searchPoints(embedding, limit + 1);\n \n return points\n .filter(point => point.payload.filePath !== filePath)\n .slice(0, limit)\n .map(point => ({\n filePath: point.payload.filePath,\n score: point.score ?? 0,\n parentDirectories: point.payload.parentDirectories\n }));\n }\n \n const firstChunkEmbedding = await generateEmbedding(fileRecord.chunks[0].content, config);\n const points = await qdrant.searchPoints(firstChunkEmbedding, limit + 1);\n \n return points\n .filter(point => point.payload.filePath !== filePath)\n .slice(0, limit)\n .map(point => ({\n filePath: point.payload.filePath,\n score: point.score ?? 0,\n parentDirectories: point.payload.parentDirectories\n }));\n } catch (error) {\n throw new SearchError(`Failed to find similar files`, error as Error);\n }\n}\n\nexport async function getFileContent(filePath: string, chunks?: string): Promise<string> {\n try {\n if (!await fileExists(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n \n const config = (await import('./config.js')).loadConfig();\n const { sqlite } = await initializeStorage(config);\n \n const fileRecord = await sqlite.getFile(filePath);\n \n if (!chunks) {\n return await fs.readFile(filePath, 'utf-8');\n }\n \n if (!fileRecord || fileRecord.chunks.length === 0) {\n return await fs.readFile(filePath, 'utf-8');\n }\n \n const chunkRange = parseChunkRange(chunks);\n const selectedChunks = fileRecord.chunks.filter(chunk => {\n const chunkNum = parseInt(chunk.id);\n return chunkNum >= chunkRange.start && chunkNum <= chunkRange.end;\n });\n \n return selectedChunks.map(chunk => chunk.content).join('');\n } catch (error) {\n throw new SearchError(`Failed to get file content`, error as Error);\n }\n}\n\nfunction parseChunkRange(chunks: string): { start: number; end: number } {\n if (chunks.includes('-')) {\n const [start, end] = chunks.split('-').map(num => parseInt(num.trim()));\n return { start: start || 0, end: end || start || 0 };\n }\n \n const num = parseInt(chunks);\n return { start: num, end: num };\n}"],"names":["fs","points","num"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"search.js","sources":["../src/search.ts"],"sourcesContent":["import { promises as fs } from 'fs';\nimport { generateEmbedding } from './embedding.js';\nimport { initializeStorage } from './storage.js';\nimport { fileExists } from './utils.js';\n\nexport interface SearchOptions {\n limit?: number;\n threshold?: number;\n directoryPath?: string;\n}\n\nexport interface ChunkMatch {\n chunkId: string;\n score: number;\n}\n\nexport interface SearchResult {\n filePath: string;\n score: number;\n matchingChunks: number;\n parentDirectories: string[];\n chunks: ChunkMatch[];\n}\n\nexport interface SimilarFile {\n filePath: string;\n score: number;\n parentDirectories: string[];\n}\n\nexport class SearchError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'SearchError';\n }\n}\n\nexport async function searchContent(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n const { limit = 10, threshold = 0.0 } = options;\n \n try {\n const config = (await import('./config.js')).loadConfig();\n const { qdrant } = await initializeStorage(config);\n \n const queryEmbedding = await generateEmbedding(query, config);\n // Get more points initially since we'll group by file\n const points = await qdrant.searchPoints(queryEmbedding, limit * 5);\n \n // Group points by file path\n const fileGroups = new Map<string, Array<{ score: number; chunkId: string; parentDirectories: string[] }>>();\n \n for (const point of points) {\n const score = point.score ?? 0;\n if (score < threshold) continue;\n \n const filePath = point.payload.filePath;\n if (!fileGroups.has(filePath)) {\n fileGroups.set(filePath, []);\n }\n \n fileGroups.get(filePath)!.push({\n score,\n chunkId: point.payload.chunkId,\n parentDirectories: point.payload.parentDirectories\n });\n }\n \n // Calculate average score per file and sort\n const results: SearchResult[] = [];\n for (const [filePath, chunks] of fileGroups.entries()) {\n const avgScore = chunks.reduce((sum, chunk) => sum + chunk.score, 0) / chunks.length;\n \n // Sort chunks by score (best first) and create chunk matches\n const sortedChunks = chunks.sort((a, b) => b.score - a.score);\n const chunkMatches: ChunkMatch[] = sortedChunks.map(chunk => ({\n chunkId: chunk.chunkId,\n score: chunk.score\n }));\n \n results.push({\n filePath,\n score: avgScore,\n matchingChunks: chunks.length,\n parentDirectories: sortedChunks[0].parentDirectories,\n chunks: chunkMatches\n });\n }\n \n // Sort by average score and return top results\n return results\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n } catch (error) {\n throw new SearchError(`Failed to search content`, error as Error);\n }\n}\n\nexport async function findSimilarFiles(filePath: string, limit: number = 5): Promise<SimilarFile[]> {\n try {\n if (!await fileExists(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n \n const config = (await import('./config.js')).loadConfig();\n const { sqlite, qdrant } = await initializeStorage(config);\n \n const fileRecord = await sqlite.getFile(filePath);\n if (!fileRecord || fileRecord.chunks.length === 0) {\n const content = await fs.readFile(filePath, 'utf-8');\n const embedding = await generateEmbedding(content, config);\n const points = await qdrant.searchPoints(embedding, limit + 1);\n \n return points\n .filter(point => point.payload.filePath !== filePath)\n .slice(0, limit)\n .map(point => ({\n filePath: point.payload.filePath,\n score: point.score ?? 0,\n parentDirectories: point.payload.parentDirectories\n }));\n }\n \n const firstChunkEmbedding = await generateEmbedding(fileRecord.chunks[0].content, config);\n const points = await qdrant.searchPoints(firstChunkEmbedding, limit + 1);\n \n return points\n .filter(point => point.payload.filePath !== filePath)\n .slice(0, limit)\n .map(point => ({\n filePath: point.payload.filePath,\n score: point.score ?? 0,\n parentDirectories: point.payload.parentDirectories\n }));\n } catch (error) {\n throw new SearchError(`Failed to find similar files`, error as Error);\n }\n}\n\nexport async function getChunkContent(filePath: string, chunkId: string): Promise<string> {\n try {\n if (!await fileExists(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n \n const config = (await import('./config.js')).loadConfig();\n const { sqlite } = await initializeStorage(config);\n \n const fileRecord = await sqlite.getFile(filePath);\n if (!fileRecord || fileRecord.chunks.length === 0) {\n throw new Error(`File not indexed: ${filePath}`);\n }\n \n const chunk = fileRecord.chunks.find(c => c.id === chunkId);\n if (!chunk) {\n throw new Error(`Chunk ${chunkId} not found in file: ${filePath}`);\n }\n \n return chunk.content;\n } catch (error) {\n throw new SearchError(`Failed to get chunk content`, error as Error);\n }\n}\n\nexport async function getFileContent(filePath: string, chunks?: string): Promise<string> {\n try {\n if (!await fileExists(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n \n const config = (await import('./config.js')).loadConfig();\n const { sqlite } = await initializeStorage(config);\n \n const fileRecord = await sqlite.getFile(filePath);\n \n if (!chunks) {\n return await fs.readFile(filePath, 'utf-8');\n }\n \n if (!fileRecord || fileRecord.chunks.length === 0) {\n return await fs.readFile(filePath, 'utf-8');\n }\n \n const chunkRange = parseChunkRange(chunks);\n const selectedChunks = fileRecord.chunks.filter(chunk => {\n const chunkNum = parseInt(chunk.id);\n return chunkNum >= chunkRange.start && chunkNum <= chunkRange.end;\n });\n \n return selectedChunks.map(chunk => chunk.content).join('');\n } catch (error) {\n throw new SearchError(`Failed to get file content`, error as Error);\n }\n}\n\nfunction parseChunkRange(chunks: string): { start: number; end: number } {\n if (chunks.includes('-')) {\n const [start, end] = chunks.split('-').map(num => parseInt(num.trim()));\n return { start: start || 0, end: end || start || 0 };\n }\n \n const num = parseInt(chunks);\n return { start: num, end: num };\n}"],"names":["fs","points","num"],"mappings":"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"}
|