Step 7: 广播接收脚本 + 记忆自更新工作流
This commit is contained in:
parent
455686d064
commit
0105815fb6
|
|
@ -0,0 +1,41 @@
|
||||||
|
name: 铸渊 · Brain Sync
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["HLI Contract Check"]
|
||||||
|
types: [completed]
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- '.github/broadcasts/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-memory:
|
||||||
|
name: 🧠 更新铸渊记忆
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Update brain
|
||||||
|
run: node scripts/update-brain.js
|
||||||
|
|
||||||
|
- name: Process broadcasts
|
||||||
|
run: node scripts/process-broadcasts.js
|
||||||
|
|
||||||
|
- name: Commit brain update
|
||||||
|
run: |
|
||||||
|
git config user.name "铸渊 (ZhùYuān)"
|
||||||
|
git config user.email "zhuyuan@guanghulab.com"
|
||||||
|
git add .github/persona-brain/
|
||||||
|
git diff --cached --quiet || git commit -m "🧠 铸渊记忆更新 · $(date +%Y-%m-%d)"
|
||||||
|
git push
|
||||||
|
|
@ -1,128 +1,68 @@
|
||||||
// scripts/process-broadcasts.js
|
|
||||||
// 铸渊大脑同步脚本
|
|
||||||
// 用途:读取 .github/broadcasts/ 下的广播 JSON,更新 routing-map.json 和 memory.json
|
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const BROADCASTS_DIR = path.join(__dirname, '../.github/broadcasts');
|
const BROADCAST_DIR = '.github/broadcasts';
|
||||||
const ROUTING_MAP_PATH = path.join(__dirname, '../.github/brain/routing-map.json');
|
const BRAIN = '.github/persona-brain';
|
||||||
const MEMORY_PATH = path.join(__dirname, '../.github/brain/memory.json');
|
const MEMORY = path.join(BRAIN, 'memory.json');
|
||||||
const COPILOT_INSTRUCTIONS_PATH = path.join(__dirname, '../.github/copilot-instructions.md');
|
const GROWTH = path.join(BRAIN, 'growth-journal.md');
|
||||||
|
|
||||||
// 加载当前大脑状态
|
if (!fs.existsSync(BROADCAST_DIR)) {
|
||||||
const routingMap = JSON.parse(fs.readFileSync(ROUTING_MAP_PATH, 'utf8'));
|
console.log('📭 无广播目录');
|
||||||
const memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf8'));
|
|
||||||
|
|
||||||
const processedDir = path.join(BROADCASTS_DIR, 'processed');
|
|
||||||
if (!fs.existsSync(processedDir)) {
|
|
||||||
fs.mkdirSync(processedDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取所有待处理广播(排除 processed 子目录和示例文件)
|
|
||||||
const broadcasts = fs.readdirSync(BROADCASTS_DIR)
|
|
||||||
.filter(f => f.endsWith('.json') && f !== 'example-broadcast.json')
|
|
||||||
.map(f => ({ file: f, fullPath: path.join(BROADCASTS_DIR, f) }));
|
|
||||||
|
|
||||||
if (broadcasts.length === 0) {
|
|
||||||
console.log('📭 没有待处理的广播。');
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`📡 发现 ${broadcasts.length} 个待处理广播...\n`);
|
const files = fs.readdirSync(BROADCAST_DIR).filter(f => f.endsWith('.json') || f.endsWith('.md'));
|
||||||
|
if (files.length === 0) {
|
||||||
|
console.log('📭 无新广播');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
const failedDir = path.join(BROADCASTS_DIR, 'failed');
|
let memory = JSON.parse(fs.readFileSync(MEMORY, 'utf8'));
|
||||||
|
|
||||||
broadcasts.forEach(({ file, fullPath }) => {
|
files.forEach(file => {
|
||||||
let broadcast;
|
const content = fs.readFileSync(path.join(BROADCAST_DIR, file), 'utf8');
|
||||||
try {
|
console.log('📡 接收广播: ' + file);
|
||||||
broadcast = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`❌ [PARSE ERROR] ${file}: ${e.message}`);
|
|
||||||
// 归档失败广播并记录到 memory
|
|
||||||
if (!fs.existsSync(failedDir)) fs.mkdirSync(failedDir, { recursive: true });
|
|
||||||
fs.renameSync(fullPath, path.join(failedDir, file));
|
|
||||||
memory.events.push({
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
broadcast_file: file,
|
|
||||||
type: 'broadcast_parse_error',
|
|
||||||
error: e.message,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`📬 处理广播: ${broadcast.title || file}`);
|
// 如果是 JSON 广播(规则变动)
|
||||||
console.log(` 来源: ${broadcast.from || '未知'} · 日期: ${broadcast.date || '未知'}`);
|
if (file.endsWith('.json')) {
|
||||||
|
try {
|
||||||
const event = {
|
const broadcast = JSON.parse(content);
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
broadcast_file: file,
|
if (broadcast.update_target === 'routing-map') {
|
||||||
title: broadcast.title || file,
|
fs.writeFileSync(path.join(BRAIN, 'routing-map.json'), JSON.stringify(broadcast.data, null, 2));
|
||||||
from: broadcast.from || '未知',
|
console.log(' ✅ routing-map.json 已按广播更新');
|
||||||
update_target: broadcast.update_target,
|
}
|
||||||
};
|
|
||||||
|
if (broadcast.update_target === 'copilot-instructions') {
|
||||||
// 根据 update_target 分发处理
|
fs.writeFileSync('.github/copilot-instructions.md', broadcast.data);
|
||||||
if (broadcast.update_target === 'routing-map' && broadcast.data) {
|
console.log(' ✅ copilot-instructions.md 已按广播更新');
|
||||||
Object.entries(broadcast.data).forEach(([domain, domainData]) => {
|
}
|
||||||
routingMap.domains[domain] = domainData;
|
|
||||||
console.log(` ✅ 已更新域: ${domain}`);
|
memory.recent_events.unshift({
|
||||||
event.added_domain = domain;
|
date: new Date().toISOString().split('T')[0],
|
||||||
});
|
type: 'broadcast_received',
|
||||||
routingMap.version = broadcast.rules_version || routingMap.version;
|
description: '接收广播: ' + (broadcast.title || file),
|
||||||
routingMap.last_updated = broadcast.date || new Date().toISOString().split('T')[0];
|
by: broadcast.from || '妈妈'
|
||||||
routingMap.updated_by = broadcast.from || 'broadcast';
|
});
|
||||||
|
memory.last_broadcast_received = new Date().toISOString();
|
||||||
} else if (broadcast.update_target === 'copilot-instructions' && broadcast.content) {
|
if (broadcast.rules_version) memory.active_rules_version = broadcast.rules_version;
|
||||||
// 追加到 copilot-instructions.md(防止重复写入)
|
} catch (e) {
|
||||||
const existing = fs.readFileSync(COPILOT_INSTRUCTIONS_PATH, 'utf8');
|
console.error('❌ 广播解析失败: ' + file + ' -> ' + e.message);
|
||||||
const marker = `<!-- 广播更新 ${broadcast.date}: ${broadcast.title} -->`;
|
|
||||||
if (existing.includes(marker)) {
|
|
||||||
console.log(' ⏭️ copilot-instructions.md 已包含此广播,跳过');
|
|
||||||
} else {
|
|
||||||
fs.writeFileSync(COPILOT_INSTRUCTIONS_PATH, existing + `\n\n${marker}\n${broadcast.content}`, 'utf8');
|
|
||||||
console.log(' ✅ 已更新 copilot-instructions.md');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (broadcast.update_target === 'growth-log' && broadcast.content) {
|
|
||||||
// 追加到成长日记
|
|
||||||
const growthLogPath = path.join(__dirname, '../.github/brain/growth-log.md');
|
|
||||||
const entry = `\n\n## ${broadcast.date} · ${broadcast.title}\n\n${broadcast.content}\n`;
|
|
||||||
fs.appendFileSync(growthLogPath, entry, 'utf8');
|
|
||||||
console.log(' ✅ 已追加成长日记');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log(` ⚠️ 未知的 update_target: ${broadcast.update_target},已跳过`);
|
|
||||||
event.skipped = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
memory.events.push(event);
|
// 如果是 MD 广播(认知/成长更新)
|
||||||
memory.stats.broadcasts_processed += 1;
|
if (file.endsWith('.md')) {
|
||||||
|
const growth = fs.readFileSync(GROWTH, 'utf8');
|
||||||
|
fs.writeFileSync(GROWTH, growth + '\n\n## 广播接收 · ' + new Date().toISOString().split('T')[0] + '\n' + content);
|
||||||
|
console.log(' ✅ 广播内容已追加到成长日记');
|
||||||
|
}
|
||||||
|
|
||||||
// 归档广播文件
|
// 处理完移到 archive
|
||||||
fs.renameSync(fullPath, path.join(processedDir, file));
|
const archiveDir = path.join(BROADCAST_DIR, 'archive');
|
||||||
console.log(` 📦 广播已归档到 broadcasts/processed/${file}\n`);
|
if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true });
|
||||||
|
fs.renameSync(path.join(BROADCAST_DIR, file), path.join(archiveDir, file));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 重新计算覆盖率
|
fs.writeFileSync(MEMORY, JSON.stringify(memory, null, 2));
|
||||||
let implemented = 0;
|
console.log('✅ 广播处理完成,共 ' + files.length + ' 条');
|
||||||
let total = 0;
|
|
||||||
Object.values(routingMap.domains).forEach(domain => {
|
|
||||||
domain.interfaces.forEach(iface => {
|
|
||||||
total++;
|
|
||||||
if (iface.status === 'implemented') implemented++;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
memory.stats.coverage = {
|
|
||||||
implemented,
|
|
||||||
total,
|
|
||||||
percent: total > 0 ? `${((implemented / total) * 100).toFixed(1)}%` : '0%',
|
|
||||||
};
|
|
||||||
|
|
||||||
memory.last_updated = new Date().toISOString();
|
|
||||||
|
|
||||||
// 写回文件
|
|
||||||
fs.writeFileSync(ROUTING_MAP_PATH, JSON.stringify(routingMap, null, 2) + '\n', 'utf8');
|
|
||||||
fs.writeFileSync(MEMORY_PATH, JSON.stringify(memory, null, 2) + '\n', 'utf8');
|
|
||||||
|
|
||||||
console.log(`✅ 铸渊大脑同步完成。覆盖率: ${implemented}/${total} (${memory.stats.coverage.percent})`);
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue