driftdetect-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -0
- package/dist/bin/server.d.ts +20 -0
- package/dist/bin/server.d.ts.map +1 -0
- package/dist/bin/server.js +40 -0
- package/dist/bin/server.js.map +1 -0
- package/dist/feedback.d.ts +80 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +197 -0
- package/dist/feedback.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/packs.d.ts +86 -0
- package/dist/packs.d.ts.map +1 -0
- package/dist/packs.js +654 -0
- package/dist/packs.js.map +1 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +976 -0
- package/dist/server.js.map +1 -0
- package/package.json +50 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,976 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drift MCP Server Implementation
|
|
3
|
+
*
|
|
4
|
+
* Provides structured access to drift functionality for AI agents.
|
|
5
|
+
*/
|
|
6
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { PatternStore, ManifestStore, } from 'driftdetect-core';
|
|
9
|
+
import { PackManager } from './packs.js';
|
|
10
|
+
import { FeedbackManager } from './feedback.js';
|
|
11
|
+
const PATTERN_CATEGORIES = [
|
|
12
|
+
'structural', 'components', 'styling', 'api', 'auth', 'errors',
|
|
13
|
+
'data-access', 'testing', 'logging', 'security', 'config',
|
|
14
|
+
'types', 'performance', 'accessibility', 'documentation',
|
|
15
|
+
];
|
|
16
|
+
const TOOLS = [
|
|
17
|
+
{
|
|
18
|
+
name: 'drift_status',
|
|
19
|
+
description: 'Get overall codebase pattern health and statistics. Use this first to understand what patterns drift has learned.',
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {},
|
|
23
|
+
required: [],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'drift_patterns',
|
|
28
|
+
description: 'Get patterns for specific categories. Returns learned patterns with confidence scores and locations.',
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
categories: {
|
|
33
|
+
type: 'array',
|
|
34
|
+
items: { type: 'string' },
|
|
35
|
+
description: `Categories to query. Valid: ${PATTERN_CATEGORIES.join(', ')}`,
|
|
36
|
+
},
|
|
37
|
+
minConfidence: {
|
|
38
|
+
type: 'number',
|
|
39
|
+
description: 'Minimum confidence score (0.0-1.0)',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: [],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'drift_files',
|
|
47
|
+
description: 'Get patterns found in a specific file or file pattern (glob supported).',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
path: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'File path or glob pattern (e.g., "src/api/*.ts")',
|
|
54
|
+
},
|
|
55
|
+
category: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Filter by category',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
required: ['path'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'drift_where',
|
|
65
|
+
description: 'Find where a pattern is used across the codebase.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
pattern: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Pattern name or ID to search for',
|
|
72
|
+
},
|
|
73
|
+
category: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: 'Filter by category',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: ['pattern'],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'drift_export',
|
|
83
|
+
description: 'Export patterns in AI-optimized format. Use this to get full context for code generation.',
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: {
|
|
87
|
+
categories: {
|
|
88
|
+
type: 'array',
|
|
89
|
+
items: { type: 'string' },
|
|
90
|
+
description: 'Categories to export (defaults to all)',
|
|
91
|
+
},
|
|
92
|
+
format: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
enum: ['ai-context', 'json', 'summary'],
|
|
95
|
+
description: 'Export format (default: ai-context)',
|
|
96
|
+
},
|
|
97
|
+
compact: {
|
|
98
|
+
type: 'boolean',
|
|
99
|
+
description: 'Compact output with fewer details',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
required: [],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'drift_contracts',
|
|
107
|
+
description: 'Get frontend/backend API contract status. Shows mismatches between API calls and endpoints.',
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
status: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
enum: ['all', 'verified', 'mismatch', 'discovered'],
|
|
114
|
+
description: 'Filter by contract status',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
required: [],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'drift_examples',
|
|
122
|
+
description: 'Get actual code examples for patterns. Returns real code snippets from the codebase that demonstrate how patterns are implemented. Use this to understand HOW to implement patterns. Automatically filters out documentation, config files, and deprecated code.',
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
categories: {
|
|
127
|
+
type: 'array',
|
|
128
|
+
items: { type: 'string' },
|
|
129
|
+
description: `Categories to get examples for. Valid: ${PATTERN_CATEGORIES.join(', ')}`,
|
|
130
|
+
},
|
|
131
|
+
pattern: {
|
|
132
|
+
type: 'string',
|
|
133
|
+
description: 'Specific pattern name or ID to get examples for',
|
|
134
|
+
},
|
|
135
|
+
maxExamples: {
|
|
136
|
+
type: 'number',
|
|
137
|
+
description: 'Maximum examples per pattern (default: 3)',
|
|
138
|
+
},
|
|
139
|
+
contextLines: {
|
|
140
|
+
type: 'number',
|
|
141
|
+
description: 'Lines of context around each match (default: 10)',
|
|
142
|
+
},
|
|
143
|
+
includeDeprecated: {
|
|
144
|
+
type: 'boolean',
|
|
145
|
+
description: 'Include deprecated/legacy code examples (default: false)',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
required: [],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'drift_pack',
|
|
153
|
+
description: 'Get a pre-defined pattern pack for common development tasks. Packs are cached and auto-invalidate when patterns change. Also supports learning new packs from usage patterns.',
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: {
|
|
157
|
+
action: {
|
|
158
|
+
type: 'string',
|
|
159
|
+
enum: ['get', 'list', 'suggest', 'create', 'delete', 'infer'],
|
|
160
|
+
description: 'Action to perform (default: "get"). "suggest" shows packs based on usage, "infer" suggests from file structure, "create" saves a custom pack, "delete" removes a custom pack',
|
|
161
|
+
},
|
|
162
|
+
name: {
|
|
163
|
+
type: 'string',
|
|
164
|
+
description: 'Pack name for get/create/delete actions',
|
|
165
|
+
},
|
|
166
|
+
refresh: {
|
|
167
|
+
type: 'boolean',
|
|
168
|
+
description: 'Force regenerate the pack even if cached (default: false)',
|
|
169
|
+
},
|
|
170
|
+
list: {
|
|
171
|
+
type: 'boolean',
|
|
172
|
+
description: '[DEPRECATED: use action="list"] List all available packs',
|
|
173
|
+
},
|
|
174
|
+
// For create action
|
|
175
|
+
description: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
description: 'Pack description (for create action)',
|
|
178
|
+
},
|
|
179
|
+
categories: {
|
|
180
|
+
type: 'array',
|
|
181
|
+
items: { type: 'string' },
|
|
182
|
+
description: 'Categories to include in pack (for create action)',
|
|
183
|
+
},
|
|
184
|
+
patterns: {
|
|
185
|
+
type: 'array',
|
|
186
|
+
items: { type: 'string' },
|
|
187
|
+
description: 'Pattern name filters (for create action)',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: [],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'drift_feedback',
|
|
195
|
+
description: 'Provide feedback on pattern examples to improve future suggestions. Good feedback helps drift learn which files produce useful examples.',
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
properties: {
|
|
199
|
+
action: {
|
|
200
|
+
type: 'string',
|
|
201
|
+
enum: ['rate', 'stats', 'clear'],
|
|
202
|
+
description: 'Action: "rate" to rate an example, "stats" to see feedback statistics, "clear" to reset all feedback',
|
|
203
|
+
},
|
|
204
|
+
patternId: {
|
|
205
|
+
type: 'string',
|
|
206
|
+
description: 'Pattern ID (for rate action)',
|
|
207
|
+
},
|
|
208
|
+
patternName: {
|
|
209
|
+
type: 'string',
|
|
210
|
+
description: 'Pattern name (for rate action)',
|
|
211
|
+
},
|
|
212
|
+
category: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
description: 'Pattern category (for rate action)',
|
|
215
|
+
},
|
|
216
|
+
file: {
|
|
217
|
+
type: 'string',
|
|
218
|
+
description: 'File path of the example (for rate action)',
|
|
219
|
+
},
|
|
220
|
+
line: {
|
|
221
|
+
type: 'number',
|
|
222
|
+
description: 'Line number of the example (for rate action)',
|
|
223
|
+
},
|
|
224
|
+
rating: {
|
|
225
|
+
type: 'string',
|
|
226
|
+
enum: ['good', 'bad', 'irrelevant'],
|
|
227
|
+
description: 'Rating: "good" = useful example, "bad" = wrong/misleading, "irrelevant" = not related to pattern',
|
|
228
|
+
},
|
|
229
|
+
reason: {
|
|
230
|
+
type: 'string',
|
|
231
|
+
description: 'Optional reason for the rating',
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
required: [],
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
];
|
|
238
|
+
export function createDriftMCPServer(config) {
|
|
239
|
+
const server = new Server({ name: 'drift', version: '0.1.0' }, { capabilities: { tools: {} } });
|
|
240
|
+
const patternStore = new PatternStore({ rootDir: config.projectRoot });
|
|
241
|
+
const manifestStore = new ManifestStore(config.projectRoot);
|
|
242
|
+
const packManager = new PackManager(config.projectRoot, patternStore);
|
|
243
|
+
const feedbackManager = new FeedbackManager(config.projectRoot);
|
|
244
|
+
// List available tools
|
|
245
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
246
|
+
tools: TOOLS,
|
|
247
|
+
}));
|
|
248
|
+
// Handle tool calls
|
|
249
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
250
|
+
const { name, arguments: args } = request.params;
|
|
251
|
+
try {
|
|
252
|
+
switch (name) {
|
|
253
|
+
case 'drift_status':
|
|
254
|
+
return await handleStatus(patternStore);
|
|
255
|
+
case 'drift_patterns':
|
|
256
|
+
return await handlePatterns(patternStore, args);
|
|
257
|
+
case 'drift_files':
|
|
258
|
+
return await handleFiles(manifestStore, args);
|
|
259
|
+
case 'drift_where':
|
|
260
|
+
return await handleWhere(patternStore, args);
|
|
261
|
+
case 'drift_export':
|
|
262
|
+
return await handleExport(patternStore, args);
|
|
263
|
+
case 'drift_contracts':
|
|
264
|
+
return await handleContracts(config.projectRoot, args);
|
|
265
|
+
case 'drift_examples':
|
|
266
|
+
return await handleExamples(config.projectRoot, patternStore, packManager, feedbackManager, args);
|
|
267
|
+
case 'drift_pack':
|
|
268
|
+
return await handlePack(packManager, args);
|
|
269
|
+
case 'drift_feedback':
|
|
270
|
+
return await handleFeedback(feedbackManager, args);
|
|
271
|
+
default:
|
|
272
|
+
return {
|
|
273
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
274
|
+
isError: true,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
return {
|
|
280
|
+
content: [{
|
|
281
|
+
type: 'text',
|
|
282
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
283
|
+
}],
|
|
284
|
+
isError: true,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return server;
|
|
289
|
+
}
|
|
290
|
+
async function handleStatus(store) {
|
|
291
|
+
await store.initialize();
|
|
292
|
+
const stats = store.getStats();
|
|
293
|
+
const result = {
|
|
294
|
+
totalPatterns: stats.totalPatterns,
|
|
295
|
+
byCategory: stats.byCategory,
|
|
296
|
+
byConfidence: stats.byConfidenceLevel,
|
|
297
|
+
byStatus: stats.byStatus,
|
|
298
|
+
totalLocations: stats.totalLocations,
|
|
299
|
+
totalOutliers: stats.totalOutliers,
|
|
300
|
+
categories: PATTERN_CATEGORIES,
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
async function handlePatterns(store, args) {
|
|
307
|
+
await store.initialize();
|
|
308
|
+
let patterns = store.getAll();
|
|
309
|
+
// Filter by categories
|
|
310
|
+
if (args.categories && args.categories.length > 0) {
|
|
311
|
+
const cats = new Set(args.categories);
|
|
312
|
+
patterns = patterns.filter(p => cats.has(p.category));
|
|
313
|
+
}
|
|
314
|
+
// Filter by confidence
|
|
315
|
+
if (args.minConfidence !== undefined) {
|
|
316
|
+
patterns = patterns.filter(p => p.confidence.score >= args.minConfidence);
|
|
317
|
+
}
|
|
318
|
+
// Format for AI consumption
|
|
319
|
+
const result = patterns.map(p => ({
|
|
320
|
+
id: p.id,
|
|
321
|
+
name: p.name,
|
|
322
|
+
category: p.category,
|
|
323
|
+
subcategory: p.subcategory,
|
|
324
|
+
description: p.description,
|
|
325
|
+
confidence: p.confidence.score,
|
|
326
|
+
confidenceLevel: p.confidence.level,
|
|
327
|
+
locationCount: p.locations.length,
|
|
328
|
+
outlierCount: p.outliers.length,
|
|
329
|
+
exampleLocations: p.locations.slice(0, 3).map(l => ({
|
|
330
|
+
file: l.file,
|
|
331
|
+
line: l.line,
|
|
332
|
+
})),
|
|
333
|
+
}));
|
|
334
|
+
return {
|
|
335
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
async function handleFiles(store, args) {
|
|
339
|
+
await store.load();
|
|
340
|
+
const query = {
|
|
341
|
+
path: args.path,
|
|
342
|
+
};
|
|
343
|
+
if (args.category) {
|
|
344
|
+
query.category = args.category;
|
|
345
|
+
}
|
|
346
|
+
const result = store.queryFile(query);
|
|
347
|
+
if (!result) {
|
|
348
|
+
return {
|
|
349
|
+
content: [{ type: 'text', text: `No patterns found in "${args.path}"` }],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
async function handleWhere(store, args) {
|
|
357
|
+
await store.initialize();
|
|
358
|
+
const searchTerm = args.pattern.toLowerCase();
|
|
359
|
+
let patterns = store.getAll().filter(p => p.id.toLowerCase().includes(searchTerm) ||
|
|
360
|
+
p.name.toLowerCase().includes(searchTerm));
|
|
361
|
+
if (args.category) {
|
|
362
|
+
patterns = patterns.filter(p => p.category === args.category);
|
|
363
|
+
}
|
|
364
|
+
const result = patterns.map(p => ({
|
|
365
|
+
id: p.id,
|
|
366
|
+
name: p.name,
|
|
367
|
+
category: p.category,
|
|
368
|
+
locations: p.locations.map(l => ({
|
|
369
|
+
file: l.file,
|
|
370
|
+
line: l.line,
|
|
371
|
+
column: l.column,
|
|
372
|
+
})),
|
|
373
|
+
}));
|
|
374
|
+
return {
|
|
375
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
async function handleExport(store, args) {
|
|
379
|
+
await store.initialize();
|
|
380
|
+
let patterns = store.getAll();
|
|
381
|
+
// Filter by categories
|
|
382
|
+
if (args.categories && args.categories.length > 0) {
|
|
383
|
+
const cats = new Set(args.categories);
|
|
384
|
+
patterns = patterns.filter(p => cats.has(p.category));
|
|
385
|
+
}
|
|
386
|
+
const format = args.format ?? 'ai-context';
|
|
387
|
+
if (format === 'ai-context') {
|
|
388
|
+
// Optimized format for LLM consumption
|
|
389
|
+
const grouped = new Map();
|
|
390
|
+
for (const p of patterns) {
|
|
391
|
+
if (!grouped.has(p.category)) {
|
|
392
|
+
grouped.set(p.category, []);
|
|
393
|
+
}
|
|
394
|
+
grouped.get(p.category).push(p);
|
|
395
|
+
}
|
|
396
|
+
let output = '# Codebase Patterns\n\n';
|
|
397
|
+
output += `Total: ${patterns.length} patterns across ${grouped.size} categories\n\n`;
|
|
398
|
+
for (const [category, categoryPatterns] of grouped) {
|
|
399
|
+
output += `## ${category.toUpperCase()}\n\n`;
|
|
400
|
+
for (const p of categoryPatterns) {
|
|
401
|
+
output += `### ${p.name}\n`;
|
|
402
|
+
output += `- Confidence: ${(p.confidence.score * 100).toFixed(0)}%\n`;
|
|
403
|
+
output += `- Found in ${p.locations.length} locations\n`;
|
|
404
|
+
if (p.description) {
|
|
405
|
+
output += `- ${p.description}\n`;
|
|
406
|
+
}
|
|
407
|
+
if (!args.compact && p.locations.length > 0) {
|
|
408
|
+
output += `- Examples: ${p.locations.slice(0, 2).map(l => l.file).join(', ')}\n`;
|
|
409
|
+
}
|
|
410
|
+
output += '\n';
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
content: [{ type: 'text', text: output }],
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
if (format === 'summary') {
|
|
418
|
+
const stats = store.getStats();
|
|
419
|
+
return {
|
|
420
|
+
content: [{
|
|
421
|
+
type: 'text',
|
|
422
|
+
text: JSON.stringify({
|
|
423
|
+
totalPatterns: stats.totalPatterns,
|
|
424
|
+
byCategory: stats.byCategory,
|
|
425
|
+
byConfidence: stats.byConfidenceLevel,
|
|
426
|
+
}, null, 2),
|
|
427
|
+
}],
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
// JSON format
|
|
431
|
+
return {
|
|
432
|
+
content: [{ type: 'text', text: JSON.stringify(patterns, null, 2) }],
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
async function handleContracts(projectRoot, args) {
|
|
436
|
+
const fs = await import('node:fs/promises');
|
|
437
|
+
const path = await import('node:path');
|
|
438
|
+
const contractsDir = path.join(projectRoot, '.drift', 'contracts');
|
|
439
|
+
try {
|
|
440
|
+
const dirs = ['discovered', 'verified', 'mismatch'];
|
|
441
|
+
const contracts = [];
|
|
442
|
+
for (const dir of dirs) {
|
|
443
|
+
if (args.status && args.status !== 'all' && args.status !== dir) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
const dirPath = path.join(contractsDir, dir);
|
|
447
|
+
try {
|
|
448
|
+
const files = await fs.readdir(dirPath);
|
|
449
|
+
for (const file of files) {
|
|
450
|
+
if (file.endsWith('.json') && !file.startsWith('.')) {
|
|
451
|
+
const content = await fs.readFile(path.join(dirPath, file), 'utf-8');
|
|
452
|
+
const data = JSON.parse(content);
|
|
453
|
+
contracts.push({ status: dir, contracts: data.contracts ?? [] });
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
// Directory doesn't exist
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
content: [{ type: 'text', text: JSON.stringify(contracts, null, 2) }],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
return {
|
|
467
|
+
content: [{
|
|
468
|
+
type: 'text',
|
|
469
|
+
text: `No contracts found. Run \`drift scan\` first.`,
|
|
470
|
+
}],
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
async function handleExamples(projectRoot, store, packManager, feedbackManager, args) {
|
|
475
|
+
const fs = await import('node:fs/promises');
|
|
476
|
+
const path = await import('node:path');
|
|
477
|
+
// Require at least one filter to avoid processing all 800+ patterns
|
|
478
|
+
if ((!args.categories || args.categories.length === 0) && !args.pattern) {
|
|
479
|
+
return {
|
|
480
|
+
content: [{
|
|
481
|
+
type: 'text',
|
|
482
|
+
text: 'Error: Please specify at least one filter:\n' +
|
|
483
|
+
'- categories: ["auth", "security", "api"] etc.\n' +
|
|
484
|
+
'- pattern: "middleware", "token", "rate-limit" etc.\n\n' +
|
|
485
|
+
'Valid categories: structural, components, styling, api, auth, errors, ' +
|
|
486
|
+
'data-access, testing, logging, security, config, types, performance, ' +
|
|
487
|
+
'accessibility, documentation',
|
|
488
|
+
}],
|
|
489
|
+
isError: true,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
await store.initialize();
|
|
493
|
+
await packManager.initialize();
|
|
494
|
+
await feedbackManager.initialize();
|
|
495
|
+
// Track usage for pack learning
|
|
496
|
+
if (args.categories && args.categories.length > 0) {
|
|
497
|
+
await packManager.trackUsage({
|
|
498
|
+
categories: args.categories,
|
|
499
|
+
patterns: args.pattern ? [args.pattern] : undefined,
|
|
500
|
+
timestamp: new Date().toISOString(),
|
|
501
|
+
context: 'code_generation',
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
const maxExamples = args.maxExamples ?? 3;
|
|
505
|
+
const contextLines = args.contextLines ?? 10;
|
|
506
|
+
const includeDeprecated = args.includeDeprecated ?? false;
|
|
507
|
+
let patterns = store.getAll();
|
|
508
|
+
// Filter by categories
|
|
509
|
+
if (args.categories && args.categories.length > 0) {
|
|
510
|
+
const cats = new Set(args.categories);
|
|
511
|
+
patterns = patterns.filter(p => cats.has(p.category));
|
|
512
|
+
}
|
|
513
|
+
// Filter by pattern name/id
|
|
514
|
+
if (args.pattern) {
|
|
515
|
+
const searchTerm = args.pattern.toLowerCase();
|
|
516
|
+
patterns = patterns.filter(p => p.id.toLowerCase().includes(searchTerm) ||
|
|
517
|
+
p.name.toLowerCase().includes(searchTerm) ||
|
|
518
|
+
p.subcategory.toLowerCase().includes(searchTerm));
|
|
519
|
+
}
|
|
520
|
+
// Deduplicate patterns by subcategory to get unique pattern types
|
|
521
|
+
const uniquePatterns = new Map();
|
|
522
|
+
for (const p of patterns) {
|
|
523
|
+
const key = `${p.category}/${p.subcategory}`;
|
|
524
|
+
if (!uniquePatterns.has(key) || p.locations.length > uniquePatterns.get(key).locations.length) {
|
|
525
|
+
uniquePatterns.set(key, p);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// Limit to 20 unique patterns max to avoid timeout
|
|
529
|
+
const limitedPatterns = Array.from(uniquePatterns.entries()).slice(0, 20);
|
|
530
|
+
// File filtering patterns (same as packs.ts)
|
|
531
|
+
const EXAMPLE_EXCLUDE_PATTERNS = [
|
|
532
|
+
/README/i, /CHANGELOG/i, /CONTRIBUTING/i, /LICENSE/i, /\.md$/i,
|
|
533
|
+
/\.github\//, /\.gitlab\//, /\.ya?ml$/i, /\.toml$/i,
|
|
534
|
+
/Dockerfile/i, /docker-compose/i,
|
|
535
|
+
/package\.json$/i, /package-lock\.json$/i, /pnpm-lock\.yaml$/i,
|
|
536
|
+
/requirements\.txt$/i, /pyproject\.toml$/i,
|
|
537
|
+
/\.env/i, /dist\//, /build\//, /node_modules\//,
|
|
538
|
+
];
|
|
539
|
+
const DEPRECATION_MARKERS = [
|
|
540
|
+
/DEPRECATED/i, /LEGACY/i, /@deprecated/i,
|
|
541
|
+
/TODO:\s*remove/i, /REMOVAL:\s*planned/i,
|
|
542
|
+
/backward.?compat/i, /will be removed/i,
|
|
543
|
+
];
|
|
544
|
+
const shouldExcludeFile = (filePath) => {
|
|
545
|
+
// Check feedback-based exclusion first
|
|
546
|
+
if (feedbackManager.shouldExcludeFile(filePath)) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
return EXAMPLE_EXCLUDE_PATTERNS.some(pattern => pattern.test(filePath));
|
|
550
|
+
};
|
|
551
|
+
const scoreLocation = (filePath) => {
|
|
552
|
+
let score = 1.0;
|
|
553
|
+
// Apply feedback-based scoring
|
|
554
|
+
score *= feedbackManager.getFileScore(filePath);
|
|
555
|
+
// Apply heuristic scoring
|
|
556
|
+
if (/\.md$/i.test(filePath))
|
|
557
|
+
score *= 0.1;
|
|
558
|
+
if (/README/i.test(filePath))
|
|
559
|
+
score *= 0.1;
|
|
560
|
+
if (/\.ya?ml$/i.test(filePath))
|
|
561
|
+
score *= 0.2;
|
|
562
|
+
if (/\.json$/i.test(filePath))
|
|
563
|
+
score *= 0.3;
|
|
564
|
+
if (/\.(ts|tsx|js|jsx|py|rb|go|rs|java)$/i.test(filePath))
|
|
565
|
+
score *= 1.5;
|
|
566
|
+
if (/\/src\//i.test(filePath))
|
|
567
|
+
score *= 1.3;
|
|
568
|
+
if (/\.(test|spec)\./i.test(filePath))
|
|
569
|
+
score *= 0.7;
|
|
570
|
+
return score;
|
|
571
|
+
};
|
|
572
|
+
// Read actual code snippets
|
|
573
|
+
const fileCache = new Map();
|
|
574
|
+
const fileContentCache = new Map();
|
|
575
|
+
let excludedCount = 0;
|
|
576
|
+
let deprecatedCount = 0;
|
|
577
|
+
async function getFileLines(filePath) {
|
|
578
|
+
if (fileCache.has(filePath)) {
|
|
579
|
+
return fileCache.get(filePath);
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
const fullPath = path.join(projectRoot, filePath);
|
|
583
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
584
|
+
const lines = content.split('\n');
|
|
585
|
+
fileCache.set(filePath, lines);
|
|
586
|
+
fileContentCache.set(filePath, content);
|
|
587
|
+
return lines;
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
return [];
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function extractSnippet(lines, startLine, endLine) {
|
|
594
|
+
const start = Math.max(0, startLine - contextLines - 1);
|
|
595
|
+
const end = Math.min(lines.length, (endLine ?? startLine) + contextLines);
|
|
596
|
+
return lines.slice(start, end).join('\n');
|
|
597
|
+
}
|
|
598
|
+
function isDeprecatedContent(content) {
|
|
599
|
+
const header = content.slice(0, 500);
|
|
600
|
+
return DEPRECATION_MARKERS.some(pattern => pattern.test(header));
|
|
601
|
+
}
|
|
602
|
+
const results = [];
|
|
603
|
+
for (const [, pattern] of limitedPatterns) {
|
|
604
|
+
const examples = [];
|
|
605
|
+
// Sort locations by quality score and filter excluded files
|
|
606
|
+
const scoredLocations = pattern.locations
|
|
607
|
+
.map(loc => ({ loc, score: scoreLocation(loc.file) }))
|
|
608
|
+
.filter(({ loc }) => !shouldExcludeFile(loc.file))
|
|
609
|
+
.sort((a, b) => b.score - a.score);
|
|
610
|
+
excludedCount += pattern.locations.length - scoredLocations.length;
|
|
611
|
+
// Get unique files to avoid duplicate examples from same file
|
|
612
|
+
const seenFiles = new Set();
|
|
613
|
+
for (const { loc } of scoredLocations) {
|
|
614
|
+
if (seenFiles.has(loc.file))
|
|
615
|
+
continue;
|
|
616
|
+
if (examples.length >= maxExamples)
|
|
617
|
+
break;
|
|
618
|
+
const lines = await getFileLines(loc.file);
|
|
619
|
+
if (lines.length === 0)
|
|
620
|
+
continue;
|
|
621
|
+
// Check for deprecation markers
|
|
622
|
+
const content = fileContentCache.get(loc.file) || '';
|
|
623
|
+
if (!includeDeprecated && isDeprecatedContent(content)) {
|
|
624
|
+
deprecatedCount++;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
const snippet = extractSnippet(lines, loc.line, loc.endLine);
|
|
628
|
+
if (snippet.trim()) {
|
|
629
|
+
examples.push({
|
|
630
|
+
file: loc.file,
|
|
631
|
+
line: loc.line,
|
|
632
|
+
code: snippet,
|
|
633
|
+
});
|
|
634
|
+
seenFiles.add(loc.file);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (examples.length > 0) {
|
|
638
|
+
results.push({
|
|
639
|
+
category: pattern.category,
|
|
640
|
+
subcategory: pattern.subcategory,
|
|
641
|
+
patternName: pattern.name,
|
|
642
|
+
description: pattern.description,
|
|
643
|
+
confidence: pattern.confidence.score,
|
|
644
|
+
examples,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
// Format output for AI consumption
|
|
649
|
+
let output = '# Code Pattern Examples\n\n';
|
|
650
|
+
output += `Found ${results.length} unique patterns with code examples.\n`;
|
|
651
|
+
if (excludedCount > 0 || deprecatedCount > 0) {
|
|
652
|
+
output += `*(${excludedCount} non-source files excluded`;
|
|
653
|
+
if (deprecatedCount > 0) {
|
|
654
|
+
output += `, ${deprecatedCount} deprecated files skipped`;
|
|
655
|
+
}
|
|
656
|
+
output += `)*\n`;
|
|
657
|
+
}
|
|
658
|
+
output += '\n';
|
|
659
|
+
// Group by category
|
|
660
|
+
const grouped = new Map();
|
|
661
|
+
for (const r of results) {
|
|
662
|
+
if (!grouped.has(r.category)) {
|
|
663
|
+
grouped.set(r.category, []);
|
|
664
|
+
}
|
|
665
|
+
grouped.get(r.category).push(r);
|
|
666
|
+
}
|
|
667
|
+
for (const [category, categoryResults] of grouped) {
|
|
668
|
+
output += `## ${category.toUpperCase()}\n\n`;
|
|
669
|
+
for (const r of categoryResults) {
|
|
670
|
+
output += `### ${r.subcategory}\n`;
|
|
671
|
+
output += `**${r.patternName}** (${(r.confidence * 100).toFixed(0)}% confidence)\n`;
|
|
672
|
+
if (r.description) {
|
|
673
|
+
output += `${r.description}\n`;
|
|
674
|
+
}
|
|
675
|
+
output += '\n';
|
|
676
|
+
for (const ex of r.examples) {
|
|
677
|
+
output += `**${ex.file}:${ex.line}**\n`;
|
|
678
|
+
output += '```\n';
|
|
679
|
+
output += ex.code;
|
|
680
|
+
output += '\n```\n\n';
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return {
|
|
685
|
+
content: [{ type: 'text', text: output }],
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
async function handlePack(packManager, args) {
|
|
689
|
+
await packManager.initialize();
|
|
690
|
+
// Determine action (support legacy 'list' boolean)
|
|
691
|
+
const action = args.action ?? (args.list ? 'list' : (args.name ? 'get' : 'list'));
|
|
692
|
+
switch (action) {
|
|
693
|
+
case 'list': {
|
|
694
|
+
const packs = packManager.getAllPacks();
|
|
695
|
+
let output = '# Available Pattern Packs\n\n';
|
|
696
|
+
output += 'Use `drift_pack` with a pack name to get pre-computed pattern context.\n\n';
|
|
697
|
+
output += '## Actions\n';
|
|
698
|
+
output += '- `action="get"` - Get a pack by name (default)\n';
|
|
699
|
+
output += '- `action="list"` - List all packs\n';
|
|
700
|
+
output += '- `action="suggest"` - Suggest packs based on your usage patterns\n';
|
|
701
|
+
output += '- `action="infer"` - Suggest packs from file structure analysis\n';
|
|
702
|
+
output += '- `action="create"` - Create a custom pack\n';
|
|
703
|
+
output += '- `action="delete"` - Delete a custom pack\n\n';
|
|
704
|
+
for (const pack of packs) {
|
|
705
|
+
output += `## ${pack.name}\n`;
|
|
706
|
+
output += `${pack.description}\n`;
|
|
707
|
+
output += `- Categories: ${pack.categories.join(', ')}\n`;
|
|
708
|
+
if (pack.patterns) {
|
|
709
|
+
output += `- Pattern filters: ${pack.patterns.join(', ')}\n`;
|
|
710
|
+
}
|
|
711
|
+
output += '\n';
|
|
712
|
+
}
|
|
713
|
+
return {
|
|
714
|
+
content: [{ type: 'text', text: output }],
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
case 'suggest': {
|
|
718
|
+
const suggestions = await packManager.suggestPacks();
|
|
719
|
+
if (suggestions.length === 0) {
|
|
720
|
+
return {
|
|
721
|
+
content: [{
|
|
722
|
+
type: 'text',
|
|
723
|
+
text: '# No Pack Suggestions Yet\n\n' +
|
|
724
|
+
'Pack suggestions are based on your usage patterns. Keep using `drift_examples` and `drift_pack` ' +
|
|
725
|
+
'with different category combinations, and suggestions will appear here.\n\n' +
|
|
726
|
+
'Alternatively, use `action="infer"` to get suggestions based on file structure analysis.',
|
|
727
|
+
}],
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
let output = '# Suggested Packs\n\n';
|
|
731
|
+
output += 'Based on your usage patterns, these category combinations might be useful as packs:\n\n';
|
|
732
|
+
for (const s of suggestions) {
|
|
733
|
+
output += `## ${s.name}\n`;
|
|
734
|
+
output += `${s.description}\n`;
|
|
735
|
+
output += `- Categories: ${s.categories.join(', ')}\n`;
|
|
736
|
+
if (s.patterns && s.patterns.length > 0) {
|
|
737
|
+
output += `- Patterns: ${s.patterns.join(', ')}\n`;
|
|
738
|
+
}
|
|
739
|
+
output += `- Used ${s.usageCount} times (last: ${s.lastUsed})\n\n`;
|
|
740
|
+
output += `To create this pack:\n`;
|
|
741
|
+
output += '```\n';
|
|
742
|
+
output += `drift_pack action="create" name="${s.name}" description="${s.description}" categories=${JSON.stringify(s.categories)}\n`;
|
|
743
|
+
output += '```\n\n';
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
content: [{ type: 'text', text: output }],
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
case 'infer': {
|
|
750
|
+
const suggestions = await packManager.inferPacksFromStructure();
|
|
751
|
+
if (suggestions.length === 0) {
|
|
752
|
+
return {
|
|
753
|
+
content: [{
|
|
754
|
+
type: 'text',
|
|
755
|
+
text: '# No Inferred Packs\n\n' +
|
|
756
|
+
'Could not find significant pattern co-occurrences in the codebase. ' +
|
|
757
|
+
'This usually means patterns are well-separated by file, or the codebase needs more scanning.',
|
|
758
|
+
}],
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
let output = '# Inferred Packs from File Structure\n\n';
|
|
762
|
+
output += 'These category combinations frequently appear together in the same files:\n\n';
|
|
763
|
+
for (const s of suggestions) {
|
|
764
|
+
output += `## ${s.name}\n`;
|
|
765
|
+
output += `${s.description}\n`;
|
|
766
|
+
output += `- Categories: ${s.categories.join(', ')}\n`;
|
|
767
|
+
output += `- Found in ${s.usageCount} files\n\n`;
|
|
768
|
+
output += `To create this pack:\n`;
|
|
769
|
+
output += '```\n';
|
|
770
|
+
output += `drift_pack action="create" name="${s.name}" description="${s.description}" categories=${JSON.stringify(s.categories)}\n`;
|
|
771
|
+
output += '```\n\n';
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
content: [{ type: 'text', text: output }],
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
case 'create': {
|
|
778
|
+
if (!args.name) {
|
|
779
|
+
return {
|
|
780
|
+
content: [{
|
|
781
|
+
type: 'text',
|
|
782
|
+
text: 'Error: Pack name is required for create action.\n\n' +
|
|
783
|
+
'Example: drift_pack action="create" name="my_pack" description="My custom pack" categories=["api", "auth"]',
|
|
784
|
+
}],
|
|
785
|
+
isError: true,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
if (!args.categories || args.categories.length === 0) {
|
|
789
|
+
return {
|
|
790
|
+
content: [{
|
|
791
|
+
type: 'text',
|
|
792
|
+
text: 'Error: At least one category is required for create action.\n\n' +
|
|
793
|
+
`Valid categories: ${PATTERN_CATEGORIES.join(', ')}`,
|
|
794
|
+
}],
|
|
795
|
+
isError: true,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
const packDef = {
|
|
799
|
+
name: args.name,
|
|
800
|
+
description: args.description ?? `Custom pack: ${args.name}`,
|
|
801
|
+
categories: args.categories,
|
|
802
|
+
patterns: args.patterns,
|
|
803
|
+
};
|
|
804
|
+
await packManager.createCustomPack(packDef);
|
|
805
|
+
return {
|
|
806
|
+
content: [{
|
|
807
|
+
type: 'text',
|
|
808
|
+
text: `# Pack Created: ${args.name}\n\n` +
|
|
809
|
+
`Successfully created custom pack "${args.name}".\n\n` +
|
|
810
|
+
`- Categories: ${args.categories.join(', ')}\n` +
|
|
811
|
+
(args.patterns ? `- Patterns: ${args.patterns.join(', ')}\n` : '') +
|
|
812
|
+
`\nUse \`drift_pack name="${args.name}"\` to get the pack content.`,
|
|
813
|
+
}],
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
case 'delete': {
|
|
817
|
+
if (!args.name) {
|
|
818
|
+
return {
|
|
819
|
+
content: [{
|
|
820
|
+
type: 'text',
|
|
821
|
+
text: 'Error: Pack name is required for delete action.',
|
|
822
|
+
}],
|
|
823
|
+
isError: true,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
const deleted = await packManager.deleteCustomPack(args.name);
|
|
827
|
+
if (!deleted) {
|
|
828
|
+
return {
|
|
829
|
+
content: [{
|
|
830
|
+
type: 'text',
|
|
831
|
+
text: `Error: Pack "${args.name}" not found or is a built-in pack (cannot delete built-in packs).`,
|
|
832
|
+
}],
|
|
833
|
+
isError: true,
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
content: [{
|
|
838
|
+
type: 'text',
|
|
839
|
+
text: `Successfully deleted custom pack "${args.name}".`,
|
|
840
|
+
}],
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
case 'get':
|
|
844
|
+
default: {
|
|
845
|
+
if (!args.name) {
|
|
846
|
+
// Fall back to list if no name provided
|
|
847
|
+
return handlePack(packManager, { action: 'list' });
|
|
848
|
+
}
|
|
849
|
+
try {
|
|
850
|
+
const result = await packManager.getPackContent(args.name, {
|
|
851
|
+
refresh: args.refresh ?? false,
|
|
852
|
+
});
|
|
853
|
+
// Track usage for learning
|
|
854
|
+
const pack = packManager.getPack(args.name);
|
|
855
|
+
if (pack) {
|
|
856
|
+
await packManager.trackUsage({
|
|
857
|
+
categories: pack.categories,
|
|
858
|
+
patterns: pack.patterns,
|
|
859
|
+
timestamp: new Date().toISOString(),
|
|
860
|
+
context: 'code_generation',
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
let header = '';
|
|
864
|
+
if (result.fromCache) {
|
|
865
|
+
header = `<!-- Served from cache (generated: ${result.generatedAt}) -->\n\n`;
|
|
866
|
+
}
|
|
867
|
+
else if (result.staleReason) {
|
|
868
|
+
header = `<!-- Regenerated: ${result.staleReason} -->\n\n`;
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
header = `<!-- Freshly generated -->\n\n`;
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
content: [{ type: 'text', text: header + result.content }],
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
catch (error) {
|
|
878
|
+
return {
|
|
879
|
+
content: [{
|
|
880
|
+
type: 'text',
|
|
881
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
882
|
+
}],
|
|
883
|
+
isError: true,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
async function handleFeedback(feedbackManager, args) {
|
|
890
|
+
await feedbackManager.initialize();
|
|
891
|
+
const action = args.action ?? 'stats';
|
|
892
|
+
switch (action) {
|
|
893
|
+
case 'rate': {
|
|
894
|
+
if (!args.file || !args.rating) {
|
|
895
|
+
return {
|
|
896
|
+
content: [{
|
|
897
|
+
type: 'text',
|
|
898
|
+
text: 'Error: "file" and "rating" are required for rate action.\n\n' +
|
|
899
|
+
'Example: drift_feedback action="rate" file="api/routes/auth.py" line=42 ' +
|
|
900
|
+
'rating="good" patternName="token-handling" category="auth"',
|
|
901
|
+
}],
|
|
902
|
+
isError: true,
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
await feedbackManager.recordFeedback({
|
|
906
|
+
patternId: args.patternId ?? 'unknown',
|
|
907
|
+
patternName: args.patternName ?? 'unknown',
|
|
908
|
+
category: args.category ?? 'unknown',
|
|
909
|
+
file: args.file,
|
|
910
|
+
line: args.line ?? 0,
|
|
911
|
+
rating: args.rating,
|
|
912
|
+
reason: args.reason,
|
|
913
|
+
});
|
|
914
|
+
const emoji = args.rating === 'good' ? '👍' : args.rating === 'bad' ? '👎' : '🤷';
|
|
915
|
+
return {
|
|
916
|
+
content: [{
|
|
917
|
+
type: 'text',
|
|
918
|
+
text: `${emoji} Feedback recorded for ${args.file}:${args.line ?? 0}\n\n` +
|
|
919
|
+
`Rating: ${args.rating}\n` +
|
|
920
|
+
(args.reason ? `Reason: ${args.reason}\n` : '') +
|
|
921
|
+
`\nThis feedback will improve future example suggestions.`,
|
|
922
|
+
}],
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
case 'stats': {
|
|
926
|
+
const stats = await feedbackManager.getStats();
|
|
927
|
+
let output = '# Example Feedback Statistics\n\n';
|
|
928
|
+
output += `Total feedback: ${stats.totalFeedback}\n`;
|
|
929
|
+
output += `- Good examples: ${stats.goodExamples}\n`;
|
|
930
|
+
output += `- Bad examples: ${stats.badExamples}\n`;
|
|
931
|
+
output += `- Irrelevant: ${stats.irrelevantExamples}\n\n`;
|
|
932
|
+
if (stats.topGoodPatterns.length > 0) {
|
|
933
|
+
output += '## Top Patterns with Good Examples\n';
|
|
934
|
+
for (const p of stats.topGoodPatterns) {
|
|
935
|
+
output += `- ${p.pattern}: ${p.count} good\n`;
|
|
936
|
+
}
|
|
937
|
+
output += '\n';
|
|
938
|
+
}
|
|
939
|
+
if (stats.topBadPatterns.length > 0) {
|
|
940
|
+
output += '## Patterns Needing Improvement\n';
|
|
941
|
+
for (const p of stats.topBadPatterns) {
|
|
942
|
+
output += `- ${p.pattern}: ${p.count} bad\n`;
|
|
943
|
+
}
|
|
944
|
+
output += '\n';
|
|
945
|
+
}
|
|
946
|
+
if (stats.topBadFiles.length > 0) {
|
|
947
|
+
output += '## Files Producing Poor Examples\n';
|
|
948
|
+
output += 'These files are being deprioritized in example selection:\n';
|
|
949
|
+
for (const f of stats.topBadFiles) {
|
|
950
|
+
output += `- ${f.file}: ${f.count} negative ratings\n`;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
content: [{ type: 'text', text: output }],
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
case 'clear': {
|
|
958
|
+
await feedbackManager.clearFeedback();
|
|
959
|
+
return {
|
|
960
|
+
content: [{
|
|
961
|
+
type: 'text',
|
|
962
|
+
text: '✅ All feedback has been cleared. Example scoring reset to defaults.',
|
|
963
|
+
}],
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
default:
|
|
967
|
+
return {
|
|
968
|
+
content: [{
|
|
969
|
+
type: 'text',
|
|
970
|
+
text: `Unknown action: ${action}. Valid actions: rate, stats, clear`,
|
|
971
|
+
}],
|
|
972
|
+
isError: true,
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
//# sourceMappingURL=server.js.map
|