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.
@@ -1,20 +1,1164 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Post-Edit Validation Pipeline
5
- * Comprehensive validation, formatting, and quality checks after file edits
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
- class PostEditPipeline {
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
- const { code } = await this.runCommand('which', [tool]);
128
- return code === 0;
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 ? 'Dependencies OK' : 'Dependency issues found',
201
- output: result.stdout + result.stderr,
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 runTests(filePath, language) {
207
- const testCommands = this.config.testCommands[language];
208
- if (!testCommands) return { success: true, message: 'No test command configured' };
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] = testCommands;
1279
+ const [tool, ...args] = linters;
211
1280
  if (!(await this.checkToolAvailable(tool))) {
212
- return { success: false, message: `Test runner ${tool} not available` };
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, projectDir);
228
-
1284
+ const result = await this.runCommand(tool, [...args, filePath]);
229
1285
  return {
230
1286
  success: result.code === 0,
231
- message: result.code === 0 ? 'Tests passed' : 'Tests failed',
1287
+ message: result.code === 0 ? 'Linting passed' : 'Linting issues found',
232
1288
  output: result.stdout + result.stderr,
233
- failures: result.code !== 0 ? result.stderr : ''
1289
+ issues: result.code !== 0 ? result.stderr : ''
234
1290
  };
235
1291
  }
236
1292
 
237
- async securityScan(filePath, language) {
238
- const scanners = this.config.securityScanners[language];
239
- if (!scanners) return { success: true, message: 'No security scanner configured' };
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] = scanners;
1297
+ const [tool, ...args] = typeCheckers;
242
1298
  if (!(await this.checkToolAvailable(tool))) {
243
- return { success: false, message: `Security scanner ${tool} not available` };
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 ? 'Security scan passed' : 'Security issues found',
1307
+ message: result.code === 0 ? 'Type checking passed' : 'Type errors found',
252
1308
  output: result.stdout + result.stderr,
253
- vulnerabilities: result.code !== 0 ? result.stderr : ''
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
- suggestions: results.summary.suggestions.length,
537
- steps: {
538
- formatting: results.steps.formatting?.success || false,
539
- linting: results.steps.linting?.success || false,
540
- typeCheck: results.steps.typeCheck?.success || false,
541
- dependencies: results.steps.dependencies?.success || false,
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 jsonMatch = section.match(/JSON:\s*(\{[\s\S]*?\})\s*$/m);
603
- if (jsonMatch) {
604
- try {
605
- const entry = JSON.parse(jsonMatch[1]);
606
- existingEntries.push(entry);
607
- } catch (e) {
608
- // Skip malformed entries
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
- const formattedEntry = [
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.memoryKey || 'N/A'}`,
634
- ` Agent Type: ${entry.agent.agentType || 'N/A'}`,
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.summary.success) {
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.summary.suggestions.length > 0) {
697
- console.log('\n💡 SUGGESTIONS:');
698
- results.summary.suggestions.forEach(suggestion => console.log(` • ${suggestion}`));
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
- // Hook execution
1722
+ // CLI execution
706
1723
  async function main() {
707
1724
  const filePath = process.argv[2];
708
1725
 
709
1726
  if (!filePath) {
710
- console.error('Usage: post-edit-pipeline.js <file-path> [--memory-key <key>] [--agent-type <type>] [--agent-name <name>]');
711
- process.exit(1);
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] === '--agent-name' && args[i + 1]) {
731
- options.agentName = args[i + 1];
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 pipeline = new PostEditPipeline();
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
- // Exit with error code if validation failed
749
- process.exit(results.summary.success ? 0 : 1);
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 default PostEditPipeline;
1807
+ export { UnifiedPostEditPipeline, SingleFileTestEngine, RustQualityEnforcer };