feat: 铸渊大脑系统 — 广播接收、路由映射、记忆文件、Brain Sync 工作流
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
parent
818e7c5d89
commit
b539a23ccc
|
|
@ -0,0 +1,16 @@
|
||||||
|
# 铸渊成长日记
|
||||||
|
|
||||||
|
## 2026-03-05 · 初始激活
|
||||||
|
|
||||||
|
铸渊(Zhùyuān)正式上线。
|
||||||
|
|
||||||
|
核心大脑初始化完成:
|
||||||
|
- `routing-map.json` — HLI 路由映射表已加载(17 个接口注册在案)
|
||||||
|
- `memory.json` — 统计记忆系统已启动
|
||||||
|
- CI/CD 流水线已配置(契约校验 + 冒烟测试)
|
||||||
|
- Brain Sync 广播系统已就绪
|
||||||
|
- 每日自检 cron 已配置(08:00 UTC)
|
||||||
|
|
||||||
|
当前 HLI 覆盖率:3/17(AUTH 域已实现)
|
||||||
|
|
||||||
|
铸渊的使命:守护代码,执行规则,连接 Notion 与 GitHub。
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体",
|
||||||
|
"rules_version": "v1.0",
|
||||||
|
"last_updated": "2026-03-05T10:30:57.157Z",
|
||||||
|
"stats": {
|
||||||
|
"ci_runs": 1,
|
||||||
|
"pr_reviews": 0,
|
||||||
|
"broadcasts_processed": 0,
|
||||||
|
"coverage": {
|
||||||
|
"implemented": 3,
|
||||||
|
"total": 17,
|
||||||
|
"percent": "17.6%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"timestamp": "2026-03-05T10:30:46.894Z",
|
||||||
|
"type": "ci_run",
|
||||||
|
"result": "unknown",
|
||||||
|
"actor": "copilot-swe-agent[bot]",
|
||||||
|
"ref": "refs/heads/copilot/create-repo-structure",
|
||||||
|
"run_id": "22713549271"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-03-05T10:30:57.156Z",
|
||||||
|
"broadcast_file": "test-broadcast.json",
|
||||||
|
"title": "测试广播:新增 NOTIFICATION 域",
|
||||||
|
"from": "霜砚",
|
||||||
|
"update_target": "routing-map",
|
||||||
|
"added_domain": "NOTIFICATION"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
{
|
||||||
|
"version": "v1.0",
|
||||||
|
"last_updated": "2026-03-05",
|
||||||
|
"updated_by": "init",
|
||||||
|
"domains": {
|
||||||
|
"AUTH": {
|
||||||
|
"module": "M01",
|
||||||
|
"route_prefix": "/hli/auth",
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"id": "HLI-AUTH-001",
|
||||||
|
"path": "/hli/auth/login",
|
||||||
|
"status": "implemented"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "HLI-AUTH-002",
|
||||||
|
"path": "/hli/auth/register",
|
||||||
|
"status": "implemented"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "HLI-AUTH-003",
|
||||||
|
"path": "/hli/auth/verify",
|
||||||
|
"status": "implemented"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PERSONA": {
|
||||||
|
"module": "M03",
|
||||||
|
"route_prefix": "/hli/persona",
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"id": "HLI-PERSONA-001",
|
||||||
|
"path": "/hli/persona/load",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "HLI-PERSONA-002",
|
||||||
|
"path": "/hli/persona/switch",
|
||||||
|
"status": "pending"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"USER": {
|
||||||
|
"module": "M05",
|
||||||
|
"route_prefix": "/hli/user",
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"id": "HLI-USER-001",
|
||||||
|
"path": "/hli/user/profile",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "HLI-USER-002",
|
||||||
|
"path": "/hli/user/profile/update",
|
||||||
|
"status": "pending"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TICKET": {
|
||||||
|
"module": "M06",
|
||||||
|
"route_prefix": "/hli/ticket",
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"id": "HLI-TICKET-001",
|
||||||
|
"path": "/hli/ticket/create",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "HLI-TICKET-002",
|
||||||
|
"path": "/hli/ticket/query",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "HLI-TICKET-003",
|
||||||
|
"path": "/hli/ticket/status",
|
||||||
|
"status": "pending"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"DIALOGUE": {
|
||||||
|
"module": "M07",
|
||||||
|
"route_prefix": "/hli/dialogue",
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"id": "HLI-DIALOGUE-001",
|
||||||
|
"path": "/hli/dialogue/send",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "HLI-DIALOGUE-002",
|
||||||
|
"path": "/hli/dialogue/stream",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "HLI-DIALOGUE-003",
|
||||||
|
"path": "/hli/dialogue/history",
|
||||||
|
"status": "pending"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"STORAGE": {
|
||||||
|
"module": "M10",
|
||||||
|
"route_prefix": "/hli/storage",
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"id": "HLI-STORAGE-001",
|
||||||
|
"path": "/hli/storage/upload",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "HLI-STORAGE-002",
|
||||||
|
"path": "/hli/storage/download",
|
||||||
|
"status": "pending"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"DASHBOARD": {
|
||||||
|
"module": "M12",
|
||||||
|
"route_prefix": "/hli/dashboard",
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"id": "HLI-DASHBOARD-001",
|
||||||
|
"path": "/hli/dashboard/status",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "HLI-DASHBOARD-002",
|
||||||
|
"path": "/hli/dashboard/realtime",
|
||||||
|
"status": "pending"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"title": "示例广播:新增 NOTIFICATION 业务域",
|
||||||
|
"from": "霜砚",
|
||||||
|
"date": "2026-03-05",
|
||||||
|
"rules_version": "v1.1",
|
||||||
|
"update_target": "routing-map",
|
||||||
|
"data": {
|
||||||
|
"NOTIFICATION": {
|
||||||
|
"module": "M09",
|
||||||
|
"route_prefix": "/hli/notification",
|
||||||
|
"interfaces": [
|
||||||
|
{ "id": "HLI-NOTIF-001", "path": "/hli/notification/push", "status": "pending" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
name: 铸渊 Brain Sync
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, dev]
|
||||||
|
paths:
|
||||||
|
- '.github/broadcasts/**'
|
||||||
|
schedule:
|
||||||
|
# 每天 08:00 UTC 自检
|
||||||
|
- cron: '0 8 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
process-broadcasts:
|
||||||
|
name: 📡 广播接收处理
|
||||||
|
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Process broadcasts
|
||||||
|
run: node scripts/process-broadcasts.js
|
||||||
|
|
||||||
|
- name: Commit updated brain state
|
||||||
|
run: |
|
||||||
|
git config user.name "铸渊[bot]"
|
||||||
|
git config user.email "zhu-yuan-bot@guanghulab.com"
|
||||||
|
git add .github/brain/ .github/broadcasts/processed/ .github/broadcasts/failed/ .github/copilot-instructions.md 2>/dev/null; true
|
||||||
|
git diff --staged --quiet || git commit -m "🧠 brain: 处理广播,更新路由映射和记忆 [skip ci]"
|
||||||
|
git push
|
||||||
|
|
||||||
|
daily-check:
|
||||||
|
name: 🔍 铸渊每日自检
|
||||||
|
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run daily self-check
|
||||||
|
run: node scripts/daily-check.js
|
||||||
|
|
||||||
|
- name: Update memory after daily check
|
||||||
|
env:
|
||||||
|
MEMORY_EVENT_TYPE: daily_check
|
||||||
|
MEMORY_EVENT_RESULT: passed
|
||||||
|
GITHUB_ACTOR: ${{ github.actor }}
|
||||||
|
GITHUB_REF: ${{ github.ref }}
|
||||||
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
|
run: node scripts/update-memory.js
|
||||||
|
|
||||||
|
- name: Commit memory update
|
||||||
|
run: |
|
||||||
|
git config user.name "铸渊[bot]"
|
||||||
|
git config user.email "zhu-yuan-bot@guanghulab.com"
|
||||||
|
git add .github/brain/memory.json || true
|
||||||
|
git diff --staged --quiet || git commit -m "🧠 memory: 每日自检记录 [skip ci]"
|
||||||
|
git push
|
||||||
|
|
@ -18,9 +18,12 @@ jobs:
|
||||||
name: 🔍 接口契约校验
|
name: 🔍 接口契约校验
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
|
@ -32,12 +35,92 @@ jobs:
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Run HLI schema validation
|
- name: Run HLI schema validation
|
||||||
run: npm run test:contract
|
id: contract
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
OUTPUT=$(npm run test:contract 2>&1)
|
||||||
|
EXIT_CODE=$?
|
||||||
|
echo "output<<EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "$OUTPUT" >> $GITHUB_OUTPUT
|
||||||
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
|
||||||
|
exit $EXIT_CODE
|
||||||
env:
|
env:
|
||||||
HLI_REGISTRY_MODE: strict
|
HLI_REGISTRY_MODE: strict
|
||||||
|
|
||||||
- name: Run route-schema alignment check
|
- name: Run route-schema alignment check
|
||||||
run: npm run test:route-align
|
id: align
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
OUTPUT=$(npm run test:route-align 2>&1)
|
||||||
|
EXIT_CODE=$?
|
||||||
|
echo "output<<EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "$OUTPUT" >> $GITHUB_OUTPUT
|
||||||
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
- name: Post PR review comment
|
||||||
|
if: always() && github.event_name == 'pull_request'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const contractResult = '${{ steps.contract.outputs.exit_code }}' === '0' ? '✅ 通过' : '❌ 失败';
|
||||||
|
const contractOutput = `${{ steps.contract.outputs.output }}`;
|
||||||
|
const alignOutput = `${{ steps.align.outputs.output }}`;
|
||||||
|
|
||||||
|
const body = [
|
||||||
|
'## ⚒️ 铸渊 · HLI 契约审核报告',
|
||||||
|
'',
|
||||||
|
`**契约校验**: ${contractResult}`,
|
||||||
|
'',
|
||||||
|
'<details>',
|
||||||
|
'<summary>📋 契约校验详情</summary>',
|
||||||
|
'',
|
||||||
|
'```',
|
||||||
|
contractOutput,
|
||||||
|
'```',
|
||||||
|
'</details>',
|
||||||
|
'',
|
||||||
|
'<details>',
|
||||||
|
'<summary>📊 路由对齐报告</summary>',
|
||||||
|
'',
|
||||||
|
'```',
|
||||||
|
alignOutput,
|
||||||
|
'```',
|
||||||
|
'</details>',
|
||||||
|
'',
|
||||||
|
`> 审核时间: ${new Date().toISOString()}`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Update memory on CI completion
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
MEMORY_EVENT_TYPE: ci_run
|
||||||
|
MEMORY_EVENT_RESULT: ${{ steps.contract.outputs.exit_code == '0' && 'passed' || 'failed' }}
|
||||||
|
GITHUB_ACTOR: ${{ github.actor }}
|
||||||
|
GITHUB_REF: ${{ github.ref }}
|
||||||
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
|
MEMORY_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
run: node scripts/update-memory.js
|
||||||
|
|
||||||
|
- name: Commit memory update
|
||||||
|
if: always() && github.event_name != 'pull_request'
|
||||||
|
run: |
|
||||||
|
git config user.name "铸渊[bot]"
|
||||||
|
git config user.email "zhu-yuan-bot@guanghulab.com"
|
||||||
|
git add .github/brain/memory.json 2>/dev/null; true
|
||||||
|
git diff --staged --quiet || git commit -m "🧠 memory: CI 记录更新 [skip ci]"
|
||||||
|
git push
|
||||||
|
|
||||||
api-smoke:
|
api-smoke:
|
||||||
name: 🚀 接口冒烟测试
|
name: 🚀 接口冒烟测试
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,10 @@
|
||||||
"test:contract": "node scripts/contract-check.js",
|
"test:contract": "node scripts/contract-check.js",
|
||||||
"test:route-align": "node scripts/route-align-check.js",
|
"test:route-align": "node scripts/route-align-check.js",
|
||||||
"test:smoke": "jest --config jest.smoke.config.js --forceExit",
|
"test:smoke": "jest --config jest.smoke.config.js --forceExit",
|
||||||
"start:test": "NODE_ENV=test node src/index.js"
|
"start:test": "NODE_ENV=test node src/index.js",
|
||||||
|
"brain:sync": "node scripts/process-broadcasts.js",
|
||||||
|
"brain:daily-check": "node scripts/daily-check.js",
|
||||||
|
"brain:update-memory": "node scripts/update-memory.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "15.3.2",
|
"next": "15.3.2",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
// scripts/daily-check.js
|
||||||
|
// 铸渊每日自检脚本
|
||||||
|
// 用途:检查大脑完整性 + 输出覆盖率报告
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const ROUTING_MAP_PATH = path.join(__dirname, '../.github/brain/routing-map.json');
|
||||||
|
const MEMORY_PATH = path.join(__dirname, '../.github/brain/memory.json');
|
||||||
|
const SCHEMA_DIR = path.join(__dirname, '../src/schemas/hli');
|
||||||
|
|
||||||
|
const errors = [];
|
||||||
|
const warnings = [];
|
||||||
|
|
||||||
|
// ── 1. 大脑文件完整性检查 ─────────────────────────────────────────────────
|
||||||
|
function checkFileIntegrity() {
|
||||||
|
[ROUTING_MAP_PATH, MEMORY_PATH].forEach(filePath => {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
errors.push(`❌ [MISSING] 大脑文件缺失: ${path.basename(filePath)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
} catch (e) {
|
||||||
|
errors.push(`❌ [CORRUPT] 大脑文件损坏: ${path.basename(filePath)} → ${e.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 2. HLI 覆盖率报告 ────────────────────────────────────────────────────
|
||||||
|
function checkCoverage() {
|
||||||
|
const routingMap = JSON.parse(fs.readFileSync(ROUTING_MAP_PATH, 'utf8'));
|
||||||
|
let total = 0;
|
||||||
|
let implemented = 0;
|
||||||
|
let pending = 0;
|
||||||
|
|
||||||
|
console.log('\n📊 HLI 接口覆盖率报告:\n');
|
||||||
|
|
||||||
|
Object.entries(routingMap.domains).forEach(([domain, domainData]) => {
|
||||||
|
const domainTotal = domainData.interfaces.length;
|
||||||
|
const domainImpl = domainData.interfaces.filter(i => i.status === 'implemented').length;
|
||||||
|
total += domainTotal;
|
||||||
|
implemented += domainImpl;
|
||||||
|
pending += domainTotal - domainImpl;
|
||||||
|
|
||||||
|
const icon = domainImpl === domainTotal ? '✅' : domainImpl > 0 ? '🔶' : '⬜';
|
||||||
|
console.log(` ${icon} ${domain.padEnd(12)} ${domainImpl}/${domainTotal}`);
|
||||||
|
|
||||||
|
domainData.interfaces.forEach(iface => {
|
||||||
|
const statusIcon = iface.status === 'implemented' ? ' ✓' : ' ○';
|
||||||
|
console.log(` ${statusIcon} ${iface.id.padEnd(20)} ${iface.path}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const percent = total > 0 ? ((implemented / total) * 100).toFixed(1) : 0;
|
||||||
|
console.log(`\n 📈 总覆盖率: ${implemented}/${total} (${percent}%)`);
|
||||||
|
console.log(` ✅ 已实现: ${implemented} · ⏳ 待实现: ${pending}\n`);
|
||||||
|
|
||||||
|
if (implemented < total) {
|
||||||
|
warnings.push(`⚠️ 覆盖率 ${percent}% (${implemented}/${total}) — 仍有 ${pending} 个接口待实现`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 schema 文件与 routing-map 的一致性
|
||||||
|
Object.entries(routingMap.domains).forEach(([domain, domainData]) => {
|
||||||
|
domainData.interfaces.forEach(iface => {
|
||||||
|
if (iface.status !== 'implemented') return;
|
||||||
|
const schemaDir = path.join(SCHEMA_DIR, domain.toLowerCase());
|
||||||
|
if (!fs.existsSync(schemaDir)) {
|
||||||
|
errors.push(`❌ [MISSING DIR] ${domain} schema 目录不存在`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rawAction = iface.path.split('/').filter(Boolean).pop();
|
||||||
|
const action = rawAction ? rawAction.replace(/[?#].*$/, '') : null;
|
||||||
|
if (!action) {
|
||||||
|
errors.push(`❌ [INVALID PATH] ${iface.id} 路径格式异常: ${iface.path}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const schemaFile = path.join(schemaDir, `${action}.schema.json`);
|
||||||
|
if (!fs.existsSync(schemaFile)) {
|
||||||
|
errors.push(`❌ [MISSING SCHEMA] ${iface.id} 标记为 implemented 但缺少 schema: ${schemaFile}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 3. memory.json 健康检查 ────────────────────────────────────────────────
|
||||||
|
function checkMemory() {
|
||||||
|
const memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf8'));
|
||||||
|
const required = ['identity', 'rules_version', 'stats', 'events'];
|
||||||
|
required.forEach(field => {
|
||||||
|
if (memory[field] === undefined) {
|
||||||
|
errors.push(`❌ [MEMORY] memory.json 缺少字段: ${field}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`🧠 铸渊记忆状态:`);
|
||||||
|
console.log(` 规则版本: ${memory.rules_version}`);
|
||||||
|
console.log(` CI 运行次数: ${memory.stats?.ci_runs ?? 0}`);
|
||||||
|
console.log(` PR 审核次数: ${memory.stats?.pr_reviews ?? 0}`);
|
||||||
|
console.log(` 广播处理次数: ${memory.stats?.broadcasts_processed ?? 0}`);
|
||||||
|
console.log(` 最近更新: ${memory.last_updated || '未知'}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 主流程 ────────────────────────────────────────────────────────────────
|
||||||
|
console.log('═══════════════════════════════════════════');
|
||||||
|
console.log('⚒️ 铸渊每日自检报告');
|
||||||
|
console.log(` 时间: ${new Date().toISOString()}`);
|
||||||
|
console.log('═══════════════════════════════════════════\n');
|
||||||
|
|
||||||
|
checkFileIntegrity();
|
||||||
|
|
||||||
|
if (errors.length === 0) {
|
||||||
|
checkMemory();
|
||||||
|
checkCoverage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warnings.length > 0) {
|
||||||
|
console.log('⚠️ 警告:\n');
|
||||||
|
warnings.forEach(w => console.log(' ' + w));
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error('🚫 自检发现严重错误:\n');
|
||||||
|
errors.forEach(e => console.error(' ' + e));
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log('✅ 铸渊自检通过 — 大脑状态正常');
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
// scripts/process-broadcasts.js
|
||||||
|
// 铸渊大脑同步脚本
|
||||||
|
// 用途:读取 .github/broadcasts/ 下的广播 JSON,更新 routing-map.json 和 memory.json
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const BROADCASTS_DIR = path.join(__dirname, '../.github/broadcasts');
|
||||||
|
const ROUTING_MAP_PATH = path.join(__dirname, '../.github/brain/routing-map.json');
|
||||||
|
const MEMORY_PATH = path.join(__dirname, '../.github/brain/memory.json');
|
||||||
|
const COPILOT_INSTRUCTIONS_PATH = path.join(__dirname, '../.github/copilot-instructions.md');
|
||||||
|
|
||||||
|
// 加载当前大脑状态
|
||||||
|
const routingMap = JSON.parse(fs.readFileSync(ROUTING_MAP_PATH, 'utf8'));
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📡 发现 ${broadcasts.length} 个待处理广播...\n`);
|
||||||
|
|
||||||
|
const failedDir = path.join(BROADCASTS_DIR, 'failed');
|
||||||
|
|
||||||
|
broadcasts.forEach(({ file, fullPath }) => {
|
||||||
|
let broadcast;
|
||||||
|
try {
|
||||||
|
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}`);
|
||||||
|
console.log(` 来源: ${broadcast.from || '未知'} · 日期: ${broadcast.date || '未知'}`);
|
||||||
|
|
||||||
|
const event = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
broadcast_file: file,
|
||||||
|
title: broadcast.title || file,
|
||||||
|
from: broadcast.from || '未知',
|
||||||
|
update_target: broadcast.update_target,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据 update_target 分发处理
|
||||||
|
if (broadcast.update_target === 'routing-map' && broadcast.data) {
|
||||||
|
Object.entries(broadcast.data).forEach(([domain, domainData]) => {
|
||||||
|
routingMap.domains[domain] = domainData;
|
||||||
|
console.log(` ✅ 已更新域: ${domain}`);
|
||||||
|
event.added_domain = domain;
|
||||||
|
});
|
||||||
|
routingMap.version = broadcast.rules_version || routingMap.version;
|
||||||
|
routingMap.last_updated = broadcast.date || new Date().toISOString().split('T')[0];
|
||||||
|
routingMap.updated_by = broadcast.from || 'broadcast';
|
||||||
|
|
||||||
|
} else if (broadcast.update_target === 'copilot-instructions' && broadcast.content) {
|
||||||
|
// 追加到 copilot-instructions.md(防止重复写入)
|
||||||
|
const existing = fs.readFileSync(COPILOT_INSTRUCTIONS_PATH, 'utf8');
|
||||||
|
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);
|
||||||
|
memory.stats.broadcasts_processed += 1;
|
||||||
|
|
||||||
|
// 归档广播文件
|
||||||
|
fs.renameSync(fullPath, path.join(processedDir, file));
|
||||||
|
console.log(` 📦 广播已归档到 broadcasts/processed/${file}\n`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重新计算覆盖率
|
||||||
|
let implemented = 0;
|
||||||
|
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})`);
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
// scripts/update-memory.js
|
||||||
|
// 用途:CI 完成后更新 memory.json 统计数据和事件日志
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const MEMORY_PATH = path.join(__dirname, '../.github/brain/memory.json');
|
||||||
|
|
||||||
|
const memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf8'));
|
||||||
|
|
||||||
|
const eventType = process.env.MEMORY_EVENT_TYPE || 'ci_run';
|
||||||
|
const result = process.env.MEMORY_EVENT_RESULT || 'unknown';
|
||||||
|
const prNumber = process.env.MEMORY_PR_NUMBER || null;
|
||||||
|
const ref = process.env.GITHUB_REF || '';
|
||||||
|
const actor = process.env.GITHUB_ACTOR || 'unknown';
|
||||||
|
const runId = process.env.GITHUB_RUN_ID || null;
|
||||||
|
|
||||||
|
const event = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
type: eventType,
|
||||||
|
result,
|
||||||
|
actor,
|
||||||
|
ref,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (prNumber) event.pr_number = prNumber;
|
||||||
|
if (runId) event.run_id = runId;
|
||||||
|
|
||||||
|
// 更新计数器
|
||||||
|
if (eventType === 'ci_run') {
|
||||||
|
memory.stats.ci_runs = (memory.stats.ci_runs || 0) + 1;
|
||||||
|
} else if (eventType === 'pr_review') {
|
||||||
|
memory.stats.pr_reviews = (memory.stats.pr_reviews || 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保留最近 100 条事件
|
||||||
|
memory.events.push(event);
|
||||||
|
if (memory.events.length > 100) {
|
||||||
|
memory.events = memory.events.slice(-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
memory.last_updated = new Date().toISOString();
|
||||||
|
|
||||||
|
fs.writeFileSync(MEMORY_PATH, JSON.stringify(memory, null, 2) + '\n', 'utf8');
|
||||||
|
|
||||||
|
console.log(`✅ memory.json 已更新 · 事件: ${eventType}(${result}) · CI总次数: ${memory.stats.ci_runs} · PR审核次数: ${memory.stats.pr_reviews}`);
|
||||||
Loading…
Reference in New Issue