guanghulab/.github/scripts/merge-watchdog.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

220 lines
6.2 KiB
JavaScript

#!/usr/bin/env node
// ═══════════════════════════════════════════════
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
// 📜 Copyright: 国作登字-2026-A-00037559
// ═══════════════════════════════════════════════
// .github/scripts/merge-watchdog.js
// 👁️ Merge Watchdog · 合并指令看守者
//
// 看守者核心逻辑:
// - 跟踪已合并的 auto-repair PR
// - 验证修复是否生效
// - 更新仪表盘记录
// - 三振出局时准备告警数据
//
// 看守者五条铁律:
// 1. 看守者自身不修改代码 — 只观察、记录、通知
// 2. 最多重试 3 次 — 硬上限
// 3. 每轮修复策略必须不同
// 4. 邮件必须包含具体操作步骤
// 5. 成功后绝对静默 — 只更新仪表盘
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const WATCHDOG_DATA_PATH = path.join(ROOT, 'docs/dashboard/watchdog-records.json');
const DASHBOARD_PATH = path.join(ROOT, 'DASHBOARD.md');
// ━━━ 工具函数 ━━━
function loadJSON(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (e) {
return null;
}
}
function saveJSON(filePath, data) {
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
}
function getTimestamp() {
return new Date().toISOString();
}
// ━━━ 看守记录管理 ━━━
function loadWatchdogRecords() {
const data = loadJSON(WATCHDOG_DATA_PATH);
if (data && data.records) return data;
return {
merge_watchdog: {
last_updated: getTimestamp(),
active_watches: 0,
resolved_watches: 0,
escalated_watches: 0
},
records: []
};
}
function saveWatchdogRecords(data) {
// Update counts
data.merge_watchdog.last_updated = getTimestamp();
data.merge_watchdog.active_watches = data.records.filter(r => r.status === 'watching').length;
data.merge_watchdog.resolved_watches = data.records.filter(r => r.status === 'fixed').length;
data.merge_watchdog.escalated_watches = data.records.filter(r => r.status === 'escalated').length;
saveJSON(WATCHDOG_DATA_PATH, data);
}
// ━━━ 创建看守记录 ━━━
function createWatchRecord(prNumber, prTitle, targetWorkflows) {
return {
pr_number: prNumber,
pr_title: prTitle,
merged_at: getTimestamp(),
target_workflows: targetWorkflows,
retry_count: 0,
max_retries: 3,
status: 'watching',
repair_strategies_used: ['standard'],
last_check_time: null,
resolution: null
};
}
// ━━━ 记录修复成功(静默更新) ━━━
function recordSuccess(prNumber) {
const data = loadWatchdogRecords();
const record = data.records.find(r => r.pr_number === prNumber);
if (record) {
record.status = 'fixed';
record.resolution = 'auto_fixed';
record.last_check_time = getTimestamp();
} else {
data.records.push({
pr_number: prNumber,
status: 'fixed',
resolved_at: getTimestamp(),
resolution: 'auto_fixed',
retry_count: 0
});
}
saveWatchdogRecords(data);
console.log(`✅ PR #${prNumber} 修复已确认生效 — 仪表盘已静默更新`);
}
// ━━━ 记录修复失败 ━━━
function recordFailure(prNumber, retryRound) {
const data = loadWatchdogRecords();
let record = data.records.find(r => r.pr_number === prNumber);
if (!record) {
record = createWatchRecord(prNumber, '', []);
data.records.push(record);
}
record.retry_count = retryRound;
record.last_check_time = getTimestamp();
// Strategy names for each round
const strategies = ['standard', 'deep_root_cause', 'conservative_fallback'];
if (retryRound <= 3) {
record.repair_strategies_used.push(strategies[retryRound - 1] || 'unknown');
}
if (retryRound >= 3) {
record.status = 'escalated';
record.resolution = 'human_intervention';
console.log(`⛔ PR #${prNumber} 三振出局 — 需人类介入`);
} else {
record.status = 'watching';
console.log(`🔄 PR #${prNumber}${retryRound}次失败 — 等待重试`);
}
saveWatchdogRecords(data);
}
// ━━━ 注册新的 watch ━━━
function registerWatch(prNumber, prTitle, targetWorkflows) {
const data = loadWatchdogRecords();
// Check if already watching
const existing = data.records.find(r => r.pr_number === prNumber);
if (existing) {
console.log(`⚠️ PR #${prNumber} 已在看守列表中`);
return;
}
const record = createWatchRecord(prNumber, prTitle, targetWorkflows);
data.records.push(record);
saveWatchdogRecords(data);
console.log(`👁️ 开始看守 PR #${prNumber}: ${prTitle}`);
}
// ━━━ 获取看守状态 ━━━
function getWatchStatus(prNumber) {
const data = loadWatchdogRecords();
const record = data.records.find(r => r.pr_number === prNumber);
return record || null;
}
// ━━━ CLI 入口 ━━━
function main() {
const args = process.argv.slice(2);
const action = args[0];
switch (action) {
case 'register': {
const prNumber = parseInt(args[1]);
const prTitle = args[2] || '';
const workflows = args[3] ? args[3].split(',') : [];
registerWatch(prNumber, prTitle, workflows);
break;
}
case 'success': {
const prNumber = parseInt(args[1]);
recordSuccess(prNumber);
break;
}
case 'failure': {
const prNumber = parseInt(args[1]);
const retryRound = parseInt(args[2]) || 1;
recordFailure(prNumber, retryRound);
break;
}
case 'status': {
const prNumber = parseInt(args[1]);
const status = getWatchStatus(prNumber);
console.log(JSON.stringify(status, null, 2));
break;
}
case 'list': {
const data = loadWatchdogRecords();
console.log(JSON.stringify(data, null, 2));
break;
}
default: {
console.log('Usage: merge-watchdog.js <action> [args]');
console.log('Actions: register, success, failure, status, list');
}
}
}
main();