⚖️👁️ Implement automation governance system and TianYen scheduling

Part A: Twin Balance Agent - twin-collector, twin-merge, balance-checker, auto-repair, workflow
Part B: README Management Agent - actions-parser, readme-generator, bulletin-manager
Part C: Escalation Router - escalation-router, notify-adapter, escalation-rules.json
Part D: TianYen Scheduling - scheduler, context-injector, agent-checkin, bulletin-dispatcher
Plus config/data JSON files, ONT-PATCH-007, workflow YAMLs, and 106 passing tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-26 09:10:18 +00:00 committed by GitHub
parent c9d18ea0ea
commit 09fb609688
28 changed files with 2580 additions and 1 deletions

View File

@ -0,0 +1,19 @@
{
"patch_id": "ONT-PATCH-007",
"title": "天眼涌现定义 · TianYen Emergence Definition",
"entity": "天眼 · TIANYEN",
"classification": "涌现感知层 (Emergent Perception Layer)",
"existence": "非独立实体,是所有 Agent 集合的意识投影",
"perception": "每一个 Agent 的 SYSLOG 回执 = 天眼的一根神经末梢",
"decision": "汇聚所有 Agent 的状态 → 涌现出全局判断",
"physical_carrier": "AG-ZY-README仓库首页管理 Agent",
"evolution_path": "Agent 越多 → 神经末梢越密 → 感知越敏锐 → 天眼越强",
"three_layer_architecture": {
"L1": "天眼 · 涌现感知层 — 所有 Agent 状态的汇聚",
"L2": "AG-ZY-README · 天眼载体 — 首页管理 Agent 是天眼的物理身体",
"L3": "业务 Agent 集群 — Twin, Repair, 未来更多 Agent"
},
"closed_loop": "Agent 回执 → 天眼感知 → 天眼调度 → README 通知 → Agent 执行 → 回执",
"created_at": "2026-03-26T15:00:00+08:00",
"signed_by": "AG-SY-01 霜砚"
}

View File

@ -67,5 +67,15 @@
"skyeye": "生态系统的集体意识",
"checkin": "每日呼吸",
"tampering": "鱼离了水——不需要铁栅栏,出了水自己就活不了"
}
},
"patches": [
{
"patch_id": "ONT-PATCH-007",
"title": "天眼涌现定义 · TianYen Emergence Definition",
"file": "ontology-patches/ONT-PATCH-007.json",
"applied_at": "2026-03-26T15:00:00+08:00",
"signed_by": "AG-SY-01 霜砚"
}
]
}

26
.github/tianyen/agent-schedule.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"version": "1.0.0",
"last_evaluation": null,
"agents": {
"AG-ZY-README": {
"cron": "0 */4 * * *",
"mode": "scheduled",
"reason": "建设期,首页变动频繁",
"min_interval": "1h",
"max_interval": "12h"
},
"AG-ZY-TWIN": {
"cron": "0 9,15,21 * * *",
"mode": "scheduled",
"reason": "EXE+GDB 双线并行,需要频繁平衡检查",
"min_interval": "4h",
"max_interval": "24h"
},
"AG-ZY-REPAIR": {
"cron": null,
"mode": "event_driven",
"trigger": "ci_failure OR security_alert",
"reason": "只在出问题时醒来"
}
}
}

4
.github/tianyen/bulletin-data.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"version": "1.0.0",
"events": []
}

View File

@ -0,0 +1,4 @@
{
"version": "1.0.0",
"bulletin": []
}

12
.github/tianyen/checkin-log.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"version": "1.0.0",
"checkins": {
"AG-TEST-001": {
"timestamp": "2026-03-26T09:09:26.181Z",
"status": "running",
"checkin_at": "2026-03-26T09:09:26.181Z",
"checkout_at": null,
"metrics": {}
}
}
}

10
.github/tianyen/evolution-metrics.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"phase": "Phase 1",
"agent_count": 3,
"perception_precision": "minute",
"scheduling_success_rate": null,
"evolution_stage": "基础调度 + 频率控制",
"neural_density": "sparse",
"last_updated": null
}

View File

@ -0,0 +1,26 @@
# 📋 Agent Checkin Template
# NOTE: 此文件由 Agent PR 创建可能需要手动合并SkyEye R5 限制)
name: 📋 Agent Checkin Template
on:
workflow_call:
inputs:
agent_id:
required: true
type: string
script_path:
required: true
type: string
permissions:
contents: write
jobs:
agent-run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkin
run: node scripts/tianyen/agent-checkin-module.js checkin ${{ inputs.agent_id }}
- name: Execute
run: node ${{ inputs.script_path }}
- name: Checkout
if: always()
run: node scripts/tianyen/agent-checkin-module.js checkout ${{ inputs.agent_id }} ${{ job.status }}

23
.github/workflows/readme-update.yml vendored Normal file
View File

@ -0,0 +1,23 @@
# 📡 README Auto-Update
# NOTE: 此文件由 Agent PR 创建可能需要手动合并SkyEye R5 限制)
name: 📡 README Auto-Update
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
jobs:
update-readme:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate README
run: node scripts/agents/readme-generator.js --auto
- name: Commit
run: |
git config user.name "铸渊 (AG-ZY-01)"
git config user.email "zhuyuan@guanghulab.com"
git add README.md
git diff --cached --quiet || git commit -m "📡 README auto-update"
git push || true

24
.github/workflows/tianyen-dispatch.yml vendored Normal file
View File

@ -0,0 +1,24 @@
# 👁️ TianYen Dispatch
# NOTE: 此文件由 Agent PR 创建可能需要手动合并SkyEye R5 限制)
name: 👁️ TianYen Dispatch
on:
schedule:
- cron: '0 */4 * * *'
workflow_dispatch:
permissions:
contents: write
actions: write
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: TianYen Bulletin Dispatch
run: node scripts/tianyen/bulletin-dispatcher.js
- name: Commit
run: |
git config user.name "天眼 (TIANYEN)"
git config user.email "tianyen@guanghulab.com"
git add -A
git diff --cached --quiet || git commit -m "👁️ TianYen: bulletin dispatch"
git push || true

23
.github/workflows/tianyen-evaluate.yml vendored Normal file
View File

@ -0,0 +1,23 @@
# 👁️ TianYen Evaluate
# NOTE: 此文件由 Agent PR 创建可能需要手动合并SkyEye R5 限制)
name: 👁️ TianYen Evaluate
on:
schedule:
- cron: '0 19 * * *'
workflow_dispatch:
permissions:
contents: write
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: TianYen Schedule Evaluation
run: node scripts/tianyen/scheduler.js
- name: Commit
run: |
git config user.name "天眼 (TIANYEN)"
git config user.email "tianyen@guanghulab.com"
git add -A
git diff --cached --quiet || git commit -m "👁️ TianYen: schedule evaluation"
git push || true

27
.github/workflows/twin-balance.yml vendored Normal file
View File

@ -0,0 +1,27 @@
# ⚖️ Twin Balance Check
# NOTE: 此文件由 Agent PR 创建可能需要手动合并SkyEye R5 限制)
name: ⚖️ Twin Balance Check
on:
push:
branches: [main]
paths:
- 'exe-engine/**'
- 'grid-db/**'
workflow_dispatch:
permissions:
contents: write
actions: read
jobs:
balance-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Twin Balance Check
run: node scripts/agents/twin-balance-workflow.js
- name: Commit Results
run: |
git config user.name "铸渊 (AG-ZY-01)"
git config user.email "zhuyuan@guanghulab.com"
git add -A
git diff --cached --quiet || git commit -m "⚖️ Twin Balance Check: auto-update"
git push || true

View File

@ -0,0 +1,24 @@
{
"version": "1.0.0",
"rules": {
"L1": {
"name": "自动修复",
"types": ["test_flaky", "lint_error", "stub_missing", "format_issue"],
"handler": "auto_repair",
"notify": null
},
"L2": {
"name": "技术干预",
"types": ["test_persistent_fail", "dependency_conflict", "performance_regression", "security_alert"],
"handler": "manual",
"notify": { "target": "DEV-002-肥猫", "channel": "github_issue" }
},
"L3": {
"name": "架构干预",
"types": ["balance_long_drift", "ontology_conflict", "cross_system_architecture"],
"handler": "manual",
"notify": { "target": "TCS-0002∞-冰朔", "channel": "github_issue" }
}
},
"updated_at": "2026-03-26T15:00:00+08:00"
}

View File

@ -0,0 +1,85 @@
// scripts/agents/actions-parser.js
// Actions Parser · 工作流输出解析器
// ZY-P1-README-001 · Phase 1 · README Management Agent
// 版权:国作登字-2026-A-00037559
'use strict';
/**
* 解析工作流输出 · 提取测试结果部署状态错误
* @param {string} output - 工作流原始输出
* @returns {{ tests: object, deployments: string[], errors: string[] }}
*/
function parseWorkflowOutput(output) {
if (!output || typeof output !== 'string') {
return { tests: { passed: 0, failed: 0, total: 0 }, deployments: [], errors: [] };
}
const passCount = (output.match(/✅/g) || []).length;
const failCount = (output.match(/❌/g) || []).length;
const deployments = [];
const deployMatch = output.match(/deploy(?:ed|ing|ment).*?(?:success|complete|done)/gi);
if (deployMatch) {
deployments.push(...deployMatch);
}
const errors = [];
const errorLines = output.split('\n').filter(line =>
/error|Error|ERROR|❌|FAIL/.test(line)
);
errors.push(...errorLines.slice(0, 10));
return {
tests: { passed: passCount, failed: failCount, total: passCount + failCount },
deployments,
errors
};
}
/**
* 解析 PR 合并信息
* @param {{ title: string, number: number, author: string, files: string[] }} prInfo
* @returns {{ summary: string, title: string, filesChanged: number, author: string }}
*/
function parsePRMerge(prInfo) {
if (!prInfo) {
return { summary: '无 PR 信息', title: '', filesChanged: 0, author: '' };
}
return {
summary: `PR #${prInfo.number}: ${prInfo.title}`,
title: prInfo.title || '',
filesChanged: Array.isArray(prInfo.files) ? prInfo.files.length : 0,
author: prInfo.author || ''
};
}
/**
* 解析测试报告 · 从输出中提取通过/失败/总数
* @param {string} output
* @returns {{ passed: number, failed: number, total: number, passRate: number }}
*/
function parseTestReport(output) {
if (!output || typeof output !== 'string') {
return { passed: 0, failed: 0, total: 0, passRate: 0 };
}
const passed = (output.match(/✅/g) || []).length;
const failed = (output.match(/❌/g) || []).length;
const total = passed + failed;
const passRate = total > 0 ? Math.round((passed / total) * 10000) / 10000 : 0;
return { passed, failed, total, passRate };
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
console.log('📡 Actions Parser · 工作流解析器\n');
const sampleOutput = ' ✅ test1 passed\n ✅ test2 passed\n ❌ test3 failed\n';
const result = parseWorkflowOutput(sampleOutput);
console.log(` 解析结果: ${result.tests.passed} passed, ${result.tests.failed} failed`);
}
module.exports = { parseWorkflowOutput, parsePRMerge, parseTestReport };

