driftdetect-mcp 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bin/server.js +0 -0
  3. package/dist/enterprise-server.d.ts +1 -0
  4. package/dist/enterprise-server.d.ts.map +1 -1
  5. package/dist/enterprise-server.js +8 -1
  6. package/dist/enterprise-server.js.map +1 -1
  7. package/dist/tools/analysis/constants.d.ts +99 -0
  8. package/dist/tools/analysis/constants.d.ts.map +1 -0
  9. package/dist/tools/analysis/constants.js +421 -0
  10. package/dist/tools/analysis/constants.js.map +1 -0
  11. package/dist/tools/analysis/index.d.ts +1 -0
  12. package/dist/tools/analysis/index.d.ts.map +1 -1
  13. package/dist/tools/analysis/index.js +70 -0
  14. package/dist/tools/analysis/index.js.map +1 -1
  15. package/dist/tools/exploration/env.d.ts +53 -0
  16. package/dist/tools/exploration/env.d.ts.map +1 -0
  17. package/dist/tools/exploration/env.js +283 -0
  18. package/dist/tools/exploration/env.js.map +1 -0
  19. package/dist/tools/exploration/index.d.ts +2 -0
  20. package/dist/tools/exploration/index.d.ts.map +1 -1
  21. package/dist/tools/exploration/index.js +32 -0
  22. package/dist/tools/exploration/index.js.map +1 -1
  23. package/dist/tools/index.d.ts +6 -1
  24. package/dist/tools/index.d.ts.map +1 -1
  25. package/dist/tools/index.js +6 -1
  26. package/dist/tools/index.js.map +1 -1
  27. package/dist/tools/registry.d.ts +7 -5
  28. package/dist/tools/registry.d.ts.map +1 -1
  29. package/dist/tools/registry.js +10 -4
  30. package/dist/tools/registry.js.map +1 -1
  31. package/dist/tools/surgical/callers.d.ts +85 -0
  32. package/dist/tools/surgical/callers.d.ts.map +1 -0
  33. package/dist/tools/surgical/callers.js +239 -0
  34. package/dist/tools/surgical/callers.js.map +1 -0
  35. package/dist/tools/surgical/dependencies.d.ts +96 -0
  36. package/dist/tools/surgical/dependencies.d.ts.map +1 -0
  37. package/dist/tools/surgical/dependencies.js +433 -0
  38. package/dist/tools/surgical/dependencies.js.map +1 -0
  39. package/dist/tools/surgical/errors.d.ts +88 -0
  40. package/dist/tools/surgical/errors.d.ts.map +1 -0
  41. package/dist/tools/surgical/errors.js +275 -0
  42. package/dist/tools/surgical/errors.js.map +1 -0
  43. package/dist/tools/surgical/hooks.d.ts +69 -0
  44. package/dist/tools/surgical/hooks.d.ts.map +1 -0
  45. package/dist/tools/surgical/hooks.js +247 -0
  46. package/dist/tools/surgical/hooks.js.map +1 -0
  47. package/dist/tools/surgical/imports.d.ts +61 -0
  48. package/dist/tools/surgical/imports.d.ts.map +1 -0
  49. package/dist/tools/surgical/imports.js +211 -0
  50. package/dist/tools/surgical/imports.js.map +1 -0
  51. package/dist/tools/surgical/index.d.ts +42 -0
  52. package/dist/tools/surgical/index.d.ts.map +1 -0
  53. package/dist/tools/surgical/index.js +66 -0
  54. package/dist/tools/surgical/index.js.map +1 -0
  55. package/dist/tools/surgical/middleware.d.ts +69 -0
  56. package/dist/tools/surgical/middleware.d.ts.map +1 -0
  57. package/dist/tools/surgical/middleware.js +237 -0
  58. package/dist/tools/surgical/middleware.js.map +1 -0
  59. package/dist/tools/surgical/prevalidate.d.ts +76 -0
  60. package/dist/tools/surgical/prevalidate.d.ts.map +1 -0
  61. package/dist/tools/surgical/prevalidate.js +303 -0
  62. package/dist/tools/surgical/prevalidate.js.map +1 -0
  63. package/dist/tools/surgical/recent.d.ts +66 -0
  64. package/dist/tools/surgical/recent.d.ts.map +1 -0
  65. package/dist/tools/surgical/recent.js +238 -0
  66. package/dist/tools/surgical/recent.js.map +1 -0
  67. package/dist/tools/surgical/signature.d.ts +73 -0
  68. package/dist/tools/surgical/signature.d.ts.map +1 -0
  69. package/dist/tools/surgical/signature.js +190 -0
  70. package/dist/tools/surgical/signature.js.map +1 -0
  71. package/dist/tools/surgical/similar.d.ts +77 -0
  72. package/dist/tools/surgical/similar.d.ts.map +1 -0
  73. package/dist/tools/surgical/similar.js +285 -0
  74. package/dist/tools/surgical/similar.js.map +1 -0
  75. package/dist/tools/surgical/test-template.d.ts +70 -0
  76. package/dist/tools/surgical/test-template.d.ts.map +1 -0
  77. package/dist/tools/surgical/test-template.js +298 -0
  78. package/dist/tools/surgical/test-template.js.map +1 -0
  79. package/dist/tools/surgical/type.d.ts +69 -0
  80. package/dist/tools/surgical/type.d.ts.map +1 -0
  81. package/dist/tools/surgical/type.js +289 -0
  82. package/dist/tools/surgical/type.js.map +1 -0
  83. package/package.json +11 -11
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similar.d.ts","sourceRoot":"","sources":["../../../src/tools/surgical/similar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAgB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAOnF,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,MAAM,EAAE,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,CAAC;IAC9F,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,EAAE,kBAAkB,CAAC;CACjC;AA0BD,wBAAsB,aAAa,CACjC,cAAc,EAAE,cAAc,EAC9B,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CA2K7D;AAuFD;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BjC,CAAC"}
@@ -0,0 +1,285 @@
1
+ /**
2
+ * drift_similar - Find Semantically Similar Code
3
+ *
4
+ * Layer: Surgical
5
+ * Token Budget: 500 target, 1000 max
6
+ * Cache TTL: 5 minutes
7
+ * Invalidation Keys: patterns, callgraph
8
+ *
9
+ * Finds code semantically similar to what the AI is about to write.
10
+ * Solves: AI needs to see an example but there are 50 options. Which is most relevant?
11
+ */
12
+ import { createResponseBuilder, Errors, metrics } from '../../infrastructure/index.js';
13
+ // ============================================================================
14
+ // Intent to Pattern Category Mapping
15
+ // ============================================================================
16
+ const INTENT_CATEGORIES = {
17
+ api_endpoint: ['api', 'errors', 'auth'],
18
+ service: ['data-access', 'errors', 'logging'],
19
+ component: ['components', 'styling', 'accessibility'],
20
+ hook: ['components', 'data-access'],
21
+ utility: ['errors', 'logging'],
22
+ test: ['testing'],
23
+ middleware: ['api', 'auth', 'errors'],
24
+ };
25
+ const INTENT_DECORATORS = {
26
+ api_endpoint: ['@app.route', '@router', 'HttpGet', 'HttpPost', '@Get', '@Post', 'Route'],
27
+ middleware: ['@middleware', 'Middleware'],
28
+ test: ['@test', 'describe', 'it', 'test'],
29
+ };
30
+ // ============================================================================
31
+ // Handler
32
+ // ============================================================================
33
+ export async function handleSimilar(callGraphStore, patternStore, args) {
34
+ const startTime = Date.now();
35
+ const builder = createResponseBuilder();
36
+ // Validate input
37
+ if (!args.intent) {
38
+ throw Errors.missingParameter('intent');
39
+ }
40
+ if (!args.description || args.description.trim() === '') {
41
+ throw Errors.missingParameter('description');
42
+ }
43
+ const intent = args.intent;
44
+ const description = args.description.toLowerCase();
45
+ const scope = args.scope;
46
+ const limit = Math.min(args.limit ?? 3, 10);
47
+ // Load stores
48
+ await callGraphStore.initialize();
49
+ await patternStore.initialize();
50
+ const graph = callGraphStore.getGraph();
51
+ if (!graph) {
52
+ throw Errors.custom('CALLGRAPH_NOT_BUILT', 'Call graph has not been built. Run "drift callgraph build" first.', ['drift_status']);
53
+ }
54
+ // Get patterns for scoring
55
+ const allPatterns = patternStore.getAll();
56
+ const patternsByFile = new Map();
57
+ for (const pattern of allPatterns) {
58
+ for (const loc of pattern.locations) {
59
+ const existing = patternsByFile.get(loc.file) ?? [];
60
+ existing.push(pattern.name);
61
+ patternsByFile.set(loc.file, existing);
62
+ }
63
+ }
64
+ // Score all functions
65
+ const scored = [];
66
+ const targetCategories = INTENT_CATEGORIES[intent] ?? [];
67
+ const targetDecorators = INTENT_DECORATORS[intent] ?? [];
68
+ for (const [, func] of graph.functions) {
69
+ // Skip if scope specified and doesn't match
70
+ if (scope && !func.file.includes(scope)) {
71
+ continue;
72
+ }
73
+ const reasons = [];
74
+ let score = 0;
75
+ // 1. Decorator match (strong signal)
76
+ for (const dec of func.decorators) {
77
+ if (targetDecorators.some(td => dec.includes(td))) {
78
+ score += 0.3;
79
+ reasons.push(`Has ${intent} decorator`);
80
+ break;
81
+ }
82
+ }
83
+ // 2. Pattern category match
84
+ const filePatterns = patternsByFile.get(func.file) ?? [];
85
+ const categoryMatches = filePatterns.filter(p => targetCategories.some(cat => p.toLowerCase().includes(cat)));
86
+ if (categoryMatches.length > 0) {
87
+ score += 0.2 * Math.min(categoryMatches.length, 3);
88
+ reasons.push(`Matches ${categoryMatches.length} ${intent} patterns`);
89
+ }
90
+ // 3. Name similarity (fuzzy match on description keywords)
91
+ const keywords = description.split(/\s+/).filter(w => w.length > 2);
92
+ const nameWords = func.name.toLowerCase().split(/(?=[A-Z])|_|-/).map(w => w.toLowerCase());
93
+ const qualifiedWords = func.qualifiedName.toLowerCase().split(/[._-]/);
94
+ let keywordMatches = 0;
95
+ for (const kw of keywords) {
96
+ if (nameWords.some(nw => nw.includes(kw) || kw.includes(nw))) {
97
+ keywordMatches++;
98
+ }
99
+ else if (qualifiedWords.some(qw => qw.includes(kw) || kw.includes(qw))) {
100
+ keywordMatches += 0.5;
101
+ }
102
+ }
103
+ if (keywordMatches > 0 && keywords.length > 0) {
104
+ const keywordScore = (keywordMatches / keywords.length) * 0.3;
105
+ score += keywordScore;
106
+ reasons.push(`Name matches ${Math.round(keywordMatches)} keywords`);
107
+ }
108
+ // 4. Exported functions are more likely to be good examples
109
+ if (func.isExported) {
110
+ score += 0.1;
111
+ }
112
+ // 5. Has parameters (more complete example)
113
+ if (func.parameters.length > 0) {
114
+ score += 0.05;
115
+ }
116
+ // 6. Has return type (better documented)
117
+ if (func.returnType) {
118
+ score += 0.05;
119
+ }
120
+ if (score > 0.1) {
121
+ scored.push({ func, score, reasons });
122
+ }
123
+ }
124
+ // Sort by score and take top N
125
+ scored.sort((a, b) => b.score - a.score);
126
+ const topMatches = scored.slice(0, limit);
127
+ // Build matches
128
+ const matches = topMatches.map(({ func, score, reasons }) => ({
129
+ file: func.file,
130
+ function: func.name,
131
+ class: func.className,
132
+ similarity: Math.round(score * 100) / 100,
133
+ reason: reasons.slice(0, 2).join('. '),
134
+ preview: buildPreview(func),
135
+ patterns: patternsByFile.get(func.file)?.slice(0, 3) ?? [],
136
+ }));
137
+ // Analyze conventions from matches
138
+ const conventions = analyzeConventions(topMatches.map(m => m.func));
139
+ const data = {
140
+ matches,
141
+ conventions,
142
+ };
143
+ // Build summary
144
+ let summary;
145
+ if (matches.length === 0) {
146
+ summary = `No similar ${intent} found matching "${args.description}"`;
147
+ }
148
+ else {
149
+ summary = `Found ${matches.length} similar ${intent} example${matches.length !== 1 ? 's' : ''}. Top: ${matches[0].file}`;
150
+ }
151
+ // Build hints
152
+ const hints = {
153
+ nextActions: matches.length > 0
154
+ ? [
155
+ `Use drift_signature to get full signature for "${matches[0].function}"`,
156
+ `Use drift_imports to get correct imports for your new file`,
157
+ ]
158
+ : [
159
+ 'Try a different intent or broader description',
160
+ 'Use drift_patterns_list to see available patterns',
161
+ ],
162
+ relatedTools: ['drift_signature', 'drift_imports', 'drift_code_examples'],
163
+ };
164
+ if (matches.some(m => m.similarity < 0.3)) {
165
+ hints.warnings = ['Some matches have low similarity - review carefully'];
166
+ }
167
+ // Record metrics
168
+ metrics.recordRequest('drift_similar', Date.now() - startTime, true, false);
169
+ return builder
170
+ .withSummary(summary)
171
+ .withData(data)
172
+ .withHints(hints)
173
+ .buildContent();
174
+ }
175
+ // ============================================================================
176
+ // Helpers
177
+ // ============================================================================
178
+ /**
179
+ * Build a preview of the function signature
180
+ */
181
+ function buildPreview(func) {
182
+ const parts = [];
183
+ // Decorators (first one)
184
+ if (func.decorators.length > 0) {
185
+ parts.push(`@${func.decorators[0]}`);
186
+ }
187
+ // Export + async
188
+ if (func.isExported)
189
+ parts.push('export');
190
+ if (func.isAsync)
191
+ parts.push('async');
192
+ parts.push('function');
193
+ parts.push(func.name);
194
+ // Parameters (abbreviated)
195
+ const params = func.parameters.slice(0, 3).map(p => {
196
+ if (p.type)
197
+ return `${p.name}: ${p.type}`;
198
+ return p.name;
199
+ });
200
+ if (func.parameters.length > 3) {
201
+ params.push('...');
202
+ }
203
+ let preview = parts.join(' ') + `(${params.join(', ')})`;
204
+ if (func.returnType) {
205
+ preview += `: ${func.returnType}`;
206
+ }
207
+ return preview;
208
+ }
209
+ /**
210
+ * Analyze conventions from matched functions
211
+ */
212
+ function analyzeConventions(funcs) {
213
+ if (funcs.length === 0) {
214
+ return {
215
+ naming: 'Unknown',
216
+ errorHandling: 'Unknown',
217
+ imports: 'Unknown',
218
+ };
219
+ }
220
+ // Naming convention
221
+ const names = funcs.map(f => f.name);
222
+ const hasCamelCase = names.some(n => /^[a-z][a-zA-Z]*$/.test(n));
223
+ const hasSnakeCase = names.some(n => /^[a-z][a-z_]*$/.test(n));
224
+ const hasPascalCase = names.some(n => /^[A-Z][a-zA-Z]*$/.test(n));
225
+ let naming = 'mixed';
226
+ if (hasCamelCase && !hasSnakeCase)
227
+ naming = 'camelCase';
228
+ else if (hasSnakeCase && !hasCamelCase)
229
+ naming = 'snake_case';
230
+ else if (hasPascalCase)
231
+ naming = 'PascalCase';
232
+ // Error handling (check return types)
233
+ const returnTypes = funcs.map(f => f.returnType).filter(Boolean);
234
+ let errorHandling = 'Unknown';
235
+ if (returnTypes.some(rt => rt?.includes('Result'))) {
236
+ errorHandling = 'Result<T> pattern';
237
+ }
238
+ else if (returnTypes.some(rt => rt?.includes('Promise'))) {
239
+ errorHandling = 'Async with try/catch';
240
+ }
241
+ else if (funcs.some(f => f.isAsync)) {
242
+ errorHandling = 'Async functions';
243
+ }
244
+ // Import style (inferred from file paths)
245
+ const files = funcs.map(f => f.file);
246
+ let imports = 'relative paths';
247
+ if (files.some(f => f.includes('@/'))) {
248
+ imports = 'Path alias (@/)';
249
+ }
250
+ else if (files.some(f => f.includes('~/'))) {
251
+ imports = 'Path alias (~/)';
252
+ }
253
+ return { naming, errorHandling, imports };
254
+ }
255
+ /**
256
+ * Tool definition for MCP registration
257
+ */
258
+ export const similarToolDefinition = {
259
+ name: 'drift_similar',
260
+ description: 'Find code semantically similar to what you\'re about to write. Returns relevant examples with patterns and conventions. Use before writing new code to find the right template.',
261
+ inputSchema: {
262
+ type: 'object',
263
+ properties: {
264
+ intent: {
265
+ type: 'string',
266
+ enum: ['api_endpoint', 'service', 'component', 'hook', 'utility', 'test', 'middleware'],
267
+ description: 'What kind of code are you writing?',
268
+ },
269
+ description: {
270
+ type: 'string',
271
+ description: 'Natural language description (e.g., "user preferences CRUD")',
272
+ },
273
+ scope: {
274
+ type: 'string',
275
+ description: 'Optional: limit to specific directory',
276
+ },
277
+ limit: {
278
+ type: 'number',
279
+ description: 'Max results (default: 3, max: 10)',
280
+ },
281
+ },
282
+ required: ['intent', 'description'],
283
+ },
284
+ };
285
+ //# sourceMappingURL=similar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similar.js","sourceRoot":"","sources":["../../../src/tools/surgical/similar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * drift_test_template - Generate Test Scaffolding
3
+ *
4
+ * Layer: Surgical
5
+ * Token Budget: 800 target, 1500 max
6
+ * Cache TTL: 5 minutes
7
+ * Invalidation Keys: test-topology, file:{targetFile}
8
+ *
9
+ * Generates test scaffolding based on existing test patterns.
10
+ * Solves: Tests are the most convention-heavy code. Every codebase is different.
11
+ */
12
+ import type { CallGraphStore } from 'driftdetect-core';
13
+ export interface TestTemplateArgs {
14
+ /** File being tested */
15
+ targetFile: string;
16
+ /** Specific function to test (optional) */
17
+ function?: string;
18
+ /** Test type */
19
+ type?: 'unit' | 'integration' | 'e2e';
20
+ }
21
+ export interface TestConventions {
22
+ framework: string;
23
+ style: string;
24
+ mockStyle: string;
25
+ assertionStyle: string;
26
+ filePattern: string;
27
+ }
28
+ export interface ExampleTest {
29
+ file: string;
30
+ preview: string;
31
+ }
32
+ export interface TestTemplateData {
33
+ testFile: string;
34
+ template: string;
35
+ conventions: TestConventions;
36
+ exampleTest?: ExampleTest | undefined;
37
+ }
38
+ export declare function handleTestTemplate(store: CallGraphStore, args: TestTemplateArgs, projectRoot: string): Promise<{
39
+ content: Array<{
40
+ type: string;
41
+ text: string;
42
+ }>;
43
+ }>;
44
+ /**
45
+ * Tool definition for MCP registration
46
+ */
47
+ export declare const testTemplateToolDefinition: {
48
+ name: string;
49
+ description: string;
50
+ inputSchema: {
51
+ type: "object";
52
+ properties: {
53
+ targetFile: {
54
+ type: string;
55
+ description: string;
56
+ };
57
+ function: {
58
+ type: string;
59
+ description: string;
60
+ };
61
+ type: {
62
+ type: string;
63
+ enum: string[];
64
+ description: string;
65
+ };
66
+ };
67
+ required: string[];
68
+ };
69
+ };
70
+ //# sourceMappingURL=test-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-template.d.ts","sourceRoot":"","sources":["../../../src/tools/surgical/test-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAgB,MAAM,kBAAkB,CAAC;AASrE,MAAM,WAAW,gBAAgB;IAC/B,wBAAwB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,KAAK,CAAC;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,eAAe,CAAC;IAC7B,WAAW,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CACvC;AAMD,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CAsF7D;AAiOD;;GAEG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;CAsBtC,CAAC"}
@@ -0,0 +1,298 @@
1
+ /**
2
+ * drift_test_template - Generate Test Scaffolding
3
+ *
4
+ * Layer: Surgical
5
+ * Token Budget: 800 target, 1500 max
6
+ * Cache TTL: 5 minutes
7
+ * Invalidation Keys: test-topology, file:{targetFile}
8
+ *
9
+ * Generates test scaffolding based on existing test patterns.
10
+ * Solves: Tests are the most convention-heavy code. Every codebase is different.
11
+ */
12
+ import { createResponseBuilder, Errors, metrics } from '../../infrastructure/index.js';
13
+ import * as fs from 'node:fs/promises';
14
+ import * as path from 'node:path';
15
+ // ============================================================================
16
+ // Handler
17
+ // ============================================================================
18
+ export async function handleTestTemplate(store, args, projectRoot) {
19
+ const startTime = Date.now();
20
+ const builder = createResponseBuilder();
21
+ // Validate input
22
+ if (!args.targetFile || args.targetFile.trim() === '') {
23
+ throw Errors.missingParameter('targetFile');
24
+ }
25
+ const targetFile = args.targetFile.trim();
26
+ const targetFunction = args.function?.trim();
27
+ const testType = args.type ?? 'unit';
28
+ // Load call graph
29
+ await store.initialize();
30
+ const graph = store.getGraph();
31
+ if (!graph) {
32
+ throw Errors.custom('CALLGRAPH_NOT_BUILT', 'Call graph has not been built. Run "drift callgraph build" first.', ['drift_status']);
33
+ }
34
+ // Find the target function if specified
35
+ let targetFunc;
36
+ if (targetFunction) {
37
+ for (const [, func] of graph.functions) {
38
+ if (func.file.endsWith(targetFile) && func.name === targetFunction) {
39
+ targetFunc = func;
40
+ break;
41
+ }
42
+ }
43
+ }
44
+ // Detect test conventions from existing tests
45
+ const detectedConventions = await detectTestConventions(projectRoot, targetFile);
46
+ // Find example test in same directory
47
+ const exampleTest = await findExampleTest(projectRoot, targetFile, detectedConventions);
48
+ // Generate test file path
49
+ const testFile = generateTestFilePath(targetFile, detectedConventions);
50
+ // Generate template
51
+ const template = generateTestTemplate(targetFile, targetFunc, detectedConventions, testType);
52
+ const data = {
53
+ testFile,
54
+ template,
55
+ conventions: detectedConventions,
56
+ exampleTest,
57
+ };
58
+ // Build summary
59
+ const funcName = targetFunc?.name ?? path.basename(targetFile, path.extname(targetFile));
60
+ const summary = `Generated ${testType} test template for "${funcName}" using ${detectedConventions.framework}/${detectedConventions.style}`;
61
+ // Build hints
62
+ const hints = {
63
+ nextActions: [
64
+ `Create ${testFile} with the template`,
65
+ 'Fill in test cases based on function behavior',
66
+ exampleTest ? `Reference ${exampleTest.file} for patterns` : 'Add assertions matching codebase style',
67
+ ],
68
+ relatedTools: ['drift_signature', 'drift_callers', 'drift_similar'],
69
+ };
70
+ if (!exampleTest) {
71
+ hints.warnings = ['No existing tests found in directory - conventions inferred from project'];
72
+ }
73
+ // Record metrics
74
+ metrics.recordRequest('drift_test_template', Date.now() - startTime, true, false);
75
+ return builder
76
+ .withSummary(summary)
77
+ .withData(data)
78
+ .withHints(hints)
79
+ .buildContent();
80
+ }
81
+ // ============================================================================
82
+ // Helpers
83
+ // ============================================================================
84
+ /**
85
+ * Detect test conventions from existing tests
86
+ */
87
+ async function detectTestConventions(projectRoot, targetFile) {
88
+ const conventions = {
89
+ framework: 'vitest',
90
+ style: 'describe/it',
91
+ mockStyle: 'vi.mock',
92
+ assertionStyle: 'expect',
93
+ filePattern: '*.test.ts',
94
+ };
95
+ // Check package.json for test framework
96
+ try {
97
+ const pkgPath = path.join(projectRoot, 'package.json');
98
+ const pkgContent = await fs.readFile(pkgPath, 'utf-8');
99
+ const pkg = JSON.parse(pkgContent);
100
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
101
+ if (deps['vitest']) {
102
+ conventions.framework = 'vitest';
103
+ conventions.mockStyle = 'vi.mock';
104
+ }
105
+ else if (deps['jest']) {
106
+ conventions.framework = 'jest';
107
+ conventions.mockStyle = 'jest.mock';
108
+ }
109
+ else if (deps['mocha']) {
110
+ conventions.framework = 'mocha';
111
+ conventions.mockStyle = 'sinon';
112
+ conventions.assertionStyle = 'chai';
113
+ }
114
+ }
115
+ catch {
116
+ // Use defaults
117
+ }
118
+ // Check for test file patterns in the target directory
119
+ const targetDir = path.dirname(targetFile);
120
+ const fullTargetDir = path.join(projectRoot, targetDir);
121
+ try {
122
+ const files = await fs.readdir(fullTargetDir);
123
+ // Check for __tests__ directory
124
+ if (files.includes('__tests__')) {
125
+ conventions.filePattern = '__tests__/*.test.ts';
126
+ }
127
+ // Check for .spec.ts files
128
+ if (files.some(f => f.endsWith('.spec.ts'))) {
129
+ conventions.filePattern = '*.spec.ts';
130
+ }
131
+ }
132
+ catch {
133
+ // Directory doesn't exist or can't be read
134
+ }
135
+ return conventions;
136
+ }
137
+ /**
138
+ * Find an example test in the same directory
139
+ */
140
+ async function findExampleTest(projectRoot, targetFile, _conventions) {
141
+ const targetDir = path.dirname(targetFile);
142
+ // Possible test locations
143
+ const testDirs = [
144
+ path.join(projectRoot, targetDir, '__tests__'),
145
+ path.join(projectRoot, targetDir),
146
+ path.join(projectRoot, targetDir.replace('/src/', '/test/')),
147
+ ];
148
+ for (const testDir of testDirs) {
149
+ try {
150
+ const files = await fs.readdir(testDir);
151
+ const testFiles = files.filter(f => f.endsWith('.test.ts') ||
152
+ f.endsWith('.spec.ts') ||
153
+ f.endsWith('.test.js') ||
154
+ f.endsWith('.spec.js'));
155
+ if (testFiles.length > 0) {
156
+ const testFile = testFiles[0];
157
+ const fullPath = path.join(testDir, testFile);
158
+ const content = await fs.readFile(fullPath, 'utf-8');
159
+ // Get first 20 lines as preview
160
+ const preview = content.split('\n').slice(0, 20).join('\n');
161
+ return {
162
+ file: path.relative(projectRoot, fullPath),
163
+ preview,
164
+ };
165
+ }
166
+ }
167
+ catch {
168
+ // Directory doesn't exist
169
+ }
170
+ }
171
+ return undefined;
172
+ }
173
+ /**
174
+ * Generate test file path based on conventions
175
+ */
176
+ function generateTestFilePath(targetFile, conventions) {
177
+ const dir = path.dirname(targetFile);
178
+ const base = path.basename(targetFile, path.extname(targetFile));
179
+ const ext = path.extname(targetFile);
180
+ if (conventions.filePattern.includes('__tests__')) {
181
+ return path.join(dir, '__tests__', `${base}.test${ext}`);
182
+ }
183
+ if (conventions.filePattern.includes('.spec.')) {
184
+ return path.join(dir, `${base}.spec${ext}`);
185
+ }
186
+ return path.join(dir, `${base}.test${ext}`);
187
+ }
188
+ /**
189
+ * Generate test template
190
+ */
191
+ function generateTestTemplate(targetFile, targetFunc, conventions, _testType) {
192
+ const moduleName = path.basename(targetFile, path.extname(targetFile));
193
+ const funcName = targetFunc?.name ?? moduleName;
194
+ // Import statement based on framework
195
+ let imports;
196
+ if (conventions.framework === 'vitest') {
197
+ imports = `import { describe, it, expect, beforeEach, vi } from 'vitest';`;
198
+ }
199
+ else if (conventions.framework === 'jest') {
200
+ imports = `// Jest globals are available automatically`;
201
+ }
202
+ else {
203
+ imports = `import { describe, it } from 'mocha';\nimport { expect } from 'chai';`;
204
+ }
205
+ // Import the module under test
206
+ const relativePath = './' + moduleName;
207
+ const moduleImport = targetFunc
208
+ ? `import { ${funcName} } from '${relativePath}';`
209
+ : `import * as ${moduleName} from '${relativePath}';`;
210
+ // Mock setup based on framework
211
+ let mockSetup = '';
212
+ if (conventions.framework === 'vitest') {
213
+ mockSetup = `
214
+ beforeEach(() => {
215
+ vi.clearAllMocks();
216
+ });`;
217
+ }
218
+ else if (conventions.framework === 'jest') {
219
+ mockSetup = `
220
+ beforeEach(() => {
221
+ jest.clearAllMocks();
222
+ });`;
223
+ }
224
+ // Generate test cases
225
+ let testCases;
226
+ if (targetFunc) {
227
+ const params = targetFunc.parameters.map(p => p.name).join(', ');
228
+ const hasParams = targetFunc.parameters.length > 0;
229
+ testCases = `
230
+ it('should ${funcName} successfully', async () => {
231
+ // Arrange
232
+ ${hasParams ? `const ${targetFunc.parameters[0]?.name ?? 'input'} = {}; // TODO: Add test data` : '// No input needed'}
233
+
234
+ // Act
235
+ const result = await ${funcName}(${params});
236
+
237
+ // Assert
238
+ expect(result).toBeDefined();
239
+ // TODO: Add specific assertions
240
+ });
241
+
242
+ it('should handle errors gracefully', async () => {
243
+ // Arrange
244
+ // TODO: Set up error condition
245
+
246
+ // Act & Assert
247
+ // TODO: Add error handling test
248
+ });`;
249
+ }
250
+ else {
251
+ testCases = `
252
+ it('should work correctly', () => {
253
+ // Arrange
254
+ // TODO: Set up test data
255
+
256
+ // Act
257
+ // TODO: Call function under test
258
+
259
+ // Assert
260
+ // TODO: Add assertions
261
+ });`;
262
+ }
263
+ // Combine template
264
+ return `${imports}
265
+ ${moduleImport}
266
+
267
+ describe('${funcName}', () => {${mockSetup}
268
+ ${testCases}
269
+ });
270
+ `;
271
+ }
272
+ /**
273
+ * Tool definition for MCP registration
274
+ */
275
+ export const testTemplateToolDefinition = {
276
+ name: 'drift_test_template',
277
+ description: 'Generate test scaffolding based on existing test patterns. Returns ready-to-use template matching codebase conventions (framework, mocking style, file location).',
278
+ inputSchema: {
279
+ type: 'object',
280
+ properties: {
281
+ targetFile: {
282
+ type: 'string',
283
+ description: 'File being tested (relative path)',
284
+ },
285
+ function: {
286
+ type: 'string',
287
+ description: 'Optional: specific function to test',
288
+ },
289
+ type: {
290
+ type: 'string',
291
+ enum: ['unit', 'integration', 'e2e'],
292
+ description: 'Test type (default: unit)',
293
+ },
294
+ },
295
+ required: ['targetFile'],
296
+ },
297
+ };
298
+ //# sourceMappingURL=test-template.js.map