cntx-ui 2.0.13 → 2.0.15
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/bin/cntx-ui.js +137 -55
- package/lib/agent-runtime.js +1480 -0
- package/lib/agent-tools.js +368 -0
- package/lib/api-router.js +978 -0
- package/lib/bundle-manager.js +471 -0
- package/lib/configuration-manager.js +725 -0
- package/lib/file-system-manager.js +472 -0
- package/lib/heuristics-manager.js +425 -0
- package/lib/mcp-server.js +1054 -1
- package/lib/semantic-splitter.js +7 -14
- package/lib/simple-vector-store.js +329 -0
- package/lib/websocket-manager.js +470 -0
- package/package.json +10 -3
- package/server.js +662 -1933
- package/templates/activities/README.md +67 -0
- package/templates/activities/activities/create-project-bundles/README.md +83 -0
- package/templates/activities/activities/create-project-bundles/notes.md +102 -0
- package/templates/activities/activities/create-project-bundles/progress.md +63 -0
- package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
- package/templates/activities/activities.json +219 -0
- package/templates/activities/lib/.markdownlint.jsonc +18 -0
- package/templates/activities/lib/create-activity.mdc +63 -0
- package/templates/activities/lib/generate-tasks.mdc +64 -0
- package/templates/activities/lib/process-task-list.mdc +52 -0
- package/templates/agent-config.yaml +78 -0
- package/templates/agent-instructions.md +218 -0
- package/templates/agent-rules/capabilities/activities-system.md +147 -0
- package/templates/agent-rules/capabilities/bundle-system.md +131 -0
- package/templates/agent-rules/capabilities/vector-search.md +135 -0
- package/templates/agent-rules/core/codebase-navigation.md +91 -0
- package/templates/agent-rules/core/performance-hierarchy.md +48 -0
- package/templates/agent-rules/core/response-formatting.md +120 -0
- package/templates/agent-rules/project-specific/architecture.md +145 -0
- package/templates/config.json +76 -0
- package/templates/hidden-files.json +14 -0
- package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +1 -0
- package/web/dist/assets/index-dF3qg-y_.js +2486 -0
- package/web/dist/assets/index-h5FGSg_P.css +1 -0
- package/web/dist/cntx-ui.svg +18 -0
- package/web/dist/index.html +25 -8
- package/lib/semantic-integration.js +0 -441
- package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
- package/web/dist/assets/index-IUp4q_fr.css +0 -1
- package/web/dist/vite.svg +0 -21
package/lib/mcp-server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
2
2
|
import { join, relative } from 'path';
|
|
3
|
+
import AgentRuntime from './agent-runtime.js';
|
|
3
4
|
|
|
4
5
|
export class MCPServer {
|
|
5
6
|
constructor(cntxServer) {
|
|
@@ -9,6 +10,7 @@ export class MCPServer {
|
|
|
9
10
|
name: 'cntx-ui',
|
|
10
11
|
version: '2.0.8'
|
|
11
12
|
};
|
|
13
|
+
this.agentRuntime = new AgentRuntime(cntxServer);
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
// JSON-RPC 2.0 message handler
|
|
@@ -214,6 +216,322 @@ export class MCPServer {
|
|
|
214
216
|
properties: {},
|
|
215
217
|
required: []
|
|
216
218
|
}
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'get_semantic_chunks',
|
|
222
|
+
description: 'Get function-level semantic chunks from the codebase',
|
|
223
|
+
inputSchema: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: {},
|
|
226
|
+
required: []
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'get_semantic_chunks_filtered',
|
|
231
|
+
description: 'Get semantic chunks filtered by purpose, type, complexity, or bundle',
|
|
232
|
+
inputSchema: {
|
|
233
|
+
type: 'object',
|
|
234
|
+
properties: {
|
|
235
|
+
purpose: {
|
|
236
|
+
type: 'string',
|
|
237
|
+
description: 'Filter by function purpose (e.g., "API handler", "React component", "Data retrieval")'
|
|
238
|
+
},
|
|
239
|
+
type: {
|
|
240
|
+
type: 'string',
|
|
241
|
+
description: 'Filter by function type (e.g., "arrow_function", "react_component", "method")'
|
|
242
|
+
},
|
|
243
|
+
complexity: {
|
|
244
|
+
type: 'string',
|
|
245
|
+
description: 'Filter by complexity level ("low", "medium", "high")'
|
|
246
|
+
},
|
|
247
|
+
bundle: {
|
|
248
|
+
type: 'string',
|
|
249
|
+
description: 'Filter by bundle membership'
|
|
250
|
+
},
|
|
251
|
+
exported: {
|
|
252
|
+
type: 'boolean',
|
|
253
|
+
description: 'Filter by export status'
|
|
254
|
+
},
|
|
255
|
+
async: {
|
|
256
|
+
type: 'boolean',
|
|
257
|
+
description: 'Filter by async functions'
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
required: []
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: 'analyze_bundle_suggestions',
|
|
265
|
+
description: 'Analyze codebase and suggest optimal bundle organization based on semantic chunks',
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: 'object',
|
|
268
|
+
properties: {
|
|
269
|
+
max_suggestions: {
|
|
270
|
+
type: 'number',
|
|
271
|
+
description: 'Maximum number of bundle suggestions to return (default: 5)'
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
required: []
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: 'create_bundle',
|
|
279
|
+
description: 'Create a new bundle with specified patterns',
|
|
280
|
+
inputSchema: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
name: {
|
|
284
|
+
type: 'string',
|
|
285
|
+
description: 'Name of the new bundle'
|
|
286
|
+
},
|
|
287
|
+
patterns: {
|
|
288
|
+
type: 'array',
|
|
289
|
+
items: { type: 'string' },
|
|
290
|
+
description: 'Array of glob patterns for the bundle (e.g., ["src/api/**", "src/services/**"])'
|
|
291
|
+
},
|
|
292
|
+
description: {
|
|
293
|
+
type: 'string',
|
|
294
|
+
description: 'Optional description of the bundle purpose'
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
required: ['name', 'patterns']
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: 'update_bundle',
|
|
302
|
+
description: 'Update an existing bundle\'s patterns',
|
|
303
|
+
inputSchema: {
|
|
304
|
+
type: 'object',
|
|
305
|
+
properties: {
|
|
306
|
+
name: {
|
|
307
|
+
type: 'string',
|
|
308
|
+
description: 'Name of the bundle to update'
|
|
309
|
+
},
|
|
310
|
+
patterns: {
|
|
311
|
+
type: 'array',
|
|
312
|
+
items: { type: 'string' },
|
|
313
|
+
description: 'New array of glob patterns for the bundle'
|
|
314
|
+
},
|
|
315
|
+
description: {
|
|
316
|
+
type: 'string',
|
|
317
|
+
description: 'Optional updated description'
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
required: ['name', 'patterns']
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: 'delete_bundle',
|
|
325
|
+
description: 'Delete an existing bundle',
|
|
326
|
+
inputSchema: {
|
|
327
|
+
type: 'object',
|
|
328
|
+
properties: {
|
|
329
|
+
name: {
|
|
330
|
+
type: 'string',
|
|
331
|
+
description: 'Name of the bundle to delete'
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
required: ['name']
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: 'update_cntxignore',
|
|
339
|
+
description: 'Update the .cntxignore file with new ignore patterns',
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: 'object',
|
|
342
|
+
properties: {
|
|
343
|
+
content: {
|
|
344
|
+
type: 'string',
|
|
345
|
+
description: 'Full content for the .cntxignore file (newline-separated patterns)'
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
required: ['content']
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: 'agent_discover',
|
|
353
|
+
description: 'Agent Discovery Mode: Get comprehensive codebase overview including bundles, architecture, and patterns',
|
|
354
|
+
inputSchema: {
|
|
355
|
+
type: 'object',
|
|
356
|
+
properties: {
|
|
357
|
+
scope: {
|
|
358
|
+
type: 'string',
|
|
359
|
+
description: 'Scope of discovery: "all" for full codebase or specific bundle name (default: "all")'
|
|
360
|
+
},
|
|
361
|
+
includeDetails: {
|
|
362
|
+
type: 'boolean',
|
|
363
|
+
description: 'Include detailed semantic analysis and complexity metrics (default: true)'
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
required: []
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
name: 'agent_query',
|
|
371
|
+
description: 'Agent Query Mode: Answer specific questions about the codebase using semantic search and analysis',
|
|
372
|
+
inputSchema: {
|
|
373
|
+
type: 'object',
|
|
374
|
+
properties: {
|
|
375
|
+
question: {
|
|
376
|
+
type: 'string',
|
|
377
|
+
description: 'The question to answer about the codebase (e.g., "Where is user authentication handled?")'
|
|
378
|
+
},
|
|
379
|
+
scope: {
|
|
380
|
+
type: 'string',
|
|
381
|
+
description: 'Optional bundle to limit search scope'
|
|
382
|
+
},
|
|
383
|
+
maxResults: {
|
|
384
|
+
type: 'number',
|
|
385
|
+
description: 'Maximum number of results to return (default: 10)'
|
|
386
|
+
},
|
|
387
|
+
includeCode: {
|
|
388
|
+
type: 'boolean',
|
|
389
|
+
description: 'Include code snippets in the response (default: false)'
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
required: ['question']
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: 'agent_investigate',
|
|
397
|
+
description: 'Agent Investigation Mode: Investigate existing implementations for a feature and find integration points',
|
|
398
|
+
inputSchema: {
|
|
399
|
+
type: 'object',
|
|
400
|
+
properties: {
|
|
401
|
+
featureDescription: {
|
|
402
|
+
type: 'string',
|
|
403
|
+
description: 'Description of the feature to investigate (e.g., "dark mode", "user authentication", "form validation")'
|
|
404
|
+
},
|
|
405
|
+
includeRecommendations: {
|
|
406
|
+
type: 'boolean',
|
|
407
|
+
description: 'Include implementation recommendations and approach suggestions (default: true)'
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
required: ['featureDescription']
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: 'agent_discuss',
|
|
415
|
+
description: 'Agent Passive Mode: Engage in discussion about codebase architecture, design decisions, and planning',
|
|
416
|
+
inputSchema: {
|
|
417
|
+
type: 'object',
|
|
418
|
+
properties: {
|
|
419
|
+
userInput: {
|
|
420
|
+
type: 'string',
|
|
421
|
+
description: 'The topic or question for discussion (e.g., "Let\'s discuss the architecture before I make changes")'
|
|
422
|
+
},
|
|
423
|
+
context: {
|
|
424
|
+
type: 'object',
|
|
425
|
+
description: 'Additional context for the discussion',
|
|
426
|
+
properties: {
|
|
427
|
+
scope: {
|
|
428
|
+
type: 'string',
|
|
429
|
+
description: 'Specific area of focus (e.g., "frontend", "api", "database")'
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
required: ['userInput']
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
name: 'agent_organize',
|
|
439
|
+
description: 'Agent Project Organizer Mode: Setup and maintenance of project organization - adapts to project maturity',
|
|
440
|
+
inputSchema: {
|
|
441
|
+
type: 'object',
|
|
442
|
+
properties: {
|
|
443
|
+
activity: {
|
|
444
|
+
type: 'string',
|
|
445
|
+
enum: ['detect', 'analyze', 'bundle', 'create', 'optimize', 'audit', 'cleanup', 'validate'],
|
|
446
|
+
description: 'Activity to perform: detect project state, analyze semantics, suggest bundles, create bundles, optimize organization, audit health, cleanup issues, or validate structure'
|
|
447
|
+
},
|
|
448
|
+
autoDetect: {
|
|
449
|
+
type: 'boolean',
|
|
450
|
+
description: 'Automatically detect appropriate activity based on project state (default: true)',
|
|
451
|
+
default: true
|
|
452
|
+
},
|
|
453
|
+
force: {
|
|
454
|
+
type: 'boolean',
|
|
455
|
+
description: 'Force execution even if preconditions are not met (default: false)',
|
|
456
|
+
default: false
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
required: []
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: 'read_file',
|
|
464
|
+
description: 'Read contents of a specific file with bundle context and metadata',
|
|
465
|
+
inputSchema: {
|
|
466
|
+
type: 'object',
|
|
467
|
+
properties: {
|
|
468
|
+
path: {
|
|
469
|
+
type: 'string',
|
|
470
|
+
description: 'File path relative to project root'
|
|
471
|
+
},
|
|
472
|
+
includeMetadata: {
|
|
473
|
+
type: 'boolean',
|
|
474
|
+
description: 'Include file metadata (size, bundles, etc.) - default: true'
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
required: ['path']
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
name: 'write_file',
|
|
482
|
+
description: 'Write content to a file with validation and safety checks',
|
|
483
|
+
inputSchema: {
|
|
484
|
+
type: 'object',
|
|
485
|
+
properties: {
|
|
486
|
+
path: {
|
|
487
|
+
type: 'string',
|
|
488
|
+
description: 'File path relative to project root'
|
|
489
|
+
},
|
|
490
|
+
content: {
|
|
491
|
+
type: 'string',
|
|
492
|
+
description: 'Content to write to the file'
|
|
493
|
+
},
|
|
494
|
+
backup: {
|
|
495
|
+
type: 'boolean',
|
|
496
|
+
description: 'Create backup before writing - default: true'
|
|
497
|
+
},
|
|
498
|
+
createDirs: {
|
|
499
|
+
type: 'boolean',
|
|
500
|
+
description: 'Create parent directories if they don\'t exist - default: true'
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
required: ['path', 'content']
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
name: 'manage_activities',
|
|
508
|
+
description: 'CRUD operations for project activities',
|
|
509
|
+
inputSchema: {
|
|
510
|
+
type: 'object',
|
|
511
|
+
properties: {
|
|
512
|
+
action: {
|
|
513
|
+
type: 'string',
|
|
514
|
+
enum: ['list', 'get', 'create', 'update', 'delete'],
|
|
515
|
+
description: 'Action to perform on activities'
|
|
516
|
+
},
|
|
517
|
+
activityId: {
|
|
518
|
+
type: 'string',
|
|
519
|
+
description: 'Activity ID (required for get, update, delete)'
|
|
520
|
+
},
|
|
521
|
+
activity: {
|
|
522
|
+
type: 'object',
|
|
523
|
+
description: 'Activity data (required for create, update)',
|
|
524
|
+
properties: {
|
|
525
|
+
title: { type: 'string' },
|
|
526
|
+
description: { type: 'string' },
|
|
527
|
+
status: { type: 'string', enum: ['todo', 'in_progress', 'completed', 'blocked'] },
|
|
528
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
529
|
+
tasks: { type: 'array', items: { type: 'object' } }
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
required: ['action']
|
|
534
|
+
}
|
|
217
535
|
}
|
|
218
536
|
];
|
|
219
537
|
|
|
@@ -241,6 +559,51 @@ export class MCPServer {
|
|
|
241
559
|
case 'get_project_status':
|
|
242
560
|
return this.toolGetProjectStatus(id);
|
|
243
561
|
|
|
562
|
+
case 'get_semantic_chunks':
|
|
563
|
+
return this.toolGetSemanticChunks(id);
|
|
564
|
+
|
|
565
|
+
case 'get_semantic_chunks_filtered':
|
|
566
|
+
return this.toolGetSemanticChunksFiltered(args, id);
|
|
567
|
+
|
|
568
|
+
case 'analyze_bundle_suggestions':
|
|
569
|
+
return this.toolAnalyzeBundleSuggestions(args, id);
|
|
570
|
+
|
|
571
|
+
case 'create_bundle':
|
|
572
|
+
return this.toolCreateBundle(args, id);
|
|
573
|
+
|
|
574
|
+
case 'update_bundle':
|
|
575
|
+
return this.toolUpdateBundle(args, id);
|
|
576
|
+
|
|
577
|
+
case 'delete_bundle':
|
|
578
|
+
return this.toolDeleteBundle(args, id);
|
|
579
|
+
|
|
580
|
+
case 'update_cntxignore':
|
|
581
|
+
return this.toolUpdateCntxignore(args, id);
|
|
582
|
+
|
|
583
|
+
case 'agent_discover':
|
|
584
|
+
return this.toolAgentDiscover(args, id);
|
|
585
|
+
|
|
586
|
+
case 'agent_query':
|
|
587
|
+
return this.toolAgentQuery(args, id);
|
|
588
|
+
|
|
589
|
+
case 'agent_investigate':
|
|
590
|
+
return this.toolAgentInvestigate(args, id);
|
|
591
|
+
|
|
592
|
+
case 'agent_discuss':
|
|
593
|
+
return this.toolAgentDiscuss(args, id);
|
|
594
|
+
|
|
595
|
+
case 'agent_organize':
|
|
596
|
+
return this.toolAgentOrganize(args, id);
|
|
597
|
+
|
|
598
|
+
case 'read_file':
|
|
599
|
+
return this.toolReadFile(args, id);
|
|
600
|
+
|
|
601
|
+
case 'write_file':
|
|
602
|
+
return this.toolWriteFile(args, id);
|
|
603
|
+
|
|
604
|
+
case 'manage_activities':
|
|
605
|
+
return this.toolManageActivities(args, id);
|
|
606
|
+
|
|
244
607
|
default:
|
|
245
608
|
return this.createErrorResponse(id, -32602, 'Unknown tool');
|
|
246
609
|
}
|
|
@@ -347,6 +710,696 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
|
|
|
347
710
|
});
|
|
348
711
|
}
|
|
349
712
|
|
|
713
|
+
// New semantic chunks tools
|
|
714
|
+
async toolGetSemanticChunks(id) {
|
|
715
|
+
try {
|
|
716
|
+
const analysis = await this.cntxServer.getSemanticAnalysis();
|
|
717
|
+
|
|
718
|
+
// Clean the analysis data to prevent JSON issues
|
|
719
|
+
const cleanAnalysis = {
|
|
720
|
+
...analysis,
|
|
721
|
+
chunks: analysis.chunks?.map(chunk => ({
|
|
722
|
+
...chunk,
|
|
723
|
+
code: chunk.code ? chunk.code.substring(0, 500) + (chunk.code.length > 500 ? '...' : '') : '',
|
|
724
|
+
bundles: chunk.bundles || [],
|
|
725
|
+
includes: {
|
|
726
|
+
imports: chunk.includes?.imports || [],
|
|
727
|
+
types: chunk.includes?.types || []
|
|
728
|
+
}
|
|
729
|
+
})) || []
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
return this.createSuccessResponse(id, {
|
|
733
|
+
content: [{
|
|
734
|
+
type: 'text',
|
|
735
|
+
text: JSON.stringify(cleanAnalysis, null, 2)
|
|
736
|
+
}]
|
|
737
|
+
});
|
|
738
|
+
} catch (error) {
|
|
739
|
+
return this.createErrorResponse(id, -32603, 'Failed to get semantic chunks', error.message);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
async toolGetSemanticChunksFiltered(args, id) {
|
|
744
|
+
try {
|
|
745
|
+
const analysis = await this.cntxServer.getSemanticAnalysis();
|
|
746
|
+
let chunks = analysis.chunks || [];
|
|
747
|
+
|
|
748
|
+
// Apply filters
|
|
749
|
+
if (args.purpose) {
|
|
750
|
+
chunks = chunks.filter(chunk =>
|
|
751
|
+
chunk.purpose && chunk.purpose.toLowerCase().includes(args.purpose.toLowerCase())
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (args.type) {
|
|
756
|
+
chunks = chunks.filter(chunk => chunk.subtype === args.type);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (args.complexity) {
|
|
760
|
+
chunks = chunks.filter(chunk => chunk.complexity?.level === args.complexity);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (args.bundle) {
|
|
764
|
+
chunks = chunks.filter(chunk =>
|
|
765
|
+
chunk.bundles && chunk.bundles.includes(args.bundle)
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (args.exported !== undefined) {
|
|
770
|
+
chunks = chunks.filter(chunk => chunk.isExported === args.exported);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (args.async !== undefined) {
|
|
774
|
+
chunks = chunks.filter(chunk => chunk.isAsync === args.async);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Clean chunks for JSON safety
|
|
778
|
+
const cleanChunks = chunks.map(chunk => ({
|
|
779
|
+
...chunk,
|
|
780
|
+
code: chunk.code ? chunk.code.substring(0, 300) + (chunk.code.length > 300 ? '...' : '') : '',
|
|
781
|
+
bundles: chunk.bundles || [],
|
|
782
|
+
includes: {
|
|
783
|
+
imports: chunk.includes?.imports || [],
|
|
784
|
+
types: chunk.includes?.types || []
|
|
785
|
+
}
|
|
786
|
+
}));
|
|
787
|
+
|
|
788
|
+
const filteredAnalysis = {
|
|
789
|
+
...analysis,
|
|
790
|
+
chunks: cleanChunks,
|
|
791
|
+
summary: {
|
|
792
|
+
...analysis.summary,
|
|
793
|
+
totalChunks: cleanChunks.length,
|
|
794
|
+
filteredCount: cleanChunks.length,
|
|
795
|
+
originalCount: analysis.chunks?.length || 0
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
return this.createSuccessResponse(id, {
|
|
800
|
+
content: [{
|
|
801
|
+
type: 'text',
|
|
802
|
+
text: JSON.stringify(filteredAnalysis, null, 2)
|
|
803
|
+
}]
|
|
804
|
+
});
|
|
805
|
+
} catch (error) {
|
|
806
|
+
return this.createErrorResponse(id, -32603, 'Failed to filter semantic chunks', error.message);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async toolAnalyzeBundleSuggestions(args, id) {
|
|
811
|
+
try {
|
|
812
|
+
const analysis = await this.cntxServer.getSemanticAnalysis();
|
|
813
|
+
const chunks = analysis.chunks || [];
|
|
814
|
+
const maxSuggestions = args.max_suggestions || 5;
|
|
815
|
+
|
|
816
|
+
// Group chunks by purpose and file location
|
|
817
|
+
const purposeGroups = {};
|
|
818
|
+
const locationGroups = {};
|
|
819
|
+
|
|
820
|
+
chunks.forEach(chunk => {
|
|
821
|
+
// Group by purpose
|
|
822
|
+
if (!purposeGroups[chunk.purpose]) {
|
|
823
|
+
purposeGroups[chunk.purpose] = [];
|
|
824
|
+
}
|
|
825
|
+
purposeGroups[chunk.purpose].push(chunk);
|
|
826
|
+
|
|
827
|
+
// Group by file location patterns
|
|
828
|
+
const pathParts = chunk.filePath.split('/');
|
|
829
|
+
if (pathParts.length > 1) {
|
|
830
|
+
const dirPattern = pathParts.slice(0, -1).join('/') + '/**';
|
|
831
|
+
if (!locationGroups[dirPattern]) {
|
|
832
|
+
locationGroups[dirPattern] = [];
|
|
833
|
+
}
|
|
834
|
+
locationGroups[dirPattern].push(chunk);
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
const suggestions = [];
|
|
839
|
+
|
|
840
|
+
// Suggest bundles by purpose
|
|
841
|
+
Object.entries(purposeGroups).forEach(([purpose, chunks]) => {
|
|
842
|
+
if (chunks.length >= 3) { // Only suggest if enough functions
|
|
843
|
+
const bundleName = purpose.toLowerCase().replace(/\s+/g, '-');
|
|
844
|
+
const patterns = [...new Set(chunks.map(c => {
|
|
845
|
+
const dir = c.filePath.split('/').slice(0, -1).join('/');
|
|
846
|
+
return dir ? `${dir}/**` : c.filePath;
|
|
847
|
+
}))];
|
|
848
|
+
|
|
849
|
+
suggestions.push({
|
|
850
|
+
name: bundleName,
|
|
851
|
+
reason: `Groups ${chunks.length} functions with purpose: ${purpose}`,
|
|
852
|
+
patterns,
|
|
853
|
+
chunkCount: chunks.length,
|
|
854
|
+
files: [...new Set(chunks.map(c => c.filePath))]
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
// Suggest bundles by common directory patterns
|
|
860
|
+
Object.entries(locationGroups).forEach(([pattern, chunks]) => {
|
|
861
|
+
if (chunks.length >= 5) { // Only suggest if enough functions in same location
|
|
862
|
+
const dirName = pattern.split('/').pop().replace('/**', '');
|
|
863
|
+
const bundleName = dirName === '*' ? 'utils' : dirName;
|
|
864
|
+
|
|
865
|
+
suggestions.push({
|
|
866
|
+
name: bundleName,
|
|
867
|
+
reason: `Groups ${chunks.length} functions from ${pattern}`,
|
|
868
|
+
patterns: [pattern],
|
|
869
|
+
chunkCount: chunks.length,
|
|
870
|
+
files: [...new Set(chunks.map(c => c.filePath))]
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
// Sort by chunk count and take top suggestions
|
|
876
|
+
const topSuggestions = suggestions
|
|
877
|
+
.sort((a, b) => b.chunkCount - a.chunkCount)
|
|
878
|
+
.slice(0, maxSuggestions);
|
|
879
|
+
|
|
880
|
+
const result = {
|
|
881
|
+
totalSuggestions: suggestions.length,
|
|
882
|
+
suggestions: topSuggestions,
|
|
883
|
+
analysis: {
|
|
884
|
+
totalChunks: chunks.length,
|
|
885
|
+
purposeGroups: Object.keys(purposeGroups).length,
|
|
886
|
+
locationGroups: Object.keys(locationGroups).length
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
return this.createSuccessResponse(id, {
|
|
891
|
+
content: [{
|
|
892
|
+
type: 'text',
|
|
893
|
+
text: JSON.stringify(result, null, 2)
|
|
894
|
+
}]
|
|
895
|
+
});
|
|
896
|
+
} catch (error) {
|
|
897
|
+
return this.createErrorResponse(id, -32603, 'Failed to analyze bundle suggestions', error.message);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Bundle management tools
|
|
902
|
+
async toolCreateBundle(args, id) {
|
|
903
|
+
try {
|
|
904
|
+
const { name, patterns, description } = args;
|
|
905
|
+
|
|
906
|
+
if (!name || !patterns || !Array.isArray(patterns)) {
|
|
907
|
+
return this.createErrorResponse(id, -32602, 'Invalid arguments: name and patterns array required');
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Prevent overwriting existing bundles
|
|
911
|
+
if (this.cntxServer.bundles.has(name)) {
|
|
912
|
+
return this.createErrorResponse(id, -32602, `Bundle '${name}' already exists`);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Load current config
|
|
916
|
+
const configPath = join(this.cntxServer.CNTX_DIR, 'config.json');
|
|
917
|
+
let config = { bundles: {} };
|
|
918
|
+
|
|
919
|
+
if (existsSync(configPath)) {
|
|
920
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Add new bundle
|
|
924
|
+
config.bundles[name] = patterns;
|
|
925
|
+
|
|
926
|
+
// Save config
|
|
927
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
928
|
+
|
|
929
|
+
// Reload server config
|
|
930
|
+
this.cntxServer.loadConfig();
|
|
931
|
+
this.cntxServer.generateAllBundles();
|
|
932
|
+
|
|
933
|
+
const result = {
|
|
934
|
+
success: true,
|
|
935
|
+
bundle: {
|
|
936
|
+
name,
|
|
937
|
+
patterns,
|
|
938
|
+
description,
|
|
939
|
+
created: new Date().toISOString()
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
return this.createSuccessResponse(id, {
|
|
944
|
+
content: [{
|
|
945
|
+
type: 'text',
|
|
946
|
+
text: JSON.stringify(result, null, 2)
|
|
947
|
+
}]
|
|
948
|
+
});
|
|
949
|
+
} catch (error) {
|
|
950
|
+
return this.createErrorResponse(id, -32603, 'Failed to create bundle', error.message);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
async toolUpdateBundle(args, id) {
|
|
955
|
+
try {
|
|
956
|
+
const { name, patterns, description } = args;
|
|
957
|
+
|
|
958
|
+
if (!name || !patterns || !Array.isArray(patterns)) {
|
|
959
|
+
return this.createErrorResponse(id, -32602, 'Invalid arguments: name and patterns array required');
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// Check if bundle exists
|
|
963
|
+
if (!this.cntxServer.bundles.has(name)) {
|
|
964
|
+
return this.createErrorResponse(id, -32602, `Bundle '${name}' not found`);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Prevent updating master bundle
|
|
968
|
+
if (name === 'master') {
|
|
969
|
+
return this.createErrorResponse(id, -32602, 'Cannot update master bundle');
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Load current config
|
|
973
|
+
const configPath = join(this.cntxServer.CNTX_DIR, 'config.json');
|
|
974
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
975
|
+
|
|
976
|
+
// Update bundle patterns
|
|
977
|
+
config.bundles[name] = patterns;
|
|
978
|
+
|
|
979
|
+
// Save config
|
|
980
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
981
|
+
|
|
982
|
+
// Reload server config
|
|
983
|
+
this.cntxServer.loadConfig();
|
|
984
|
+
this.cntxServer.generateAllBundles();
|
|
985
|
+
|
|
986
|
+
const result = {
|
|
987
|
+
success: true,
|
|
988
|
+
bundle: {
|
|
989
|
+
name,
|
|
990
|
+
patterns,
|
|
991
|
+
description,
|
|
992
|
+
updated: new Date().toISOString()
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
return this.createSuccessResponse(id, {
|
|
997
|
+
content: [{
|
|
998
|
+
type: 'text',
|
|
999
|
+
text: JSON.stringify(result, null, 2)
|
|
1000
|
+
}]
|
|
1001
|
+
});
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
return this.createErrorResponse(id, -32603, 'Failed to update bundle', error.message);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
async toolDeleteBundle(args, id) {
|
|
1008
|
+
try {
|
|
1009
|
+
const { name } = args;
|
|
1010
|
+
|
|
1011
|
+
if (!name) {
|
|
1012
|
+
return this.createErrorResponse(id, -32602, 'Bundle name required');
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Check if bundle exists
|
|
1016
|
+
if (!this.cntxServer.bundles.has(name)) {
|
|
1017
|
+
return this.createErrorResponse(id, -32602, `Bundle '${name}' not found`);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Prevent deleting master bundle
|
|
1021
|
+
if (name === 'master') {
|
|
1022
|
+
return this.createErrorResponse(id, -32602, 'Cannot delete master bundle');
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// Load current config
|
|
1026
|
+
const configPath = join(this.cntxServer.CNTX_DIR, 'config.json');
|
|
1027
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1028
|
+
|
|
1029
|
+
// Remove bundle
|
|
1030
|
+
delete config.bundles[name];
|
|
1031
|
+
|
|
1032
|
+
// Save config
|
|
1033
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1034
|
+
|
|
1035
|
+
// Reload server config
|
|
1036
|
+
this.cntxServer.loadConfig();
|
|
1037
|
+
this.cntxServer.generateAllBundles();
|
|
1038
|
+
|
|
1039
|
+
const result = {
|
|
1040
|
+
success: true,
|
|
1041
|
+
deleted: name,
|
|
1042
|
+
timestamp: new Date().toISOString()
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1045
|
+
return this.createSuccessResponse(id, {
|
|
1046
|
+
content: [{
|
|
1047
|
+
type: 'text',
|
|
1048
|
+
text: JSON.stringify(result, null, 2)
|
|
1049
|
+
}]
|
|
1050
|
+
});
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
return this.createErrorResponse(id, -32603, 'Failed to delete bundle', error.message);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
async toolUpdateCntxignore(args, id) {
|
|
1057
|
+
try {
|
|
1058
|
+
const { content } = args;
|
|
1059
|
+
|
|
1060
|
+
if (content === undefined) {
|
|
1061
|
+
return this.createErrorResponse(id, -32602, 'Content required');
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const ignorePath = join(this.cntxServer.CWD, '.cntxignore');
|
|
1065
|
+
|
|
1066
|
+
// Write the .cntxignore file
|
|
1067
|
+
writeFileSync(ignorePath, content);
|
|
1068
|
+
|
|
1069
|
+
// Reload ignore patterns
|
|
1070
|
+
this.cntxServer.loadIgnorePatterns();
|
|
1071
|
+
this.cntxServer.generateAllBundles();
|
|
1072
|
+
|
|
1073
|
+
const result = {
|
|
1074
|
+
success: true,
|
|
1075
|
+
file: '.cntxignore',
|
|
1076
|
+
lines: content.split('\n').length,
|
|
1077
|
+
patterns: content.split('\n').filter(line => line.trim() && !line.trim().startsWith('#')).length,
|
|
1078
|
+
updated: new Date().toISOString()
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
return this.createSuccessResponse(id, {
|
|
1082
|
+
content: [{
|
|
1083
|
+
type: 'text',
|
|
1084
|
+
text: JSON.stringify(result, null, 2)
|
|
1085
|
+
}]
|
|
1086
|
+
});
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
return this.createErrorResponse(id, -32603, 'Failed to update .cntxignore', error.message);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Agent Tools Implementation
|
|
1093
|
+
async toolAgentDiscover(args, id) {
|
|
1094
|
+
try {
|
|
1095
|
+
const { scope = 'all', includeDetails = true } = args;
|
|
1096
|
+
const result = await this.agentRuntime.discoverCodebase({ scope, includeDetails });
|
|
1097
|
+
|
|
1098
|
+
return this.createSuccessResponse(id, {
|
|
1099
|
+
content: [{
|
|
1100
|
+
type: 'text',
|
|
1101
|
+
text: JSON.stringify(result, null, 2)
|
|
1102
|
+
}]
|
|
1103
|
+
});
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
return this.createErrorResponse(id, -32603, 'Agent discovery failed', error.message);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
async toolAgentQuery(args, id) {
|
|
1110
|
+
try {
|
|
1111
|
+
const { question, scope, maxResults = 10, includeCode = false } = args;
|
|
1112
|
+
|
|
1113
|
+
if (!question) {
|
|
1114
|
+
return this.createErrorResponse(id, -32602, 'Question is required');
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const result = await this.agentRuntime.answerQuery(question, { scope, maxResults, includeCode });
|
|
1118
|
+
|
|
1119
|
+
return this.createSuccessResponse(id, {
|
|
1120
|
+
content: [{
|
|
1121
|
+
type: 'text',
|
|
1122
|
+
text: JSON.stringify(result, null, 2)
|
|
1123
|
+
}]
|
|
1124
|
+
});
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
return this.createErrorResponse(id, -32603, 'Agent query failed', error.message);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
async toolAgentInvestigate(args, id) {
|
|
1131
|
+
try {
|
|
1132
|
+
const { featureDescription, includeRecommendations = true } = args;
|
|
1133
|
+
|
|
1134
|
+
if (!featureDescription) {
|
|
1135
|
+
return this.createErrorResponse(id, -32602, 'Feature description is required');
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const result = await this.agentRuntime.investigateFeature(featureDescription, { includeRecommendations });
|
|
1139
|
+
|
|
1140
|
+
return this.createSuccessResponse(id, {
|
|
1141
|
+
content: [{
|
|
1142
|
+
type: 'text',
|
|
1143
|
+
text: JSON.stringify(result, null, 2)
|
|
1144
|
+
}]
|
|
1145
|
+
});
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
return this.createErrorResponse(id, -32603, 'Agent investigation failed', error.message);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
async toolAgentDiscuss(args, id) {
|
|
1152
|
+
try {
|
|
1153
|
+
const { userInput, context = {} } = args;
|
|
1154
|
+
|
|
1155
|
+
if (!userInput) {
|
|
1156
|
+
return this.createErrorResponse(id, -32602, 'User input is required');
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const result = await this.agentRuntime.discussAndPlan(userInput, context);
|
|
1160
|
+
|
|
1161
|
+
return this.createSuccessResponse(id, {
|
|
1162
|
+
content: [{
|
|
1163
|
+
type: 'text',
|
|
1164
|
+
text: JSON.stringify(result, null, 2)
|
|
1165
|
+
}]
|
|
1166
|
+
});
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
return this.createErrorResponse(id, -32603, 'Agent discussion failed', error.message);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
async toolAgentOrganize(args, id) {
|
|
1173
|
+
try {
|
|
1174
|
+
const { activity = 'detect', autoDetect = true, force = false } = args;
|
|
1175
|
+
|
|
1176
|
+
const result = await this.agentRuntime.organizeProject({ activity, autoDetect, force });
|
|
1177
|
+
|
|
1178
|
+
return this.createSuccessResponse(id, {
|
|
1179
|
+
content: [{
|
|
1180
|
+
type: 'text',
|
|
1181
|
+
text: JSON.stringify(result, null, 2)
|
|
1182
|
+
}]
|
|
1183
|
+
});
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
return this.createErrorResponse(id, -32603, 'Agent organization failed', error.message);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// New tool implementations
|
|
1190
|
+
async toolReadFile(args, id) {
|
|
1191
|
+
const { path, includeMetadata = true } = args;
|
|
1192
|
+
|
|
1193
|
+
if (!path) {
|
|
1194
|
+
return this.createErrorResponse(id, -32602, 'Path is required');
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
try {
|
|
1198
|
+
const fullPath = join(this.cntxServer.CWD, path);
|
|
1199
|
+
|
|
1200
|
+
if (!existsSync(fullPath)) {
|
|
1201
|
+
return this.createErrorResponse(id, -32602, `File not found: ${path}`);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
1205
|
+
const result = { path, content };
|
|
1206
|
+
|
|
1207
|
+
if (includeMetadata) {
|
|
1208
|
+
const stats = require('fs').statSync(fullPath);
|
|
1209
|
+
const bundles = [];
|
|
1210
|
+
|
|
1211
|
+
// Find which bundles include this file
|
|
1212
|
+
this.cntxServer.bundles.forEach((bundle, name) => {
|
|
1213
|
+
if (bundle.files && bundle.files.includes(fullPath)) {
|
|
1214
|
+
bundles.push(name);
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
result.metadata = {
|
|
1219
|
+
size: stats.size,
|
|
1220
|
+
mimeType: this.getMimeType(path),
|
|
1221
|
+
modified: stats.mtime.toISOString(),
|
|
1222
|
+
lines: content.split('\n').length,
|
|
1223
|
+
bundles: bundles
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
return this.createSuccessResponse(id, {
|
|
1228
|
+
contents: [{
|
|
1229
|
+
type: 'text',
|
|
1230
|
+
text: JSON.stringify(result, null, 2)
|
|
1231
|
+
}]
|
|
1232
|
+
});
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
return this.createErrorResponse(id, -32603, 'Failed to read file', error.message);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
async toolWriteFile(args, id) {
|
|
1239
|
+
const { path, content, backup = true, createDirs = true } = args;
|
|
1240
|
+
|
|
1241
|
+
if (!path || content === undefined) {
|
|
1242
|
+
return this.createErrorResponse(id, -32602, 'Path and content are required');
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
try {
|
|
1246
|
+
const fullPath = join(this.cntxServer.CWD, path);
|
|
1247
|
+
const parentDir = require('path').dirname(fullPath);
|
|
1248
|
+
|
|
1249
|
+
// Create parent directories if needed
|
|
1250
|
+
if (createDirs && !existsSync(parentDir)) {
|
|
1251
|
+
require('fs').mkdirSync(parentDir, { recursive: true });
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Create backup if file exists
|
|
1255
|
+
if (backup && existsSync(fullPath)) {
|
|
1256
|
+
const backupPath = `${fullPath}.backup.${Date.now()}`;
|
|
1257
|
+
require('fs').copyFileSync(fullPath, backupPath);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Write the file
|
|
1261
|
+
writeFileSync(fullPath, content, 'utf8');
|
|
1262
|
+
|
|
1263
|
+
// Mark relevant bundles as changed
|
|
1264
|
+
this.cntxServer.bundles.forEach((bundle, name) => {
|
|
1265
|
+
if (bundle.patterns && bundle.patterns.some(pattern =>
|
|
1266
|
+
this.cntxServer.fileSystemManager.matchesPattern(fullPath, pattern)
|
|
1267
|
+
)) {
|
|
1268
|
+
bundle.changed = true;
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
const stats = require('fs').statSync(fullPath);
|
|
1273
|
+
|
|
1274
|
+
return this.createSuccessResponse(id, {
|
|
1275
|
+
contents: [{
|
|
1276
|
+
type: 'text',
|
|
1277
|
+
text: JSON.stringify({
|
|
1278
|
+
path,
|
|
1279
|
+
written: true,
|
|
1280
|
+
size: stats.size,
|
|
1281
|
+
modified: stats.mtime.toISOString()
|
|
1282
|
+
}, null, 2)
|
|
1283
|
+
}]
|
|
1284
|
+
});
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
return this.createErrorResponse(id, -32603, 'Failed to write file', error.message);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
async toolManageActivities(args, id) {
|
|
1291
|
+
const { action, activityId, activity } = args;
|
|
1292
|
+
|
|
1293
|
+
if (!action) {
|
|
1294
|
+
return this.createErrorResponse(id, -32602, 'Action is required');
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
try {
|
|
1298
|
+
const activitiesPath = join(this.cntxServer.CWD, '.cntx', 'activities');
|
|
1299
|
+
const activitiesJsonPath = join(activitiesPath, 'activities.json');
|
|
1300
|
+
|
|
1301
|
+
let activities = [];
|
|
1302
|
+
if (existsSync(activitiesJsonPath)) {
|
|
1303
|
+
activities = JSON.parse(readFileSync(activitiesJsonPath, 'utf8'));
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
let result;
|
|
1307
|
+
|
|
1308
|
+
switch (action) {
|
|
1309
|
+
case 'list':
|
|
1310
|
+
result = { activities: activities.map(a => ({
|
|
1311
|
+
id: a.title.toLowerCase().replace(/[^a-z0-9]/g, '-'),
|
|
1312
|
+
title: a.title,
|
|
1313
|
+
description: a.description,
|
|
1314
|
+
status: a.status,
|
|
1315
|
+
tags: a.tags
|
|
1316
|
+
})) };
|
|
1317
|
+
break;
|
|
1318
|
+
|
|
1319
|
+
case 'get':
|
|
1320
|
+
if (!activityId) {
|
|
1321
|
+
return this.createErrorResponse(id, -32602, 'Activity ID is required for get action');
|
|
1322
|
+
}
|
|
1323
|
+
const found = activities.find(a =>
|
|
1324
|
+
a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
|
|
1325
|
+
);
|
|
1326
|
+
if (!found) {
|
|
1327
|
+
return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// Load markdown files
|
|
1331
|
+
const activityDir = join(activitiesPath, 'activities', activityId);
|
|
1332
|
+
const files = {};
|
|
1333
|
+
['README.md', 'progress.md', 'tasks.md', 'notes.md'].forEach(file => {
|
|
1334
|
+
const filePath = join(activityDir, file);
|
|
1335
|
+
files[file.replace('.md', '')] = existsSync(filePath)
|
|
1336
|
+
? readFileSync(filePath, 'utf8')
|
|
1337
|
+
: 'No content available';
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
result = { ...found, files };
|
|
1341
|
+
break;
|
|
1342
|
+
|
|
1343
|
+
case 'create':
|
|
1344
|
+
if (!activity || !activity.title) {
|
|
1345
|
+
return this.createErrorResponse(id, -32602, 'Activity with title is required for create action');
|
|
1346
|
+
}
|
|
1347
|
+
activities.push({
|
|
1348
|
+
title: activity.title,
|
|
1349
|
+
description: activity.description || '',
|
|
1350
|
+
status: activity.status || 'todo',
|
|
1351
|
+
tags: activity.tags || ['general'],
|
|
1352
|
+
tasks: activity.tasks || []
|
|
1353
|
+
});
|
|
1354
|
+
writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
|
|
1355
|
+
result = { created: true, activityId: activity.title.toLowerCase().replace(/[^a-z0-9]/g, '-') };
|
|
1356
|
+
break;
|
|
1357
|
+
|
|
1358
|
+
case 'update':
|
|
1359
|
+
if (!activityId || !activity) {
|
|
1360
|
+
return this.createErrorResponse(id, -32602, 'Activity ID and activity data are required for update action');
|
|
1361
|
+
}
|
|
1362
|
+
const updateIndex = activities.findIndex(a =>
|
|
1363
|
+
a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
|
|
1364
|
+
);
|
|
1365
|
+
if (updateIndex === -1) {
|
|
1366
|
+
return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
|
|
1367
|
+
}
|
|
1368
|
+
activities[updateIndex] = { ...activities[updateIndex], ...activity };
|
|
1369
|
+
writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
|
|
1370
|
+
result = { updated: true };
|
|
1371
|
+
break;
|
|
1372
|
+
|
|
1373
|
+
case 'delete':
|
|
1374
|
+
if (!activityId) {
|
|
1375
|
+
return this.createErrorResponse(id, -32602, 'Activity ID is required for delete action');
|
|
1376
|
+
}
|
|
1377
|
+
const deleteIndex = activities.findIndex(a =>
|
|
1378
|
+
a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
|
|
1379
|
+
);
|
|
1380
|
+
if (deleteIndex === -1) {
|
|
1381
|
+
return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
|
|
1382
|
+
}
|
|
1383
|
+
activities.splice(deleteIndex, 1);
|
|
1384
|
+
writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
|
|
1385
|
+
result = { deleted: true };
|
|
1386
|
+
break;
|
|
1387
|
+
|
|
1388
|
+
default:
|
|
1389
|
+
return this.createErrorResponse(id, -32602, `Unknown action: ${action}`);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
return this.createSuccessResponse(id, {
|
|
1393
|
+
contents: [{
|
|
1394
|
+
type: 'text',
|
|
1395
|
+
text: JSON.stringify(result, null, 2)
|
|
1396
|
+
}]
|
|
1397
|
+
});
|
|
1398
|
+
} catch (error) {
|
|
1399
|
+
return this.createErrorResponse(id, -32603, 'Failed to manage activities', error.message);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
350
1403
|
// Helper methods
|
|
351
1404
|
getMimeType(filePath) {
|
|
352
1405
|
const ext = filePath.split('.').pop()?.toLowerCase();
|