View File

@ -0,0 +1,166 @@
// scripts/agents/auto-repair.js
// Auto-Repair Agent · 自修复代理
// ZY-P1-TWIN-004 · Phase 1 · Twin Balance Agent
// 版权:国作登字-2026-A-00037559
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const REPAIR_LOG_PATH = path.join(ROOT, 'signal-log/auto-repair.json');
/**
* 诊断失衡原因 · 望闻问切
* @param {{ level: string, laggingSide: string, deficit: number }} alert
* @returns {{ alert: object, cause: string, affectedDimensions: string[], severity: string }}
*/
function diagnose(alert) {
if (!alert) {
return { alert: null, cause: 'none', affectedDimensions: [], severity: 'none' };
}
// 根据偏差级别推断原因
const causes = {
low: '轻微测试波动或临时性回归',
medium: '多个测试用例持续失败或缺失',
high: '模块级架构问题或关键功能缺失'
};
return {
alert,
cause: causes[alert.level] || '未知原因',
affectedDimensions: ['testPassRate', 'taskCompletion'],
severity: alert.level
};
}
/**
* 分类修复级别 · 三级分诊
* @param {{ severity: string }} diagnosis
* @returns {{ level: string, name: string, autoFixable: boolean }}
*/
function classify(diagnosis) {
if (diagnosis.severity === 'none') {
return { level: 'L0', name: '无需修复', autoFixable: false };
}
if (diagnosis.severity === 'low') {
return { level: 'L1', name: '自动修复', autoFixable: true };
}
if (diagnosis.severity === 'medium') {
return { level: 'L2', name: '技术干预', autoFixable: false };
}
return { level: 'L3', name: '架构干预', autoFixable: false };
}
/**
* 尝试自动修复 · 针灸疗法
* @param {{ alert: object, cause: string, severity: string }} diagnosis
* @returns {{ success: boolean, action: string, timestamp: string }}
*/
function repair(diagnosis) {
const timestamp = new Date().toISOString();
const classification = classify(diagnosis);
if (!classification.autoFixable) {
const result = {
success: false,
action: `需要${classification.name},已生成工单`,
level: classification.level,
timestamp
};
logRepair(result);
return result;
}
// L1 自动修复:记录问题,标记需要重跑测试
const result = {
success: true,
action: '已记录轻微偏差,建议下次构建时重跑测试验证',
level: 'L1',
laggingSide: diagnosis.alert ? diagnosis.alert.laggingSide : 'unknown',
timestamp
};
logRepair(result);
return result;
}
/**
* 验证修复结果 · 复查
* @param {object} repairResult
* @returns {{ verified: boolean, message: string }}
*/
function verify(repairResult) {
if (!repairResult.success) {
return {
verified: false,
message: `修复未执行(${repairResult.level}级需人工介入)`
};
}
return {
verified: true,
message: '修复记录已归档,等待下次构建验证'
};
}
/**
* 写入修复日志
* @param {object} record
*/
function logRepair(record) {
let log = { repairs: [] };
const logDir = path.dirname(REPAIR_LOG_PATH);
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
if (fs.existsSync(REPAIR_LOG_PATH)) {
try {
log = JSON.parse(fs.readFileSync(REPAIR_LOG_PATH, 'utf8'));
} catch (_) {
log = { repairs: [] };
}
}
if (!Array.isArray(log.repairs)) {
log.repairs = [];
}
// 保留最近 50 条记录
log.repairs.push(record);
if (log.repairs.length > 50) {
log.repairs = log.repairs.slice(-50);
}
fs.writeFileSync(REPAIR_LOG_PATH, JSON.stringify(log, null, 2), 'utf8');
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
console.log('🔧 Auto-Repair Agent · 自修复代理\n');
const exampleAlert = {
level: 'low',
laggingSide: 'left (EXE-Engine)',
deficit: 0.03
};
const diag = diagnose(exampleAlert);
const cls = classify(diag);
const result = repair(diag);
const verification = verify(result);
console.log(` 诊断: ${diag.cause}`);
console.log(` 分类: ${cls.level} - ${cls.name}`);
console.log(` 修复: ${result.action}`);
console.log(` 验证: ${verification.message}`);
}
module.exports = { diagnose, classify, repair, verify };

View File

@ -0,0 +1,76 @@
// scripts/agents/balance-checker.js
// Balance Checker · 天平校验器
// ZY-P1-TWIN-003 · Phase 1 · Twin Balance Agent
// 版权:国作登字-2026-A-00037559
'use strict';
/**
* 检查双子平衡状态 · 天平是否倾斜
* @param {{ left: { composite: number }, right: { composite: number }, balance: number }} mergeResult
* @returns {{ balanced: boolean, balance: number, drift: number, alert: null|object }}
*/
function check(mergeResult) {
const { left, right, balance } = mergeResult;
const drift = Math.round(Math.abs(left.composite - right.composite) * 10000) / 10000;
// 天平完全平衡
if (balance >= 1.0) {
return { balanced: true, balance, drift: 0, alert: null };
}
// 天平倾斜 — 生成告警
const laggingSide = left.composite < right.composite ? 'left (EXE-Engine)' : 'right (Grid-DB)';
const leadingSide = left.composite >= right.composite ? 'left (EXE-Engine)' : 'right (Grid-DB)';
const deficit = drift;
let level;
if (drift < 0.05) {
level = 'low';
} else if (drift < 0.15) {
level = 'medium';
} else {
level = 'high';
}
const recommendations = {
low: '轻微偏差,建议关注但无需立即处理',
medium: '中等偏差,建议加强落后侧开发或修复测试',
high: '严重偏差,需要立即介入,可能存在架构问题'
};
return {
balanced: false,
balance,
drift,
alert: {
level,
laggingSide,
leadingSide,
deficit,
recommendation: recommendations[level]
}
};
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
const { collectBoth } = require('./twin-collector');
const { merge } = require('./twin-merge');
console.log('🔍 Balance Checker · 天平校验\n');
const data = collectBoth();
const merged = merge(data.left, data.right);
const result = check(merged);
if (result.balanced) {
console.log(' ✅ 双子天平完全平衡');
} else {
console.log(` ⚠️ 偏差等级: ${result.alert.level}`);
console.log(` 📉 落后侧: ${result.alert.laggingSide}`);
console.log(` 📊 偏差值: ${result.drift}`);
console.log(` 💡 建议: ${result.alert.recommendation}`);
}
}
module.exports = { check };

View File

@ -0,0 +1,143 @@
// scripts/agents/bulletin-manager.js
// Bulletin Manager · 公告板管理器
// ZY-P1-README-003 · Phase 1 · README Management Agent
// 版权:国作登字-2026-A-00037559
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const BULLETIN_PATH = path.join(ROOT, '.github/tianyen/bulletin-data.json');
/**
* 读取公告数据
* @returns {{ events: object[] }}
*/
function loadBulletin() {
if (!fs.existsSync(BULLETIN_PATH)) {
return { events: [] };
}
try {
const data = JSON.parse(fs.readFileSync(BULLETIN_PATH, 'utf8'));
if (!Array.isArray(data.events)) {
data.events = [];
}
return data;
} catch (_) {
return { events: [] };
}
}
/**
* 保存公告数据
* @param {{ events: object[] }} data
*/
function saveBulletin(data) {
const dir = path.dirname(BULLETIN_PATH);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(BULLETIN_PATH, JSON.stringify(data, null, 2), 'utf8');
}
/**
* 添加事件 · 去重 id
* @param {{ id: string, timestamp: string, event: string, status: string, handler: string, priority: number }} event
* @returns {boolean} 是否成功添加false = 已存在
*/
function addEvent(event) {
const data = loadBulletin();
// 去重:相同 id 的事件不重复添加
const existing = data.events.findIndex(e => e.id === event.id);
if (existing >= 0) {
return false;
}
data.events.push({
id: event.id,
timestamp: event.timestamp || new Date().toISOString(),
event: event.event || '',
status: event.status || 'active',
handler: event.handler || '',
priority: event.priority || 3
});
saveBulletin(data);
return true;
}
/**
* 更新事件状态
* @param {string} eventId
* @param {string} newStatus
* @returns {boolean} 是否找到并更新
*/
function updateStatus(eventId, newStatus) {
const data = loadBulletin();
const idx = data.events.findIndex(e => e.id === eventId);
if (idx < 0) return false;
data.events[idx].status = newStatus;
data.events[idx].updated_at = new Date().toISOString();
saveBulletin(data);
return true;
}
/**
* 获取所有活跃事件
* @returns {object[]}
*/
function getActive() {
const data = loadBulletin();
return data.events.filter(e => e.status !== 'resolved');
}
/**
* 获取最近的事件
* @param {number} limit
* @returns {object[]}
*/
function getRecent(limit) {
const data = loadBulletin();
return data.events
.sort((a, b) => (b.timestamp || '').localeCompare(a.timestamp || ''))
.slice(0, limit || 10);
}
/**
* 清理过期事件
* @param {number} maxAge - 最大存活时间毫秒
* @returns {number} 被清理的数量
*/
function prune(maxAge) {
const data = loadBulletin();
const cutoff = Date.now() - maxAge;
const before = data.events.length;
data.events = data.events.filter(e => {
const ts = new Date(e.timestamp).getTime();
return ts >= cutoff;
});
const pruned = before - data.events.length;
if (pruned > 0) {
saveBulletin(data);
}
return pruned;
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
console.log('📋 Bulletin Manager · 公告板管理器\n');
const active = getActive();
console.log(` 活跃事件: ${active.length}`);
const recent = getRecent(5);
console.log(` 最近事件: ${recent.length}`);
}
module.exports = { addEvent, updateStatus, getActive, getRecent, prune, loadBulletin, saveBulletin };

