@claudetools/tools 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +62 -0
- package/dist/codedna/generators/base.d.ts +11 -2
- package/dist/codedna/generators/base.js +91 -8
- package/dist/codedna/generators/react-frontend.js +2 -1
- package/dist/codedna/parser.d.ts +6 -0
- package/dist/codedna/parser.js +7 -0
- package/dist/codedna/registry.d.ts +23 -17
- package/dist/codedna/registry.js +103 -263
- package/dist/codedna/template-engine.js +23 -0
- package/dist/codedna/types.d.ts +22 -0
- package/dist/handlers/codedna-handlers.d.ts +219 -6
- package/dist/handlers/codedna-handlers.js +379 -11
- package/dist/handlers/tool-handlers.js +56 -7
- package/dist/helpers/codebase-mapper.d.ts +66 -0
- package/dist/helpers/codebase-mapper.js +634 -0
- package/dist/helpers/config.d.ts +1 -1
- package/dist/helpers/config.js +39 -2
- package/dist/helpers/workers.js +60 -7
- package/dist/templates/orchestrator-prompt.js +15 -7
- package/dist/templates/worker-prompt.js +24 -31
- package/dist/tools.js +101 -2
- package/dist/watcher.js +97 -1
- package/package.json +3 -2
|
@@ -15,13 +15,75 @@ import { VueFrontendGenerator } from '../codedna/generators/vue-frontend.js';
|
|
|
15
15
|
import { UIComponentGenerator } from '../codedna/generators/ui-component.js';
|
|
16
16
|
import { errorTracker } from '../helpers/error-tracking.js';
|
|
17
17
|
import { analytics } from '../helpers/usage-analytics.js';
|
|
18
|
+
import { apiRequest } from '../helpers/api-client.js';
|
|
18
19
|
// Singleton registry instance
|
|
19
20
|
const registry = new TemplateRegistry();
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Pattern Integration Helpers
|
|
23
|
+
// =============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Build PatternContext from detected patterns for use in generation
|
|
26
|
+
*/
|
|
27
|
+
function buildPatternContext(detectedPatterns, confidenceScores = {}) {
|
|
28
|
+
const detected = detectedPatterns.map(p => ({
|
|
29
|
+
pattern_id: p.pattern_id,
|
|
30
|
+
name: p.name,
|
|
31
|
+
category: p.category,
|
|
32
|
+
confidence: confidenceScores[p.pattern_id] || 0.5,
|
|
33
|
+
}));
|
|
34
|
+
// Separate preferred from avoid (anti-patterns)
|
|
35
|
+
const preferred = detected
|
|
36
|
+
.filter(p => !detectedPatterns.find(dp => dp.pattern_id === p.pattern_id)?.is_anti_pattern)
|
|
37
|
+
.map(p => p.pattern_id);
|
|
38
|
+
const avoid = detected
|
|
39
|
+
.filter(p => detectedPatterns.find(dp => dp.pattern_id === p.pattern_id)?.is_anti_pattern)
|
|
40
|
+
.map(p => p.pattern_id);
|
|
41
|
+
return { detected, preferred, avoid };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Detect patterns from package.json and build PatternContext
|
|
45
|
+
*/
|
|
46
|
+
async function detectPatternsFromPackageJson(packageJson) {
|
|
47
|
+
try {
|
|
48
|
+
// Get all patterns from registry
|
|
49
|
+
const patterns = await apiRequest('/api/v1/codedna/patterns');
|
|
50
|
+
const deps = {
|
|
51
|
+
...(packageJson.dependencies || {}),
|
|
52
|
+
...(packageJson.devDependencies || {}),
|
|
53
|
+
};
|
|
54
|
+
const detected = [];
|
|
55
|
+
const confidenceScores = {};
|
|
56
|
+
for (const pattern of patterns) {
|
|
57
|
+
let confidence = 0;
|
|
58
|
+
let matched = false;
|
|
59
|
+
for (const signal of pattern.package_signals) {
|
|
60
|
+
if (deps[signal]) {
|
|
61
|
+
matched = true;
|
|
62
|
+
confidence += 0.4; // Each package match adds confidence
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (matched) {
|
|
66
|
+
detected.push(pattern);
|
|
67
|
+
confidenceScores[pattern.pattern_id] = Math.min(confidence, 1.0);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return buildPatternContext(detected, confidenceScores);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.warn('Pattern detection failed, proceeding without patterns:', error);
|
|
74
|
+
return { detected: [], preferred: [], avoid: [] };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
20
77
|
/**
|
|
21
78
|
* Handle codedna_generate_api tool call
|
|
79
|
+
*
|
|
80
|
+
* Pattern-aware generation:
|
|
81
|
+
* - If package_json is provided, patterns are detected automatically
|
|
82
|
+
* - If patterns is provided in options, those are used directly
|
|
83
|
+
* - Generated code will use template variants matching detected patterns
|
|
22
84
|
*/
|
|
23
85
|
export async function handleGenerateApi(args) {
|
|
24
|
-
const { spec, framework, options = {} } = args;
|
|
86
|
+
const { spec, framework, options = {}, package_json } = args;
|
|
25
87
|
// Validate Entity DSL specification
|
|
26
88
|
const validation = validateSpec(spec);
|
|
27
89
|
if (!validation.valid) {
|
|
@@ -51,8 +113,18 @@ export async function handleGenerateApi(args) {
|
|
|
51
113
|
}
|
|
52
114
|
try {
|
|
53
115
|
const startTime = Date.now();
|
|
116
|
+
// Detect patterns if package.json provided
|
|
117
|
+
let patternContext = options.patterns;
|
|
118
|
+
if (!patternContext && package_json) {
|
|
119
|
+
patternContext = await detectPatternsFromPackageJson(package_json);
|
|
120
|
+
}
|
|
121
|
+
// Merge pattern context into options
|
|
122
|
+
const generationOptions = {
|
|
123
|
+
...options,
|
|
124
|
+
patterns: patternContext,
|
|
125
|
+
};
|
|
54
126
|
// Generate code
|
|
55
|
-
const result = await generator.generate(validation.parsed,
|
|
127
|
+
const result = await generator.generate(validation.parsed, generationOptions);
|
|
56
128
|
const executionTime = Date.now() - startTime;
|
|
57
129
|
// Track successful generation
|
|
58
130
|
await analytics.trackGeneration({
|
|
@@ -65,13 +137,16 @@ export async function handleGenerateApi(args) {
|
|
|
65
137
|
filesGenerated: result.metadata.filesGenerated,
|
|
66
138
|
linesOfCode: result.metadata.linesOfCode,
|
|
67
139
|
tokensSaved: result.metadata.estimatedTokensSaved,
|
|
68
|
-
options:
|
|
140
|
+
options: generationOptions,
|
|
69
141
|
executionTimeMs: executionTime,
|
|
70
142
|
});
|
|
71
143
|
return {
|
|
72
144
|
success: true,
|
|
73
145
|
files: result.files,
|
|
74
146
|
metadata: result.metadata,
|
|
147
|
+
patternsApplied: patternContext?.detected?.length
|
|
148
|
+
? patternContext.detected.map(p => p.pattern_id)
|
|
149
|
+
: undefined,
|
|
75
150
|
tokenSavings: {
|
|
76
151
|
traditional: result.metadata.linesOfCode * 25, // ~25 tokens per line
|
|
77
152
|
codedna: 150, // ~150 tokens for MCP call
|
|
@@ -91,9 +166,14 @@ export async function handleGenerateApi(args) {
|
|
|
91
166
|
}
|
|
92
167
|
/**
|
|
93
168
|
* Handle codedna_generate_frontend tool call
|
|
169
|
+
*
|
|
170
|
+
* Pattern-aware generation:
|
|
171
|
+
* - If package_json is provided, patterns are detected automatically
|
|
172
|
+
* - Detects React Hook Form, Zod, TanStack Query, etc.
|
|
173
|
+
* - Uses template variants matching detected patterns
|
|
94
174
|
*/
|
|
95
175
|
export async function handleGenerateFrontend(args) {
|
|
96
|
-
const { spec, framework, options = {} } = args;
|
|
176
|
+
const { spec, framework, options = {}, package_json } = args;
|
|
97
177
|
// Validate Entity DSL specification
|
|
98
178
|
const validation = validateSpec(spec);
|
|
99
179
|
if (!validation.valid) {
|
|
@@ -118,8 +198,18 @@ export async function handleGenerateFrontend(args) {
|
|
|
118
198
|
}
|
|
119
199
|
try {
|
|
120
200
|
const startTime = Date.now();
|
|
201
|
+
// Detect patterns if package.json provided
|
|
202
|
+
let patternContext = options.patterns;
|
|
203
|
+
if (!patternContext && package_json) {
|
|
204
|
+
patternContext = await detectPatternsFromPackageJson(package_json);
|
|
205
|
+
}
|
|
206
|
+
// Merge pattern context into options
|
|
207
|
+
const generationOptions = {
|
|
208
|
+
...options,
|
|
209
|
+
patterns: patternContext,
|
|
210
|
+
};
|
|
121
211
|
// Generate code
|
|
122
|
-
const result = await generator.generate(validation.parsed,
|
|
212
|
+
const result = await generator.generate(validation.parsed, generationOptions);
|
|
123
213
|
const executionTime = Date.now() - startTime;
|
|
124
214
|
// Track successful generation
|
|
125
215
|
await analytics.trackGeneration({
|
|
@@ -132,13 +222,16 @@ export async function handleGenerateFrontend(args) {
|
|
|
132
222
|
filesGenerated: result.metadata.filesGenerated,
|
|
133
223
|
linesOfCode: result.metadata.linesOfCode,
|
|
134
224
|
tokensSaved: result.metadata.estimatedTokensSaved,
|
|
135
|
-
options:
|
|
225
|
+
options: generationOptions,
|
|
136
226
|
executionTimeMs: executionTime,
|
|
137
227
|
});
|
|
138
228
|
return {
|
|
139
229
|
success: true,
|
|
140
230
|
files: result.files,
|
|
141
231
|
metadata: result.metadata,
|
|
232
|
+
patternsApplied: patternContext?.detected?.length
|
|
233
|
+
? patternContext.detected.map(p => p.pattern_id)
|
|
234
|
+
: undefined,
|
|
142
235
|
tokenSavings: {
|
|
143
236
|
traditional: result.metadata.linesOfCode * 25,
|
|
144
237
|
codedna: 150,
|
|
@@ -157,9 +250,13 @@ export async function handleGenerateFrontend(args) {
|
|
|
157
250
|
}
|
|
158
251
|
/**
|
|
159
252
|
* Handle codedna_generate_component tool call
|
|
253
|
+
*
|
|
254
|
+
* Pattern-aware generation:
|
|
255
|
+
* - If package_json is provided, patterns are detected automatically
|
|
256
|
+
* - Uses matching patterns for form libraries, validation, etc.
|
|
160
257
|
*/
|
|
161
258
|
export async function handleGenerateComponent(args) {
|
|
162
|
-
const { spec, type, framework, options = {} } = args;
|
|
259
|
+
const { spec, type, framework, options = {}, package_json } = args;
|
|
163
260
|
// Validate Entity DSL specification
|
|
164
261
|
const validation = validateSpec(spec);
|
|
165
262
|
if (!validation.valid) {
|
|
@@ -186,8 +283,18 @@ export async function handleGenerateComponent(args) {
|
|
|
186
283
|
}
|
|
187
284
|
try {
|
|
188
285
|
const startTime = Date.now();
|
|
286
|
+
// Detect patterns if package.json provided
|
|
287
|
+
let patternContext = options.patterns;
|
|
288
|
+
if (!patternContext && package_json) {
|
|
289
|
+
patternContext = await detectPatternsFromPackageJson(package_json);
|
|
290
|
+
}
|
|
291
|
+
// Merge pattern context into options
|
|
292
|
+
const generationOptions = {
|
|
293
|
+
...options,
|
|
294
|
+
patterns: patternContext,
|
|
295
|
+
};
|
|
189
296
|
const generator = new UIComponentGenerator(registry, type, framework);
|
|
190
|
-
const result = await generator.generate(validation.parsed,
|
|
297
|
+
const result = await generator.generate(validation.parsed, generationOptions);
|
|
191
298
|
const executionTime = Date.now() - startTime;
|
|
192
299
|
await analytics.trackGeneration({
|
|
193
300
|
operation: 'generate_component',
|
|
@@ -199,13 +306,16 @@ export async function handleGenerateComponent(args) {
|
|
|
199
306
|
filesGenerated: result.metadata.filesGenerated,
|
|
200
307
|
linesOfCode: result.metadata.linesOfCode,
|
|
201
308
|
tokensSaved: result.metadata.estimatedTokensSaved,
|
|
202
|
-
options:
|
|
309
|
+
options: generationOptions,
|
|
203
310
|
executionTimeMs: executionTime,
|
|
204
311
|
});
|
|
205
312
|
return {
|
|
206
313
|
success: true,
|
|
207
314
|
files: result.files,
|
|
208
315
|
metadata: result.metadata,
|
|
316
|
+
patternsApplied: patternContext?.detected?.length
|
|
317
|
+
? patternContext.detected.map(p => p.pattern_id)
|
|
318
|
+
: undefined,
|
|
209
319
|
tokenSavings: {
|
|
210
320
|
traditional: result.metadata.linesOfCode * 25,
|
|
211
321
|
codedna: 100,
|
|
@@ -224,18 +334,47 @@ export async function handleGenerateComponent(args) {
|
|
|
224
334
|
}
|
|
225
335
|
/**
|
|
226
336
|
* Handle codedna_list_generators tool call
|
|
337
|
+
* @param args.domain Optional domain filter: 'api' | 'frontend' | 'component'
|
|
227
338
|
*/
|
|
228
|
-
export async function handleListGenerators() {
|
|
339
|
+
export async function handleListGenerators(args) {
|
|
229
340
|
try {
|
|
230
|
-
const
|
|
341
|
+
const domain = args?.domain;
|
|
342
|
+
const generators = await registry.listGenerators(domain);
|
|
343
|
+
// Collect unique UI libraries across all generators
|
|
344
|
+
const allUiLibraries = new Set();
|
|
345
|
+
generators.forEach(gen => {
|
|
346
|
+
gen.uiLibraries?.forEach(lib => allUiLibraries.add(lib));
|
|
347
|
+
});
|
|
348
|
+
// Group by domain for easier discovery (only when not filtered)
|
|
349
|
+
const byDomain = domain ? undefined : generators.reduce((acc, gen) => {
|
|
350
|
+
const d = gen.domain || 'unknown';
|
|
351
|
+
if (!acc[d])
|
|
352
|
+
acc[d] = [];
|
|
353
|
+
acc[d].push(gen);
|
|
354
|
+
return acc;
|
|
355
|
+
}, {});
|
|
231
356
|
return {
|
|
232
357
|
generators,
|
|
358
|
+
...(byDomain && { byDomain }),
|
|
233
359
|
summary: {
|
|
234
360
|
total: generators.length,
|
|
361
|
+
...(domain && { filteredByDomain: domain }),
|
|
235
362
|
byFramework: generators.reduce((acc, gen) => {
|
|
236
363
|
acc[gen.framework] = (acc[gen.framework] || 0) + 1;
|
|
237
364
|
return acc;
|
|
238
365
|
}, {}),
|
|
366
|
+
byDomain: domain ? undefined : generators.reduce((acc, gen) => {
|
|
367
|
+
const d = gen.domain || 'unknown';
|
|
368
|
+
acc[d] = (acc[d] || 0) + 1;
|
|
369
|
+
return acc;
|
|
370
|
+
}, {}),
|
|
371
|
+
availableUiLibraries: Array.from(allUiLibraries),
|
|
372
|
+
},
|
|
373
|
+
usage: {
|
|
374
|
+
api: 'codedna_generate_api(spec, framework, options)',
|
|
375
|
+
frontend: 'codedna_generate_frontend(spec, framework, options)',
|
|
376
|
+
component: 'codedna_generate_component(spec, type, framework, options)',
|
|
377
|
+
note: 'Check generator.uiLibraries for available UI options. Pass ui: "shadcn"|"mui"|"chakra" in options.',
|
|
239
378
|
},
|
|
240
379
|
};
|
|
241
380
|
}
|
|
@@ -281,3 +420,232 @@ export async function handleValidateSpec(args) {
|
|
|
281
420
|
errors: validation.errors,
|
|
282
421
|
};
|
|
283
422
|
}
|
|
423
|
+
// =============================================================================
|
|
424
|
+
// Pattern Library Handlers
|
|
425
|
+
// =============================================================================
|
|
426
|
+
/**
|
|
427
|
+
* Handle codedna_list_patterns tool call
|
|
428
|
+
* Lists patterns from the library, optionally filtered by category
|
|
429
|
+
*/
|
|
430
|
+
export async function handleListPatterns(args) {
|
|
431
|
+
try {
|
|
432
|
+
const { category, recommended_only, include_anti_patterns = true } = args || {};
|
|
433
|
+
// Build query parameters
|
|
434
|
+
const params = new URLSearchParams();
|
|
435
|
+
if (category)
|
|
436
|
+
params.set('category', category);
|
|
437
|
+
if (recommended_only)
|
|
438
|
+
params.set('recommended_only', 'true');
|
|
439
|
+
if (!include_anti_patterns)
|
|
440
|
+
params.set('exclude_anti_patterns', 'true');
|
|
441
|
+
const patterns = await apiRequest(`/api/v1/codedna/patterns?${params.toString()}`);
|
|
442
|
+
// Group by category for easier discovery
|
|
443
|
+
const byCategory = patterns.reduce((acc, p) => {
|
|
444
|
+
if (!acc[p.category])
|
|
445
|
+
acc[p.category] = [];
|
|
446
|
+
acc[p.category].push(p);
|
|
447
|
+
return acc;
|
|
448
|
+
}, {});
|
|
449
|
+
const recommended = patterns.filter(p => p.is_recommended);
|
|
450
|
+
const antiPatterns = patterns.filter(p => p.is_anti_pattern);
|
|
451
|
+
return {
|
|
452
|
+
patterns,
|
|
453
|
+
byCategory,
|
|
454
|
+
summary: {
|
|
455
|
+
total: patterns.length,
|
|
456
|
+
recommended: recommended.length,
|
|
457
|
+
antiPatterns: antiPatterns.length,
|
|
458
|
+
categories: Object.keys(byCategory),
|
|
459
|
+
},
|
|
460
|
+
usage: {
|
|
461
|
+
getDetails: 'codedna_get_pattern(pattern_id) - Get full pattern documentation',
|
|
462
|
+
detect: 'codedna_detect_patterns(project_path) - Detect patterns in codebase',
|
|
463
|
+
init: 'codedna_init_project(patterns) - Initialize project with chosen patterns',
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
return {
|
|
469
|
+
error: 'Failed to list patterns',
|
|
470
|
+
message: error instanceof Error ? error.message : String(error),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Handle codedna_get_pattern tool call
|
|
476
|
+
* Gets detailed pattern information including full markdown documentation
|
|
477
|
+
*/
|
|
478
|
+
export async function handleGetPattern(args) {
|
|
479
|
+
try {
|
|
480
|
+
const { pattern_id } = args;
|
|
481
|
+
if (!pattern_id) {
|
|
482
|
+
return { error: 'pattern_id is required' };
|
|
483
|
+
}
|
|
484
|
+
const pattern = await apiRequest(`/api/v1/codedna/patterns/${pattern_id}`);
|
|
485
|
+
return {
|
|
486
|
+
pattern,
|
|
487
|
+
sections: {
|
|
488
|
+
description: pattern.description,
|
|
489
|
+
whenToUse: pattern.conditions,
|
|
490
|
+
whenNotToUse: pattern.anti_conditions,
|
|
491
|
+
relatedPatterns: pattern.related_patterns,
|
|
492
|
+
migratesFrom: pattern.migrates_from,
|
|
493
|
+
},
|
|
494
|
+
detection: {
|
|
495
|
+
codeSignals: pattern.code_signals,
|
|
496
|
+
packageSignals: pattern.package_signals,
|
|
497
|
+
},
|
|
498
|
+
content: pattern.content, // Full markdown from R2
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
return {
|
|
503
|
+
error: 'Failed to get pattern',
|
|
504
|
+
message: error instanceof Error ? error.message : String(error),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Handle codedna_detect_patterns tool call
|
|
510
|
+
* Detects patterns in a project based on code and package.json signals
|
|
511
|
+
*/
|
|
512
|
+
export async function handleDetectPatterns(args) {
|
|
513
|
+
try {
|
|
514
|
+
const { project_path, package_json, code_samples } = args;
|
|
515
|
+
// Get all patterns with their detection signals
|
|
516
|
+
const patterns = await apiRequest('/api/v1/codedna/patterns');
|
|
517
|
+
const detected = [];
|
|
518
|
+
for (const pattern of patterns) {
|
|
519
|
+
const matchedSignals = [];
|
|
520
|
+
let confidence = 0;
|
|
521
|
+
// Check package.json signals
|
|
522
|
+
if (package_json?.dependencies || package_json?.devDependencies) {
|
|
523
|
+
const deps = {
|
|
524
|
+
...(package_json.dependencies || {}),
|
|
525
|
+
...(package_json.devDependencies || {}),
|
|
526
|
+
};
|
|
527
|
+
for (const signal of pattern.package_signals) {
|
|
528
|
+
if (deps[signal]) {
|
|
529
|
+
matchedSignals.push(`package:${signal}`);
|
|
530
|
+
confidence += 0.4; // Package match is strong signal
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// Check code signals in samples
|
|
535
|
+
if (code_samples) {
|
|
536
|
+
const allCode = code_samples.join('\n');
|
|
537
|
+
for (const signal of pattern.code_signals) {
|
|
538
|
+
if (allCode.includes(signal)) {
|
|
539
|
+
matchedSignals.push(`code:${signal}`);
|
|
540
|
+
confidence += 0.2; // Code match is moderate signal
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
// Add to detected if any signals matched
|
|
545
|
+
if (matchedSignals.length > 0) {
|
|
546
|
+
detected.push({
|
|
547
|
+
pattern,
|
|
548
|
+
confidence: Math.min(confidence, 1.0),
|
|
549
|
+
signals: matchedSignals,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
// Sort by confidence
|
|
554
|
+
detected.sort((a, b) => b.confidence - a.confidence);
|
|
555
|
+
// Separate recommended vs detected anti-patterns
|
|
556
|
+
const goodPatterns = detected.filter(d => !d.pattern.is_anti_pattern);
|
|
557
|
+
const antiPatterns = detected.filter(d => d.pattern.is_anti_pattern);
|
|
558
|
+
return {
|
|
559
|
+
detected: goodPatterns,
|
|
560
|
+
warnings: antiPatterns.length > 0 ? {
|
|
561
|
+
message: 'Anti-patterns detected in codebase',
|
|
562
|
+
patterns: antiPatterns,
|
|
563
|
+
} : undefined,
|
|
564
|
+
summary: {
|
|
565
|
+
totalDetected: detected.length,
|
|
566
|
+
goodPatterns: goodPatterns.length,
|
|
567
|
+
antiPatterns: antiPatterns.length,
|
|
568
|
+
highConfidence: detected.filter(d => d.confidence >= 0.6).length,
|
|
569
|
+
},
|
|
570
|
+
recommendations: patterns
|
|
571
|
+
.filter(p => p.is_recommended && !detected.find(d => d.pattern.pattern_id === p.pattern_id))
|
|
572
|
+
.slice(0, 5)
|
|
573
|
+
.map(p => ({
|
|
574
|
+
pattern_id: p.pattern_id,
|
|
575
|
+
name: p.name,
|
|
576
|
+
description: p.description,
|
|
577
|
+
reason: 'Recommended pattern not yet detected in codebase',
|
|
578
|
+
})),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
return {
|
|
583
|
+
error: 'Failed to detect patterns',
|
|
584
|
+
message: error instanceof Error ? error.message : String(error),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Handle codedna_init_project tool call
|
|
590
|
+
* Initializes a new project with recommended patterns
|
|
591
|
+
*/
|
|
592
|
+
export async function handleInitProject(args) {
|
|
593
|
+
try {
|
|
594
|
+
const { project_id, patterns: selectedPatterns, auto_detect = true, project_type = 'new' } = args;
|
|
595
|
+
if (!project_id) {
|
|
596
|
+
return { error: 'project_id is required' };
|
|
597
|
+
}
|
|
598
|
+
// Get all patterns
|
|
599
|
+
const allPatterns = await apiRequest('/api/v1/codedna/patterns');
|
|
600
|
+
let patternsToApply;
|
|
601
|
+
if (project_type === 'new' && !selectedPatterns) {
|
|
602
|
+
// For new projects, use recommended patterns
|
|
603
|
+
patternsToApply = allPatterns
|
|
604
|
+
.filter(p => p.is_recommended && !p.is_anti_pattern)
|
|
605
|
+
.map(p => p.pattern_id);
|
|
606
|
+
}
|
|
607
|
+
else if (selectedPatterns) {
|
|
608
|
+
// Use explicitly selected patterns
|
|
609
|
+
patternsToApply = selectedPatterns;
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
// No patterns specified
|
|
613
|
+
patternsToApply = [];
|
|
614
|
+
}
|
|
615
|
+
// Store pattern associations in memory
|
|
616
|
+
const stored = await apiRequest(`/api/v1/codedna/projects/${project_id}/patterns`, 'POST', {
|
|
617
|
+
patterns: patternsToApply,
|
|
618
|
+
detection_source: selectedPatterns ? 'user_selected' : 'recommended',
|
|
619
|
+
});
|
|
620
|
+
const appliedPatterns = allPatterns.filter(p => patternsToApply.includes(p.pattern_id));
|
|
621
|
+
return {
|
|
622
|
+
success: stored.success,
|
|
623
|
+
project_id,
|
|
624
|
+
project_type,
|
|
625
|
+
patterns: appliedPatterns.map(p => ({
|
|
626
|
+
pattern_id: p.pattern_id,
|
|
627
|
+
name: p.name,
|
|
628
|
+
category: p.category,
|
|
629
|
+
description: p.description,
|
|
630
|
+
})),
|
|
631
|
+
summary: {
|
|
632
|
+
totalPatterns: patternsToApply.length,
|
|
633
|
+
byCategory: appliedPatterns.reduce((acc, p) => {
|
|
634
|
+
acc[p.category] = (acc[p.category] || 0) + 1;
|
|
635
|
+
return acc;
|
|
636
|
+
}, {}),
|
|
637
|
+
},
|
|
638
|
+
nextSteps: [
|
|
639
|
+
'Patterns are now associated with this project',
|
|
640
|
+
'CodeDNA generators will use these patterns for template selection',
|
|
641
|
+
'Use codedna_detect_patterns to update as codebase evolves',
|
|
642
|
+
],
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
catch (error) {
|
|
646
|
+
return {
|
|
647
|
+
error: 'Failed to initialize project patterns',
|
|
648
|
+
message: error instanceof Error ? error.message : String(error),
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
}
|
|
@@ -13,7 +13,7 @@ import { formatContextForClaude } from '../helpers/formatter.js';
|
|
|
13
13
|
import { createTask, listTasks, getTask, claimTask, releaseTask, updateTaskStatus, addTaskContext, getTaskSummary, heartbeatTask, parseJsonArray, getDispatchableTasks, getExecutionContext, resolveTaskDependencies, getEpicStatus, getActiveTaskCount, } from '../helpers/tasks.js';
|
|
14
14
|
import { detectTimedOutTasks, retryTask, failTask, autoRetryTimedOutTasks, } from '../helpers/tasks-retry.js';
|
|
15
15
|
import { detectLibrariesFromPlan } from '../helpers/library-detection.js';
|
|
16
|
-
import { handleGenerateApi, handleGenerateFrontend, handleGenerateComponent, handleListGenerators, handleValidateSpec, } from './codedna-handlers.js';
|
|
16
|
+
import { handleGenerateApi, handleGenerateFrontend, handleGenerateComponent, handleListGenerators, handleValidateSpec, handleListPatterns, handleGetPattern, handleDetectPatterns, handleInitProject, } from './codedna-handlers.js';
|
|
17
17
|
export function registerToolHandlers(server) {
|
|
18
18
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
19
19
|
const { name, arguments: args } = request.params;
|
|
@@ -758,7 +758,7 @@ export function registerToolHandlers(server) {
|
|
|
758
758
|
blocked: '🚫', review: '👀', done: '✅', cancelled: '❌'
|
|
759
759
|
};
|
|
760
760
|
const emoji = statusEmoji[t.status] || '📝';
|
|
761
|
-
let line = `- ${emoji} **${t.title}** (
|
|
761
|
+
let line = `- ${emoji} **${t.title}** (\`${t.id}\`)`;
|
|
762
762
|
line += ` [${t.status}]`;
|
|
763
763
|
if (t.priority !== 'medium')
|
|
764
764
|
line += ` [${t.priority}]`;
|
|
@@ -817,12 +817,12 @@ export function registerToolHandlers(server) {
|
|
|
817
817
|
}
|
|
818
818
|
if (data.parent) {
|
|
819
819
|
output += `\n## Parent\n`;
|
|
820
|
-
output += `- **${data.parent.title}** (
|
|
820
|
+
output += `- **${data.parent.title}** (\`${data.parent.id}\`) [${data.parent.status}]\n`;
|
|
821
821
|
}
|
|
822
822
|
if (data.subtasks?.length) {
|
|
823
823
|
output += `\n## Subtasks (${data.subtasks.length})\n`;
|
|
824
824
|
data.subtasks.forEach(s => {
|
|
825
|
-
output += `- ${s.title} (
|
|
825
|
+
output += `- ${s.title} (\`${s.id}\`) [${s.status}]\n`;
|
|
826
826
|
});
|
|
827
827
|
}
|
|
828
828
|
if (data.context?.length) {
|
|
@@ -1430,14 +1430,20 @@ export function registerToolHandlers(server) {
|
|
|
1430
1430
|
// =========================================================================
|
|
1431
1431
|
case 'codebase_map': {
|
|
1432
1432
|
// Use raw fetch since this endpoint returns markdown, not JSON
|
|
1433
|
+
// But we still need auth header
|
|
1434
|
+
const { getConfig } = await import('../helpers/config-manager.js');
|
|
1435
|
+
const config = getConfig();
|
|
1436
|
+
const apiKey = config.apiKey || process.env.CLAUDETOOLS_API_KEY || process.env.MEMORY_API_KEY;
|
|
1433
1437
|
const url = `${API_BASE_URL}/api/v1/codebase/${projectId}/map`;
|
|
1434
|
-
const response = await fetch(url
|
|
1438
|
+
const response = await fetch(url, {
|
|
1439
|
+
headers: apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {},
|
|
1440
|
+
});
|
|
1435
1441
|
mcpLogger.toolResult(name, response.ok, timer());
|
|
1436
1442
|
if (!response.ok) {
|
|
1437
1443
|
return {
|
|
1438
1444
|
content: [{
|
|
1439
1445
|
type: 'text',
|
|
1440
|
-
text: `# No Codebase Map Found\n\nThe codebase map hasn't been generated yet. Use the file watcher or API to generate one
|
|
1446
|
+
text: `# No Codebase Map Found\n\nThe codebase map hasn't been generated yet. Use the file watcher or API to generate one.\n\n**Debug:** \`${url}\` returned ${response.status}`,
|
|
1441
1447
|
}],
|
|
1442
1448
|
};
|
|
1443
1449
|
}
|
|
@@ -1697,7 +1703,7 @@ export function registerToolHandlers(server) {
|
|
|
1697
1703
|
};
|
|
1698
1704
|
}
|
|
1699
1705
|
case 'codedna_list_generators': {
|
|
1700
|
-
const result = await handleListGenerators();
|
|
1706
|
+
const result = await handleListGenerators(args);
|
|
1701
1707
|
mcpLogger.toolResult(name, true, timer());
|
|
1702
1708
|
return {
|
|
1703
1709
|
content: [{
|
|
@@ -1716,6 +1722,49 @@ export function registerToolHandlers(server) {
|
|
|
1716
1722
|
}],
|
|
1717
1723
|
};
|
|
1718
1724
|
}
|
|
1725
|
+
// =====================================================================
|
|
1726
|
+
// CodeDNA Pattern Library Tools
|
|
1727
|
+
// =====================================================================
|
|
1728
|
+
case 'codedna_list_patterns': {
|
|
1729
|
+
const result = await handleListPatterns(args);
|
|
1730
|
+
mcpLogger.toolResult(name, true, timer());
|
|
1731
|
+
return {
|
|
1732
|
+
content: [{
|
|
1733
|
+
type: 'text',
|
|
1734
|
+
text: JSON.stringify(result, null, 2),
|
|
1735
|
+
}],
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
case 'codedna_get_pattern': {
|
|
1739
|
+
const result = await handleGetPattern(args);
|
|
1740
|
+
mcpLogger.toolResult(name, true, timer());
|
|
1741
|
+
return {
|
|
1742
|
+
content: [{
|
|
1743
|
+
type: 'text',
|
|
1744
|
+
text: JSON.stringify(result, null, 2),
|
|
1745
|
+
}],
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
case 'codedna_detect_patterns': {
|
|
1749
|
+
const result = await handleDetectPatterns(args);
|
|
1750
|
+
mcpLogger.toolResult(name, true, timer());
|
|
1751
|
+
return {
|
|
1752
|
+
content: [{
|
|
1753
|
+
type: 'text',
|
|
1754
|
+
text: JSON.stringify(result, null, 2),
|
|
1755
|
+
}],
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
case 'codedna_init_project': {
|
|
1759
|
+
const result = await handleInitProject(args);
|
|
1760
|
+
mcpLogger.toolResult(name, true, timer());
|
|
1761
|
+
return {
|
|
1762
|
+
content: [{
|
|
1763
|
+
type: 'text',
|
|
1764
|
+
text: JSON.stringify(result, null, 2),
|
|
1765
|
+
}],
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1719
1768
|
default:
|
|
1720
1769
|
throw new Error(`Unknown tool: ${name}`);
|
|
1721
1770
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codebase Mapper
|
|
3
|
+
*
|
|
4
|
+
* Client-side AST analysis to generate codebase maps.
|
|
5
|
+
* Runs in the MCP server and uploads results to the API.
|
|
6
|
+
*/
|
|
7
|
+
export interface FileSymbol {
|
|
8
|
+
name: string;
|
|
9
|
+
type: 'function' | 'class' | 'interface' | 'type' | 'const' | 'enum' | 'component';
|
|
10
|
+
exported: boolean;
|
|
11
|
+
line: number;
|
|
12
|
+
signature?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface FileEntry {
|
|
16
|
+
path: string;
|
|
17
|
+
relativePath: string;
|
|
18
|
+
role: FileRole;
|
|
19
|
+
symbols: FileSymbol[];
|
|
20
|
+
imports: string[];
|
|
21
|
+
exports: string[];
|
|
22
|
+
dependencies: string[];
|
|
23
|
+
dependents: string[];
|
|
24
|
+
linesOfCode: number;
|
|
25
|
+
}
|
|
26
|
+
export type FileRole = 'entry-point' | 'route-handler' | 'api-endpoint' | 'mcp-tool' | 'component' | 'hook' | 'utility' | 'type-definitions' | 'config' | 'test' | 'model' | 'service' | 'extractor' | 'unknown';
|
|
27
|
+
export interface DirectoryEntry {
|
|
28
|
+
path: string;
|
|
29
|
+
role: string;
|
|
30
|
+
fileCount: number;
|
|
31
|
+
primaryLanguage: string;
|
|
32
|
+
keyFiles: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface CodebaseIndex {
|
|
35
|
+
version: string;
|
|
36
|
+
generatedAt: string;
|
|
37
|
+
projectRoot: string;
|
|
38
|
+
summary: {
|
|
39
|
+
totalFiles: number;
|
|
40
|
+
totalSymbols: number;
|
|
41
|
+
languages: Record<string, number>;
|
|
42
|
+
frameworks: string[];
|
|
43
|
+
};
|
|
44
|
+
directories: Record<string, DirectoryEntry>;
|
|
45
|
+
files: Record<string, FileEntry>;
|
|
46
|
+
symbols: Record<string, {
|
|
47
|
+
file: string;
|
|
48
|
+
line: number;
|
|
49
|
+
type: string;
|
|
50
|
+
exported: boolean;
|
|
51
|
+
}[]>;
|
|
52
|
+
callGraph: Record<string, string[]>;
|
|
53
|
+
reverseCallGraph: Record<string, string[]>;
|
|
54
|
+
}
|
|
55
|
+
export declare function generateCodebaseMap(projectRoot: string, projectId: string, apiKey: string): Promise<{
|
|
56
|
+
success: boolean;
|
|
57
|
+
error?: string;
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Generate a codebase map locally without uploading
|
|
61
|
+
* Useful for previewing or debugging
|
|
62
|
+
*/
|
|
63
|
+
export declare function generateCodebaseMapLocal(projectRoot: string): {
|
|
64
|
+
index: CodebaseIndex;
|
|
65
|
+
markdown: string;
|
|
66
|
+
};
|