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,1245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool definitions and handlers for AiDex
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { init, query, signature, signatures, update, remove, summary, tree, describe, link, unlink, listLinks, scan, files, note, session, formatSessionTime, formatDuration } from '../commands/index.js';
|
|
7
|
+
import { openDatabase } from '../db/index.js';
|
|
8
|
+
import { startViewer, stopViewer } from '../viewer/index.js';
|
|
9
|
+
import { PRODUCT_NAME, INDEX_DIR, TOOL_PREFIX } from '../constants.js';
|
|
10
|
+
/**
|
|
11
|
+
* Register all available tools
|
|
12
|
+
*/
|
|
13
|
+
export function registerTools() {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
name: `${TOOL_PREFIX}init`,
|
|
17
|
+
description: `Initialize ${PRODUCT_NAME} indexing for a project. Scans all source files and builds a searchable index of identifiers, methods, types, and signatures.`,
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
path: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Absolute path to the project directory to index',
|
|
24
|
+
},
|
|
25
|
+
name: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Optional project name (defaults to directory name)',
|
|
28
|
+
},
|
|
29
|
+
exclude: {
|
|
30
|
+
type: 'array',
|
|
31
|
+
items: { type: 'string' },
|
|
32
|
+
description: 'Additional glob patterns to exclude (e.g., ["**/test/**"])',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: ['path'],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: `${TOOL_PREFIX}query`,
|
|
40
|
+
description: `Search for terms/identifiers in the ${PRODUCT_NAME} index. Returns file locations where the term appears. PREFERRED over Grep/Glob for code searches when ${INDEX_DIR}/ exists - faster and more precise. Use this instead of grep for finding functions, classes, variables by name.`,
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
path: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
47
|
+
},
|
|
48
|
+
term: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'The term to search for',
|
|
51
|
+
},
|
|
52
|
+
mode: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
enum: ['exact', 'contains', 'starts_with'],
|
|
55
|
+
description: 'Search mode: exact match, contains, or starts_with (default: exact)',
|
|
56
|
+
},
|
|
57
|
+
file_filter: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
description: 'Glob pattern to filter files (e.g., "src/commands/**")',
|
|
60
|
+
},
|
|
61
|
+
type_filter: {
|
|
62
|
+
type: 'array',
|
|
63
|
+
items: { type: 'string' },
|
|
64
|
+
description: 'Filter by line type: code, comment, method, struct, property',
|
|
65
|
+
},
|
|
66
|
+
modified_since: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description: 'Only include lines modified after this time. Supports: "2h" (hours), "30m" (minutes), "1d" (days), "1w" (weeks), or ISO date string',
|
|
69
|
+
},
|
|
70
|
+
modified_before: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description: 'Only include lines modified before this time. Same format as modified_since',
|
|
73
|
+
},
|
|
74
|
+
limit: {
|
|
75
|
+
type: 'number',
|
|
76
|
+
description: 'Maximum number of results (default: 100)',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
required: ['path', 'term'],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: `${TOOL_PREFIX}status`,
|
|
84
|
+
description: `Get ${PRODUCT_NAME} server status and statistics for an indexed project`,
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
path: {
|
|
89
|
+
type: 'string',
|
|
90
|
+
description: `Path to project with ${INDEX_DIR} directory (optional, shows server status if not provided)`,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
required: [],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: `${TOOL_PREFIX}signature`,
|
|
98
|
+
description: 'Get the signature of a single file: header comments, types (classes/structs/interfaces), and method prototypes. Use this INSTEAD of reading entire files when you only need to know what methods/classes exist. Much faster than Read tool for understanding file structure.',
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {
|
|
102
|
+
path: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
105
|
+
},
|
|
106
|
+
file: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'Relative path to the file within the project (e.g., "src/Core/Engine.cs")',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ['path', 'file'],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: `${TOOL_PREFIX}signatures`,
|
|
116
|
+
description: 'Get signatures for multiple files at once using glob pattern or file list. Returns types and method prototypes. Use INSTEAD of reading multiple files when exploring codebase structure. Much more efficient than multiple Read calls.',
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
path: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
123
|
+
},
|
|
124
|
+
pattern: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
description: 'Glob pattern to match files (e.g., "src/Core/**/*.cs", "**/*.ts")',
|
|
127
|
+
},
|
|
128
|
+
files: {
|
|
129
|
+
type: 'array',
|
|
130
|
+
items: { type: 'string' },
|
|
131
|
+
description: 'Explicit list of relative file paths (alternative to pattern)',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
required: ['path'],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: `${TOOL_PREFIX}update`,
|
|
139
|
+
description: `Re-index a single file. Use after editing a file to update the ${PRODUCT_NAME} index. If the file is new, it will be added to the index. If unchanged (same hash), no update is performed.`,
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: {
|
|
143
|
+
path: {
|
|
144
|
+
type: 'string',
|
|
145
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
146
|
+
},
|
|
147
|
+
file: {
|
|
148
|
+
type: 'string',
|
|
149
|
+
description: 'Relative path to the file to update (e.g., "src/Core/Engine.cs")',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
required: ['path', 'file'],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: `${TOOL_PREFIX}remove`,
|
|
157
|
+
description: `Remove a file from the ${PRODUCT_NAME} index. Use when a file has been deleted from the project.`,
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
path: {
|
|
162
|
+
type: 'string',
|
|
163
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
164
|
+
},
|
|
165
|
+
file: {
|
|
166
|
+
type: 'string',
|
|
167
|
+
description: 'Relative path to the file to remove (e.g., "src/OldFile.cs")',
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
required: ['path', 'file'],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: `${TOOL_PREFIX}summary`,
|
|
175
|
+
description: 'Get project summary including auto-detected entry points, main types, and languages. Also returns content from summary.md if it exists.',
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: {
|
|
179
|
+
path: {
|
|
180
|
+
type: 'string',
|
|
181
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
required: ['path'],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: `${TOOL_PREFIX}tree`,
|
|
189
|
+
description: 'Get the indexed file tree. Optionally filter by subdirectory, limit depth, or include statistics per file.',
|
|
190
|
+
inputSchema: {
|
|
191
|
+
type: 'object',
|
|
192
|
+
properties: {
|
|
193
|
+
path: {
|
|
194
|
+
type: 'string',
|
|
195
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
196
|
+
},
|
|
197
|
+
subpath: {
|
|
198
|
+
type: 'string',
|
|
199
|
+
description: 'Subdirectory to list (default: project root)',
|
|
200
|
+
},
|
|
201
|
+
depth: {
|
|
202
|
+
type: 'number',
|
|
203
|
+
description: 'Maximum depth to traverse (default: unlimited)',
|
|
204
|
+
},
|
|
205
|
+
include_stats: {
|
|
206
|
+
type: 'boolean',
|
|
207
|
+
description: 'Include item/method/type counts per file',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
required: ['path'],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: `${TOOL_PREFIX}describe`,
|
|
215
|
+
description: 'Add or update a section in the project summary (summary.md). Use to document project purpose, architecture, key concepts, or patterns.',
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: 'object',
|
|
218
|
+
properties: {
|
|
219
|
+
path: {
|
|
220
|
+
type: 'string',
|
|
221
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
222
|
+
},
|
|
223
|
+
section: {
|
|
224
|
+
type: 'string',
|
|
225
|
+
enum: ['purpose', 'architecture', 'concepts', 'patterns', 'notes'],
|
|
226
|
+
description: 'Section to update',
|
|
227
|
+
},
|
|
228
|
+
content: {
|
|
229
|
+
type: 'string',
|
|
230
|
+
description: 'Content to add to the section',
|
|
231
|
+
},
|
|
232
|
+
replace: {
|
|
233
|
+
type: 'boolean',
|
|
234
|
+
description: 'Replace existing section content (default: append)',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
required: ['path', 'section', 'content'],
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: `${TOOL_PREFIX}link`,
|
|
242
|
+
description: `Link a dependency project to enable cross-project queries. The dependency must have its own ${INDEX_DIR} index.`,
|
|
243
|
+
inputSchema: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
properties: {
|
|
246
|
+
path: {
|
|
247
|
+
type: 'string',
|
|
248
|
+
description: `Path to current project with ${INDEX_DIR} directory`,
|
|
249
|
+
},
|
|
250
|
+
dependency: {
|
|
251
|
+
type: 'string',
|
|
252
|
+
description: 'Path to dependency project to link',
|
|
253
|
+
},
|
|
254
|
+
name: {
|
|
255
|
+
type: 'string',
|
|
256
|
+
description: 'Optional display name for the dependency',
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
required: ['path', 'dependency'],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: `${TOOL_PREFIX}unlink`,
|
|
264
|
+
description: 'Remove a linked dependency project.',
|
|
265
|
+
inputSchema: {
|
|
266
|
+
type: 'object',
|
|
267
|
+
properties: {
|
|
268
|
+
path: {
|
|
269
|
+
type: 'string',
|
|
270
|
+
description: `Path to current project with ${INDEX_DIR} directory`,
|
|
271
|
+
},
|
|
272
|
+
dependency: {
|
|
273
|
+
type: 'string',
|
|
274
|
+
description: 'Path to dependency project to unlink',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
required: ['path', 'dependency'],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: `${TOOL_PREFIX}links`,
|
|
282
|
+
description: 'List all linked dependency projects.',
|
|
283
|
+
inputSchema: {
|
|
284
|
+
type: 'object',
|
|
285
|
+
properties: {
|
|
286
|
+
path: {
|
|
287
|
+
type: 'string',
|
|
288
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
required: ['path'],
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: `${TOOL_PREFIX}scan`,
|
|
296
|
+
description: `Scan a directory tree to find all projects with ${PRODUCT_NAME} indexes (${INDEX_DIR} directories). Use this to discover which projects are already indexed before using Grep/Glob - indexed projects should use ${TOOL_PREFIX}query instead.`,
|
|
297
|
+
inputSchema: {
|
|
298
|
+
type: 'object',
|
|
299
|
+
properties: {
|
|
300
|
+
path: {
|
|
301
|
+
type: 'string',
|
|
302
|
+
description: `Root path to scan for ${INDEX_DIR} directories`,
|
|
303
|
+
},
|
|
304
|
+
max_depth: {
|
|
305
|
+
type: 'number',
|
|
306
|
+
description: 'Maximum directory depth to scan (default: 10)',
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
required: ['path'],
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: `${TOOL_PREFIX}files`,
|
|
314
|
+
description: 'List all files and directories in the indexed project. Returns the complete project structure with file types (code, config, doc, asset, test, other) and whether each file is indexed for code search. Use modified_since to find files changed in this session.',
|
|
315
|
+
inputSchema: {
|
|
316
|
+
type: 'object',
|
|
317
|
+
properties: {
|
|
318
|
+
path: {
|
|
319
|
+
type: 'string',
|
|
320
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
321
|
+
},
|
|
322
|
+
type: {
|
|
323
|
+
type: 'string',
|
|
324
|
+
enum: ['dir', 'code', 'config', 'doc', 'asset', 'test', 'other'],
|
|
325
|
+
description: 'Filter by file type',
|
|
326
|
+
},
|
|
327
|
+
pattern: {
|
|
328
|
+
type: 'string',
|
|
329
|
+
description: 'Glob pattern to filter files (e.g., "src/**/*.ts")',
|
|
330
|
+
},
|
|
331
|
+
modified_since: {
|
|
332
|
+
type: 'string',
|
|
333
|
+
description: 'Only files indexed after this time. Supports: "2h", "30m", "1d", "1w", or ISO date. Use to find files changed this session.',
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
required: ['path'],
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: `${TOOL_PREFIX}note`,
|
|
341
|
+
description: `Read or write a session note for the project. Use this to leave reminders for the next session (e.g., "Test the glob fix", "Refactor X"). Notes persist in the ${PRODUCT_NAME} database and are shown when querying the project.`,
|
|
342
|
+
inputSchema: {
|
|
343
|
+
type: 'object',
|
|
344
|
+
properties: {
|
|
345
|
+
path: {
|
|
346
|
+
type: 'string',
|
|
347
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
348
|
+
},
|
|
349
|
+
note: {
|
|
350
|
+
type: 'string',
|
|
351
|
+
description: 'Note to save. If omitted, reads the current note.',
|
|
352
|
+
},
|
|
353
|
+
append: {
|
|
354
|
+
type: 'boolean',
|
|
355
|
+
description: 'If true, appends to existing note instead of replacing (default: false)',
|
|
356
|
+
},
|
|
357
|
+
clear: {
|
|
358
|
+
type: 'boolean',
|
|
359
|
+
description: 'If true, clears the note (default: false)',
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
required: ['path'],
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: `${TOOL_PREFIX}session`,
|
|
367
|
+
description: `Start or check an ${PRODUCT_NAME} session. Call this at the beginning of a new chat session to: (1) detect files changed externally since last session, (2) auto-reindex modified files, (3) get session note and last session times. Returns info for "What did we do last session?" queries.`,
|
|
368
|
+
inputSchema: {
|
|
369
|
+
type: 'object',
|
|
370
|
+
properties: {
|
|
371
|
+
path: {
|
|
372
|
+
type: 'string',
|
|
373
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
required: ['path'],
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: `${TOOL_PREFIX}viewer`,
|
|
381
|
+
description: 'Open an interactive project tree viewer in the browser. Shows the indexed file structure with clickable nodes - click on a file to see its signature (header comments, types, methods). Uses a local HTTP server with WebSocket for live updates.',
|
|
382
|
+
inputSchema: {
|
|
383
|
+
type: 'object',
|
|
384
|
+
properties: {
|
|
385
|
+
path: {
|
|
386
|
+
type: 'string',
|
|
387
|
+
description: `Path to project with ${INDEX_DIR} directory`,
|
|
388
|
+
},
|
|
389
|
+
action: {
|
|
390
|
+
type: 'string',
|
|
391
|
+
enum: ['open', 'close'],
|
|
392
|
+
description: 'Action to perform: open (default) or close the viewer',
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
required: ['path'],
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
];
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Handle tool calls
|
|
402
|
+
*/
|
|
403
|
+
export async function handleToolCall(name, args) {
|
|
404
|
+
try {
|
|
405
|
+
switch (name) {
|
|
406
|
+
case `${TOOL_PREFIX}init`:
|
|
407
|
+
return await handleInit(args);
|
|
408
|
+
case `${TOOL_PREFIX}query`:
|
|
409
|
+
return handleQuery(args);
|
|
410
|
+
case `${TOOL_PREFIX}status`:
|
|
411
|
+
return handleStatus(args);
|
|
412
|
+
case `${TOOL_PREFIX}signature`:
|
|
413
|
+
return handleSignature(args);
|
|
414
|
+
case `${TOOL_PREFIX}signatures`:
|
|
415
|
+
return handleSignatures(args);
|
|
416
|
+
case `${TOOL_PREFIX}update`:
|
|
417
|
+
return handleUpdate(args);
|
|
418
|
+
case `${TOOL_PREFIX}remove`:
|
|
419
|
+
return handleRemove(args);
|
|
420
|
+
case `${TOOL_PREFIX}summary`:
|
|
421
|
+
return handleSummary(args);
|
|
422
|
+
case `${TOOL_PREFIX}tree`:
|
|
423
|
+
return handleTree(args);
|
|
424
|
+
case `${TOOL_PREFIX}describe`:
|
|
425
|
+
return handleDescribe(args);
|
|
426
|
+
case `${TOOL_PREFIX}link`:
|
|
427
|
+
return handleLink(args);
|
|
428
|
+
case `${TOOL_PREFIX}unlink`:
|
|
429
|
+
return handleUnlink(args);
|
|
430
|
+
case `${TOOL_PREFIX}links`:
|
|
431
|
+
return handleLinks(args);
|
|
432
|
+
case `${TOOL_PREFIX}scan`:
|
|
433
|
+
return handleScan(args);
|
|
434
|
+
case `${TOOL_PREFIX}files`:
|
|
435
|
+
return handleFiles(args);
|
|
436
|
+
case `${TOOL_PREFIX}note`:
|
|
437
|
+
return handleNote(args);
|
|
438
|
+
case `${TOOL_PREFIX}session`:
|
|
439
|
+
return handleSession(args);
|
|
440
|
+
case `${TOOL_PREFIX}viewer`:
|
|
441
|
+
return handleViewer(args);
|
|
442
|
+
default:
|
|
443
|
+
return {
|
|
444
|
+
content: [
|
|
445
|
+
{
|
|
446
|
+
type: 'text',
|
|
447
|
+
text: `Unknown tool: ${name}`,
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
return {
|
|
455
|
+
content: [
|
|
456
|
+
{
|
|
457
|
+
type: 'text',
|
|
458
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
459
|
+
},
|
|
460
|
+
],
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Handle init
|
|
466
|
+
*/
|
|
467
|
+
async function handleInit(args) {
|
|
468
|
+
const path = args.path;
|
|
469
|
+
if (!path) {
|
|
470
|
+
return {
|
|
471
|
+
content: [{ type: 'text', text: 'Error: path parameter is required' }],
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
const result = await init({
|
|
475
|
+
path,
|
|
476
|
+
name: args.name,
|
|
477
|
+
exclude: args.exclude,
|
|
478
|
+
});
|
|
479
|
+
if (result.success) {
|
|
480
|
+
let message = `ā ${PRODUCT_NAME} initialized for project\n\n`;
|
|
481
|
+
message += `Database: ${result.indexPath}/index.db\n`;
|
|
482
|
+
message += `Files indexed: ${result.filesIndexed}`;
|
|
483
|
+
if (result.filesSkipped > 0) {
|
|
484
|
+
message += ` (${result.filesSkipped} unchanged, skipped)`;
|
|
485
|
+
}
|
|
486
|
+
message += `\n`;
|
|
487
|
+
if (result.filesRemoved > 0) {
|
|
488
|
+
message += `Files removed: ${result.filesRemoved} (now excluded)\n`;
|
|
489
|
+
}
|
|
490
|
+
message += `Items found: ${result.itemsFound}\n`;
|
|
491
|
+
message += `Methods found: ${result.methodsFound}\n`;
|
|
492
|
+
message += `Types found: ${result.typesFound}\n`;
|
|
493
|
+
message += `Duration: ${result.durationMs}ms`;
|
|
494
|
+
if (result.errors.length > 0) {
|
|
495
|
+
message += `\n\nWarnings (${result.errors.length}):\n`;
|
|
496
|
+
message += result.errors.slice(0, 10).map(e => ` - ${e}`).join('\n');
|
|
497
|
+
if (result.errors.length > 10) {
|
|
498
|
+
message += `\n ... and ${result.errors.length - 10} more`;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
content: [{ type: 'text', text: message }],
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
return {
|
|
507
|
+
content: [{ type: 'text', text: `Error: ${result.errors.join(', ')}` }],
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Handle query
|
|
513
|
+
*/
|
|
514
|
+
function handleQuery(args) {
|
|
515
|
+
const path = args.path;
|
|
516
|
+
const term = args.term;
|
|
517
|
+
if (!path || !term) {
|
|
518
|
+
return {
|
|
519
|
+
content: [{ type: 'text', text: 'Error: path and term parameters are required' }],
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const result = query({
|
|
523
|
+
path,
|
|
524
|
+
term,
|
|
525
|
+
mode: args.mode ?? 'exact',
|
|
526
|
+
fileFilter: args.file_filter,
|
|
527
|
+
typeFilter: args.type_filter,
|
|
528
|
+
modifiedSince: args.modified_since,
|
|
529
|
+
modifiedBefore: args.modified_before,
|
|
530
|
+
limit: args.limit,
|
|
531
|
+
});
|
|
532
|
+
if (!result.success) {
|
|
533
|
+
return {
|
|
534
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
if (result.matches.length === 0) {
|
|
538
|
+
return {
|
|
539
|
+
content: [{ type: 'text', text: `No matches found for "${term}" (mode: ${result.mode})` }],
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
// Format results
|
|
543
|
+
let message = `Found ${result.totalMatches} match(es) for "${term}" (mode: ${result.mode})`;
|
|
544
|
+
if (result.truncated) {
|
|
545
|
+
message += ` [showing first ${result.matches.length}]`;
|
|
546
|
+
}
|
|
547
|
+
message += '\n\n';
|
|
548
|
+
// Group by file
|
|
549
|
+
const byFile = new Map();
|
|
550
|
+
for (const match of result.matches) {
|
|
551
|
+
if (!byFile.has(match.file)) {
|
|
552
|
+
byFile.set(match.file, []);
|
|
553
|
+
}
|
|
554
|
+
byFile.get(match.file).push({ lineNumber: match.lineNumber, lineType: match.lineType });
|
|
555
|
+
}
|
|
556
|
+
for (const [file, lines] of byFile) {
|
|
557
|
+
message += `${file}\n`;
|
|
558
|
+
for (const line of lines) {
|
|
559
|
+
message += ` :${line.lineNumber} (${line.lineType})\n`;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return {
|
|
563
|
+
content: [{ type: 'text', text: message.trimEnd() }],
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Handle status
|
|
568
|
+
*/
|
|
569
|
+
function handleStatus(args) {
|
|
570
|
+
const path = args.path;
|
|
571
|
+
if (!path) {
|
|
572
|
+
return {
|
|
573
|
+
content: [
|
|
574
|
+
{
|
|
575
|
+
type: 'text',
|
|
576
|
+
text: JSON.stringify({
|
|
577
|
+
status: 'running',
|
|
578
|
+
version: '0.1.0',
|
|
579
|
+
message: `${PRODUCT_NAME} MCP server is running. Use ${TOOL_PREFIX}init to index a project.`,
|
|
580
|
+
}, null, 2),
|
|
581
|
+
},
|
|
582
|
+
],
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
// Check if project has index
|
|
586
|
+
const indexDir = join(path, INDEX_DIR);
|
|
587
|
+
const dbPath = join(indexDir, 'index.db');
|
|
588
|
+
if (!existsSync(dbPath)) {
|
|
589
|
+
return {
|
|
590
|
+
content: [
|
|
591
|
+
{
|
|
592
|
+
type: 'text',
|
|
593
|
+
text: `No ${PRODUCT_NAME} index found at ${path}. Run ${TOOL_PREFIX}init first.`,
|
|
594
|
+
},
|
|
595
|
+
],
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
// Open database and get stats
|
|
599
|
+
const db = openDatabase(dbPath, true);
|
|
600
|
+
const stats = db.getStats();
|
|
601
|
+
const projectName = db.getMetadata('project_name') ?? 'Unknown';
|
|
602
|
+
const schemaVersion = db.getMetadata('schema_version') ?? 'Unknown';
|
|
603
|
+
db.close();
|
|
604
|
+
return {
|
|
605
|
+
content: [
|
|
606
|
+
{
|
|
607
|
+
type: 'text',
|
|
608
|
+
text: JSON.stringify({
|
|
609
|
+
project: projectName,
|
|
610
|
+
schemaVersion,
|
|
611
|
+
statistics: stats,
|
|
612
|
+
databasePath: dbPath,
|
|
613
|
+
}, null, 2),
|
|
614
|
+
},
|
|
615
|
+
],
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Handle signature
|
|
620
|
+
*/
|
|
621
|
+
function handleSignature(args) {
|
|
622
|
+
const path = args.path;
|
|
623
|
+
const file = args.file;
|
|
624
|
+
if (!path || !file) {
|
|
625
|
+
return {
|
|
626
|
+
content: [{ type: 'text', text: 'Error: path and file parameters are required' }],
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
const result = signature({ path, file });
|
|
630
|
+
if (!result.success) {
|
|
631
|
+
return {
|
|
632
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
// Format output
|
|
636
|
+
let message = `# Signature: ${result.file}\n\n`;
|
|
637
|
+
// Header comments
|
|
638
|
+
if (result.headerComments) {
|
|
639
|
+
message += `## Header Comments\n\`\`\`\n${result.headerComments}\n\`\`\`\n\n`;
|
|
640
|
+
}
|
|
641
|
+
// Types
|
|
642
|
+
if (result.types.length > 0) {
|
|
643
|
+
message += `## Types (${result.types.length})\n`;
|
|
644
|
+
for (const t of result.types) {
|
|
645
|
+
message += `- **${t.kind}** \`${t.name}\` (line ${t.lineNumber})\n`;
|
|
646
|
+
}
|
|
647
|
+
message += '\n';
|
|
648
|
+
}
|
|
649
|
+
// Methods
|
|
650
|
+
if (result.methods.length > 0) {
|
|
651
|
+
message += `## Methods (${result.methods.length})\n`;
|
|
652
|
+
for (const m of result.methods) {
|
|
653
|
+
const modifiers = [];
|
|
654
|
+
if (m.visibility)
|
|
655
|
+
modifiers.push(m.visibility);
|
|
656
|
+
if (m.isStatic)
|
|
657
|
+
modifiers.push('static');
|
|
658
|
+
if (m.isAsync)
|
|
659
|
+
modifiers.push('async');
|
|
660
|
+
const prefix = modifiers.length > 0 ? `[${modifiers.join(' ')}] ` : '';
|
|
661
|
+
message += `- ${prefix}\`${m.prototype}\` (line ${m.lineNumber})\n`;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (result.types.length === 0 && result.methods.length === 0 && !result.headerComments) {
|
|
665
|
+
message += '_No signature data found for this file._\n';
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
content: [{ type: 'text', text: message.trimEnd() }],
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Handle signatures
|
|
673
|
+
*/
|
|
674
|
+
function handleSignatures(args) {
|
|
675
|
+
const path = args.path;
|
|
676
|
+
const pattern = args.pattern;
|
|
677
|
+
const files = args.files;
|
|
678
|
+
if (!path) {
|
|
679
|
+
return {
|
|
680
|
+
content: [{ type: 'text', text: 'Error: path parameter is required' }],
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
if (!pattern && (!files || files.length === 0)) {
|
|
684
|
+
return {
|
|
685
|
+
content: [{ type: 'text', text: 'Error: either pattern or files parameter is required' }],
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
const result = signatures({ path, pattern, files });
|
|
689
|
+
if (!result.success) {
|
|
690
|
+
return {
|
|
691
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
if (result.signatures.length === 0) {
|
|
695
|
+
const searchDesc = pattern ? `pattern "${pattern}"` : `files list`;
|
|
696
|
+
return {
|
|
697
|
+
content: [{ type: 'text', text: `No files found matching ${searchDesc}` }],
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
// Format output - summary view
|
|
701
|
+
let message = `# Signatures (${result.totalFiles} files)\n\n`;
|
|
702
|
+
for (const sig of result.signatures) {
|
|
703
|
+
if (!sig.success) {
|
|
704
|
+
message += `## ${sig.file}\n_Error: ${sig.error}_\n\n`;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
message += `## ${sig.file}\n`;
|
|
708
|
+
// Compact summary
|
|
709
|
+
const parts = [];
|
|
710
|
+
if (sig.types.length > 0) {
|
|
711
|
+
const typesSummary = sig.types.map(t => `${t.kind} ${t.name}`).join(', ');
|
|
712
|
+
parts.push(`Types: ${typesSummary}`);
|
|
713
|
+
}
|
|
714
|
+
if (sig.methods.length > 0) {
|
|
715
|
+
parts.push(`Methods: ${sig.methods.length}`);
|
|
716
|
+
}
|
|
717
|
+
if (parts.length > 0) {
|
|
718
|
+
message += parts.join(' | ') + '\n';
|
|
719
|
+
}
|
|
720
|
+
// List methods compactly
|
|
721
|
+
if (sig.methods.length > 0) {
|
|
722
|
+
for (const m of sig.methods) {
|
|
723
|
+
const modifiers = [];
|
|
724
|
+
if (m.visibility)
|
|
725
|
+
modifiers.push(m.visibility);
|
|
726
|
+
if (m.isStatic)
|
|
727
|
+
modifiers.push('static');
|
|
728
|
+
if (m.isAsync)
|
|
729
|
+
modifiers.push('async');
|
|
730
|
+
const prefix = modifiers.length > 0 ? `[${modifiers.join(' ')}] ` : '';
|
|
731
|
+
message += ` - ${prefix}${m.prototype} :${m.lineNumber}\n`;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
message += '\n';
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
content: [{ type: 'text', text: message.trimEnd() }],
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Handle update
|
|
742
|
+
*/
|
|
743
|
+
function handleUpdate(args) {
|
|
744
|
+
const path = args.path;
|
|
745
|
+
const file = args.file;
|
|
746
|
+
if (!path || !file) {
|
|
747
|
+
return {
|
|
748
|
+
content: [{ type: 'text', text: 'Error: path and file parameters are required' }],
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
const result = update({ path, file });
|
|
752
|
+
if (!result.success) {
|
|
753
|
+
return {
|
|
754
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
// Check if file was unchanged
|
|
758
|
+
if (result.error === 'File unchanged (hash match)') {
|
|
759
|
+
return {
|
|
760
|
+
content: [{ type: 'text', text: `File unchanged: ${result.file} (hash match, no update needed)` }],
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
let message = `ā Updated: ${result.file}\n`;
|
|
764
|
+
message += ` Items: +${result.itemsAdded} / -${result.itemsRemoved}\n`;
|
|
765
|
+
message += ` Methods: ${result.methodsUpdated}\n`;
|
|
766
|
+
message += ` Types: ${result.typesUpdated}\n`;
|
|
767
|
+
message += ` Duration: ${result.durationMs}ms`;
|
|
768
|
+
return {
|
|
769
|
+
content: [{ type: 'text', text: message }],
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Handle remove
|
|
774
|
+
*/
|
|
775
|
+
function handleRemove(args) {
|
|
776
|
+
const path = args.path;
|
|
777
|
+
const file = args.file;
|
|
778
|
+
if (!path || !file) {
|
|
779
|
+
return {
|
|
780
|
+
content: [{ type: 'text', text: 'Error: path and file parameters are required' }],
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
const result = remove({ path, file });
|
|
784
|
+
if (!result.success) {
|
|
785
|
+
return {
|
|
786
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
if (!result.removed) {
|
|
790
|
+
return {
|
|
791
|
+
content: [{ type: 'text', text: `File not in index: ${result.file}` }],
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
return {
|
|
795
|
+
content: [{ type: 'text', text: `ā Removed from index: ${result.file}` }],
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Handle summary
|
|
800
|
+
*/
|
|
801
|
+
function handleSummary(args) {
|
|
802
|
+
const path = args.path;
|
|
803
|
+
if (!path) {
|
|
804
|
+
return {
|
|
805
|
+
content: [{ type: 'text', text: 'Error: path parameter is required' }],
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const result = summary({ path });
|
|
809
|
+
if (!result.success) {
|
|
810
|
+
return {
|
|
811
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
let message = `# Project: ${result.name}\n\n`;
|
|
815
|
+
// Auto-generated info
|
|
816
|
+
message += `## Overview\n`;
|
|
817
|
+
message += `- **Files indexed:** ${result.autoGenerated.fileCount}\n`;
|
|
818
|
+
message += `- **Languages:** ${result.autoGenerated.languages.join(', ') || 'None detected'}\n`;
|
|
819
|
+
if (result.autoGenerated.entryPoints.length > 0) {
|
|
820
|
+
message += `- **Entry points:** ${result.autoGenerated.entryPoints.join(', ')}\n`;
|
|
821
|
+
}
|
|
822
|
+
if (result.autoGenerated.mainTypes.length > 0) {
|
|
823
|
+
message += `\n## Main Types\n`;
|
|
824
|
+
for (const t of result.autoGenerated.mainTypes) {
|
|
825
|
+
message += `- ${t}\n`;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// User-provided summary content
|
|
829
|
+
if (result.content) {
|
|
830
|
+
message += `\n---\n\n${result.content}`;
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
content: [{ type: 'text', text: message.trimEnd() }],
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Handle tree
|
|
838
|
+
*/
|
|
839
|
+
function handleTree(args) {
|
|
840
|
+
const path = args.path;
|
|
841
|
+
if (!path) {
|
|
842
|
+
return {
|
|
843
|
+
content: [{ type: 'text', text: 'Error: path parameter is required' }],
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
const result = tree({
|
|
847
|
+
path,
|
|
848
|
+
subpath: args.subpath,
|
|
849
|
+
depth: args.depth,
|
|
850
|
+
includeStats: args.include_stats,
|
|
851
|
+
});
|
|
852
|
+
if (!result.success) {
|
|
853
|
+
return {
|
|
854
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
if (result.entries.length === 0) {
|
|
858
|
+
return {
|
|
859
|
+
content: [{ type: 'text', text: `No files found in ${result.root}` }],
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
let message = `# File Tree: ${result.root} (${result.totalFiles} files)\n\n`;
|
|
863
|
+
for (const entry of result.entries) {
|
|
864
|
+
if (entry.type === 'directory') {
|
|
865
|
+
message += `š ${entry.path}/\n`;
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
let stats = '';
|
|
869
|
+
if (entry.itemCount !== undefined) {
|
|
870
|
+
stats = ` [${entry.itemCount} items, ${entry.methodCount} methods, ${entry.typeCount} types]`;
|
|
871
|
+
}
|
|
872
|
+
message += ` š ${entry.path}${stats}\n`;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return {
|
|
876
|
+
content: [{ type: 'text', text: message.trimEnd() }],
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Handle describe
|
|
881
|
+
*/
|
|
882
|
+
function handleDescribe(args) {
|
|
883
|
+
const path = args.path;
|
|
884
|
+
const section = args.section;
|
|
885
|
+
const content = args.content;
|
|
886
|
+
if (!path || !section || !content) {
|
|
887
|
+
return {
|
|
888
|
+
content: [{ type: 'text', text: 'Error: path, section, and content parameters are required' }],
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
const validSections = ['purpose', 'architecture', 'concepts', 'patterns', 'notes'];
|
|
892
|
+
if (!validSections.includes(section)) {
|
|
893
|
+
return {
|
|
894
|
+
content: [{ type: 'text', text: `Error: section must be one of: ${validSections.join(', ')}` }],
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
const result = describe({
|
|
898
|
+
path,
|
|
899
|
+
section: section,
|
|
900
|
+
content,
|
|
901
|
+
replace: args.replace,
|
|
902
|
+
});
|
|
903
|
+
if (!result.success) {
|
|
904
|
+
return {
|
|
905
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
return {
|
|
909
|
+
content: [{ type: 'text', text: `ā Updated section: ${result.section}` }],
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Handle link
|
|
914
|
+
*/
|
|
915
|
+
function handleLink(args) {
|
|
916
|
+
const path = args.path;
|
|
917
|
+
const dependency = args.dependency;
|
|
918
|
+
if (!path || !dependency) {
|
|
919
|
+
return {
|
|
920
|
+
content: [{ type: 'text', text: 'Error: path and dependency parameters are required' }],
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
const result = link({
|
|
924
|
+
path,
|
|
925
|
+
dependency,
|
|
926
|
+
name: args.name,
|
|
927
|
+
});
|
|
928
|
+
if (!result.success) {
|
|
929
|
+
return {
|
|
930
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
return {
|
|
934
|
+
content: [{ type: 'text', text: `ā Linked: ${result.name} (${result.filesAvailable} files)` }],
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Handle unlink
|
|
939
|
+
*/
|
|
940
|
+
function handleUnlink(args) {
|
|
941
|
+
const path = args.path;
|
|
942
|
+
const dependency = args.dependency;
|
|
943
|
+
if (!path || !dependency) {
|
|
944
|
+
return {
|
|
945
|
+
content: [{ type: 'text', text: 'Error: path and dependency parameters are required' }],
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
const result = unlink({ path, dependency });
|
|
949
|
+
if (!result.success) {
|
|
950
|
+
return {
|
|
951
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
if (!result.removed) {
|
|
955
|
+
return {
|
|
956
|
+
content: [{ type: 'text', text: `Dependency not found: ${dependency}` }],
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
return {
|
|
960
|
+
content: [{ type: 'text', text: `ā Unlinked: ${dependency}` }],
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Handle links
|
|
965
|
+
*/
|
|
966
|
+
function handleLinks(args) {
|
|
967
|
+
const path = args.path;
|
|
968
|
+
if (!path) {
|
|
969
|
+
return {
|
|
970
|
+
content: [{ type: 'text', text: 'Error: path parameter is required' }],
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
const result = listLinks({ path });
|
|
974
|
+
if (!result.success) {
|
|
975
|
+
return {
|
|
976
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
if (result.dependencies.length === 0) {
|
|
980
|
+
return {
|
|
981
|
+
content: [{ type: 'text', text: 'No linked dependencies.' }],
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
let message = `# Linked Dependencies (${result.dependencies.length})\n\n`;
|
|
985
|
+
for (const dep of result.dependencies) {
|
|
986
|
+
const status = dep.available ? 'ā' : 'ā';
|
|
987
|
+
const name = dep.name ?? 'unnamed';
|
|
988
|
+
message += `${status} **${name}**\n`;
|
|
989
|
+
message += ` Path: ${dep.path}\n`;
|
|
990
|
+
message += ` Files: ${dep.filesAvailable}\n`;
|
|
991
|
+
if (!dep.available) {
|
|
992
|
+
message += ` ā ļø Not available (index missing)\n`;
|
|
993
|
+
}
|
|
994
|
+
message += '\n';
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
content: [{ type: 'text', text: message.trimEnd() }],
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Handle scan
|
|
1002
|
+
*/
|
|
1003
|
+
function handleScan(args) {
|
|
1004
|
+
const path = args.path;
|
|
1005
|
+
if (!path) {
|
|
1006
|
+
return {
|
|
1007
|
+
content: [{ type: 'text', text: 'Error: path parameter is required' }],
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
const result = scan({
|
|
1011
|
+
path,
|
|
1012
|
+
maxDepth: args.max_depth,
|
|
1013
|
+
});
|
|
1014
|
+
if (!result.success) {
|
|
1015
|
+
return {
|
|
1016
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
if (result.projects.length === 0) {
|
|
1020
|
+
return {
|
|
1021
|
+
content: [{ type: 'text', text: `No ${PRODUCT_NAME} indexes found in ${result.searchPath}\n(scanned ${result.scannedDirs} directories)` }],
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
let message = `# ${PRODUCT_NAME} Indexes Found (${result.projects.length})\n\n`;
|
|
1025
|
+
message += `Scanned: ${result.searchPath} (${result.scannedDirs} directories)\n\n`;
|
|
1026
|
+
for (const proj of result.projects) {
|
|
1027
|
+
message += `## ${proj.name}\n`;
|
|
1028
|
+
message += `- **Path:** ${proj.path}\n`;
|
|
1029
|
+
message += `- **Files:** ${proj.files} | **Items:** ${proj.items} | **Methods:** ${proj.methods} | **Types:** ${proj.types}\n`;
|
|
1030
|
+
message += `- **Last indexed:** ${proj.lastIndexed}\n`;
|
|
1031
|
+
message += '\n';
|
|
1032
|
+
}
|
|
1033
|
+
return {
|
|
1034
|
+
content: [{ type: 'text', text: message.trimEnd() }],
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Handle files
|
|
1039
|
+
*/
|
|
1040
|
+
function handleFiles(args) {
|
|
1041
|
+
const path = args.path;
|
|
1042
|
+
if (!path) {
|
|
1043
|
+
return {
|
|
1044
|
+
content: [{ type: 'text', text: 'Error: path parameter is required' }],
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
const result = files({
|
|
1048
|
+
path,
|
|
1049
|
+
type: args.type,
|
|
1050
|
+
pattern: args.pattern,
|
|
1051
|
+
modifiedSince: args.modified_since,
|
|
1052
|
+
});
|
|
1053
|
+
if (!result.success) {
|
|
1054
|
+
return {
|
|
1055
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
if (result.files.length === 0) {
|
|
1059
|
+
return {
|
|
1060
|
+
content: [{ type: 'text', text: 'No files found in project.' }],
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
// Build summary
|
|
1064
|
+
let message = `# Project Files (${result.totalFiles})\n\n`;
|
|
1065
|
+
// Type statistics
|
|
1066
|
+
message += `## By Type\n`;
|
|
1067
|
+
for (const [type, count] of Object.entries(result.byType).sort()) {
|
|
1068
|
+
message += `- **${type}:** ${count}\n`;
|
|
1069
|
+
}
|
|
1070
|
+
message += '\n';
|
|
1071
|
+
// Group files by directory
|
|
1072
|
+
const byDir = new Map();
|
|
1073
|
+
for (const file of result.files) {
|
|
1074
|
+
const dir = file.path.includes('/') ? file.path.substring(0, file.path.lastIndexOf('/')) : '.';
|
|
1075
|
+
if (!byDir.has(dir)) {
|
|
1076
|
+
byDir.set(dir, []);
|
|
1077
|
+
}
|
|
1078
|
+
byDir.get(dir).push(file);
|
|
1079
|
+
}
|
|
1080
|
+
// List files (limit output for large projects)
|
|
1081
|
+
const MAX_ENTRIES = 200;
|
|
1082
|
+
let entriesShown = 0;
|
|
1083
|
+
message += `## Files\n`;
|
|
1084
|
+
for (const [dir, dirFiles] of [...byDir.entries()].sort()) {
|
|
1085
|
+
if (entriesShown >= MAX_ENTRIES) {
|
|
1086
|
+
message += `\n... and ${result.totalFiles - entriesShown} more files\n`;
|
|
1087
|
+
break;
|
|
1088
|
+
}
|
|
1089
|
+
// Show directory
|
|
1090
|
+
if (dir !== '.') {
|
|
1091
|
+
message += `\nš ${dir}/\n`;
|
|
1092
|
+
entriesShown++;
|
|
1093
|
+
}
|
|
1094
|
+
// Show files in directory
|
|
1095
|
+
for (const file of dirFiles) {
|
|
1096
|
+
if (entriesShown >= MAX_ENTRIES)
|
|
1097
|
+
break;
|
|
1098
|
+
const fileName = file.path.includes('/') ? file.path.substring(file.path.lastIndexOf('/') + 1) : file.path;
|
|
1099
|
+
const icon = file.type === 'dir' ? 'š' : 'š';
|
|
1100
|
+
const indexed = file.indexed ? ' ā' : '';
|
|
1101
|
+
message += ` ${icon} ${fileName} (${file.type})${indexed}\n`;
|
|
1102
|
+
entriesShown++;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
content: [{ type: 'text', text: message.trimEnd() }],
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Handle note
|
|
1111
|
+
*/
|
|
1112
|
+
function handleNote(args) {
|
|
1113
|
+
const path = args.path;
|
|
1114
|
+
if (!path) {
|
|
1115
|
+
return {
|
|
1116
|
+
content: [{ type: 'text', text: 'Error: path parameter is required' }],
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
const result = note({
|
|
1120
|
+
path,
|
|
1121
|
+
note: args.note,
|
|
1122
|
+
append: args.append,
|
|
1123
|
+
clear: args.clear,
|
|
1124
|
+
});
|
|
1125
|
+
if (!result.success) {
|
|
1126
|
+
return {
|
|
1127
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
switch (result.action) {
|
|
1131
|
+
case 'clear':
|
|
1132
|
+
return {
|
|
1133
|
+
content: [{ type: 'text', text: 'ā Session note cleared.' }],
|
|
1134
|
+
};
|
|
1135
|
+
case 'write':
|
|
1136
|
+
return {
|
|
1137
|
+
content: [{ type: 'text', text: `ā Session note saved:\n\n${result.note}` }],
|
|
1138
|
+
};
|
|
1139
|
+
case 'append':
|
|
1140
|
+
return {
|
|
1141
|
+
content: [{ type: 'text', text: `ā Appended to session note:\n\n${result.note}` }],
|
|
1142
|
+
};
|
|
1143
|
+
case 'read':
|
|
1144
|
+
default:
|
|
1145
|
+
if (!result.note) {
|
|
1146
|
+
return {
|
|
1147
|
+
content: [{ type: 'text', text: 'No session note set for this project.' }],
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
return {
|
|
1151
|
+
content: [{ type: 'text', text: `š Session Note:\n\n${result.note}` }],
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Handle session
|
|
1157
|
+
*/
|
|
1158
|
+
function handleSession(args) {
|
|
1159
|
+
const path = args.path;
|
|
1160
|
+
if (!path) {
|
|
1161
|
+
return {
|
|
1162
|
+
content: [{ type: 'text', text: 'Error: path parameter is required' }],
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
const result = session({ path });
|
|
1166
|
+
if (!result.success) {
|
|
1167
|
+
return {
|
|
1168
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
let message = '';
|
|
1172
|
+
// Session status
|
|
1173
|
+
if (result.isNewSession) {
|
|
1174
|
+
message += 'š **New Session Started**\n\n';
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
message += 'ā¶ļø **Session Continued**\n\n';
|
|
1178
|
+
}
|
|
1179
|
+
// Last session info
|
|
1180
|
+
if (result.sessionInfo.lastSessionStart && result.sessionInfo.lastSessionEnd) {
|
|
1181
|
+
message += '## Last Session\n';
|
|
1182
|
+
message += `- **Start:** ${formatSessionTime(result.sessionInfo.lastSessionStart)}\n`;
|
|
1183
|
+
message += `- **End:** ${formatSessionTime(result.sessionInfo.lastSessionEnd)}\n`;
|
|
1184
|
+
message += `- **Duration:** ${formatDuration(result.sessionInfo.lastSessionStart, result.sessionInfo.lastSessionEnd)}\n`;
|
|
1185
|
+
message += `\nš” Query last session changes with:\n\`${TOOL_PREFIX}query({ term: "...", modified_since: "${result.sessionInfo.lastSessionStart}", modified_before: "${result.sessionInfo.lastSessionEnd}" })\`\n\n`;
|
|
1186
|
+
}
|
|
1187
|
+
// External changes
|
|
1188
|
+
if (result.externalChanges.length > 0) {
|
|
1189
|
+
message += '## External Changes Detected\n';
|
|
1190
|
+
message += `Found ${result.externalChanges.length} file(s) changed outside of session:\n\n`;
|
|
1191
|
+
for (const change of result.externalChanges) {
|
|
1192
|
+
const icon = change.reason === 'deleted' ? 'šļø' : 'āļø';
|
|
1193
|
+
message += `- ${icon} ${change.path} (${change.reason})\n`;
|
|
1194
|
+
}
|
|
1195
|
+
if (result.reindexed.length > 0) {
|
|
1196
|
+
message += `\nā
Auto-reindexed ${result.reindexed.length} file(s)\n`;
|
|
1197
|
+
}
|
|
1198
|
+
message += '\n';
|
|
1199
|
+
}
|
|
1200
|
+
// Session note
|
|
1201
|
+
if (result.note) {
|
|
1202
|
+
message += '## š Session Note\n';
|
|
1203
|
+
message += result.note + '\n';
|
|
1204
|
+
}
|
|
1205
|
+
return {
|
|
1206
|
+
content: [{ type: 'text', text: message.trimEnd() }],
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Handle viewer
|
|
1211
|
+
*/
|
|
1212
|
+
async function handleViewer(args) {
|
|
1213
|
+
const path = args.path;
|
|
1214
|
+
const action = args.action || 'open';
|
|
1215
|
+
if (!path) {
|
|
1216
|
+
return {
|
|
1217
|
+
content: [{ type: 'text', text: 'Error: path parameter is required' }],
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
// Check if index directory exists
|
|
1221
|
+
const indexPath = join(path, INDEX_DIR);
|
|
1222
|
+
if (!existsSync(indexPath)) {
|
|
1223
|
+
return {
|
|
1224
|
+
content: [{ type: 'text', text: `Error: No ${INDEX_DIR} directory found at ${path}. Run ${TOOL_PREFIX}init first.` }],
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
if (action === 'close') {
|
|
1228
|
+
const message = stopViewer();
|
|
1229
|
+
return {
|
|
1230
|
+
content: [{ type: 'text', text: message }],
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
try {
|
|
1234
|
+
const message = await startViewer(path);
|
|
1235
|
+
return {
|
|
1236
|
+
content: [{ type: 'text', text: `š„ļø ${message}` }],
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
catch (error) {
|
|
1240
|
+
return {
|
|
1241
|
+
content: [{ type: 'text', text: `Error starting viewer: ${error instanceof Error ? error.message : String(error)}` }],
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
//# sourceMappingURL=tools.js.map
|