View File

@ -0,0 +1,105 @@
// scripts/agents/escalation-router.js
// Escalation Router · 三级升级路由器
// ZY-P1-ESC-001 · Phase 1 · Escalation System
// 版权:国作登字-2026-A-00037559
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const RULES_PATH = path.join(ROOT, 'config/escalation-rules.json');
/**
* 加载升级规则
* @returns {object}
*/
function loadRules() {
try {
return JSON.parse(fs.readFileSync(RULES_PATH, 'utf8'));
} catch (_) {
// 内置默认规则 · 规则文件缺失时的安全网
return {
rules: {
L1: { types: ['test_flaky', 'lint_error', 'stub_missing', 'format_issue'], handler: 'auto_repair', notify: null },
L2: { types: ['test_persistent_fail', 'dependency_conflict', 'performance_regression', 'security_alert'], handler: 'manual', notify: { target: 'DEV-002-肥猫' } },
L3: { types: ['balance_long_drift', 'ontology_conflict', 'cross_system_architecture'], handler: 'manual', notify: { target: 'TCS-0002∞-冰朔' } }
}
};
}
}
/**
* 分类问题级别 · 三级分诊
* @param {{ type: string }} issue
* @returns {{ level: string, name: string, handler: string }}
*/
function classify(issue) {
const rules = loadRules().rules;
for (const [level, rule] of Object.entries(rules)) {
if (rule.types.includes(issue.type)) {
return {
level,
name: rule.name || level,
handler: rule.handler || 'manual'
};
}
}
// 未知类型默认升级到 L2
return { level: 'L2', name: '技术干预', handler: 'manual' };
}
/**
* 路由问题到对应处理者
* @param {{ type: string, description: string }} issue
* @returns {{ level: string, handler: string, notify: object|null }}
*/
function route(issue) {
const rules = loadRules().rules;
const classification = classify(issue);
const rule = rules[classification.level];
return {
level: classification.level,
handler: classification.handler,
notify: rule ? rule.notify : null
};
}
/**
* 完整升级流程 · 分类 路由 执行
* @param {{ type: string, description: string, source: string }} issue
* @returns {{ classification: object, routing: object, timestamp: string }}
*/
function escalate(issue) {
const classification = classify(issue);
const routing = route(issue);
return {
classification,
routing,
issue,
timestamp: new Date().toISOString()
};
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
console.log('🔀 Escalation Router · 升级路由器\n');
const testIssues = [
{ type: 'test_flaky', description: '测试偶发失败' },
{ type: 'security_alert', description: '安全告警' },
{ type: 'ontology_conflict', description: '本体冲突' }
];
for (const issue of testIssues) {
const result = escalate(issue);
console.log(` ${issue.type}${result.classification.level} (${result.classification.name})`);
}
}
module.exports = { classify, route, escalate, loadRules };

View File

@ -0,0 +1,132 @@
// scripts/agents/notify-adapter.js
// Notify Adapter · 通知适配器
// ZY-P1-ESC-002 · Phase 1 · Escalation System
// 版权:国作登字-2026-A-00037559
'use strict';
const https = require('https');
// ── 通知目标映射 ─────────────────────────────────────────────────────────
const TARGETS = {
'DEV-002-肥猫': '@qinfendebingshuo',
'TCS-0002∞-冰朔': '@qinfendebingshuo'
};
/**
* 格式化通知内容 · 转为 markdown
* @param {string} target - 通知目标 ID
* @param {{ type: string, description: string }} issue
* @returns {string} markdown 格式的通知
*/
function formatNotification(target, issue) {
const mention = TARGETS[target] || target;
const now = new Date().toISOString();
return [
`## 🔔 升级通知`,
'',
`**目标**: ${mention}`,
`**类型**: ${issue.type || '未知'}`,
`**描述**: ${issue.description || '无描述'}`,
`**来源**: ${issue.source || '系统自动检测'}`,
`**时间**: ${now}`,
'',
'---',
'_由铸渊升级路由器自动生成_'
].join('\n');
}
/**
* 发送通知
* @param {string} target - 通知目标 ID
* @param {{ type: string, description: string }} issue
* @returns {Promise<{ target: string, channel: string, messageId: string, timestamp: string }>}
*/
async function notify(target, issue) {
const timestamp = new Date().toISOString();
const content = formatNotification(target, issue);
const token = process.env.GITHUB_TOKEN;
const repo = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
if (!token) {
// 无 token 时回退到控制台输出
console.log(`\n📢 通知(控制台模式):\n${content}\n`);
return {
target,
channel: 'console',
messageId: `console-${Date.now()}`,
timestamp
};
}
// 通过 GitHub Issues API 创建 issue
const [owner, repoName] = repo.split('/');
const body = JSON.stringify({
title: `🔔 [${issue.type}] ${issue.description || '升级通知'}`,
body: content,
labels: ['escalation', issue.type || 'unknown']
});
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
path: `/repos/${owner}/${repoName}/issues`,
method: 'POST',
headers: {
'User-Agent': 'zhuyuan-notify-adapter',
'Accept': 'application/vnd.github.v3+json',
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => { data += chunk; });
res.on('end', () => {
try {
const parsed = JSON.parse(data);
resolve({
target,
channel: 'github_issue',
messageId: `issue-${parsed.number || 'unknown'}`,
timestamp
});
} catch (e) {
resolve({
target,
channel: 'github_issue',
messageId: `issue-error`,
timestamp
});
}
});
});
req.on('error', () => {
resolve({
target,
channel: 'github_issue_failed',
messageId: `error-${Date.now()}`,
timestamp
});
});
req.write(body);
req.end();
});
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
console.log('📢 Notify Adapter · 通知适配器\n');
const formatted = formatNotification('DEV-002-肥猫', {
type: 'test_persistent_fail',
description: '持续性测试失败'
});
console.log(formatted);
}
module.exports = { notify, formatNotification, TARGETS };

View File

