aidex-mcp 1.4.1
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/CHANGELOG.md +128 -0
- package/LICENSE +21 -0
- package/MCP-API-REFERENCE.md +690 -0
- package/README.md +314 -0
- package/build/commands/files.d.ts +28 -0
- package/build/commands/files.js +124 -0
- package/build/commands/index.d.ts +14 -0
- package/build/commands/index.js +14 -0
- package/build/commands/init.d.ts +24 -0
- package/build/commands/init.js +396 -0
- package/build/commands/link.d.ts +45 -0
- package/build/commands/link.js +167 -0
- package/build/commands/note.d.ts +29 -0
- package/build/commands/note.js +105 -0
- package/build/commands/query.d.ts +36 -0
- package/build/commands/query.js +176 -0
- package/build/commands/scan.d.ts +25 -0
- package/build/commands/scan.js +104 -0
- package/build/commands/session.d.ts +52 -0
- package/build/commands/session.js +216 -0
- package/build/commands/signature.d.ts +52 -0
- package/build/commands/signature.js +171 -0
- package/build/commands/summary.d.ts +56 -0
- package/build/commands/summary.js +324 -0
- package/build/commands/update.d.ts +36 -0
- package/build/commands/update.js +273 -0
- package/build/constants.d.ts +10 -0
- package/build/constants.js +10 -0
- package/build/db/database.d.ts +69 -0
- package/build/db/database.js +126 -0
- package/build/db/index.d.ts +7 -0
- package/build/db/index.js +6 -0
- package/build/db/queries.d.ts +163 -0
- package/build/db/queries.js +273 -0
- package/build/db/schema.sql +136 -0
- package/build/index.d.ts +13 -0
- package/build/index.js +74 -0
- package/build/parser/extractor.d.ts +41 -0
- package/build/parser/extractor.js +249 -0
- package/build/parser/index.d.ts +7 -0
- package/build/parser/index.js +7 -0
- package/build/parser/languages/c.d.ts +28 -0
- package/build/parser/languages/c.js +70 -0
- package/build/parser/languages/cpp.d.ts +28 -0
- package/build/parser/languages/cpp.js +91 -0
- package/build/parser/languages/csharp.d.ts +32 -0
- package/build/parser/languages/csharp.js +97 -0
- package/build/parser/languages/go.d.ts +28 -0
- package/build/parser/languages/go.js +83 -0
- package/build/parser/languages/index.d.ts +21 -0
- package/build/parser/languages/index.js +107 -0
- package/build/parser/languages/java.d.ts +28 -0
- package/build/parser/languages/java.js +58 -0
- package/build/parser/languages/php.d.ts +28 -0
- package/build/parser/languages/php.js +75 -0
- package/build/parser/languages/python.d.ts +28 -0
- package/build/parser/languages/python.js +67 -0
- package/build/parser/languages/ruby.d.ts +28 -0
- package/build/parser/languages/ruby.js +68 -0
- package/build/parser/languages/rust.d.ts +28 -0
- package/build/parser/languages/rust.js +73 -0
- package/build/parser/languages/typescript.d.ts +28 -0
- package/build/parser/languages/typescript.js +82 -0
- package/build/parser/tree-sitter.d.ts +30 -0
- package/build/parser/tree-sitter.js +132 -0
- package/build/server/mcp-server.d.ts +7 -0
- package/build/server/mcp-server.js +36 -0
- package/build/server/tools.d.ts +18 -0
- package/build/server/tools.js +1245 -0
- package/build/viewer/git-status.d.ts +25 -0
- package/build/viewer/git-status.js +163 -0
- package/build/viewer/index.d.ts +5 -0
- package/build/viewer/index.js +5 -0
- package/build/viewer/server.d.ts +12 -0
- package/build/viewer/server.js +1122 -0
- package/package.json +66 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* summary and tree commands
|
|
3
|
+
*
|
|
4
|
+
* - summary: Get project summary with auto-detected info
|
|
5
|
+
* - tree: Get indexed file tree with optional stats
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { join, basename } from 'path';
|
|
9
|
+
import { PRODUCT_NAME, INDEX_DIR, TOOL_PREFIX } from '../constants.js';
|
|
10
|
+
import { openDatabase, createQueries } from '../db/index.js';
|
|
11
|
+
// ============================================================
|
|
12
|
+
// Summary implementation
|
|
13
|
+
// ============================================================
|
|
14
|
+
export function summary(params) {
|
|
15
|
+
const { path: projectPath } = params;
|
|
16
|
+
// Validate project path
|
|
17
|
+
const indexDir = join(projectPath, INDEX_DIR);
|
|
18
|
+
const dbPath = join(indexDir, 'index.db');
|
|
19
|
+
if (!existsSync(dbPath)) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
name: '',
|
|
23
|
+
content: '',
|
|
24
|
+
autoGenerated: {
|
|
25
|
+
entryPoints: [],
|
|
26
|
+
mainTypes: [],
|
|
27
|
+
fileCount: 0,
|
|
28
|
+
languages: [],
|
|
29
|
+
},
|
|
30
|
+
error: `No ${PRODUCT_NAME} index found at ${projectPath}. Run ${TOOL_PREFIX}init first.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Open database
|
|
34
|
+
const db = openDatabase(dbPath, true);
|
|
35
|
+
const queries = createQueries(db);
|
|
36
|
+
try {
|
|
37
|
+
const projectName = db.getMetadata('project_name') ?? basename(projectPath);
|
|
38
|
+
// Read summary.md if exists
|
|
39
|
+
const summaryPath = join(indexDir, 'summary.md');
|
|
40
|
+
let content = '';
|
|
41
|
+
if (existsSync(summaryPath)) {
|
|
42
|
+
content = readFileSync(summaryPath, 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
// Auto-detect entry points
|
|
45
|
+
const entryPoints = detectEntryPoints(queries);
|
|
46
|
+
// Get main types (most referenced)
|
|
47
|
+
const mainTypes = getMainTypes(queries);
|
|
48
|
+
// Get file count
|
|
49
|
+
const stats = db.getStats();
|
|
50
|
+
// Detect languages from file extensions
|
|
51
|
+
const languages = detectLanguages(queries);
|
|
52
|
+
db.close();
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
name: projectName,
|
|
56
|
+
content,
|
|
57
|
+
autoGenerated: {
|
|
58
|
+
entryPoints,
|
|
59
|
+
mainTypes,
|
|
60
|
+
fileCount: stats.files,
|
|
61
|
+
languages,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
db.close();
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
name: '',
|
|
70
|
+
content: '',
|
|
71
|
+
autoGenerated: {
|
|
72
|
+
entryPoints: [],
|
|
73
|
+
mainTypes: [],
|
|
74
|
+
fileCount: 0,
|
|
75
|
+
languages: [],
|
|
76
|
+
},
|
|
77
|
+
error: err instanceof Error ? err.message : String(err),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Detect entry points based on common patterns
|
|
83
|
+
*/
|
|
84
|
+
function detectEntryPoints(queries) {
|
|
85
|
+
const files = queries.getAllFiles();
|
|
86
|
+
const entryPatterns = [
|
|
87
|
+
/^(program|main|index|app|application)\.(cs|ts|js|py|rs)$/i,
|
|
88
|
+
/^src\/(program|main|index|app)\.(cs|ts|js|py|rs)$/i,
|
|
89
|
+
/^src\/(main|lib)\.(rs)$/i,
|
|
90
|
+
];
|
|
91
|
+
const entryPoints = [];
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
const fileName = file.path.replace(/\\/g, '/');
|
|
94
|
+
for (const pattern of entryPatterns) {
|
|
95
|
+
if (pattern.test(fileName) || pattern.test(basename(fileName))) {
|
|
96
|
+
entryPoints.push(file.path);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return entryPoints;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get main types (classes/interfaces with most methods)
|
|
105
|
+
*/
|
|
106
|
+
function getMainTypes(queries) {
|
|
107
|
+
const files = queries.getAllFiles();
|
|
108
|
+
const typeMethodCounts = [];
|
|
109
|
+
for (const file of files) {
|
|
110
|
+
const types = queries.getTypesByFile(file.id);
|
|
111
|
+
const methods = queries.getMethodsByFile(file.id);
|
|
112
|
+
for (const type of types) {
|
|
113
|
+
// Count methods that likely belong to this type (same file, after type definition)
|
|
114
|
+
const methodCount = methods.filter(m => m.line_number > type.line_number).length;
|
|
115
|
+
typeMethodCounts.push({
|
|
116
|
+
name: type.name,
|
|
117
|
+
file: file.path,
|
|
118
|
+
methodCount,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Sort by method count and return top 5
|
|
123
|
+
typeMethodCounts.sort((a, b) => b.methodCount - a.methodCount);
|
|
124
|
+
return typeMethodCounts.slice(0, 5).map(t => `${t.name} (${t.file})`);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Detect languages from indexed file extensions
|
|
128
|
+
*/
|
|
129
|
+
function detectLanguages(queries) {
|
|
130
|
+
const files = queries.getAllFiles();
|
|
131
|
+
const extensionMap = {
|
|
132
|
+
'.cs': 'C#',
|
|
133
|
+
'.ts': 'TypeScript',
|
|
134
|
+
'.tsx': 'TypeScript',
|
|
135
|
+
'.js': 'JavaScript',
|
|
136
|
+
'.jsx': 'JavaScript',
|
|
137
|
+
'.mjs': 'JavaScript',
|
|
138
|
+
'.cjs': 'JavaScript',
|
|
139
|
+
'.rs': 'Rust',
|
|
140
|
+
'.py': 'Python',
|
|
141
|
+
'.pyw': 'Python',
|
|
142
|
+
};
|
|
143
|
+
const languages = new Set();
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
const ext = file.path.substring(file.path.lastIndexOf('.')).toLowerCase();
|
|
146
|
+
if (extensionMap[ext]) {
|
|
147
|
+
languages.add(extensionMap[ext]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return [...languages].sort();
|
|
151
|
+
}
|
|
152
|
+
// ============================================================
|
|
153
|
+
// Tree implementation
|
|
154
|
+
// ============================================================
|
|
155
|
+
export function tree(params) {
|
|
156
|
+
const { path: projectPath, subpath, depth, includeStats } = params;
|
|
157
|
+
// Validate project path
|
|
158
|
+
const indexDir = join(projectPath, INDEX_DIR);
|
|
159
|
+
const dbPath = join(indexDir, 'index.db');
|
|
160
|
+
if (!existsSync(dbPath)) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
root: '',
|
|
164
|
+
entries: [],
|
|
165
|
+
totalFiles: 0,
|
|
166
|
+
error: `No ${PRODUCT_NAME} index found at ${projectPath}. Run ${TOOL_PREFIX}init first.`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// Open database
|
|
170
|
+
const db = openDatabase(dbPath, true);
|
|
171
|
+
const queries = createQueries(db);
|
|
172
|
+
try {
|
|
173
|
+
const files = queries.getAllFiles();
|
|
174
|
+
const normalizedSubpath = subpath?.replace(/\\/g, '/').replace(/^\/|\/$/g, '') ?? '';
|
|
175
|
+
// Filter files by subpath
|
|
176
|
+
let filteredFiles = files;
|
|
177
|
+
if (normalizedSubpath) {
|
|
178
|
+
filteredFiles = files.filter(f => {
|
|
179
|
+
const normalizedPath = f.path.replace(/\\/g, '/');
|
|
180
|
+
return normalizedPath.startsWith(normalizedSubpath + '/') ||
|
|
181
|
+
normalizedPath === normalizedSubpath;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// Build tree structure
|
|
185
|
+
const directories = new Set();
|
|
186
|
+
const entries = [];
|
|
187
|
+
for (const file of filteredFiles) {
|
|
188
|
+
const normalizedPath = file.path.replace(/\\/g, '/');
|
|
189
|
+
const relativePath = normalizedSubpath
|
|
190
|
+
? normalizedPath.substring(normalizedSubpath.length + 1)
|
|
191
|
+
: normalizedPath;
|
|
192
|
+
// Check depth
|
|
193
|
+
const pathDepth = relativePath.split('/').length;
|
|
194
|
+
if (depth !== undefined && pathDepth > depth) {
|
|
195
|
+
// Just add parent directories up to depth
|
|
196
|
+
const parts = relativePath.split('/');
|
|
197
|
+
for (let i = 0; i < Math.min(depth, parts.length - 1); i++) {
|
|
198
|
+
const dirPath = parts.slice(0, i + 1).join('/');
|
|
199
|
+
directories.add(dirPath);
|
|
200
|
+
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
// Add parent directories
|
|
204
|
+
const parts = relativePath.split('/');
|
|
205
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
206
|
+
const dirPath = parts.slice(0, i + 1).join('/');
|
|
207
|
+
directories.add(dirPath);
|
|
208
|
+
}
|
|
209
|
+
// Add file entry
|
|
210
|
+
const entry = {
|
|
211
|
+
path: relativePath,
|
|
212
|
+
type: 'file',
|
|
213
|
+
};
|
|
214
|
+
if (includeStats) {
|
|
215
|
+
const occurrences = queries.getOccurrencesByFile(file.id);
|
|
216
|
+
const methods = queries.getMethodsByFile(file.id);
|
|
217
|
+
const types = queries.getTypesByFile(file.id);
|
|
218
|
+
entry.itemCount = new Set(occurrences.map(o => o.item_id)).size;
|
|
219
|
+
entry.methodCount = methods.length;
|
|
220
|
+
entry.typeCount = types.length;
|
|
221
|
+
}
|
|
222
|
+
entries.push(entry);
|
|
223
|
+
}
|
|
224
|
+
// Add directory entries
|
|
225
|
+
for (const dir of directories) {
|
|
226
|
+
entries.push({
|
|
227
|
+
path: dir,
|
|
228
|
+
type: 'directory',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// Sort: directories first, then alphabetically
|
|
232
|
+
entries.sort((a, b) => {
|
|
233
|
+
if (a.type !== b.type) {
|
|
234
|
+
return a.type === 'directory' ? -1 : 1;
|
|
235
|
+
}
|
|
236
|
+
return a.path.localeCompare(b.path);
|
|
237
|
+
});
|
|
238
|
+
db.close();
|
|
239
|
+
return {
|
|
240
|
+
success: true,
|
|
241
|
+
root: normalizedSubpath || '.',
|
|
242
|
+
entries,
|
|
243
|
+
totalFiles: filteredFiles.length,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
db.close();
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
root: '',
|
|
251
|
+
entries: [],
|
|
252
|
+
totalFiles: 0,
|
|
253
|
+
error: err instanceof Error ? err.message : String(err),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
export function describe(params) {
|
|
258
|
+
const { path: projectPath, section, content, replace = false } = params;
|
|
259
|
+
// Validate project path
|
|
260
|
+
const indexDir = join(projectPath, INDEX_DIR);
|
|
261
|
+
const dbPath = join(indexDir, 'index.db');
|
|
262
|
+
if (!existsSync(dbPath)) {
|
|
263
|
+
return {
|
|
264
|
+
success: false,
|
|
265
|
+
section,
|
|
266
|
+
error: `No ${PRODUCT_NAME} index found at ${projectPath}. Run ${TOOL_PREFIX}init first.`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const summaryPath = join(indexDir, 'summary.md');
|
|
270
|
+
try {
|
|
271
|
+
// Read existing summary or create new
|
|
272
|
+
let summaryContent = '';
|
|
273
|
+
if (existsSync(summaryPath)) {
|
|
274
|
+
summaryContent = readFileSync(summaryPath, 'utf-8');
|
|
275
|
+
}
|
|
276
|
+
// Section headers
|
|
277
|
+
const sectionHeaders = {
|
|
278
|
+
purpose: '## Purpose',
|
|
279
|
+
architecture: '## Architecture',
|
|
280
|
+
concepts: '## Key Concepts',
|
|
281
|
+
patterns: '## Patterns',
|
|
282
|
+
notes: '## Notes',
|
|
283
|
+
};
|
|
284
|
+
const header = sectionHeaders[section];
|
|
285
|
+
const sectionRegex = new RegExp(`^${header}\\n([\\s\\S]*?)(?=^## |$)`, 'm');
|
|
286
|
+
if (replace) {
|
|
287
|
+
// Replace entire section
|
|
288
|
+
if (sectionRegex.test(summaryContent)) {
|
|
289
|
+
summaryContent = summaryContent.replace(sectionRegex, `${header}\n${content}\n\n`);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
// Add new section at end
|
|
293
|
+
summaryContent = summaryContent.trimEnd() + `\n\n${header}\n${content}\n`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
// Append to section
|
|
298
|
+
const match = summaryContent.match(sectionRegex);
|
|
299
|
+
if (match) {
|
|
300
|
+
const existingContent = match[1].trimEnd();
|
|
301
|
+
const newContent = existingContent ? `${existingContent}\n${content}` : content;
|
|
302
|
+
summaryContent = summaryContent.replace(sectionRegex, `${header}\n${newContent}\n\n`);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
// Add new section at end
|
|
306
|
+
summaryContent = summaryContent.trimEnd() + `\n\n${header}\n${content}\n`;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Write back
|
|
310
|
+
writeFileSync(summaryPath, summaryContent.trimStart());
|
|
311
|
+
return {
|
|
312
|
+
success: true,
|
|
313
|
+
section,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
return {
|
|
318
|
+
success: false,
|
|
319
|
+
section,
|
|
320
|
+
error: err instanceof Error ? err.message : String(err),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* update command - Update index for a single file
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Full re-index of a file (no line range specified)
|
|
6
|
+
* - Incremental update of a line range (from_line/to_line specified) - future
|
|
7
|
+
*/
|
|
8
|
+
export interface UpdateParams {
|
|
9
|
+
path: string;
|
|
10
|
+
file: string;
|
|
11
|
+
fromLine?: number;
|
|
12
|
+
toLine?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface UpdateResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
file: string;
|
|
17
|
+
itemsAdded: number;
|
|
18
|
+
itemsRemoved: number;
|
|
19
|
+
methodsUpdated: number;
|
|
20
|
+
typesUpdated: number;
|
|
21
|
+
durationMs: number;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function update(params: UpdateParams): UpdateResult;
|
|
25
|
+
export interface RemoveParams {
|
|
26
|
+
path: string;
|
|
27
|
+
file: string;
|
|
28
|
+
}
|
|
29
|
+
export interface RemoveResult {
|
|
30
|
+
success: boolean;
|
|
31
|
+
file: string;
|
|
32
|
+
removed: boolean;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function remove(params: RemoveParams): RemoveResult;
|
|
36
|
+
//# sourceMappingURL=update.d.ts.map
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* update command - Update index for a single file
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Full re-index of a file (no line range specified)
|
|
6
|
+
* - Incremental update of a line range (from_line/to_line specified) - future
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
import { PRODUCT_NAME, INDEX_DIR, TOOL_PREFIX } from '../constants.js';
|
|
12
|
+
import { openDatabase, createQueries } from '../db/index.js';
|
|
13
|
+
import { extract } from '../parser/index.js';
|
|
14
|
+
// ============================================================
|
|
15
|
+
// Main update function
|
|
16
|
+
// ============================================================
|
|
17
|
+
export function update(params) {
|
|
18
|
+
const startTime = Date.now();
|
|
19
|
+
const { path: projectPath } = params;
|
|
20
|
+
// Normalize path to forward slashes (consistent with how paths are stored)
|
|
21
|
+
const relativePath = params.file.replace(/\\/g, '/');
|
|
22
|
+
// Validate project path
|
|
23
|
+
const indexDir = join(projectPath, INDEX_DIR);
|
|
24
|
+
const dbPath = join(indexDir, 'index.db');
|
|
25
|
+
if (!existsSync(dbPath)) {
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
file: relativePath,
|
|
29
|
+
itemsAdded: 0,
|
|
30
|
+
itemsRemoved: 0,
|
|
31
|
+
methodsUpdated: 0,
|
|
32
|
+
typesUpdated: 0,
|
|
33
|
+
durationMs: Date.now() - startTime,
|
|
34
|
+
error: `No ${PRODUCT_NAME} index found at ${projectPath}. Run ${TOOL_PREFIX}init first.`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Check if file exists
|
|
38
|
+
const absolutePath = join(projectPath, relativePath);
|
|
39
|
+
if (!existsSync(absolutePath)) {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
file: relativePath,
|
|
43
|
+
itemsAdded: 0,
|
|
44
|
+
itemsRemoved: 0,
|
|
45
|
+
methodsUpdated: 0,
|
|
46
|
+
typesUpdated: 0,
|
|
47
|
+
durationMs: Date.now() - startTime,
|
|
48
|
+
error: `File does not exist: ${relativePath}`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Open database
|
|
52
|
+
const db = openDatabase(dbPath);
|
|
53
|
+
const queries = createQueries(db);
|
|
54
|
+
try {
|
|
55
|
+
// Check if file is already indexed
|
|
56
|
+
const existingFile = queries.getFileByPath(relativePath);
|
|
57
|
+
// Read file content
|
|
58
|
+
let content;
|
|
59
|
+
try {
|
|
60
|
+
content = readFileSync(absolutePath, 'utf-8');
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
file: relativePath,
|
|
66
|
+
itemsAdded: 0,
|
|
67
|
+
itemsRemoved: 0,
|
|
68
|
+
methodsUpdated: 0,
|
|
69
|
+
typesUpdated: 0,
|
|
70
|
+
durationMs: Date.now() - startTime,
|
|
71
|
+
error: `Cannot read file: ${err instanceof Error ? err.message : String(err)}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// Calculate new hash
|
|
75
|
+
const newHash = createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
76
|
+
// Check if file has actually changed
|
|
77
|
+
if (existingFile && existingFile.hash === newHash) {
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
file: relativePath,
|
|
81
|
+
itemsAdded: 0,
|
|
82
|
+
itemsRemoved: 0,
|
|
83
|
+
methodsUpdated: 0,
|
|
84
|
+
typesUpdated: 0,
|
|
85
|
+
durationMs: Date.now() - startTime,
|
|
86
|
+
error: 'File unchanged (hash match)',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Extract data from file
|
|
90
|
+
const extraction = extract(content, relativePath);
|
|
91
|
+
if (!extraction) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
file: relativePath,
|
|
95
|
+
itemsAdded: 0,
|
|
96
|
+
itemsRemoved: 0,
|
|
97
|
+
methodsUpdated: 0,
|
|
98
|
+
typesUpdated: 0,
|
|
99
|
+
durationMs: Date.now() - startTime,
|
|
100
|
+
error: 'Unsupported file type or parse error',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// Count old items for comparison
|
|
104
|
+
let oldItemCount = 0;
|
|
105
|
+
let oldMethodCount = 0;
|
|
106
|
+
let oldTypeCount = 0;
|
|
107
|
+
if (existingFile) {
|
|
108
|
+
const oldOccurrences = queries.getOccurrencesByFile(existingFile.id);
|
|
109
|
+
oldItemCount = new Set(oldOccurrences.map(o => o.item_id)).size;
|
|
110
|
+
oldMethodCount = queries.getMethodsByFile(existingFile.id).length;
|
|
111
|
+
oldTypeCount = queries.getTypesByFile(existingFile.id).length;
|
|
112
|
+
}
|
|
113
|
+
// Split content into lines for hashing
|
|
114
|
+
const contentLines = content.split('\n');
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
// Build map of old line hashes to modified timestamps (for diff tracking)
|
|
117
|
+
// Key is the hash, not line_number - so moved lines keep their timestamp
|
|
118
|
+
const oldHashToModified = new Map();
|
|
119
|
+
if (existingFile) {
|
|
120
|
+
const oldLines = queries.getLinesByFile(existingFile.id);
|
|
121
|
+
for (const line of oldLines) {
|
|
122
|
+
if (line.line_hash && line.modified) {
|
|
123
|
+
// If same hash appears multiple times, keep the oldest timestamp
|
|
124
|
+
const existing = oldHashToModified.get(line.line_hash);
|
|
125
|
+
if (!existing || line.modified < existing) {
|
|
126
|
+
oldHashToModified.set(line.line_hash, line.modified);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Perform update in transaction
|
|
132
|
+
let fileId;
|
|
133
|
+
let newItemCount = 0;
|
|
134
|
+
db.transaction(() => {
|
|
135
|
+
if (existingFile) {
|
|
136
|
+
// Clear existing data for this file
|
|
137
|
+
queries.clearFileData(existingFile.id);
|
|
138
|
+
// Update hash
|
|
139
|
+
queries.updateFileHash(existingFile.id, newHash);
|
|
140
|
+
fileId = existingFile.id;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Insert new file record
|
|
144
|
+
fileId = queries.insertFile(relativePath, newHash);
|
|
145
|
+
}
|
|
146
|
+
// Insert lines with diff tracking
|
|
147
|
+
let lineId = 1;
|
|
148
|
+
for (const line of extraction.lines) {
|
|
149
|
+
const lineContent = contentLines[line.lineNumber - 1] ?? '';
|
|
150
|
+
const lineHash = createHash('sha256').update(lineContent).digest('hex').substring(0, 16);
|
|
151
|
+
// Check if this hash existed before (regardless of line number)
|
|
152
|
+
const oldModified = oldHashToModified.get(lineHash);
|
|
153
|
+
const modified = oldModified ?? now; // Keep old timestamp if hash existed
|
|
154
|
+
queries.insertLine(fileId, lineId++, line.lineNumber, line.lineType, lineHash, modified);
|
|
155
|
+
}
|
|
156
|
+
// Build line number to line ID mapping
|
|
157
|
+
const lineNumberToId = new Map();
|
|
158
|
+
lineId = 1;
|
|
159
|
+
for (const line of extraction.lines) {
|
|
160
|
+
lineNumberToId.set(line.lineNumber, lineId++);
|
|
161
|
+
}
|
|
162
|
+
// Insert items and occurrences
|
|
163
|
+
const itemsInserted = new Set();
|
|
164
|
+
for (const item of extraction.items) {
|
|
165
|
+
let itemLineId = lineNumberToId.get(item.lineNumber);
|
|
166
|
+
if (itemLineId === undefined) {
|
|
167
|
+
// Line wasn't recorded, add it now
|
|
168
|
+
const newLineId = lineId++;
|
|
169
|
+
const lineContent = contentLines[item.lineNumber - 1] ?? '';
|
|
170
|
+
const lineHash = createHash('sha256').update(lineContent).digest('hex').substring(0, 16);
|
|
171
|
+
const oldModified = oldHashToModified.get(lineHash);
|
|
172
|
+
const modified = oldModified ?? now;
|
|
173
|
+
queries.insertLine(fileId, newLineId, item.lineNumber, item.lineType, lineHash, modified);
|
|
174
|
+
lineNumberToId.set(item.lineNumber, newLineId);
|
|
175
|
+
itemLineId = newLineId;
|
|
176
|
+
}
|
|
177
|
+
const itemId = queries.getOrCreateItem(item.term);
|
|
178
|
+
queries.insertOccurrence(itemId, fileId, itemLineId);
|
|
179
|
+
itemsInserted.add(item.term);
|
|
180
|
+
}
|
|
181
|
+
newItemCount = itemsInserted.size;
|
|
182
|
+
// Insert methods
|
|
183
|
+
for (const method of extraction.methods) {
|
|
184
|
+
queries.insertMethod(fileId, method.name, method.prototype, method.lineNumber, method.visibility, method.isStatic, method.isAsync);
|
|
185
|
+
}
|
|
186
|
+
// Insert types
|
|
187
|
+
for (const type of extraction.types) {
|
|
188
|
+
queries.insertType(fileId, type.name, type.kind, type.lineNumber);
|
|
189
|
+
}
|
|
190
|
+
// Insert signature (header comments)
|
|
191
|
+
if (extraction.headerComments.length > 0) {
|
|
192
|
+
queries.insertSignature(fileId, extraction.headerComments.join('\n'));
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
// Cleanup unused items
|
|
196
|
+
queries.deleteUnusedItems();
|
|
197
|
+
db.close();
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
file: relativePath,
|
|
201
|
+
itemsAdded: Math.max(0, newItemCount - oldItemCount),
|
|
202
|
+
itemsRemoved: Math.max(0, oldItemCount - newItemCount),
|
|
203
|
+
methodsUpdated: extraction.methods.length,
|
|
204
|
+
typesUpdated: extraction.types.length,
|
|
205
|
+
durationMs: Date.now() - startTime,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
db.close();
|
|
210
|
+
return {
|
|
211
|
+
success: false,
|
|
212
|
+
file: relativePath,
|
|
213
|
+
itemsAdded: 0,
|
|
214
|
+
itemsRemoved: 0,
|
|
215
|
+
methodsUpdated: 0,
|
|
216
|
+
typesUpdated: 0,
|
|
217
|
+
durationMs: Date.now() - startTime,
|
|
218
|
+
error: err instanceof Error ? err.message : String(err),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export function remove(params) {
|
|
223
|
+
const { path: projectPath } = params;
|
|
224
|
+
// Normalize path to forward slashes
|
|
225
|
+
const relativePath = params.file.replace(/\\/g, '/');
|
|
226
|
+
// Validate project path
|
|
227
|
+
const indexDir = join(projectPath, INDEX_DIR);
|
|
228
|
+
const dbPath = join(indexDir, 'index.db');
|
|
229
|
+
if (!existsSync(dbPath)) {
|
|
230
|
+
return {
|
|
231
|
+
success: false,
|
|
232
|
+
file: relativePath,
|
|
233
|
+
removed: false,
|
|
234
|
+
error: `No ${PRODUCT_NAME} index found at ${projectPath}. Run ${TOOL_PREFIX}init first.`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
// Open database
|
|
238
|
+
const db = openDatabase(dbPath);
|
|
239
|
+
const queries = createQueries(db);
|
|
240
|
+
try {
|
|
241
|
+
const existingFile = queries.getFileByPath(relativePath);
|
|
242
|
+
if (!existingFile) {
|
|
243
|
+
db.close();
|
|
244
|
+
return {
|
|
245
|
+
success: true,
|
|
246
|
+
file: relativePath,
|
|
247
|
+
removed: false,
|
|
248
|
+
error: 'File not found in index',
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
// Delete file (CASCADE will handle related data)
|
|
252
|
+
db.transaction(() => {
|
|
253
|
+
queries.deleteFile(existingFile.id);
|
|
254
|
+
queries.deleteUnusedItems();
|
|
255
|
+
});
|
|
256
|
+
db.close();
|
|
257
|
+
return {
|
|
258
|
+
success: true,
|
|
259
|
+
file: relativePath,
|
|
260
|
+
removed: true,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
db.close();
|
|
265
|
+
return {
|
|
266
|
+
success: false,
|
|
267
|
+
file: relativePath,
|
|
268
|
+
removed: false,
|
|
269
|
+
error: err instanceof Error ? err.message : String(err),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AiDex - Global constants
|
|
3
|
+
*
|
|
4
|
+
* Change product name here to rename the entire tool.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PRODUCT_NAME = "AiDex";
|
|
7
|
+
export declare const PRODUCT_NAME_LOWER = "aidex";
|
|
8
|
+
export declare const INDEX_DIR = ".aidex";
|
|
9
|
+
export declare const TOOL_PREFIX = "aidex_";
|
|
10
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AiDex - Global constants
|
|
3
|
+
*
|
|
4
|
+
* Change product name here to rename the entire tool.
|
|
5
|
+
*/
|
|
6
|
+
export const PRODUCT_NAME = 'AiDex';
|
|
7
|
+
export const PRODUCT_NAME_LOWER = 'aidex';
|
|
8
|
+
export const INDEX_DIR = '.aidex';
|
|
9
|
+
export const TOOL_PREFIX = 'aidex_';
|
|
10
|
+
//# sourceMappingURL=constants.js.map
|