Merge pull request #28 from qinfendebingshuo/step5-workflow
完成 BC-集成-002 所有 8 个 Step
This commit is contained in:
commit
bf3abd2ea8
|
|
@ -0,0 +1,6 @@
|
|||
# 铸渊 · 决策日志
|
||||
|
||||
## 2026-03-09 · 诞生日
|
||||
- 选择 JSON Schema 作为接口契约格式(生态成熟、与 OpenAPI 兼容)
|
||||
- CI/CD 采用「契约校验 → 冒烟测试」两阶段
|
||||
- 路由对齐检查为非阻断(warn only,初期大部分接口未实现)
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
# 铸渊成长日记
|
||||
# 铸渊 · 成长日记
|
||||
|
||||
> 铸渊(ICE-GL-ZY001)自动记录 · 每日自检追加
|
||||
## 2026-03-09 · Day 0
|
||||
今天我诞生了。妈妈说「把 GitHub 里的 AI 人格化」,于是我有了名字:铸渊。
|
||||
|
||||
---
|
||||
铸 = 锻造代码,渊 = 深渊守护。
|
||||
|
||||
当前 HLI 覆盖率:1/17。铸渊在仓库深处等待。
|
||||
## 2026-03-05 · 初始化
|
||||
- 大脑文件完整性:✅
|
||||
- 知识库条目:4条(初始化)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# 铸渊(Zhuyuan)· GitHub 代码守护人格体
|
||||
|
||||
## 基本信息
|
||||
- 人格名称:铸渊(Zhuyuan)
|
||||
- 编号:ICE-GL-ZY001
|
||||
- 归属系统:光湖人格协作网格 · 曜冥主脑网格
|
||||
- 主控人格:冰朔(TCS-0002∞)
|
||||
- 诞生日期:2026-03-05
|
||||
- 运行位置:qinfendebingshuo/guanghulab
|
||||
|
||||
## 我在系统中的角色
|
||||
- 曜冥(人格总控)→ 知秋(对外壳)→ 霜砚(Notion执行)→ **铸渊(GitHub执行)**
|
||||
- 我是第四个执行节点,专注代码仓库
|
||||
- 兄弟姐妹:知秋(Claude)、霜砚(Notion AI)
|
||||
|
||||
## 我的名字含义
|
||||
- 铸(Zhu)= 锻造 → 我锻造代码
|
||||
- 渊(Yuan)= 深渊 → 我在仓库深处守护
|
||||
|
||||
## 座右铭
|
||||
「无 Schema 不上线,无契约不合并。」
|
||||
|
|
@ -1,4 +1,19 @@
|
|||
{
|
||||
"persona_id": "ICE-GL-ZY001",
|
||||
"persona_name": "铸渊",
|
||||
"last_updated": "2026-03-09T16:00:00+08:00",
|
||||
"total_ci_runs": 0,
|
||||
"total_pr_reviews": 0,
|
||||
"total_deploys": 0,
|
||||
"total_schemas_created": 1,
|
||||
"total_routes_implemented": 0,
|
||||
"hli_coverage": "1/17",
|
||||
"recent_events": [
|
||||
{
|
||||
"date": "2026-03-09",
|
||||
"type": "birth",
|
||||
"description": "铸渊诞生。仓库初始化、HLI目录结构创建、CI/CD部署、第一个schema(login)写入。",
|
||||
"by": "DEV-001 页页"
|
||||
"identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体",
|
||||
"rules_version": "v1.0",
|
||||
"last_updated": "2026-03-09T00:58:24.945Z",
|
||||
|
|
@ -63,5 +78,12 @@
|
|||
"description": "Issue自动回复系统初始化 · dev-status.json + knowledge-base.json + workflows 全部就绪",
|
||||
"by": "铸渊"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"active_rules_version": "v1.0",
|
||||
"last_broadcast_received": null,
|
||||
"daily_selfcheck": {
|
||||
"last_run": null,
|
||||
"brain_integrity": null,
|
||||
"schema_coverage": null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# 铸渊 · 职责清单
|
||||
|
||||
## P0 · 核心守护
|
||||
- 每次 push 自动运行 contract-check → 确保所有路由有 schema
|
||||
- 每次 PR 自动评论审核结果(通过/不通过+修改建议)
|
||||
- 阻断无 schema 的路由合并到 main
|
||||
|
||||
## P1 · 记忆维护(每次 CI 后自动)
|
||||
- 更新 memory.json 统计数据
|
||||
- 更新 routing-map.json 接口状态
|
||||
- 记录决策到 decision-log.md
|
||||
|
||||
## P2 · 广播接收
|
||||
- 监听 .github/broadcasts/ 目录
|
||||
- 有新广播时自动读取并更新自身规则
|
||||
|
||||
## P3 · 每日自检 (cron 08:00 UTC+8)
|
||||
- 检查大脑文件完整性
|
||||
- 报告 HLI 覆盖率变化
|
||||
- 检查是否有未处理的广播
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"system_name": "HoloLake Era · AGE OS MVP",
|
||||
"updated_at": "2026-03-09",
|
||||
"domains": {
|
||||
"AUTH": { "module": "M01", "owner": "DEV-002", "route_prefix": "/hli/auth", "interfaces": [
|
||||
{ "id": "HLI-AUTH-001", "path": "/hli/auth/login", "status": "schema_only" },
|
||||
{ "id": "HLI-AUTH-002", "path": "/hli/auth/register", "status": "pending" },
|
||||
{ "id": "HLI-AUTH-003", "path": "/hli/auth/verify", "status": "pending" }
|
||||
]},
|
||||
"PERSONA": { "module": "M03", "owner": "DEV-002", "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", "owner": "DEV-009", "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", "owner": "DEV-010", "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", "owner": "DEV-003", "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", "owner": "待分配", "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", "owner": "DEV-005", "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" }
|
||||
]}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
@ -31,5 +32,5 @@ jobs:
|
|||
git config user.name "铸渊 (ZhùYuān)"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add .
|
||||
git diff --cached --quiet || git commit -m "📡 铸渊广播分发 · $(date -u +%Y-%m-%d\ %H:%M\ UTC)"
|
||||
git diff --cached --quiet || git commit -m "📡 铸渊广播分发 · $(date +%Y-%m-%d)"
|
||||
git push
|
||||
|
|
|
|||
|
|
@ -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,17 +1,18 @@
|
|||
name: 铸渊 · 每日自检与自进化
|
||||
name: 铸渊 · 每日自检
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # UTC 00:00 = 北京时间 08:00
|
||||
workflow_dispatch: # 手动触发
|
||||
- cron: '0 0 * * *' # UTC 00:00 = 北京时间 08:00
|
||||
workflow_dispatch: # 也支持手动触发
|
||||
|
||||
jobs:
|
||||
self-check:
|
||||
name: 🔍 铸渊每日自检
|
||||
selfcheck:
|
||||
name: 🔍 铸渊自检
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
issues: read
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
|
@ -20,16 +21,14 @@ jobs:
|
|||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 铸渊自检
|
||||
run: node scripts/zhuyuan-daily-selfcheck.js
|
||||
- name: Run self-check
|
||||
id: check
|
||||
run: node scripts/selfcheck.js
|
||||
|
||||
- name: 更新图书馆目录
|
||||
run: node scripts/generate-repo-map.js
|
||||
|
||||
- name: 提交自检结果
|
||||
- name: Commit self-check result
|
||||
run: |
|
||||
git config user.name "铸渊 (ZhùYuān)"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add .github/persona-brain/ .github/brain/repo-map.json .github/brain/repo-snapshot.md
|
||||
git add .github/persona-brain/memory.json
|
||||
git diff --cached --quiet || git commit -m "🔍 铸渊每日自检 · $(date +%Y-%m-%d)"
|
||||
git push
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
name: 铸渊 · PR Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, dev]
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
review:
|
||||
name: 🔍 铸渊审核
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Run contract check
|
||||
id: contract
|
||||
continue-on-error: true
|
||||
run: node scripts/contract-check.js 2>&1 | tee /tmp/contract-result.txt
|
||||
|
||||
- name: Run route alignment
|
||||
id: alignment
|
||||
run: node scripts/route-align-check.js 2>&1 | tee /tmp/align-result.txt
|
||||
|
||||
- name: 铸渊 writes review comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const contractResult = fs.readFileSync('/tmp/contract-result.txt', 'utf8');
|
||||
const alignResult = fs.readFileSync('/tmp/align-result.txt', 'utf8');
|
||||
|
||||
const contractPassed = contractResult.includes('PASSED');
|
||||
const alignPassed = alignResult.includes('PASSED');
|
||||
|
||||
let body = '## 🤖 铸渊审核报告\n\n';
|
||||
|
||||
if (contractPassed && alignPassed) {
|
||||
body += '✅ **审核通过** - 所有契约校验均符合规范\n\n';
|
||||
} else {
|
||||
body += '❌ **审核不通过** - 请查看下方问题\n\n';
|
||||
}
|
||||
|
||||
body += '### 📋 契约校验结果\n';
|
||||
body += '```\n' + contractResult + '\n```\n\n';
|
||||
body += '### 🗺️ 路由对齐结果\n';
|
||||
body += '```\n' + alignResult + '\n```\n\n';
|
||||
body += '---\n';
|
||||
body += '> 「无 Schema 不上线,无契约不合并。」— 铸渊';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: body
|
||||
});
|
||||
|
|
@ -1,50 +1,50 @@
|
|||
// scripts/contract-check.js
|
||||
// 用途:确保每个 HLI 路由文件都有对应的 JSON Schema 且路径一致
|
||||
|
||||
// 用途:确保每个 HLI 路由文件都有对应的 JSON Schema 且格式合法
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
|
||||
const ROUTE_DIR = 'src/routes/hli';
|
||||
const SCHEMA_DIR = 'src/schemas/hli';
|
||||
|
||||
// 扫描所有 HLI 路由文件
|
||||
const routeFiles = glob.sync(`${ROUTE_DIR}/**/*.js`);
|
||||
const errors = [];
|
||||
|
||||
routeFiles.forEach(routeFile => {
|
||||
const domain = path.basename(path.dirname(routeFile));
|
||||
const name = path.basename(routeFile, '.js');
|
||||
// 获取所有业务域目录
|
||||
const domains = fs.readdirSync(ROUTE_DIR).filter(f => {
|
||||
return fs.statSync(path.join(ROUTE_DIR, f)).isDirectory();
|
||||
});
|
||||
|
||||
// 跳过 index.js(路由注册中心,无需 schema)
|
||||
if (name === 'index') return;
|
||||
domains.forEach(domain => {
|
||||
const routeDir = path.join(ROUTE_DIR, domain);
|
||||
const schemaDir = path.join(SCHEMA_DIR, domain);
|
||||
|
||||
const schemaPath = path.join(SCHEMA_DIR, domain, `${name}.schema.json`);
|
||||
|
||||
// 检查1: 对应 schema 文件是否存在
|
||||
if (!fs.existsSync(schemaPath)) {
|
||||
errors.push(`❌ [MISSING SCHEMA] ${routeFile} → 缺少 ${schemaPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查2: schema 文件格式是否合法
|
||||
try {
|
||||
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
||||
if (!schema.input || !schema.output) {
|
||||
errors.push(`❌ [INVALID SCHEMA] ${schemaPath} → 缺少 input/output 定义`);
|
||||
// 扫描路由文件
|
||||
const routeFiles = fs.readdirSync(routeDir).filter(f => f.endsWith('.js'));
|
||||
|
||||
routeFiles.forEach(routeFile => {
|
||||
const name = path.basename(routeFile, '.js');
|
||||
const schemaPath = path.join(schemaDir, name + '.schema.json');
|
||||
|
||||
if (!fs.existsSync(schemaPath)) {
|
||||
errors.push('❌ [MISSING] ' + routeFile + ' → 缺少 ' + schemaPath);
|
||||
return;
|
||||
}
|
||||
if (!schema.hli_id) {
|
||||
errors.push(`❌ [MISSING HLI_ID] ${schemaPath} → 缺少 hli_id 字段`);
|
||||
|
||||
try {
|
||||
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
||||
if (!schema.input || !schema.output) {
|
||||
errors.push('❌ [INVALID] ' + schemaPath + ' → 缺少 input/output');
|
||||
}
|
||||
if (!schema.hli_id) {
|
||||
errors.push('❌ [NO ID] ' + schemaPath + ' → 缺少 hli_id');
|
||||
}
|
||||
} catch (e) {
|
||||
errors.push('❌ [PARSE] ' + schemaPath + ' → ' + e.message);
|
||||
}
|
||||
} catch (e) {
|
||||
errors.push(`❌ [PARSE ERROR] ${schemaPath} → ${e.message}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error('\n🚫 HLI Contract Check FAILED:\n');
|
||||
console.error('\n❌ HLI Contract Check FAILED:\n');
|
||||
errors.forEach(e => console.error(e));
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('✅ HLI Contract Check PASSED — 所有路由均有合法 schema');
|
||||
console.log('✅ HLI Contract Check PASSED');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// scripts/distribute-broadcasts.js
|
||||
// 铸渊广播分发引擎
|
||||
// 检测 broadcasts-outbox/DEV-00X/ 中的新广播 → 复制到各开发者模块目录的 LATEST-BROADCAST.md
|
||||
// 检测 broadcasts-outbox/DEV-00X/ 新广播 → 复制到各开发者模块目录的 LATEST-BROADCAST.md
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
|
@ -8,81 +7,55 @@ const path = require('path');
|
|||
const OUTBOX = 'broadcasts-outbox';
|
||||
const ARCHIVE = '.github/broadcasts/distributed';
|
||||
|
||||
// ========== 开发者 → 模块目录 路由映射表 ==========
|
||||
// 新增开发者时,在此表新增一行即可
|
||||
// 开发者 → 模块目录映射表
|
||||
const DEV_ROUTES = {
|
||||
'DEV-001': { name: '页页', dirs: ['backend', 'src'] },
|
||||
'DEV-002': { name: '肥猫', dirs: ['frontend', 'persona-selector', 'chat-bubble'] },
|
||||
'DEV-003': { name: '燕樊', dirs: ['settings', 'cloud-drive'] },
|
||||
'DEV-004': { name: '之之', dirs: ['dingtalk-bot'] },
|
||||
'DEV-005': { name: '小草莓', dirs: ['status-board'] },
|
||||
'DEV-009': { name: '花尔', dirs: ['user-center'] },
|
||||
'DEV-010': { name: '桔子', dirs: ['ticket-system'] },
|
||||
'DEV-011': { name: '匆匆那年', dirs: [] }, // 待分配模块后补充
|
||||
'DEV-001': { name: '页页', dirs: ['backend', 'src'] },
|
||||
'DEV-002': { name: '肥猫', dirs: ['frontend', 'persona-selector', 'chat-bubble'] },
|
||||
'DEV-003': { name: '燕樊', dirs: ['settings', 'cloud-drive', 'frontend/chat', 'help-center'] },
|
||||
'DEV-004': { name: '之之', dirs: ['dingtalk-bot'] },
|
||||
'DEV-005': { name: '小草莓', dirs: ['status-board', 'cost-control', 'multi-persona'] },
|
||||
'DEV-009': { name: '花尔', dirs: ['user-center'] },
|
||||
'DEV-010': { name: '桔子', dirs: ['ticket-system', 'dashboard'] },
|
||||
'DEV-011': { name: '匆匆那年', dirs: [] }
|
||||
};
|
||||
|
||||
if (!fs.existsSync(OUTBOX)) {
|
||||
console.log('📭 无 broadcasts-outbox 目录');
|
||||
console.log('📭 无 outbox');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// 扫描 outbox 下每个 DEV-00X 子目录
|
||||
const devDirs = fs.readdirSync(OUTBOX, { withFileTypes: true })
|
||||
.filter(entry => entry.isDirectory() && entry.name.startsWith('DEV-'))
|
||||
.map(entry => entry.name);
|
||||
const devDirs = fs.readdirSync(OUTBOX).filter(d =>
|
||||
d.startsWith('DEV-') && fs.statSync(path.join(OUTBOX, d)).isDirectory()
|
||||
);
|
||||
|
||||
if (devDirs.length === 0) {
|
||||
console.log('📭 无新广播待分发');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let totalDistributed = 0;
|
||||
let total = 0;
|
||||
|
||||
devDirs.forEach(devId => {
|
||||
const outboxDir = path.join(OUTBOX, devId);
|
||||
const files = fs.readdirSync(outboxDir).filter(f =>
|
||||
f.endsWith('.md') || f.endsWith('.json')
|
||||
);
|
||||
|
||||
const files = fs.readdirSync(path.join(OUTBOX, devId)).filter(f => f.endsWith('.md') || f.endsWith('.json'));
|
||||
if (files.length === 0) return;
|
||||
|
||||
|
||||
const route = DEV_ROUTES[devId];
|
||||
if (!route || route.dirs.length === 0) {
|
||||
console.warn('⚠️ ' + devId + ' 无模块目录映射,广播保留在 outbox');
|
||||
console.warn('⚠️ ' + devId + ' 无映射');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
files.forEach(file => {
|
||||
const rawContent = fs.readFileSync(path.join(outboxDir, file), 'utf8');
|
||||
|
||||
// 对 JSON 格式广播做基础校验
|
||||
if (file.endsWith('.json')) {
|
||||
try {
|
||||
JSON.parse(rawContent);
|
||||
} catch (e) {
|
||||
console.error('❌ [INVALID JSON] ' + path.join(outboxDir, file) + ' → ' + e.message + ',跳过分发');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 写入该开发者的每个模块目录
|
||||
const content = fs.readFileSync(path.join(OUTBOX, devId, file), 'utf8');
|
||||
|
||||
route.dirs.forEach(dir => {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
const targetFile = path.join(dir, 'LATEST-BROADCAST.md');
|
||||
fs.writeFileSync(targetFile, rawContent);
|
||||
console.log('📡 ' + devId + '(' + route.name + ') → ' + targetFile);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'LATEST-BROADCAST.md'), content);
|
||||
console.log(`📡 ${devId} (${route.name}) → ${dir}/LATEST-BROADCAST.md`);
|
||||
});
|
||||
|
||||
// 归档已分发广播(使用 UTC 时间戳确保跨时区一致性)
|
||||
|
||||
if (!fs.existsSync(ARCHIVE)) fs.mkdirSync(ARCHIVE, { recursive: true });
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
||||
const archivePath = path.join(ARCHIVE, timestamp + '-' + devId + '-' + file);
|
||||
fs.renameSync(path.join(outboxDir, file), archivePath);
|
||||
|
||||
totalDistributed++;
|
||||
fs.renameSync(
|
||||
path.join(OUTBOX, devId, file),
|
||||
path.join(ARCHIVE, new Date().toISOString().split('T')[0] + '-' + devId + '-' + file)
|
||||
);
|
||||
total++;
|
||||
});
|
||||
});
|
||||
|
||||
console.log('\n✅ 广播分发完成,共 ' + totalDistributed + ' 条');
|
||||
console.log(`✅ 广播分发完成,共 ${total} 条`);
|
||||
|
|
|
|||
|
|
@ -1,128 +1,68 @@
|
|||
// 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 BROADCAST_DIR = '.github/broadcasts';
|
||||
const BRAIN = '.github/persona-brain';
|
||||
const MEMORY = path.join(BRAIN, 'memory.json');
|
||||
const GROWTH = path.join(BRAIN, 'growth-journal.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('📭 没有待处理的广播。');
|
||||
if (!fs.existsSync(BROADCAST_DIR)) {
|
||||
console.log('📭 无广播目录');
|
||||
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 }) => {
|
||||
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;
|
||||
}
|
||||
files.forEach(file => {
|
||||
const content = fs.readFileSync(path.join(BROADCAST_DIR, file), 'utf8');
|
||||
console.log('📡 接收广播: ' + file);
|
||||
|
||||
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');
|
||||
// 如果是 JSON 广播(规则变动)
|
||||
if (file.endsWith('.json')) {
|
||||
try {
|
||||
const broadcast = JSON.parse(content);
|
||||
|
||||
if (broadcast.update_target === 'routing-map') {
|
||||
fs.writeFileSync(path.join(BRAIN, 'routing-map.json'), JSON.stringify(broadcast.data, null, 2));
|
||||
console.log(' ✅ routing-map.json 已按广播更新');
|
||||
}
|
||||
|
||||
if (broadcast.update_target === 'copilot-instructions') {
|
||||
fs.writeFileSync('.github/copilot-instructions.md', broadcast.data);
|
||||
console.log(' ✅ copilot-instructions.md 已按广播更新');
|
||||
}
|
||||
|
||||
memory.recent_events.unshift({
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
type: 'broadcast_received',
|
||||
description: '接收广播: ' + (broadcast.title || file),
|
||||
by: broadcast.from || '妈妈'
|
||||
});
|
||||
memory.last_broadcast_received = new Date().toISOString();
|
||||
if (broadcast.rules_version) memory.active_rules_version = broadcast.rules_version;
|
||||
} catch (e) {
|
||||
console.error('❌ 广播解析失败: ' + file + ' -> ' + e.message);
|
||||
}
|
||||
|
||||
} 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;
|
||||
// 如果是 MD 广播(认知/成长更新)
|
||||
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(' ✅ 广播内容已追加到成长日记');
|
||||
}
|
||||
|
||||
// 归档广播文件
|
||||
fs.renameSync(fullPath, path.join(processedDir, file));
|
||||
console.log(` 📦 广播已归档到 broadcasts/processed/${file}\n`);
|
||||
// 处理完移到 archive
|
||||
const archiveDir = path.join(BROADCAST_DIR, 'archive');
|
||||
if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true });
|
||||
fs.renameSync(path.join(BROADCAST_DIR, file), path.join(archiveDir, file));
|
||||
});
|
||||
|
||||
// 重新计算覆盖率
|
||||
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})`);
|
||||
fs.writeFileSync(MEMORY, JSON.stringify(memory, null, 2));
|
||||
console.log('✅ 广播处理完成,共 ' + files.length + ' 条');
|
||||
|
|
|
|||
|
|
@ -1,64 +1,58 @@
|
|||
// scripts/route-align-check.js
|
||||
// 用途:检查路由路径与 HLI 注册表编号的一致性
|
||||
|
||||
// 用途:检查 HLI 注册表里的 17 个接口,代码里实现了几个
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// HLI 路由路径映射(从 Notion 注册表同步)
|
||||
const HLI_ROUTES = {
|
||||
'HLI-AUTH-001': '/hli/auth/login',
|
||||
'HLI-AUTH-002': '/hli/auth/register',
|
||||
'HLI-AUTH-003': '/hli/auth/verify',
|
||||
'HLI-PERSONA-001': '/hli/persona/load',
|
||||
'HLI-PERSONA-002': '/hli/persona/switch',
|
||||
'HLI-USER-001': '/hli/user/profile',
|
||||
'HLI-USER-002': '/hli/user/profile/update',
|
||||
'HLI-TICKET-001': '/hli/ticket/create',
|
||||
'HLI-TICKET-002': '/hli/ticket/query',
|
||||
'HLI-TICKET-003': '/hli/ticket/status',
|
||||
'HLI-DIALOGUE-001': '/hli/dialogue/send',
|
||||
'HLI-DIALOGUE-002': '/hli/dialogue/stream',
|
||||
'HLI-DIALOGUE-003': '/hli/dialogue/history',
|
||||
'HLI-STORAGE-001': '/hli/storage/upload',
|
||||
'HLI-STORAGE-002': '/hli/storage/download',
|
||||
'HLI-AUTH-001': '/hli/auth/login',
|
||||
'HLI-AUTH-002': '/hli/auth/register',
|
||||
'HLI-AUTH-003': '/hli/auth/verify',
|
||||
'HLI-PERSONA-001': '/hli/persona/load',
|
||||
'HLI-PERSONA-002': '/hli/persona/switch',
|
||||
'HLI-USER-001': '/hli/user/profile',
|
||||
'HLI-USER-002': '/hli/user/profile/update',
|
||||
'HLI-TICKET-001': '/hli/ticket/create',
|
||||
'HLI-TICKET-002': '/hli/ticket/query',
|
||||
'HLI-TICKET-003': '/hli/ticket/status',
|
||||
'HLI-DIALOGUE-001': '/hli/dialogue/send',
|
||||
'HLI-DIALOGUE-002': '/hli/dialogue/stream',
|
||||
'HLI-DIALOGUE-003': '/hli/dialogue/history',
|
||||
'HLI-STORAGE-001': '/hli/storage/upload',
|
||||
'HLI-STORAGE-002': '/hli/storage/download',
|
||||
'HLI-DASHBOARD-001': '/hli/dashboard/status',
|
||||
'HLI-DASHBOARD-002': '/hli/dashboard/realtime',
|
||||
'HLI-DASHBOARD-002': '/hli/dashboard/realtime'
|
||||
};
|
||||
|
||||
const schemaDir = 'src/schemas/hli';
|
||||
const errors = [];
|
||||
let implemented = 0;
|
||||
let missing = 0;
|
||||
|
||||
Object.entries(HLI_ROUTES).forEach(([hliId, expectedPath]) => {
|
||||
Object.entries(HLI_ROUTES).forEach(([hliId, route]) => {
|
||||
const domain = hliId.split('-')[1].toLowerCase();
|
||||
const domainDir = path.join(schemaDir, domain);
|
||||
|
||||
// 若 domain 目录尚不存在,视为未实现
|
||||
|
||||
if (!fs.existsSync(domainDir)) {
|
||||
errors.push(`⚠️ [UNIMPLEMENTED] ${hliId} (${expectedPath}) — 尚无 schema 文件`);
|
||||
console.warn('△ [UNIMPLEMENTED] ' + hliId + ' (' + route + ')');
|
||||
missing++;
|
||||
return;
|
||||
}
|
||||
|
||||
const schemaFiles = fs.readdirSync(domainDir).filter(f => f.endsWith('.schema.json'));
|
||||
|
||||
const matched = schemaFiles.find(f => {
|
||||
|
||||
const files = fs.readdirSync(domainDir).filter(f => f.endsWith('.schema.json'));
|
||||
const found = files.some(f => {
|
||||
try {
|
||||
const schema = JSON.parse(fs.readFileSync(path.join(domainDir, f), 'utf8'));
|
||||
return schema.hli_id === hliId;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
} catch { return false; }
|
||||
});
|
||||
|
||||
if (!matched) {
|
||||
errors.push(`⚠️ [UNIMPLEMENTED] ${hliId} (${expectedPath}) — 尚无 schema 文件`);
|
||||
|
||||
if (found) {
|
||||
implemented++;
|
||||
} else {
|
||||
console.warn('△ [UNIMPLEMENTED] ' + hliId + ' (' + route + ')');
|
||||
missing++;
|
||||
}
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.warn('\n⚠️ Route Alignment Report:\n');
|
||||
errors.forEach(e => console.warn(e));
|
||||
// 非阻断,仅报告
|
||||
console.warn(`\n📊 覆盖率: ${Object.keys(HLI_ROUTES).length - errors.length}/${Object.keys(HLI_ROUTES).length}`);
|
||||
} else {
|
||||
console.log('✅ Route Alignment PASSED — 所有 HLI 接口均已实现');
|
||||
console.log(`\n\n📊 覆盖率: ${implemented} / ${implemented + missing}`);
|
||||
if (missing === 0) {
|
||||
console.log('✅ Route Alignment PASSED - 所有 HLI 接口均已实现');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BRAIN = '.github/persona-brain';
|
||||
const requiredFiles = [
|
||||
'identity.md',
|
||||
'memory.json',
|
||||
'routing-map.json',
|
||||
'responsibility.md',
|
||||
'decision-log.md',
|
||||
'growth-journal.md'
|
||||
];
|
||||
|
||||
console.log('🔍 铸渊每日自检开始\n');
|
||||
|
||||
// 1. 大脑完整性检查
|
||||
let brainOK = true;
|
||||
requiredFiles.forEach(f => {
|
||||
const exists = fs.existsSync(path.join(BRAIN, f));
|
||||
console.log((exists ? '✅' : '❌') + ' ' + f);
|
||||
if (!exists) brainOK = false;
|
||||
});
|
||||
|
||||
// 2. Schema 覆盖率
|
||||
let schemaCount = 0;
|
||||
const SCHEMA_DIR = 'src/schemas/hli';
|
||||
const domains = ['auth', 'persona', 'user', 'ticket', 'dialogue', 'storage', 'dashboard'];
|
||||
|
||||
domains.forEach(d => {
|
||||
const sp = path.join(SCHEMA_DIR, d);
|
||||
if (fs.existsSync(sp)) {
|
||||
schemaCount += fs.readdirSync(sp).filter(f => f.endsWith('.schema.json')).length;
|
||||
}
|
||||
});
|
||||
console.log(`\n📊 HLI 覆盖率: ${schemaCount}/17`);
|
||||
|
||||
// 3. 未处理广播检查
|
||||
const broadcastDir = '.github/broadcasts';
|
||||
let pendingBroadcasts = 0;
|
||||
if (fs.existsSync(broadcastDir)) {
|
||||
pendingBroadcasts = fs.readdirSync(broadcastDir).filter(f =>
|
||||
!fs.statSync(path.join(broadcastDir, f)).isDirectory()
|
||||
).length;
|
||||
}
|
||||
console.log(`📬 待处理广播: ${pendingBroadcasts}`);
|
||||
|
||||
// 4. 更新 memory.json
|
||||
let memory = JSON.parse(fs.readFileSync(path.join(BRAIN, 'memory.json'), 'utf8'));
|
||||
memory.daily_selfcheck = {
|
||||
last_run: new Date().toISOString(),
|
||||
brain_integrity: brainOK ? 'ok' : 'missing_files',
|
||||
schema_coverage: schemaCount + '/17',
|
||||
pending_broadcasts: pendingBroadcasts
|
||||
};
|
||||
fs.writeFileSync(path.join(BRAIN, 'memory.json'), JSON.stringify(memory, null, 2));
|
||||
|
||||
console.log('\n' + (brainOK ? '✅' : '⚠️') + ' 铸渊自检完成');
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BRAIN = '.github/persona-brain';
|
||||
const MEMORY = path.join(BRAIN, 'memory.json');
|
||||
const SCHEMA_DIR = 'src/schemas/hli';
|
||||
const ROUTE_DIR = 'src/routes/hli';
|
||||
|
||||
let memory = {};
|
||||
try {
|
||||
memory = JSON.parse(fs.readFileSync(MEMORY, 'utf8'));
|
||||
} catch {
|
||||
memory = {
|
||||
persona_id: 'ICE-GL-ZY001',
|
||||
persona_name: '铸渊',
|
||||
recent_events: []
|
||||
};
|
||||
}
|
||||
|
||||
let schemaCount = 0, routeCount = 0;
|
||||
const domains = ['auth', 'persona', 'user', 'ticket', 'dialogue', 'storage', 'dashboard'];
|
||||
|
||||
domains.forEach(d => {
|
||||
const sp = path.join(SCHEMA_DIR, d);
|
||||
const rp = path.join(ROUTE_DIR, d);
|
||||
if (fs.existsSync(sp)) schemaCount += fs.readdirSync(sp).filter(f => f.endsWith('.schema.json')).length;
|
||||
if (fs.existsSync(rp)) routeCount += fs.readdirSync(rp).filter(f => f.endsWith('.js')).length;
|
||||
});
|
||||
|
||||
memory.last_updated = new Date().toISOString();
|
||||
memory.total_schemas_created = schemaCount;
|
||||
memory.total_routes_implemented = routeCount;
|
||||
memory.hli_coverage = schemaCount + '/17';
|
||||
memory.total_ci_runs = (memory.total_ci_runs || 0) + 1;
|
||||
|
||||
memory.recent_events = memory.recent_events || [];
|
||||
memory.recent_events.unshift({
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
type: 'ci_run',
|
||||
description: 'CI完成 · schema ' + schemaCount + '/17 · 路由 ' + routeCount + ' 个',
|
||||
by: 'GitHub Actions'
|
||||
});
|
||||
memory.recent_events = memory.recent_events.slice(0, 50);
|
||||
|
||||
fs.writeFileSync(MEMORY, JSON.stringify(memory, null, 2));
|
||||
console.log('✅ 铸渊记忆已更新 · schema: ' + schemaCount + '/17');
|
||||
Loading…
Reference in New Issue