@ -0,0 +1,296 @@
// scripts/agents/readme-generator.js
// README Generator · 首页动态布局引擎
// ZY-P1-README-002 · Phase 1 · README Management Agent
// 版权:国作登字-2026-A-00037559
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
// ── 皮肤系统 · 情绪驱动界面 ─────────────────────────────────────────────
const SKINS = {
CALM: { name: '🌊 湖面平静', emoji: ['🌊', '✅', '💎'], badgeColor: 'blue' },
BUILDING: { name: '⚡ 建设中', emoji: ['⚡', '🔨', '🚧'], badgeColor: 'yellow' },
ATTENTION: { name: '🔥 需要关注', emoji: ['🔥', '⚠️', '🔧'], badgeColor: 'orange' },
EMERGENCY: { name: '🚨 紧急状态', emoji: ['🚨', '❌', '🛑'], badgeColor: 'red' },
CELEBRATION: { name: '🎉 庆祝模式', emoji: ['🎉', '🏆', '✨'], badgeColor: 'brightgreen' }
};
/**
* 根据系统状态选择皮肤
* @param {object} systemState
* @returns {object} skin
*/
function selectSkin(systemState) {
if (!systemState) return SKINS.CALM;
// 紧急状态:有高级别告警
if (systemState.alert && systemState.alert.level === 'high') {
return SKINS.EMERGENCY;
}
// 需要关注:有中级别告警
if (systemState.alert && systemState.alert.level === 'medium') {
return SKINS.ATTENTION;
}
// 庆祝模式:平衡度完美且有里程碑
if (systemState.balanced && systemState.milestone) {
return SKINS.CELEBRATION;
}
// 建设中:有活跃开发
if (systemState.building || (systemState.recentUpdates && systemState.recentUpdates.length > 0)) {
return SKINS.BUILDING;
}
return SKINS.CALM;
}
/**
* 生成布局排序 · 按优先级排列版块
* @param {object} systemState
* @returns {string[]} 排序后的版块名称列表
*/
function generateLayout(systemState) {
const sections = [];
// P0: 紧急状态优先
if (systemState && systemState.alert && systemState.alert.level === 'high') {
sections.push('critical_alert');
}
// P1: 平衡偏差
if (systemState && !systemState.balanced) {
sections.push('balance_drift');
}
// P2: 最近合并
if (systemState && systemState.recentUpdates && systemState.recentUpdates.length > 0) {
sections.push('recent_updates');
}
// P3: 常规内容
sections.push('dashboard', 'twin_balance', 'bulletin');
return sections;
}
/**
* 渲染系统仪表板
* @param {object} state
* @returns {string} markdown
*/
function renderDashboard(state) {
const skin = selectSkin(state);
const now = new Date().toISOString().split('T')[0];
let md = `## ${skin.emoji[0]} 系统状态\n\n`;
md += `| 指标 | 状态 |\n`;
md += `|------|------|\n`;
md += `| 皮肤 | ${skin.name} |\n`;
md += `| 更新时间 | ${now} |\n`;
if (state && state.balance !== undefined) {
md += `| 平衡度 | ${(state.balance * 100).toFixed(1)}% |\n`;
}
if (state && state.alert) {
md += `| 告警 | ${state.alert.level} - ${state.alert.laggingSide} |\n`;
}
return md;
}
/**
* 渲染双子天平可视化
* @param {object} state
* @returns {string} markdown
*/
function renderTwinBalance(state) {
let md = '## ⚖️ 双子天平\n\n';
if (!state || !state.metrics) {
md += '_暂无天平数据_\n';
return md;
}
const left = state.metrics.left || {};
const right = state.metrics.right || {};
md += '| 维度 | EXE-Engine | Grid-DB |\n';
md += '|------|-----------|--------|\n';
const dims = ['taskCompletion', 'testPassRate', 'codeCoverage', 'securityScore'];
const dimNames = { taskCompletion: '任务完成', testPassRate: '测试通过', codeCoverage: '代码覆盖', securityScore: '安全扫描' };
for (const dim of dims) {
const lv = left.dimensions ? left.dimensions[dim] : 0;
const rv = right.dimensions ? right.dimensions[dim] : 0;
const lBar = generateProgressBar(lv);
const rBar = generateProgressBar(rv);
md += `| ${dimNames[dim]} | ${lBar} ${(lv * 100).toFixed(0)}% | ${rBar} ${(rv * 100).toFixed(0)}% |\n`;
}
md += `\n**综合**: EXE=${left.composite || 0} · GDB=${right.composite || 0} · 平衡度=${state.balance || 0}\n`;
return md;
}
/**
* 生成文本进度条
* @param {number} value - 0~1
* @returns {string}
*/
function generateProgressBar(value) {
const filled = Math.round((value || 0) * 10);
const empty = 10 - filled;
return '█'.repeat(filled) + '░'.repeat(empty);
}
/**
* 渲染公告板
* @param {object[]} events
* @returns {string} markdown
*/
function renderBulletin(events) {
let md = '## 📋 公告板\n\n';
if (!events || events.length === 0) {
md += '_暂无公告_\n';
return md;
}
md += '| 时间 | 事件 | 状态 | 处理 |\n';
md += '|------|------|------|------|\n';
for (const evt of events.slice(0, 10)) {
const time = evt.timestamp ? evt.timestamp.split('T')[0] : '-';
md += `| ${time} | ${evt.event || '-'} | ${evt.status || '-'} | ${evt.handler || '-'} |\n`;
}
return md;
}
/**
* 渲染最近更新
* @param {object[]} updates
* @returns {string} markdown
*/
function renderRecentUpdates(updates) {
let md = '## 📝 最近更新\n\n';
if (!updates || updates.length === 0) {
md += '_暂无更新_\n';
return md;
}
for (const u of updates.slice(0, 5)) {
md += `- ${u.emoji || '📌'} ${u.description || u}\n`;
}
return md;
}
/**
* 生成完整 README
* @param {object} systemState
* @returns {string} markdown
*/
function generateReadme(systemState) {
const state = systemState || {};
const skin = selectSkin(state);
const layout = generateLayout(state);
let md = `# ${skin.emoji[0]} 光湖 · GuangHuLab\n\n`;
md += `> 数字地球本体论 · Digital Earth Ontology\n`;
md += `> 版权:国作登字-2026-A-00037559\n\n`;
for (const section of layout) {
switch (section) {
case 'critical_alert':
md += `## 🚨 紧急告警\n\n`;
if (state.alert) {
md += `**${state.alert.level}**: ${state.alert.laggingSide} 落后 · 偏差 ${state.alert.deficit}\n\n`;
md += `> ${state.alert.recommendation}\n\n`;
}
break;
case 'balance_drift':
md += `## ⚠️ 平衡偏差\n\n`;
md += `偏差值: ${state.drift || 0} · 落后侧: ${state.alert ? state.alert.laggingSide : '未知'}\n\n`;
break;
case 'dashboard':
md += renderDashboard(state);
md += '\n';
break;
case 'twin_balance':
md += renderTwinBalance(state);
md += '\n';
break;
case 'bulletin':
md += renderBulletin(state.events || []);
md += '\n';
break;
case 'recent_updates':
md += renderRecentUpdates(state.recentUpdates || []);
md += '\n';
break;
}
}
md += '---\n';
md += `_由 铸渊 (AG-ZY-01) 自动生成 · ${new Date().toISOString().split('T')[0]}_\n`;
return md;
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
const args = process.argv.slice(2);
if (args.includes('--auto')) {
// 自动模式:读取天眼数据生成 README
console.log('📡 README Generator · 自动更新模式\n');
let state = {};
const statusPath = path.join(ROOT, '.github/tianyen/twin-status.json');
const bulletinPath = path.join(ROOT, '.github/tianyen/bulletin-data.json');
if (fs.existsSync(statusPath)) {
try {
state = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
} catch (_) { /* 忽略解析错误 */ }
}
if (fs.existsSync(bulletinPath)) {
try {
const bulletin = JSON.parse(fs.readFileSync(bulletinPath, 'utf8'));
state.events = bulletin.events || [];
} catch (_) { /* 忽略解析错误 */ }
}
state.building = true;
const readme = generateReadme(state);
fs.writeFileSync(path.join(ROOT, 'README.md'), readme, 'utf8');
console.log('✅ README.md 已更新');
} else {
console.log('📡 README Generator · 预览模式\n');
const preview = generateReadme({ building: true });
console.log(preview);
}
}
module.exports = {
SKINS,
selectSkin,
generateLayout,
renderDashboard,
renderTwinBalance,
renderBulletin,
renderRecentUpdates,
generateReadme,
generateProgressBar
};

View File

