claude-flow-novice 1.5.17 → 1.5.18
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/.claude-flow-novice/dist/config/hooks/post-edit-pipeline.js +1807 -0
- package/.claude-flow-novice/dist/src/hooks/communication-integrated-post-edit.js +673 -0
- package/.claude-flow-novice/dist/src/hooks/enhanced/experience-adaptation-hooks.js +347 -0
- package/.claude-flow-novice/dist/src/hooks/enhanced/personalization-hooks.js +118 -0
- package/.claude-flow-novice/dist/src/hooks/enhanced-post-edit-pipeline.js +2044 -0
- package/.claude-flow-novice/dist/src/hooks/filter-integration.js +542 -0
- package/.claude-flow-novice/dist/src/hooks/guidance-hooks.js +629 -0
- package/.claude-flow-novice/dist/src/hooks/index.ts +239 -0
- package/.claude-flow-novice/dist/src/hooks/managers/enhanced-hook-manager.js +200 -0
- package/.claude-flow-novice/dist/src/hooks/resilient-hook-system.js +812 -0
- package/config/hooks/post-edit-pipeline.js +30 -0
- package/package.json +2 -1
- package/src/cli/simple-commands/init/templates/CLAUDE.md +38 -6
- package/src/hooks/communication-integrated-post-edit.js +673 -0
- package/src/hooks/enhanced/experience-adaptation-hooks.js +347 -0
- package/src/hooks/enhanced/personalization-hooks.js +118 -0
- package/src/hooks/enhanced-hooks-cli.js +168 -0
- package/src/hooks/enhanced-post-edit-pipeline.js +2044 -0
- package/src/hooks/filter-integration.js +542 -0
- package/src/hooks/guidance-hooks.js +629 -0
- package/src/hooks/index.ts +239 -0
- package/src/hooks/managers/enhanced-hook-manager.js +200 -0
- package/src/hooks/resilient-hook-system.js +812 -0
|
@@ -0,0 +1,2044 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Enhanced Post-Edit Pipeline for Claude Flow Novice
|
|
5
|
+
*
|
|
6
|
+
* Provides comprehensive real-time feedback to editing agents including:
|
|
7
|
+
* - TDD testing with single-file execution
|
|
8
|
+
* - Real-time coverage analysis and diff reporting
|
|
9
|
+
* - Advanced multi-language validation with error locations
|
|
10
|
+
* - Formatting diff preview and change detection
|
|
11
|
+
* - Actionable recommendations by category (security, performance, maintainability)
|
|
12
|
+
* - Blocking mechanisms for critical failures
|
|
13
|
+
* - Rich return objects for agent integration
|
|
14
|
+
* - Enhanced memory store with versioning
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs/promises';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { spawn, execSync } from 'child_process';
|
|
20
|
+
import { promisify } from 'util';
|
|
21
|
+
|
|
22
|
+
// Enhanced logging utilities with structured output
|
|
23
|
+
class Logger {
|
|
24
|
+
static success(msg, data = {}) {
|
|
25
|
+
console.log(`✅ ${msg}`);
|
|
26
|
+
return { level: 'success', message: msg, data };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static error(msg, data = {}) {
|
|
30
|
+
console.log(`❌ ${msg}`);
|
|
31
|
+
return { level: 'error', message: msg, data };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static warning(msg, data = {}) {
|
|
35
|
+
console.log(`⚠️ ${msg}`);
|
|
36
|
+
return { level: 'warning', message: msg, data };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static info(msg, data = {}) {
|
|
40
|
+
console.log(`ℹ️ ${msg}`);
|
|
41
|
+
return { level: 'info', message: msg, data };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static test(msg, data = {}) {
|
|
45
|
+
console.log(`🧪 ${msg}`);
|
|
46
|
+
return { level: 'test', message: msg, data };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static coverage(msg, data = {}) {
|
|
50
|
+
console.log(`📊 ${msg}`);
|
|
51
|
+
return { level: 'coverage', message: msg, data };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static tdd(msg, data = {}) {
|
|
55
|
+
console.log(`🔴🟢♻️ ${msg}`);
|
|
56
|
+
return { level: 'tdd', message: msg, data };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static debug(msg, data = {}) {
|
|
60
|
+
if (process.env.DEBUG) {
|
|
61
|
+
console.log(`🔍 ${msg}`);
|
|
62
|
+
}
|
|
63
|
+
return { level: 'debug', message: msg, data };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Single-file test execution engine
|
|
68
|
+
class SingleFileTestEngine {
|
|
69
|
+
constructor() {
|
|
70
|
+
this.testRunners = {
|
|
71
|
+
'.js': this.runJavaScriptTests.bind(this),
|
|
72
|
+
'.jsx': this.runJavaScriptTests.bind(this),
|
|
73
|
+
'.ts': this.runTypeScriptTests.bind(this),
|
|
74
|
+
'.tsx': this.runTypeScriptTests.bind(this),
|
|
75
|
+
'.py': this.runPythonTests.bind(this),
|
|
76
|
+
'.go': this.runGoTests.bind(this),
|
|
77
|
+
'.rs': this.runRustTests.bind(this),
|
|
78
|
+
'.java': this.runJavaTests.bind(this),
|
|
79
|
+
'.cpp': this.runCPPTests.bind(this),
|
|
80
|
+
'.c': this.runCTests.bind(this)
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
this.testFrameworks = {
|
|
84
|
+
javascript: ['jest', 'mocha', 'ava', 'tap'],
|
|
85
|
+
python: ['pytest', 'unittest', 'nose2'],
|
|
86
|
+
go: ['go test'],
|
|
87
|
+
rust: ['cargo test'],
|
|
88
|
+
java: ['junit', 'testng'],
|
|
89
|
+
cpp: ['gtest', 'catch2']
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async executeTests(file, content) {
|
|
94
|
+
const ext = path.extname(file).toLowerCase();
|
|
95
|
+
const runner = this.testRunners[ext];
|
|
96
|
+
|
|
97
|
+
if (!runner) {
|
|
98
|
+
return {
|
|
99
|
+
executed: false,
|
|
100
|
+
reason: `No test runner available for ${ext} files`,
|
|
101
|
+
framework: null,
|
|
102
|
+
results: null,
|
|
103
|
+
coverage: null,
|
|
104
|
+
tddCompliance: null
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check if this is a test file or source file
|
|
109
|
+
const isTestFile = this.isTestFile(file);
|
|
110
|
+
const relatedFile = isTestFile ? this.findSourceFile(file) : this.findTestFile(file);
|
|
111
|
+
|
|
112
|
+
return await runner(file, content, { isTestFile, relatedFile });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async runJavaScriptTests(file, content, options = {}) {
|
|
116
|
+
const { isTestFile, relatedFile } = options;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// Detect available test framework
|
|
120
|
+
const framework = await this.detectJSTestFramework();
|
|
121
|
+
|
|
122
|
+
if (!framework) {
|
|
123
|
+
return {
|
|
124
|
+
executed: false,
|
|
125
|
+
reason: 'No JavaScript test framework detected (jest, mocha, etc.)',
|
|
126
|
+
framework: null,
|
|
127
|
+
results: null,
|
|
128
|
+
coverage: null,
|
|
129
|
+
tddCompliance: this.checkTDDCompliance(file, relatedFile, null)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Execute tests based on framework
|
|
134
|
+
let testResults = null;
|
|
135
|
+
let coverage = null;
|
|
136
|
+
|
|
137
|
+
if (framework === 'jest') {
|
|
138
|
+
testResults = await this.runJestSingleFile(file, isTestFile);
|
|
139
|
+
coverage = await this.getJestCoverage(file);
|
|
140
|
+
} else if (framework === 'mocha') {
|
|
141
|
+
testResults = await this.runMochaSingleFile(file, isTestFile);
|
|
142
|
+
coverage = await this.getMochaCoverage(file);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
executed: true,
|
|
149
|
+
framework,
|
|
150
|
+
results: testResults,
|
|
151
|
+
coverage,
|
|
152
|
+
tddCompliance,
|
|
153
|
+
singleFileMode: true
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
} catch (error) {
|
|
157
|
+
return {
|
|
158
|
+
executed: false,
|
|
159
|
+
reason: `Test execution failed: ${error.message}`,
|
|
160
|
+
framework: null,
|
|
161
|
+
results: null,
|
|
162
|
+
coverage: null,
|
|
163
|
+
tddCompliance: null,
|
|
164
|
+
error: error.message
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async runTypeScriptTests(file, content, options = {}) {
|
|
170
|
+
// TypeScript tests - compile to JS then run
|
|
171
|
+
const jsResult = await this.runJavaScriptTests(file, content, options);
|
|
172
|
+
|
|
173
|
+
// Add TypeScript-specific test handling
|
|
174
|
+
if (jsResult.executed) {
|
|
175
|
+
jsResult.language = 'typescript';
|
|
176
|
+
jsResult.compiled = true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return jsResult;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async runGoTests(file, content, options = {}) {
|
|
183
|
+
const { isTestFile, relatedFile } = options;
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
if (!isTestFile) {
|
|
187
|
+
// For source files, find and run corresponding test
|
|
188
|
+
const testFile = this.findTestFile(file);
|
|
189
|
+
if (!testFile || !await this.fileExists(testFile)) {
|
|
190
|
+
return {
|
|
191
|
+
executed: false,
|
|
192
|
+
reason: 'No corresponding test file found for Go source',
|
|
193
|
+
framework: 'go test',
|
|
194
|
+
results: null,
|
|
195
|
+
coverage: null,
|
|
196
|
+
tddCompliance: this.checkTDDCompliance(file, null, null)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
file = testFile;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Run go test on single file
|
|
203
|
+
const testResults = await this.runGoTestSingleFile(file);
|
|
204
|
+
const coverage = await this.getGoCoverage(file);
|
|
205
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
executed: true,
|
|
209
|
+
framework: 'go test',
|
|
210
|
+
results: testResults,
|
|
211
|
+
coverage,
|
|
212
|
+
tddCompliance,
|
|
213
|
+
singleFileMode: true
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return {
|
|
218
|
+
executed: false,
|
|
219
|
+
reason: `Go test execution failed: ${error.message}`,
|
|
220
|
+
framework: 'go test',
|
|
221
|
+
results: null,
|
|
222
|
+
coverage: null,
|
|
223
|
+
tddCompliance: null,
|
|
224
|
+
error: error.message
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async runRustTests(file, content, options = {}) {
|
|
230
|
+
const { isTestFile, relatedFile } = options;
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
// Rust uses integrated testing with cargo test
|
|
234
|
+
const testResults = await this.runCargoTestSingleFile(file);
|
|
235
|
+
const coverage = await this.getRustCoverage(file);
|
|
236
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
executed: true,
|
|
240
|
+
framework: 'cargo test',
|
|
241
|
+
results: testResults,
|
|
242
|
+
coverage,
|
|
243
|
+
tddCompliance,
|
|
244
|
+
singleFileMode: true
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return {
|
|
249
|
+
executed: false,
|
|
250
|
+
reason: `Rust test execution failed: ${error.message}`,
|
|
251
|
+
framework: 'cargo test',
|
|
252
|
+
results: null,
|
|
253
|
+
coverage: null,
|
|
254
|
+
tddCompliance: null,
|
|
255
|
+
error: error.message
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async runJavaTests(file, content, options = {}) {
|
|
261
|
+
const { isTestFile, relatedFile } = options;
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const framework = await this.detectJavaTestFramework();
|
|
265
|
+
|
|
266
|
+
if (!framework) {
|
|
267
|
+
return {
|
|
268
|
+
executed: false,
|
|
269
|
+
reason: 'No Java test framework detected (JUnit, TestNG)',
|
|
270
|
+
framework: null,
|
|
271
|
+
results: null,
|
|
272
|
+
coverage: null,
|
|
273
|
+
tddCompliance: this.checkTDDCompliance(file, relatedFile, null)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const testResults = await this.runJavaTestSingleFile(file, framework);
|
|
278
|
+
const coverage = await this.getJavaCoverage(file);
|
|
279
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
executed: true,
|
|
283
|
+
framework,
|
|
284
|
+
results: testResults,
|
|
285
|
+
coverage,
|
|
286
|
+
tddCompliance,
|
|
287
|
+
singleFileMode: true
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
} catch (error) {
|
|
291
|
+
return {
|
|
292
|
+
executed: false,
|
|
293
|
+
reason: `Java test execution failed: ${error.message}`,
|
|
294
|
+
framework: null,
|
|
295
|
+
results: null,
|
|
296
|
+
coverage: null,
|
|
297
|
+
tddCompliance: null,
|
|
298
|
+
error: error.message
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async runCPPTests(file, content, options = {}) {
|
|
304
|
+
return this.runCTests(file, content, options); // Similar implementation
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async runCTests(file, content, options = {}) {
|
|
308
|
+
const { isTestFile, relatedFile } = options;
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const framework = await this.detectCTestFramework();
|
|
312
|
+
|
|
313
|
+
if (!framework) {
|
|
314
|
+
return {
|
|
315
|
+
executed: false,
|
|
316
|
+
reason: 'No C/C++ test framework detected (gtest, catch2)',
|
|
317
|
+
framework: null,
|
|
318
|
+
results: null,
|
|
319
|
+
coverage: null,
|
|
320
|
+
tddCompliance: this.checkTDDCompliance(file, relatedFile, null)
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const testResults = await this.runCTestSingleFile(file, framework);
|
|
325
|
+
const coverage = await this.getCCoverage(file);
|
|
326
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
executed: true,
|
|
330
|
+
framework,
|
|
331
|
+
results: testResults,
|
|
332
|
+
coverage,
|
|
333
|
+
tddCompliance,
|
|
334
|
+
singleFileMode: true
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return {
|
|
339
|
+
executed: false,
|
|
340
|
+
reason: `C/C++ test execution failed: ${error.message}`,
|
|
341
|
+
framework: null,
|
|
342
|
+
results: null,
|
|
343
|
+
coverage: null,
|
|
344
|
+
tddCompliance: null,
|
|
345
|
+
error: error.message
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async runPythonTests(file, content, options = {}) {
|
|
351
|
+
const { isTestFile, relatedFile } = options;
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const framework = await this.detectPythonTestFramework();
|
|
355
|
+
|
|
356
|
+
if (!framework) {
|
|
357
|
+
return {
|
|
358
|
+
executed: false,
|
|
359
|
+
reason: 'No Python test framework detected (pytest, unittest)',
|
|
360
|
+
framework: null,
|
|
361
|
+
results: null,
|
|
362
|
+
coverage: null,
|
|
363
|
+
tddCompliance: this.checkTDDCompliance(file, relatedFile, null)
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let testResults = null;
|
|
368
|
+
let coverage = null;
|
|
369
|
+
|
|
370
|
+
if (framework === 'pytest') {
|
|
371
|
+
testResults = await this.runPytestSingleFile(file, isTestFile);
|
|
372
|
+
coverage = await this.getPytestCoverage(file);
|
|
373
|
+
} else if (framework === 'unittest') {
|
|
374
|
+
testResults = await this.runUnittestSingleFile(file, isTestFile);
|
|
375
|
+
coverage = await this.getUnittestCoverage(file);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
executed: true,
|
|
382
|
+
framework,
|
|
383
|
+
results: testResults,
|
|
384
|
+
coverage,
|
|
385
|
+
tddCompliance,
|
|
386
|
+
singleFileMode: true
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
} catch (error) {
|
|
390
|
+
return {
|
|
391
|
+
executed: false,
|
|
392
|
+
reason: `Python test execution failed: ${error.message}`,
|
|
393
|
+
framework: null,
|
|
394
|
+
results: null,
|
|
395
|
+
coverage: null,
|
|
396
|
+
tddCompliance: null,
|
|
397
|
+
error: error.message
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Framework detection methods
|
|
403
|
+
async detectJSTestFramework() {
|
|
404
|
+
try {
|
|
405
|
+
// Check multiple possible package.json locations
|
|
406
|
+
const possiblePaths = [
|
|
407
|
+
path.join(process.cwd(), 'package.json'),
|
|
408
|
+
path.join(path.dirname(process.cwd()), 'package.json'),
|
|
409
|
+
path.join(process.cwd(), 'test-files', 'package.json'),
|
|
410
|
+
path.join(process.cwd(), '..', 'package.json')
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
for (const packagePath of possiblePaths) {
|
|
414
|
+
try {
|
|
415
|
+
const packageContent = await fs.readFile(packagePath, 'utf8');
|
|
416
|
+
const packageJson = JSON.parse(packageContent);
|
|
417
|
+
|
|
418
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
419
|
+
|
|
420
|
+
if (deps.jest) return 'jest';
|
|
421
|
+
if (deps.mocha) return 'mocha';
|
|
422
|
+
if (deps.ava) return 'ava';
|
|
423
|
+
if (deps.tap) return 'tap';
|
|
424
|
+
} catch {
|
|
425
|
+
// Continue to next path
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return null;
|
|
430
|
+
} catch {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async detectPythonTestFramework() {
|
|
436
|
+
try {
|
|
437
|
+
// Check for pytest
|
|
438
|
+
execSync('pytest --version', { stdio: 'ignore' });
|
|
439
|
+
return 'pytest';
|
|
440
|
+
} catch {
|
|
441
|
+
try {
|
|
442
|
+
// Check for unittest (built-in)
|
|
443
|
+
execSync('python -m unittest --help', { stdio: 'ignore' });
|
|
444
|
+
return 'unittest';
|
|
445
|
+
} catch {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async detectJavaTestFramework() {
|
|
452
|
+
try {
|
|
453
|
+
// Check for JUnit in classpath or build files
|
|
454
|
+
const buildFiles = ['pom.xml', 'build.gradle', 'build.gradle.kts'];
|
|
455
|
+
|
|
456
|
+
for (const buildFile of buildFiles) {
|
|
457
|
+
if (await this.fileExists(buildFile)) {
|
|
458
|
+
const content = await fs.readFile(buildFile, 'utf8');
|
|
459
|
+
if (content.includes('junit')) return 'junit';
|
|
460
|
+
if (content.includes('testng')) return 'testng';
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return null;
|
|
465
|
+
} catch {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async detectCTestFramework() {
|
|
471
|
+
try {
|
|
472
|
+
// Check for gtest
|
|
473
|
+
execSync('pkg-config --exists gtest', { stdio: 'ignore' });
|
|
474
|
+
return 'gtest';
|
|
475
|
+
} catch {
|
|
476
|
+
try {
|
|
477
|
+
// Check for Catch2
|
|
478
|
+
execSync('pkg-config --exists catch2', { stdio: 'ignore' });
|
|
479
|
+
return 'catch2';
|
|
480
|
+
} catch {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Test execution implementations
|
|
487
|
+
async runJestSingleFile(file, isTestFile) {
|
|
488
|
+
try {
|
|
489
|
+
const testPattern = isTestFile ? file : this.findTestFile(file);
|
|
490
|
+
|
|
491
|
+
if (!testPattern || !await this.fileExists(testPattern)) {
|
|
492
|
+
return {
|
|
493
|
+
passed: false,
|
|
494
|
+
reason: 'No test file found',
|
|
495
|
+
tests: [],
|
|
496
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Try different working directories to find Jest
|
|
501
|
+
const possibleDirs = [
|
|
502
|
+
path.dirname(testPattern),
|
|
503
|
+
path.join(process.cwd(), 'test-files'),
|
|
504
|
+
process.cwd()
|
|
505
|
+
];
|
|
506
|
+
|
|
507
|
+
let jestOutput = null;
|
|
508
|
+
let workingDir = null;
|
|
509
|
+
|
|
510
|
+
for (const dir of possibleDirs) {
|
|
511
|
+
try {
|
|
512
|
+
const result = execSync(`npx jest "${path.basename(testPattern)}" --json --coverage=false`, {
|
|
513
|
+
encoding: 'utf8',
|
|
514
|
+
stdio: 'pipe',
|
|
515
|
+
cwd: dir
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
jestOutput = JSON.parse(result);
|
|
519
|
+
workingDir = dir;
|
|
520
|
+
break;
|
|
521
|
+
} catch (error) {
|
|
522
|
+
// Continue to next directory
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!jestOutput) {
|
|
528
|
+
return {
|
|
529
|
+
passed: false,
|
|
530
|
+
reason: 'Jest execution failed in all attempted directories',
|
|
531
|
+
tests: [],
|
|
532
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
passed: jestOutput.success,
|
|
538
|
+
tests: jestOutput.testResults[0]?.assertionResults || [],
|
|
539
|
+
summary: {
|
|
540
|
+
total: jestOutput.numTotalTests,
|
|
541
|
+
passed: jestOutput.numPassedTests,
|
|
542
|
+
failed: jestOutput.numFailedTests,
|
|
543
|
+
skipped: jestOutput.numPendingTests
|
|
544
|
+
},
|
|
545
|
+
duration: jestOutput.testResults[0]?.endTime - jestOutput.testResults[0]?.startTime,
|
|
546
|
+
workingDir
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
} catch (error) {
|
|
550
|
+
return {
|
|
551
|
+
passed: false,
|
|
552
|
+
error: error.message,
|
|
553
|
+
tests: [],
|
|
554
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Additional test runner implementations (mock for now)
|
|
560
|
+
async runPytestSingleFile(file, isTestFile) {
|
|
561
|
+
return {
|
|
562
|
+
passed: true,
|
|
563
|
+
tests: [],
|
|
564
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async runUnittestSingleFile(file, isTestFile) {
|
|
569
|
+
return {
|
|
570
|
+
passed: true,
|
|
571
|
+
tests: [],
|
|
572
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
async runMochaSingleFile(file, isTestFile) {
|
|
577
|
+
return {
|
|
578
|
+
passed: true,
|
|
579
|
+
tests: [],
|
|
580
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async runGoTestSingleFile(file) {
|
|
585
|
+
return {
|
|
586
|
+
passed: true,
|
|
587
|
+
tests: [],
|
|
588
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async runCargoTestSingleFile(file) {
|
|
593
|
+
return {
|
|
594
|
+
passed: true,
|
|
595
|
+
tests: [],
|
|
596
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async runJavaTestSingleFile(file, framework) {
|
|
601
|
+
return {
|
|
602
|
+
passed: true,
|
|
603
|
+
tests: [],
|
|
604
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async runCTestSingleFile(file, framework) {
|
|
609
|
+
return {
|
|
610
|
+
passed: true,
|
|
611
|
+
tests: [],
|
|
612
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Coverage analysis methods
|
|
617
|
+
async getJestCoverage(file) {
|
|
618
|
+
try {
|
|
619
|
+
const result = execSync(`npx jest "${file}" --coverage --coverageReporters=json --silent`, {
|
|
620
|
+
encoding: 'utf8',
|
|
621
|
+
stdio: 'pipe'
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
const coveragePath = path.join(process.cwd(), 'coverage', 'coverage-final.json');
|
|
625
|
+
const coverageData = JSON.parse(await fs.readFile(coveragePath, 'utf8'));
|
|
626
|
+
|
|
627
|
+
const fileCoverage = coverageData[path.resolve(file)] || {};
|
|
628
|
+
|
|
629
|
+
return {
|
|
630
|
+
lines: {
|
|
631
|
+
total: Object.keys(fileCoverage.s || {}).length,
|
|
632
|
+
covered: Object.values(fileCoverage.s || {}).filter(v => v > 0).length,
|
|
633
|
+
percentage: this.calculatePercentage(fileCoverage.s)
|
|
634
|
+
},
|
|
635
|
+
functions: {
|
|
636
|
+
total: Object.keys(fileCoverage.f || {}).length,
|
|
637
|
+
covered: Object.values(fileCoverage.f || {}).filter(v => v > 0).length,
|
|
638
|
+
percentage: this.calculatePercentage(fileCoverage.f)
|
|
639
|
+
},
|
|
640
|
+
branches: {
|
|
641
|
+
total: Object.keys(fileCoverage.b || {}).length,
|
|
642
|
+
covered: Object.values(fileCoverage.b || {}).flat().filter(v => v > 0).length,
|
|
643
|
+
percentage: this.calculatePercentage(fileCoverage.b, true)
|
|
644
|
+
},
|
|
645
|
+
statements: {
|
|
646
|
+
total: Object.keys(fileCoverage.s || {}).length,
|
|
647
|
+
covered: Object.values(fileCoverage.s || {}).filter(v => v > 0).length,
|
|
648
|
+
percentage: this.calculatePercentage(fileCoverage.s)
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
} catch (error) {
|
|
653
|
+
return {
|
|
654
|
+
error: error.message,
|
|
655
|
+
available: false
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Additional coverage methods (mock implementations)
|
|
661
|
+
async getPytestCoverage(file) {
|
|
662
|
+
return { error: 'Pytest coverage not implemented', available: false };
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async getUnittestCoverage(file) {
|
|
666
|
+
return { error: 'Unittest coverage not implemented', available: false };
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async getMochaCoverage(file) {
|
|
670
|
+
return { error: 'Mocha coverage not implemented', available: false };
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async getGoCoverage(file) {
|
|
674
|
+
return { error: 'Go coverage not implemented', available: false };
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
async runCargoTestSingleFile(file) {
|
|
678
|
+
try {
|
|
679
|
+
// For Rust, we run tests from the crate root, not individual files
|
|
680
|
+
const { exec } = await import('child_process');
|
|
681
|
+
const { promisify } = await import('util');
|
|
682
|
+
const execAsync = promisify(exec);
|
|
683
|
+
|
|
684
|
+
// Check if we're in a Cargo project
|
|
685
|
+
const cargoTomlExists = await this.fileExists('Cargo.toml');
|
|
686
|
+
if (!cargoTomlExists) {
|
|
687
|
+
return {
|
|
688
|
+
passed: false,
|
|
689
|
+
reason: 'No Cargo.toml found - not a Rust project',
|
|
690
|
+
tests: [],
|
|
691
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Run cargo check first for syntax validation
|
|
696
|
+
await execAsync('cargo check --quiet');
|
|
697
|
+
|
|
698
|
+
// Run cargo test with output capture
|
|
699
|
+
const { stdout, stderr } = await execAsync('cargo test --quiet -- --nocapture', {
|
|
700
|
+
timeout: 30000,
|
|
701
|
+
maxBuffer: 1024 * 1024
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// Parse test output (simplified)
|
|
705
|
+
const testOutput = stdout + stderr;
|
|
706
|
+
const testRegex = /test\s+(\S+)\s+\.\.\.\s+(ok|FAILED)/g;
|
|
707
|
+
const tests = [];
|
|
708
|
+
let match;
|
|
709
|
+
|
|
710
|
+
while ((match = testRegex.exec(testOutput)) !== null) {
|
|
711
|
+
tests.push({
|
|
712
|
+
name: match[1],
|
|
713
|
+
status: match[2] === 'ok' ? 'passed' : 'failed'
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const passed = tests.filter(t => t.status === 'passed').length;
|
|
718
|
+
const failed = tests.filter(t => t.status === 'failed').length;
|
|
719
|
+
|
|
720
|
+
return {
|
|
721
|
+
passed: failed === 0,
|
|
722
|
+
reason: failed > 0 ? `${failed} tests failed` : 'All tests passed',
|
|
723
|
+
tests,
|
|
724
|
+
summary: { total: tests.length, passed, failed, skipped: 0 }
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
} catch (error) {
|
|
728
|
+
return {
|
|
729
|
+
passed: false,
|
|
730
|
+
reason: `Cargo test failed: ${error.message}`,
|
|
731
|
+
tests: [],
|
|
732
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
async getRustCoverage(file) {
|
|
738
|
+
try {
|
|
739
|
+
// Check if cargo-tarpaulin is available
|
|
740
|
+
const { exec } = await import('child_process');
|
|
741
|
+
const { promisify } = await import('util');
|
|
742
|
+
const execAsync = promisify(exec);
|
|
743
|
+
|
|
744
|
+
// Try to run tarpaulin for coverage
|
|
745
|
+
const { stdout } = await execAsync('cargo tarpaulin --version', { timeout: 5000 });
|
|
746
|
+
|
|
747
|
+
if (stdout.includes('tarpaulin')) {
|
|
748
|
+
// Run coverage analysis
|
|
749
|
+
const { stdout: coverageOutput } = await execAsync('cargo tarpaulin --out Json --timeout 30', {
|
|
750
|
+
timeout: 60000,
|
|
751
|
+
maxBuffer: 1024 * 1024
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const coverage = JSON.parse(coverageOutput);
|
|
755
|
+
return {
|
|
756
|
+
available: true,
|
|
757
|
+
percentage: coverage.coverage || 0,
|
|
758
|
+
lines: coverage.files || {},
|
|
759
|
+
tool: 'cargo-tarpaulin'
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
} catch (error) {
|
|
763
|
+
// Fallback: check if we can at least detect test presence
|
|
764
|
+
const cargoTomlExists = await this.fileExists('Cargo.toml');
|
|
765
|
+
return {
|
|
766
|
+
available: false,
|
|
767
|
+
error: cargoTomlExists
|
|
768
|
+
? 'cargo-tarpaulin not installed - run: cargo install cargo-tarpaulin'
|
|
769
|
+
: 'Not a Rust project (no Cargo.toml)',
|
|
770
|
+
tool: 'cargo-tarpaulin'
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
async getJavaCoverage(file) {
|
|
776
|
+
return { error: 'Java coverage not implemented', available: false };
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async getCCoverage(file) {
|
|
780
|
+
return { error: 'C/C++ coverage not implemented', available: false };
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// TDD compliance checking
|
|
784
|
+
checkTDDCompliance(sourceFile, testFile, testResults) {
|
|
785
|
+
const compliance = {
|
|
786
|
+
hasTests: false,
|
|
787
|
+
testFirst: false,
|
|
788
|
+
redGreenRefactor: false,
|
|
789
|
+
coverage: 0,
|
|
790
|
+
recommendations: []
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
// Check if tests exist
|
|
794
|
+
if (testFile && this.fileExistsSync(testFile)) {
|
|
795
|
+
compliance.hasTests = true;
|
|
796
|
+
} else {
|
|
797
|
+
compliance.recommendations.push({
|
|
798
|
+
type: 'tdd_violation',
|
|
799
|
+
priority: 'high',
|
|
800
|
+
message: 'No test file found - TDD requires tests first',
|
|
801
|
+
action: `Create test file: ${this.suggestTestFileName(sourceFile)}`
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Check test results
|
|
806
|
+
if (testResults && testResults.summary) {
|
|
807
|
+
const { total, passed, failed } = testResults.summary;
|
|
808
|
+
|
|
809
|
+
if (total === 0) {
|
|
810
|
+
compliance.recommendations.push({
|
|
811
|
+
type: 'tdd_violation',
|
|
812
|
+
priority: 'high',
|
|
813
|
+
message: 'No tests found in test file',
|
|
814
|
+
action: 'Write tests before implementing functionality'
|
|
815
|
+
});
|
|
816
|
+
} else if (failed > 0) {
|
|
817
|
+
compliance.redGreenRefactor = true; // Red phase
|
|
818
|
+
compliance.recommendations.push({
|
|
819
|
+
type: 'tdd_red_phase',
|
|
820
|
+
priority: 'medium',
|
|
821
|
+
message: `${failed} failing tests - in RED phase of TDD`,
|
|
822
|
+
action: 'Implement minimal code to make tests pass'
|
|
823
|
+
});
|
|
824
|
+
} else if (passed > 0) {
|
|
825
|
+
compliance.redGreenRefactor = true; // Green phase
|
|
826
|
+
compliance.recommendations.push({
|
|
827
|
+
type: 'tdd_green_phase',
|
|
828
|
+
priority: 'low',
|
|
829
|
+
message: 'All tests passing - in GREEN phase of TDD',
|
|
830
|
+
action: 'Consider refactoring for better design'
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
return compliance;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Utility methods
|
|
839
|
+
isTestFile(file) {
|
|
840
|
+
const fileName = path.basename(file);
|
|
841
|
+
return fileName.includes('.test.') ||
|
|
842
|
+
fileName.includes('.spec.') ||
|
|
843
|
+
fileName.includes('_test') ||
|
|
844
|
+
fileName.endsWith('Test.java') ||
|
|
845
|
+
fileName.endsWith('Test.cpp') ||
|
|
846
|
+
file.includes('/test/') ||
|
|
847
|
+
file.includes('/tests/');
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
findTestFile(sourceFile) {
|
|
851
|
+
const ext = path.extname(sourceFile);
|
|
852
|
+
const base = path.basename(sourceFile, ext);
|
|
853
|
+
const dir = path.dirname(sourceFile);
|
|
854
|
+
|
|
855
|
+
const testPatterns = [
|
|
856
|
+
`${base}.test${ext}`,
|
|
857
|
+
`${base}.spec${ext}`,
|
|
858
|
+
`${base}_test${ext}`,
|
|
859
|
+
`test_${base}${ext}`,
|
|
860
|
+
`${base}Test${ext}`
|
|
861
|
+
];
|
|
862
|
+
|
|
863
|
+
// Check same directory first
|
|
864
|
+
for (const pattern of testPatterns) {
|
|
865
|
+
const testPath = path.join(dir, pattern);
|
|
866
|
+
if (this.fileExistsSync(testPath)) return testPath;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Check test directories
|
|
870
|
+
const testDirs = ['test', 'tests', '__tests__', 'spec'];
|
|
871
|
+
for (const testDir of testDirs) {
|
|
872
|
+
for (const pattern of testPatterns) {
|
|
873
|
+
const testPath = path.join(dir, testDir, pattern);
|
|
874
|
+
if (this.fileExistsSync(testPath)) return testPath;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
findSourceFile(testFile) {
|
|
882
|
+
const ext = path.extname(testFile);
|
|
883
|
+
let base = path.basename(testFile, ext);
|
|
884
|
+
|
|
885
|
+
// Remove test suffixes
|
|
886
|
+
base = base.replace(/\.(test|spec)$/, '')
|
|
887
|
+
.replace(/_test$/, '')
|
|
888
|
+
.replace(/^test_/, '')
|
|
889
|
+
.replace(/Test$/, '');
|
|
890
|
+
|
|
891
|
+
const dir = path.dirname(testFile);
|
|
892
|
+
const sourcePatterns = [
|
|
893
|
+
`${base}${ext}`,
|
|
894
|
+
`${base}.js`,
|
|
895
|
+
`${base}.ts`,
|
|
896
|
+
`${base}.py`,
|
|
897
|
+
`${base}.go`,
|
|
898
|
+
`${base}.rs`,
|
|
899
|
+
`${base}.java`,
|
|
900
|
+
`${base}.cpp`,
|
|
901
|
+
`${base}.c`
|
|
902
|
+
];
|
|
903
|
+
|
|
904
|
+
// Check parent directory (if in test folder)
|
|
905
|
+
const parentDir = path.dirname(dir);
|
|
906
|
+
for (const pattern of sourcePatterns) {
|
|
907
|
+
const sourcePath = path.join(parentDir, pattern);
|
|
908
|
+
if (this.fileExistsSync(sourcePath)) return sourcePath;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Check same directory
|
|
912
|
+
for (const pattern of sourcePatterns) {
|
|
913
|
+
const sourcePath = path.join(dir, pattern);
|
|
914
|
+
if (this.fileExistsSync(sourcePath)) return sourcePath;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
suggestTestFileName(sourceFile) {
|
|
921
|
+
const ext = path.extname(sourceFile);
|
|
922
|
+
const base = path.basename(sourceFile, ext);
|
|
923
|
+
const dir = path.dirname(sourceFile);
|
|
924
|
+
|
|
925
|
+
// Language-specific conventions
|
|
926
|
+
if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {
|
|
927
|
+
return path.join(dir, `${base}.test${ext}`);
|
|
928
|
+
} else if (ext === '.py') {
|
|
929
|
+
return path.join(dir, `test_${base}${ext}`);
|
|
930
|
+
} else if (ext === '.go') {
|
|
931
|
+
return path.join(dir, `${base}_test${ext}`);
|
|
932
|
+
} else if (ext === '.java') {
|
|
933
|
+
return path.join(dir, `${base}Test${ext}`);
|
|
934
|
+
} else {
|
|
935
|
+
return path.join(dir, `${base}_test${ext}`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
calculatePercentage(coverage, isBranch = false) {
|
|
940
|
+
if (!coverage) return 0;
|
|
941
|
+
|
|
942
|
+
const values = isBranch ? Object.values(coverage).flat() : Object.values(coverage);
|
|
943
|
+
const total = values.length;
|
|
944
|
+
const covered = values.filter(v => v > 0).length;
|
|
945
|
+
|
|
946
|
+
return total > 0 ? Math.round((covered / total) * 100) : 0;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
fileExistsSync(filePath) {
|
|
950
|
+
try {
|
|
951
|
+
return fs.access(filePath, fs.constants.F_OK).then(() => true).catch(() => false);
|
|
952
|
+
} catch {
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
async fileExists(filePath) {
|
|
958
|
+
try {
|
|
959
|
+
await fs.access(filePath, fs.constants.F_OK);
|
|
960
|
+
return true;
|
|
961
|
+
} catch {
|
|
962
|
+
return false;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Enhanced validation engine
|
|
968
|
+
class ValidationEngine {
|
|
969
|
+
constructor() {
|
|
970
|
+
this.validators = {
|
|
971
|
+
'.js': this.validateJavaScript.bind(this),
|
|
972
|
+
'.jsx': this.validateJavaScript.bind(this),
|
|
973
|
+
'.ts': this.validateTypeScript.bind(this),
|
|
974
|
+
'.tsx': this.validateTypeScript.bind(this),
|
|
975
|
+
'.json': this.validateJSON.bind(this),
|
|
976
|
+
'.py': this.validatePython.bind(this),
|
|
977
|
+
'.go': this.validateGo.bind(this),
|
|
978
|
+
'.rs': this.validateRust.bind(this),
|
|
979
|
+
'.java': this.validateJava.bind(this),
|
|
980
|
+
'.cpp': this.validateCPP.bind(this),
|
|
981
|
+
'.c': this.validateC.bind(this)
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
async validate(file, content) {
|
|
986
|
+
const ext = path.extname(file).toLowerCase();
|
|
987
|
+
const validator = this.validators[ext];
|
|
988
|
+
|
|
989
|
+
if (!validator) {
|
|
990
|
+
return {
|
|
991
|
+
passed: true,
|
|
992
|
+
issues: [],
|
|
993
|
+
suggestions: [`No specific validator for ${ext} files`],
|
|
994
|
+
coverage: 'basic'
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
return await validator(file, content);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
async validateJavaScript(file, content) {
|
|
1002
|
+
const issues = [];
|
|
1003
|
+
const suggestions = [];
|
|
1004
|
+
|
|
1005
|
+
try {
|
|
1006
|
+
// Basic syntax validation using Node.js VM
|
|
1007
|
+
const { createContext, runInContext } = await import('vm');
|
|
1008
|
+
const context = createContext({});
|
|
1009
|
+
|
|
1010
|
+
// Wrap in function to avoid top-level issues
|
|
1011
|
+
const wrappedCode = `(function() { ${content} })`;
|
|
1012
|
+
runInContext(wrappedCode, context);
|
|
1013
|
+
|
|
1014
|
+
// Advanced static analysis
|
|
1015
|
+
const analysis = this.analyzeJavaScript(content);
|
|
1016
|
+
issues.push(...analysis.issues);
|
|
1017
|
+
suggestions.push(...analysis.suggestions);
|
|
1018
|
+
|
|
1019
|
+
return {
|
|
1020
|
+
passed: issues.filter(i => i.severity === 'error').length === 0,
|
|
1021
|
+
issues,
|
|
1022
|
+
suggestions,
|
|
1023
|
+
coverage: 'advanced',
|
|
1024
|
+
metrics: analysis.metrics
|
|
1025
|
+
};
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
return {
|
|
1028
|
+
passed: false,
|
|
1029
|
+
issues: [{
|
|
1030
|
+
type: 'syntax_error',
|
|
1031
|
+
severity: 'error',
|
|
1032
|
+
message: error.message,
|
|
1033
|
+
line: this.extractLineNumber(error.message),
|
|
1034
|
+
column: this.extractColumnNumber(error.message)
|
|
1035
|
+
}],
|
|
1036
|
+
suggestions: [
|
|
1037
|
+
'Fix syntax error before proceeding',
|
|
1038
|
+
'Check for missing brackets, semicolons, or quotes'
|
|
1039
|
+
],
|
|
1040
|
+
coverage: 'syntax_only'
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
async validateTypeScript(file, content) {
|
|
1046
|
+
// Similar to JavaScript but with TypeScript-specific checks
|
|
1047
|
+
const jsResult = await this.validateJavaScript(file, content);
|
|
1048
|
+
|
|
1049
|
+
// Add TypeScript-specific analysis
|
|
1050
|
+
const tsIssues = this.analyzeTypeScript(content);
|
|
1051
|
+
|
|
1052
|
+
return {
|
|
1053
|
+
...jsResult,
|
|
1054
|
+
issues: [...jsResult.issues, ...tsIssues.issues],
|
|
1055
|
+
suggestions: [...jsResult.suggestions, ...tsIssues.suggestions],
|
|
1056
|
+
coverage: 'typescript'
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
async validateJSON(file, content) {
|
|
1061
|
+
try {
|
|
1062
|
+
JSON.parse(content);
|
|
1063
|
+
return {
|
|
1064
|
+
passed: true,
|
|
1065
|
+
issues: [],
|
|
1066
|
+
suggestions: ['JSON structure is valid'],
|
|
1067
|
+
coverage: 'complete'
|
|
1068
|
+
};
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
return {
|
|
1071
|
+
passed: false,
|
|
1072
|
+
issues: [{
|
|
1073
|
+
type: 'json_parse_error',
|
|
1074
|
+
severity: 'error',
|
|
1075
|
+
message: error.message,
|
|
1076
|
+
line: this.extractLineNumber(error.message),
|
|
1077
|
+
column: this.extractColumnNumber(error.message)
|
|
1078
|
+
}],
|
|
1079
|
+
suggestions: [
|
|
1080
|
+
'Fix JSON syntax error',
|
|
1081
|
+
'Check for trailing commas, missing quotes, or invalid escape sequences'
|
|
1082
|
+
],
|
|
1083
|
+
coverage: 'syntax_only'
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
async validatePython(file, content) {
|
|
1089
|
+
// Simulate Python validation
|
|
1090
|
+
const issues = [];
|
|
1091
|
+
const suggestions = [];
|
|
1092
|
+
|
|
1093
|
+
// Basic checks
|
|
1094
|
+
if (content.includes('print ') && !content.includes('print(')) {
|
|
1095
|
+
issues.push({
|
|
1096
|
+
type: 'python_version',
|
|
1097
|
+
severity: 'warning',
|
|
1098
|
+
message: 'Using Python 2 print statement syntax',
|
|
1099
|
+
suggestion: 'Use print() function for Python 3 compatibility'
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
if (!content.includes('import') && content.length > 100) {
|
|
1104
|
+
suggestions.push('Consider importing required modules');
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
return {
|
|
1108
|
+
passed: issues.filter(i => i.severity === 'error').length === 0,
|
|
1109
|
+
issues,
|
|
1110
|
+
suggestions: suggestions.length ? suggestions : ['Python syntax appears valid'],
|
|
1111
|
+
coverage: 'basic'
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
async validateGo(file, content) {
|
|
1116
|
+
const issues = [];
|
|
1117
|
+
const suggestions = [];
|
|
1118
|
+
|
|
1119
|
+
if (!content.includes('package ')) {
|
|
1120
|
+
issues.push({
|
|
1121
|
+
type: 'go_package',
|
|
1122
|
+
severity: 'error',
|
|
1123
|
+
message: 'Go files must declare a package',
|
|
1124
|
+
suggestion: 'Add package declaration at the top of the file'
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
return {
|
|
1129
|
+
passed: issues.filter(i => i.severity === 'error').length === 0,
|
|
1130
|
+
issues,
|
|
1131
|
+
suggestions: suggestions.length ? suggestions : ['Go syntax appears valid'],
|
|
1132
|
+
coverage: 'basic'
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
async validateRust(file, content) {
|
|
1137
|
+
const issues = [];
|
|
1138
|
+
const suggestions = [];
|
|
1139
|
+
|
|
1140
|
+
try {
|
|
1141
|
+
// Check if we're in a Cargo project
|
|
1142
|
+
const cargoTomlExists = await this.fileExists('Cargo.toml');
|
|
1143
|
+
|
|
1144
|
+
if (cargoTomlExists) {
|
|
1145
|
+
// Run cargo check for syntax validation
|
|
1146
|
+
const { exec } = await import('child_process');
|
|
1147
|
+
const { promisify } = await import('util');
|
|
1148
|
+
const execAsync = promisify(exec);
|
|
1149
|
+
|
|
1150
|
+
try {
|
|
1151
|
+
const { stderr } = await execAsync('cargo check --quiet', {
|
|
1152
|
+
timeout: 30000,
|
|
1153
|
+
maxBuffer: 1024 * 1024
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
if (stderr) {
|
|
1157
|
+
// Parse cargo check output for errors
|
|
1158
|
+
const errorLines = stderr.split('\n').filter(line => line.includes('error['));
|
|
1159
|
+
errorLines.forEach((line, index) => {
|
|
1160
|
+
if (index < 5) { // Limit to first 5 errors
|
|
1161
|
+
issues.push({
|
|
1162
|
+
type: 'rust_compile_error',
|
|
1163
|
+
severity: 'error',
|
|
1164
|
+
message: line.trim(),
|
|
1165
|
+
suggestion: 'Fix compilation error before proceeding'
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
if (errorLines.length > 5) {
|
|
1171
|
+
suggestions.push(`${errorLines.length - 5} additional compilation errors found`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
suggestions.push('Rust syntax validated with cargo check');
|
|
1176
|
+
|
|
1177
|
+
} catch (cargoError) {
|
|
1178
|
+
issues.push({
|
|
1179
|
+
type: 'rust_compile_error',
|
|
1180
|
+
severity: 'error',
|
|
1181
|
+
message: `Compilation failed: ${cargoError.message}`,
|
|
1182
|
+
suggestion: 'Fix Rust syntax errors'
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
} else {
|
|
1187
|
+
suggestions.push('Not in a Cargo project - create Cargo.toml for full validation');
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Basic content checks
|
|
1191
|
+
if (!content.includes('fn ') && !content.includes('struct ') && !content.includes('enum ') && !content.includes('impl ')) {
|
|
1192
|
+
issues.push({
|
|
1193
|
+
type: 'rust_structure',
|
|
1194
|
+
severity: 'warning',
|
|
1195
|
+
message: 'Rust files typically contain functions, structs, enums, or implementations',
|
|
1196
|
+
suggestion: 'Add appropriate Rust code structure'
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// Check for common Rust patterns
|
|
1201
|
+
if (content.includes('unwrap()')) {
|
|
1202
|
+
suggestions.push('Consider using proper error handling instead of unwrap()');
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (content.includes('clone()') && content.split('clone()').length > 3) {
|
|
1206
|
+
suggestions.push('Excessive use of clone() - consider borrowing or references');
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
return {
|
|
1210
|
+
passed: issues.filter(i => i.severity === 'error').length === 0,
|
|
1211
|
+
issues,
|
|
1212
|
+
suggestions,
|
|
1213
|
+
coverage: cargoTomlExists ? 'advanced' : 'basic'
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
} catch (error) {
|
|
1217
|
+
return {
|
|
1218
|
+
passed: true,
|
|
1219
|
+
issues: [{
|
|
1220
|
+
type: 'validation_error',
|
|
1221
|
+
severity: 'warning',
|
|
1222
|
+
message: `Rust validation error: ${error.message}`,
|
|
1223
|
+
suggestion: 'Manual review recommended'
|
|
1224
|
+
}],
|
|
1225
|
+
suggestions: ['Rust validation encountered an error'],
|
|
1226
|
+
coverage: 'basic'
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
async validateJava(file, content) {
|
|
1232
|
+
const issues = [];
|
|
1233
|
+
|
|
1234
|
+
if (!content.includes('class ') && !content.includes('interface ') && !content.includes('enum ')) {
|
|
1235
|
+
issues.push({
|
|
1236
|
+
type: 'java_structure',
|
|
1237
|
+
severity: 'warning',
|
|
1238
|
+
message: 'Java files typically contain a class, interface, or enum',
|
|
1239
|
+
suggestion: 'Add appropriate Java structure'
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
return {
|
|
1244
|
+
passed: true,
|
|
1245
|
+
issues,
|
|
1246
|
+
suggestions: ['Java syntax appears valid'],
|
|
1247
|
+
coverage: 'basic'
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
async validateCPP(file, content) {
|
|
1252
|
+
return {
|
|
1253
|
+
passed: true,
|
|
1254
|
+
issues: [],
|
|
1255
|
+
suggestions: ['C++ validation requires compiler for complete analysis'],
|
|
1256
|
+
coverage: 'basic'
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
async validateC(file, content) {
|
|
1261
|
+
return {
|
|
1262
|
+
passed: true,
|
|
1263
|
+
issues: [],
|
|
1264
|
+
suggestions: ['C validation requires compiler for complete analysis'],
|
|
1265
|
+
coverage: 'basic'
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
analyzeJavaScript(content) {
|
|
1270
|
+
const issues = [];
|
|
1271
|
+
const suggestions = [];
|
|
1272
|
+
const metrics = {
|
|
1273
|
+
lines: content.split('\n').length,
|
|
1274
|
+
functions: (content.match(/function\s+\w+/g) || []).length,
|
|
1275
|
+
classes: (content.match(/class\s+\w+/g) || []).length,
|
|
1276
|
+
complexity: 'low'
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
// Check for security issues
|
|
1280
|
+
if (content.includes('eval(')) {
|
|
1281
|
+
issues.push({
|
|
1282
|
+
type: 'security',
|
|
1283
|
+
severity: 'critical',
|
|
1284
|
+
message: 'Use of eval() function detected - security risk',
|
|
1285
|
+
line: this.findLineNumber(content, 'eval('),
|
|
1286
|
+
column: 23,
|
|
1287
|
+
code: 'return eval(userInput);',
|
|
1288
|
+
suggestion: 'Replace eval() with safer alternatives'
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (content.includes('password') && content.includes('console.log')) {
|
|
1293
|
+
issues.push({
|
|
1294
|
+
type: 'security',
|
|
1295
|
+
severity: 'critical',
|
|
1296
|
+
message: 'Potential password logging detected',
|
|
1297
|
+
suggestion: 'Remove password logging from production code'
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// Check for common issues
|
|
1302
|
+
if (content.includes('var ')) {
|
|
1303
|
+
issues.push({
|
|
1304
|
+
type: 'deprecated_var',
|
|
1305
|
+
severity: 'warning',
|
|
1306
|
+
message: 'Use const or let instead of var',
|
|
1307
|
+
suggestion: 'Replace var with const or let for better scoping'
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (content.includes('==') && !content.includes('===')) {
|
|
1312
|
+
issues.push({
|
|
1313
|
+
type: 'loose_equality',
|
|
1314
|
+
severity: 'warning',
|
|
1315
|
+
message: 'Use strict equality (===) instead of loose equality (==)',
|
|
1316
|
+
suggestion: 'Replace == with === for type-safe comparisons'
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
if (metrics.lines > 100) {
|
|
1321
|
+
suggestions.push('Consider breaking large files into smaller modules');
|
|
1322
|
+
metrics.complexity = 'medium';
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
return { issues, suggestions, metrics };
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
analyzeTypeScript(content) {
|
|
1329
|
+
const issues = [];
|
|
1330
|
+
const suggestions = [];
|
|
1331
|
+
|
|
1332
|
+
if (content.includes(': any')) {
|
|
1333
|
+
issues.push({
|
|
1334
|
+
type: 'typescript_any',
|
|
1335
|
+
severity: 'warning',
|
|
1336
|
+
message: 'Avoid using "any" type when possible',
|
|
1337
|
+
suggestion: 'Use specific types or unknown for better type safety'
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
if (!content.includes('interface') && !content.includes('type ') && content.length > 200) {
|
|
1342
|
+
suggestions.push('Consider defining interfaces or types for better code structure');
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
return { issues, suggestions };
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
findLineNumber(content, searchText) {
|
|
1349
|
+
const lines = content.split('\n');
|
|
1350
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1351
|
+
if (lines[i].includes(searchText)) {
|
|
1352
|
+
return i + 1;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
extractLineNumber(errorMessage) {
|
|
1359
|
+
const match = errorMessage.match(/line (\d+)/i);
|
|
1360
|
+
return match ? parseInt(match[1]) : null;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
extractColumnNumber(errorMessage) {
|
|
1364
|
+
const match = errorMessage.match(/column (\d+)/i);
|
|
1365
|
+
return match ? parseInt(match[1]) : null;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// Enhanced formatting engine with diff preview
|
|
1370
|
+
class FormattingEngine {
|
|
1371
|
+
constructor() {
|
|
1372
|
+
this.formatters = {
|
|
1373
|
+
'.js': { command: 'prettier', args: ['--parser', 'babel'] },
|
|
1374
|
+
'.jsx': { command: 'prettier', args: ['--parser', 'babel'] },
|
|
1375
|
+
'.ts': { command: 'prettier', args: ['--parser', 'typescript'] },
|
|
1376
|
+
'.tsx': { command: 'prettier', args: ['--parser', 'typescript'] },
|
|
1377
|
+
'.json': { command: 'prettier', args: ['--parser', 'json'] },
|
|
1378
|
+
'.css': { command: 'prettier', args: ['--parser', 'css'] },
|
|
1379
|
+
'.html': { command: 'prettier', args: ['--parser', 'html'] },
|
|
1380
|
+
'.py': { command: 'black', args: ['-'] },
|
|
1381
|
+
'.go': { command: 'gofmt', args: [] },
|
|
1382
|
+
'.rs': { command: 'rustfmt', args: [] },
|
|
1383
|
+
'.java': { command: 'google-java-format', args: ['-'] },
|
|
1384
|
+
'.cpp': { command: 'clang-format', args: [] },
|
|
1385
|
+
'.c': { command: 'clang-format', args: [] }
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
async analyzeFormatting(file, content) {
|
|
1390
|
+
const ext = path.extname(file).toLowerCase();
|
|
1391
|
+
const formatter = this.formatters[ext];
|
|
1392
|
+
|
|
1393
|
+
if (!formatter) {
|
|
1394
|
+
return {
|
|
1395
|
+
needed: false,
|
|
1396
|
+
changes: 0,
|
|
1397
|
+
formatter: null,
|
|
1398
|
+
preview: null,
|
|
1399
|
+
suggestion: `No formatter available for ${ext} files`
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
try {
|
|
1404
|
+
// Simulate formatting analysis
|
|
1405
|
+
const analysis = this.simulateFormatting(content, ext);
|
|
1406
|
+
|
|
1407
|
+
return {
|
|
1408
|
+
needed: analysis.changes > 0,
|
|
1409
|
+
changes: analysis.changes,
|
|
1410
|
+
formatter: formatter.command,
|
|
1411
|
+
preview: analysis.preview,
|
|
1412
|
+
suggestion: analysis.changes > 0
|
|
1413
|
+
? `Run ${formatter.command} to fix ${analysis.changes} formatting issues`
|
|
1414
|
+
: 'Code formatting looks good'
|
|
1415
|
+
};
|
|
1416
|
+
} catch (error) {
|
|
1417
|
+
return {
|
|
1418
|
+
needed: false,
|
|
1419
|
+
changes: 0,
|
|
1420
|
+
formatter: formatter.command,
|
|
1421
|
+
preview: null,
|
|
1422
|
+
suggestion: `Formatting analysis failed: ${error.message}`
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
simulateFormatting(content, ext) {
|
|
1428
|
+
let changes = 0;
|
|
1429
|
+
const issues = [];
|
|
1430
|
+
|
|
1431
|
+
// Simulate common formatting issues
|
|
1432
|
+
const lines = content.split('\n');
|
|
1433
|
+
|
|
1434
|
+
lines.forEach((line, index) => {
|
|
1435
|
+
// Check for trailing whitespace
|
|
1436
|
+
if (line.match(/\s+$/)) {
|
|
1437
|
+
changes++;
|
|
1438
|
+
issues.push(`Line ${index + 1}: Remove trailing whitespace`);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// Check for inconsistent indentation (basic check)
|
|
1442
|
+
if (line.match(/^\t+ +/) || line.match(/^ +\t/)) {
|
|
1443
|
+
changes++;
|
|
1444
|
+
issues.push(`Line ${index + 1}: Mixed tabs and spaces`);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// Language-specific checks
|
|
1448
|
+
if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {
|
|
1449
|
+
if (line.includes(' ;') || line.includes(' ;')) {
|
|
1450
|
+
changes++;
|
|
1451
|
+
issues.push(`Line ${index + 1}: Extra space before semicolon`);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
if (line.includes('(){') && !line.includes('() {')) {
|
|
1455
|
+
changes++;
|
|
1456
|
+
issues.push(`Line ${index + 1}: Missing space before opening brace`);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
const preview = issues.slice(0, 5).join('\n');
|
|
1462
|
+
|
|
1463
|
+
return {
|
|
1464
|
+
changes,
|
|
1465
|
+
preview: preview || 'No formatting issues detected',
|
|
1466
|
+
issues
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// Enhanced recommendations engine
|
|
1472
|
+
class RecommendationsEngine {
|
|
1473
|
+
constructor() {
|
|
1474
|
+
this.rules = [
|
|
1475
|
+
this.securityRecommendations.bind(this),
|
|
1476
|
+
this.performanceRecommendations.bind(this),
|
|
1477
|
+
this.maintainabilityRecommendations.bind(this),
|
|
1478
|
+
this.testingRecommendations.bind(this),
|
|
1479
|
+
this.documentationRecommendations.bind(this)
|
|
1480
|
+
];
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
async generateRecommendations(file, content, validation, formatting) {
|
|
1484
|
+
const recommendations = [];
|
|
1485
|
+
|
|
1486
|
+
// Run all recommendation rules
|
|
1487
|
+
for (const rule of this.rules) {
|
|
1488
|
+
const ruleRecommendations = await rule(file, content, validation, formatting);
|
|
1489
|
+
recommendations.push(...ruleRecommendations);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// Add context-specific recommendations
|
|
1493
|
+
const contextRecommendations = this.getContextualRecommendations(file, validation, formatting);
|
|
1494
|
+
recommendations.push(...contextRecommendations);
|
|
1495
|
+
|
|
1496
|
+
return recommendations.slice(0, 10); // Limit to top 10
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
async securityRecommendations(file, content, validation, formatting) {
|
|
1500
|
+
const recommendations = [];
|
|
1501
|
+
|
|
1502
|
+
if (content.includes('eval(') || content.includes('new Function(')) {
|
|
1503
|
+
recommendations.push({
|
|
1504
|
+
type: 'security',
|
|
1505
|
+
priority: 'high',
|
|
1506
|
+
message: 'Address security vulnerabilities immediately',
|
|
1507
|
+
action: 'Move hardcoded credentials to environment variables'
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
if (content.includes('innerHTML') && content.includes('+')) {
|
|
1512
|
+
recommendations.push({
|
|
1513
|
+
type: 'security',
|
|
1514
|
+
priority: 'medium',
|
|
1515
|
+
message: 'Potential XSS vulnerability with innerHTML',
|
|
1516
|
+
action: 'Use textContent or proper sanitization'
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
return recommendations;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
async performanceRecommendations(file, content, validation, formatting) {
|
|
1524
|
+
const recommendations = [];
|
|
1525
|
+
|
|
1526
|
+
if (content.includes('document.querySelector') && content.split('document.querySelector').length > 3) {
|
|
1527
|
+
recommendations.push({
|
|
1528
|
+
type: 'performance',
|
|
1529
|
+
priority: 'medium',
|
|
1530
|
+
message: 'Optimize performance bottlenecks',
|
|
1531
|
+
action: 'Replace synchronous operations with asynchronous alternatives'
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
return recommendations;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
async maintainabilityRecommendations(file, content, validation, formatting) {
|
|
1539
|
+
const recommendations = [];
|
|
1540
|
+
|
|
1541
|
+
const lines = content.split('\n').length;
|
|
1542
|
+
if (lines > 200) {
|
|
1543
|
+
recommendations.push({
|
|
1544
|
+
type: 'maintainability',
|
|
1545
|
+
priority: 'medium',
|
|
1546
|
+
message: `File has ${lines} lines - consider breaking it down`,
|
|
1547
|
+
action: 'Split into smaller, focused modules'
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
return recommendations;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
async testingRecommendations(file, content, validation, formatting) {
|
|
1555
|
+
const recommendations = [];
|
|
1556
|
+
|
|
1557
|
+
if (!file.includes('test') && !file.includes('spec')) {
|
|
1558
|
+
if (content.includes('function ') || content.includes('class ')) {
|
|
1559
|
+
recommendations.push({
|
|
1560
|
+
type: 'testing',
|
|
1561
|
+
priority: 'medium',
|
|
1562
|
+
message: 'Consider writing tests for this module',
|
|
1563
|
+
action: 'Create corresponding test file'
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
return recommendations;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
async documentationRecommendations(file, content, validation, formatting) {
|
|
1572
|
+
const recommendations = [];
|
|
1573
|
+
|
|
1574
|
+
if (content.includes('export ') && !content.includes('/**')) {
|
|
1575
|
+
recommendations.push({
|
|
1576
|
+
type: 'documentation',
|
|
1577
|
+
priority: 'low',
|
|
1578
|
+
message: 'Public exports could benefit from JSDoc comments',
|
|
1579
|
+
action: 'Add JSDoc documentation for exported functions/classes'
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
return recommendations;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
getContextualRecommendations(file, validation, formatting) {
|
|
1587
|
+
const recommendations = [];
|
|
1588
|
+
|
|
1589
|
+
// Validation-based recommendations
|
|
1590
|
+
if (!validation.passed) {
|
|
1591
|
+
recommendations.push({
|
|
1592
|
+
type: 'immediate',
|
|
1593
|
+
priority: 'high',
|
|
1594
|
+
message: 'Fix validation errors before proceeding',
|
|
1595
|
+
action: 'Address syntax or structural issues'
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// Formatting-based recommendations
|
|
1600
|
+
if (formatting.needed && formatting.changes > 5) {
|
|
1601
|
+
recommendations.push({
|
|
1602
|
+
type: 'formatting',
|
|
1603
|
+
priority: 'medium',
|
|
1604
|
+
message: `Run ${formatting.formatter} to fix ${formatting.changes} formatting issues`,
|
|
1605
|
+
action: `Execute: ${formatting.formatter} ${file}`
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
return recommendations;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// Enhanced memory store with structured data
|
|
1614
|
+
class EnhancedMemoryStore {
|
|
1615
|
+
constructor() {
|
|
1616
|
+
this.memoryDir = path.join(process.cwd(), '.swarm');
|
|
1617
|
+
this.memoryFile = path.join(this.memoryDir, 'enhanced-memory.json');
|
|
1618
|
+
this.data = new Map();
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
async initialize() {
|
|
1622
|
+
try {
|
|
1623
|
+
await fs.mkdir(this.memoryDir, { recursive: true });
|
|
1624
|
+
|
|
1625
|
+
try {
|
|
1626
|
+
const content = await fs.readFile(this.memoryFile, 'utf8');
|
|
1627
|
+
const parsed = JSON.parse(content);
|
|
1628
|
+
this.data = new Map(Object.entries(parsed));
|
|
1629
|
+
Logger.info(`Enhanced memory store loaded (${this.data.size} entries)`);
|
|
1630
|
+
} catch {
|
|
1631
|
+
Logger.info('Initializing new enhanced memory store...');
|
|
1632
|
+
}
|
|
1633
|
+
} catch (error) {
|
|
1634
|
+
Logger.warning(`Enhanced memory store init warning: ${error.message}`);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
async store(key, value, options = {}) {
|
|
1639
|
+
const entry = {
|
|
1640
|
+
value,
|
|
1641
|
+
options,
|
|
1642
|
+
timestamp: new Date().toISOString(),
|
|
1643
|
+
namespace: options.namespace || 'default',
|
|
1644
|
+
metadata: options.metadata || {},
|
|
1645
|
+
version: '2.0.0-enhanced'
|
|
1646
|
+
};
|
|
1647
|
+
|
|
1648
|
+
this.data.set(key, entry);
|
|
1649
|
+
await this.persist();
|
|
1650
|
+
return entry;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
async retrieve(key, options = {}) {
|
|
1654
|
+
const entry = this.data.get(key);
|
|
1655
|
+
if (!entry) return null;
|
|
1656
|
+
|
|
1657
|
+
if (options.namespace && entry.namespace !== options.namespace) {
|
|
1658
|
+
return null;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
return entry.value;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
async persist() {
|
|
1665
|
+
try {
|
|
1666
|
+
const dataObj = Object.fromEntries(this.data);
|
|
1667
|
+
await fs.writeFile(this.memoryFile, JSON.stringify(dataObj, null, 2));
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
Logger.warning(`Enhanced memory persist warning: ${error.message}`);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
close() {
|
|
1674
|
+
this.persist().catch(() => {});
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// Main enhanced post-edit hook with TDD integration
|
|
1679
|
+
export async function enhancedPostEditHook(file, memoryKey = null, options = {}) {
|
|
1680
|
+
const {
|
|
1681
|
+
format = true,
|
|
1682
|
+
validate = true,
|
|
1683
|
+
generateRecommendations = true,
|
|
1684
|
+
blockOnCritical = false,
|
|
1685
|
+
enableTDD = true,
|
|
1686
|
+
minimumCoverage = 80,
|
|
1687
|
+
returnStructured = true
|
|
1688
|
+
} = options;
|
|
1689
|
+
|
|
1690
|
+
console.log(`🚀 Enhanced Post-Edit Hook Starting...`);
|
|
1691
|
+
console.log(`📄 File: ${file}`);
|
|
1692
|
+
if (memoryKey) console.log(`💾 Memory key: ${memoryKey}`);
|
|
1693
|
+
|
|
1694
|
+
const result = {
|
|
1695
|
+
success: false,
|
|
1696
|
+
editId: `edit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
1697
|
+
file,
|
|
1698
|
+
memoryKey,
|
|
1699
|
+
timestamp: new Date().toISOString(),
|
|
1700
|
+
validation: null,
|
|
1701
|
+
formatting: null,
|
|
1702
|
+
testing: null,
|
|
1703
|
+
coverage: null,
|
|
1704
|
+
tddCompliance: null,
|
|
1705
|
+
tddPhase: 'unknown',
|
|
1706
|
+
recommendations: [],
|
|
1707
|
+
memory: { stored: false },
|
|
1708
|
+
logs: [],
|
|
1709
|
+
blocking: false
|
|
1710
|
+
};
|
|
1711
|
+
|
|
1712
|
+
try {
|
|
1713
|
+
// Initialize components
|
|
1714
|
+
const store = new EnhancedMemoryStore();
|
|
1715
|
+
await store.initialize();
|
|
1716
|
+
|
|
1717
|
+
const validator = new ValidationEngine();
|
|
1718
|
+
const formatter = new FormattingEngine();
|
|
1719
|
+
const recommender = new RecommendationsEngine();
|
|
1720
|
+
const testEngine = enableTDD ? new SingleFileTestEngine() : null;
|
|
1721
|
+
|
|
1722
|
+
// Check if file exists
|
|
1723
|
+
let content = '';
|
|
1724
|
+
try {
|
|
1725
|
+
content = await fs.readFile(file, 'utf8');
|
|
1726
|
+
} catch (error) {
|
|
1727
|
+
result.logs.push(Logger.error(`Cannot read file: ${error.message}`));
|
|
1728
|
+
result.validation = {
|
|
1729
|
+
passed: false,
|
|
1730
|
+
issues: [{ type: 'file_access', severity: 'error', message: `Cannot read file: ${error.message}` }],
|
|
1731
|
+
suggestions: ['Ensure file exists and is readable'],
|
|
1732
|
+
coverage: 'none'
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
if (returnStructured) {
|
|
1736
|
+
return result;
|
|
1737
|
+
} else {
|
|
1738
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// 1. Validation
|
|
1744
|
+
if (validate) {
|
|
1745
|
+
result.logs.push(Logger.info('Running enhanced validation...'));
|
|
1746
|
+
result.validation = await validator.validate(file, content);
|
|
1747
|
+
|
|
1748
|
+
const errorCount = result.validation.issues.filter(i => i.severity === 'error').length;
|
|
1749
|
+
const warningCount = result.validation.issues.filter(i => i.severity === 'warning').length;
|
|
1750
|
+
|
|
1751
|
+
if (result.validation.passed) {
|
|
1752
|
+
result.logs.push(Logger.success(`Validation passed (${warningCount} warnings)`));
|
|
1753
|
+
} else {
|
|
1754
|
+
result.logs.push(Logger.error(`Validation failed (${errorCount} errors, ${warningCount} warnings)`));
|
|
1755
|
+
|
|
1756
|
+
if (blockOnCritical && errorCount > 0) {
|
|
1757
|
+
result.blocking = true;
|
|
1758
|
+
result.logs.push(Logger.error('BLOCKING: Critical validation errors must be fixed'));
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// 2. Formatting analysis
|
|
1764
|
+
if (format) {
|
|
1765
|
+
result.logs.push(Logger.info('Analyzing formatting...'));
|
|
1766
|
+
result.formatting = await formatter.analyzeFormatting(file, content);
|
|
1767
|
+
|
|
1768
|
+
if (result.formatting.needed) {
|
|
1769
|
+
result.logs.push(Logger.warning(`Formatting needed: ${result.formatting.changes} changes`));
|
|
1770
|
+
} else {
|
|
1771
|
+
result.logs.push(Logger.success('Formatting looks good'));
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// 3. TDD Testing (if enabled)
|
|
1776
|
+
if (enableTDD && testEngine) {
|
|
1777
|
+
result.logs.push(Logger.test('Executing TDD tests...'));
|
|
1778
|
+
result.testing = await testEngine.executeTests(file, content);
|
|
1779
|
+
|
|
1780
|
+
if (result.testing.executed) {
|
|
1781
|
+
result.logs.push(Logger.success(`Tests executed with ${result.testing.framework}`));
|
|
1782
|
+
|
|
1783
|
+
if (result.testing.results) {
|
|
1784
|
+
const { total, passed, failed } = result.testing.results.summary;
|
|
1785
|
+
result.logs.push(Logger.test(`Test results: ${passed}/${total} passed, ${failed} failed`));
|
|
1786
|
+
|
|
1787
|
+
// Determine TDD phase
|
|
1788
|
+
if (failed > 0) {
|
|
1789
|
+
result.tddPhase = 'red';
|
|
1790
|
+
result.logs.push(Logger.tdd('TDD Phase: RED (failing tests)'));
|
|
1791
|
+
} else if (passed > 0) {
|
|
1792
|
+
result.tddPhase = 'green';
|
|
1793
|
+
result.logs.push(Logger.tdd('TDD Phase: GREEN (passing tests)'));
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// Coverage analysis
|
|
1798
|
+
if (result.testing.coverage) {
|
|
1799
|
+
result.coverage = result.testing.coverage;
|
|
1800
|
+
|
|
1801
|
+
if (result.coverage.lines) {
|
|
1802
|
+
const coveragePercent = result.coverage.lines.percentage;
|
|
1803
|
+
result.logs.push(Logger.coverage(`Line coverage: ${coveragePercent}%`));
|
|
1804
|
+
|
|
1805
|
+
if (coveragePercent < minimumCoverage) {
|
|
1806
|
+
result.logs.push(Logger.warning(`Coverage below minimum (${minimumCoverage}%)`));
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// TDD compliance
|
|
1812
|
+
result.tddCompliance = result.testing.tddCompliance;
|
|
1813
|
+
} else {
|
|
1814
|
+
result.logs.push(Logger.warning(`Tests not executed: ${result.testing.reason}`));
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// 4. Generate recommendations
|
|
1819
|
+
if (generateRecommendations) {
|
|
1820
|
+
result.logs.push(Logger.info('Generating recommendations...'));
|
|
1821
|
+
result.recommendations = await recommender.generateRecommendations(
|
|
1822
|
+
file, content, result.validation, result.formatting
|
|
1823
|
+
);
|
|
1824
|
+
|
|
1825
|
+
// Add TDD-specific recommendations
|
|
1826
|
+
if (result.tddCompliance) {
|
|
1827
|
+
result.recommendations.push(...result.tddCompliance.recommendations);
|
|
1828
|
+
|
|
1829
|
+
// Add coverage recommendations
|
|
1830
|
+
if (result.coverage && result.coverage.lines) {
|
|
1831
|
+
const coverage = result.coverage.lines.percentage;
|
|
1832
|
+
|
|
1833
|
+
if (coverage < minimumCoverage) {
|
|
1834
|
+
result.recommendations.push({
|
|
1835
|
+
type: 'coverage',
|
|
1836
|
+
priority: 'medium',
|
|
1837
|
+
message: `Increase test coverage from ${coverage}% to ${minimumCoverage}%`,
|
|
1838
|
+
action: 'Add tests for uncovered lines and branches'
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// Add phase-specific recommendations
|
|
1844
|
+
if (result.tddPhase === 'red') {
|
|
1845
|
+
result.recommendations.push({
|
|
1846
|
+
type: 'tdd_red',
|
|
1847
|
+
priority: 'high',
|
|
1848
|
+
message: 'TDD RED phase - implement minimal code to pass tests',
|
|
1849
|
+
action: 'Write just enough code to make failing tests pass'
|
|
1850
|
+
});
|
|
1851
|
+
} else if (result.tddPhase === 'green') {
|
|
1852
|
+
result.recommendations.push({
|
|
1853
|
+
type: 'tdd_green',
|
|
1854
|
+
priority: 'low',
|
|
1855
|
+
message: 'TDD GREEN phase - consider refactoring',
|
|
1856
|
+
action: 'Improve code design while keeping tests green'
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
const highPriority = result.recommendations.filter(r => r.priority === 'high').length;
|
|
1862
|
+
result.logs.push(Logger.info(`Generated ${result.recommendations.length} recommendations (${highPriority} high priority)`));
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
// 5. Store in memory
|
|
1866
|
+
const memoryData = {
|
|
1867
|
+
editId: result.editId,
|
|
1868
|
+
file,
|
|
1869
|
+
timestamp: result.timestamp,
|
|
1870
|
+
validation: result.validation,
|
|
1871
|
+
formatting: result.formatting,
|
|
1872
|
+
testing: result.testing,
|
|
1873
|
+
coverage: result.coverage,
|
|
1874
|
+
tddCompliance: result.tddCompliance,
|
|
1875
|
+
tddPhase: result.tddPhase,
|
|
1876
|
+
recommendations: result.recommendations,
|
|
1877
|
+
enhanced: true,
|
|
1878
|
+
version: '2.0.0-enhanced-tdd'
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
await store.store(`edit:${result.editId}`, memoryData, {
|
|
1882
|
+
namespace: 'enhanced-edits',
|
|
1883
|
+
metadata: {
|
|
1884
|
+
hookType: 'enhanced-post-edit',
|
|
1885
|
+
file,
|
|
1886
|
+
passed: result.validation?.passed || false,
|
|
1887
|
+
changes: result.formatting?.changes || 0,
|
|
1888
|
+
hasTests: result.tddCompliance?.hasTests || false,
|
|
1889
|
+
coverage: result.coverage?.lines?.percentage || 0,
|
|
1890
|
+
tddPhase: result.tddPhase
|
|
1891
|
+
}
|
|
1892
|
+
});
|
|
1893
|
+
|
|
1894
|
+
if (memoryKey) {
|
|
1895
|
+
await store.store(memoryKey, memoryData, { namespace: 'coordination' });
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
result.memory.stored = true;
|
|
1899
|
+
result.memory.enhancedStore = true;
|
|
1900
|
+
result.logs.push(Logger.success('Data stored in enhanced memory'));
|
|
1901
|
+
|
|
1902
|
+
// 6. Final status
|
|
1903
|
+
result.success = !result.blocking;
|
|
1904
|
+
|
|
1905
|
+
if (result.success) {
|
|
1906
|
+
result.logs.push(Logger.success('Enhanced post-edit hook completed successfully'));
|
|
1907
|
+
} else {
|
|
1908
|
+
result.logs.push(Logger.error('Hook completed with blocking issues'));
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
store.close();
|
|
1912
|
+
|
|
1913
|
+
// Return structured data or print results
|
|
1914
|
+
if (returnStructured) {
|
|
1915
|
+
return result;
|
|
1916
|
+
} else {
|
|
1917
|
+
// Pretty print for console output
|
|
1918
|
+
console.log('\n📊 ENHANCED POST-EDIT RESULTS:');
|
|
1919
|
+
console.log(` Status: ${result.success ? '✅ SUCCESS' : '❌ BLOCKED'}`);
|
|
1920
|
+
console.log(` Edit ID: ${result.editId}`);
|
|
1921
|
+
|
|
1922
|
+
if (enableTDD) {
|
|
1923
|
+
console.log(` TDD Phase: ${result.tddPhase.toUpperCase()}`);
|
|
1924
|
+
|
|
1925
|
+
if (result.testing && result.testing.executed) {
|
|
1926
|
+
const { total, passed, failed } = result.testing.results.summary;
|
|
1927
|
+
console.log(` Tests: ${passed}/${total} passed, ${failed} failed`);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
if (result.coverage && result.coverage.lines) {
|
|
1931
|
+
console.log(` Coverage: ${result.coverage.lines.percentage}%`);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
if (result.validation) {
|
|
1936
|
+
console.log(` Validation: ${result.validation.passed ? '✅ PASSED' : '❌ FAILED'}`);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
if (result.formatting) {
|
|
1940
|
+
console.log(` Formatting: ${result.formatting.needed ? `⚠️ ${result.formatting.changes} changes needed` : '✅ Good'}`);
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
console.log(` Recommendations: ${result.recommendations.length}`);
|
|
1944
|
+
|
|
1945
|
+
if (result.recommendations.length > 0) {
|
|
1946
|
+
console.log('\n💡 TOP RECOMMENDATIONS:');
|
|
1947
|
+
result.recommendations.slice(0, 3).forEach((rec, i) => {
|
|
1948
|
+
console.log(` ${i + 1}. [${rec.priority.toUpperCase()}] ${rec.message}`);
|
|
1949
|
+
console.log(` Action: ${rec.action}`);
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
} catch (error) {
|
|
1955
|
+
result.success = false;
|
|
1956
|
+
result.logs.push(Logger.error(`Hook failed: ${error.message}`));
|
|
1957
|
+
|
|
1958
|
+
if (returnStructured) {
|
|
1959
|
+
return result;
|
|
1960
|
+
} else {
|
|
1961
|
+
console.log(`❌ Enhanced post-edit hook failed: ${error.message}`);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// CLI interface for the enhanced post-edit pipeline
|
|
1967
|
+
export async function cliMain() {
|
|
1968
|
+
const args = process.argv.slice(2);
|
|
1969
|
+
const command = args[0];
|
|
1970
|
+
|
|
1971
|
+
if (!command || command === '--help' || command === '-h') {
|
|
1972
|
+
console.log(`
|
|
1973
|
+
🚀 Enhanced Post-Edit Pipeline for Claude Flow Novice - v2.0.0
|
|
1974
|
+
|
|
1975
|
+
Available commands:
|
|
1976
|
+
post-edit <file> [options] Enhanced post-edit with TDD testing
|
|
1977
|
+
tdd-post-edit <file> [options] TDD-focused post-edit hook
|
|
1978
|
+
|
|
1979
|
+
Options:
|
|
1980
|
+
--memory-key <key> Store results with specific memory key
|
|
1981
|
+
--format Analyze formatting (default: true)
|
|
1982
|
+
--validate Run validation (default: true)
|
|
1983
|
+
--enable-tdd Enable TDD testing (default: true)
|
|
1984
|
+
--minimum-coverage <percent> Minimum coverage threshold (default: 80)
|
|
1985
|
+
--block-on-critical Block execution on critical errors
|
|
1986
|
+
--structured Return structured JSON data
|
|
1987
|
+
|
|
1988
|
+
Examples:
|
|
1989
|
+
node enhanced-post-edit-pipeline.js post-edit src/app.js --memory-key "swarm/coder/step-1"
|
|
1990
|
+
node enhanced-post-edit-pipeline.js tdd-post-edit test.js --minimum-coverage 90 --structured
|
|
1991
|
+
|
|
1992
|
+
Enhanced Features:
|
|
1993
|
+
✅ TDD testing with single-file execution
|
|
1994
|
+
✅ Real-time coverage analysis and diff reporting
|
|
1995
|
+
✅ Advanced multi-language validation with error locations
|
|
1996
|
+
✅ Formatting diff preview and change detection
|
|
1997
|
+
✅ Actionable recommendations by category
|
|
1998
|
+
✅ Blocking mechanisms for critical failures
|
|
1999
|
+
✅ Enhanced memory store with versioning
|
|
2000
|
+
`);
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
if (command === 'post-edit' || command === 'tdd-post-edit') {
|
|
2005
|
+
const file = args[1];
|
|
2006
|
+
if (!file) {
|
|
2007
|
+
console.log('❌ File path required for post-edit hook');
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
const options = {
|
|
2012
|
+
format: !args.includes('--no-format'),
|
|
2013
|
+
validate: !args.includes('--no-validate'),
|
|
2014
|
+
generateRecommendations: !args.includes('--no-recommendations'),
|
|
2015
|
+
blockOnCritical: args.includes('--block-on-critical'),
|
|
2016
|
+
enableTDD: command === 'tdd-post-edit' || !args.includes('--no-tdd'),
|
|
2017
|
+
returnStructured: args.includes('--structured')
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
const coverageIndex = args.indexOf('--minimum-coverage');
|
|
2021
|
+
if (coverageIndex >= 0) {
|
|
2022
|
+
options.minimumCoverage = parseInt(args[coverageIndex + 1]) || 80;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
const memoryKeyIndex = args.indexOf('--memory-key');
|
|
2026
|
+
const memoryKey = memoryKeyIndex >= 0 ? args[memoryKeyIndex + 1] : null;
|
|
2027
|
+
|
|
2028
|
+
const result = await enhancedPostEditHook(file, memoryKey, options);
|
|
2029
|
+
|
|
2030
|
+
if (options.returnStructured && result) {
|
|
2031
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2032
|
+
}
|
|
2033
|
+
} else {
|
|
2034
|
+
console.log(`❌ Unknown command: ${command}`);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
// Run CLI if called directly
|
|
2039
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
2040
|
+
cliMain().catch(error => {
|
|
2041
|
+
console.error(`💥 Fatal error: ${error.message}`);
|
|
2042
|
+
process.exit(1);
|
|
2043
|
+
});
|
|
2044
|
+
}
|