directory-indexer 0.1.1 → 0.1.2
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/README.md +27 -0
- package/dist/cli.js +150 -104
- package/dist/cli.js.map +1 -1
- package/dist/config.js +59 -2
- package/dist/config.js.map +1 -1
- package/dist/search.js +59 -18
- package/dist/search.js.map +1 -1
- package/dist/storage.js +34 -0
- package/dist/storage.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -197,6 +197,33 @@ Configure with custom endpoints and data directory:
|
|
|
197
197
|
}
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
+
### Workspace Support
|
|
201
|
+
|
|
202
|
+
Organize content into workspaces for focused searches:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"mcpServers": {
|
|
207
|
+
"directory-indexer": {
|
|
208
|
+
"command": "npx",
|
|
209
|
+
"args": ["directory-indexer@latest", "serve"],
|
|
210
|
+
"env": {
|
|
211
|
+
"WORKSPACE_DOCS": "/home/user/docs,/home/user/wiki",
|
|
212
|
+
"WORKSPACE_PROJECTS": "/home/user/code/api,/home/user/code/web",
|
|
213
|
+
"WORKSPACE_PERSONAL": "/home/user/notes,/home/user/journal"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**How workspaces work:**
|
|
221
|
+
- Define workspace environments with `WORKSPACE_NAME` format
|
|
222
|
+
- Use comma-separated paths or JSON arrays: `["path1", "path2"]`
|
|
223
|
+
- Search within specific workspaces: _"Find API docs in projects workspace"_
|
|
224
|
+
- Your AI assistant can filter results to relevant workspace content
|
|
225
|
+
- Use `server_info` to see available workspaces and their statistics
|
|
226
|
+
|
|
200
227
|
### CLI Usage
|
|
201
228
|
|
|
202
229
|
For advanced users who prefer command-line usage, see [CLI Documentation](./docs/design.md#cli-usage).
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,127 @@ import { getIndexStatus } from "./storage.js";
|
|
|
10
10
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
12
|
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
function isIndexToolArgs(args) {
|
|
14
|
+
return typeof args === "object" && args !== null && typeof args.directory_path === "string";
|
|
15
|
+
}
|
|
16
|
+
function isSearchToolArgs(args) {
|
|
17
|
+
return typeof args === "object" && args !== null && typeof args.query === "string";
|
|
18
|
+
}
|
|
19
|
+
function isSimilarFilesToolArgs(args) {
|
|
20
|
+
return typeof args === "object" && args !== null && typeof args.file_path === "string";
|
|
21
|
+
}
|
|
22
|
+
function isGetContentToolArgs(args) {
|
|
23
|
+
return typeof args === "object" && args !== null && typeof args.file_path === "string";
|
|
24
|
+
}
|
|
25
|
+
function isGetChunkToolArgs(args) {
|
|
26
|
+
return typeof args === "object" && args !== null && typeof args.file_path === "string" && typeof args.chunk_id === "string";
|
|
27
|
+
}
|
|
28
|
+
async function handleIndexTool(args, config) {
|
|
29
|
+
if (!isIndexToolArgs(args)) {
|
|
30
|
+
throw new Error("directory_path is required");
|
|
31
|
+
}
|
|
32
|
+
const paths = args.directory_path.split(",").map((p) => p.trim());
|
|
33
|
+
const result = await indexDirectories(paths, config);
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: `Indexed ${result.indexed} files, skipped ${result.skipped} files, ${result.errors.length} errors`
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function handleSearchTool(args) {
|
|
44
|
+
if (!isSearchToolArgs(args)) {
|
|
45
|
+
throw new Error("query is required");
|
|
46
|
+
}
|
|
47
|
+
const options = {
|
|
48
|
+
limit: args.limit || 10,
|
|
49
|
+
workspace: args.workspace
|
|
50
|
+
};
|
|
51
|
+
const results = await searchContent(args.query, options);
|
|
52
|
+
return {
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: JSON.stringify(results, null, 2)
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function handleSimilarFilesTool(args) {
|
|
62
|
+
if (!isSimilarFilesToolArgs(args)) {
|
|
63
|
+
throw new Error("file_path is required");
|
|
64
|
+
}
|
|
65
|
+
const results = await findSimilarFiles(
|
|
66
|
+
args.file_path,
|
|
67
|
+
args.limit || 10,
|
|
68
|
+
args.workspace
|
|
69
|
+
);
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: JSON.stringify(results, null, 2)
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function handleGetContentTool(args) {
|
|
80
|
+
if (!isGetContentToolArgs(args)) {
|
|
81
|
+
throw new Error("file_path is required");
|
|
82
|
+
}
|
|
83
|
+
const content = await getFileContent(args.file_path, args.chunks);
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: content
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function handleGetChunkTool(args) {
|
|
94
|
+
if (!isGetChunkToolArgs(args)) {
|
|
95
|
+
throw new Error("file_path and chunk_id are required");
|
|
96
|
+
}
|
|
97
|
+
const content = await getChunkContent(args.file_path, args.chunk_id);
|
|
98
|
+
return {
|
|
99
|
+
content: [
|
|
100
|
+
{
|
|
101
|
+
type: "text",
|
|
102
|
+
text: content
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async function handleServerInfoTool(version) {
|
|
108
|
+
const status = await getIndexStatus();
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: JSON.stringify({
|
|
114
|
+
name: "directory-indexer",
|
|
115
|
+
version,
|
|
116
|
+
status
|
|
117
|
+
}, null, 2)
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function formatErrorResponse(error) {
|
|
123
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
124
|
+
return {
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: "text",
|
|
128
|
+
text: `Error: ${errorMessage}`
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
isError: true
|
|
132
|
+
};
|
|
133
|
+
}
|
|
13
134
|
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
14
135
|
const packageJsonPath$1 = join(__dirname$1, "../package.json");
|
|
15
136
|
const packageJson$1 = JSON.parse(readFileSync(packageJsonPath$1, "utf-8"));
|
|
@@ -64,11 +185,11 @@ How it works:
|
|
|
64
185
|
- Returns files ranked by relevance score
|
|
65
186
|
|
|
66
187
|
Examples:
|
|
67
|
-
- "database configuration" - finds config files, documentation about DB setup
|
|
68
|
-
- "error handling patterns" - finds code files with exception handling
|
|
69
|
-
- "authentication implementation" - finds auth-related code and docs
|
|
70
|
-
- "API documentation" - finds API guides, endpoint definitions
|
|
71
|
-
- "deployment scripts
|
|
188
|
+
- "database configuration and connection pooling setup" - finds config files, documentation about DB setup
|
|
189
|
+
- "comprehensive error handling patterns and exception management" - finds code files with exception handling
|
|
190
|
+
- "JWT authentication implementation and session management" - finds auth-related code and docs
|
|
191
|
+
- "REST API documentation and endpoint specifications" - finds API guides, endpoint definitions
|
|
192
|
+
- "Docker deployment scripts and CI/CD pipeline configuration" - finds deployment automation
|
|
72
193
|
|
|
73
194
|
Returns files with similarity scores and chunk information. Use get_content to retrieve full file content or get_chunk to retrieve specific chunk content by chunk ID.
|
|
74
195
|
- Groups results by file to avoid duplicates from multiple matching sections
|
|
@@ -80,9 +201,9 @@ Response format:
|
|
|
80
201
|
- Average similarity score calculated across all matching chunks per file
|
|
81
202
|
|
|
82
203
|
Example queries:
|
|
83
|
-
- "error handling patterns" (finds try/catch, error classes, logging)
|
|
84
|
-
- "database migration scripts" (finds SQL, schema changes, migration files)
|
|
85
|
-
- "authentication middleware" (finds auth logic, JWT handling, middleware functions)`,
|
|
204
|
+
- "error handling patterns and exception management strategies" (finds try/catch, error classes, logging)
|
|
205
|
+
- "database migration scripts and schema versioning approaches" (finds SQL, schema changes, migration files)
|
|
206
|
+
- "authentication middleware and JWT token validation logic" (finds auth logic, JWT handling, middleware functions)`,
|
|
86
207
|
inputSchema: {
|
|
87
208
|
type: "object",
|
|
88
209
|
properties: {
|
|
@@ -94,6 +215,10 @@ Example queries:
|
|
|
94
215
|
type: "number",
|
|
95
216
|
description: "Maximum number of files to return (default: 10). Each file may contain multiple matching chunks.",
|
|
96
217
|
default: 10
|
|
218
|
+
},
|
|
219
|
+
workspace: {
|
|
220
|
+
type: "string",
|
|
221
|
+
description: "Optional workspace name to filter search results. Only files within the workspace directories will be searched. Use server_info to see available workspaces."
|
|
97
222
|
}
|
|
98
223
|
},
|
|
99
224
|
required: ["query"]
|
|
@@ -132,6 +257,10 @@ Returns file paths with similarity scores. Use get_content to read full files or
|
|
|
132
257
|
type: "number",
|
|
133
258
|
description: "Maximum number of similar files to return (default: 10). Results are sorted by similarity score.",
|
|
134
259
|
default: 10
|
|
260
|
+
},
|
|
261
|
+
workspace: {
|
|
262
|
+
type: "string",
|
|
263
|
+
description: "Optional workspace name to filter results. Only files within the workspace directories will be considered. Use server_info to see available workspaces."
|
|
135
264
|
}
|
|
136
265
|
},
|
|
137
266
|
required: ["file_path"]
|
|
@@ -261,106 +390,23 @@ async function startMcpServer(config) {
|
|
|
261
390
|
const { name, arguments: args } = request.params;
|
|
262
391
|
try {
|
|
263
392
|
switch (name) {
|
|
264
|
-
case "index":
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
]
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
case "search": {
|
|
280
|
-
if (!args || typeof args.query !== "string") {
|
|
281
|
-
throw new Error("query is required");
|
|
282
|
-
}
|
|
283
|
-
const results = await searchContent(args.query, { limit: args.limit || 10 });
|
|
284
|
-
return {
|
|
285
|
-
content: [
|
|
286
|
-
{
|
|
287
|
-
type: "text",
|
|
288
|
-
text: JSON.stringify(results, null, 2)
|
|
289
|
-
}
|
|
290
|
-
]
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
case "similar_files": {
|
|
294
|
-
if (!args || typeof args.file_path !== "string") {
|
|
295
|
-
throw new Error("file_path is required");
|
|
296
|
-
}
|
|
297
|
-
const results = await findSimilarFiles(args.file_path, args.limit || 10);
|
|
298
|
-
return {
|
|
299
|
-
content: [
|
|
300
|
-
{
|
|
301
|
-
type: "text",
|
|
302
|
-
text: JSON.stringify(results, null, 2)
|
|
303
|
-
}
|
|
304
|
-
]
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
case "get_content": {
|
|
308
|
-
if (!args || typeof args.file_path !== "string") {
|
|
309
|
-
throw new Error("file_path is required");
|
|
310
|
-
}
|
|
311
|
-
const content = await getFileContent(args.file_path, args.chunks);
|
|
312
|
-
return {
|
|
313
|
-
content: [
|
|
314
|
-
{
|
|
315
|
-
type: "text",
|
|
316
|
-
text: content
|
|
317
|
-
}
|
|
318
|
-
]
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
case "get_chunk": {
|
|
322
|
-
if (!args || typeof args.file_path !== "string" || typeof args.chunk_id !== "string") {
|
|
323
|
-
throw new Error("file_path and chunk_id are required");
|
|
324
|
-
}
|
|
325
|
-
const content = await getChunkContent(args.file_path, args.chunk_id);
|
|
326
|
-
return {
|
|
327
|
-
content: [
|
|
328
|
-
{
|
|
329
|
-
type: "text",
|
|
330
|
-
text: content
|
|
331
|
-
}
|
|
332
|
-
]
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
case "server_info": {
|
|
336
|
-
const status = await getIndexStatus();
|
|
337
|
-
return {
|
|
338
|
-
content: [
|
|
339
|
-
{
|
|
340
|
-
type: "text",
|
|
341
|
-
text: JSON.stringify({
|
|
342
|
-
name: "directory-indexer",
|
|
343
|
-
version: VERSION$1,
|
|
344
|
-
status
|
|
345
|
-
}, null, 2)
|
|
346
|
-
}
|
|
347
|
-
]
|
|
348
|
-
};
|
|
349
|
-
}
|
|
393
|
+
case "index":
|
|
394
|
+
return await handleIndexTool(args, config);
|
|
395
|
+
case "search":
|
|
396
|
+
return await handleSearchTool(args);
|
|
397
|
+
case "similar_files":
|
|
398
|
+
return await handleSimilarFilesTool(args);
|
|
399
|
+
case "get_content":
|
|
400
|
+
return await handleGetContentTool(args);
|
|
401
|
+
case "get_chunk":
|
|
402
|
+
return await handleGetChunkTool(args);
|
|
403
|
+
case "server_info":
|
|
404
|
+
return await handleServerInfoTool(VERSION$1);
|
|
350
405
|
default:
|
|
351
406
|
throw new Error(`Unknown tool: ${name}`);
|
|
352
407
|
}
|
|
353
408
|
} catch (error) {
|
|
354
|
-
|
|
355
|
-
return {
|
|
356
|
-
content: [
|
|
357
|
-
{
|
|
358
|
-
type: "text",
|
|
359
|
-
text: `Error: ${errorMessage}`
|
|
360
|
-
}
|
|
361
|
-
],
|
|
362
|
-
isError: true
|
|
363
|
-
};
|
|
409
|
+
return formatErrorResponse(error);
|
|
364
410
|
}
|
|
365
411
|
});
|
|
366
412
|
const transport = new StdioServerTransport();
|
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, 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 to make their files searchable. Processes files to create vector embeddings for semantic search.\n\nWhen to use this tool:\n- User specifically requests indexing a directory as a knowledge base\n- Adding new documentation, code repositories, or file collections to search\n- Updating index when many files have changed\n\nHow it works:\n- Recursively scans directories for supported file types\n- Extracts text content and splits into chunks\n- Generates vector embeddings for semantic similarity\n- Stores in database for fast retrieval\n\nExamples:\n- Index documentation: \"/home/user/docs/project-wiki\"\n- Index codebase: \"/home/user/projects/api-server\"\n- Index multiple directories: \"/home/user/docs,/home/user/configs\"\n\nIndexing can take several minutes for large directories. Most users will already have directories indexed and can directly use search tool. Use server_info to check current indexing status first.`,\n inputSchema: {\n type: 'object',\n properties: {\n directory_path: {\n type: 'string',\n description: 'Comma-separated list of absolute directory paths to index. Must be absolute paths since MCP server runs independently. Examples: \"/home/user/projects\" (Unix) or \"C:\\\\Users\\\\user\\\\projects\" (Windows)'\n }\n },\n required: ['directory_path']\n }\n },\n {\n name: 'search',\n description: `Search indexed files using natural language queries. Finds files containing content semantically similar to the query.\n\nWhen to use this tool:\n- Find documentation, guides, or explanations about specific topics\n- Locate code files implementing certain functionality or patterns\n- Discover configuration files, scripts, or settings related to a topic\n- Search for files covering specific concepts or technologies\n\nHow it works:\n- Converts query to vector embedding using semantic similarity\n- Searches all indexed file chunks for relevant content\n- Groups results by file and calculates average relevance scores\n- Returns files ranked by relevance score\n\nExamples:\n- \"database configuration\" - finds config files, documentation about DB setup\n- \"error handling patterns\" - finds code files with exception handling\n- \"authentication implementation\" - finds auth-related code and docs\n- \"API documentation\" - finds API guides, endpoint definitions\n- \"deployment scripts\" - finds CI/CD configs, deployment automation\n\nReturns files with similarity scores and chunk information. Use get_content to retrieve full file content or get_chunk to retrieve specific chunk content by chunk ID.\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 with content similar to a reference file. Uses semantic similarity to find related documents, code files, or any text content.\n\nWhen to use this tool:\n- Find documentation similar to a specific guide or README\n- Locate related code files, configuration files, or scripts\n- Discover alternative implementations or approaches\n- Find files covering similar topics or concepts\n\nHow it works:\n- Analyzes the semantic content of the reference file\n- Compares against all indexed files using vector similarity\n- Returns files ranked by content similarity score\n\nExamples:\n- Given \"deployment-guide.md\" - finds other deployment docs, CI/CD guides, infrastructure setup\n- Given \"troubleshooting.md\" - finds other troubleshooting guides, FAQ files, error documentation\n- Given \"config.yaml\" - finds other configuration files, settings, environment setups\n- Given \"auth.py\" - finds other authentication modules, security code, middleware\n\nReturns file paths with similarity scores. Use get_content to read full files or get_chunk for specific sections.`,\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. Reads files directly from the filesystem.\n\nWhen to use this tool:\n- Get complete file content after finding files through search\n- Read documentation, code files, or configuration files for analysis\n- Extract specific sections of large files using chunk ranges\n- Access any text-based file content\n\nHow it works:\n- Reads files directly from filesystem (not from search index)\n- Returns entire file by default\n- Can return specific chunk ranges for indexed files\n- Preserves original formatting and content\n\nExamples:\n- Get full file: file_path=\"/home/user/docs/api.md\"\n- Get specific chunks: file_path=\"/home/user/code/main.py\", chunks=\"2-5\"\n- Get single chunk: file_path=\"/home/user/config.json\", chunks=\"1\"\n\nReturns file content as text. Use this after search or similar_files to read actual content.`,\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 content of a specific chunk from an indexed file. Gets exact text segments identified during search.\n\nWhen to use this tool:\n- Get specific relevant sections after performing a search\n- Access only the most pertinent parts of large files\n- Retrieve content from high-scoring chunks identified in search results\n- Avoid reading entire files when only specific sections are needed\n\nHow it works:\n- Files are split into overlapping text chunks during indexing\n- Each chunk has a sequential ID (\"0\", \"1\", \"2\", etc.)\n- Search results include chunk IDs for relevant sections\n- Returns the exact content that was semantically matched\n\nExamples:\n- After search returns chunk \"3\" from \"api-docs.md\" with high score\n- Get chunk content: file_path=\"/docs/api-docs.md\", chunk_id=\"3\"\n- Returns the specific text segment that matched your query\n\nReturns chunk content as text. Use this with chunk IDs from search results to get precise content sections.`,\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 information about server status and indexed content. Shows what directories and files are available for search.\n\nWhen to use this tool:\n- Check what content is already indexed before performing searches\n- Verify system is working properly\n- See indexing statistics and status\n- Understand scope of available searchable content\n\nHow it works:\n- Reports total indexed directories, files, and chunks\n- Shows database size and last indexing time\n- Lists all indexed directories with file counts\n- Reports any errors or issues\n\nExamples:\n- Check before searching: \"What content is indexed?\"\n- Verify after indexing: \"Did the indexing complete successfully?\"\n- Monitor system: \"How many files are searchable?\"\n\nReturns server version, indexing statistics, directory list, and any errors. Use this to understand what content is available for search and similar_files tools.`,\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":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","sources":["../src/mcp-handlers.ts","../src/mcp.ts","../src/cli.ts"],"sourcesContent":["import { Config } from './config.js';\nimport { indexDirectories } from './indexing.js';\nimport { searchContent, findSimilarFiles, getFileContent, getChunkContent } from './search.js';\nimport { getIndexStatus } from './storage.js';\nimport { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// Type-safe interfaces for MCP tool arguments\ninterface IndexToolArgs {\n directory_path: string;\n}\n\ninterface SearchToolArgs {\n query: string;\n limit?: number;\n workspace?: string;\n}\n\ninterface SimilarFilesToolArgs {\n file_path: string;\n limit?: number;\n workspace?: string;\n}\n\ninterface GetContentToolArgs {\n file_path: string;\n chunks?: string;\n}\n\ninterface GetChunkToolArgs {\n file_path: string;\n chunk_id: string;\n}\n\n// Type guard functions\nfunction isIndexToolArgs(args: unknown): args is IndexToolArgs {\n return typeof args === 'object' && args !== null && \n typeof (args as IndexToolArgs).directory_path === 'string';\n}\n\nfunction isSearchToolArgs(args: unknown): args is SearchToolArgs {\n return typeof args === 'object' && args !== null && \n typeof (args as SearchToolArgs).query === 'string';\n}\n\nfunction isSimilarFilesToolArgs(args: unknown): args is SimilarFilesToolArgs {\n return typeof args === 'object' && args !== null && \n typeof (args as SimilarFilesToolArgs).file_path === 'string';\n}\n\nfunction isGetContentToolArgs(args: unknown): args is GetContentToolArgs {\n return typeof args === 'object' && args !== null && \n typeof (args as GetContentToolArgs).file_path === 'string';\n}\n\nfunction isGetChunkToolArgs(args: unknown): args is GetChunkToolArgs {\n return typeof args === 'object' && args !== null && \n typeof (args as GetChunkToolArgs).file_path === 'string' &&\n typeof (args as GetChunkToolArgs).chunk_id === 'string';\n}\n\nexport async function handleIndexTool(args: unknown, config: Config): Promise<CallToolResult> {\n if (!isIndexToolArgs(args)) {\n throw new Error('directory_path is required');\n }\n \n const paths = args.directory_path.split(',').map((p: string) => p.trim());\n const result = await indexDirectories(paths, config);\n \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\nexport async function handleSearchTool(args: unknown): Promise<CallToolResult> {\n if (!isSearchToolArgs(args)) {\n throw new Error('query is required');\n }\n \n const options = { \n limit: args.limit || 10,\n workspace: args.workspace\n };\n \n const results = await searchContent(args.query, options);\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(results, null, 2)\n }\n ]\n };\n}\n\nexport async function handleSimilarFilesTool(args: unknown): Promise<CallToolResult> {\n if (!isSimilarFilesToolArgs(args)) {\n throw new Error('file_path is required');\n }\n \n const results = await findSimilarFiles(\n args.file_path, \n args.limit || 10,\n args.workspace\n );\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(results, null, 2)\n }\n ]\n };\n}\n\nexport async function handleGetContentTool(args: unknown): Promise<CallToolResult> {\n if (!isGetContentToolArgs(args)) {\n throw new Error('file_path is required');\n }\n \n const content = await getFileContent(args.file_path, args.chunks);\n \n return {\n content: [\n {\n type: 'text',\n text: content\n }\n ]\n };\n}\n\nexport async function handleGetChunkTool(args: unknown): Promise<CallToolResult> {\n if (!isGetChunkToolArgs(args)) {\n throw new Error('file_path and chunk_id are required');\n }\n \n const content = await getChunkContent(args.file_path, args.chunk_id);\n \n return {\n content: [\n {\n type: 'text',\n text: content\n }\n ]\n };\n}\n\nexport async function handleServerInfoTool(version: string): Promise<CallToolResult> {\n const status = await getIndexStatus();\n \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\nexport function formatErrorResponse(error: unknown): CallToolResult {\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}","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 { \n handleIndexTool, \n handleSearchTool, \n handleSimilarFilesTool, \n handleGetContentTool, \n handleGetChunkTool, \n handleServerInfoTool,\n formatErrorResponse\n} from './mcp-handlers.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 to make their files searchable. Processes files to create vector embeddings for semantic search.\n\nWhen to use this tool:\n- User specifically requests indexing a directory as a knowledge base\n- Adding new documentation, code repositories, or file collections to search\n- Updating index when many files have changed\n\nHow it works:\n- Recursively scans directories for supported file types\n- Extracts text content and splits into chunks\n- Generates vector embeddings for semantic similarity\n- Stores in database for fast retrieval\n\nExamples:\n- Index documentation: \"/home/user/docs/project-wiki\"\n- Index codebase: \"/home/user/projects/api-server\"\n- Index multiple directories: \"/home/user/docs,/home/user/configs\"\n\nIndexing can take several minutes for large directories. Most users will already have directories indexed and can directly use search tool. Use server_info to check current indexing status first.`,\n inputSchema: {\n type: 'object',\n properties: {\n directory_path: {\n type: 'string',\n description: 'Comma-separated list of absolute directory paths to index. Must be absolute paths since MCP server runs independently. Examples: \"/home/user/projects\" (Unix) or \"C:\\\\Users\\\\user\\\\projects\" (Windows)'\n }\n },\n required: ['directory_path']\n }\n },\n {\n name: 'search',\n description: `Search indexed files using natural language queries. Finds files containing content semantically similar to the query.\n\nWhen to use this tool:\n- Find documentation, guides, or explanations about specific topics\n- Locate code files implementing certain functionality or patterns\n- Discover configuration files, scripts, or settings related to a topic\n- Search for files covering specific concepts or technologies\n\nHow it works:\n- Converts query to vector embedding using semantic similarity\n- Searches all indexed file chunks for relevant content\n- Groups results by file and calculates average relevance scores\n- Returns files ranked by relevance score\n\nExamples:\n- \"database configuration and connection pooling setup\" - finds config files, documentation about DB setup\n- \"comprehensive error handling patterns and exception management\" - finds code files with exception handling\n- \"JWT authentication implementation and session management\" - finds auth-related code and docs\n- \"REST API documentation and endpoint specifications\" - finds API guides, endpoint definitions\n- \"Docker deployment scripts and CI/CD pipeline configuration\" - finds deployment automation\n\nReturns files with similarity scores and chunk information. Use get_content to retrieve full file content or get_chunk to retrieve specific chunk content by chunk ID.\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 and exception management strategies\" (finds try/catch, error classes, logging)\n- \"database migration scripts and schema versioning approaches\" (finds SQL, schema changes, migration files)\n- \"authentication middleware and JWT token validation logic\" (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 workspace: {\n type: 'string',\n description: 'Optional workspace name to filter search results. Only files within the workspace directories will be searched. Use server_info to see available workspaces.'\n }\n },\n required: ['query']\n }\n },\n {\n name: 'similar_files',\n description: `Find files with content similar to a reference file. Uses semantic similarity to find related documents, code files, or any text content.\n\nWhen to use this tool:\n- Find documentation similar to a specific guide or README\n- Locate related code files, configuration files, or scripts\n- Discover alternative implementations or approaches\n- Find files covering similar topics or concepts\n\nHow it works:\n- Analyzes the semantic content of the reference file\n- Compares against all indexed files using vector similarity\n- Returns files ranked by content similarity score\n\nExamples:\n- Given \"deployment-guide.md\" - finds other deployment docs, CI/CD guides, infrastructure setup\n- Given \"troubleshooting.md\" - finds other troubleshooting guides, FAQ files, error documentation\n- Given \"config.yaml\" - finds other configuration files, settings, environment setups\n- Given \"auth.py\" - finds other authentication modules, security code, middleware\n\nReturns file paths with similarity scores. Use get_content to read full files or get_chunk for specific sections.`,\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 workspace: {\n type: 'string',\n description: 'Optional workspace name to filter results. Only files within the workspace directories will be considered. Use server_info to see available workspaces.'\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. Reads files directly from the filesystem.\n\nWhen to use this tool:\n- Get complete file content after finding files through search\n- Read documentation, code files, or configuration files for analysis\n- Extract specific sections of large files using chunk ranges\n- Access any text-based file content\n\nHow it works:\n- Reads files directly from filesystem (not from search index)\n- Returns entire file by default\n- Can return specific chunk ranges for indexed files\n- Preserves original formatting and content\n\nExamples:\n- Get full file: file_path=\"/home/user/docs/api.md\"\n- Get specific chunks: file_path=\"/home/user/code/main.py\", chunks=\"2-5\"\n- Get single chunk: file_path=\"/home/user/config.json\", chunks=\"1\"\n\nReturns file content as text. Use this after search or similar_files to read actual content.`,\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 content of a specific chunk from an indexed file. Gets exact text segments identified during search.\n\nWhen to use this tool:\n- Get specific relevant sections after performing a search\n- Access only the most pertinent parts of large files\n- Retrieve content from high-scoring chunks identified in search results\n- Avoid reading entire files when only specific sections are needed\n\nHow it works:\n- Files are split into overlapping text chunks during indexing\n- Each chunk has a sequential ID (\"0\", \"1\", \"2\", etc.)\n- Search results include chunk IDs for relevant sections\n- Returns the exact content that was semantically matched\n\nExamples:\n- After search returns chunk \"3\" from \"api-docs.md\" with high score\n- Get chunk content: file_path=\"/docs/api-docs.md\", chunk_id=\"3\"\n- Returns the specific text segment that matched your query\n\nReturns chunk content as text. Use this with chunk IDs from search results to get precise content sections.`,\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 information about server status and indexed content. Shows what directories and files are available for search.\n\nWhen to use this tool:\n- Check what content is already indexed before performing searches\n- Verify system is working properly\n- See indexing statistics and status\n- Understand scope of available searchable content\n\nHow it works:\n- Reports total indexed directories, files, and chunks\n- Shows database size and last indexing time\n- Lists all indexed directories with file counts\n- Reports any errors or issues\n\nExamples:\n- Check before searching: \"What content is indexed?\"\n- Verify after indexing: \"Did the indexing complete successfully?\"\n- Monitor system: \"How many files are searchable?\"\n\nReturns server version, indexing statistics, directory list, and any errors. Use this to understand what content is available for search and similar_files tools.`,\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 return await handleIndexTool(args, config);\n \n case 'search':\n return await handleSearchTool(args);\n \n case 'similar_files':\n return await handleSimilarFilesTool(args);\n \n case 'get_content':\n return await handleGetContentTool(args);\n \n case 'get_chunk':\n return await handleGetChunkTool(args);\n \n case 'server_info':\n return await handleServerInfoTool(VERSION);\n \n default:\n throw new Error(`Unknown tool: ${name}`);\n }\n } catch (error) {\n return formatErrorResponse(error);\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":"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"}
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { homedir } from "os";
|
|
2
2
|
import { join } from "path";
|
|
3
|
+
import { existsSync, statSync } from "fs";
|
|
3
4
|
import { z } from "zod";
|
|
5
|
+
import { normalizePath } from "./utils.js";
|
|
6
|
+
const WorkspaceSchema = z.object({
|
|
7
|
+
paths: z.array(z.string()),
|
|
8
|
+
isValid: z.boolean(),
|
|
9
|
+
filesCount: z.number().optional(),
|
|
10
|
+
chunksCount: z.number().optional()
|
|
11
|
+
});
|
|
4
12
|
const ConfigSchema = z.object({
|
|
5
13
|
storage: z.object({
|
|
6
14
|
sqlitePath: z.string(),
|
|
@@ -19,7 +27,8 @@ const ConfigSchema = z.object({
|
|
|
19
27
|
ignorePatterns: z.array(z.string())
|
|
20
28
|
}),
|
|
21
29
|
dataDir: z.string(),
|
|
22
|
-
verbose: z.boolean()
|
|
30
|
+
verbose: z.boolean(),
|
|
31
|
+
workspaces: z.record(WorkspaceSchema)
|
|
23
32
|
});
|
|
24
33
|
class ConfigError extends Error {
|
|
25
34
|
constructor(message, cause) {
|
|
@@ -28,11 +37,55 @@ class ConfigError extends Error {
|
|
|
28
37
|
this.name = "ConfigError";
|
|
29
38
|
}
|
|
30
39
|
}
|
|
40
|
+
function parseWorkspaces(env) {
|
|
41
|
+
const workspaces = {};
|
|
42
|
+
for (const [key, value] of Object.entries(env)) {
|
|
43
|
+
if (key.startsWith("WORKSPACE_") && value) {
|
|
44
|
+
const name = key.replace("WORKSPACE_", "").toLowerCase();
|
|
45
|
+
let paths;
|
|
46
|
+
try {
|
|
47
|
+
paths = JSON.parse(value);
|
|
48
|
+
if (!Array.isArray(paths)) {
|
|
49
|
+
throw new Error("Not an array");
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
paths = value.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
53
|
+
}
|
|
54
|
+
const normalizedPaths = paths.map(normalizePath);
|
|
55
|
+
const isValid = normalizedPaths.every((path) => {
|
|
56
|
+
try {
|
|
57
|
+
return existsSync(path) && statSync(path).isDirectory();
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
workspaces[name] = {
|
|
63
|
+
paths: normalizedPaths,
|
|
64
|
+
isValid
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return workspaces;
|
|
69
|
+
}
|
|
70
|
+
function getWorkspacePaths(config, workspace) {
|
|
71
|
+
const workspaceConfig = config.workspaces[workspace];
|
|
72
|
+
return workspaceConfig?.paths || [];
|
|
73
|
+
}
|
|
74
|
+
function isFileInWorkspace(filePath, workspacePaths) {
|
|
75
|
+
const normalizedFilePath = normalizePath(filePath);
|
|
76
|
+
return workspacePaths.some(
|
|
77
|
+
(workspacePath) => normalizedFilePath.startsWith(workspacePath + "/") || normalizedFilePath === workspacePath
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
function getAvailableWorkspaces(config) {
|
|
81
|
+
return Object.keys(config.workspaces);
|
|
82
|
+
}
|
|
31
83
|
function loadConfig(options = {}) {
|
|
32
84
|
const dataDir = process.env.DIRECTORY_INDEXER_DATA_DIR || join(homedir(), ".directory-indexer");
|
|
33
85
|
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
|
|
34
86
|
const dbFileName = isTest ? "test-data.db" : "data.db";
|
|
35
87
|
const defaultCollection = isTest ? "directory-indexer-test" : "directory-indexer";
|
|
88
|
+
const workspaces = parseWorkspaces(process.env);
|
|
36
89
|
const config = {
|
|
37
90
|
storage: {
|
|
38
91
|
sqlitePath: join(dataDir, dbFileName),
|
|
@@ -51,7 +104,8 @@ function loadConfig(options = {}) {
|
|
|
51
104
|
ignorePatterns: [".git", "node_modules", "target", ".DS_Store"]
|
|
52
105
|
},
|
|
53
106
|
dataDir,
|
|
54
|
-
verbose: options.verbose ?? process.env.VERBOSE === "true"
|
|
107
|
+
verbose: options.verbose ?? process.env.VERBOSE === "true",
|
|
108
|
+
workspaces
|
|
55
109
|
};
|
|
56
110
|
try {
|
|
57
111
|
return ConfigSchema.parse(config);
|
|
@@ -65,6 +119,9 @@ function loadConfig(options = {}) {
|
|
|
65
119
|
}
|
|
66
120
|
export {
|
|
67
121
|
ConfigError,
|
|
122
|
+
getAvailableWorkspaces,
|
|
123
|
+
getWorkspacePaths,
|
|
124
|
+
isFileInWorkspace,
|
|
68
125
|
loadConfig
|
|
69
126
|
};
|
|
70
127
|
//# sourceMappingURL=config.js.map
|
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://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":"
|
|
1
|
+
{"version":3,"file":"config.js","sources":["../src/config.ts"],"sourcesContent":["import { homedir } from 'os';\nimport { join } from 'path';\nimport { existsSync, statSync } from 'fs';\nimport { z } from 'zod';\nimport { normalizePath } from './utils';\n\nconst WorkspaceSchema = z.object({\n paths: z.array(z.string()),\n isValid: z.boolean(),\n filesCount: z.number().optional(),\n chunksCount: z.number().optional(),\n});\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 workspaces: z.record(WorkspaceSchema),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\nexport type WorkspaceConfig = z.infer<typeof WorkspaceSchema>;\n\nexport class ConfigError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\nfunction parseWorkspaces(env: Record<string, string | undefined>): Record<string, WorkspaceConfig> {\n const workspaces: Record<string, WorkspaceConfig> = {};\n \n for (const [key, value] of Object.entries(env)) {\n if (key.startsWith('WORKSPACE_') && value) {\n const name = key.replace('WORKSPACE_', '').toLowerCase();\n \n // Parse paths from comma-separated string or JSON array\n let paths: string[];\n try {\n // Try parsing as JSON array first\n paths = JSON.parse(value);\n if (!Array.isArray(paths)) {\n throw new Error('Not an array');\n }\n } catch {\n // Fall back to comma-separated string\n paths = value.split(',').map(p => p.trim()).filter(p => p.length > 0);\n }\n \n // Normalize paths for consistent comparison\n const normalizedPaths = paths.map(normalizePath);\n \n // Validate that paths exist and are directories\n const isValid = normalizedPaths.every(path => {\n try {\n return existsSync(path) && statSync(path).isDirectory();\n } catch {\n return false;\n }\n });\n \n workspaces[name] = {\n paths: normalizedPaths,\n isValid,\n };\n }\n }\n \n return workspaces;\n}\n\nexport function getWorkspacePaths(config: Config, workspace: string): string[] {\n const workspaceConfig = config.workspaces[workspace];\n return workspaceConfig?.paths || [];\n}\n\nexport function isFileInWorkspace(filePath: string, workspacePaths: string[]): boolean {\n const normalizedFilePath = normalizePath(filePath);\n return workspacePaths.some(workspacePath => \n normalizedFilePath.startsWith(workspacePath + '/') || \n normalizedFilePath === workspacePath\n );\n}\n\nexport function getAvailableWorkspaces(config: Config): string[] {\n return Object.keys(config.workspaces);\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 // Parse workspace configurations from environment variables\n const workspaces = parseWorkspaces(process.env);\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 workspaces,\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":";;;;;AAMA,MAAM,kBAAkB,EAAE,OAAO;AAAA,EAC/B,OAAO,EAAE,MAAM,EAAE,QAAQ;AAAA,EACzB,SAAS,EAAE,QAAA;AAAA,EACX,YAAY,EAAE,OAAA,EAAS,SAAA;AAAA,EACvB,aAAa,EAAE,OAAA,EAAS,SAAA;AAC1B,CAAC;AAED,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;AAAA,EACX,YAAY,EAAE,OAAO,eAAe;AACtC,CAAC;AAKM,MAAM,oBAAoB,MAAM;AAAA,EACrC,YAAY,SAAiC,OAAe;AAC1D,UAAM,OAAO;AAD8B,SAAA,QAAA;AAE3C,SAAK,OAAO;AAAA,EACd;AACF;AAEA,SAAS,gBAAgB,KAA0E;AACjG,QAAM,aAA8C,CAAA;AAEpD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,QAAI,IAAI,WAAW,YAAY,KAAK,OAAO;AACzC,YAAM,OAAO,IAAI,QAAQ,cAAc,EAAE,EAAE,YAAA;AAG3C,UAAI;AACJ,UAAI;AAEF,gBAAQ,KAAK,MAAM,KAAK;AACxB,YAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;AACzB,gBAAM,IAAI,MAAM,cAAc;AAAA,QAChC;AAAA,MACF,QAAQ;AAEN,gBAAQ,MAAM,MAAM,GAAG,EAAE,IAAI,CAAA,MAAK,EAAE,KAAA,CAAM,EAAE,OAAO,CAAA,MAAK,EAAE,SAAS,CAAC;AAAA,MACtE;AAGA,YAAM,kBAAkB,MAAM,IAAI,aAAa;AAG/C,YAAM,UAAU,gBAAgB,MAAM,CAAA,SAAQ;AAC5C,YAAI;AACF,iBAAO,WAAW,IAAI,KAAK,SAAS,IAAI,EAAE,YAAA;AAAA,QAC5C,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAED,iBAAW,IAAI,IAAI;AAAA,QACjB,OAAO;AAAA,QACP;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,kBAAkB,QAAgB,WAA6B;AAC7E,QAAM,kBAAkB,OAAO,WAAW,SAAS;AACnD,SAAO,iBAAiB,SAAS,CAAA;AACnC;AAEO,SAAS,kBAAkB,UAAkB,gBAAmC;AACrF,QAAM,qBAAqB,cAAc,QAAQ;AACjD,SAAO,eAAe;AAAA,IAAK,mBACzB,mBAAmB,WAAW,gBAAgB,GAAG,KACjD,uBAAuB;AAAA,EAAA;AAE3B;AAEO,SAAS,uBAAuB,QAA0B;AAC/D,SAAO,OAAO,KAAK,OAAO,UAAU;AACtC;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;AAG9D,QAAM,aAAa,gBAAgB,QAAQ,GAAG;AAE9C,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,IACrD;AAAA,EAAA;AAGF,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/search.js
CHANGED
|
@@ -10,24 +10,31 @@ class SearchError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
async function searchContent(query, options = {}) {
|
|
13
|
-
const { limit = 10, threshold = 0 } = options;
|
|
13
|
+
const { limit = 10, threshold = 0, workspace } = options;
|
|
14
14
|
try {
|
|
15
15
|
const config = (await import("./config.js")).loadConfig();
|
|
16
|
-
const {
|
|
16
|
+
const { getWorkspacePaths, isFileInWorkspace } = await import("./config.js");
|
|
17
|
+
const { sqlite, qdrant } = await initializeStorage(config);
|
|
17
18
|
const queryEmbedding = await generateEmbedding(query, config);
|
|
18
|
-
const
|
|
19
|
+
const workspacePaths = workspace ? getWorkspacePaths(config, workspace) : [];
|
|
20
|
+
const searchLimit = workspace ? limit * 10 : limit * 5;
|
|
21
|
+
const points = await qdrant.searchPoints(queryEmbedding, searchLimit);
|
|
19
22
|
const fileGroups = /* @__PURE__ */ new Map();
|
|
20
23
|
for (const point of points) {
|
|
21
24
|
const score = point.score ?? 0;
|
|
22
25
|
if (score < threshold) continue;
|
|
23
26
|
const filePath = point.payload.filePath;
|
|
27
|
+
if (workspace && workspacePaths.length > 0) {
|
|
28
|
+
if (!isFileInWorkspace(filePath, workspacePaths)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
24
32
|
if (!fileGroups.has(filePath)) {
|
|
25
33
|
fileGroups.set(filePath, []);
|
|
26
34
|
}
|
|
27
35
|
fileGroups.get(filePath).push({
|
|
28
36
|
score,
|
|
29
|
-
chunkId: point.payload.chunkId
|
|
30
|
-
parentDirectories: point.payload.parentDirectories
|
|
37
|
+
chunkId: point.payload.chunkId
|
|
31
38
|
});
|
|
32
39
|
}
|
|
33
40
|
const results = [];
|
|
@@ -38,11 +45,13 @@ async function searchContent(query, options = {}) {
|
|
|
38
45
|
chunkId: chunk.chunkId,
|
|
39
46
|
score: chunk.score
|
|
40
47
|
}));
|
|
48
|
+
const fileRecord = await sqlite.getFile(filePath);
|
|
49
|
+
const fileSizeBytes = fileRecord?.size ?? 0;
|
|
41
50
|
results.push({
|
|
42
51
|
filePath,
|
|
43
52
|
score: avgScore,
|
|
53
|
+
fileSizeBytes,
|
|
44
54
|
matchingChunks: chunks.length,
|
|
45
|
-
parentDirectories: sortedChunks[0].parentDirectories,
|
|
46
55
|
chunks: chunkMatches
|
|
47
56
|
});
|
|
48
57
|
}
|
|
@@ -51,31 +60,63 @@ async function searchContent(query, options = {}) {
|
|
|
51
60
|
throw new SearchError(`Failed to search content`, error);
|
|
52
61
|
}
|
|
53
62
|
}
|
|
54
|
-
async function findSimilarFiles(filePath, limit = 5) {
|
|
63
|
+
async function findSimilarFiles(filePath, limit = 5, workspace) {
|
|
55
64
|
try {
|
|
56
65
|
if (!await fileExists(filePath)) {
|
|
57
66
|
throw new Error(`File not found: ${filePath}`);
|
|
58
67
|
}
|
|
59
68
|
const config = (await import("./config.js")).loadConfig();
|
|
69
|
+
const { getWorkspacePaths, isFileInWorkspace } = await import("./config.js");
|
|
60
70
|
const { sqlite, qdrant } = await initializeStorage(config);
|
|
71
|
+
const workspacePaths = workspace ? getWorkspacePaths(config, workspace) : [];
|
|
61
72
|
const fileRecord = await sqlite.getFile(filePath);
|
|
62
73
|
if (!fileRecord || fileRecord.chunks.length === 0) {
|
|
63
74
|
const content = await promises.readFile(filePath, "utf-8");
|
|
64
75
|
const embedding = await generateEmbedding(content, config);
|
|
65
|
-
const
|
|
66
|
-
|
|
76
|
+
const searchLimit2 = workspace ? (limit + 1) * 5 : limit + 1;
|
|
77
|
+
const points2 = await qdrant.searchPoints(embedding, searchLimit2);
|
|
78
|
+
const filteredPoints2 = points2.filter((point) => {
|
|
79
|
+
const pointFilePath = point.payload.filePath;
|
|
80
|
+
if (pointFilePath === filePath) return false;
|
|
81
|
+
if (workspace && workspacePaths.length > 0) {
|
|
82
|
+
return isFileInWorkspace(pointFilePath, workspacePaths);
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}).slice(0, limit);
|
|
86
|
+
const results2 = [];
|
|
87
|
+
for (const point of filteredPoints2) {
|
|
88
|
+
const pointFileRecord = await sqlite.getFile(point.payload.filePath);
|
|
89
|
+
const fileSizeBytes = pointFileRecord?.size ?? 0;
|
|
90
|
+
results2.push({
|
|
91
|
+
filePath: point.payload.filePath,
|
|
92
|
+
score: point.score ?? 0,
|
|
93
|
+
fileSizeBytes
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return results2;
|
|
97
|
+
}
|
|
98
|
+
const firstChunkEmbedding = await generateEmbedding(fileRecord.chunks[0].content, config);
|
|
99
|
+
const searchLimit = workspace ? (limit + 1) * 5 : limit + 1;
|
|
100
|
+
const points = await qdrant.searchPoints(firstChunkEmbedding, searchLimit);
|
|
101
|
+
const filteredPoints = points.filter((point) => {
|
|
102
|
+
const pointFilePath = point.payload.filePath;
|
|
103
|
+
if (pointFilePath === filePath) return false;
|
|
104
|
+
if (workspace && workspacePaths.length > 0) {
|
|
105
|
+
return isFileInWorkspace(pointFilePath, workspacePaths);
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}).slice(0, limit);
|
|
109
|
+
const results = [];
|
|
110
|
+
for (const point of filteredPoints) {
|
|
111
|
+
const pointFileRecord = await sqlite.getFile(point.payload.filePath);
|
|
112
|
+
const fileSizeBytes = pointFileRecord?.size ?? 0;
|
|
113
|
+
results.push({
|
|
67
114
|
filePath: point.payload.filePath,
|
|
68
115
|
score: point.score ?? 0,
|
|
69
|
-
|
|
70
|
-
})
|
|
116
|
+
fileSizeBytes
|
|
117
|
+
});
|
|
71
118
|
}
|
|
72
|
-
|
|
73
|
-
const points = await qdrant.searchPoints(firstChunkEmbedding, limit + 1);
|
|
74
|
-
return points.filter((point) => point.payload.filePath !== filePath).slice(0, limit).map((point) => ({
|
|
75
|
-
filePath: point.payload.filePath,
|
|
76
|
-
score: point.score ?? 0,
|
|
77
|
-
parentDirectories: point.payload.parentDirectories
|
|
78
|
-
}));
|
|
119
|
+
return results;
|
|
79
120
|
} catch (error) {
|
|
80
121
|
throw new SearchError(`Failed to find similar files`, error);
|
|
81
122
|
}
|
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 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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"}
|
|
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 workspace?: string;\n}\n\nexport interface ChunkMatch {\n chunkId: string;\n score: number;\n}\n\nexport interface SearchResult {\n filePath: string;\n score: number;\n fileSizeBytes: number;\n matchingChunks: number;\n chunks: ChunkMatch[];\n}\n\nexport interface SimilarFile {\n filePath: string;\n score: number;\n fileSizeBytes: number;\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, workspace } = options;\n \n try {\n const config = (await import('./config.js')).loadConfig();\n const { getWorkspacePaths, isFileInWorkspace } = await import('./config.js');\n const { sqlite, qdrant } = await initializeStorage(config);\n \n const queryEmbedding = await generateEmbedding(query, config);\n \n // Get workspace paths if workspace is specified\n const workspacePaths = workspace ? getWorkspacePaths(config, workspace) : [];\n \n // Get more points initially since we'll group by file and potentially filter by workspace\n const searchLimit = workspace ? limit * 10 : limit * 5;\n const points = await qdrant.searchPoints(queryEmbedding, searchLimit);\n \n // Group points by file path, filtering by workspace if specified\n const fileGroups = new Map<string, Array<{ score: number; chunkId: 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 \n // Filter by workspace if specified\n if (workspace && workspacePaths.length > 0) {\n if (!isFileInWorkspace(filePath, workspacePaths)) {\n continue;\n }\n }\n \n if (!fileGroups.has(filePath)) {\n fileGroups.set(filePath, []);\n }\n \n fileGroups.get(filePath)!.push({\n score,\n chunkId: point.payload.chunkId\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 // Get file size from SQLite database\n const fileRecord = await sqlite.getFile(filePath);\n const fileSizeBytes = fileRecord?.size ?? 0;\n \n results.push({\n filePath,\n score: avgScore,\n fileSizeBytes,\n matchingChunks: chunks.length,\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, workspace?: string): 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 { getWorkspacePaths, isFileInWorkspace } = await import('./config.js');\n const { sqlite, qdrant } = await initializeStorage(config);\n \n // Get workspace paths if workspace is specified\n const workspacePaths = workspace ? getWorkspacePaths(config, workspace) : [];\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 searchLimit = workspace ? (limit + 1) * 5 : limit + 1;\n const points = await qdrant.searchPoints(embedding, searchLimit);\n \n const filteredPoints = points\n .filter(point => {\n const pointFilePath = point.payload.filePath;\n // Exclude the reference file itself\n if (pointFilePath === filePath) return false;\n \n // Filter by workspace if specified\n if (workspace && workspacePaths.length > 0) {\n return isFileInWorkspace(pointFilePath, workspacePaths);\n }\n \n return true;\n })\n .slice(0, limit);\n \n const results: SimilarFile[] = [];\n for (const point of filteredPoints) {\n const pointFileRecord = await sqlite.getFile(point.payload.filePath);\n const fileSizeBytes = pointFileRecord?.size ?? 0;\n \n results.push({\n filePath: point.payload.filePath,\n score: point.score ?? 0,\n fileSizeBytes\n });\n }\n \n return results;\n }\n \n const firstChunkEmbedding = await generateEmbedding(fileRecord.chunks[0].content, config);\n const searchLimit = workspace ? (limit + 1) * 5 : limit + 1;\n const points = await qdrant.searchPoints(firstChunkEmbedding, searchLimit);\n \n const filteredPoints = points\n .filter(point => {\n const pointFilePath = point.payload.filePath;\n // Exclude the reference file itself\n if (pointFilePath === filePath) return false;\n \n // Filter by workspace if specified\n if (workspace && workspacePaths.length > 0) {\n return isFileInWorkspace(pointFilePath, workspacePaths);\n }\n \n return true;\n })\n .slice(0, limit);\n \n const results: SimilarFile[] = [];\n for (const point of filteredPoints) {\n const pointFileRecord = await sqlite.getFile(point.payload.filePath);\n const fileSizeBytes = pointFileRecord?.size ?? 0;\n \n results.push({\n filePath: point.payload.filePath,\n score: point.score ?? 0,\n fileSizeBytes\n });\n }\n \n return results;\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","searchLimit","points","filteredPoints","results","num"],"mappings":"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"}
|
package/dist/storage.js
CHANGED
|
@@ -268,6 +268,38 @@ async function initializeStorage(config) {
|
|
|
268
268
|
async function initDatabase(dbPath) {
|
|
269
269
|
return new Database(dbPath);
|
|
270
270
|
}
|
|
271
|
+
async function calculateWorkspaceStatistics(sqlite, config) {
|
|
272
|
+
const { getAvailableWorkspaces, getWorkspacePaths, isFileInWorkspace } = await import("./config.js");
|
|
273
|
+
const workspaces = [];
|
|
274
|
+
for (const workspaceName of getAvailableWorkspaces(config)) {
|
|
275
|
+
const workspacePaths = getWorkspacePaths(config, workspaceName);
|
|
276
|
+
const workspaceConfig = config.workspaces[workspaceName];
|
|
277
|
+
const filesStmt = sqlite.db.prepare("SELECT path, chunks_json FROM files");
|
|
278
|
+
const allFiles = filesStmt.all();
|
|
279
|
+
let filesCount = 0;
|
|
280
|
+
let chunksCount = 0;
|
|
281
|
+
for (const file of allFiles) {
|
|
282
|
+
if (isFileInWorkspace(file.path, workspacePaths)) {
|
|
283
|
+
filesCount++;
|
|
284
|
+
if (file.chunks_json) {
|
|
285
|
+
try {
|
|
286
|
+
const chunks = JSON.parse(file.chunks_json);
|
|
287
|
+
chunksCount += chunks.length;
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
workspaces.push({
|
|
294
|
+
name: workspaceName,
|
|
295
|
+
paths: workspacePaths,
|
|
296
|
+
isValid: workspaceConfig.isValid,
|
|
297
|
+
filesCount,
|
|
298
|
+
chunksCount
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return workspaces;
|
|
302
|
+
}
|
|
271
303
|
async function checkQdrantConsistency(sqlite, config) {
|
|
272
304
|
const issues = [];
|
|
273
305
|
try {
|
|
@@ -373,6 +405,7 @@ async function getIndexStatus() {
|
|
|
373
405
|
};
|
|
374
406
|
});
|
|
375
407
|
const qdrantConsistency = await checkQdrantConsistency(sqlite, config);
|
|
408
|
+
const workspaces = await calculateWorkspaceStatistics(sqlite, config);
|
|
376
409
|
const fs = await import("fs");
|
|
377
410
|
let databaseSize = "0 KB";
|
|
378
411
|
try {
|
|
@@ -396,6 +429,7 @@ async function getIndexStatus() {
|
|
|
396
429
|
lastIndexed: lastIndexedResult.last_indexed ? new Date(lastIndexedResult.last_indexed).toISOString() : null,
|
|
397
430
|
errors: allErrors,
|
|
398
431
|
directories,
|
|
432
|
+
workspaces,
|
|
399
433
|
qdrantConsistency
|
|
400
434
|
};
|
|
401
435
|
} finally {
|
package/dist/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sources":["../src/storage.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { Config } from './config.js';\nimport { FileInfo, ChunkInfo, ensureDirectory } from './utils.js';\nimport { dirname } from 'path';\n\nexport interface DirectoryRecord {\n id: number;\n path: string;\n status: 'pending' | 'indexing' | 'completed' | 'failed';\n indexedAt: Date;\n}\n\nexport interface FileRecord {\n id: number;\n path: string;\n size: number;\n modifiedTime: Date;\n hash: string;\n parentDirs: string[];\n chunks: ChunkInfo[];\n errors?: string[];\n}\n\nexport interface QdrantPoint {\n id: string | number;\n vector: number[];\n payload: {\n filePath: string;\n chunkId: string;\n parentDirectories: string[];\n };\n score?: number;\n}\n\nexport class StorageError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'StorageError';\n }\n}\n\nexport class QdrantClient {\n constructor(private config: Config) {}\n\n async healthCheck(): Promise<boolean> {\n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/healthz`);\n return response.ok;\n } catch {\n return false;\n }\n }\n\n async createCollection(): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const checkResponse = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`);\n if (checkResponse.ok) {\n return;\n }\n\n const createResponse = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n vectors: {\n size: 768,\n distance: 'Cosine'\n }\n })\n });\n\n if (!createResponse.ok) {\n throw new Error(`Failed to create collection: ${createResponse.statusText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to create Qdrant collection`, error as Error);\n }\n }\n\n async upsertPoints(points: QdrantPoint[]): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ points })\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to upsert points: ${response.status} ${response.statusText} - ${errorText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to upsert points to Qdrant`, error as Error);\n }\n }\n\n async searchPoints(vector: number[], limit: number = 10): Promise<QdrantPoint[]> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/search`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n vector,\n limit,\n with_payload: true\n })\n });\n\n if (!response.ok) {\n throw new Error(`Failed to search points: ${response.statusText}`);\n }\n\n const data = await response.json();\n return data.result.map((item: { id: string | number; vector: number[]; payload: Record<string, unknown>; score: number }) => ({\n id: item.id,\n vector: item.vector,\n payload: item.payload,\n score: item.score\n }));\n } catch (error) {\n throw new StorageError(`Failed to search points in Qdrant`, error as Error);\n }\n }\n\n async deletePoints(ids: (string | number)[]): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/delete`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ points: ids })\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete points: ${response.statusText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to delete points from Qdrant`, error as Error);\n }\n }\n\n async deletePointsByFileHash(fileHash: string): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/delete`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n filter: {\n must: [\n {\n key: 'fileHash',\n match: { value: fileHash }\n }\n ]\n }\n })\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to delete points by file hash: ${response.status} ${response.statusText} - ${errorText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to delete points by file hash from Qdrant`, error as Error);\n }\n }\n}\n\nexport class SQLiteStorage {\n public db: Database.Database;\n\n constructor(private config: Config) {\n this.db = this.initializeDatabase();\n }\n\n private initializeDatabase(): Database.Database {\n try {\n ensureDirectory(dirname(this.config.storage.sqlitePath));\n \n const db = new Database(this.config.storage.sqlitePath);\n \n db.exec(`\n CREATE TABLE IF NOT EXISTS directories (\n id INTEGER PRIMARY KEY,\n path TEXT UNIQUE NOT NULL,\n status TEXT DEFAULT 'pending',\n indexed_at INTEGER DEFAULT 0\n );\n\n CREATE TABLE IF NOT EXISTS files (\n id INTEGER PRIMARY KEY,\n path TEXT UNIQUE NOT NULL,\n size INTEGER NOT NULL,\n modified_time INTEGER NOT NULL,\n hash TEXT NOT NULL,\n parent_dirs TEXT NOT NULL,\n chunks_json TEXT,\n errors_json TEXT\n );\n\n CREATE INDEX IF NOT EXISTS idx_files_path ON files(path);\n CREATE INDEX IF NOT EXISTS idx_files_hash ON files(hash);\n CREATE INDEX IF NOT EXISTS idx_directories_path ON directories(path);\n `);\n\n return db;\n } catch (error) {\n throw new StorageError(`Failed to initialize SQLite database`, error as Error);\n }\n }\n\n async getDirectory(path: string): Promise<DirectoryRecord | null> {\n try {\n const stmt = this.db.prepare('SELECT * FROM directories WHERE path = ?');\n const row = stmt.get(path) as { id: number; path: string; status: 'pending' | 'indexing' | 'completed' | 'failed'; indexed_at: number } | undefined;\n \n if (!row) return null;\n \n return {\n id: row.id,\n path: row.path,\n status: row.status,\n indexedAt: new Date(row.indexed_at)\n };\n } catch (error) {\n throw new StorageError(`Failed to get directory record`, error as Error);\n }\n }\n\n async upsertDirectory(path: string, status: DirectoryRecord['status']): Promise<void> {\n try {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO directories (path, status, indexed_at)\n VALUES (?, ?, ?)\n `);\n \n stmt.run(path, status, Date.now());\n } catch (error) {\n throw new StorageError(`Failed to upsert directory record`, error as Error);\n }\n }\n\n async getFile(path: string): Promise<FileRecord | null> {\n try {\n const stmt = this.db.prepare('SELECT * FROM files WHERE path = ?');\n const row = stmt.get(path) as { id: number; path: string; size: number; modified_time: number; hash: string; parent_dirs: string; chunks_json: string | null; errors_json: string | null } | undefined;\n \n if (!row) return null;\n \n return {\n id: row.id,\n path: row.path,\n size: row.size,\n modifiedTime: new Date(row.modified_time * 1000),\n hash: row.hash,\n parentDirs: JSON.parse(row.parent_dirs),\n chunks: row.chunks_json ? JSON.parse(row.chunks_json) : [],\n errors: row.errors_json ? JSON.parse(row.errors_json) : undefined\n };\n } catch (error) {\n throw new StorageError(`Failed to get file record`, error as Error);\n }\n }\n\n async upsertFile(fileInfo: FileInfo, chunks: ChunkInfo[] = [], errors: string[] = []): Promise<void> {\n try {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO files (path, size, modified_time, hash, parent_dirs, chunks_json, errors_json)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n \n stmt.run(\n fileInfo.path,\n fileInfo.size,\n fileInfo.modifiedTime.getTime(),\n fileInfo.hash,\n JSON.stringify(fileInfo.parentDirs),\n chunks.length > 0 ? JSON.stringify(chunks) : null,\n errors.length > 0 ? JSON.stringify(errors) : null\n );\n } catch (error) {\n throw new StorageError(`Failed to upsert file record`, error as Error);\n }\n }\n\n async deleteFile(path: string): Promise<void> {\n try {\n const stmt = this.db.prepare('DELETE FROM files WHERE path = ?');\n stmt.run(path);\n } catch (error) {\n throw new StorageError(`Failed to delete file record`, error as Error);\n }\n }\n\n async getFilesByDirectory(directoryPath: string): Promise<FileRecord[]> {\n try {\n const stmt = this.db.prepare('SELECT * FROM files WHERE path LIKE ?');\n const rows = stmt.all(`${directoryPath}%`) as { id: number; path: string; size: number; modified_time: number; hash: string; parent_dirs: string; chunks_json: string | null; errors_json: string | null }[];\n \n return rows.map(row => ({\n id: row.id,\n path: row.path,\n size: row.size,\n modifiedTime: new Date(row.modified_time * 1000),\n hash: row.hash,\n parentDirs: JSON.parse(row.parent_dirs),\n chunks: row.chunks_json ? JSON.parse(row.chunks_json) : [],\n errors: row.errors_json ? JSON.parse(row.errors_json) : undefined\n }));\n } catch (error) {\n throw new StorageError(`Failed to get files by directory`, error as Error);\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n\nexport async function initializeStorage(config: Config): Promise<{ sqlite: SQLiteStorage; qdrant: QdrantClient }> {\n const sqlite = new SQLiteStorage(config);\n const qdrant = new QdrantClient(config);\n \n await qdrant.createCollection();\n \n return { sqlite, qdrant };\n}\n\nexport async function initDatabase(dbPath: string): Promise<Database.Database> {\n return new Database(dbPath);\n}\n\nexport interface DirectoryStatus {\n path: string;\n status: string;\n filesCount: number;\n chunksCount: number;\n lastIndexed: string | null;\n errors: string[];\n}\n\nexport interface IndexStatus {\n directoriesIndexed: number;\n filesIndexed: number;\n chunksIndexed: number;\n databaseSize: string;\n lastIndexed: string | null;\n errors: string[];\n directories: DirectoryStatus[];\n qdrantConsistency: {\n isConsistent: boolean;\n issues: string[];\n };\n}\n\nasync function checkQdrantConsistency(sqlite: SQLiteStorage, config: Config): Promise<{ isConsistent: boolean; issues: string[] }> {\n const issues: string[] = [];\n \n try {\n const qdrant = new QdrantClient(config);\n const isHealthy = await qdrant.healthCheck();\n \n if (!isHealthy) {\n issues.push('Qdrant vector database is not running or accessible');\n return { isConsistent: false, issues };\n }\n \n const filesWithChunksStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM files WHERE chunks_json IS NOT NULL');\n const filesWithChunks = filesWithChunksStmt.get() as { count: number };\n \n const totalChunksStmt = sqlite.db.prepare('SELECT SUM(json_array_length(chunks_json)) as count FROM files WHERE chunks_json IS NOT NULL');\n const totalChunks = totalChunksStmt.get() as { count: number | null };\n \n if (filesWithChunks.count > 0 && (totalChunks.count || 0) === 0) {\n issues.push('Files exist but no chunks found - possible data corruption');\n }\n \n const collectionName = config.storage.qdrantCollection;\n try {\n const response = await fetch(`${config.storage.qdrantEndpoint}/collections/${collectionName}`);\n if (!response.ok) {\n issues.push(`Vector collection '${collectionName}' not found (normal during first-time setup)`);\n return { isConsistent: false, issues };\n }\n \n const collectionInfo = await response.json();\n const qdrantPointCount = collectionInfo.result?.points_count || 0;\n const sqliteChunkCount = totalChunks.count || 0;\n \n if (Math.abs(qdrantPointCount - sqliteChunkCount) > 0) {\n if (qdrantPointCount > sqliteChunkCount) {\n issues.push(`Extra vectors in database: ${qdrantPointCount} vectors vs ${sqliteChunkCount} indexed chunks (normal during cleanup)`);\n } else {\n issues.push(`Missing vectors: ${sqliteChunkCount} indexed chunks vs ${qdrantPointCount} vectors (normal during indexing)`);\n }\n }\n } catch (error) {\n issues.push(`Cannot verify vector database status: ${error}`);\n }\n \n } catch (error) {\n issues.push(`Database status check failed: ${error}`);\n }\n \n return {\n isConsistent: issues.length === 0,\n issues\n };\n}\n\nexport async function getIndexStatus(): Promise<IndexStatus> {\n const config = await import('./config.js').then(m => m.loadConfig());\n const sqlite = new SQLiteStorage(config);\n \n try {\n const directoriesStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM directories WHERE status = ?');\n const directoriesCount = directoriesStmt.get('completed') as { count: number };\n \n const filesStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM files');\n const filesCount = filesStmt.get() as { count: number };\n \n const chunksStmt = sqlite.db.prepare('SELECT SUM(json_array_length(chunks_json)) as count FROM files WHERE chunks_json IS NOT NULL');\n const chunksCount = chunksStmt.get() as { count: number | null };\n \n const lastIndexedStmt = sqlite.db.prepare('SELECT MAX(indexed_at) as last_indexed FROM directories WHERE indexed_at > 0');\n const lastIndexedResult = lastIndexedStmt.get() as { last_indexed: number | null };\n \n const errorsStmt = sqlite.db.prepare('SELECT errors_json FROM files WHERE errors_json IS NOT NULL');\n const errorRows = errorsStmt.all() as { errors_json: string }[];\n \n const allErrors: string[] = [];\n errorRows.forEach(row => {\n try {\n const errors = JSON.parse(row.errors_json);\n allErrors.push(...errors);\n } catch {\n allErrors.push('Failed to parse error JSON');\n }\n });\n \n const directoriesDetailStmt = sqlite.db.prepare(`\n SELECT \n d.path,\n d.status,\n d.indexed_at,\n COUNT(f.id) as files_count,\n COALESCE(SUM(json_array_length(f.chunks_json)), 0) as chunks_count\n FROM directories d\n LEFT JOIN files f ON f.parent_dirs LIKE '%\"' || d.path || '\"%'\n GROUP BY d.id, d.path, d.status, d.indexed_at\n ORDER BY d.indexed_at DESC\n `);\n const directoryDetails = directoriesDetailStmt.all() as { id: number; path: string; status: 'pending' | 'indexing' | 'completed' | 'failed'; indexed_at: number; files_count: number; chunks_count: number }[];\n \n const directories: DirectoryStatus[] = directoryDetails.map(row => {\n const errorsByDirStmt = sqlite.db.prepare(`\n SELECT errors_json FROM files \n WHERE parent_dirs LIKE '%\"' || ? || '\"%' AND errors_json IS NOT NULL\n `);\n const dirErrors = errorsByDirStmt.all(row.path) as { errors_json: string }[];\n \n const dirErrorsList: string[] = [];\n dirErrors.forEach(errorRow => {\n try {\n const errors = JSON.parse(errorRow.errors_json);\n dirErrorsList.push(...errors);\n } catch {\n dirErrorsList.push('Failed to parse error JSON');\n }\n });\n \n return {\n path: row.path,\n status: row.status,\n filesCount: row.files_count,\n chunksCount: row.chunks_count,\n lastIndexed: row.indexed_at && row.indexed_at > 0 ? new Date(row.indexed_at).toISOString() : null,\n errors: dirErrorsList\n };\n });\n \n const qdrantConsistency = await checkQdrantConsistency(sqlite, config);\n \n const fs = await import('fs');\n let databaseSize = '0 KB';\n try {\n const stats = fs.statSync(config.storage.sqlitePath);\n const sizeInBytes = stats.size;\n if (sizeInBytes > 1024 * 1024) {\n databaseSize = `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`;\n } else if (sizeInBytes > 1024) {\n databaseSize = `${(sizeInBytes / 1024).toFixed(2)} KB`;\n } else {\n databaseSize = `${sizeInBytes} bytes`;\n }\n } catch {\n databaseSize = 'Unknown';\n }\n \n return {\n directoriesIndexed: directoriesCount.count,\n filesIndexed: filesCount.count,\n chunksIndexed: chunksCount.count || 0,\n databaseSize,\n lastIndexed: lastIndexedResult.last_indexed ? new Date(lastIndexedResult.last_indexed).toISOString() : null,\n errors: allErrors,\n directories,\n qdrantConsistency\n };\n } finally {\n sqlite.close();\n }\n}"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage.js","sources":["../src/storage.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { Config } from './config.js';\nimport { FileInfo, ChunkInfo, ensureDirectory } from './utils.js';\nimport { dirname } from 'path';\n\nexport interface DirectoryRecord {\n id: number;\n path: string;\n status: 'pending' | 'indexing' | 'completed' | 'failed';\n indexedAt: Date;\n}\n\nexport interface FileRecord {\n id: number;\n path: string;\n size: number;\n modifiedTime: Date;\n hash: string;\n parentDirs: string[];\n chunks: ChunkInfo[];\n errors?: string[];\n}\n\nexport interface QdrantPoint {\n id: string | number;\n vector: number[];\n payload: {\n filePath: string;\n chunkId: string;\n parentDirectories: string[];\n };\n score?: number;\n}\n\nexport class StorageError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'StorageError';\n }\n}\n\nexport class QdrantClient {\n constructor(private config: Config) {}\n\n async healthCheck(): Promise<boolean> {\n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/healthz`);\n return response.ok;\n } catch {\n return false;\n }\n }\n\n async createCollection(): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const checkResponse = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`);\n if (checkResponse.ok) {\n return;\n }\n\n const createResponse = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n vectors: {\n size: 768,\n distance: 'Cosine'\n }\n })\n });\n\n if (!createResponse.ok) {\n throw new Error(`Failed to create collection: ${createResponse.statusText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to create Qdrant collection`, error as Error);\n }\n }\n\n async upsertPoints(points: QdrantPoint[]): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ points })\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to upsert points: ${response.status} ${response.statusText} - ${errorText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to upsert points to Qdrant`, error as Error);\n }\n }\n\n async searchPoints(vector: number[], limit: number = 10): Promise<QdrantPoint[]> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/search`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n vector,\n limit,\n with_payload: true\n })\n });\n\n if (!response.ok) {\n throw new Error(`Failed to search points: ${response.statusText}`);\n }\n\n const data = await response.json();\n return data.result.map((item: { id: string | number; vector: number[]; payload: Record<string, unknown>; score: number }) => ({\n id: item.id,\n vector: item.vector,\n payload: item.payload,\n score: item.score\n }));\n } catch (error) {\n throw new StorageError(`Failed to search points in Qdrant`, error as Error);\n }\n }\n\n async deletePoints(ids: (string | number)[]): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/delete`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ points: ids })\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete points: ${response.statusText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to delete points from Qdrant`, error as Error);\n }\n }\n\n async deletePointsByFileHash(fileHash: string): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/delete`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n filter: {\n must: [\n {\n key: 'fileHash',\n match: { value: fileHash }\n }\n ]\n }\n })\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to delete points by file hash: ${response.status} ${response.statusText} - ${errorText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to delete points by file hash from Qdrant`, error as Error);\n }\n }\n}\n\nexport class SQLiteStorage {\n public db: Database.Database;\n\n constructor(private config: Config) {\n this.db = this.initializeDatabase();\n }\n\n private initializeDatabase(): Database.Database {\n try {\n ensureDirectory(dirname(this.config.storage.sqlitePath));\n \n const db = new Database(this.config.storage.sqlitePath);\n \n db.exec(`\n CREATE TABLE IF NOT EXISTS directories (\n id INTEGER PRIMARY KEY,\n path TEXT UNIQUE NOT NULL,\n status TEXT DEFAULT 'pending',\n indexed_at INTEGER DEFAULT 0\n );\n\n CREATE TABLE IF NOT EXISTS files (\n id INTEGER PRIMARY KEY,\n path TEXT UNIQUE NOT NULL,\n size INTEGER NOT NULL,\n modified_time INTEGER NOT NULL,\n hash TEXT NOT NULL,\n parent_dirs TEXT NOT NULL,\n chunks_json TEXT,\n errors_json TEXT\n );\n\n CREATE INDEX IF NOT EXISTS idx_files_path ON files(path);\n CREATE INDEX IF NOT EXISTS idx_files_hash ON files(hash);\n CREATE INDEX IF NOT EXISTS idx_directories_path ON directories(path);\n `);\n\n return db;\n } catch (error) {\n throw new StorageError(`Failed to initialize SQLite database`, error as Error);\n }\n }\n\n async getDirectory(path: string): Promise<DirectoryRecord | null> {\n try {\n const stmt = this.db.prepare('SELECT * FROM directories WHERE path = ?');\n const row = stmt.get(path) as { id: number; path: string; status: 'pending' | 'indexing' | 'completed' | 'failed'; indexed_at: number } | undefined;\n \n if (!row) return null;\n \n return {\n id: row.id,\n path: row.path,\n status: row.status,\n indexedAt: new Date(row.indexed_at)\n };\n } catch (error) {\n throw new StorageError(`Failed to get directory record`, error as Error);\n }\n }\n\n async upsertDirectory(path: string, status: DirectoryRecord['status']): Promise<void> {\n try {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO directories (path, status, indexed_at)\n VALUES (?, ?, ?)\n `);\n \n stmt.run(path, status, Date.now());\n } catch (error) {\n throw new StorageError(`Failed to upsert directory record`, error as Error);\n }\n }\n\n async getFile(path: string): Promise<FileRecord | null> {\n try {\n const stmt = this.db.prepare('SELECT * FROM files WHERE path = ?');\n const row = stmt.get(path) as { id: number; path: string; size: number; modified_time: number; hash: string; parent_dirs: string; chunks_json: string | null; errors_json: string | null } | undefined;\n \n if (!row) return null;\n \n return {\n id: row.id,\n path: row.path,\n size: row.size,\n modifiedTime: new Date(row.modified_time * 1000),\n hash: row.hash,\n parentDirs: JSON.parse(row.parent_dirs),\n chunks: row.chunks_json ? JSON.parse(row.chunks_json) : [],\n errors: row.errors_json ? JSON.parse(row.errors_json) : undefined\n };\n } catch (error) {\n throw new StorageError(`Failed to get file record`, error as Error);\n }\n }\n\n async upsertFile(fileInfo: FileInfo, chunks: ChunkInfo[] = [], errors: string[] = []): Promise<void> {\n try {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO files (path, size, modified_time, hash, parent_dirs, chunks_json, errors_json)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n \n stmt.run(\n fileInfo.path,\n fileInfo.size,\n fileInfo.modifiedTime.getTime(),\n fileInfo.hash,\n JSON.stringify(fileInfo.parentDirs),\n chunks.length > 0 ? JSON.stringify(chunks) : null,\n errors.length > 0 ? JSON.stringify(errors) : null\n );\n } catch (error) {\n throw new StorageError(`Failed to upsert file record`, error as Error);\n }\n }\n\n async deleteFile(path: string): Promise<void> {\n try {\n const stmt = this.db.prepare('DELETE FROM files WHERE path = ?');\n stmt.run(path);\n } catch (error) {\n throw new StorageError(`Failed to delete file record`, error as Error);\n }\n }\n\n async getFilesByDirectory(directoryPath: string): Promise<FileRecord[]> {\n try {\n const stmt = this.db.prepare('SELECT * FROM files WHERE path LIKE ?');\n const rows = stmt.all(`${directoryPath}%`) as { id: number; path: string; size: number; modified_time: number; hash: string; parent_dirs: string; chunks_json: string | null; errors_json: string | null }[];\n \n return rows.map(row => ({\n id: row.id,\n path: row.path,\n size: row.size,\n modifiedTime: new Date(row.modified_time * 1000),\n hash: row.hash,\n parentDirs: JSON.parse(row.parent_dirs),\n chunks: row.chunks_json ? JSON.parse(row.chunks_json) : [],\n errors: row.errors_json ? JSON.parse(row.errors_json) : undefined\n }));\n } catch (error) {\n throw new StorageError(`Failed to get files by directory`, error as Error);\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n\nexport async function initializeStorage(config: Config): Promise<{ sqlite: SQLiteStorage; qdrant: QdrantClient }> {\n const sqlite = new SQLiteStorage(config);\n const qdrant = new QdrantClient(config);\n \n await qdrant.createCollection();\n \n return { sqlite, qdrant };\n}\n\nexport async function initDatabase(dbPath: string): Promise<Database.Database> {\n return new Database(dbPath);\n}\n\nexport interface DirectoryStatus {\n path: string;\n status: string;\n filesCount: number;\n chunksCount: number;\n lastIndexed: string | null;\n errors: string[];\n}\n\nexport interface WorkspaceStatus {\n name: string;\n paths: string[];\n isValid: boolean;\n filesCount: number;\n chunksCount: number;\n}\n\nexport interface IndexStatus {\n directoriesIndexed: number;\n filesIndexed: number;\n chunksIndexed: number;\n databaseSize: string;\n lastIndexed: string | null;\n errors: string[];\n directories: DirectoryStatus[];\n workspaces: WorkspaceStatus[];\n qdrantConsistency: {\n isConsistent: boolean;\n issues: string[];\n };\n}\n\nasync function calculateWorkspaceStatistics(sqlite: SQLiteStorage, config: Config): Promise<WorkspaceStatus[]> {\n const { getAvailableWorkspaces, getWorkspacePaths, isFileInWorkspace } = await import('./config.js');\n const workspaces: WorkspaceStatus[] = [];\n \n for (const workspaceName of getAvailableWorkspaces(config)) {\n const workspacePaths = getWorkspacePaths(config, workspaceName);\n const workspaceConfig = config.workspaces[workspaceName];\n \n // Get all files and count those in this workspace\n const filesStmt = sqlite.db.prepare('SELECT path, chunks_json FROM files');\n const allFiles = filesStmt.all() as { path: string; chunks_json: string | null }[];\n \n let filesCount = 0;\n let chunksCount = 0;\n \n for (const file of allFiles) {\n if (isFileInWorkspace(file.path, workspacePaths)) {\n filesCount++;\n if (file.chunks_json) {\n try {\n const chunks = JSON.parse(file.chunks_json);\n chunksCount += chunks.length;\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n \n workspaces.push({\n name: workspaceName,\n paths: workspacePaths,\n isValid: workspaceConfig.isValid,\n filesCount,\n chunksCount\n });\n }\n \n return workspaces;\n}\n\nasync function checkQdrantConsistency(sqlite: SQLiteStorage, config: Config): Promise<{ isConsistent: boolean; issues: string[] }> {\n const issues: string[] = [];\n \n try {\n const qdrant = new QdrantClient(config);\n const isHealthy = await qdrant.healthCheck();\n \n if (!isHealthy) {\n issues.push('Qdrant vector database is not running or accessible');\n return { isConsistent: false, issues };\n }\n \n const filesWithChunksStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM files WHERE chunks_json IS NOT NULL');\n const filesWithChunks = filesWithChunksStmt.get() as { count: number };\n \n const totalChunksStmt = sqlite.db.prepare('SELECT SUM(json_array_length(chunks_json)) as count FROM files WHERE chunks_json IS NOT NULL');\n const totalChunks = totalChunksStmt.get() as { count: number | null };\n \n if (filesWithChunks.count > 0 && (totalChunks.count || 0) === 0) {\n issues.push('Files exist but no chunks found - possible data corruption');\n }\n \n const collectionName = config.storage.qdrantCollection;\n try {\n const response = await fetch(`${config.storage.qdrantEndpoint}/collections/${collectionName}`);\n if (!response.ok) {\n issues.push(`Vector collection '${collectionName}' not found (normal during first-time setup)`);\n return { isConsistent: false, issues };\n }\n \n const collectionInfo = await response.json();\n const qdrantPointCount = collectionInfo.result?.points_count || 0;\n const sqliteChunkCount = totalChunks.count || 0;\n \n if (Math.abs(qdrantPointCount - sqliteChunkCount) > 0) {\n if (qdrantPointCount > sqliteChunkCount) {\n issues.push(`Extra vectors in database: ${qdrantPointCount} vectors vs ${sqliteChunkCount} indexed chunks (normal during cleanup)`);\n } else {\n issues.push(`Missing vectors: ${sqliteChunkCount} indexed chunks vs ${qdrantPointCount} vectors (normal during indexing)`);\n }\n }\n } catch (error) {\n issues.push(`Cannot verify vector database status: ${error}`);\n }\n \n } catch (error) {\n issues.push(`Database status check failed: ${error}`);\n }\n \n return {\n isConsistent: issues.length === 0,\n issues\n };\n}\n\nexport async function getIndexStatus(): Promise<IndexStatus> {\n const config = await import('./config.js').then(m => m.loadConfig());\n const sqlite = new SQLiteStorage(config);\n \n try {\n const directoriesStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM directories WHERE status = ?');\n const directoriesCount = directoriesStmt.get('completed') as { count: number };\n \n const filesStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM files');\n const filesCount = filesStmt.get() as { count: number };\n \n const chunksStmt = sqlite.db.prepare('SELECT SUM(json_array_length(chunks_json)) as count FROM files WHERE chunks_json IS NOT NULL');\n const chunksCount = chunksStmt.get() as { count: number | null };\n \n const lastIndexedStmt = sqlite.db.prepare('SELECT MAX(indexed_at) as last_indexed FROM directories WHERE indexed_at > 0');\n const lastIndexedResult = lastIndexedStmt.get() as { last_indexed: number | null };\n \n const errorsStmt = sqlite.db.prepare('SELECT errors_json FROM files WHERE errors_json IS NOT NULL');\n const errorRows = errorsStmt.all() as { errors_json: string }[];\n \n const allErrors: string[] = [];\n errorRows.forEach(row => {\n try {\n const errors = JSON.parse(row.errors_json);\n allErrors.push(...errors);\n } catch {\n allErrors.push('Failed to parse error JSON');\n }\n });\n \n const directoriesDetailStmt = sqlite.db.prepare(`\n SELECT \n d.path,\n d.status,\n d.indexed_at,\n COUNT(f.id) as files_count,\n COALESCE(SUM(json_array_length(f.chunks_json)), 0) as chunks_count\n FROM directories d\n LEFT JOIN files f ON f.parent_dirs LIKE '%\"' || d.path || '\"%'\n GROUP BY d.id, d.path, d.status, d.indexed_at\n ORDER BY d.indexed_at DESC\n `);\n const directoryDetails = directoriesDetailStmt.all() as { id: number; path: string; status: 'pending' | 'indexing' | 'completed' | 'failed'; indexed_at: number; files_count: number; chunks_count: number }[];\n \n const directories: DirectoryStatus[] = directoryDetails.map(row => {\n const errorsByDirStmt = sqlite.db.prepare(`\n SELECT errors_json FROM files \n WHERE parent_dirs LIKE '%\"' || ? || '\"%' AND errors_json IS NOT NULL\n `);\n const dirErrors = errorsByDirStmt.all(row.path) as { errors_json: string }[];\n \n const dirErrorsList: string[] = [];\n dirErrors.forEach(errorRow => {\n try {\n const errors = JSON.parse(errorRow.errors_json);\n dirErrorsList.push(...errors);\n } catch {\n dirErrorsList.push('Failed to parse error JSON');\n }\n });\n \n return {\n path: row.path,\n status: row.status,\n filesCount: row.files_count,\n chunksCount: row.chunks_count,\n lastIndexed: row.indexed_at && row.indexed_at > 0 ? new Date(row.indexed_at).toISOString() : null,\n errors: dirErrorsList\n };\n });\n \n const qdrantConsistency = await checkQdrantConsistency(sqlite, config);\n const workspaces = await calculateWorkspaceStatistics(sqlite, config);\n \n const fs = await import('fs');\n let databaseSize = '0 KB';\n try {\n const stats = fs.statSync(config.storage.sqlitePath);\n const sizeInBytes = stats.size;\n if (sizeInBytes > 1024 * 1024) {\n databaseSize = `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`;\n } else if (sizeInBytes > 1024) {\n databaseSize = `${(sizeInBytes / 1024).toFixed(2)} KB`;\n } else {\n databaseSize = `${sizeInBytes} bytes`;\n }\n } catch {\n databaseSize = 'Unknown';\n }\n \n return {\n directoriesIndexed: directoriesCount.count,\n filesIndexed: filesCount.count,\n chunksIndexed: chunksCount.count || 0,\n databaseSize,\n lastIndexed: lastIndexedResult.last_indexed ? new Date(lastIndexedResult.last_indexed).toISOString() : null,\n errors: allErrors,\n directories,\n workspaces,\n qdrantConsistency\n };\n } finally {\n sqlite.close();\n }\n}"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "directory-indexer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "AI-powered directory indexing with semantic search for MCP servers",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"build": "vite build",
|
|
12
12
|
"dev": "vite build --watch",
|
|
13
13
|
"test": "vitest run",
|
|
14
|
-
"test:unit": "vitest run tests/unit.test.ts tests/cli.unit.test.ts tests/edge-cases.unit.test.ts tests/error-handling.unit.test.ts tests/providers.unit.test.ts",
|
|
14
|
+
"test:unit": "vitest run tests/unit.test.ts tests/cli.unit.test.ts tests/edge-cases.unit.test.ts tests/error-handling.unit.test.ts tests/providers.unit.test.ts tests/mcp-handlers.test.ts",
|
|
15
15
|
"test:integration": "vitest run tests/integration.test.ts",
|
|
16
16
|
"test:watch": "vitest",
|
|
17
17
|
"test:coverage": "vitest run --coverage",
|