158 lines
4.6 KiB
JavaScript
158 lines
4.6 KiB
JavaScript
// scripts/agents/twin-collector.js
|
|
// Twin Data Collector · 双子数据采集器
|
|
// ZY-P1-TWIN-001 · Phase 1 · Twin Balance Agent
|
|
// 版权:国作登字-2026-A-00037559
|
|
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execSync } = require('child_process');
|
|
|
|
const ROOT = path.resolve(__dirname, '../..');
|
|
const TIANYEN_DIR = path.join(ROOT, '.github/tianyen');
|
|
const TWIN_DATA_PATH = path.join(TIANYEN_DIR, 'twin-data.json');
|
|
|
|
// ── 维度权重 · 四维天平 ─────────────────────────────────────────────────
|
|
const WEIGHTS = {
|
|
taskCompletion: 0.40,
|
|
testPassRate: 0.25,
|
|
codeCoverage: 0.20,
|
|
securityScore: 0.15
|
|
};
|
|
|
|
/**
|
|
* 执行测试命令,解析 ✅/❌ 计数
|
|
* @param {string} cmd - 测试命令
|
|
* @returns {{ passed: number, failed: number, total: number, output: string }}
|
|
*/
|
|
function runTests(cmd) {
|
|
let output = '';
|
|
try {
|
|
output = execSync(cmd, {
|
|
cwd: ROOT,
|
|
encoding: 'utf8',
|
|
timeout: 60000,
|
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
});
|
|
} catch (err) {
|
|
// 测试失败时 execSync 会抛错,但 stderr/stdout 仍有数据
|
|
output = (err.stdout || '') + '\n' + (err.stderr || '');
|
|
}
|
|
|
|
const passCount = (output.match(/✅/g) || []).length;
|
|
const failCount = (output.match(/❌/g) || []).length;
|
|
const total = passCount + failCount;
|
|
|
|
return { passed: passCount, failed: failCount, total, output };
|
|
}
|
|
|
|
/**
|
|
* 采集单侧指标
|
|
* @param {'exe-engine'|'grid-db'} side
|
|
* @returns {{ taskCompletion: number, testPassRate: number, codeCoverage: number, securityScore: number, composite: number, details: object }}
|
|
*/
|
|
function collectMetrics(side) {
|
|
const testCommands = {
|
|
'exe-engine': [
|
|
'node exe-engine/tests/smoke/exe-engine.test.js',
|
|
'node exe-engine/tests/smoke/exe-engine-p1.test.js'
|
|
],
|
|
'grid-db': [
|
|
'node grid-db/tests/smoke/grid-db.test.js'
|
|
]
|
|
};
|
|
|
|
const commands = testCommands[side] || [];
|
|
let totalPassed = 0;
|
|
let totalFailed = 0;
|
|
let totalTests = 0;
|
|
const testDetails = [];
|
|
|
|
for (const cmd of commands) {
|
|
const result = runTests(cmd);
|
|
totalPassed += result.passed;
|
|
totalFailed += result.failed;
|
|
totalTests += result.total;
|
|
testDetails.push({
|
|
command: cmd,
|
|
passed: result.passed,
|
|
failed: result.failed,
|
|
total: result.total
|
|
});
|
|
}
|
|
|
|
// 测试通过率
|
|
const testPassRate = totalTests > 0 ? totalPassed / totalTests : 1.0;
|
|
|
|
// 任务完成度 — 基于测试结果推导
|
|
const taskCompletion = testPassRate;
|
|
|
|
// 代码覆盖率 — 无覆盖工具时默认 100%
|
|
const codeCoverage = 1.0;
|
|
|
|
// 安全扫描 — 默认无告警 = 100%
|
|
const securityScore = 1.0;
|
|
|
|
// 综合分数 = 加权求和
|
|
const composite =
|
|
WEIGHTS.taskCompletion * taskCompletion +
|
|
WEIGHTS.testPassRate * testPassRate +
|
|
WEIGHTS.codeCoverage * codeCoverage +
|
|
WEIGHTS.securityScore * securityScore;
|
|
|
|
return {
|
|
taskCompletion: Math.round(taskCompletion * 10000) / 10000,
|
|
testPassRate: Math.round(testPassRate * 10000) / 10000,
|
|
codeCoverage: Math.round(codeCoverage * 10000) / 10000,
|
|
securityScore: Math.round(securityScore * 10000) / 10000,
|
|
composite: Math.round(composite * 10000) / 10000,
|
|
details: {
|
|
totalPassed,
|
|
totalFailed,
|
|
totalTests,
|
|
tests: testDetails
|
|
}
|
|
};
|
|
}
|
|
|
|
/** 采集左翼 · EXE-Engine */
|
|
function collectLeft() {
|
|
return collectMetrics('exe-engine');
|
|
}
|
|
|
|
/** 采集右翼 · Grid-DB */
|
|
function collectRight() {
|
|
return collectMetrics('grid-db');
|
|
}
|
|
|
|
/** 采集双翼 · 同时采集左右 */
|
|
function collectBoth() {
|
|
const left = collectLeft();
|
|
const right = collectRight();
|
|
const result = {
|
|
left,
|
|
right,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
// 确保输出目录存在
|
|
if (!fs.existsSync(TIANYEN_DIR)) {
|
|
fs.mkdirSync(TIANYEN_DIR, { recursive: true });
|
|
}
|
|
fs.writeFileSync(TWIN_DATA_PATH, JSON.stringify(result, null, 2), 'utf8');
|
|
|
|
return result;
|
|
}
|
|
|
|
// ── CLI 入口 ─────────────────────────────────────────────────────────────
|
|
if (require.main === module) {
|
|
console.log('🔬 Twin Data Collector · 双子数据采集\n');
|
|
const data = collectBoth();
|
|
console.log(` 左翼 (EXE-Engine): composite = ${data.left.composite}`);
|
|
console.log(` 右翼 (Grid-DB): composite = ${data.right.composite}`);
|
|
console.log(`\n✅ 数据已写入 ${TWIN_DATA_PATH}`);
|
|
}
|
|
|
|
module.exports = { collectMetrics, collectLeft, collectRight, collectBoth, WEIGHTS };
|