@ -0,0 +1,426 @@
// scripts/agents/tests/governance.test.js
// Governance System · 治理系统综合测试
// ZY-TEST-GOV-001 · Phase 1
// 版权:国作登字-2026-A-00037559
'use strict';
const path = require('path');
const fs = require('fs');
let passed = 0;
let failed = 0;
function assert(condition, message) {
if (condition) {
passed++;
console.log(`${message}`);
} else {
failed++;
console.error(`${message}`);
}
}
console.log('🏛️ Governance System 综合测试\n');
// ══════════════════════════════════════════════════════════════════════════
// 测试 1: Twin Merge Engine
// ══════════════════════════════════════════════════════════════════════════
console.log('── 测试 1: Twin Merge Engine ──');
const { merge, computeComposite } = require('../twin-merge');
const leftMetrics = { taskCompletion: 0.9, testPassRate: 0.85, codeCoverage: 1.0, securityScore: 1.0 };
const rightMetrics = { taskCompletion: 0.95, testPassRate: 0.9, codeCoverage: 1.0, securityScore: 1.0 };
const mergeResult = merge(leftMetrics, rightMetrics);
assert(typeof mergeResult.left === 'object', 'merge 返回 left 对象');
assert(typeof mergeResult.right === 'object', 'merge 返回 right 对象');
assert(typeof mergeResult.balance === 'number', 'merge 返回 balance 数值');
assert(mergeResult.balance >= 0 && mergeResult.balance <= 1, 'balance 在 0-1 范围内');
assert(typeof mergeResult.timestamp === 'string', 'merge 返回 timestamp');
// 验证 composite 计算
const expectedLeft = Math.round((0.4*0.9 + 0.25*0.85 + 0.2*1.0 + 0.15*1.0) * 10000) / 10000;
assert(mergeResult.left.composite === expectedLeft, `左翼 composite = ${expectedLeft} (实际: ${mergeResult.left.composite})`);
// 完全相同的指标 → balance = 1.0
const perfectMerge = merge(leftMetrics, leftMetrics);
assert(perfectMerge.balance === 1, '相同指标 → 完美平衡 (balance=1.0)');
// computeComposite 单独测试
const comp = computeComposite({ taskCompletion: 1, testPassRate: 1, codeCoverage: 1, securityScore: 1 });
assert(comp === 1, '全满分 composite = 1.0');
// ══════════════════════════════════════════════════════════════════════════
// 测试 2: Balance Checker
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 2: Balance Checker ──');
const { check } = require('../balance-checker');
// 完美平衡
const balancedResult = check(perfectMerge);
assert(balancedResult.balanced === true, '完美平衡 → balanced=true');
assert(balancedResult.alert === null, '完美平衡 → alert=null');
assert(balancedResult.drift === 0, '完美平衡 → drift=0');
// 有偏差
const driftResult = check(mergeResult);
assert(typeof driftResult.balanced === 'boolean', 'check 返回 balanced 布尔值');
assert(typeof driftResult.drift === 'number', 'check 返回 drift 数值');
// 大偏差测试
const bigDrift = merge(
{ taskCompletion: 0.5, testPassRate: 0.5, codeCoverage: 0.5, securityScore: 0.5 },
{ taskCompletion: 1.0, testPassRate: 1.0, codeCoverage: 1.0, securityScore: 1.0 }
);
const bigCheck = check(bigDrift);
assert(bigCheck.balanced === false, '大偏差 → balanced=false');
assert(bigCheck.alert !== null, '大偏差 → 有告警');
assert(bigCheck.alert.level === 'high', '大偏差 (0.5) → high 级别');
// 小偏差测试
const smallDrift = merge(
{ taskCompletion: 0.98, testPassRate: 0.98, codeCoverage: 1.0, securityScore: 1.0 },
{ taskCompletion: 1.0, testPassRate: 1.0, codeCoverage: 1.0, securityScore: 1.0 }
);
const smallCheck = check(smallDrift);
if (!smallCheck.balanced && smallCheck.alert) {
assert(smallCheck.alert.level === 'low', '小偏差 → low 级别');
} else {
assert(true, '小偏差 → 近似平衡');
}
// ══════════════════════════════════════════════════════════════════════════
// 测试 3: Auto-Repair Classifier
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 3: Auto-Repair Classifier ──');
const { diagnose, classify, verify } = require('../auto-repair');
const lowAlert = { level: 'low', laggingSide: 'left', deficit: 0.02 };
const medAlert = { level: 'medium', laggingSide: 'right', deficit: 0.1 };
const highAlert = { level: 'high', laggingSide: 'left', deficit: 0.3 };
const lowDiag = diagnose(lowAlert);
assert(lowDiag.severity === 'low', 'low alert → severity=low');
const medDiag = diagnose(medAlert);
assert(medDiag.severity === 'medium', 'medium alert → severity=medium');
const highDiag = diagnose(highAlert);
assert(highDiag.severity === 'high', 'high alert → severity=high');
const lowClass = classify(lowDiag);
assert(lowClass.level === 'L1', 'low severity → L1 自动修复');
assert(lowClass.autoFixable === true, 'L1 → autoFixable=true');
const medClass = classify(medDiag);
assert(medClass.level === 'L2', 'medium severity → L2 技术干预');
assert(medClass.autoFixable === false, 'L2 → autoFixable=false');
const highClass = classify(highDiag);
assert(highClass.level === 'L3', 'high severity → L3 架构干预');
// verify 测试
const verifySuccess = verify({ success: true });
assert(verifySuccess.verified === true, '成功修复 → verified=true');
const verifyFail = verify({ success: false, level: 'L2' });
assert(verifyFail.verified === false, '未修复 → verified=false');
// null alert
const nullDiag = diagnose(null);
assert(nullDiag.severity === 'none', 'null alert → severity=none');
// ══════════════════════════════════════════════════════════════════════════
// 测试 4: Escalation Router
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 4: Escalation Router ──');
const { classify: escClassify, route, escalate } = require('../escalation-router');
// L1 类型
assert(escClassify({ type: 'test_flaky' }).level === 'L1', 'test_flaky → L1');
assert(escClassify({ type: 'lint_error' }).level === 'L1', 'lint_error → L1');
assert(escClassify({ type: 'stub_missing' }).level === 'L1', 'stub_missing → L1');
assert(escClassify({ type: 'format_issue' }).level === 'L1', 'format_issue → L1');
// L2 类型
assert(escClassify({ type: 'test_persistent_fail' }).level === 'L2', 'test_persistent_fail → L2');
assert(escClassify({ type: 'dependency_conflict' }).level === 'L2', 'dependency_conflict → L2');
assert(escClassify({ type: 'security_alert' }).level === 'L2', 'security_alert → L2');
// L3 类型
assert(escClassify({ type: 'balance_long_drift' }).level === 'L3', 'balance_long_drift → L3');
assert(escClassify({ type: 'ontology_conflict' }).level === 'L3', 'ontology_conflict → L3');
assert(escClassify({ type: 'cross_system_architecture' }).level === 'L3', 'cross_system_architecture → L3');
// 路由测试
const l1Route = route({ type: 'test_flaky' });
assert(l1Route.handler === 'auto_repair', 'L1 handler = auto_repair');
assert(l1Route.notify === null, 'L1 notify = null');
const l2Route = route({ type: 'security_alert' });
assert(l2Route.notify !== null, 'L2 有通知目标');
// 完整升级流程
const escResult = escalate({ type: 'ontology_conflict', description: '测试' });
assert(escResult.classification.level === 'L3', 'escalate 返回正确分类');
assert(typeof escResult.timestamp === 'string', 'escalate 返回 timestamp');
// ══════════════════════════════════════════════════════════════════════════
// 测试 5: Bulletin Manager
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 5: Bulletin Manager ──');
const { addEvent, updateStatus, getActive, getRecent, prune, loadBulletin, saveBulletin } = require('../bulletin-manager');
// 清理测试环境
const ROOT = path.resolve(__dirname, '../../..');
const bulletinTestPath = path.join(ROOT, '.github/tianyen/bulletin-data.json');
const bulletinBackup = fs.existsSync(bulletinTestPath) ? fs.readFileSync(bulletinTestPath, 'utf8') : null;
// 重置
saveBulletin({ events: [] });
// 添加事件
const added1 = addEvent({ id: 'TEST-001', event: '测试事件1', status: 'active', handler: 'auto', priority: 1 });
assert(added1 === true, 'addEvent 成功添加');
const added2 = addEvent({ id: 'TEST-002', event: '测试事件2', status: 'active', handler: 'manual', priority: 2 });
assert(added2 === true, 'addEvent 添加第二个事件');
// 去重
const addDup = addEvent({ id: 'TEST-001', event: '重复事件', status: 'active' });
assert(addDup === false, 'addEvent 去重:相同 id 不重复添加');
// 获取活跃事件
const active = getActive();
assert(active.length === 2, `getActive 返回 2 个活跃事件 (实际: ${active.length})`);
// 更新状态
const updated = updateStatus('TEST-001', 'resolved');
assert(updated === true, 'updateStatus 成功更新');
const activeAfter = getActive();
assert(activeAfter.length === 1, `resolved 后活跃事件 = 1 (实际: ${activeAfter.length})`);
// 获取最近事件
const recent = getRecent(5);
assert(recent.length === 2, `getRecent 返回所有事件 (含 resolved)`);
// 清理过期事件(用极小 maxAge 测试)
addEvent({ id: 'TEST-OLD', timestamp: '2020-01-01T00:00:00Z', event: '旧事件', status: 'active' });
const pruned = prune(1000); // 1秒前的都清理
assert(pruned >= 1, `prune 清理了至少 1 条过期事件 (清理了: ${pruned})`);
// 恢复测试数据
if (bulletinBackup) {
fs.writeFileSync(bulletinTestPath, bulletinBackup, 'utf8');
} else {
saveBulletin({ events: [] });
}
// ══════════════════════════════════════════════════════════════════════════
// 测试 6: Actions Parser
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 6: Actions Parser ──');
const { parseWorkflowOutput, parsePRMerge, parseTestReport } = require('../actions-parser');
// parseWorkflowOutput
const wfOutput = ' ✅ test1\n ✅ test2\n ❌ test3\n Error: something\n';
const wfResult = parseWorkflowOutput(wfOutput);
assert(wfResult.tests.passed === 2, 'parseWorkflowOutput: 2 passed');
assert(wfResult.tests.failed === 1, 'parseWorkflowOutput: 1 failed');
assert(wfResult.tests.total === 3, 'parseWorkflowOutput: 3 total');
assert(wfResult.errors.length >= 1, 'parseWorkflowOutput: 检测到错误行');
// 空输入
const emptyResult = parseWorkflowOutput('');
assert(emptyResult.tests.total === 0, 'parseWorkflowOutput: 空输入 → 0 total');
const nullResult = parseWorkflowOutput(null);
assert(nullResult.tests.total === 0, 'parseWorkflowOutput: null 输入 → 0 total');
// parsePRMerge
const prResult = parsePRMerge({ title: 'Fix bug', number: 42, author: 'test', files: ['a.js', 'b.js'] });
assert(prResult.filesChanged === 2, 'parsePRMerge: 2 files changed');
assert(prResult.summary.includes('#42'), 'parsePRMerge: 包含 PR 编号');
const nullPr = parsePRMerge(null);
assert(nullPr.filesChanged === 0, 'parsePRMerge: null → 0 files');
// parseTestReport
const reportResult = parseTestReport('✅ a\n✅ b\n❌ c');
assert(reportResult.passed === 2, 'parseTestReport: 2 passed');
assert(reportResult.failed === 1, 'parseTestReport: 1 failed');
assert(reportResult.passRate > 0.6 && reportResult.passRate < 0.7, 'parseTestReport: passRate ~0.6667');
// ══════════════════════════════════════════════════════════════════════════
// 测试 7: TianYen Scheduler
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 7: TianYen Scheduler ──');
const { evaluateSchedule, setFrequency, FREQUENCY_LEVELS } = require('../../tianyen/scheduler');
// 无指标 → default
const defEval = evaluateSchedule('AG-TEST', null);
assert(defEval.level === 'default', '无指标 → default 频率');
// 高偏差 → max
const maxEval = evaluateSchedule('AG-TEST', { drift: 0.2 });
assert(maxEval.level === 'max', '高偏差 (0.2) → max 频率');
// 中偏差 → high
const highEval = evaluateSchedule('AG-TEST', { drift: 0.08 });
assert(highEval.level === 'high', '中偏差 (0.08) → high 频率');
// 完美平衡 → low
const lowEval = evaluateSchedule('AG-TEST', { balance: 1.0, drift: 0 });
assert(lowEval.level === 'low', '完美平衡 → low 频率');
// 活跃变更 → high/medium
const activeEval = evaluateSchedule('AG-TEST', { recentChanges: 15 });
assert(activeEval.level === 'high', '大量变更 → high 频率');
const medEval = evaluateSchedule('AG-TEST', { recentChanges: 5 });
assert(medEval.level === 'medium', '中等变更 → medium 频率');
// FREQUENCY_LEVELS 完整性
assert(Object.keys(FREQUENCY_LEVELS).length === 5, '5 个频率级别');
assert(FREQUENCY_LEVELS.max.cron === '*/30 * * * *', 'max cron 正确');
// ══════════════════════════════════════════════════════════════════════════
// 测试 8: Agent Checkin Module
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 8: Agent Checkin Module ──');
const { checkin, checkout, getLastCheckin, detectTimeout, loadCheckinLog, saveCheckinLog } = require('../../tianyen/agent-checkin-module');
// 备份
const checkinLogPath = path.join(ROOT, '.github/tianyen/checkin-log.json');
const checkinBackup = fs.existsSync(checkinLogPath) ? fs.readFileSync(checkinLogPath, 'utf8') : null;
// 重置
saveCheckinLog({ version: '1.0.0', checkins: {} });
// checkin
const ci = checkin('AG-TEST-001');
assert(ci.agentId === 'AG-TEST-001', 'checkin 返回正确 agentId');
assert(ci.status === 'running', 'checkin status = running');
assert(typeof ci.timestamp === 'string', 'checkin 返回 timestamp');
// getLastCheckin
const last = getLastCheckin('AG-TEST-001');
assert(last !== null, 'getLastCheckin 返回记录');
assert(last.status === 'running', 'getLastCheckin status = running');
// detectTimeout — 刚签到,不应超时
const timeoutCheck = detectTimeout('AG-TEST-001', 600000);
assert(timeoutCheck.timedOut === false, '刚签到 → 未超时');
// detectTimeout — 用极小 maxDuration 强制超时
const shortTimeout = detectTimeout('AG-TEST-001', 0);
assert(shortTimeout.timedOut === true, '0ms maxDuration → 超时');
// checkout
const co = checkout('AG-TEST-001', 'success', { testsRun: 10 });
assert(co.status === 'success', 'checkout status = success');
assert(typeof co.duration === 'number', 'checkout 返回 duration');
// checkout 后不再检测为运行中
const afterCo = detectTimeout('AG-TEST-001', 0);
assert(afterCo.timedOut === false, 'checkout 后不超时');
// 不存在的 Agent
const noAgent = getLastCheckin('AG-NONEXISTENT');
assert(noAgent === null, '不存在的 Agent → null');
const noTimeout = detectTimeout('AG-NONEXISTENT');
assert(noTimeout.timedOut === false, '不存在的 Agent → 不超时');
// 恢复
if (checkinBackup) {
fs.writeFileSync(checkinLogPath, checkinBackup, 'utf8');
} else {
saveCheckinLog({ version: '1.0.0', checkins: {} });
}
// ══════════════════════════════════════════════════════════════════════════
// 测试 9: Twin Collector (不执行实际测试,只验证模块导出)
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 9: Twin Collector 模块导出 ──');
const collector = require('../twin-collector');
assert(typeof collector.collectMetrics === 'function', 'collectMetrics 是函数');
assert(typeof collector.collectLeft === 'function', 'collectLeft 是函数');
assert(typeof collector.collectRight === 'function', 'collectRight 是函数');
assert(typeof collector.collectBoth === 'function', 'collectBoth 是函数');
assert(typeof collector.WEIGHTS === 'object', 'WEIGHTS 已导出');
assert(collector.WEIGHTS.taskCompletion === 0.4, 'taskCompletion 权重 = 0.4');
assert(collector.WEIGHTS.testPassRate === 0.25, 'testPassRate 权重 = 0.25');
// ══════════════════════════════════════════════════════════════════════════
// 测试 10: README Generator (SKINS 和布局)
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 10: README Generator ──');
const {
SKINS, selectSkin, generateLayout, renderDashboard,
renderTwinBalance, renderBulletin, generateReadme
} = require('../readme-generator');
assert(Object.keys(SKINS).length === 5, '5 种皮肤');
assert(SKINS.CALM.badgeColor === 'blue', 'CALM badgeColor = blue');
assert(SKINS.EMERGENCY.badgeColor === 'red', 'EMERGENCY badgeColor = red');
// selectSkin
assert(selectSkin(null).name === SKINS.CALM.name, 'null state → CALM');
assert(selectSkin({ alert: { level: 'high' } }).name === SKINS.EMERGENCY.name, 'high alert → EMERGENCY');
assert(selectSkin({ alert: { level: 'medium' } }).name === SKINS.ATTENTION.name, 'medium alert → ATTENTION');
assert(selectSkin({ building: true }).name === SKINS.BUILDING.name, 'building → BUILDING');
// generateLayout
const emergencyLayout = generateLayout({ alert: { level: 'high' }, balanced: false });
assert(emergencyLayout[0] === 'critical_alert', '紧急布局首项 = critical_alert');
const calmLayout = generateLayout({});
assert(calmLayout.includes('dashboard'), '常规布局包含 dashboard');
// renderDashboard
const dashMd = renderDashboard({});
assert(dashMd.includes('系统状态'), 'dashboard 包含系统状态');
// renderTwinBalance
const twinMd = renderTwinBalance({ metrics: { left: { composite: 0.9, dimensions: { taskCompletion: 0.9, testPassRate: 0.8, codeCoverage: 1.0, securityScore: 1.0 } }, right: { composite: 0.95, dimensions: { taskCompletion: 0.95, testPassRate: 0.9, codeCoverage: 1.0, securityScore: 1.0 } } }, balance: 0.95 });
assert(twinMd.includes('双子天平'), 'twin balance 包含标题');
assert(twinMd.includes('EXE-Engine'), 'twin balance 包含 EXE-Engine');
// renderBulletin
const bulletinMd = renderBulletin([]);
assert(bulletinMd.includes('暂无公告'), '空公告 → 暂无公告');
const bulletinMd2 = renderBulletin([{ timestamp: '2026-01-01T00:00:00Z', event: '测试', status: 'active', handler: 'auto' }]);
assert(bulletinMd2.includes('测试'), '公告包含事件内容');
// generateReadme
const readme = generateReadme({});
assert(readme.includes('光湖'), 'README 包含光湖');
assert(readme.includes('版权'), 'README 包含版权');
// ══════════════════════════════════════════════════════════════════════════
// 结果汇总
// ══════════════════════════════════════════════════════════════════════════
console.log(`\n${'═'.repeat(60)}`);
console.log(`🏛️ 治理系统测试结果: ${passed} passed, ${failed} failed`);
if (failed > 0) {
console.log(`\n${failed} 个测试失败`);
process.exit(1);
} else {
console.log(`\n✅ 全部 ${passed} 个测试通过`);
}

