agentic-qe 3.8.8 → 3.8.9

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.
@@ -5,6 +5,12 @@
5
5
  * - LCOV format (from Istanbul, nyc, c8)
6
6
  * - Cobertura XML format
7
7
  * - JSON format (Istanbul/vitest)
8
+ * - JaCoCo XML format (Java/Kotlin)
9
+ * - dotcover XML format (C#/.NET)
10
+ * - Tarpaulin JSON format (Rust)
11
+ * - Go cover text format (Go)
12
+ * - Kover XML format (Kotlin/JVM)
13
+ * - xcresult JSON format (Swift/iOS)
8
14
  *
9
15
  * This is NOT a simulation - it reads and parses real coverage data.
10
16
  *
@@ -319,29 +325,560 @@ export async function parseJSONCoverage(jsonPath, projectRoot) {
319
325
  };
320
326
  }
321
327
  // ============================================================================
328
+ // JaCoCo XML Parser (Java/Kotlin)
329
+ // ============================================================================
330
+ /**
331
+ * Parse JaCoCo XML coverage data.
332
+ *
333
+ * JaCoCo XML structure:
334
+ * <report><package><class><method><counter type="LINE" missed="X" covered="Y"/></method></class></package></report>
335
+ */
336
+ export async function parseJaCoCo(xmlPath, projectRoot) {
337
+ const content = await fs.readFile(xmlPath, 'utf-8');
338
+ return parseJaCoCoContent(content, projectRoot || path.dirname(xmlPath));
339
+ }
340
+ export function parseJaCoCoContent(content, projectRoot) {
341
+ const files = new Map();
342
+ // Parse <package> blocks
343
+ const packagePattern = /<package\s+name="([^"]*)">([\s\S]*?)<\/package>/g;
344
+ let pkgMatch;
345
+ while ((pkgMatch = packagePattern.exec(content)) !== null) {
346
+ const pkgName = pkgMatch[1].replace(/\//g, path.sep);
347
+ const pkgBody = pkgMatch[2];
348
+ // Parse <sourcefile> blocks within package
349
+ const sfPattern = /<sourcefile\s+name="([^"]*)">([\s\S]*?)<\/sourcefile>/g;
350
+ let sfMatch;
351
+ while ((sfMatch = sfPattern.exec(pkgBody)) !== null) {
352
+ const fileName = sfMatch[1];
353
+ const sfBody = sfMatch[2];
354
+ const filePath = path.join(projectRoot, pkgName, fileName);
355
+ // Parse <counter> elements
356
+ const counters = parseJaCoCoCounters(sfBody);
357
+ const lineDetails = new Map();
358
+ // Parse <line> elements for detailed line data
359
+ const linePattern = /<line\s+nr="(\d+)"\s+mi="(\d+)"\s+ci="(\d+)"/g;
360
+ let lineMatch;
361
+ while ((lineMatch = linePattern.exec(sfBody)) !== null) {
362
+ const lineNum = parseInt(lineMatch[1], 10);
363
+ const ci = parseInt(lineMatch[3], 10);
364
+ lineDetails.set(lineNum, ci);
365
+ }
366
+ const uncoveredLines = Array.from(lineDetails.entries())
367
+ .filter(([, hits]) => hits === 0)
368
+ .map(([line]) => line)
369
+ .sort((a, b) => a - b);
370
+ files.set(filePath, {
371
+ path: filePath,
372
+ relativePath: path.join(pkgName, fileName),
373
+ lines: {
374
+ total: counters.LINE.total,
375
+ covered: counters.LINE.covered,
376
+ percentage: counters.LINE.total > 0 ? (counters.LINE.covered / counters.LINE.total) * 100 : 0,
377
+ details: lineDetails,
378
+ uncoveredLines,
379
+ },
380
+ branches: {
381
+ total: counters.BRANCH.total,
382
+ covered: counters.BRANCH.covered,
383
+ percentage: counters.BRANCH.total > 0 ? (counters.BRANCH.covered / counters.BRANCH.total) * 100 : 0,
384
+ uncoveredBranches: [],
385
+ },
386
+ functions: {
387
+ total: counters.METHOD.total,
388
+ covered: counters.METHOD.covered,
389
+ percentage: counters.METHOD.total > 0 ? (counters.METHOD.covered / counters.METHOD.total) * 100 : 0,
390
+ details: [],
391
+ uncoveredFunctions: [],
392
+ },
393
+ statements: {
394
+ total: counters.INSTRUCTION.total,
395
+ covered: counters.INSTRUCTION.covered,
396
+ percentage: counters.INSTRUCTION.total > 0 ? (counters.INSTRUCTION.covered / counters.INSTRUCTION.total) * 100 : 0,
397
+ },
398
+ coveragePercentage: calculateOverallPercentage(counters.LINE.covered, counters.LINE.total, counters.BRANCH.covered, counters.BRANCH.total),
399
+ });
400
+ }
401
+ }
402
+ return {
403
+ timestamp: new Date(),
404
+ format: 'jacoco',
405
+ language: 'java',
406
+ files,
407
+ summary: calculateSummary(files),
408
+ };
409
+ }
410
+ function parseJaCoCoCounters(xml) {
411
+ const result = {
412
+ LINE: { covered: 0, total: 0 },
413
+ BRANCH: { covered: 0, total: 0 },
414
+ METHOD: { covered: 0, total: 0 },
415
+ INSTRUCTION: { covered: 0, total: 0 },
416
+ };
417
+ const counterPattern = /<counter\s+type="(\w+)"\s+missed="(\d+)"\s+covered="(\d+)"\s*\/>/g;
418
+ let m;
419
+ while ((m = counterPattern.exec(xml)) !== null) {
420
+ const type = m[1];
421
+ const missed = parseInt(m[2], 10);
422
+ const covered = parseInt(m[3], 10);
423
+ if (result[type]) {
424
+ result[type].covered += covered;
425
+ result[type].total += missed + covered;
426
+ }
427
+ }
428
+ return result;
429
+ }
430
+ // ============================================================================
431
+ // dotcover XML Parser (C#/.NET)
432
+ // ============================================================================
433
+ /**
434
+ * Parse dotcover XML coverage data.
435
+ *
436
+ * dotcover XML structure:
437
+ * <Root CoveredStatements="X" TotalStatements="Y"><Assembly><Namespace><Type><Member Statement="..."/></Type></Namespace></Assembly></Root>
438
+ */
439
+ export async function parseDotcover(xmlPath, projectRoot) {
440
+ const content = await fs.readFile(xmlPath, 'utf-8');
441
+ return parseDotcoverContent(content, projectRoot || path.dirname(xmlPath));
442
+ }
443
+ export function parseDotcoverContent(content, projectRoot) {
444
+ const files = new Map();
445
+ // Parse <File> elements to build file path index
446
+ const fileIndex = new Map();
447
+ const filePattern = /<File\s+Index="(\d+)"\s+Name="([^"]*)"\s*\/>/g;
448
+ let fm;
449
+ while ((fm = filePattern.exec(content)) !== null) {
450
+ fileIndex.set(fm[1], fm[2]);
451
+ }
452
+ // Parse <Statement> elements to build per-file coverage
453
+ const stmtPattern = /<Statement\s+FileIndex="(\d+)"\s+Line="(\d+)"\s+Column="\d+"\s+EndLine="\d+"\s+EndColumn="\d+"\s+Covered="(\w+)"\s*\/>/g;
454
+ const fileData = new Map();
455
+ let sm;
456
+ while ((sm = stmtPattern.exec(content)) !== null) {
457
+ const fileIdx = sm[1];
458
+ const lineNum = parseInt(sm[2], 10);
459
+ const isCovered = sm[3] === 'True';
460
+ const filePath = fileIndex.get(fileIdx) || `file-${fileIdx}`;
461
+ if (!fileData.has(filePath)) {
462
+ fileData.set(filePath, { lineDetails: new Map(), total: 0, covered: 0 });
463
+ }
464
+ const data = fileData.get(filePath);
465
+ data.lineDetails.set(lineNum, isCovered ? 1 : 0);
466
+ data.total++;
467
+ if (isCovered)
468
+ data.covered++;
469
+ }
470
+ // If no Statement elements found, parse from CoveredStatements/TotalStatements attributes
471
+ if (fileData.size === 0) {
472
+ const rootPattern = /CoveredStatements="(\d+)"\s+TotalStatements="(\d+)"/;
473
+ const rootMatch = rootPattern.exec(content);
474
+ if (rootMatch) {
475
+ const covered = parseInt(rootMatch[1], 10);
476
+ const total = parseInt(rootMatch[2], 10);
477
+ const filePath = path.join(projectRoot, 'aggregate');
478
+ files.set(filePath, {
479
+ path: filePath,
480
+ relativePath: 'aggregate',
481
+ lines: { total, covered, percentage: total > 0 ? (covered / total) * 100 : 0, details: new Map(), uncoveredLines: [] },
482
+ branches: { total: 0, covered: 0, percentage: 0, uncoveredBranches: [] },
483
+ functions: { total: 0, covered: 0, percentage: 0, details: [], uncoveredFunctions: [] },
484
+ statements: { total, covered, percentage: total > 0 ? (covered / total) * 100 : 0 },
485
+ coveragePercentage: total > 0 ? (covered / total) * 100 : 0,
486
+ });
487
+ }
488
+ }
489
+ for (const [filePath, data] of fileData.entries()) {
490
+ const uncoveredLines = Array.from(data.lineDetails.entries())
491
+ .filter(([, hits]) => hits === 0)
492
+ .map(([line]) => line)
493
+ .sort((a, b) => a - b);
494
+ files.set(filePath, {
495
+ path: filePath,
496
+ relativePath: path.relative(projectRoot, filePath),
497
+ lines: {
498
+ total: data.lineDetails.size,
499
+ covered: Array.from(data.lineDetails.values()).filter(h => h > 0).length,
500
+ percentage: data.lineDetails.size > 0 ? (Array.from(data.lineDetails.values()).filter(h => h > 0).length / data.lineDetails.size) * 100 : 0,
501
+ details: data.lineDetails,
502
+ uncoveredLines,
503
+ },
504
+ branches: { total: 0, covered: 0, percentage: 0, uncoveredBranches: [] },
505
+ functions: { total: 0, covered: 0, percentage: 0, details: [], uncoveredFunctions: [] },
506
+ statements: {
507
+ total: data.total,
508
+ covered: data.covered,
509
+ percentage: data.total > 0 ? (data.covered / data.total) * 100 : 0,
510
+ },
511
+ coveragePercentage: data.total > 0 ? (data.covered / data.total) * 100 : 0,
512
+ });
513
+ }
514
+ return {
515
+ timestamp: new Date(),
516
+ format: 'dotcover',
517
+ language: 'csharp',
518
+ files,
519
+ summary: calculateSummary(files),
520
+ };
521
+ }
522
+ // ============================================================================
523
+ // Tarpaulin JSON Parser (Rust)
524
+ // ============================================================================
525
+ /**
526
+ * Parse Tarpaulin JSON coverage data.
527
+ *
528
+ * Tarpaulin JSON structure (--output-dir with --out Json):
529
+ * { "files": [ { "path": "...", "content": "...", "traces": [ { "line": N, "stats": { "Line": N } } ] } ] }
530
+ *
531
+ * Also handles tarpaulin LCOV output by delegating to parseLCOVContent.
532
+ */
533
+ export async function parseTarpaulin(filePath, projectRoot) {
534
+ const content = await fs.readFile(filePath, 'utf-8');
535
+ const root = projectRoot || path.dirname(filePath);
536
+ // If it looks like LCOV, delegate
537
+ if (content.includes('SF:') && content.includes('end_of_record')) {
538
+ const report = parseLCOVContent(content, root);
539
+ report.format = 'tarpaulin';
540
+ report.language = 'rust';
541
+ return report;
542
+ }
543
+ return parseTarpaulinContent(content, root);
544
+ }
545
+ export function parseTarpaulinContent(content, projectRoot) {
546
+ const files = new Map();
547
+ const data = safeJsonParse(content);
548
+ for (const fileEntry of data?.files || []) {
549
+ const filePath = fileEntry.path || '';
550
+ const lineDetails = new Map();
551
+ let linesTotal = 0;
552
+ let linesCovered = 0;
553
+ for (const trace of fileEntry.traces || []) {
554
+ const lineNum = trace.line;
555
+ const hits = trace.stats?.Line ?? (trace.stats?.line ?? 0);
556
+ if (typeof lineNum === 'number') {
557
+ lineDetails.set(lineNum, hits);
558
+ linesTotal++;
559
+ if (hits > 0)
560
+ linesCovered++;
561
+ }
562
+ }
563
+ const uncoveredLines = Array.from(lineDetails.entries())
564
+ .filter(([, hits]) => hits === 0)
565
+ .map(([line]) => line)
566
+ .sort((a, b) => a - b);
567
+ files.set(filePath, {
568
+ path: filePath,
569
+ relativePath: path.relative(projectRoot, filePath),
570
+ lines: {
571
+ total: linesTotal,
572
+ covered: linesCovered,
573
+ percentage: linesTotal > 0 ? (linesCovered / linesTotal) * 100 : 0,
574
+ details: lineDetails,
575
+ uncoveredLines,
576
+ },
577
+ branches: { total: 0, covered: 0, percentage: 0, uncoveredBranches: [] },
578
+ functions: { total: 0, covered: 0, percentage: 0, details: [], uncoveredFunctions: [] },
579
+ statements: {
580
+ total: linesTotal,
581
+ covered: linesCovered,
582
+ percentage: linesTotal > 0 ? (linesCovered / linesTotal) * 100 : 0,
583
+ },
584
+ coveragePercentage: linesTotal > 0 ? (linesCovered / linesTotal) * 100 : 0,
585
+ });
586
+ }
587
+ return {
588
+ timestamp: new Date(),
589
+ format: 'tarpaulin',
590
+ language: 'rust',
591
+ files,
592
+ summary: calculateSummary(files),
593
+ };
594
+ }
595
+ // ============================================================================
596
+ // Go cover Parser
597
+ // ============================================================================
598
+ /**
599
+ * Parse Go coverage profile text format.
600
+ *
601
+ * Format:
602
+ * mode: set|count|atomic
603
+ * pkg/file.go:startLine.startCol,endLine.endCol numStatements count
604
+ */
605
+ export async function parseGoCover(coverPath, projectRoot) {
606
+ const content = await fs.readFile(coverPath, 'utf-8');
607
+ return parseGoCoverContent(content, projectRoot || path.dirname(coverPath));
608
+ }
609
+ export function parseGoCoverContent(content, projectRoot) {
610
+ const files = new Map();
611
+ const fileData = new Map();
612
+ const lines = content.split('\n');
613
+ for (const line of lines) {
614
+ const trimmed = line.trim();
615
+ if (!trimmed || trimmed.startsWith('mode:'))
616
+ continue;
617
+ // Format: file:startLine.startCol,endLine.endCol numStatements count
618
+ const match = trimmed.match(/^(.+):(\d+)\.\d+,(\d+)\.\d+\s+(\d+)\s+(\d+)$/);
619
+ if (!match)
620
+ continue;
621
+ const filePath = match[1];
622
+ const startLine = parseInt(match[2], 10);
623
+ const endLine = parseInt(match[3], 10);
624
+ const numStmts = parseInt(match[4], 10);
625
+ const count = parseInt(match[5], 10);
626
+ if (!fileData.has(filePath)) {
627
+ fileData.set(filePath, { lineDetails: new Map(), stmtTotal: 0, stmtCovered: 0 });
628
+ }
629
+ const data = fileData.get(filePath);
630
+ // Mark each line in the range
631
+ for (let l = startLine; l <= endLine; l++) {
632
+ const existing = data.lineDetails.get(l) || 0;
633
+ data.lineDetails.set(l, existing + count);
634
+ }
635
+ data.stmtTotal += numStmts;
636
+ if (count > 0)
637
+ data.stmtCovered += numStmts;
638
+ }
639
+ for (const [filePath, data] of fileData.entries()) {
640
+ const linesTotal = data.lineDetails.size;
641
+ const linesCovered = Array.from(data.lineDetails.values()).filter(h => h > 0).length;
642
+ const uncoveredLines = Array.from(data.lineDetails.entries())
643
+ .filter(([, hits]) => hits === 0)
644
+ .map(([line]) => line)
645
+ .sort((a, b) => a - b);
646
+ files.set(filePath, {
647
+ path: filePath,
648
+ relativePath: path.relative(projectRoot, filePath),
649
+ lines: {
650
+ total: linesTotal,
651
+ covered: linesCovered,
652
+ percentage: linesTotal > 0 ? (linesCovered / linesTotal) * 100 : 0,
653
+ details: data.lineDetails,
654
+ uncoveredLines,
655
+ },
656
+ branches: { total: 0, covered: 0, percentage: 0, uncoveredBranches: [] },
657
+ functions: { total: 0, covered: 0, percentage: 0, details: [], uncoveredFunctions: [] },
658
+ statements: {
659
+ total: data.stmtTotal,
660
+ covered: data.stmtCovered,
661
+ percentage: data.stmtTotal > 0 ? (data.stmtCovered / data.stmtTotal) * 100 : 0,
662
+ },
663
+ coveragePercentage: data.stmtTotal > 0 ? (data.stmtCovered / data.stmtTotal) * 100 : 0,
664
+ });
665
+ }
666
+ return {
667
+ timestamp: new Date(),
668
+ format: 'gocover',
669
+ language: 'go',
670
+ files,
671
+ summary: calculateSummary(files),
672
+ };
673
+ }
674
+ // ============================================================================
675
+ // Kover XML Parser (Kotlin/JVM)
676
+ // ============================================================================
677
+ /**
678
+ * Parse Kover XML coverage data.
679
+ *
680
+ * Kover outputs JaCoCo-compatible XML. This delegates to the JaCoCo parser
681
+ * but sets the format and language appropriately.
682
+ */
683
+ export async function parseKover(xmlPath, projectRoot) {
684
+ const content = await fs.readFile(xmlPath, 'utf-8');
685
+ const report = parseJaCoCoContent(content, projectRoot || path.dirname(xmlPath));
686
+ report.format = 'kover';
687
+ report.language = 'kotlin';
688
+ return report;
689
+ }
690
+ // ============================================================================
691
+ // xcresult JSON Parser (Swift/iOS)
692
+ // ============================================================================
693
+ /**
694
+ * Parse xcresult coverage data exported via:
695
+ * xcrun xccov view --report --json result.xcresult > coverage.json
696
+ *
697
+ * Structure:
698
+ * { "targets": [ { "files": [ { "path": "...", "lineCoverage": 0.85, "coveredLines": N, "executableLines": N, "functions": [...] } ] } ] }
699
+ */
700
+ export async function parseXcresult(jsonPath, projectRoot) {
701
+ const content = await fs.readFile(jsonPath, 'utf-8');
702
+ return parseXcresultContent(content, projectRoot || path.dirname(jsonPath));
703
+ }
704
+ export function parseXcresultContent(content, projectRoot) {
705
+ const files = new Map();
706
+ const data = safeJsonParse(content);
707
+ const targets = data?.targets || [];
708
+ for (const target of targets) {
709
+ for (const fileEntry of target.files || []) {
710
+ const filePath = fileEntry.path || '';
711
+ const executableLines = fileEntry.executableLines || 0;
712
+ const coveredLines = fileEntry.coveredLines || 0;
713
+ const lineCoverage = fileEntry.lineCoverage || 0;
714
+ // Parse function data if available
715
+ const functions = [];
716
+ let fnTotal = 0;
717
+ let fnCovered = 0;
718
+ for (const fn of fileEntry.functions || []) {
719
+ const hits = fn.coveredLines > 0 ? 1 : 0;
720
+ functions.push({
721
+ name: fn.name || 'unknown',
722
+ line: fn.lineNumber || 0,
723
+ hits,
724
+ });
725
+ fnTotal++;
726
+ if (hits > 0)
727
+ fnCovered++;
728
+ }
729
+ files.set(filePath, {
730
+ path: filePath,
731
+ relativePath: path.relative(projectRoot, filePath),
732
+ lines: {
733
+ total: executableLines,
734
+ covered: coveredLines,
735
+ percentage: lineCoverage * 100,
736
+ details: new Map(),
737
+ uncoveredLines: [],
738
+ },
739
+ branches: { total: 0, covered: 0, percentage: 0, uncoveredBranches: [] },
740
+ functions: {
741
+ total: fnTotal,
742
+ covered: fnCovered,
743
+ percentage: fnTotal > 0 ? (fnCovered / fnTotal) * 100 : 0,
744
+ details: functions,
745
+ uncoveredFunctions: functions.filter(f => f.hits === 0).map(f => f.name),
746
+ },
747
+ statements: {
748
+ total: executableLines,
749
+ covered: coveredLines,
750
+ percentage: lineCoverage * 100,
751
+ },
752
+ coveragePercentage: lineCoverage * 100,
753
+ });
754
+ }
755
+ }
756
+ return {
757
+ timestamp: new Date(),
758
+ format: 'xcresult',
759
+ language: 'swift',
760
+ files,
761
+ summary: calculateSummary(files),
762
+ };
763
+ }
764
+ // ============================================================================
322
765
  // Auto-Detect Parser
