ruvector-fixed / dist /core /coverage-router.js
Archie
Fix dimension/dimensions bug and positional insert/search args
40d7073
"use strict";
/**
* Coverage Router - Test coverage-aware agent routing
*
* Uses test coverage data to make smarter routing decisions:
* - Prioritize testing for uncovered code
* - Route to tester agent for low-coverage files
* - Suggest test files for modified code
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseIstanbulCoverage = parseIstanbulCoverage;
exports.findCoverageReport = findCoverageReport;
exports.getFileCoverage = getFileCoverage;
exports.suggestTests = suggestTests;
exports.shouldRouteToTester = shouldRouteToTester;
exports.getCoverageRoutingWeight = getCoverageRoutingWeight;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
/**
* Parse Istanbul/NYC JSON coverage report
*/
function parseIstanbulCoverage(coveragePath) {
const files = new Map();
const lowCoverageFiles = [];
const uncoveredFiles = [];
let totalLines = 0, coveredLines = 0;
let totalFunctions = 0, coveredFunctions = 0;
let totalBranches = 0, coveredBranches = 0;
try {
const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
for (const [file, data] of Object.entries(coverage)) {
// Skip test files
if (file.includes('.test.') || file.includes('.spec.') || file.includes('__tests__')) {
continue;
}
// Parse statement coverage
const statements = Object.values(data.s || {});
const linesCovered = statements.filter(n => n > 0).length;
const linesTotal = statements.length;
// Parse function coverage
const functions = Object.values(data.f || {});
const fnCovered = functions.filter(n => n > 0).length;
const fnTotal = functions.length;
// Parse branch coverage
const branches = Object.values(data.b || {}).flat();
const brCovered = branches.filter(n => n > 0).length;
const brTotal = branches.length;
// Find uncovered lines
const uncoveredLines = [];
for (const [line, count] of Object.entries(data.s || {})) {
if (count === 0) {
uncoveredLines.push(parseInt(line));
}
}
// Find uncovered functions
const uncoveredFunctions = [];
const fnMap = data.fnMap || {};
for (const [fnId, count] of Object.entries(data.f || {})) {
if (count === 0 && fnMap[fnId]) {
uncoveredFunctions.push(fnMap[fnId].name || `function_${fnId}`);
}
}
const linePercentage = linesTotal > 0 ? (linesCovered / linesTotal) * 100 : 100;
const fnPercentage = fnTotal > 0 ? (fnCovered / fnTotal) * 100 : 100;
const brPercentage = brTotal > 0 ? (brCovered / brTotal) * 100 : 100;
files.set(file, {
file,
lines: { total: linesTotal, covered: linesCovered, percentage: linePercentage },
functions: { total: fnTotal, covered: fnCovered, percentage: fnPercentage },
branches: { total: brTotal, covered: brCovered, percentage: brPercentage },
uncoveredLines,
uncoveredFunctions,
});
totalLines += linesTotal;
coveredLines += linesCovered;
totalFunctions += fnTotal;
coveredFunctions += fnCovered;
totalBranches += brTotal;
coveredBranches += brCovered;
if (linePercentage < 50) {
lowCoverageFiles.push(file);
}
if (linePercentage === 0 && linesTotal > 0) {
uncoveredFiles.push(file);
}
}
}
catch (e) {
// Return empty summary on error
}
return {
files,
overall: {
lines: totalLines > 0 ? (coveredLines / totalLines) * 100 : 0,
functions: totalFunctions > 0 ? (coveredFunctions / totalFunctions) * 100 : 0,
branches: totalBranches > 0 ? (coveredBranches / totalBranches) * 100 : 0,
},
lowCoverageFiles,
uncoveredFiles,
};
}
/**
* Find coverage report in project
*/
function findCoverageReport(projectRoot = process.cwd()) {
const possiblePaths = [
'coverage/coverage-final.json',
'coverage/coverage-summary.json',
'.nyc_output/coverage.json',
'coverage.json',
'coverage/lcov.info',
];
for (const p of possiblePaths) {
const fullPath = path.join(projectRoot, p);
if (fs.existsSync(fullPath)) {
return fullPath;
}
}
return null;
}
/**
* Get coverage data for a specific file
*/
function getFileCoverage(file, summary) {
if (!summary) {
const reportPath = findCoverageReport();
if (!reportPath)
return null;
summary = parseIstanbulCoverage(reportPath);
}
// Try exact match first
if (summary.files.has(file)) {
return summary.files.get(file);
}
// Try matching by basename
const basename = path.basename(file);
for (const [key, data] of summary.files) {
if (key.endsWith(file) || key.endsWith(basename)) {
return data;
}
}
return null;
}
/**
* Suggest tests for files based on coverage
*/
function suggestTests(files, summary) {
if (!summary) {
const reportPath = findCoverageReport();
if (reportPath) {
summary = parseIstanbulCoverage(reportPath);
}
}
const suggestions = [];
for (const file of files) {
const coverage = summary ? getFileCoverage(file, summary) : null;
// Determine test file path
const ext = path.extname(file);
const base = path.basename(file, ext);
const dir = path.dirname(file);
const possibleTestFiles = [
path.join(dir, `${base}.test${ext}`),
path.join(dir, `${base}.spec${ext}`),
path.join(dir, '__tests__', `${base}.test${ext}`),
path.join('test', `${base}.test${ext}`),
path.join('tests', `${base}.test${ext}`),
];
const existingTestFile = possibleTestFiles.find(t => fs.existsSync(t));
const testFile = existingTestFile || possibleTestFiles[0];
if (!coverage) {
suggestions.push({
file,
testFile,
reason: 'No coverage data - needs test file',
priority: 'high',
coverage: 0,
uncoveredFunctions: [],
});
}
else if (coverage.lines.percentage < 30) {
suggestions.push({
file,
testFile,
reason: `Very low coverage (${coverage.lines.percentage.toFixed(1)}%)`,
priority: 'high',
coverage: coverage.lines.percentage,
uncoveredFunctions: coverage.uncoveredFunctions,
});
}
else if (coverage.lines.percentage < 70) {
suggestions.push({
file,
testFile,
reason: `Low coverage (${coverage.lines.percentage.toFixed(1)}%)`,
priority: 'medium',
coverage: coverage.lines.percentage,
uncoveredFunctions: coverage.uncoveredFunctions,
});
}
else if (coverage.uncoveredFunctions.length > 0) {
suggestions.push({
file,
testFile,
reason: `${coverage.uncoveredFunctions.length} untested functions`,
priority: 'low',
coverage: coverage.lines.percentage,
uncoveredFunctions: coverage.uncoveredFunctions,
});
}
}
return suggestions.sort((a, b) => {
const priorityOrder = { high: 0, medium: 1, low: 2 };
return priorityOrder[a.priority] - priorityOrder[b.priority];
});
}
/**
* Determine if a file needs the tester agent based on coverage
*/
function shouldRouteToTester(file, summary) {
const coverage = getFileCoverage(file, summary);
if (!coverage) {
return {
route: true,
reason: 'No test coverage data available',
coverage: 0,
};
}
if (coverage.lines.percentage < 50) {
return {
route: true,
reason: `Low coverage: ${coverage.lines.percentage.toFixed(1)}%`,
coverage: coverage.lines.percentage,
};
}
if (coverage.uncoveredFunctions.length > 3) {
return {
route: true,
reason: `${coverage.uncoveredFunctions.length} untested functions`,
coverage: coverage.lines.percentage,
};
}
return {
route: false,
reason: `Adequate coverage: ${coverage.lines.percentage.toFixed(1)}%`,
coverage: coverage.lines.percentage,
};
}
/**
* Get coverage-aware routing weight for agent selection
*/
function getCoverageRoutingWeight(file, summary) {
const coverage = getFileCoverage(file, summary);
if (!coverage) {
// No coverage = prioritize testing
return { coder: 0.3, tester: 0.5, reviewer: 0.2 };
}
const pct = coverage.lines.percentage;
if (pct < 30) {
// Very low - strongly prioritize testing
return { coder: 0.2, tester: 0.6, reviewer: 0.2 };
}
else if (pct < 60) {
// Low - moderate testing priority
return { coder: 0.4, tester: 0.4, reviewer: 0.2 };
}
else if (pct < 80) {
// Okay - balanced
return { coder: 0.5, tester: 0.3, reviewer: 0.2 };
}
else {
// Good - focus on code quality
return { coder: 0.5, tester: 0.2, reviewer: 0.3 };
}
}
exports.default = {
parseIstanbulCoverage,
findCoverageReport,
getFileCoverage,
suggestTests,
shouldRouteToTester,
getCoverageRoutingWeight,
};