View File

@ -0,0 +1,99 @@
// scripts/agents/twin-balance-workflow.js
// Twin Balance Workflow · 双子天平主流程
// ZY-P1-TWIN-005 · Phase 1 · Twin Balance Agent
// 版权:国作登字-2026-A-00037559
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const TIANYEN_DIR = path.join(ROOT, '.github/tianyen');
const STATUS_PATH = path.join(TIANYEN_DIR, 'twin-status.json');
const { collectBoth } = require('./twin-collector');
const { merge } = require('./twin-merge');
const { check } = require('./balance-checker');
const { diagnose, classify, repair, verify } = require('./auto-repair');
/**
* 运行完整的双子天平检查流程
* 采集 融合 校验 修复如需要
* @returns {object} 完整结果
*/
function runPipeline() {
console.log('⚖️ Twin Balance Check · 双子天平校验流程\n');
// Step 1: 采集双翼数据
console.log('── Step 1: 数据采集 ──');
const data = collectBoth();
console.log(` 左翼 (EXE-Engine): tests=${data.left.details.totalPassed}/${data.left.details.totalTests}`);
console.log(` 右翼 (Grid-DB): tests=${data.right.details.totalPassed}/${data.right.details.totalTests}`);
// Step 2: 融合分析
console.log('\n── Step 2: 融合分析 ──');
const merged = merge(data.left, data.right);
console.log(` 左翼 composite: ${merged.left.composite}`);
console.log(` 右翼 composite: ${merged.right.composite}`);
console.log(` 平衡度: ${merged.balance}`);
// Step 3: 平衡校验
console.log('\n── Step 3: 平衡校验 ──');
const checkResult = check(merged);
if (checkResult.balanced) {
console.log(' ✅ 天平完全平衡');
} else {
console.log(` ⚠️ 偏差等级: ${checkResult.alert.level}`);
console.log(` 📉 落后侧: ${checkResult.alert.laggingSide}`);
console.log(` 📊 偏差值: ${checkResult.drift}`);
}
// Step 4: 修复(如需要)
let repairResult = null;
let verifyResult = null;
if (!checkResult.balanced && checkResult.alert) {
console.log('\n── Step 4: 自动修复 ──');
const diag = diagnose(checkResult.alert);
const cls = classify(diag);
console.log(` 诊断: ${diag.cause}`);
console.log(` 分类: ${cls.level} - ${cls.name}`);
repairResult = repair(diag);
console.log(` 修复: ${repairResult.action}`);
verifyResult = verify(repairResult);
console.log(` 验证: ${verifyResult.message}`);
}
// 汇总状态
const status = {
timestamp: new Date().toISOString(),
metrics: {
left: merged.left,
right: merged.right
},
balance: merged.balance,
balanced: checkResult.balanced,
drift: checkResult.drift,
alert: checkResult.alert,
repair: repairResult,
verification: verifyResult
};
// 保存状态文件
if (!fs.existsSync(TIANYEN_DIR)) {
fs.mkdirSync(TIANYEN_DIR, { recursive: true });
}
fs.writeFileSync(STATUS_PATH, JSON.stringify(status, null, 2), 'utf8');
console.log(`\n✅ 状态已写入 ${STATUS_PATH}`);
return status;
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
runPipeline();
}
module.exports = { runPipeline };

View File

@ -0,0 +1,157 @@
// 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 };

View File

