claude-flow-novice 1.5.14 → 1.5.16
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/commands/hooks.js +19 -16
- package/.claude-flow-novice/.claude/agents/analysis/code-review/analyze-code-quality.md +160 -177
- package/.claude-flow-novice/.claude/agents/architecture/system-design/arch-system-design.md +118 -153
- package/.claude-flow-novice/dist/src/agents/agent-loader.js +27 -7
- package/.claude-flow-novice/dist/src/agents/agent-loader.js.map +1 -1
- package/.claude-flow-novice/dist/src/cli/simple-commands/init/templates/CLAUDE.md +25 -83
- package/.claude-flow-novice/dist/src/hooks/enhanced-hooks-cli.js +168 -0
- package/.claude-flow-novice/dist/src/preferences/example.js +203 -0
- package/.claude-flow-novice/dist/src/preferences/example.js.map +1 -0
- package/.claude-flow-novice/dist/src/preferences/user-preference-manager.js +2 -2
- package/.claude-flow-novice/dist/src/preferences/user-preference-manager.js.map +1 -1
- package/CLAUDE.md +10 -8
- package/README-NPM.md +0 -0
- package/config/hooks/documentation-auto-update.js +6 -1
- package/config/hooks/post-edit-pipeline.js +1475 -428
- package/package.json +1 -1
- package/src/cli/simple-commands/init/templates/CLAUDE.md +25 -85
- package/src/cli/simple-commands/sparc/refinement.js +3 -2
- package/.claude/agents/analysis/code-analyzer.md +0 -192
|
@@ -1,20 +1,1164 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Post-Edit Validation Pipeline
|
|
5
|
-
*
|
|
4
|
+
* Unified Post-Edit Validation Pipeline
|
|
5
|
+
* Combines comprehensive validation with TDD enforcement
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Progressive validation (syntax → interface → integration → full)
|
|
9
|
+
* - TDD enforcement with test-first compliance
|
|
10
|
+
* - Single-file testing without full system compilation
|
|
11
|
+
* - Real-time coverage analysis
|
|
12
|
+
* - Rust-specific quality enforcements
|
|
13
|
+
* - Multi-language support
|
|
14
|
+
* - Security scanning
|
|
15
|
+
* - Agent coordination
|
|
6
16
|
*/
|
|
7
17
|
|
|
8
18
|
import path from 'path';
|
|
9
19
|
import fs from 'fs';
|
|
20
|
+
import fsSync from 'fs';
|
|
10
21
|
import { exec, spawn } from 'child_process';
|
|
11
22
|
import { promisify } from 'util';
|
|
12
23
|
|
|
13
24
|
const execAsync = promisify(exec);
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
// Enhanced logging utilities
|
|
27
|
+
class Logger {
|
|
28
|
+
static success(msg, data = {}) {
|
|
29
|
+
console.log(`✅ ${msg}`);
|
|
30
|
+
return { level: 'success', message: msg, data };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static error(msg, data = {}) {
|
|
34
|
+
console.log(`❌ ${msg}`);
|
|
35
|
+
return { level: 'error', message: msg, data };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static warning(msg, data = {}) {
|
|
39
|
+
console.log(`⚠️ ${msg}`);
|
|
40
|
+
return { level: 'warning', message: msg, data };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static info(msg, data = {}) {
|
|
44
|
+
console.log(`ℹ️ ${msg}`);
|
|
45
|
+
return { level: 'info', message: msg, data };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static test(msg, data = {}) {
|
|
49
|
+
console.log(`🧪 ${msg}`);
|
|
50
|
+
return { level: 'test', message: msg, data };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static coverage(msg, data = {}) {
|
|
54
|
+
console.log(`📊 ${msg}`);
|
|
55
|
+
return { level: 'coverage', message: msg, data };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static tdd(msg, data = {}) {
|
|
59
|
+
console.log(`🔴🟢♻️ ${msg}`);
|
|
60
|
+
return { level: 'tdd', message: msg, data };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Single-file test execution engine
|
|
65
|
+
class SingleFileTestEngine {
|
|
16
66
|
constructor() {
|
|
67
|
+
this.testRunners = {
|
|
68
|
+
'.js': this.runJavaScriptTests.bind(this),
|
|
69
|
+
'.jsx': this.runJavaScriptTests.bind(this),
|
|
70
|
+
'.ts': this.runTypeScriptTests.bind(this),
|
|
71
|
+
'.tsx': this.runTypeScriptTests.bind(this),
|
|
72
|
+
'.py': this.runPythonTests.bind(this),
|
|
73
|
+
'.go': this.runGoTests.bind(this),
|
|
74
|
+
'.rs': this.runRustTests.bind(this),
|
|
75
|
+
'.java': this.runJavaTests.bind(this),
|
|
76
|
+
'.cpp': this.runCPPTests.bind(this),
|
|
77
|
+
'.c': this.runCTests.bind(this)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async executeTests(file, content) {
|
|
82
|
+
const ext = path.extname(file).toLowerCase();
|
|
83
|
+
const runner = this.testRunners[ext];
|
|
84
|
+
|
|
85
|
+
if (!runner) {
|
|
86
|
+
return {
|
|
87
|
+
executed: false,
|
|
88
|
+
reason: `No test runner available for ${ext} files`,
|
|
89
|
+
framework: null,
|
|
90
|
+
results: null,
|
|
91
|
+
coverage: null,
|
|
92
|
+
tddCompliance: null
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const isTestFile = this.isTestFile(file);
|
|
97
|
+
const relatedFile = isTestFile ? this.findSourceFile(file) : this.findTestFile(file);
|
|
98
|
+
|
|
99
|
+
return await runner(file, content, { isTestFile, relatedFile });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async runJavaScriptTests(file, content, options = {}) {
|
|
103
|
+
const { isTestFile, relatedFile } = options;
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const framework = await this.detectJSTestFramework();
|
|
107
|
+
|
|
108
|
+
if (!framework) {
|
|
109
|
+
return {
|
|
110
|
+
executed: false,
|
|
111
|
+
reason: 'No JavaScript test framework detected (jest, mocha, etc.)',
|
|
112
|
+
framework: null,
|
|
113
|
+
results: null,
|
|
114
|
+
coverage: null,
|
|
115
|
+
tddCompliance: this.checkTDDCompliance(file, relatedFile, null)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let testResults = null;
|
|
120
|
+
let coverage = null;
|
|
121
|
+
|
|
122
|
+
if (framework === 'jest') {
|
|
123
|
+
testResults = await this.runJestSingleFile(file, isTestFile);
|
|
124
|
+
coverage = await this.getJestCoverage(file);
|
|
125
|
+
} else if (framework === 'mocha') {
|
|
126
|
+
testResults = await this.runMochaSingleFile(file, isTestFile);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
executed: true,
|
|
133
|
+
framework,
|
|
134
|
+
results: testResults,
|
|
135
|
+
coverage,
|
|
136
|
+
tddCompliance,
|
|
137
|
+
singleFileMode: true
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
executed: false,
|
|
143
|
+
reason: `Test execution failed: ${error.message}`,
|
|
144
|
+
framework: null,
|
|
145
|
+
results: null,
|
|
146
|
+
coverage: null,
|
|
147
|
+
tddCompliance: null,
|
|
148
|
+
error: error.message
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async runTypeScriptTests(file, content, options = {}) {
|
|
154
|
+
const jsResult = await this.runJavaScriptTests(file, content, options);
|
|
155
|
+
if (jsResult.executed) {
|
|
156
|
+
jsResult.language = 'typescript';
|
|
157
|
+
jsResult.compiled = true;
|
|
158
|
+
}
|
|
159
|
+
return jsResult;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async runPythonTests(file, content, options = {}) {
|
|
163
|
+
const { isTestFile, relatedFile } = options;
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const framework = await this.detectPythonTestFramework();
|
|
167
|
+
|
|
168
|
+
if (!framework) {
|
|
169
|
+
return {
|
|
170
|
+
executed: false,
|
|
171
|
+
reason: 'No Python test framework detected (pytest, unittest)',
|
|
172
|
+
framework: null,
|
|
173
|
+
results: null,
|
|
174
|
+
coverage: null,
|
|
175
|
+
tddCompliance: this.checkTDDCompliance(file, relatedFile, null)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let testResults = null;
|
|
180
|
+
let coverage = null;
|
|
181
|
+
|
|
182
|
+
if (framework === 'pytest') {
|
|
183
|
+
testResults = await this.runPytestSingleFile(file, isTestFile);
|
|
184
|
+
coverage = await this.getPytestCoverage(file);
|
|
185
|
+
} else if (framework === 'unittest') {
|
|
186
|
+
testResults = await this.runUnittestSingleFile(file, isTestFile);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
executed: true,
|
|
193
|
+
framework,
|
|
194
|
+
results: testResults,
|
|
195
|
+
coverage,
|
|
196
|
+
tddCompliance,
|
|
197
|
+
singleFileMode: true
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
} catch (error) {
|
|
201
|
+
return {
|
|
202
|
+
executed: false,
|
|
203
|
+
reason: `Python test execution failed: ${error.message}`,
|
|
204
|
+
framework: null,
|
|
205
|
+
results: null,
|
|
206
|
+
coverage: null,
|
|
207
|
+
tddCompliance: null,
|
|
208
|
+
error: error.message
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async runGoTests(file, content, options = {}) {
|
|
214
|
+
const { isTestFile, relatedFile } = options;
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
if (!isTestFile) {
|
|
218
|
+
const testFile = this.findTestFile(file);
|
|
219
|
+
if (!testFile || !await this.fileExists(testFile)) {
|
|
220
|
+
return {
|
|
221
|
+
executed: false,
|
|
222
|
+
reason: 'No corresponding test file found for Go source',
|
|
223
|
+
framework: 'go test',
|
|
224
|
+
results: null,
|
|
225
|
+
coverage: null,
|
|
226
|
+
tddCompliance: this.checkTDDCompliance(file, null, null)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
file = testFile;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const testResults = await this.runGoTestSingleFile(file);
|
|
233
|
+
const coverage = await this.getGoCoverage(file);
|
|
234
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
executed: true,
|
|
238
|
+
framework: 'go test',
|
|
239
|
+
results: testResults,
|
|
240
|
+
coverage,
|
|
241
|
+
tddCompliance,
|
|
242
|
+
singleFileMode: true
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return {
|
|
247
|
+
executed: false,
|
|
248
|
+
reason: `Go test execution failed: ${error.message}`,
|
|
249
|
+
framework: 'go test',
|
|
250
|
+
results: null,
|
|
251
|
+
coverage: null,
|
|
252
|
+
tddCompliance: null,
|
|
253
|
+
error: error.message
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async runRustTests(file, content, options = {}) {
|
|
259
|
+
const { isTestFile, relatedFile } = options;
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const testResults = await this.runCargoTestSingleFile(file);
|
|
263
|
+
const coverage = await this.getRustCoverage(file);
|
|
264
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
executed: true,
|
|
268
|
+
framework: 'cargo test',
|
|
269
|
+
results: testResults,
|
|
270
|
+
coverage,
|
|
271
|
+
tddCompliance,
|
|
272
|
+
singleFileMode: true
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
} catch (error) {
|
|
276
|
+
return {
|
|
277
|
+
executed: false,
|
|
278
|
+
reason: `Rust test execution failed: ${error.message}`,
|
|
279
|
+
framework: 'cargo test',
|
|
280
|
+
results: null,
|
|
281
|
+
coverage: null,
|
|
282
|
+
tddCompliance: null,
|
|
283
|
+
error: error.message
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async runJavaTests(file, content, options = {}) {
|
|
289
|
+
const { isTestFile, relatedFile } = options;
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const framework = await this.detectJavaTestFramework();
|
|
293
|
+
|
|
294
|
+
if (!framework) {
|
|
295
|
+
return {
|
|
296
|
+
executed: false,
|
|
297
|
+
reason: 'No Java test framework detected (JUnit, TestNG)',
|
|
298
|
+
framework: null,
|
|
299
|
+
results: null,
|
|
300
|
+
coverage: null,
|
|
301
|
+
tddCompliance: this.checkTDDCompliance(file, relatedFile, null)
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const testResults = await this.runJavaTestSingleFile(file, framework);
|
|
306
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
executed: true,
|
|
310
|
+
framework,
|
|
311
|
+
results: testResults,
|
|
312
|
+
coverage: null,
|
|
313
|
+
tddCompliance,
|
|
314
|
+
singleFileMode: true
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
} catch (error) {
|
|
318
|
+
return {
|
|
319
|
+
executed: false,
|
|
320
|
+
reason: `Java test execution failed: ${error.message}`,
|
|
321
|
+
framework: null,
|
|
322
|
+
results: null,
|
|
323
|
+
coverage: null,
|
|
324
|
+
tddCompliance: null,
|
|
325
|
+
error: error.message
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async runCPPTests(file, content, options = {}) {
|
|
331
|
+
return this.runCTests(file, content, options);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async runCTests(file, content, options = {}) {
|
|
335
|
+
const { isTestFile, relatedFile } = options;
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const framework = await this.detectCTestFramework();
|
|
339
|
+
|
|
340
|
+
if (!framework) {
|
|
341
|
+
return {
|
|
342
|
+
executed: false,
|
|
343
|
+
reason: 'No C/C++ test framework detected (gtest, catch2)',
|
|
344
|
+
framework: null,
|
|
345
|
+
results: null,
|
|
346
|
+
coverage: null,
|
|
347
|
+
tddCompliance: this.checkTDDCompliance(file, relatedFile, null)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const testResults = await this.runCTestSingleFile(file, framework);
|
|
352
|
+
const tddCompliance = this.checkTDDCompliance(file, relatedFile, testResults);
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
executed: true,
|
|
356
|
+
framework,
|
|
357
|
+
results: testResults,
|
|
358
|
+
coverage: null,
|
|
359
|
+
tddCompliance,
|
|
360
|
+
singleFileMode: true
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
} catch (error) {
|
|
364
|
+
return {
|
|
365
|
+
executed: false,
|
|
366
|
+
reason: `C/C++ test execution failed: ${error.message}`,
|
|
367
|
+
framework: null,
|
|
368
|
+
results: null,
|
|
369
|
+
coverage: null,
|
|
370
|
+
tddCompliance: null,
|
|
371
|
+
error: error.message
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Framework detection methods
|
|
377
|
+
async detectJSTestFramework() {
|
|
378
|
+
try {
|
|
379
|
+
const possiblePaths = [
|
|
380
|
+
path.join(process.cwd(), 'package.json'),
|
|
381
|
+
path.join(path.dirname(process.cwd()), 'package.json'),
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
for (const packagePath of possiblePaths) {
|
|
385
|
+
try {
|
|
386
|
+
const packageContent = await fs.promises.readFile(packagePath, 'utf8');
|
|
387
|
+
const packageJson = JSON.parse(packageContent);
|
|
388
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
389
|
+
|
|
390
|
+
if (deps.jest) return 'jest';
|
|
391
|
+
if (deps.mocha) return 'mocha';
|
|
392
|
+
if (deps.ava) return 'ava';
|
|
393
|
+
if (deps.tap) return 'tap';
|
|
394
|
+
} catch {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return null;
|
|
400
|
+
} catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async detectPythonTestFramework() {
|
|
406
|
+
try {
|
|
407
|
+
await execAsync('pytest --version');
|
|
408
|
+
return 'pytest';
|
|
409
|
+
} catch {
|
|
410
|
+
try {
|
|
411
|
+
await execAsync('python -m unittest --help');
|
|
412
|
+
return 'unittest';
|
|
413
|
+
} catch {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async detectJavaTestFramework() {
|
|
420
|
+
try {
|
|
421
|
+
const buildFiles = ['pom.xml', 'build.gradle', 'build.gradle.kts'];
|
|
422
|
+
|
|
423
|
+
for (const buildFile of buildFiles) {
|
|
424
|
+
if (await this.fileExists(buildFile)) {
|
|
425
|
+
const content = await fs.promises.readFile(buildFile, 'utf8');
|
|
426
|
+
if (content.includes('junit')) return 'junit';
|
|
427
|
+
if (content.includes('testng')) return 'testng';
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return null;
|
|
432
|
+
} catch {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async detectCTestFramework() {
|
|
438
|
+
try {
|
|
439
|
+
await execAsync('pkg-config --exists gtest');
|
|
440
|
+
return 'gtest';
|
|
441
|
+
} catch {
|
|
442
|
+
try {
|
|
443
|
+
await execAsync('pkg-config --exists catch2');
|
|
444
|
+
return 'catch2';
|
|
445
|
+
} catch {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Test execution implementations
|
|
452
|
+
async runJestSingleFile(file, isTestFile) {
|
|
453
|
+
try {
|
|
454
|
+
const testPattern = isTestFile ? file : this.findTestFile(file);
|
|
455
|
+
|
|
456
|
+
if (!testPattern || !await this.fileExists(testPattern)) {
|
|
457
|
+
return {
|
|
458
|
+
passed: false,
|
|
459
|
+
reason: 'No test file found',
|
|
460
|
+
tests: [],
|
|
461
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const result = await execAsync(`npx jest "${path.basename(testPattern)}" --json --coverage=false`, {
|
|
466
|
+
cwd: path.dirname(testPattern)
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const jestOutput = JSON.parse(result.stdout);
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
passed: jestOutput.success,
|
|
473
|
+
tests: jestOutput.testResults[0]?.assertionResults || [],
|
|
474
|
+
summary: {
|
|
475
|
+
total: jestOutput.numTotalTests,
|
|
476
|
+
passed: jestOutput.numPassedTests,
|
|
477
|
+
failed: jestOutput.numFailedTests,
|
|
478
|
+
skipped: jestOutput.numPendingTests
|
|
479
|
+
},
|
|
480
|
+
duration: jestOutput.testResults[0]?.endTime - jestOutput.testResults[0]?.startTime
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
} catch (error) {
|
|
484
|
+
return {
|
|
485
|
+
passed: false,
|
|
486
|
+
error: error.message,
|
|
487
|
+
tests: [],
|
|
488
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async runMochaSingleFile(file, isTestFile) {
|
|
494
|
+
try {
|
|
495
|
+
const testFile = isTestFile ? file : this.findTestFile(file);
|
|
496
|
+
|
|
497
|
+
if (!testFile || !await this.fileExists(testFile)) {
|
|
498
|
+
return {
|
|
499
|
+
passed: false,
|
|
500
|
+
reason: 'No test file found',
|
|
501
|
+
tests: [],
|
|
502
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const result = await execAsync(`npx mocha "${testFile}" --reporter json`);
|
|
507
|
+
const mochaOutput = JSON.parse(result.stdout);
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
passed: mochaOutput.failures === 0,
|
|
511
|
+
tests: mochaOutput.tests || [],
|
|
512
|
+
summary: {
|
|
513
|
+
total: mochaOutput.tests.length,
|
|
514
|
+
passed: mochaOutput.passes,
|
|
515
|
+
failed: mochaOutput.failures,
|
|
516
|
+
skipped: mochaOutput.pending
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
} catch (error) {
|
|
521
|
+
return {
|
|
522
|
+
passed: false,
|
|
523
|
+
error: error.message,
|
|
524
|
+
tests: [],
|
|
525
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async runPytestSingleFile(file, isTestFile) {
|
|
531
|
+
try {
|
|
532
|
+
const testFile = isTestFile ? file : this.findTestFile(file);
|
|
533
|
+
|
|
534
|
+
if (!testFile || !await this.fileExists(testFile)) {
|
|
535
|
+
return {
|
|
536
|
+
passed: false,
|
|
537
|
+
reason: 'No test file found',
|
|
538
|
+
tests: [],
|
|
539
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const result = await execAsync(`pytest "${testFile}" -v`);
|
|
544
|
+
|
|
545
|
+
// Parse pytest output (simplified)
|
|
546
|
+
return {
|
|
547
|
+
passed: result.code === 0,
|
|
548
|
+
tests: [],
|
|
549
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
} catch (error) {
|
|
553
|
+
return {
|
|
554
|
+
passed: false,
|
|
555
|
+
error: error.message,
|
|
556
|
+
tests: [],
|
|
557
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async runUnittestSingleFile(file, isTestFile) {
|
|
563
|
+
try {
|
|
564
|
+
const testFile = isTestFile ? file : this.findTestFile(file);
|
|
565
|
+
|
|
566
|
+
if (!testFile || !await this.fileExists(testFile)) {
|
|
567
|
+
return {
|
|
568
|
+
passed: false,
|
|
569
|
+
reason: 'No test file found',
|
|
570
|
+
tests: [],
|
|
571
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const result = await execAsync(`python -m unittest "${testFile}" -v`);
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
passed: result.code === 0,
|
|
579
|
+
tests: [],
|
|
580
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
} catch (error) {
|
|
584
|
+
return {
|
|
585
|
+
passed: false,
|
|
586
|
+
error: error.message,
|
|
587
|
+
tests: [],
|
|
588
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async runGoTestSingleFile(file) {
|
|
594
|
+
try {
|
|
595
|
+
const result = await execAsync(`go test "${file}" -json`);
|
|
596
|
+
|
|
597
|
+
const lines = result.stdout.trim().split('\n');
|
|
598
|
+
const tests = [];
|
|
599
|
+
let passed = 0, failed = 0, skipped = 0;
|
|
600
|
+
|
|
601
|
+
for (const line of lines) {
|
|
602
|
+
try {
|
|
603
|
+
const testResult = JSON.parse(line);
|
|
604
|
+
if (testResult.Action === 'pass' || testResult.Action === 'fail' || testResult.Action === 'skip') {
|
|
605
|
+
tests.push(testResult);
|
|
606
|
+
if (testResult.Action === 'pass') passed++;
|
|
607
|
+
else if (testResult.Action === 'fail') failed++;
|
|
608
|
+
else if (testResult.Action === 'skip') skipped++;
|
|
609
|
+
}
|
|
610
|
+
} catch {
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return {
|
|
616
|
+
passed: failed === 0,
|
|
617
|
+
tests,
|
|
618
|
+
summary: {
|
|
619
|
+
total: passed + failed + skipped,
|
|
620
|
+
passed,
|
|
621
|
+
failed,
|
|
622
|
+
skipped
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
} catch (error) {
|
|
627
|
+
return {
|
|
628
|
+
passed: false,
|
|
629
|
+
error: error.message,
|
|
630
|
+
tests: [],
|
|
631
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async runCargoTestSingleFile(file) {
|
|
637
|
+
try {
|
|
638
|
+
const result = await execAsync(`cargo test --message-format=json`, {
|
|
639
|
+
cwd: path.dirname(file)
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const lines = result.stdout.trim().split('\n');
|
|
643
|
+
const tests = [];
|
|
644
|
+
let passed = 0, failed = 0;
|
|
645
|
+
|
|
646
|
+
for (const line of lines) {
|
|
647
|
+
try {
|
|
648
|
+
const testResult = JSON.parse(line);
|
|
649
|
+
if (testResult.type === 'test') {
|
|
650
|
+
tests.push(testResult);
|
|
651
|
+
if (testResult.event === 'ok') passed++;
|
|
652
|
+
else if (testResult.event === 'failed') failed++;
|
|
653
|
+
}
|
|
654
|
+
} catch {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return {
|
|
660
|
+
passed: failed === 0,
|
|
661
|
+
tests,
|
|
662
|
+
summary: {
|
|
663
|
+
total: passed + failed,
|
|
664
|
+
passed,
|
|
665
|
+
failed,
|
|
666
|
+
skipped: 0
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
} catch (error) {
|
|
671
|
+
return {
|
|
672
|
+
passed: false,
|
|
673
|
+
error: error.message,
|
|
674
|
+
tests: [],
|
|
675
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async runJavaTestSingleFile(file, framework) {
|
|
681
|
+
// Simplified - would need actual implementation
|
|
682
|
+
return {
|
|
683
|
+
passed: false,
|
|
684
|
+
reason: 'Java test execution not fully implemented',
|
|
685
|
+
tests: [],
|
|
686
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
async runCTestSingleFile(file, framework) {
|
|
691
|
+
// Simplified - would need actual implementation
|
|
692
|
+
return {
|
|
693
|
+
passed: false,
|
|
694
|
+
reason: 'C/C++ test execution not fully implemented',
|
|
695
|
+
tests: [],
|
|
696
|
+
summary: { total: 0, passed: 0, failed: 0, skipped: 0 }
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Coverage analysis
|
|
701
|
+
async getJestCoverage(file) {
|
|
702
|
+
try {
|
|
703
|
+
await execAsync(`npx jest "${file}" --coverage --coverageReporters=json --silent`);
|
|
704
|
+
|
|
705
|
+
const coveragePath = path.join(process.cwd(), 'coverage', 'coverage-final.json');
|
|
706
|
+
const coverageData = JSON.parse(await fs.promises.readFile(coveragePath, 'utf8'));
|
|
707
|
+
|
|
708
|
+
const fileCoverage = coverageData[path.resolve(file)] || {};
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
lines: {
|
|
712
|
+
total: Object.keys(fileCoverage.s || {}).length,
|
|
713
|
+
covered: Object.values(fileCoverage.s || {}).filter(v => v > 0).length,
|
|
714
|
+
percentage: this.calculatePercentage(fileCoverage.s)
|
|
715
|
+
},
|
|
716
|
+
functions: {
|
|
717
|
+
total: Object.keys(fileCoverage.f || {}).length,
|
|
718
|
+
covered: Object.values(fileCoverage.f || {}).filter(v => v > 0).length,
|
|
719
|
+
percentage: this.calculatePercentage(fileCoverage.f)
|
|
720
|
+
},
|
|
721
|
+
branches: {
|
|
722
|
+
total: Object.keys(fileCoverage.b || {}).length,
|
|
723
|
+
covered: Object.values(fileCoverage.b || {}).flat().filter(v => v > 0).length,
|
|
724
|
+
percentage: this.calculatePercentage(fileCoverage.b, true)
|
|
725
|
+
},
|
|
726
|
+
statements: {
|
|
727
|
+
total: Object.keys(fileCoverage.s || {}).length,
|
|
728
|
+
covered: Object.values(fileCoverage.s || {}).filter(v => v > 0).length,
|
|
729
|
+
percentage: this.calculatePercentage(fileCoverage.s)
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
} catch (error) {
|
|
734
|
+
return {
|
|
735
|
+
error: error.message,
|
|
736
|
+
available: false
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async getPytestCoverage(file) {
|
|
742
|
+
try {
|
|
743
|
+
const result = await execAsync(`pytest "${file}" --cov="${path.dirname(file)}" --cov-report=json:/tmp/coverage.json`);
|
|
744
|
+
|
|
745
|
+
const coverageData = JSON.parse(await fs.promises.readFile('/tmp/coverage.json', 'utf8'));
|
|
746
|
+
const fileCoverage = coverageData.files[path.resolve(file)] || {};
|
|
747
|
+
|
|
748
|
+
return {
|
|
749
|
+
lines: {
|
|
750
|
+
total: fileCoverage.summary?.num_statements || 0,
|
|
751
|
+
covered: fileCoverage.summary?.covered_lines || 0,
|
|
752
|
+
percentage: fileCoverage.summary?.percent_covered || 0
|
|
753
|
+
},
|
|
754
|
+
missing: fileCoverage.missing_lines || [],
|
|
755
|
+
executed: fileCoverage.executed_lines || []
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
} catch (error) {
|
|
759
|
+
return {
|
|
760
|
+
error: error.message,
|
|
761
|
+
available: false
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async getRustCoverage(file) {
|
|
767
|
+
try {
|
|
768
|
+
// Try to check if cargo-tarpaulin is available
|
|
769
|
+
try {
|
|
770
|
+
await execAsync('cargo tarpaulin --version');
|
|
771
|
+
} catch {
|
|
772
|
+
return {
|
|
773
|
+
error: 'cargo-tarpaulin not installed',
|
|
774
|
+
available: false,
|
|
775
|
+
suggestion: 'Install with: cargo install cargo-tarpaulin'
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const projectDir = path.dirname(file);
|
|
780
|
+
let rootDir = projectDir;
|
|
781
|
+
|
|
782
|
+
// Find Cargo.toml root
|
|
783
|
+
while (rootDir !== path.dirname(rootDir)) {
|
|
784
|
+
if (fsSync.existsSync(path.join(rootDir, 'Cargo.toml'))) {
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
rootDir = path.dirname(rootDir);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const result = await execAsync(`cargo tarpaulin --out Json --output-dir /tmp`, {
|
|
791
|
+
cwd: rootDir
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
const coverageData = JSON.parse(await fs.promises.readFile('/tmp/tarpaulin-report.json', 'utf8'));
|
|
795
|
+
|
|
796
|
+
return {
|
|
797
|
+
lines: {
|
|
798
|
+
total: coverageData.files[file]?.total_lines || 0,
|
|
799
|
+
covered: coverageData.files[file]?.covered_lines || 0,
|
|
800
|
+
percentage: coverageData.files[file]?.coverage || 0
|
|
801
|
+
},
|
|
802
|
+
overall: {
|
|
803
|
+
percentage: coverageData.coverage || 0
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
} catch (error) {
|
|
808
|
+
return {
|
|
809
|
+
error: error.message,
|
|
810
|
+
available: false
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
async getGoCoverage(file) {
|
|
816
|
+
try {
|
|
817
|
+
const result = await execAsync(`go test -coverprofile=/tmp/coverage.out "${file}"`);
|
|
818
|
+
|
|
819
|
+
const coverageOutput = await fs.promises.readFile('/tmp/coverage.out', 'utf8');
|
|
820
|
+
const lines = coverageOutput.split('\n').filter(line => line.includes(file));
|
|
821
|
+
|
|
822
|
+
let totalStatements = 0;
|
|
823
|
+
let coveredStatements = 0;
|
|
824
|
+
|
|
825
|
+
lines.forEach(line => {
|
|
826
|
+
const match = line.match(/(\d+)\s+(\d+)\s+(\d+)/);
|
|
827
|
+
if (match) {
|
|
828
|
+
const count = parseInt(match[3]);
|
|
829
|
+
totalStatements++;
|
|
830
|
+
if (count > 0) coveredStatements++;
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
const percentage = totalStatements > 0 ? Math.round((coveredStatements / totalStatements) * 100) : 0;
|
|
835
|
+
|
|
836
|
+
return {
|
|
837
|
+
lines: {
|
|
838
|
+
total: totalStatements,
|
|
839
|
+
covered: coveredStatements,
|
|
840
|
+
percentage
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
} catch (error) {
|
|
845
|
+
return {
|
|
846
|
+
error: error.message,
|
|
847
|
+
available: false
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// TDD compliance checking
|
|
853
|
+
checkTDDCompliance(sourceFile, testFile, testResults) {
|
|
854
|
+
const compliance = {
|
|
855
|
+
hasTests: false,
|
|
856
|
+
testFirst: false,
|
|
857
|
+
redGreenRefactor: false,
|
|
858
|
+
coverage: 0,
|
|
859
|
+
testsPassed: 0,
|
|
860
|
+
testsFailed: 0,
|
|
861
|
+
recommendations: []
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
// Check if tests exist
|
|
865
|
+
if (testFile && this.fileExistsSync(testFile)) {
|
|
866
|
+
compliance.hasTests = true;
|
|
867
|
+
} else {
|
|
868
|
+
compliance.recommendations.push({
|
|
869
|
+
type: 'tdd_violation',
|
|
870
|
+
priority: 'high',
|
|
871
|
+
message: 'No test file found - TDD requires tests first',
|
|
872
|
+
action: `Create test file: ${this.suggestTestFileName(sourceFile)}`
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Check test results
|
|
877
|
+
if (testResults && testResults.summary) {
|
|
878
|
+
const { total, passed, failed } = testResults.summary;
|
|
879
|
+
|
|
880
|
+
compliance.testsPassed = passed;
|
|
881
|
+
compliance.testsFailed = failed;
|
|
882
|
+
|
|
883
|
+
if (total === 0) {
|
|
884
|
+
compliance.recommendations.push({
|
|
885
|
+
type: 'tdd_violation',
|
|
886
|
+
priority: 'high',
|
|
887
|
+
message: 'No tests found in test file',
|
|
888
|
+
action: 'Write tests before implementing functionality'
|
|
889
|
+
});
|
|
890
|
+
} else if (failed > 0) {
|
|
891
|
+
compliance.redGreenRefactor = true;
|
|
892
|
+
compliance.recommendations.push({
|
|
893
|
+
type: 'tdd_red_phase',
|
|
894
|
+
priority: 'medium',
|
|
895
|
+
message: `${failed} failing tests - in RED phase of TDD`,
|
|
896
|
+
action: 'Implement minimal code to make tests pass'
|
|
897
|
+
});
|
|
898
|
+
} else if (passed > 0) {
|
|
899
|
+
compliance.redGreenRefactor = true;
|
|
900
|
+
compliance.recommendations.push({
|
|
901
|
+
type: 'tdd_green_phase',
|
|
902
|
+
priority: 'low',
|
|
903
|
+
message: 'All tests passing - in GREEN phase of TDD',
|
|
904
|
+
action: 'Consider refactoring for better design'
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return compliance;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Utility methods
|
|
913
|
+
isTestFile(file) {
|
|
914
|
+
const fileName = path.basename(file);
|
|
915
|
+
return fileName.includes('.test.') ||
|
|
916
|
+
fileName.includes('.spec.') ||
|
|
917
|
+
fileName.includes('_test') ||
|
|
918
|
+
fileName.endsWith('Test.java') ||
|
|
919
|
+
fileName.endsWith('Test.cpp') ||
|
|
920
|
+
file.includes('/test/') ||
|
|
921
|
+
file.includes('/tests/');
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
findTestFile(sourceFile) {
|
|
925
|
+
const ext = path.extname(sourceFile);
|
|
926
|
+
const base = path.basename(sourceFile, ext);
|
|
927
|
+
const dir = path.dirname(sourceFile);
|
|
928
|
+
|
|
929
|
+
const testPatterns = [
|
|
930
|
+
`${base}.test${ext}`,
|
|
931
|
+
`${base}.spec${ext}`,
|
|
932
|
+
`${base}_test${ext}`,
|
|
933
|
+
`test_${base}${ext}`,
|
|
934
|
+
`${base}Test${ext}`
|
|
935
|
+
];
|
|
936
|
+
|
|
937
|
+
for (const pattern of testPatterns) {
|
|
938
|
+
const testPath = path.join(dir, pattern);
|
|
939
|
+
if (this.fileExistsSync(testPath)) return testPath;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const testDirs = ['test', 'tests', '__tests__', 'spec'];
|
|
943
|
+
for (const testDir of testDirs) {
|
|
944
|
+
for (const pattern of testPatterns) {
|
|
945
|
+
const testPath = path.join(dir, testDir, pattern);
|
|
946
|
+
if (this.fileExistsSync(testPath)) return testPath;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
findSourceFile(testFile) {
|
|
954
|
+
const ext = path.extname(testFile);
|
|
955
|
+
let base = path.basename(testFile, ext);
|
|
956
|
+
|
|
957
|
+
base = base.replace(/\.(test|spec)$/, '')
|
|
958
|
+
.replace(/_test$/, '')
|
|
959
|
+
.replace(/^test_/, '')
|
|
960
|
+
.replace(/Test$/, '');
|
|
961
|
+
|
|
962
|
+
const dir = path.dirname(testFile);
|
|
963
|
+
const sourcePatterns = [`${base}${ext}`];
|
|
964
|
+
|
|
965
|
+
const parentDir = path.dirname(dir);
|
|
966
|
+
for (const pattern of sourcePatterns) {
|
|
967
|
+
const sourcePath = path.join(parentDir, pattern);
|
|
968
|
+
if (this.fileExistsSync(sourcePath)) return sourcePath;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
for (const pattern of sourcePatterns) {
|
|
972
|
+
const sourcePath = path.join(dir, pattern);
|
|
973
|
+
if (this.fileExistsSync(sourcePath)) return sourcePath;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
suggestTestFileName(sourceFile) {
|
|
980
|
+
const ext = path.extname(sourceFile);
|
|
981
|
+
const base = path.basename(sourceFile, ext);
|
|
982
|
+
const dir = path.dirname(sourceFile);
|
|
983
|
+
|
|
984
|
+
if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {
|
|
985
|
+
return path.join(dir, `${base}.test${ext}`);
|
|
986
|
+
} else if (ext === '.py') {
|
|
987
|
+
return path.join(dir, `test_${base}${ext}`);
|
|
988
|
+
} else if (ext === '.go') {
|
|
989
|
+
return path.join(dir, `${base}_test${ext}`);
|
|
990
|
+
} else if (ext === '.java') {
|
|
991
|
+
return path.join(dir, `${base}Test${ext}`);
|
|
992
|
+
} else {
|
|
993
|
+
return path.join(dir, `${base}_test${ext}`);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
calculatePercentage(coverage, isBranch = false) {
|
|
998
|
+
if (!coverage) return 0;
|
|
999
|
+
|
|
1000
|
+
const values = isBranch ? Object.values(coverage).flat() : Object.values(coverage);
|
|
1001
|
+
const total = values.length;
|
|
1002
|
+
const covered = values.filter(v => v > 0).length;
|
|
1003
|
+
|
|
1004
|
+
return total > 0 ? Math.round((covered / total) * 100) : 0;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
fileExistsSync(filePath) {
|
|
1008
|
+
try {
|
|
1009
|
+
fsSync.accessSync(filePath, fsSync.constants.F_OK);
|
|
1010
|
+
return true;
|
|
1011
|
+
} catch {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
async fileExists(filePath) {
|
|
1017
|
+
try {
|
|
1018
|
+
await fs.promises.access(filePath, fs.constants.F_OK);
|
|
1019
|
+
return true;
|
|
1020
|
+
} catch {
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Rust-specific quality enforcement with enhanced validation
|
|
1027
|
+
class RustQualityEnforcer {
|
|
1028
|
+
constructor(options = {}) {
|
|
1029
|
+
this.config = {
|
|
1030
|
+
allowUnwrap: options.allowUnwrap !== undefined ? options.allowUnwrap : false,
|
|
1031
|
+
allowExpect: options.allowExpect !== undefined ? options.allowExpect : true,
|
|
1032
|
+
allowPanic: options.allowPanic !== undefined ? options.allowPanic : false,
|
|
1033
|
+
allowTodo: options.allowTodo !== undefined ? options.allowTodo : false,
|
|
1034
|
+
allowUnimplemented: options.allowUnimplemented !== undefined ? options.allowUnimplemented : false
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
async analyzeFile(filePath, content) {
|
|
1039
|
+
const issues = [];
|
|
1040
|
+
const lines = content.split('\n');
|
|
1041
|
+
|
|
1042
|
+
// Regex patterns that skip comments
|
|
1043
|
+
const unwrapPattern = /(?<!\/\/.*)\.unwrap\(\)/;
|
|
1044
|
+
const expectPattern = /(?<!\/\/.*)\.expect\(/;
|
|
1045
|
+
const panicPattern = /(?<!\/\/)panic!\(/;
|
|
1046
|
+
const todoPattern = /(?<!\/\/)todo!\(/;
|
|
1047
|
+
const unimplementedPattern = /(?<!\/\/)unimplemented!\(/;
|
|
1048
|
+
|
|
1049
|
+
lines.forEach((line, index) => {
|
|
1050
|
+
// Remove comments from the line for accurate analysis
|
|
1051
|
+
const cleanedLine = line.replace(/\/\/.*$/, '').replace(/\/\*[\s\S]*?\*\//, '');
|
|
1052
|
+
|
|
1053
|
+
if (unwrapPattern.test(cleanedLine)) {
|
|
1054
|
+
if (!this.config.allowUnwrap) {
|
|
1055
|
+
issues.push({
|
|
1056
|
+
type: 'rust_unwrap',
|
|
1057
|
+
severity: 'critical',
|
|
1058
|
+
priority: 'high',
|
|
1059
|
+
message: 'Use of .unwrap() in production code - may panic',
|
|
1060
|
+
line: index + 1,
|
|
1061
|
+
code: line.trim(),
|
|
1062
|
+
suggestion: 'Use match, if let, or ? operator instead',
|
|
1063
|
+
action: 'Replace .unwrap() with proper error handling'
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (expectPattern.test(cleanedLine)) {
|
|
1069
|
+
if (!this.config.allowExpect) {
|
|
1070
|
+
issues.push({
|
|
1071
|
+
type: 'rust_expect',
|
|
1072
|
+
severity: 'warning',
|
|
1073
|
+
priority: 'medium',
|
|
1074
|
+
message: 'Use of .expect() may panic at runtime',
|
|
1075
|
+
line: index + 1,
|
|
1076
|
+
code: line.trim(),
|
|
1077
|
+
suggestion: 'Consider propagating errors with ?',
|
|
1078
|
+
action: 'Replace .expect() with proper error handling'
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if (panicPattern.test(cleanedLine)) {
|
|
1084
|
+
if (!this.config.allowPanic) {
|
|
1085
|
+
issues.push({
|
|
1086
|
+
type: 'rust_panic',
|
|
1087
|
+
severity: 'critical',
|
|
1088
|
+
priority: 'high',
|
|
1089
|
+
message: 'panic!() will crash the program',
|
|
1090
|
+
line: index + 1,
|
|
1091
|
+
code: line.trim(),
|
|
1092
|
+
suggestion: 'Return Result<T, E> and handle errors properly',
|
|
1093
|
+
action: 'Replace panic!() with error propagation'
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
if (todoPattern.test(cleanedLine)) {
|
|
1099
|
+
if (!this.config.allowTodo) {
|
|
1100
|
+
issues.push({
|
|
1101
|
+
type: 'rust_todo',
|
|
1102
|
+
severity: 'error',
|
|
1103
|
+
priority: 'high',
|
|
1104
|
+
message: 'todo!() detected - incomplete code',
|
|
1105
|
+
line: index + 1,
|
|
1106
|
+
code: line.trim(),
|
|
1107
|
+
suggestion: 'Implement the missing functionality',
|
|
1108
|
+
action: 'Complete the implementation'
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (unimplementedPattern.test(cleanedLine)) {
|
|
1114
|
+
if (!this.config.allowUnimplemented) {
|
|
1115
|
+
issues.push({
|
|
1116
|
+
type: 'rust_unimplemented',
|
|
1117
|
+
severity: 'error',
|
|
1118
|
+
priority: 'high',
|
|
1119
|
+
message: 'unimplemented!() detected - incomplete code',
|
|
1120
|
+
line: index + 1,
|
|
1121
|
+
code: line.trim(),
|
|
1122
|
+
suggestion: 'Implement the missing functionality',
|
|
1123
|
+
action: 'Complete the implementation'
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
return {
|
|
1130
|
+
passed: issues.filter(i => i.severity === 'error' || i.severity === 'critical').length === 0,
|
|
1131
|
+
issues,
|
|
1132
|
+
suggestions: issues.map(i => i.suggestion).filter((v, i, a) => a.indexOf(v) === i),
|
|
1133
|
+
coverage: 'advanced',
|
|
1134
|
+
summary: {
|
|
1135
|
+
unwrap: issues.filter(i => i.type === 'rust_unwrap').length,
|
|
1136
|
+
expect: issues.filter(i => i.type === 'rust_expect').length,
|
|
1137
|
+
panic: issues.filter(i => i.type === 'rust_panic').length,
|
|
1138
|
+
todo: issues.filter(i => i.type === 'rust_todo').length,
|
|
1139
|
+
unimplemented: issues.filter(i => i.type === 'rust_unimplemented').length
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// Main unified pipeline
|
|
1146
|
+
class UnifiedPostEditPipeline {
|
|
1147
|
+
constructor(options = {}) {
|
|
1148
|
+
this.tddMode = options.tddMode || false;
|
|
1149
|
+
this.minimumCoverage = options.minimumCoverage || 80;
|
|
1150
|
+
this.blockOnTDDViolations = options.blockOnTDDViolations || false;
|
|
1151
|
+
this.rustStrict = options.rustStrict || false;
|
|
17
1152
|
this.config = this.loadConfig();
|
|
1153
|
+
this.testEngine = new SingleFileTestEngine();
|
|
1154
|
+
this.rustEnforcer = new RustQualityEnforcer({
|
|
1155
|
+
allowUnwrap: !this.rustStrict,
|
|
1156
|
+
allowExpect: !this.rustStrict, // In strict mode, also warn about .expect()
|
|
1157
|
+
allowPanic: !this.rustStrict,
|
|
1158
|
+
allowTodo: false,
|
|
1159
|
+
allowUnimplemented: false
|
|
1160
|
+
});
|
|
1161
|
+
|
|
18
1162
|
this.languageDetectors = {
|
|
19
1163
|
'.js': 'javascript',
|
|
20
1164
|
'.jsx': 'javascript',
|
|
@@ -70,27 +1214,6 @@ class PostEditPipeline {
|
|
|
70
1214
|
python: ['mypy'],
|
|
71
1215
|
rust: ['cargo', 'check'],
|
|
72
1216
|
go: ['go', 'vet']
|
|
73
|
-
},
|
|
74
|
-
testCommands: {
|
|
75
|
-
javascript: ['npm', 'test'],
|
|
76
|
-
typescript: ['npm', 'test'],
|
|
77
|
-
python: ['pytest'],
|
|
78
|
-
rust: ['cargo', 'test'],
|
|
79
|
-
go: ['go', 'test']
|
|
80
|
-
},
|
|
81
|
-
securityScanners: {
|
|
82
|
-
javascript: ['npm', 'audit'],
|
|
83
|
-
typescript: ['npm', 'audit'],
|
|
84
|
-
python: ['bandit', '-r'],
|
|
85
|
-
rust: ['cargo', 'audit'],
|
|
86
|
-
go: ['gosec']
|
|
87
|
-
},
|
|
88
|
-
dependencyCheckers: {
|
|
89
|
-
javascript: ['npm', 'ls'],
|
|
90
|
-
typescript: ['npm', 'ls'],
|
|
91
|
-
python: ['pip', 'check'],
|
|
92
|
-
rust: ['cargo', 'tree'],
|
|
93
|
-
go: ['go', 'mod', 'verify']
|
|
94
1217
|
}
|
|
95
1218
|
};
|
|
96
1219
|
}
|
|
@@ -124,8 +1247,12 @@ class PostEditPipeline {
|
|
|
124
1247
|
}
|
|
125
1248
|
|
|
126
1249
|
async checkToolAvailable(tool) {
|
|
127
|
-
|
|
128
|
-
|
|
1250
|
+
try {
|
|
1251
|
+
const { code } = await this.runCommand('which', [tool]);
|
|
1252
|
+
return code === 0;
|
|
1253
|
+
} catch {
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
129
1256
|
}
|
|
130
1257
|
|
|
131
1258
|
async formatFile(filePath, language) {
|
|
@@ -137,110 +1264,39 @@ class PostEditPipeline {
|
|
|
137
1264
|
return { success: false, message: `Formatter ${tool} not available` };
|
|
138
1265
|
}
|
|
139
1266
|
|
|
140
|
-
const result = await this.runCommand(tool, [...args, filePath]);
|
|
141
|
-
return {
|
|
142
|
-
success: result.code === 0,
|
|
143
|
-
message: result.code === 0 ? 'Formatted successfully' : result.stderr,
|
|
144
|
-
output: result.stdout
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async lintFile(filePath, language) {
|
|
149
|
-
const linters = this.config.linters[language];
|
|
150
|
-
if (!linters) return { success: true, message: 'No linter configured' };
|
|
151
|
-
|
|
152
|
-
const [tool, ...args] = linters;
|
|
153
|
-
if (!(await this.checkToolAvailable(tool))) {
|
|
154
|
-
return { success: false, message: `Linter ${tool} not available` };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const result = await this.runCommand(tool, [...args, filePath]);
|
|
158
|
-
return {
|
|
159
|
-
success: result.code === 0,
|
|
160
|
-
message: result.code === 0 ? 'Linting passed' : 'Linting issues found',
|
|
161
|
-
output: result.stdout + result.stderr,
|
|
162
|
-
issues: result.code !== 0 ? result.stderr : ''
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async typeCheck(filePath, language) {
|
|
167
|
-
const typeCheckers = this.config.typeCheckers[language];
|
|
168
|
-
if (!typeCheckers) return { success: true, message: 'No type checker configured' };
|
|
169
|
-
|
|
170
|
-
const [tool, ...args] = typeCheckers;
|
|
171
|
-
if (!(await this.checkToolAvailable(tool))) {
|
|
172
|
-
return { success: false, message: `Type checker ${tool} not available` };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const projectDir = this.findProjectRoot(filePath);
|
|
176
|
-
const result = await this.runCommand(tool, args, projectDir);
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
success: result.code === 0,
|
|
180
|
-
message: result.code === 0 ? 'Type checking passed' : 'Type errors found',
|
|
181
|
-
output: result.stdout + result.stderr,
|
|
182
|
-
errors: result.code !== 0 ? result.stderr : ''
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async checkDependencies(filePath, language) {
|
|
187
|
-
const depCheckers = this.config.dependencyCheckers[language];
|
|
188
|
-
if (!depCheckers) return { success: true, message: 'No dependency checker configured' };
|
|
189
|
-
|
|
190
|
-
const [tool, ...args] = depCheckers;
|
|
191
|
-
if (!(await this.checkToolAvailable(tool))) {
|
|
192
|
-
return { success: false, message: `Dependency checker ${tool} not available` };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const projectDir = this.findProjectRoot(filePath);
|
|
196
|
-
const result = await this.runCommand(tool, args, projectDir);
|
|
197
|
-
|
|
1267
|
+
const result = await this.runCommand(tool, [...args, filePath]);
|
|
198
1268
|
return {
|
|
199
1269
|
success: result.code === 0,
|
|
200
|
-
message: result.code === 0 ? '
|
|
201
|
-
output: result.stdout
|
|
202
|
-
issues: result.code !== 0 ? result.stderr : ''
|
|
1270
|
+
message: result.code === 0 ? 'Formatted successfully' : result.stderr,
|
|
1271
|
+
output: result.stdout
|
|
203
1272
|
};
|
|
204
1273
|
}
|
|
205
1274
|
|
|
206
|
-
async
|
|
207
|
-
const
|
|
208
|
-
if (!
|
|
1275
|
+
async lintFile(filePath, language) {
|
|
1276
|
+
const linters = this.config.linters[language];
|
|
1277
|
+
if (!linters) return { success: true, message: 'No linter configured' };
|
|
209
1278
|
|
|
210
|
-
const [tool, ...args] =
|
|
1279
|
+
const [tool, ...args] = linters;
|
|
211
1280
|
if (!(await this.checkToolAvailable(tool))) {
|
|
212
|
-
return { success: false, message: `
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const projectDir = this.findProjectRoot(filePath);
|
|
216
|
-
|
|
217
|
-
// Check if tests exist
|
|
218
|
-
const testDirs = ['test', 'tests', '__tests__', 'spec'];
|
|
219
|
-
const hasTests = testDirs.some(dir =>
|
|
220
|
-
fs.existsSync(path.join(projectDir, dir))
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
if (!hasTests) {
|
|
224
|
-
return { success: true, message: 'No tests found to run' };
|
|
1281
|
+
return { success: false, message: `Linter ${tool} not available` };
|
|
225
1282
|
}
|
|
226
1283
|
|
|
227
|
-
const result = await this.runCommand(tool, args,
|
|
228
|
-
|
|
1284
|
+
const result = await this.runCommand(tool, [...args, filePath]);
|
|
229
1285
|
return {
|
|
230
1286
|
success: result.code === 0,
|
|
231
|
-
message: result.code === 0 ? '
|
|
1287
|
+
message: result.code === 0 ? 'Linting passed' : 'Linting issues found',
|
|
232
1288
|
output: result.stdout + result.stderr,
|
|
233
|
-
|
|
1289
|
+
issues: result.code !== 0 ? result.stderr : ''
|
|
234
1290
|
};
|
|
235
1291
|
}
|
|
236
1292
|
|
|
237
|
-
async
|
|
238
|
-
const
|
|
239
|
-
if (!
|
|
1293
|
+
async typeCheck(filePath, language) {
|
|
1294
|
+
const typeCheckers = this.config.typeCheckers[language];
|
|
1295
|
+
if (!typeCheckers) return { success: true, message: 'No type checker configured' };
|
|
240
1296
|
|
|
241
|
-
const [tool, ...args] =
|
|
1297
|
+
const [tool, ...args] = typeCheckers;
|
|
242
1298
|
if (!(await this.checkToolAvailable(tool))) {
|
|
243
|
-
return { success: false, message: `
|
|
1299
|
+
return { success: false, message: `Type checker ${tool} not available` };
|
|
244
1300
|
}
|
|
245
1301
|
|
|
246
1302
|
const projectDir = this.findProjectRoot(filePath);
|
|
@@ -248,9 +1304,9 @@ class PostEditPipeline {
|
|
|
248
1304
|
|
|
249
1305
|
return {
|
|
250
1306
|
success: result.code === 0,
|
|
251
|
-
message: result.code === 0 ? '
|
|
1307
|
+
message: result.code === 0 ? 'Type checking passed' : 'Type errors found',
|
|
252
1308
|
output: result.stdout + result.stderr,
|
|
253
|
-
|
|
1309
|
+
errors: result.code !== 0 ? result.stderr : ''
|
|
254
1310
|
};
|
|
255
1311
|
}
|
|
256
1312
|
|
|
@@ -268,223 +1324,7 @@ class PostEditPipeline {
|
|
|
268
1324
|
return process.cwd();
|
|
269
1325
|
}
|
|
270
1326
|
|
|
271
|
-
async analyzeDependencies(filePath) {
|
|
272
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
273
|
-
const language = this.detectLanguage(filePath);
|
|
274
|
-
|
|
275
|
-
let imports = [];
|
|
276
|
-
|
|
277
|
-
// Extract imports based on language
|
|
278
|
-
switch (language) {
|
|
279
|
-
case 'javascript':
|
|
280
|
-
case 'typescript':
|
|
281
|
-
imports = this.extractJSImports(content);
|
|
282
|
-
break;
|
|
283
|
-
case 'python':
|
|
284
|
-
imports = this.extractPythonImports(content);
|
|
285
|
-
break;
|
|
286
|
-
case 'rust':
|
|
287
|
-
imports = this.extractRustUses(content);
|
|
288
|
-
break;
|
|
289
|
-
case 'go':
|
|
290
|
-
imports = this.extractGoImports(content);
|
|
291
|
-
break;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Check which dependencies exist
|
|
295
|
-
const analysis = {
|
|
296
|
-
total: imports.length,
|
|
297
|
-
existing: 0,
|
|
298
|
-
missing: [],
|
|
299
|
-
suggestions: []
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
for (const imp of imports) {
|
|
303
|
-
if (await this.dependencyExists(imp, language)) {
|
|
304
|
-
analysis.existing++;
|
|
305
|
-
} else {
|
|
306
|
-
analysis.missing.push(imp);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return analysis;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
extractJSImports(content) {
|
|
314
|
-
const importRegex = /(?:import.*?from\s+['"`]([^'"`]+)['"`]|require\(['"`]([^'"`]+)['"`]\))/g;
|
|
315
|
-
const imports = [];
|
|
316
|
-
let match;
|
|
317
|
-
|
|
318
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
319
|
-
imports.push(match[1] || match[2]);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return imports.filter(imp => !imp.startsWith('.') && !imp.startsWith('/'));
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
extractPythonImports(content) {
|
|
326
|
-
const importRegex = /(?:^from\s+(\S+)|^import\s+(\S+))/gm;
|
|
327
|
-
const imports = [];
|
|
328
|
-
let match;
|
|
329
|
-
|
|
330
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
331
|
-
imports.push(match[1] || match[2]);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return imports;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
extractRustUses(content) {
|
|
338
|
-
const useRegex = /^use\s+([^;]+);/gm;
|
|
339
|
-
const imports = [];
|
|
340
|
-
let match;
|
|
341
|
-
|
|
342
|
-
while ((match = useRegex.exec(content)) !== null) {
|
|
343
|
-
imports.push(match[1].split('::')[0]);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return imports;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
extractGoImports(content) {
|
|
350
|
-
const importRegex = /import\s+(?:\(\s*([\s\S]*?)\s*\)|"([^"]+)")/g;
|
|
351
|
-
const imports = [];
|
|
352
|
-
let match;
|
|
353
|
-
|
|
354
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
355
|
-
if (match[1]) {
|
|
356
|
-
// Multi-line import block
|
|
357
|
-
const lines = match[1].split('\n');
|
|
358
|
-
for (const line of lines) {
|
|
359
|
-
const lineMatch = line.match(/"([^"]+)"/);
|
|
360
|
-
if (lineMatch) imports.push(lineMatch[1]);
|
|
361
|
-
}
|
|
362
|
-
} else {
|
|
363
|
-
imports.push(match[2]);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return imports;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
async dependencyExists(dependency, language) {
|
|
371
|
-
// This is a simplified check - in reality you'd want more sophisticated detection
|
|
372
|
-
const projectDir = this.findProjectRoot(process.cwd());
|
|
373
|
-
|
|
374
|
-
switch (language) {
|
|
375
|
-
case 'javascript':
|
|
376
|
-
case 'typescript':
|
|
377
|
-
return fs.existsSync(path.join(projectDir, 'node_modules', dependency));
|
|
378
|
-
case 'python':
|
|
379
|
-
try {
|
|
380
|
-
await execAsync(`python -c "import ${dependency}"`);
|
|
381
|
-
return true;
|
|
382
|
-
} catch {
|
|
383
|
-
return false;
|
|
384
|
-
}
|
|
385
|
-
default:
|
|
386
|
-
return true; // Assume exists for unknown languages
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
async run(filePath, options = {}) {
|
|
391
|
-
const language = this.detectLanguage(filePath);
|
|
392
|
-
const results = {
|
|
393
|
-
file: filePath,
|
|
394
|
-
language,
|
|
395
|
-
timestamp: new Date().toISOString(),
|
|
396
|
-
agentContext: this.extractAgentContext(options),
|
|
397
|
-
steps: {},
|
|
398
|
-
summary: {
|
|
399
|
-
success: true,
|
|
400
|
-
warnings: [],
|
|
401
|
-
errors: [],
|
|
402
|
-
suggestions: []
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
console.log(`\n🔍 STARTING VALIDATION PIPELINE FOR: ${path.basename(filePath)}`);
|
|
407
|
-
console.log(`📋 Language: ${language.toUpperCase()}`);
|
|
408
|
-
|
|
409
|
-
// Step 1: Format
|
|
410
|
-
console.log('\n📝 FORMATTING...');
|
|
411
|
-
results.steps.formatting = await this.formatFile(filePath, language);
|
|
412
|
-
this.logStepResult('Format', results.steps.formatting);
|
|
413
|
-
|
|
414
|
-
// Step 2: Lint
|
|
415
|
-
console.log('\n🔍 LINTING...');
|
|
416
|
-
results.steps.linting = await this.lintFile(filePath, language);
|
|
417
|
-
this.logStepResult('Lint', results.steps.linting);
|
|
418
|
-
if (!results.steps.linting.success) {
|
|
419
|
-
results.summary.warnings.push(`Linting issues in ${path.basename(filePath)}`);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Step 3: Type Check
|
|
423
|
-
console.log('\n🎯 TYPE CHECKING...');
|
|
424
|
-
results.steps.typeCheck = await this.typeCheck(filePath, language);
|
|
425
|
-
this.logStepResult('Type Check', results.steps.typeCheck);
|
|
426
|
-
if (!results.steps.typeCheck.success) {
|
|
427
|
-
results.summary.errors.push(`Type errors in ${path.basename(filePath)}`);
|
|
428
|
-
results.summary.success = false;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Step 4: Dependency Analysis
|
|
432
|
-
console.log('\n📦 ANALYZING DEPENDENCIES...');
|
|
433
|
-
results.steps.dependencies = await this.checkDependencies(filePath, language);
|
|
434
|
-
this.logStepResult('Dependencies', results.steps.dependencies);
|
|
435
|
-
|
|
436
|
-
const depAnalysis = await this.analyzeDependencies(filePath);
|
|
437
|
-
console.log(`📊 Dependency Analysis: ${depAnalysis.existing}/${depAnalysis.total} dependencies found`);
|
|
438
|
-
|
|
439
|
-
if (depAnalysis.missing.length > 0) {
|
|
440
|
-
results.summary.warnings.push(`Missing dependencies: ${depAnalysis.missing.join(', ')}`);
|
|
441
|
-
results.summary.suggestions.push('🤖 Consider spawning agents to create missing dependencies');
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// Step 5: Security Scan
|
|
445
|
-
console.log('\n🛡️ SECURITY SCANNING...');
|
|
446
|
-
results.steps.security = await this.securityScan(filePath, language);
|
|
447
|
-
this.logStepResult('Security', results.steps.security);
|
|
448
|
-
if (!results.steps.security.success) {
|
|
449
|
-
results.summary.warnings.push(`Security vulnerabilities found in ${path.basename(filePath)}`);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Step 6: Run Tests (if validation tier allows)
|
|
453
|
-
if (depAnalysis.existing / depAnalysis.total > 0.7) { // Only if most dependencies exist
|
|
454
|
-
console.log('\n🧪 RUNNING TESTS...');
|
|
455
|
-
results.steps.tests = await this.runTests(filePath, language);
|
|
456
|
-
this.logStepResult('Tests', results.steps.tests);
|
|
457
|
-
if (!results.steps.tests.success) {
|
|
458
|
-
results.summary.errors.push(`Test failures in ${path.basename(filePath)}`);
|
|
459
|
-
results.summary.success = false;
|
|
460
|
-
}
|
|
461
|
-
} else {
|
|
462
|
-
console.log('\n⏳ SKIPPING TESTS: Insufficient dependencies (Progressive validation)');
|
|
463
|
-
results.steps.tests = { success: true, message: 'Skipped due to missing dependencies' };
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Generate summary
|
|
467
|
-
this.printSummary(results);
|
|
468
|
-
|
|
469
|
-
// Log to root file
|
|
470
|
-
await this.logToRootFile(results);
|
|
471
|
-
|
|
472
|
-
return results;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
logStepResult(step, result) {
|
|
476
|
-
if (result.success) {
|
|
477
|
-
console.log(` ✅ ${step}: ${result.message}`);
|
|
478
|
-
} else {
|
|
479
|
-
console.log(` ❌ ${step}: ${result.message}`);
|
|
480
|
-
if (result.issues || result.errors || result.failures) {
|
|
481
|
-
console.log(` ${(result.issues || result.errors || result.failures).slice(0, 200)}...`);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
1327
|
extractAgentContext(options = {}) {
|
|
487
|
-
// Extract agent information from various sources
|
|
488
1328
|
const context = {
|
|
489
1329
|
memoryKey: options.memoryKey || process.env.MEMORY_KEY || null,
|
|
490
1330
|
agentType: options.agentType || process.env.AGENT_TYPE || null,
|
|
@@ -494,7 +1334,6 @@ class PostEditPipeline {
|
|
|
494
1334
|
sessionId: options.sessionId || process.env.SESSION_ID || null
|
|
495
1335
|
};
|
|
496
1336
|
|
|
497
|
-
// Parse agent info from memory key (format: "swarm/[agent]/[step]")
|
|
498
1337
|
if (context.memoryKey && !context.agentType) {
|
|
499
1338
|
const keyParts = context.memoryKey.split('/');
|
|
500
1339
|
if (keyParts.length >= 2) {
|
|
@@ -523,25 +1362,24 @@ class PostEditPipeline {
|
|
|
523
1362
|
const logPath = path.join(process.cwd(), 'post-edit-pipeline.log');
|
|
524
1363
|
const MAX_ENTRIES = 500;
|
|
525
1364
|
|
|
526
|
-
// Create log entry
|
|
527
1365
|
const logEntry = {
|
|
528
1366
|
timestamp: results.timestamp,
|
|
529
1367
|
displayTimestamp: this.formatTimestamp(results.timestamp),
|
|
530
1368
|
file: results.file,
|
|
1369
|
+
editId: results.editId || 'N/A',
|
|
531
1370
|
language: results.language,
|
|
532
1371
|
agent: results.agentContext,
|
|
533
|
-
status: results.summary.success ? 'PASSED' : 'FAILED',
|
|
1372
|
+
status: results.summary.success ? 'PASSED' : (results.blocking ? 'BLOCKED' : 'FAILED'),
|
|
1373
|
+
tddMode: this.tddMode,
|
|
1374
|
+
tddPhase: results.tddPhase || 'N/A',
|
|
534
1375
|
errors: results.summary.errors.length,
|
|
535
1376
|
warnings: results.summary.warnings.length,
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
security: results.steps.security?.success || false,
|
|
543
|
-
tests: results.steps.tests?.success || false
|
|
544
|
-
},
|
|
1377
|
+
steps: results.steps || {},
|
|
1378
|
+
testing: results.testing || {},
|
|
1379
|
+
coverage: results.coverage || {},
|
|
1380
|
+
tddCompliance: results.tddCompliance || {},
|
|
1381
|
+
rustQuality: results.rustQuality || {},
|
|
1382
|
+
recommendations: results.recommendations || [],
|
|
545
1383
|
details: {
|
|
546
1384
|
errors: results.summary.errors,
|
|
547
1385
|
warnings: results.summary.warnings,
|
|
@@ -549,38 +1387,19 @@ class PostEditPipeline {
|
|
|
549
1387
|
}
|
|
550
1388
|
};
|
|
551
1389
|
|
|
552
|
-
// Format log entry with separator
|
|
553
1390
|
const logText = [
|
|
554
1391
|
'═'.repeat(80),
|
|
555
1392
|
`TIMESTAMP: ${logEntry.displayTimestamp}`,
|
|
556
1393
|
`FILE: ${logEntry.file}`,
|
|
1394
|
+
`EDIT ID: ${logEntry.editId}`,
|
|
557
1395
|
`LANGUAGE: ${logEntry.language}`,
|
|
558
1396
|
`STATUS: ${logEntry.status}`,
|
|
1397
|
+
`TDD MODE: ${logEntry.tddMode ? 'ENABLED' : 'DISABLED'}`,
|
|
1398
|
+
`TDD PHASE: ${logEntry.tddPhase}`,
|
|
559
1399
|
'',
|
|
560
1400
|
'AGENT CONTEXT:',
|
|
561
1401
|
` Memory Key: ${logEntry.agent.memoryKey || 'N/A'}`,
|
|
562
1402
|
` Agent Type: ${logEntry.agent.agentType || 'N/A'}`,
|
|
563
|
-
` Agent Name: ${logEntry.agent.agentName || 'N/A'}`,
|
|
564
|
-
` Swarm ID: ${logEntry.agent.swarmId || 'N/A'}`,
|
|
565
|
-
` Task ID: ${logEntry.agent.taskId || 'N/A'}`,
|
|
566
|
-
` Session ID: ${logEntry.agent.sessionId || 'N/A'}`,
|
|
567
|
-
'',
|
|
568
|
-
'VALIDATION STEPS:',
|
|
569
|
-
` ✓ Formatting: ${logEntry.steps.formatting ? '✅' : '❌'}`,
|
|
570
|
-
` ✓ Linting: ${logEntry.steps.linting ? '✅' : '❌'}`,
|
|
571
|
-
` ✓ Type Check: ${logEntry.steps.typeCheck ? '✅' : '❌'}`,
|
|
572
|
-
` ✓ Dependencies: ${logEntry.steps.dependencies ? '✅' : '❌'}`,
|
|
573
|
-
` ✓ Security: ${logEntry.steps.security ? '✅' : '❌'}`,
|
|
574
|
-
` ✓ Tests: ${logEntry.steps.tests ? '✅' : '❌'}`,
|
|
575
|
-
'',
|
|
576
|
-
`ERRORS (${logEntry.errors}):`,
|
|
577
|
-
...logEntry.details.errors.map(e => ` • ${e}`),
|
|
578
|
-
'',
|
|
579
|
-
`WARNINGS (${logEntry.warnings}):`,
|
|
580
|
-
...logEntry.details.warnings.map(w => ` • ${w}`),
|
|
581
|
-
'',
|
|
582
|
-
`SUGGESTIONS (${logEntry.suggestions}):`,
|
|
583
|
-
...logEntry.details.suggestions.map(s => ` • ${s}`),
|
|
584
1403
|
'',
|
|
585
1404
|
'JSON:',
|
|
586
1405
|
JSON.stringify(logEntry, null, 2),
|
|
@@ -590,69 +1409,83 @@ class PostEditPipeline {
|
|
|
590
1409
|
].join('\n');
|
|
591
1410
|
|
|
592
1411
|
try {
|
|
593
|
-
// Read existing log and parse entries
|
|
594
1412
|
let existingEntries = [];
|
|
595
1413
|
if (fs.existsSync(logPath)) {
|
|
596
1414
|
const existingLog = fs.readFileSync(logPath, 'utf8');
|
|
597
|
-
|
|
598
|
-
// Split by separator and parse JSON from each entry
|
|
599
1415
|
const entrySections = existingLog.split('═'.repeat(80)).filter(s => s.trim());
|
|
600
1416
|
|
|
601
1417
|
for (const section of entrySections) {
|
|
602
|
-
const
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
1418
|
+
const jsonStart = section.indexOf('JSON:');
|
|
1419
|
+
if (jsonStart !== -1) {
|
|
1420
|
+
const jsonText = section.substring(jsonStart + 5).trim();
|
|
1421
|
+
let braceCount = 0;
|
|
1422
|
+
let jsonEnd = 0;
|
|
1423
|
+
let inString = false;
|
|
1424
|
+
let escapeNext = false;
|
|
1425
|
+
|
|
1426
|
+
for (let i = 0; i < jsonText.length; i++) {
|
|
1427
|
+
const char = jsonText[i];
|
|
1428
|
+
|
|
1429
|
+
if (escapeNext) {
|
|
1430
|
+
escapeNext = false;
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
if (char === '\\') {
|
|
1435
|
+
escapeNext = true;
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
if (char === '"') {
|
|
1440
|
+
inString = !inString;
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
if (!inString) {
|
|
1445
|
+
if (char === '{') braceCount++;
|
|
1446
|
+
if (char === '}') {
|
|
1447
|
+
braceCount--;
|
|
1448
|
+
if (braceCount === 0) {
|
|
1449
|
+
jsonEnd = i + 1;
|
|
1450
|
+
break;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
if (jsonEnd > 0) {
|
|
1457
|
+
try {
|
|
1458
|
+
const entry = JSON.parse(jsonText.substring(0, jsonEnd));
|
|
1459
|
+
existingEntries.push(entry);
|
|
1460
|
+
} catch (e) {
|
|
1461
|
+
console.error(`Failed to parse JSON entry: ${e.message}`);
|
|
1462
|
+
}
|
|
609
1463
|
}
|
|
610
1464
|
}
|
|
611
1465
|
}
|
|
612
1466
|
}
|
|
613
1467
|
|
|
614
|
-
// Add new entry at the beginning
|
|
615
1468
|
existingEntries.unshift(logEntry);
|
|
616
1469
|
|
|
617
|
-
// Enforce 500 entry limit - keep newest 500
|
|
618
1470
|
if (existingEntries.length > MAX_ENTRIES) {
|
|
619
1471
|
existingEntries = existingEntries.slice(0, MAX_ENTRIES);
|
|
620
1472
|
console.log(`\n🗑️ Trimmed log to ${MAX_ENTRIES} most recent entries`);
|
|
621
1473
|
}
|
|
622
1474
|
|
|
623
|
-
// Rebuild log file with all entries
|
|
624
1475
|
const rebuiltLog = existingEntries.map(entry => {
|
|
625
|
-
|
|
1476
|
+
return [
|
|
626
1477
|
'═'.repeat(80),
|
|
627
1478
|
`TIMESTAMP: ${entry.displayTimestamp}`,
|
|
628
1479
|
`FILE: ${entry.file}`,
|
|
1480
|
+
`EDIT ID: ${entry.editId || 'N/A'}`,
|
|
629
1481
|
`LANGUAGE: ${entry.language}`,
|
|
630
1482
|
`STATUS: ${entry.status}`,
|
|
1483
|
+
`TDD MODE: ${entry.tddMode ? 'ENABLED' : 'DISABLED'}`,
|
|
1484
|
+
`TDD PHASE: ${entry.tddPhase || 'N/A'}`,
|
|
631
1485
|
'',
|
|
632
1486
|
'AGENT CONTEXT:',
|
|
633
|
-
` Memory Key: ${entry.agent
|
|
634
|
-
` Agent Type: ${entry.agent
|
|
635
|
-
` Agent Name: ${entry.agent.agentName || 'N/A'}`,
|
|
636
|
-
` Swarm ID: ${entry.agent.swarmId || 'N/A'}`,
|
|
637
|
-
` Task ID: ${entry.agent.taskId || 'N/A'}`,
|
|
638
|
-
` Session ID: ${entry.agent.sessionId || 'N/A'}`,
|
|
639
|
-
'',
|
|
640
|
-
'VALIDATION STEPS:',
|
|
641
|
-
` ✓ Formatting: ${entry.steps.formatting ? '✅' : '❌'}`,
|
|
642
|
-
` ✓ Linting: ${entry.steps.linting ? '✅' : '❌'}`,
|
|
643
|
-
` ✓ Type Check: ${entry.steps.typeCheck ? '✅' : '❌'}`,
|
|
644
|
-
` ✓ Dependencies: ${entry.steps.dependencies ? '✅' : '❌'}`,
|
|
645
|
-
` ✓ Security: ${entry.steps.security ? '✅' : '❌'}`,
|
|
646
|
-
` ✓ Tests: ${entry.steps.tests ? '✅' : '❌'}`,
|
|
647
|
-
'',
|
|
648
|
-
`ERRORS (${entry.errors}):`,
|
|
649
|
-
...(entry.details.errors || []).map(e => ` • ${e}`),
|
|
650
|
-
'',
|
|
651
|
-
`WARNINGS (${entry.warnings}):`,
|
|
652
|
-
...(entry.details.warnings || []).map(w => ` • ${w}`),
|
|
653
|
-
'',
|
|
654
|
-
`SUGGESTIONS (${entry.suggestions}):`,
|
|
655
|
-
...(entry.details.suggestions || []).map(s => ` • ${s}`),
|
|
1487
|
+
` Memory Key: ${entry.agent?.memoryKey || 'N/A'}`,
|
|
1488
|
+
` Agent Type: ${entry.agent?.agentType || 'N/A'}`,
|
|
656
1489
|
'',
|
|
657
1490
|
'JSON:',
|
|
658
1491
|
JSON.stringify(entry, null, 2),
|
|
@@ -660,8 +1493,6 @@ class PostEditPipeline {
|
|
|
660
1493
|
'',
|
|
661
1494
|
''
|
|
662
1495
|
].join('\n');
|
|
663
|
-
|
|
664
|
-
return formattedEntry;
|
|
665
1496
|
}).join('');
|
|
666
1497
|
|
|
667
1498
|
fs.writeFileSync(logPath, rebuiltLog, 'utf8');
|
|
@@ -672,17 +1503,200 @@ class PostEditPipeline {
|
|
|
672
1503
|
}
|
|
673
1504
|
}
|
|
674
1505
|
|
|
1506
|
+
async run(filePath, options = {}) {
|
|
1507
|
+
const language = this.detectLanguage(filePath);
|
|
1508
|
+
const results = {
|
|
1509
|
+
file: filePath,
|
|
1510
|
+
language,
|
|
1511
|
+
timestamp: new Date().toISOString(),
|
|
1512
|
+
editId: `edit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
1513
|
+
agentContext: this.extractAgentContext(options),
|
|
1514
|
+
steps: {},
|
|
1515
|
+
testing: null,
|
|
1516
|
+
coverage: null,
|
|
1517
|
+
tddCompliance: null,
|
|
1518
|
+
tddPhase: 'unknown',
|
|
1519
|
+
rustQuality: null,
|
|
1520
|
+
recommendations: [],
|
|
1521
|
+
blocking: false,
|
|
1522
|
+
summary: {
|
|
1523
|
+
success: true,
|
|
1524
|
+
warnings: [],
|
|
1525
|
+
errors: [],
|
|
1526
|
+
suggestions: []
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
console.log(`\n🔍 UNIFIED POST-EDIT PIPELINE`);
|
|
1531
|
+
console.log(`📄 File: ${path.basename(filePath)}`);
|
|
1532
|
+
console.log(`📋 Language: ${language.toUpperCase()}`);
|
|
1533
|
+
console.log(`🧪 TDD Mode: ${this.tddMode ? 'ENABLED' : 'DISABLED'}`);
|
|
1534
|
+
|
|
1535
|
+
let content = '';
|
|
1536
|
+
try {
|
|
1537
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
1538
|
+
} catch (error) {
|
|
1539
|
+
results.summary.errors.push(`Cannot read file: ${error.message}`);
|
|
1540
|
+
results.summary.success = false;
|
|
1541
|
+
await this.logToRootFile(results);
|
|
1542
|
+
return results;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// Step 1: Format
|
|
1546
|
+
console.log('\n📝 FORMATTING...');
|
|
1547
|
+
results.steps.formatting = await this.formatFile(filePath, language);
|
|
1548
|
+
this.logStepResult('Format', results.steps.formatting);
|
|
1549
|
+
|
|
1550
|
+
// Step 2: Lint
|
|
1551
|
+
console.log('\n🔍 LINTING...');
|
|
1552
|
+
results.steps.linting = await this.lintFile(filePath, language);
|
|
1553
|
+
this.logStepResult('Lint', results.steps.linting);
|
|
1554
|
+
if (!results.steps.linting.success) {
|
|
1555
|
+
results.summary.warnings.push(`Linting issues in ${path.basename(filePath)}`);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// Step 3: Type Check
|
|
1559
|
+
console.log('\n🎯 TYPE CHECKING...');
|
|
1560
|
+
results.steps.typeCheck = await this.typeCheck(filePath, language);
|
|
1561
|
+
this.logStepResult('Type Check', results.steps.typeCheck);
|
|
1562
|
+
if (!results.steps.typeCheck.success) {
|
|
1563
|
+
results.summary.errors.push(`Type errors in ${path.basename(filePath)}`);
|
|
1564
|
+
results.summary.success = false;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
// Step 4: Rust Quality Enforcement (if Rust and strict mode)
|
|
1568
|
+
if (language === 'rust' && this.rustStrict) {
|
|
1569
|
+
console.log('\n🦀 RUST QUALITY ENFORCEMENT...');
|
|
1570
|
+
results.rustQuality = await this.rustEnforcer.analyzeFile(filePath, content);
|
|
1571
|
+
|
|
1572
|
+
if (!results.rustQuality.passed) {
|
|
1573
|
+
console.log(` ❌ Rust quality issues found`);
|
|
1574
|
+
results.rustQuality.issues.forEach(issue => {
|
|
1575
|
+
console.log(` [${issue.severity.toUpperCase()}] ${issue.message}`);
|
|
1576
|
+
results.recommendations.push(issue);
|
|
1577
|
+
|
|
1578
|
+
if (issue.severity === 'error') {
|
|
1579
|
+
results.summary.errors.push(issue.message);
|
|
1580
|
+
results.summary.success = false;
|
|
1581
|
+
} else {
|
|
1582
|
+
results.summary.warnings.push(issue.message);
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
} else {
|
|
1586
|
+
console.log(` ✅ Rust quality checks passed`);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// Step 5: TDD Testing (if enabled)
|
|
1591
|
+
if (this.tddMode) {
|
|
1592
|
+
console.log('\n🧪 TDD TESTING...');
|
|
1593
|
+
results.testing = await this.testEngine.executeTests(filePath, content);
|
|
1594
|
+
|
|
1595
|
+
if (results.testing.executed) {
|
|
1596
|
+
Logger.success(`Tests executed with ${results.testing.framework}`);
|
|
1597
|
+
|
|
1598
|
+
if (results.testing.results) {
|
|
1599
|
+
const { total, passed, failed } = results.testing.results.summary;
|
|
1600
|
+
Logger.test(`Test results: ${passed}/${total} passed, ${failed} failed`);
|
|
1601
|
+
|
|
1602
|
+
if (failed > 0) {
|
|
1603
|
+
results.tddPhase = 'red';
|
|
1604
|
+
Logger.tdd('TDD Phase: RED (failing tests)');
|
|
1605
|
+
} else if (passed > 0) {
|
|
1606
|
+
results.tddPhase = 'green';
|
|
1607
|
+
Logger.tdd('TDD Phase: GREEN (passing tests)');
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
} else {
|
|
1611
|
+
Logger.warning(`Tests not executed: ${results.testing.reason}`);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// Step 6: Coverage Analysis
|
|
1615
|
+
if (results.testing.coverage) {
|
|
1616
|
+
results.coverage = results.testing.coverage;
|
|
1617
|
+
|
|
1618
|
+
if (results.coverage.lines) {
|
|
1619
|
+
const coveragePercent = results.coverage.lines.percentage;
|
|
1620
|
+
Logger.coverage(`Line coverage: ${coveragePercent}%`);
|
|
1621
|
+
|
|
1622
|
+
if (coveragePercent < this.minimumCoverage) {
|
|
1623
|
+
Logger.warning(`Coverage below minimum (${this.minimumCoverage}%)`);
|
|
1624
|
+
results.recommendations.push({
|
|
1625
|
+
type: 'coverage',
|
|
1626
|
+
priority: 'medium',
|
|
1627
|
+
message: `Increase test coverage from ${coveragePercent}% to ${this.minimumCoverage}%`,
|
|
1628
|
+
action: 'Add tests for uncovered lines and branches'
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
// Step 7: TDD Compliance
|
|
1635
|
+
results.tddCompliance = results.testing.tddCompliance;
|
|
1636
|
+
|
|
1637
|
+
if (results.tddCompliance) {
|
|
1638
|
+
if (!results.tddCompliance.hasTests) {
|
|
1639
|
+
Logger.error('TDD Violation: No tests found');
|
|
1640
|
+
|
|
1641
|
+
if (this.blockOnTDDViolations) {
|
|
1642
|
+
results.blocking = true;
|
|
1643
|
+
results.summary.success = false;
|
|
1644
|
+
Logger.error('BLOCKING: TDD requires tests first');
|
|
1645
|
+
}
|
|
1646
|
+
} else {
|
|
1647
|
+
Logger.success('TDD Compliance: Tests exist');
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
if (results.tddCompliance.recommendations) {
|
|
1651
|
+
results.recommendations.push(...results.tddCompliance.recommendations);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// Generate summary
|
|
1657
|
+
this.printSummary(results);
|
|
1658
|
+
|
|
1659
|
+
// Log to root file
|
|
1660
|
+
await this.logToRootFile(results);
|
|
1661
|
+
|
|
1662
|
+
return results;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
logStepResult(step, result) {
|
|
1666
|
+
if (result.success) {
|
|
1667
|
+
console.log(` ✅ ${step}: ${result.message}`);
|
|
1668
|
+
} else {
|
|
1669
|
+
console.log(` ❌ ${step}: ${result.message}`);
|
|
1670
|
+
if (result.issues || result.errors) {
|
|
1671
|
+
console.log(` ${(result.issues || result.errors).slice(0, 200)}...`);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
675
1676
|
printSummary(results) {
|
|
676
1677
|
console.log('\n' + '='.repeat(60));
|
|
677
1678
|
console.log('📊 VALIDATION SUMMARY');
|
|
678
1679
|
console.log('='.repeat(60));
|
|
679
1680
|
|
|
680
|
-
if (results.
|
|
1681
|
+
if (results.blocking) {
|
|
1682
|
+
console.log('🚫 Overall Status: BLOCKED');
|
|
1683
|
+
} else if (results.summary.success) {
|
|
681
1684
|
console.log('✅ Overall Status: PASSED');
|
|
682
1685
|
} else {
|
|
683
1686
|
console.log('❌ Overall Status: FAILED');
|
|
684
1687
|
}
|
|
685
1688
|
|
|
1689
|
+
if (this.tddMode && results.testing) {
|
|
1690
|
+
console.log(`\n🧪 TDD Phase: ${results.tddPhase.toUpperCase()}`);
|
|
1691
|
+
if (results.testing.executed && results.testing.results) {
|
|
1692
|
+
const { total, passed, failed } = results.testing.results.summary;
|
|
1693
|
+
console.log(` Tests: ${passed}/${total} passed, ${failed} failed`);
|
|
1694
|
+
}
|
|
1695
|
+
if (results.coverage && results.coverage.lines) {
|
|
1696
|
+
console.log(` Coverage: ${results.coverage.lines.percentage}%`);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
|
|
686
1700
|
if (results.summary.errors.length > 0) {
|
|
687
1701
|
console.log('\n🚨 ERRORS:');
|
|
688
1702
|
results.summary.errors.forEach(error => console.log(` • ${error}`));
|
|
@@ -693,22 +1707,52 @@ class PostEditPipeline {
|
|
|
693
1707
|
results.summary.warnings.forEach(warning => console.log(` • ${warning}`));
|
|
694
1708
|
}
|
|
695
1709
|
|
|
696
|
-
if (results.
|
|
697
|
-
console.log('\n💡
|
|
698
|
-
results.
|
|
1710
|
+
if (results.recommendations.length > 0) {
|
|
1711
|
+
console.log('\n💡 RECOMMENDATIONS:');
|
|
1712
|
+
results.recommendations.slice(0, 5).forEach((rec, i) => {
|
|
1713
|
+
console.log(` ${i + 1}. [${rec.priority.toUpperCase()}] ${rec.message}`);
|
|
1714
|
+
if (rec.action) console.log(` Action: ${rec.action}`);
|
|
1715
|
+
});
|
|
699
1716
|
}
|
|
700
1717
|
|
|
701
1718
|
console.log('='.repeat(60));
|
|
702
1719
|
}
|
|
703
1720
|
}
|
|
704
1721
|
|
|
705
|
-
//
|
|
1722
|
+
// CLI execution
|
|
706
1723
|
async function main() {
|
|
707
1724
|
const filePath = process.argv[2];
|
|
708
1725
|
|
|
709
1726
|
if (!filePath) {
|
|
710
|
-
console.
|
|
711
|
-
|
|
1727
|
+
console.log(`
|
|
1728
|
+
🔴🟢♻️ UNIFIED POST-EDIT PIPELINE - v3.0.0
|
|
1729
|
+
|
|
1730
|
+
Usage: post-edit-pipeline.js <file> [options]
|
|
1731
|
+
|
|
1732
|
+
Options:
|
|
1733
|
+
--memory-key <key> Store results with specific memory key
|
|
1734
|
+
--tdd-mode Enable TDD testing and enforcement
|
|
1735
|
+
--minimum-coverage <percent> Minimum coverage threshold (default: 80)
|
|
1736
|
+
--block-on-tdd-violations Block execution on TDD violations
|
|
1737
|
+
--rust-strict Enable strict Rust quality checks
|
|
1738
|
+
--structured Return structured JSON data
|
|
1739
|
+
|
|
1740
|
+
Examples:
|
|
1741
|
+
node post-edit-pipeline.js src/app.js --tdd-mode --minimum-coverage 90
|
|
1742
|
+
node post-edit-pipeline.js src/lib.rs --rust-strict
|
|
1743
|
+
node post-edit-pipeline.js src/test.ts --tdd-mode --block-on-tdd-violations
|
|
1744
|
+
|
|
1745
|
+
Features:
|
|
1746
|
+
✅ Progressive validation (syntax → interface → integration → full)
|
|
1747
|
+
✅ TDD enforcement with Red-Green-Refactor detection
|
|
1748
|
+
✅ Single-file testing without full compilation
|
|
1749
|
+
✅ Real-time coverage analysis
|
|
1750
|
+
✅ Rust quality enforcement (.unwrap(), panic!, todo! detection)
|
|
1751
|
+
✅ Multi-language support (JS, TS, Python, Rust, Go, Java, C/C++)
|
|
1752
|
+
✅ Security scanning and dependency analysis
|
|
1753
|
+
✅ Agent coordination and memory storage
|
|
1754
|
+
`);
|
|
1755
|
+
return;
|
|
712
1756
|
}
|
|
713
1757
|
|
|
714
1758
|
if (!fs.existsSync(filePath)) {
|
|
@@ -716,10 +1760,10 @@ async function main() {
|
|
|
716
1760
|
process.exit(1);
|
|
717
1761
|
}
|
|
718
1762
|
|
|
719
|
-
// Parse command-line options for agent context
|
|
720
|
-
const options = {};
|
|
721
1763
|
const args = process.argv.slice(3);
|
|
1764
|
+
const options = {};
|
|
722
1765
|
|
|
1766
|
+
// Parse command-line options
|
|
723
1767
|
for (let i = 0; i < args.length; i++) {
|
|
724
1768
|
if (args[i] === '--memory-key' && args[i + 1]) {
|
|
725
1769
|
options.memoryKey = args[i + 1];
|
|
@@ -727,26 +1771,29 @@ async function main() {
|
|
|
727
1771
|
} else if (args[i] === '--agent-type' && args[i + 1]) {
|
|
728
1772
|
options.agentType = args[i + 1];
|
|
729
1773
|
i++;
|
|
730
|
-
} else if (args[i] === '--
|
|
731
|
-
options.
|
|
732
|
-
i++;
|
|
733
|
-
} else if (args[i] === '--swarm-id' && args[i + 1]) {
|
|
734
|
-
options.swarmId = args[i + 1];
|
|
735
|
-
i++;
|
|
736
|
-
} else if (args[i] === '--task-id' && args[i + 1]) {
|
|
737
|
-
options.taskId = args[i + 1];
|
|
738
|
-
i++;
|
|
739
|
-
} else if (args[i] === '--session-id' && args[i + 1]) {
|
|
740
|
-
options.sessionId = args[i + 1];
|
|
1774
|
+
} else if (args[i] === '--minimum-coverage' && args[i + 1]) {
|
|
1775
|
+
options.minimumCoverage = parseInt(args[i + 1]) || 80;
|
|
741
1776
|
i++;
|
|
742
1777
|
}
|
|
743
1778
|
}
|
|
744
1779
|
|
|
745
|
-
const
|
|
1780
|
+
const pipelineOptions = {
|
|
1781
|
+
tddMode: args.includes('--tdd-mode'),
|
|
1782
|
+
minimumCoverage: options.minimumCoverage || 80,
|
|
1783
|
+
blockOnTDDViolations: args.includes('--block-on-tdd-violations'),
|
|
1784
|
+
rustStrict: args.includes('--rust-strict'),
|
|
1785
|
+
structured: args.includes('--structured')
|
|
1786
|
+
};
|
|
1787
|
+
|
|
1788
|
+
const pipeline = new UnifiedPostEditPipeline(pipelineOptions);
|
|
746
1789
|
const results = await pipeline.run(filePath, options);
|
|
747
1790
|
|
|
748
|
-
|
|
749
|
-
|
|
1791
|
+
if (pipelineOptions.structured) {
|
|
1792
|
+
console.log('\n📋 STRUCTURED OUTPUT:');
|
|
1793
|
+
console.log(JSON.stringify(results, null, 2));
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
process.exit(results.summary.success && !results.blocking ? 0 : 1);
|
|
750
1797
|
}
|
|
751
1798
|
|
|
752
1799
|
// Run if called directly
|
|
@@ -757,4 +1804,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
757
1804
|
});
|
|
758
1805
|
}
|
|
759
1806
|
|
|
760
|
-
export
|
|
1807
|
+
export { UnifiedPostEditPipeline, SingleFileTestEngine, RustQualityEnforcer };
|