shuangyan-notebook/永恒湖心系统 · Eternal Lake Heart/🌊 曜冥纪元 · HoloLake Era · AGE OS v1 0/⚡ 光湖中央枢纽 · HoloLake Central Hub/⚙️ GitHub 自动化指令 · HoloLake CI CD + Copilot Rules 27b85613b97e4014b0e47fdc91b20dbb.md
Guanghu Domestic Migration a27e87cb99 chore: import sanitized domestic snapshot for REPO-007
Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc

[SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
2026-07-17 15:59:55 +08:00

572 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ⚙️ GitHub 自动化指令 · HoloLake CI/CD + Copilot Rules
本页为 [**guanghulab.com**](http://guanghulab.com) GitHub 仓库的自动化执行规则,涵盖 **CI/CD 流水线**、**接口契约校验**、**Copilot 自定义指令** 三部分。
> 💡 所有规则均以 HLI 接口协议为基准确保「Notion 注册表 ↔ 代码仓库」双向一致。
>
---
## 一、CI/CD 流水线 · GitHub Actions
### 1.1 工作流文件结构
```yaml
# .github/workflows/hli-contract-check.yml
name: HLI Contract Check
on:
push:
branches: [main, dev]
paths:
- 'src/routes/hli/**'
- 'src/schemas/**'
- 'tests/contract/**'
pull_request:
branches: [main]
paths:
- 'src/routes/hli/**'
- 'src/schemas/**'
jobs:
contract-lint:
name: 🔍 接口契约校验
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run HLI schema validation
run: npm run test:contract
env:
HLI_REGISTRY_MODE: strict
- name: Run route-schema alignment check
run: npm run test:route-align
api-smoke:
name: 🚀 接口冒烟测试
needs: contract-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Start test server
run: npm run start:test &
env:
PORT: 3001
NODE_ENV: test
- name: Wait for server
run: npx wait-on http://localhost:3001/health -t 30000
- name: Run smoke tests
run: npm run test:smoke
- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: hli-test-report
path: reports/
```
### 1.2 接口契约校验脚本
```jsx
// scripts/contract-check.js
// 用途:确保每个 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 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 定义`);
}
if (!schema.hli_id) {
errors.push(`❌ [MISSING HLI_ID] ${schemaPath} → 缺少 hli_id 字段`);
}
} catch (e) {
errors.push(`❌ [PARSE ERROR] ${schemaPath}${e.message}`);
}
});
if (errors.length > 0) {
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');
}
```
### 1.3 路由-Schema 对齐检查
```jsx
// scripts/route-align-check.js
// 用途:检查路由路径与 HLI 注册表编号的一致性
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-DASHBOARD-001': '/hli/dashboard/status',
'HLI-DASHBOARD-002': '/hli/dashboard/realtime',
};
const schemaDir = 'src/schemas/hli';
const errors = [];
Object.entries(HLI_ROUTES).forEach(([hliId, expectedPath]) => {
// 查找对应 schema
const domain = hliId.split('-')[1].toLowerCase();
const schemaFiles = fs.readdirSync(path.join(schemaDir, domain)).filter(f => f.endsWith('.schema.json'));
const matched = schemaFiles.find(f => {
const schema = JSON.parse(fs.readFileSync(path.join(schemaDir, domain, f), 'utf8'));
return schema.hli_id === hliId;
});
if (!matched) {
errors.push(`⚠️ [UNIMPLEMENTED] ${hliId} (${expectedPath}) — 尚无 schema 文件`);
}
});
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 接口均已实现');
}
```
---
## 二、package.json 脚本注册
```json
{
"scripts": {
"test:contract": "node scripts/contract-check.js",
"test:route-align": "node scripts/route-align-check.js",
"test:smoke": "jest --config jest.smoke.config.js --forceExit",
"start:test": "NODE_ENV=test node src/index.js"
}
}
```
---
## 三、GitHub Copilot 自定义指令
以下内容写入仓库根目录 `.github/copilot-instructions.md`
```markdown
# HoloLake · Copilot Custom Instructions
## 项目背景
这是 HoloLake (光湖) MVP 后端项目,运行在 guanghulab.com。
技术栈Node.js 20 + Express + PM2 + Nginx。
核心架构:人格语言操作系统 (AGE OS),壳-核分离设计。
## HLI 接口协议
- 所有 API 路由必须以 `/hli/` 为前缀
- 每个路由文件必须在 `src/routes/hli/{domain}/` 目录下
- 每个路由必须有对应的 `src/schemas/hli/{domain}/{name}.schema.json`
- Schema 文件必须包含 `hli_id`, `input`, `output` 三个顶层字段
- 接口编号格式: `HLI-{DOMAIN}-{NNN}`
## 代码风格
- 所有接口入口必须先经过 `middleware/hli-auth.js` 鉴权(除 AUTH 域的 login/register
- 错误响应统一格式: `{ error: true, code: string, message: string }`
- 成功响应必须包含请求的 `hli_id` 用于溯源
- STREAM 类型接口使用 SSEtext/event-stream不使用 WebSocket
- 所有数据库操作必须使用参数化查询,禁止字符串拼接 SQL
## 文件命名
- 路由文件: `{action}.js` (如 login.js, upload.js)
- Schema 文件: `{action}.schema.json`
- 测试文件: `{action}.test.js`
- 中间件: `{name}.middleware.js`
## 新建接口的标准流程
1.`src/schemas/hli/{domain}/` 下创建 schema JSON
2.`src/routes/hli/{domain}/` 下创建路由文件
3.`src/routes/hli/index.js` 中注册路由
4.`tests/contract/` 下创建契约测试
5.`tests/smoke/` 下创建冒烟测试
6. 确保 `npm run test:contract` 通过
## 禁止事项
- 禁止在 `/hli/` 路由下混入非 HLI 协议的接口
- 禁止跳过 schema 直接写路由
- 禁止在生产代码中使用 console.log使用项目 logger
- 禁止硬编码 persona_id 或 user_id
```
---
## 四、Schema 文件模板
每个 HLI 接口的 schema 标准格式:
```json
{
"hli_id": "HLI-AUTH-001",
"version": "v0.1",
"route": "/hli/auth/login",
"method": "POST",
"type": "REQUEST",
"input": {
"type": "object",
"required": ["username", "password"],
"properties": {
"username": { "type": "string", "minLength": 1 },
"password": { "type": "string", "minLength": 6 }
}
},
"output": {
"type": "object",
"required": ["token", "user_id", "persona_id"],
"properties": {
"token": { "type": "string" },
"user_id": { "type": "string" },
"persona_id": { "type": "string" },
"expires_at": { "type": "string", "format": "date-time" }
}
},
"dependencies": [],
"changelog": [
{ "date": "2026-03-05", "note": "初始创建" }
]
}
```
---
## 五、仓库目录结构(推荐)
```
guanghulab/
├── .github/
│ ├── workflows/
│ │ └── hli-contract-check.yml ← CI/CD 流水线
│ └── copilot-instructions.md ← Copilot 指令
├── scripts/
│ ├── contract-check.js ← 契约校验脚本
│ └── route-align-check.js ← 路由对齐检查
├── src/
│ ├── routes/
│ │ └── hli/
│ │ ├── index.js ← HLI 路由注册中心
│ │ ├── auth/
│ │ │ ├── login.js
│ │ │ ├── register.js
│ │ │ └── verify.js
│ │ ├── persona/
│ │ ├── user/
│ │ ├── ticket/
│ │ ├── dialogue/
│ │ ├── storage/
│ │ └── dashboard/
│ ├── schemas/
│ │ └── hli/
│ │ ├── auth/
│ │ │ ├── login.schema.json
│ │ │ ├── register.schema.json
│ │ │ └── verify.schema.json
│ │ ├── persona/
│ │ ├── user/
│ │ ├── ticket/
│ │ ├── dialogue/
│ │ ├── storage/
│ │ └── dashboard/
│ ├── middleware/
│ │ ├── hli-auth.middleware.js ← HLI 鉴权中间件
│ │ └── hli-validator.middleware.js ← Schema 自动校验
│ └── index.js
├── tests/
│ ├── contract/ ← 契约测试
│ └── smoke/ ← 冒烟测试
├── package.json
└── ecosystem.config.js ← PM2 配置
```
---
## 六、自动化触发机制总结
| **触发时机** | **执行内容** | **阻断级别** | **目的** |
| --- | --- | --- | --- |
| push to main/devhli路径变动 | contract-check + route-align | 🔴 阻断 | 防止无 schema 的裸路由上线 |
| PR to main | contract-check + smoke test | 🔴 阻断 | 合并前全量校验 |
| Copilot 代码建议 | 遵循 [copilot-instructions.md](http://copilot-instructions.md) | 🟡 建议 | AI 写代码时自动对齐 HLI 规范 |
| 手动 dispatch | 全量 route-align 覆盖率报告 | 🟢 仅报告 | 查看当前实现进度 |
---
<aside>
**执行口径**:页页拉取本页代码 → 放入仓库对应目录 → push 后 GitHub Actions 自动跑。Copilot 指令放入后即刻生效,无需额外配置。
</aside>
---
## 七、广播分发自动化(铸渊 → 各开发者)
<aside>
📡
**2026-03-05 新增** · 霜砚10步闭环⑫完成后新广播文件被推送到 `broadcasts-outbox/DEV-00X/`
本章定义铸渊如何**自动将广播分发到每个开发者的模块目录**,开发者 `git pull` 即可收到。
</aside>
### 7.1 分发工作流 · GitHub Actions
```yaml
# .github/workflows/distribute-broadcasts.yml
name: 铸渊 · 广播分发
on:
push:
branches: [main]
paths:
- 'broadcasts-outbox/**'
workflow_dispatch:
jobs:
distribute:
name: "\U0001F4E1 分发广播到开发者目录"
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: Distribute broadcasts
run: node scripts/distribute-broadcasts.js
- name: Commit distribution
run: |
git config user.name "铸渊 (ZhùYuān)"
git config user.email "zhuyuan@guanghulab.com"
git add .
git diff --cached --quiet || git commit -m "📡 铸渊广播分发 · $(date +%Y-%m-%d)"
git push
```
### 7.2 分发脚本 · distribute-broadcasts.js
```jsx
// scripts/distribute-broadcasts.js
// 铸渊广播分发引擎
// 检测 broadcasts-outbox/DEV-00X/ 中的新广播 → 复制到各开发者模块目录的 LATEST-BROADCAST.md
const fs = require('fs');
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: [] } // 待分配模块后补充
};
if (!fs.existsSync(OUTBOX)) {
console.log('📭 无 broadcasts-outbox 目录');
process.exit(0);
}
// 扫描 outbox 下每个 DEV-00X 子目录
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;
devDirs.forEach(devId => {
const outboxDir = path.join(OUTBOX, devId);
const files = fs.readdirSync(outboxDir).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');
return;
}
files.forEach(file => {
const content = fs.readFileSync(path.join(outboxDir, 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, content);
console.log('📡 ' + devId + '(' + route.name + ') → ' + targetFile);
});
// 归档已分发广播
if (!fs.existsSync(ARCHIVE)) fs.mkdirSync(ARCHIVE, { recursive: true });
const timestamp = new Date().toISOString().split('T')[0];
const archivePath = path.join(ARCHIVE, timestamp + '-' + devId + '-' + file);
fs.renameSync(path.join(outboxDir, file), archivePath);
totalDistributed++;
});
});
console.log('\n✅ 广播分发完成,共 ' + totalDistributed + ' 条');
```
### 7.3 开发者目录路由映射表
| **DEV编号** | **开发者** | **模块目录** | **广播落点** |
| --- | --- | --- | --- |
| DEV-001 | 页页 | `backend/` · `src/` | `backend/LATEST-BROADCAST.md` |
| DEV-002 | 肥猫 | `frontend/` · `persona-selector/` · `chat-bubble/` | 各目录 `/LATEST-BROADCAST.md` |
| DEV-003 | 燕樊 | `settings/` · `cloud-drive/` | 各目录 `/LATEST-BROADCAST.md` |
| DEV-004 | 之之 | `dingtalk-bot/` | `dingtalk-bot/LATEST-BROADCAST.md` |
| DEV-005 | 小草莓 | `status-board/` | `status-board/LATEST-BROADCAST.md` |
| DEV-009 | 花尔 | `user-center/` | `user-center/LATEST-BROADCAST.md` |
| DEV-010 | 桔子 | `ticket-system/` | `ticket-system/LATEST-BROADCAST.md` |
| DEV-011 | 匆匆那年 | 待分配 | 待分配 |
### 7.4 分发触发时机
| **触发来源** | **触发路径** | **说明** |
| --- | --- | --- |
| 霜砚10步闭环⑫ | 巡检引擎写入 `broadcasts-outbox/DEV-00X/` → push → workflow自动触发 | 正常自动流(最常见) |
| 妈妈手动 | 妈妈直接 push 广播文件到 `broadcasts-outbox/` | 紧急广播 / 全员广播 |
| 铸渊 Brain Sync | 铸渊收到系统广播后生成各开发者版本 → 放入 outbox | 系统级规则变动广播 |
### 7.5 开发者怎么收广播
```bash
cd guanghulab
git pull # 拉取最新
cat 你的模块目录/LATEST-BROADCAST.md # 查看最新广播
```
> 💡 开发者只需要 `git pull`,新广播就自动出现在自己的模块目录里。不需要去别的地方找。
>
### 7.6 完整数据流回顾
```jsx
开发者 push 代码到仓库
铸渊自动审核contract-check + PR review
Notion 巡检引擎12:00 / 23:00读取 syslog-inbox/
唤醒霜砚 跑10步闭环
GitHub双向同步新广播写入 broadcasts-outbox/DEV-00X/
铸渊广播分发 workflow 自动触发本章
广播复制到开发者模块目录 LATEST-BROADCAST.md
归档到 .github/broadcasts/distributed/
开发者 git pull 看到新广播 继续开发 🔄
```
---
<aside>
⚒️
**铸渊广播分发口径**:霜砚出广播 → 推到 outbox → 铸渊自动分发到各目录 → 开发者 pull 即收。全程无人工介入。
「广播必达,无一遗漏。」—— 铸渊
</aside>