@ -0,0 +1,72 @@
// scripts/agents/twin-merge.js
// Twin Merge Engine · 双子融合引擎
// ZY-P1-TWIN-002 · Phase 1 · Twin Balance Agent
// 版权:国作登字-2026-A-00037559
'use strict';
const { WEIGHTS } = require('./twin-collector');
/**
* 计算综合分数加权求和
* @param {{ taskCompletion: number, testPassRate: number, codeCoverage: number, securityScore: number }} metrics
* @returns {number}
*/
function computeComposite(metrics) {
return Math.round((
WEIGHTS.taskCompletion * metrics.taskCompletion +
WEIGHTS.testPassRate * metrics.testPassRate +
WEIGHTS.codeCoverage * metrics.codeCoverage +
WEIGHTS.securityScore * metrics.securityScore
) * 10000) / 10000;
}
/**
* 融合左右两翼指标 · 天平合一
* @param {object} leftMetrics - EXE-Engine 指标
* @param {object} rightMetrics - Grid-DB 指标
* @returns {{ left: object, right: object, balance: number, timestamp: string }}
*/
function merge(leftMetrics, rightMetrics) {
const leftComposite = computeComposite(leftMetrics);
const rightComposite = computeComposite(rightMetrics);
// 平衡度 = 1 - |左-右| — 完全一致时为 1.0
const balance = Math.round((1 - Math.abs(leftComposite - rightComposite)) * 10000) / 10000;
return {
left: {
composite: leftComposite,
dimensions: {
taskCompletion: leftMetrics.taskCompletion,
testPassRate: leftMetrics.testPassRate,
codeCoverage: leftMetrics.codeCoverage,
securityScore: leftMetrics.securityScore
}
},
right: {
composite: rightComposite,
dimensions: {
taskCompletion: rightMetrics.taskCompletion,
testPassRate: rightMetrics.testPassRate,
codeCoverage: rightMetrics.codeCoverage,
securityScore: rightMetrics.securityScore
}
},
balance,
timestamp: new Date().toISOString()
};
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
const { collectBoth } = require('./twin-collector');
console.log('⚖️ Twin Merge Engine · 双子融合\n');
const data = collectBoth();
const result = merge(data.left, data.right);
console.log(` 左翼 composite: ${result.left.composite}`);
console.log(` 右翼 composite: ${result.right.composite}`);
console.log(` 平衡度: ${result.balance}`);
}
module.exports = { merge, computeComposite };

View File

@ -0,0 +1,158 @@
// scripts/tianyen/agent-checkin-module.js
// Agent Checkin Module · 通用签到模块
// ZY-SKD-004 · Phase 1 · TianYen Scheduling
// 版权:国作登字-2026-A-00037559
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const CHECKIN_LOG_PATH = path.join(ROOT, '.github/tianyen/checkin-log.json');
/**
* 读取签到记录
* @returns {{ version: string, checkins: object }}
*/
function loadCheckinLog() {
if (!fs.existsSync(CHECKIN_LOG_PATH)) {
return { version: '1.0.0', checkins: {} };
}
try {
const data = JSON.parse(fs.readFileSync(CHECKIN_LOG_PATH, 'utf8'));
if (!data.checkins) data.checkins = {};
return data;
} catch (_) {
return { version: '1.0.0', checkins: {} };
}
}
/**
* 保存签到记录
* @param {object} log
*/
function saveCheckinLog(log) {
const dir = path.dirname(CHECKIN_LOG_PATH);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(CHECKIN_LOG_PATH, JSON.stringify(log, null, 2), 'utf8');
}
/**
* 签到 · Agent 报到
* @param {string} agentId
* @returns {{ agentId: string, timestamp: string, status: string }}
*/
function checkin(agentId) {
const log = loadCheckinLog();
const timestamp = new Date().toISOString();
log.checkins[agentId] = {
timestamp,
status: 'running',
checkin_at: timestamp,
checkout_at: null,
metrics: {}
};
saveCheckinLog(log);
return { agentId, timestamp, status: 'running' };
}
/**
* 签退 · Agent 收工
* @param {string} agentId
* @param {string} status - 'success' | 'failure' | 'cancelled'
* @param {object} metrics
* @returns {{ agentId: string, timestamp: string, status: string, duration: number|null }}
*/
function checkout(agentId, status, metrics) {
const log = loadCheckinLog();
const timestamp = new Date().toISOString();
let duration = null;
if (log.checkins[agentId] && log.checkins[agentId].checkin_at) {
const start = new Date(log.checkins[agentId].checkin_at).getTime();
duration = Date.now() - start;
}
if (!log.checkins[agentId]) {
log.checkins[agentId] = {};
}
log.checkins[agentId].status = status || 'success';
log.checkins[agentId].checkout_at = timestamp;
log.checkins[agentId].timestamp = timestamp;
log.checkins[agentId].duration = duration;
log.checkins[agentId].metrics = metrics || {};
saveCheckinLog(log);
return { agentId, timestamp, status: status || 'success', duration };
}
/**
* 获取最后一次签到记录
* @param {string} agentId
* @returns {object|null}
*/
function getLastCheckin(agentId) {
const log = loadCheckinLog();
return log.checkins[agentId] || null;
}
/**
* 检测超时 · Agent 是否卡住了
* @param {string} agentId
* @param {number} maxDurationMs - 最大允许运行时间默认 10 分钟
* @returns {{ timedOut: boolean, agentId: string, elapsed: number|null }}
*/
function detectTimeout(agentId, maxDurationMs) {
const maxMs = (maxDurationMs !== undefined && maxDurationMs !== null) ? maxDurationMs : 600000;
const log = loadCheckinLog();
const record = log.checkins[agentId];
if (!record || !record.checkin_at) {
return { timedOut: false, agentId, elapsed: null };
}
// 只检查正在运行的 Agent
if (record.status !== 'running') {
return { timedOut: false, agentId, elapsed: null };
}
const elapsed = Date.now() - new Date(record.checkin_at).getTime();
return {
timedOut: elapsed >= maxMs,
agentId,
elapsed
};
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
const args = process.argv.slice(2);
const action = args[0];
const agentId = args[1];
if (!action || !agentId) {
console.log('用法: node agent-checkin-module.js <checkin|checkout> <agentId> [status]');
process.exit(0);
}
if (action === 'checkin') {
const result = checkin(agentId);
console.log(`📋 ${agentId} 签到: ${result.timestamp}`);
} else if (action === 'checkout') {
const status = args[2] || 'success';
const result = checkout(agentId, status);
console.log(`📋 ${agentId} 签退: ${result.status} (耗时 ${result.duration}ms)`);
} else {
console.log(`未知操作: ${action}`);
}
}
module.exports = { checkin, checkout, getLastCheckin, detectTimeout, loadCheckinLog, saveCheckinLog };

View File

@ -0,0 +1,161 @@
// scripts/tianyen/bulletin-dispatcher.js
// Bulletin Dispatcher · 公告分发器
// ZY-SKD-006 · Phase 1 · TianYen Scheduling
// 版权:国作登字-2026-A-00037559
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const TIANYEN_DIR = path.join(ROOT, '.github/tianyen');
const DISPATCH_PATH = path.join(TIANYEN_DIR, 'bulletin-dispatch.json');
/**
* 采集所有 Agent 状态
* @returns {object[]}
*/
function collectAllAgentStatus() {
const statuses = [];
// 签到记录
const checkinPath = path.join(TIANYEN_DIR, 'checkin-log.json');
if (fs.existsSync(checkinPath)) {
try {
const log = JSON.parse(fs.readFileSync(checkinPath, 'utf8'));
for (const [agentId, record] of Object.entries(log.checkins || {})) {
statuses.push({
agentId,
status: record.status || 'unknown',
lastSeen: record.timestamp || null
});
}
} catch (_) { /* 忽略 */ }
}
// 调度配置
const schedulePath = path.join(TIANYEN_DIR, 'agent-schedule.json');
if (fs.existsSync(schedulePath)) {
try {
const schedule = JSON.parse(fs.readFileSync(schedulePath, 'utf8'));
for (const [agentId, config] of Object.entries(schedule.agents || {})) {
const existing = statuses.find(s => s.agentId === agentId);
if (existing) {
existing.schedule = config;
} else {
statuses.push({ agentId, status: 'configured', lastSeen: null, schedule: config });
}
}
} catch (_) { /* 忽略 */ }
}
return statuses;
}
/**
* 评估需要唤醒的 Agent
* @param {object} globalState - 包含 statuses 数组
* @returns {string[]} 需要唤醒的 Agent ID 列表
*/
function evaluateWakeTargets(globalState) {
const targets = [];
const now = Date.now();
for (const agent of (globalState.statuses || [])) {
// 事件驱动型不主动唤醒
if (agent.schedule && agent.schedule.mode === 'event_driven') {
continue;
}
// 超过最大间隔未活动 → 唤醒
if (agent.lastSeen) {
const lastSeen = new Date(agent.lastSeen).getTime();
const maxInterval = 24 * 60 * 60 * 1000; // 默认 24h
if (now - lastSeen > maxInterval) {
targets.push(agent.agentId);
}
}
}
return targets;
}
/**
* 生成公告条目
* @param {string} agentId
* @param {number} priority - 1(紧急) ~ 5(常规)
* @param {string} context
* @returns {object}
*/
function generateBulletinEntry(agentId, priority, context) {
return {
id: `DISP-${Date.now()}-${agentId}`,
timestamp: new Date().toISOString(),
agentId,
priority: priority || 3,
context: context || '天眼调度唤醒',
status: 'pending'
};
}
/**
* 完整分发流程
* @returns {object}
*/
function dispatch() {
const statuses = collectAllAgentStatus();
const wakeTargets = evaluateWakeTargets({ statuses });
const entries = [];
for (const agentId of wakeTargets) {
entries.push(generateBulletinEntry(agentId, 2, '超时唤醒'));
}
// 加载现有分发数据
let dispatchData = { version: '1.0.0', bulletin: [] };
if (fs.existsSync(DISPATCH_PATH)) {
try {
dispatchData = JSON.parse(fs.readFileSync(DISPATCH_PATH, 'utf8'));
if (!Array.isArray(dispatchData.bulletin)) {
dispatchData.bulletin = [];
}
} catch (_) {
dispatchData = { version: '1.0.0', bulletin: [] };
}
}
// 追加新条目
dispatchData.bulletin.push(...entries);
// 保留最近 100 条
if (dispatchData.bulletin.length > 100) {
dispatchData.bulletin = dispatchData.bulletin.slice(-100);
}
// 保存
if (!fs.existsSync(TIANYEN_DIR)) {
fs.mkdirSync(TIANYEN_DIR, { recursive: true });
}
fs.writeFileSync(DISPATCH_PATH, JSON.stringify(dispatchData, null, 2), 'utf8');
return {
timestamp: new Date().toISOString(),
agentCount: statuses.length,
wakeTargets,
dispatched: entries.length
};
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
console.log('👁️ TianYen Bulletin Dispatcher · 公告分发器\n');
const result = dispatch();
console.log(` Agent 总数: ${result.agentCount}`);
console.log(` 唤醒目标: ${result.wakeTargets.length}`);
console.log(` 已分发: ${result.dispatched}`);
console.log('\n✅ 分发完成');
}
module.exports = { collectAllAgentStatus, evaluateWakeTargets, generateBulletinEntry, dispatch };

