186 lines
6.2 KiB
JavaScript
186 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
||
'use strict';
|
||
|
||
/**
|
||
* AOAC-02 · 合并哨兵 (Action Merge Sentinel)
|
||
* ═══════════════════════════════════════════════
|
||
* AGE OS Agent Chain · 半Agent(CI侧)
|
||
*
|
||
* 唤醒条件:PR合并到main
|
||
* 职责:收集CI日志(通过/失败/报错/警告)
|
||
* 输出:data/aoac/merge-result-log.json
|
||
*
|
||
* 版权:国作登字-2026-A-00037559 · TCS-0002∞
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const https = require('https');
|
||
|
||
const ROOT = path.resolve(__dirname, '..', '..');
|
||
const OUTPUT_PATH = path.join(ROOT, 'data', 'aoac', 'merge-result-log.json');
|
||
const CHAIN_STATUS_PATH = path.join(ROOT, 'data', 'aoac', 'chain-status.json');
|
||
const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
|
||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
|
||
|
||
function getBeijingTime() {
|
||
const now = new Date();
|
||
return new Date(now.getTime() + BEIJING_OFFSET_MS)
|
||
.toISOString().replace('T', ' ').slice(0, 19) + '+08:00';
|
||
}
|
||
|
||
function readJSON(filePath) {
|
||
try {
|
||
if (!fs.existsSync(filePath)) return null;
|
||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||
} catch { return null; }
|
||
}
|
||
|
||
function writeJSON(filePath, data) {
|
||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
||
}
|
||
|
||
function githubRequest(endpoint) {
|
||
return new Promise((resolve, reject) => {
|
||
const url = new URL(`https://api.github.com${endpoint}`);
|
||
const options = {
|
||
hostname: url.hostname,
|
||
path: url.pathname + url.search,
|
||
method: 'GET',
|
||
headers: {
|
||
'User-Agent': 'AOAC-Merge-Sentinel/1.0',
|
||
'Accept': 'application/vnd.github.v3+json',
|
||
...(GITHUB_TOKEN ? { 'Authorization': `Bearer ${GITHUB_TOKEN}` } : {})
|
||
}
|
||
};
|
||
const req = https.request(options, (res) => {
|
||
let data = '';
|
||
res.on('data', (chunk) => data += chunk);
|
||
res.on('end', () => {
|
||
try { resolve(JSON.parse(data)); }
|
||
catch { resolve(data); }
|
||
});
|
||
});
|
||
req.on('error', reject);
|
||
req.setTimeout(30000, () => { req.destroy(); reject(new Error('timeout')); });
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
async function collectMergeInfo() {
|
||
const prNumber = process.env.PR_NUMBER;
|
||
const prTitle = process.env.PR_TITLE || '';
|
||
const mergeCommitSha = process.env.MERGE_COMMIT_SHA || '';
|
||
const headSha = process.env.HEAD_SHA || '';
|
||
const baseBranch = process.env.BASE_BRANCH || 'main';
|
||
|
||
let checkRuns = [];
|
||
let ciSummary = { total: 0, passed: 0, failed: 0, skipped: 0 };
|
||
|
||
// Collect check runs for the merge commit
|
||
if (mergeCommitSha && GITHUB_TOKEN) {
|
||
try {
|
||
const checks = await githubRequest(`/repos/${REPO}/commits/${mergeCommitSha}/check-runs?per_page=100`);
|
||
if (checks && checks.check_runs) {
|
||
checkRuns = checks.check_runs.map(cr => ({
|
||
name: cr.name,
|
||
status: cr.status,
|
||
conclusion: cr.conclusion,
|
||
started_at: cr.started_at,
|
||
completed_at: cr.completed_at,
|
||
output_title: cr.output ? cr.output.title : null,
|
||
output_summary: cr.output ? (cr.output.summary || '').slice(0, 200) : null
|
||
}));
|
||
ciSummary = {
|
||
total: checkRuns.length,
|
||
passed: checkRuns.filter(c => c.conclusion === 'success').length,
|
||
failed: checkRuns.filter(c => c.conclusion === 'failure').length,
|
||
skipped: checkRuns.filter(c => c.conclusion === 'skipped' || c.conclusion === 'cancelled').length
|
||
};
|
||
}
|
||
} catch (err) {
|
||
console.error('⚠️ 获取CI检查结果失败:', err.message);
|
||
}
|
||
}
|
||
|
||
// Collect combined status
|
||
let combinedStatus = 'unknown';
|
||
if (mergeCommitSha && GITHUB_TOKEN) {
|
||
try {
|
||
const status = await githubRequest(`/repos/${REPO}/commits/${mergeCommitSha}/status`);
|
||
if (status && status.state) {
|
||
combinedStatus = status.state;
|
||
}
|
||
} catch (err) {
|
||
console.error('⚠️ 获取合并状态失败:', err.message);
|
||
}
|
||
}
|
||
|
||
// Determine overall result
|
||
const overallResult = ciSummary.failed > 0 ? 'failure' :
|
||
ciSummary.passed === ciSummary.total && ciSummary.total > 0 ? 'success' :
|
||
combinedStatus !== 'unknown' ? combinedStatus : 'no_checks';
|
||
|
||
return {
|
||
aoac_agent: 'AOAC-02',
|
||
aoac_agent_name: 'action-merge-sentinel',
|
||
status: 'half_ready',
|
||
timestamp: getBeijingTime(),
|
||
timestamp_utc: new Date().toISOString(),
|
||
merge: {
|
||
pr_number: prNumber ? parseInt(prNumber) : null,
|
||
pr_title: prTitle.slice(0, 200),
|
||
merge_commit_sha: mergeCommitSha,
|
||
head_sha: headSha,
|
||
base_branch: baseBranch
|
||
},
|
||
ci: {
|
||
combined_status: combinedStatus,
|
||
overall_result: overallResult,
|
||
summary: ciSummary,
|
||
check_runs: checkRuns.slice(0, 30)
|
||
},
|
||
ready_for_fusion: true,
|
||
next_agent: 'AOAC-03 (chain-fusion)'
|
||
};
|
||
}
|
||
|
||
async function main() {
|
||
console.log('🔍 AOAC-02 · 合并哨兵 · 唤醒中...');
|
||
console.log(`📅 ${getBeijingTime()}`);
|
||
|
||
try {
|
||
const mergeLog = await collectMergeInfo();
|
||
|
||
writeJSON(OUTPUT_PATH, mergeLog);
|
||
console.log(`✅ 合并结果已写入: ${path.relative(ROOT, OUTPUT_PATH)}`);
|
||
console.log(` PR #${mergeLog.merge.pr_number}: ${mergeLog.merge.pr_title}`);
|
||
console.log(` CI结果: ${mergeLog.ci.overall_result}`);
|
||
console.log(` ✅${mergeLog.ci.summary.passed} ❌${mergeLog.ci.summary.failed} ⏭${mergeLog.ci.summary.skipped}`);
|
||
|
||
// Update chain status
|
||
const chainStatus = readJSON(CHAIN_STATUS_PATH) || {};
|
||
if (chainStatus.agents && chainStatus.agents['AOAC-02']) {
|
||
chainStatus.agents['AOAC-02'].status = 'half_ready';
|
||
chainStatus.agents['AOAC-02'].last_run = new Date().toISOString();
|
||
writeJSON(CHAIN_STATUS_PATH, chainStatus);
|
||
}
|
||
|
||
// Output for workflow
|
||
const outputFile = process.env.GITHUB_OUTPUT;
|
||
if (outputFile) {
|
||
fs.appendFileSync(outputFile, `ci_result=${mergeLog.ci.overall_result}\n`);
|
||
fs.appendFileSync(outputFile, `ready_for_fusion=true\n`);
|
||
}
|
||
|
||
console.log('🔍 AOAC-02 · 半Agent就绪 · 准备触发AOAC-03合体');
|
||
} catch (err) {
|
||
console.error('❌ AOAC-02 执行失败:', err.message);
|
||
process.exitCode = 1;
|
||
}
|
||
}
|
||
|
||
main();
|