323
766
  // ============================================================================
324
767
  /**
325
- * Auto-detect coverage format and parse accordingly
768
+ * Language-to-format mapping for inference when format is not specified
769
+ */
770
+ const LANGUAGE_FORMAT_HINTS = {
771
+ java: ['jacoco', 'cobertura', 'lcov'],
772
+ kotlin: ['kover', 'jacoco'],
773
+ csharp: ['dotcover', 'cobertura'],
774
+ rust: ['tarpaulin', 'lcov'],
775
+ go: ['gocover'],
776
+ swift: ['xcresult'],
777
+ dart: ['lcov'],
778
+ typescript: ['lcov', 'json'],
779
+ javascript: ['lcov', 'json'],
780
+ python: ['lcov', 'cobertura', 'json'],
781
+ };
782
+ /**
783
+ * Auto-detect coverage format and parse accordingly.
784
+ *
785
+ * @param coveragePath - Path to the coverage file
786
+ * @param projectRoot - Project root for relative path calculation
787
+ * @param format - Explicit format override
788
+ * @param language - Language hint for format inference
326
789
  */
327
- export async function parseCoverage(coveragePath, projectRoot) {
790
+ export async function parseCoverage(coveragePath, projectRoot, format, language) {
791
+ // If format is explicitly specified, use it directly
792
+ if (format) {
793
+ switch (format) {
794
+ case 'lcov': return parseLCOV(coveragePath, projectRoot);
795
+ case 'json': return parseJSONCoverage(coveragePath, projectRoot);
796
+ case 'jacoco': return parseJaCoCo(coveragePath, projectRoot);
797
+ case 'dotcover': return parseDotcover(coveragePath, projectRoot);
798
+ case 'tarpaulin': return parseTarpaulin(coveragePath, projectRoot);
799
+ case 'gocover': return parseGoCover(coveragePath, projectRoot);
800
+ case 'kover': return parseKover(coveragePath, projectRoot);
801
+ case 'xcresult': return parseXcresult(coveragePath, projectRoot);
802
+ default: break;
803
+ }
804
+ }
328
805
  const ext = path.extname(coveragePath).toLowerCase();
329
806
  const basename = path.basename(coveragePath).toLowerCase();
330
- // Try to detect format
807
+ // Detect by filename/extension
331
808
  if (ext === '.json' || basename.includes('coverage-final')) {
332
809
  return parseJSONCoverage(coveragePath, projectRoot);
333
810
  }
334
811
  if (basename === 'lcov.info' || ext === '.info' || basename.includes('lcov')) {
335
812
  return parseLCOV(coveragePath, projectRoot);
336
813
  }
337
- // Try to read and detect
814
+ if (basename.includes('jacoco') && ext === '.xml') {
815
+ return parseJaCoCo(coveragePath, projectRoot);
816
+ }
817
+ if (basename.includes('dotcover') && ext === '.xml') {
818
+ return parseDotcover(coveragePath, projectRoot);
819
+ }
820
+ if (basename.includes('tarpaulin')) {
821
+ return parseTarpaulin(coveragePath, projectRoot);
822
+ }
823
+ if (basename.includes('kover') && ext === '.xml') {
824
+ return parseKover(coveragePath, projectRoot);
825
+ }
826
+ if (basename.includes('coverage') && ext === '.out') {
827
+ return parseGoCover(coveragePath, projectRoot);
828
+ }
829
+ if (basename.includes('xcresult') || basename.includes('xccov')) {
830
+ return parseXcresult(coveragePath, projectRoot);
831
+ }
832
+ // Content-based detection
338
833
  const content = await fs.readFile(coveragePath, 'utf-8');
339
- if (content.trim().startsWith('{')) {
834
+ const trimmed = content.trim();
835
+ if (trimmed.startsWith('{')) {
836
+ // Check for tarpaulin JSON structure
837
+ if (trimmed.includes('"files"') && trimmed.includes('"traces"')) {
838
+ return parseTarpaulinContent(content, projectRoot || path.dirname(coveragePath));
839
+ }
840
+ // Check for xcresult JSON structure
841
+ if (trimmed.includes('"targets"') && trimmed.includes('"lineCoverage"')) {
842
+ return parseXcresultContent(content, projectRoot || path.dirname(coveragePath));
843
+ }
340
844
  return parseJSONCoverage(coveragePath, projectRoot);
341
845
  }
342
846
  if (content.includes('SF:') && content.includes('end_of_record')) {
343
847
  return parseLCOV(coveragePath, projectRoot);
344
848
  }
849
+ // Go cover format: starts with "mode:"
850
+ if (trimmed.startsWith('mode:')) {
851
+ return parseGoCoverContent(content, projectRoot || path.dirname(coveragePath));
852
+ }
853
+ // XML-based detection
854
+ if (trimmed.startsWith('<?xml') || trimmed.startsWith('<')) {
855
+ if (content.includes('<counter type="') || content.includes('<report ')) {
856
+ // Differentiate JaCoCo vs Kover using language hint
857
+ if (language === 'kotlin') {
858
+ const report = parseJaCoCoContent(content, projectRoot || path.dirname(coveragePath));
859
+ report.format = 'kover';
860
+ report.language = 'kotlin';
861
+ return report;
862
+ }
863
+ return parseJaCoCoContent(content, projectRoot || path.dirname(coveragePath));
864
+ }
865
+ if (content.includes('CoveredStatements=') || content.includes('<Root')) {
866
+ return parseDotcoverContent(content, projectRoot || path.dirname(coveragePath));
867
+ }
868
+ }
869
+ // Language-based fallback: try the first format associated with the language
870
+ if (language) {
871
+ const langLower = language.toLowerCase();
872
+ const hints = LANGUAGE_FORMAT_HINTS[langLower];
873
+ if (hints && hints.length > 0) {
874
+ try {
875
+ return await parseCoverage(coveragePath, projectRoot, hints[0]);
876
+ }
877
+ catch {
878
+ // Language hint didn't help, fall through to error
879
+ }
880
+ }
881
+ }
345
882
  throw new Error(`Unknown coverage format: ${coveragePath}`);
346
883
  }
347
884
  /**
@@ -349,12 +886,28 @@ export async function parseCoverage(coveragePath, projectRoot) {
349
886
  */
350
887
  export async function findAndParseCoverage(searchDir, projectRoot) {
351
888
  const root = projectRoot || searchDir;
352
- // Common coverage file locations
889
+ // Common coverage file locations (multi-language)
353
890
  const candidates = [
891
+ // JS/TS (Istanbul, nyc, c8, vitest)
354
892
  path.join(searchDir, 'coverage', 'lcov.info'),
355
893
  path.join(searchDir, 'coverage', 'coverage-final.json'),
356
894
  path.join(searchDir, 'lcov.info'),
357
895
  path.join(searchDir, '.nyc_output', 'coverage.json'),
896
+ // Java (JaCoCo)
897
+ path.join(searchDir, 'target', 'site', 'jacoco', 'jacoco.xml'),
898
+ path.join(searchDir, 'build', 'reports', 'jacoco', 'test', 'jacocoTestReport.xml'),
899
+ // C# (dotcover)
900
+ path.join(searchDir, 'dotcover-report.xml'),
901
+ // Rust (tarpaulin)
902
+ path.join(searchDir, 'tarpaulin-report.json'),
903
+ path.join(searchDir, 'cobertura.xml'),
904
+ // Go
905
+ path.join(searchDir, 'coverage.out'),
906
+ path.join(searchDir, 'cover.out'),
907
+ // Kotlin (Kover)
908
+ path.join(searchDir, 'build', 'reports', 'kover', 'report.xml'),
909
+ // Swift (xcresult)
910
+ path.join(searchDir, 'coverage.json'),
358
911
  ];
359
912
  for (const candidate of candidates) {
360
913
  try {
@@ -1,4 +1,4 @@
1
- import { randomUUID } from 'node:crypto';
1
+ import { randomUUID, createHash } from 'node:crypto';
2
2
  import { safeJsonParse } from '../shared/safe-json.js';
3
3
  /**
4
4
  * Proof Envelope Integration for Agentic QE Fleet
@@ -48,17 +48,23 @@ export class ProofEnvelopeIntegration {
48
48
  *
49
49
  * @param signingKey - Key used for signing envelopes
50
50
  */
51
- async initialize(signingKey = 'agentic-qe-default-key') {
51
+ async initialize(signingKey) {
52
52
  if (this.initialized)
53
53
  return;
54
54
  await this.kernel.initialize();
55
- this.signingKey = signingKey;
55
+ // Derive a deterministic per-installation signing key from the project
56
+ // directory. This is consistent across restarts (so envelopes remain
57
+ // verifiable) but unique per installation (unlike a global hardcoded key).
58
+ // For production use, pass an explicit key managed outside the process.
59
+ this.signingKey = signingKey || createHash('sha256')
60
+ .update(`aqe-proof-envelope:${process.cwd()}`)
61
+ .digest('hex');
56
62
  // Try loading guidance ProofChain for parallel audit trail
57
63
  try {
58
64
  const modulePath = '@claude-flow/guidance/proof';
59
65
  const mod = await import(/* @vite-ignore */ modulePath);
60
66
  if (mod && typeof mod.createProofChain === 'function') {
61
- this.guidanceProofChain = mod.createProofChain({ signingKey });
67
+ this.guidanceProofChain = mod.createProofChain({ signingKey: this.signingKey });
62
68
  console.log('[ProofEnvelopeIntegration] Guidance ProofChain loaded');
63
69
  }
64
70
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Agentic QE v3 - Witness Engine Adapter
3
3
  *
4
- * Wraps the Prime Radiant WitnessEngine for Blake3 witness chain operations.
4
+ * Wraps the Prime Radiant WitnessEngine for SHA-256 witness chain operations.
5
5
  * Used for creating tamper-evident audit trails of agent decisions.
6
6
  *
7
- * Blake3 Witness Chains:
8
- * - Each decision is hashed with Blake3
7
+ * Witness Chains:
8
+ * - Each decision is hashed with SHA-256
9
9
  * - Hash includes reference to previous witness
10
10
  * - Creates immutable audit trail
11
11
  * - Enables deterministic replay
@@ -94,8 +94,8 @@ export declare class WitnessAdapter implements IWitnessAdapter {
94
94
  */
95
95
  private createFallbackEngine;
96
96
  /**
97
- * Compute a hash for witness creation
98
- * Uses a simple hash when crypto is not available
97
+ * Compute a SHA-256 hash for witness creation.
98
+ * Chains the previous hash into the computation for tamper-evidence.
99
99
  */
100
100
  private computeHash;
101
101
  /**