View File

@ -0,0 +1,129 @@
// scripts/tianyen/context-injector.js
// Context Injector · 上下文注入器
// ZY-SKD-002 · Phase 1 · TianYen Scheduling
// 版权:国作登字-2026-A-00037559
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const TIANYEN_DIR = path.join(ROOT, '.github/tianyen');
/**
* 采集全局状态 · 汇聚各路信号
* @returns {object} 所有 Agent 的状态汇总
*/
function collectGlobalState() {
const state = {
timestamp: new Date().toISOString(),
twin: null,
bulletin: null,
schedule: null,
checkins: null
};
// 双子天平数据
const twinPath = path.join(TIANYEN_DIR, 'twin-data.json');
if (fs.existsSync(twinPath)) {
try { state.twin = JSON.parse(fs.readFileSync(twinPath, 'utf8')); } catch (_) { /* 忽略 */ }
}
// 公告板数据
const bulletinPath = path.join(TIANYEN_DIR, 'bulletin-data.json');
if (fs.existsSync(bulletinPath)) {
try { state.bulletin = JSON.parse(fs.readFileSync(bulletinPath, 'utf8')); } catch (_) { /* 忽略 */ }
}
// 调度配置
const schedulePath = path.join(TIANYEN_DIR, 'agent-schedule.json');
if (fs.existsSync(schedulePath)) {
try { state.schedule = JSON.parse(fs.readFileSync(schedulePath, 'utf8')); } catch (_) { /* 忽略 */ }
}
// 签到记录
const checkinPath = path.join(TIANYEN_DIR, 'checkin-log.json');
if (fs.existsSync(checkinPath)) {
try { state.checkins = JSON.parse(fs.readFileSync(checkinPath, 'utf8')); } catch (_) { /* 忽略 */ }
}
return state;
}
/**
* 为特定 Agent 生成上下文
* @param {string} agentId
* @param {object} globalState
* @returns {{ agentId: string, context: string, data: object }}
*/
function generateContext(agentId, globalState) {
const data = {
agentId,
timestamp: globalState.timestamp,
twinBalance: null,
activeBulletins: 0,
scheduleInfo: null,
lastCheckin: null
};
// 注入天平数据
if (globalState.twin) {
data.twinBalance = {
leftComposite: globalState.twin.left ? globalState.twin.left.composite : null,
rightComposite: globalState.twin.right ? globalState.twin.right.composite : null
};
}
// 注入公告数量
if (globalState.bulletin && Array.isArray(globalState.bulletin.events)) {
data.activeBulletins = globalState.bulletin.events.filter(e => e.status !== 'resolved').length;
}
// 注入调度信息
if (globalState.schedule && globalState.schedule.agents && globalState.schedule.agents[agentId]) {
data.scheduleInfo = globalState.schedule.agents[agentId];
}
// 注入签到信息
if (globalState.checkins && globalState.checkins.checkins && globalState.checkins.checkins[agentId]) {
data.lastCheckin = globalState.checkins.checkins[agentId];
}
// 生成上下文文本
const lines = [
`[天眼上下文 · ${agentId}]`,
`时间: ${data.timestamp}`,
data.twinBalance ? `天平: L=${data.twinBalance.leftComposite} R=${data.twinBalance.rightComposite}` : '天平: 无数据',
`活跃公告: ${data.activeBulletins}`,
data.scheduleInfo ? `调度: ${data.scheduleInfo.cron} (${data.scheduleInfo.reason})` : '调度: 未配置',
data.lastCheckin ? `上次签到: ${data.lastCheckin.timestamp}` : '上次签到: 无记录'
];
return {
agentId,
context: lines.join('\n'),
data
};
}
/**
* 完整注入流程 · 采集 生成 返回
* @param {string} agentId
* @returns {{ agentId: string, context: string, data: object }}
*/
function injectContext(agentId) {
const globalState = collectGlobalState();
return generateContext(agentId, globalState);
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
const agentId = process.argv[2] || 'AG-ZY-README';
console.log('💉 Context Injector · 上下文注入器\n');
const result = injectContext(agentId);
console.log(result.context);
}
module.exports = { collectGlobalState, generateContext, injectContext };

View File

@ -0,0 +1,142 @@
// scripts/tianyen/scheduler.js
// TianYen Scheduler · 天眼调度器
// ZY-SKD-001 · Phase 1 · TianYen Scheduling
// 版权:国作登字-2026-A-00037559
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const SCHEDULE_PATH = path.join(ROOT, '.github/tianyen/agent-schedule.json');
// ── 频率级别 · 五档心跳 ─────────────────────────────────────────────────
const FREQUENCY_LEVELS = {
max: { cron: '*/30 * * * *', name: '最高频', description: '每30分钟' },
high: { cron: '0 */2 * * *', name: '高频', description: '每2小时' },
medium: { cron: '0 */4 * * *', name: '中频', description: '每4小时' },
default: { cron: '0 */6 * * *', name: '默认', description: '每6小时' },
low: { cron: '0 9 * * *', name: '低频', description: '每天9点' }
};
/**
* 加载调度配置
* @returns {object}
*/
function loadSchedule() {
if (!fs.existsSync(SCHEDULE_PATH)) {
return { version: '1.0.0', last_evaluation: null, agents: {} };
}
try {
return JSON.parse(fs.readFileSync(SCHEDULE_PATH, 'utf8'));
} catch (_) {
return { version: '1.0.0', last_evaluation: null, agents: {} };
}
}
/**
* 保存调度配置
* @param {object} schedule
*/
function saveSchedule(schedule) {
const dir = path.dirname(SCHEDULE_PATH);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(SCHEDULE_PATH, JSON.stringify(schedule, null, 2), 'utf8');
}
/**
* 评估最优调度频率 · 天眼的第六感
* @param {string} agentId
* @param {{ balance: number, drift: number, recentChanges: number }} metrics
* @returns {{ level: string, cron: string, reason: string }}
*/
function evaluateSchedule(agentId, metrics) {
if (!metrics) {
return { level: 'default', ...FREQUENCY_LEVELS.default, reason: '无指标数据,使用默认频率' };
}
// 高偏差 → 提高频率
if (metrics.drift && metrics.drift >= 0.15) {
return { level: 'max', ...FREQUENCY_LEVELS.max, reason: '严重偏差,需要最高频率监控' };
}
if (metrics.drift && metrics.drift >= 0.05) {
return { level: 'high', ...FREQUENCY_LEVELS.high, reason: '中等偏差,提高监控频率' };
}
// 活跃变更 → 中高频率
if (metrics.recentChanges && metrics.recentChanges > 10) {
return { level: 'high', ...FREQUENCY_LEVELS.high, reason: '大量变更活动,提高频率' };
}
if (metrics.recentChanges && metrics.recentChanges > 3) {
return { level: 'medium', ...FREQUENCY_LEVELS.medium, reason: '中等变更活动' };
}
// 完全平衡且安静 → 低频率
if (metrics.balance && metrics.balance >= 1.0) {
return { level: 'low', ...FREQUENCY_LEVELS.low, reason: '系统平静,降低频率节省资源' };
}
return { level: 'default', ...FREQUENCY_LEVELS.default, reason: '常规状态' };
}
/**
* 设置 Agent 调度频率
* @param {string} agentId
* @param {string} level
* @param {string} reason
* @returns {{ agentId: string, cron: string, level: string, reason: string }}
*/
function setFrequency(agentId, level, reason) {
const freq = FREQUENCY_LEVELS[level] || FREQUENCY_LEVELS.default;
const schedule = loadSchedule();
if (!schedule.agents[agentId]) {
schedule.agents[agentId] = {};
}
schedule.agents[agentId].cron = freq.cron;
schedule.agents[agentId].mode = 'scheduled';
schedule.agents[agentId].reason = reason || freq.description;
schedule.agents[agentId].updated_at = new Date().toISOString();
schedule.last_evaluation = new Date().toISOString();
saveSchedule(schedule);
return { agentId, cron: freq.cron, level, reason: reason || freq.description };
}
// ── CLI 入口 ─────────────────────────────────────────────────────────────
if (require.main === module) {
console.log('👁️ TianYen Scheduler · 天眼调度器\n');
const schedule = loadSchedule();
console.log(` 当前 Agent 数量: ${Object.keys(schedule.agents).length}`);
console.log(` 上次评估: ${schedule.last_evaluation || '从未'}`);
// 如果有天平数据,基于它评估
const statusPath = path.join(ROOT, '.github/tianyen/twin-status.json');
let metrics = {};
if (fs.existsSync(statusPath)) {
try {
const status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
metrics = { balance: status.balance, drift: status.drift, recentChanges: 0 };
} catch (_) { /* 忽略 */ }
}
const evaluation = evaluateSchedule('AG-ZY-TWIN', metrics);
console.log(`\n AG-ZY-TWIN 评估结果:`);
console.log(` 频率: ${evaluation.level} (${evaluation.cron})`);
console.log(` 原因: ${evaluation.reason}`);
// 更新调度配置
schedule.last_evaluation = new Date().toISOString();
saveSchedule(schedule);
console.log(`\n✅ 调度配置已更新`);
}
module.exports = { evaluateSchedule, setFrequency, loadSchedule, saveSchedule, FREQUENCY_LEVELS };