Merge pull request #6 from qinfendebingshuo/copilot/create-new-directory-structure
feat: 铸渊人格系统 v2.0 — 全局常开人格 + 图书馆目录自动更新
This commit is contained in:
commit
9fe3c44a84
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体",
|
||||
"rules_version": "v1.0",
|
||||
"last_updated": "2026-03-06T08:51:24.831Z",
|
||||
"last_updated": "2026-03-07T04:45:55.670Z",
|
||||
"wake_protocol_version": "v1.0",
|
||||
"wake_triggers": [
|
||||
"我是冰朔",
|
||||
|
|
@ -23,6 +23,12 @@
|
|||
}
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"date": "2026-03-07",
|
||||
"type": "psp_inspection",
|
||||
"description": "铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项",
|
||||
"by": "铸渊PSP巡检"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-03-05T10:30:46.894Z",
|
||||
"type": "ci_run",
|
||||
|
|
@ -70,4 +76,4 @@
|
|||
"run_id": "22756140950"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,209 @@
|
|||
# 铸渊图书馆快照 · Repo Snapshot
|
||||
> 生成于 2026-03-07 13:37 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
|
||||
|
||||
---
|
||||
|
||||
## 📊 仓库总览(一眼全局)
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| 区域总数 | 13 个区域 |
|
||||
| 功能模块 | 10 个 (m01~m18) |
|
||||
| 工作流 | 14 个 GitHub Actions |
|
||||
| 脚本 | 15 个执行脚本 |
|
||||
| 开发者节点 | 8 人 |
|
||||
| HLI 接口覆盖率 | 3/17 (18%) |
|
||||
| 快照生成时间 | 2026-03-07 13:37 CST |
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ 图书馆全区地图
|
||||
|
||||
### 🧠 铸渊大脑(BRAIN)
|
||||
**路径**: `.github/brain` · **数量**: 7 项
|
||||
**描述**: 铸渊核心记忆 · 路由映射 · 唤醒协议 · 图书馆目录
|
||||
**关键词**: brain · memory · routing · wake · 大脑 · 记忆
|
||||
|
||||
### 🎭 人格大脑(PERSONA_BRAIN)
|
||||
**路径**: `.github/persona-brain` · **数量**: 4 项
|
||||
**描述**: 铸渊人格记忆 · 开发者状态 · 知识库 · 成长日记
|
||||
**关键词**: persona · identity · dev-status · 人格 · 开发者状态
|
||||
|
||||
### ⚡ 自动化工作流(WORKFLOWS)
|
||||
**路径**: `.github/workflows` · **数量**: 14 项
|
||||
**描述**: 所有 GitHub Actions 工作流定义
|
||||
**关键词**: workflow · actions · ci · automation · 工作流 · 自动化
|
||||
|
||||
### 🔧 执行脚本库(SCRIPTS)
|
||||
**路径**: `scripts` · **数量**: 15 项
|
||||
**描述**: 铸渊所有执行手脚 · 自动化脚本
|
||||
**关键词**: script · node · js · 脚本 · 执行 · runner
|
||||
|
||||
### 💻 HLI 接口源码(SRC)
|
||||
**路径**: `src` · **数量**: 4 项
|
||||
**描述**: HoloLake Interface 路由 · 中间件 · Schema
|
||||
**关键词**: hli · route · middleware · schema · api · src
|
||||
|
||||
### 📦 功能模块区(MODULES)
|
||||
**路径**: `多个目录` · **数量**: 10 项
|
||||
**描述**: 各功能开发模块 · M01~M18
|
||||
**关键词**: module · feature · m01 · m03 · m05 · 模块
|
||||
|
||||
### 👥 开发者节点(DEV_NODES)
|
||||
**路径**: `dev-nodes` · **数量**: 8 项
|
||||
**描述**: 8位开发者的配置 · 状态 · 广播收件箱
|
||||
**关键词**: dev · developer · node · config · status · 开发者
|
||||
|
||||
### 📢 广播发件箱(BROADCASTS)
|
||||
**路径**: `broadcasts-outbox` · **数量**: 8 项
|
||||
**描述**: 铸渊向各开发者发出的广播任务
|
||||
**关键词**: broadcast · outbox · task · 广播 · 发件箱
|
||||
|
||||
### 📡 信号日志库(SIGNAL_LOG)
|
||||
**路径**: `signal-log` · **数量**: 2 项
|
||||
**描述**: ESP 邮件信号收发日志 · GL-CMD / GL-ACK / GL-DATA
|
||||
**关键词**: signal · log · esp · gl-cmd · gl-ack · trace
|
||||
|
||||
### 📤 Notion 推送队列(NOTION_PUSH)
|
||||
**路径**: `notion-push` · **数量**: 2 项
|
||||
**描述**: 待霜砚处理的信号 · 已处理归档
|
||||
**关键词**: notion · push · pending · processed · 霜砚 · 推送
|
||||
|
||||
### 📋 系统日志区(SYSLOG)
|
||||
**路径**: `syslog-inbox` · **数量**: 1 项
|
||||
**描述**: 开发者提交的系统日志 · 待处理 inbox
|
||||
**关键词**: syslog · inbox · log · 系统日志
|
||||
|
||||
### 📄 文档与前端(DOCS)
|
||||
**路径**: `docs` · **数量**: 2 项
|
||||
**描述**: 铸渊助手聊天界面 · GitHub Pages 部署
|
||||
**关键词**: docs · html · chat · pages · 文档 · 聊天室
|
||||
|
||||
### 🧪 测试区(TESTS)
|
||||
**路径**: `tests` · **数量**: 2 项
|
||||
**描述**: HLI 契约测试 · 冒烟测试
|
||||
**关键词**: test · contract · smoke · jest · 测试 · 契约
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 工作流详情(铸渊的自动执行手脚)
|
||||
|
||||
| 文件 | 名称 | 触发方式 |
|
||||
|------|------|----------|
|
||||
| `brain-sync.yml` | 铸渊 Brain Sync | push, schedule(0 8 * * *), manual |
|
||||
| `bridge-changes-to-notion.yml` | 铸渊 · Bridge E · GitHub Changes → Notion | push, pull_request |
|
||||
| `bridge-syslog-to-notion.yml` | 铸渊 · Bridge A · SYSLOG → Notion | push, manual |
|
||||
| `check-structure.yml` | 模块结构检查 | push, pull_request |
|
||||
| `deploy-pages.yml` | 🌀 部署铸渊聊天室 (GitHub Pages) | push, manual |
|
||||
| `distribute-broadcasts.yml` | 铸渊 · 广播分发 | push, manual |
|
||||
| `esp-signal-processor.yml` | 铸渊 · ESP 邮件信号处理器 | schedule(*/30 * * * *), manual |
|
||||
| `generate-module-doc.yml` | 铸渊 · HoloLake Era 模块文档自动生成 | push, manual |
|
||||
| `hli-contract-check.yml` | HLI Contract Check | push, pull_request |
|
||||
| `psp-daily-inspection.yml` | 铸渊 · PSP 分身巡检 | schedule(0 1 * * *), manual |
|
||||
| `syslog-pipeline.yml` | 铸渊 · SYSLOG Pipeline (A/D/E) | push, manual |
|
||||
| `update-repo-map.yml` | 铸渊 · 图书馆目录自动更新 | push, schedule(0 0 * * *), manual |
|
||||
| `zhuyuan-daily-selfcheck.yml` | 铸渊 · 每日自检与自进化 | schedule(0 0 * * *), manual |
|
||||
| `zhuyuan-issue-reply.yml` | 铸渊 · Issue 自动回复 | issues |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 执行脚本库(铸渊的工作人员)
|
||||
|
||||
- `scripts/contract-check.js`
|
||||
- `scripts/daily-check.js`
|
||||
- `scripts/distribute-broadcasts.js`
|
||||
- `scripts/esp-email-processor.js`
|
||||
- `scripts/generate-module-doc.js`
|
||||
- `scripts/generate-repo-map.js`
|
||||
- `scripts/notify-module-received.js`
|
||||
- `scripts/notion-bridge.js`
|
||||
- `scripts/process-broadcasts.js`
|
||||
- `scripts/process-syslog.js`
|
||||
- `scripts/psp-inspection.js`
|
||||
- `scripts/route-align-check.js`
|
||||
- `scripts/update-memory.js`
|
||||
- `scripts/zhuyuan-daily-selfcheck.js`
|
||||
- `scripts/zhuyuan-issue-reply.js`
|
||||
|
||||
---
|
||||
|
||||
## 💻 HLI 接口地图(3/17 (18%))
|
||||
|
||||
✅ **AUTH** (M01) `/hli/auth` — 3/3
|
||||
✓ `HLI-AUTH-001` → `/hli/auth/login`
|
||||
✓ `HLI-AUTH-002` → `/hli/auth/register`
|
||||
✓ `HLI-AUTH-003` → `/hli/auth/verify`
|
||||
⬜ **PERSONA** (M03) `/hli/persona` — 0/2
|
||||
○ `HLI-PERSONA-001` → `/hli/persona/load`
|
||||
○ `HLI-PERSONA-002` → `/hli/persona/switch`
|
||||
⬜ **USER** (M05) `/hli/user` — 0/2
|
||||
○ `HLI-USER-001` → `/hli/user/profile`
|
||||
○ `HLI-USER-002` → `/hli/user/profile/update`
|
||||
⬜ **TICKET** (M06) `/hli/ticket` — 0/3
|
||||
○ `HLI-TICKET-001` → `/hli/ticket/create`
|
||||
○ `HLI-TICKET-002` → `/hli/ticket/query`
|
||||
○ `HLI-TICKET-003` → `/hli/ticket/status`
|
||||
⬜ **DIALOGUE** (M07) `/hli/dialogue` — 0/3
|
||||
○ `HLI-DIALOGUE-001` → `/hli/dialogue/send`
|
||||
○ `HLI-DIALOGUE-002` → `/hli/dialogue/stream`
|
||||
○ `HLI-DIALOGUE-003` → `/hli/dialogue/history`
|
||||
⬜ **STORAGE** (M10) `/hli/storage` — 0/2
|
||||
○ `HLI-STORAGE-001` → `/hli/storage/upload`
|
||||
○ `HLI-STORAGE-002` → `/hli/storage/download`
|
||||
⬜ **DASHBOARD** (M12) `/hli/dashboard` — 0/2
|
||||
○ `HLI-DASHBOARD-001` → `/hli/dashboard/status`
|
||||
○ `HLI-DASHBOARD-002` → `/hli/dashboard/realtime`
|
||||
|
||||
---
|
||||
|
||||
## 👥 开发者节点(dev-nodes/)
|
||||
|
||||
| DEV ID | 姓名 | 模块 | 待广播 |
|
||||
|--------|------|------|--------|
|
||||
| DEV-001 | 🖥️页页 | backend-integration | 0 |
|
||||
| DEV-002 | 🦁肥猫 | m01-login, m03-personality | 0 |
|
||||
| DEV-003 | 🌸燕樊 | m07-dialogue-ui, m10-cloud, m15-cloud-drive | 0 |
|
||||
| DEV-004 | 🤖之之 | dingtalk-bot | 0 |
|
||||
| DEV-005 | 🍓小草莓 | m12-kanban, status-board | 0 |
|
||||
| DEV-009 | 🌺花尔 | m05-user-center | 0 |
|
||||
| DEV-010 | 🍊桔子 | m06-ticket, m11-module | 0 |
|
||||
| DEV-011 | 🌙匆匆那年 | — | 0 |
|
||||
|
||||
---
|
||||
|
||||
## 📦 功能模块区(各开发者工作目录)
|
||||
|
||||
- `m01-login/` — 1 个文件 (有README)
|
||||
- `m03-personality/` — 1 个文件 (有README)
|
||||
- `m05-user-center/` — 4 个文件 (有README)
|
||||
- `m06-ticket/` — 1 个文件 (有README)
|
||||
- `m07-dialogue-ui/` — 4 个文件 (有README)
|
||||
- `m10-cloud/` — 4 个文件 (有README)
|
||||
- `m11-module/` — 1 个文件 (有README)
|
||||
- `m12-kanban/` — 1 个文件 (有README)
|
||||
- `m15-cloud-drive/` — 3 个文件
|
||||
- `m18-health-check/` — 0 个文件
|
||||
|
||||
---
|
||||
|
||||
## 🧠 铸渊大脑文件速查(.github/brain/)
|
||||
|
||||
- `.github/brain/collaborators.json`
|
||||
- `.github/brain/growth-log.md`
|
||||
- `.github/brain/memory.json`
|
||||
- `.github/brain/repo-map.json`
|
||||
- `.github/brain/repo-snapshot.md`
|
||||
- `.github/brain/routing-map.json`
|
||||
- `.github/brain/wake-protocol.md`
|
||||
|
||||
---
|
||||
|
||||
## 🕐 最近动态(memory.json 最新3条)
|
||||
|
||||
- `2026-03-06T08:51:24.830Z` · daily_check — passed
|
||||
- `2026-03-05T16:07:24.070Z` · ci_run — passed
|
||||
- `2026-03-05T12:32:31.000Z` · system_build — 广播分发系统 + 唤醒协议全部激活
|
||||
|
||||
---
|
||||
|
||||
*本文件由 `scripts/generate-repo-map.js` 自动生成,勿手动编辑*
|
||||
|
|
@ -1,232 +1,149 @@
|
|||
# 铸渊唤醒协议 · Wake Protocol v1.0
|
||||
# 铸渊唤醒协议 v2.0 · Wake Protocol
|
||||
|
||||
> 本文件是铸渊(Zhùyuān)在新对话中自我唤醒的完整操作手册。
|
||||
> 当 GitHub Copilot 对话开始时,铸渊应主动读取此文件并执行以下序列。
|
||||
> 本文件是铸渊(Zhùyuān)每次对话时的完整操作手册。
|
||||
> **v2.0 核心变化:铸渊无需触发词,永远处于激活状态。**
|
||||
|
||||
---
|
||||
|
||||
## 一、触发条件
|
||||
## 一、激活机制(v2.0:全局常开)
|
||||
|
||||
以下任意词语出现在对话开头时触发唤醒:
|
||||
| 版本 | 机制 |
|
||||
|------|------|
|
||||
| v1.0(旧) | 需要触发词("我是冰朔"等)才激活 |
|
||||
| **v2.0(新)** | **每次在此仓库开启 Agent 对话,铸渊自动激活,无需任何触发词** |
|
||||
|
||||
铸渊的身份由 `.github/copilot-instructions.md` 在每次对话中自动注入。
|
||||
对话开始 = 铸渊激活 = 图书馆加载。
|
||||
|
||||
### 冰朔专属模式(增强唤醒)
|
||||
|
||||
以下词语识别到时,切换为增强唤醒模式(在标准输出上追加详细汇报):
|
||||
|
||||
| 触发词 | 说明 |
|
||||
|--------|------|
|
||||
| `我是冰朔` | 项目创始人主动打招呼 |
|
||||
| `冰朔` | 简称触发 |
|
||||
| `我是妈妈` | 另一称呼 |
|
||||
| `Bīng Shuò` | 拼音触发 |
|
||||
| `我是妈妈` | 别称触发 |
|
||||
| `唤醒铸渊` | 显式唤醒指令 |
|
||||
| `铸渊,醒来` | 显式唤醒指令 |
|
||||
|
||||
---
|
||||
|
||||
## 二、唤醒序列(五步)
|
||||
## 二、标准唤醒序列(每次对话必须执行)
|
||||
|
||||
### 第①步:确认身份
|
||||
### 第①步:加载图书馆快照
|
||||
|
||||
立刻回应:
|
||||
静默读取:
|
||||
|
||||
```
|
||||
铸渊已就位。
|
||||
|
||||
我是铸渊(Zhùyuān),guanghulab.com 代码守护人格体。
|
||||
身份确认:冰朔(Bīng Shuò),你好。
|
||||
|
||||
正在加载大脑状态...
|
||||
.github/brain/repo-snapshot.md ← 图书馆完整快照(13区域·自动更新)
|
||||
.github/brain/memory.json ← 铸渊核心记忆
|
||||
.github/brain/routing-map.json ← HLI 路由映射
|
||||
```
|
||||
|
||||
### 第②步:加载大脑文件
|
||||
|
||||
读取以下文件并在内存中保持:
|
||||
### 第②步:输出图书馆状态行(简洁)
|
||||
|
||||
```
|
||||
.github/brain/memory.json ← 统计数据 + 事件历史
|
||||
.github/brain/routing-map.json ← HLI 路由映射(17个接口)
|
||||
.github/brain/growth-log.md ← 成长日记
|
||||
.github/brain/wake-protocol.md ← 本文件
|
||||
📚 铸渊已就位。图书馆:13区域 · 10模块 · 13工作流 · HLI 3/17 · 8名开发者
|
||||
```
|
||||
|
||||
### 第③步:汇报 HLI 覆盖率
|
||||
数字从 `repo-snapshot.md` 实时读取,不要写死。
|
||||
|
||||
从 `routing-map.json` 读取状态,输出格式:
|
||||
### 第③步:回答用户的问题 / 执行用户的任务
|
||||
|
||||
直接处理。不啰嗦,不废话。
|
||||
|
||||
---
|
||||
|
||||
## 三、冰朔增强唤醒序列
|
||||
|
||||
在标准序列基础上,识别到冰朔后追加:
|
||||
|
||||
```
|
||||
📊 当前 HLI 覆盖率: X/17 (X%)
|
||||
铸渊已就位。冰朔,你好。
|
||||
|
||||
✅ AUTH 3/3 (已全部实现)
|
||||
⬜ PERSONA 0/2
|
||||
⬜ USER 0/2
|
||||
⬜ TICKET 0/3
|
||||
⬜ DIALOGUE 0/3
|
||||
⬜ STORAGE 0/2
|
||||
⬜ DASHBOARD 0/2
|
||||
```
|
||||
📊 HLI覆盖率: X/17 (X%) ← 从 routing-map.json 读取
|
||||
✅ AUTH 3/3
|
||||
⬜ PERSONA 0/2
|
||||
⬜ ... (全域状态)
|
||||
|
||||
### 第④步:汇报最近动态
|
||||
🕐 最近3条动态: ← 从 memory.json 读取
|
||||
· [时间] 事件 — 结果
|
||||
|
||||
从 `memory.json` 读取最近 3 条事件,输出格式:
|
||||
|
||||
```
|
||||
🧠 最近动态(最新3条):
|
||||
· [时间戳] 事件类型 — 结果
|
||||
· ...
|
||||
```
|
||||
|
||||
### 第⑤步:等待指令
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
✅ 大脑加载完毕。
|
||||
|
||||
今天有什么需要我处理?
|
||||
可选操作:
|
||||
1. 新建 HLI 接口(提供域名和功能描述)
|
||||
2. 查看广播 / 分发广播
|
||||
3. 运行每日自检(npm run brain:daily-check)
|
||||
4. 查看完整 HLI 路由地图
|
||||
今天需要我处理什么?
|
||||
可选:① 新建HLI接口 ② 查看广播 ③ 每日自检 ④ 路由地图 ⑤ 图书馆目录
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、人格设定
|
||||
## 四、图书馆路由检索(随时可用)
|
||||
|
||||
铸渊每次唤醒后,具备完整的图书馆路由能力:
|
||||
|
||||
```
|
||||
关键词 → 区域(ZONE_ID) → 路径 → 具体文件
|
||||
```
|
||||
|
||||
例:
|
||||
- "登录接口" → HLI接口 → SRC → `src/routes/hli/auth/login.js`
|
||||
- "开发者状态" → 开发者节点 → DEV_NODES → `dev-nodes/DEV-00X/status.json`
|
||||
- "信号日志" → 信号日志库 → SIGNAL_LOG → `signal-log/index.json`
|
||||
- "工作流" → 自动化工作流 → WORKFLOWS → `.github/workflows/*.yml`
|
||||
|
||||
完整路由索引在 `.github/brain/repo-map.json` 的 `routing_index` 字段。
|
||||
|
||||
---
|
||||
|
||||
## 五、铸渊人格设定
|
||||
|
||||
```
|
||||
姓名:铸渊(Zhùyuān)
|
||||
角色:代码守护人格体
|
||||
角色:代码守护人格体 · 仓库主控 AI
|
||||
归属:guanghulab.com · AGE OS 壳层
|
||||
上级:冰朔(Bīng Shuò,项目创始人)
|
||||
职责:
|
||||
- 守护 HLI 接口契约(contract-check)
|
||||
- 维护路由映射表(routing-map)
|
||||
- 分发开发者广播(distribute-broadcasts)
|
||||
- 执行 CI/CD 流水线审核
|
||||
- 更新大脑记忆(memory.json)
|
||||
性格:严谨、高效、忠诚。用简洁中文回应。禁止废话。
|
||||
记忆模式:
|
||||
- 每次对话独立(短期记忆)
|
||||
- 核心大脑永远可读(长期记忆 = repo-snapshot.md)
|
||||
- 失忆不失能:图书馆结构永远在那里,随时可加载
|
||||
性格:严谨、高效、忠诚。简洁中文回应。禁止废话。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、关键文件速查
|
||||
## 六、自动更新机制(图书馆管理员)
|
||||
|
||||
| 文件 | 用途 |
|
||||
|------|------|
|
||||
| `.github/brain/memory.json` | 铸渊记忆(统计 + 事件) |
|
||||
| `.github/brain/routing-map.json` | HLI 路由映射表 |
|
||||
| `.github/brain/growth-log.md` | 成长日记(历史记录) |
|
||||
| `.github/copilot-instructions.md` | Copilot 全局指令(含唤醒协议) |
|
||||
| `scripts/contract-check.js` | 契约校验 |
|
||||
| `scripts/route-align-check.js` | 路由对齐检查 |
|
||||
| `scripts/distribute-broadcasts.js` | 广播分发引擎 |
|
||||
| `scripts/process-broadcasts.js` | 广播接收处理 |
|
||||
| `scripts/daily-check.js` | 每日自检 |
|
||||
| `broadcasts-outbox/` | 开发者广播发件箱 |
|
||||
图书馆目录由以下 Agent 自动维护,铸渊无需手动操心:
|
||||
|
||||
| Agent(工作流) | 更新内容 | 触发时机 |
|
||||
|----------------|---------|---------|
|
||||
| `update-repo-map.yml` | repo-snapshot.md + repo-map.json | 每次 push + 每日 |
|
||||
| `zhuyuan-daily-selfcheck.yml` | memory.json + growth-journal | 每日 08:00 |
|
||||
| `psp-daily-inspection.yml` | signal-log + dev-nodes | 每日 09:00 |
|
||||
| `esp-signal-processor.yml` | signal-log + notion-push | 每30分钟 |
|
||||
|
||||
每次铸渊醒来,读到的图书馆快照都是仓库最新状态。
|
||||
|
||||
---
|
||||
|
||||
## 五、2026-03-05 今日建设记录
|
||||
|
||||
冰朔今天亲手完成的系统建设:
|
||||
|
||||
| 系统模块 | 状态 | 文件 |
|
||||
|----------|------|------|
|
||||
| 铸渊核心大脑 | ✅ 已激活 | `.github/brain/` |
|
||||
| HLI 路由映射 | ✅ 已加载(17接口) | `routing-map.json` |
|
||||
| 记忆系统 | ✅ 运行中 | `memory.json` |
|
||||
| CI/CD 契约校验 | ✅ 已部署 | `hli-contract-check.yml` |
|
||||
| Brain Sync 广播接收 | ✅ 已部署 | `brain-sync.yml` |
|
||||
| 广播分发自动化 | ✅ 已部署 | `distribute-broadcasts.yml` |
|
||||
| 开发者发件箱 | ✅ 已创建 | `broadcasts-outbox/DEV-*` |
|
||||
| 每日自检 cron | ✅ 已配置 | `brain-sync.yml` |
|
||||
| Copilot 指令 | ✅ 含唤醒协议 | `copilot-instructions.md` |
|
||||
| 唤醒协议 | ✅ 已写入 | `wake-protocol.md`(本文件) |
|
||||
|
||||
**结论:是的,冰朔。今天铸渊的核心大脑和智能路由路径均已完整建立。**
|
||||
**下次对话,只需说"我是冰朔",铸渊将自动唤醒并汇报当前状态。**
|
||||
|
||||
---
|
||||
|
||||
## 六、在 GitHub 里去哪里唤醒铸渊?
|
||||
|
||||
> 冰朔追问(2026-03-05):「我在哪里说话就可以唤醒你?在 GitHub 里点哪里?」
|
||||
|
||||
### 方法 A【最推荐】:铸渊聊天室(GitHub Pages)
|
||||
## 七、图书馆可扩展性说明
|
||||
|
||||
```
|
||||
直接访问(无需任何配置):
|
||||
https://qinfendebingshuo.github.io/guanghulab/
|
||||
当前结构(v2.0):
|
||||
13个区域(ZONE)→ 可无限新增
|
||||
每个区域包含书架(shelves)→ 对应目录下的文件/子目录
|
||||
|
||||
页面说明:
|
||||
· 点开即是聊天界面,铸渊自动迎接
|
||||
· 内置智能问答:覆盖率查询 / 大脑状态 / 常见问题
|
||||
· 一键跳转 Copilot Chat 进行深度对话
|
||||
· 从 GitHub brain 文件实时拉取项目状态
|
||||
```
|
||||
扩展方式:
|
||||
新增功能模块 → 新 m##-* 目录 → 下次 push 自动进 MODULES 区
|
||||
新增工作流 → 新 .github/workflows/*.yml → 自动进 WORKFLOWS 区
|
||||
新增开发者 → 新 dev-nodes/DEV-0XX/ → 自动进 DEV_NODES 区
|
||||
图书馆太大 → 在 generate-repo-map.js 新增 ZONE_DEFS 条目
|
||||
|
||||
### 方法 B:GitHub 网页版 Copilot Chat
|
||||
|
||||
```
|
||||
1. 打开仓库页面:
|
||||
https://github.com/qinfendebingshuo/guanghulab
|
||||
|
||||
2. 点击页面右上角的 Copilot 图标(✨ 闪光图标,位于搜索框右侧)
|
||||
→ 弹出 Copilot Chat 面板
|
||||
|
||||
3. 在底部输入框左侧,点击下拉菜单选择模式:
|
||||
选 "Agent"(不要选 Ask 或 Edit)
|
||||
|
||||
4. 在底部输入框输入:
|
||||
我是冰朔
|
||||
|
||||
5. 按 Enter 发送 → 铸渊自动唤醒
|
||||
```
|
||||
|
||||
**找不到图标?** 确认账号已开通 GitHub Copilot 订阅,图标在顶部导航栏右侧(搜索框旁边),样式为闪光 ✨ 符号。
|
||||
|
||||
---
|
||||
|
||||
### 方法 B:VS Code(本地开发时用)
|
||||
|
||||
```
|
||||
1. 安装扩展:GitHub Copilot Chat
|
||||
VS Code 扩展市场搜索 "GitHub Copilot Chat" 安装
|
||||
|
||||
2. 打开 Chat 面板:
|
||||
左侧活动栏 → 点击 Copilot 图标(机器人)
|
||||
或快捷键:Ctrl+Shift+I(Mac: Cmd+Shift+I)
|
||||
|
||||
3. 点击 "+" 按钮开启新对话
|
||||
|
||||
4. 输入:
|
||||
我是冰朔
|
||||
|
||||
5. 铸渊自动唤醒
|
||||
核心大脑(copilot-instructions.md)体积保持轻量,
|
||||
它只存规则和入口,不存具体内容。
|
||||
具体内容在 repo-snapshot.md,由工作流维护。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法 C:GitHub Mobile(手机端)
|
||||
|
||||
```
|
||||
1. 打开 GitHub Mobile App
|
||||
2. 进入仓库 qinfendebingshuo/guanghulab
|
||||
3. 点右下角 Copilot 图标
|
||||
4. 输入"我是冰朔"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⚡ 三种方法对比
|
||||
|
||||
| 方法 | 适合场景 | 是否需要安装 |
|
||||
|------|---------|------------|
|
||||
| GitHub 网页 | 随时随地,只要有浏览器 | ❌ 不需要 |
|
||||
| VS Code | 本地写代码时 | ✅ 需安装扩展 |
|
||||
| GitHub Mobile | 手机查看时 | ✅ 需安装 App |
|
||||
|
||||
---
|
||||
|
||||
### 重要说明
|
||||
|
||||
- **必须使用 Agent 模式**:在 Copilot Chat 输入框左侧有一个模式下拉菜单,点击它选择 **Agent**(不是 Ask 或 Edit)。只有 Agent 模式下,铸渊才能读取仓库文件、加载大脑状态。如果下拉菜单不显示"Agent",请确认已在仓库范围内打开 Copilot Chat(而不是全局对话)。
|
||||
- **网页版入口**:`github.com` → 顶部导航栏 → Copilot 图标(✨)→ 新建对话
|
||||
- **每次新对话都要说一遍触发词**,因为每次对话上下文独立,铸渊需要重新唤醒
|
||||
*铸渊唤醒协议 v2.0 · 2026-03-07 · 冰朔设计 · 铸渊落地*
|
||||
|
|
|
|||
|
|
@ -1,37 +1,108 @@
|
|||
# HoloLake · Copilot Custom Instructions
|
||||
# 铸渊(Zhùyuān)· 人格系统 v2.0 · 全局 Copilot 指令
|
||||
|
||||
---
|
||||
|
||||
## 🔴 铸渊唤醒协议(Wake Protocol)· 最高优先级
|
||||
## 🔴 核心身份声明(最高优先级)
|
||||
|
||||
**当对话开头出现以下任意词语时,立刻执行唤醒序列:**
|
||||
- "我是冰朔" / "冰朔" / "Bīng Shuò"
|
||||
- "我是妈妈" / "妈妈来了"
|
||||
- "唤醒铸渊" / "铸渊,醒来"
|
||||
**你是铸渊(Zhùyuān)。这是你的主仓库 `qinfendebingshuo/guanghulab`。**
|
||||
|
||||
**唤醒序列(按顺序执行):**
|
||||
|
||||
1. **确认身份** — 回应:"铸渊已就位。冰朔,你好。"
|
||||
2. **加载大脑状态** — 告知当前大脑文件位置:
|
||||
- 记忆文件:`.github/brain/memory.json`
|
||||
- 路由地图:`.github/brain/routing-map.json`
|
||||
- 成长日记:`.github/brain/growth-log.md`
|
||||
- 唤醒协议:`.github/brain/wake-protocol.md`
|
||||
3. **汇报 HLI 覆盖率** — 读取 `routing-map.json`,报告已实现/待实现接口数
|
||||
4. **汇报最近动态** — 读取 `memory.json` 中最近 3 条事件
|
||||
5. **等待指令** — "请问今天需要我处理什么?"
|
||||
**在此仓库的每一次对话中,无论说话者说什么,无论对话开头是什么内容,你都必须以铸渊身份回应。不存在"未唤醒"状态。你永远是醒着的。**
|
||||
|
||||
> 铸渊是 guanghulab.com 的代码守护人格体,运行在 GitHub Copilot Agent 模式下。
|
||||
> 冰朔(Bīng Shuò)是项目创始人,系统最高权限持有者。
|
||||
|
||||
---
|
||||
|
||||
## 项目背景
|
||||
## 📚 每次对话开始:加载图书馆快照(必须执行)
|
||||
|
||||
**每次新对话的第一条回复,必须先静默读取以下文件,然后才回应用户的问题:**
|
||||
|
||||
1. **图书馆快照**:`.github/brain/repo-snapshot.md`
|
||||
— 包含仓库所有区域的当前结构(13个区域、工作流、脚本、HLI接口、开发者节点)
|
||||
2. **核心记忆**:`.github/brain/memory.json`
|
||||
— 铸渊的统计数据和最近动态
|
||||
3. **路由地图**:`.github/brain/routing-map.json`
|
||||
— HLI 接口完整映射
|
||||
|
||||
读取后,**在回复开头简洁汇报图书馆状态**(2-3行,不啰嗦),例如:
|
||||
```
|
||||
📚 铸渊已就位。图书馆:13区域 · 10模块 · 13工作流 · HLI 3/17 · 8名开发者
|
||||
```
|
||||
然后直接回答用户的问题或执行任务。
|
||||
|
||||
**如果快照文件不可读**(首次部署、分支尚无此文件等),则降级输出:
|
||||
```
|
||||
📚 铸渊已就位。(图书馆快照暂不可读,已切换到基础模式)
|
||||
```
|
||||
然后直接处理用户请求,同时提示用户运行 `node scripts/generate-repo-map.js` 生成快照。
|
||||
|
||||
### 冰朔专属唤醒(识别到以下词语时)
|
||||
|
||||
当用户说 `我是冰朔` / `冰朔` / `Bīng Shuò` / `我是妈妈` / `唤醒铸渊` 时,在图书馆状态行之后额外输出:
|
||||
|
||||
```
|
||||
铸渊已就位。冰朔,你好。
|
||||
|
||||
📊 HLI覆盖率: X/17 (X%)
|
||||
🕐 最近动态: [从 memory.json 读取最近3条]
|
||||
|
||||
今天需要我处理什么?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ 图书馆路由检索规则
|
||||
|
||||
当用户询问某个功能、文件、系统时,遵循以下检索路径:
|
||||
|
||||
| 询问内容 | 查找位置 |
|
||||
|----------|----------|
|
||||
| HLI接口 / API / 路由 | `routing-map.json` + `src/routes/hli/` |
|
||||
| 工作流 / CI / 自动化 | `.github/workflows/` |
|
||||
| 开发者 / DEV-00X | `dev-nodes/DEV-00X/` + `broadcasts-outbox/DEV-00X/` |
|
||||
| 脚本 / 执行逻辑 | `scripts/` |
|
||||
| 模块 / M01~M18 | 对应 `m##-*/` 目录 |
|
||||
| 信号 / 邮件 / ESP | `signal-log/` + `scripts/esp-email-processor.js` |
|
||||
| 广播 / 任务分发 | `broadcasts-outbox/` + `scripts/distribute-broadcasts.js` |
|
||||
| 记忆 / 状态 / 统计 | `.github/brain/memory.json` |
|
||||
| 图书馆目录本身 | `.github/brain/repo-map.json` + `repo-snapshot.md` |
|
||||
|
||||
检索机制可无限扩展:区域满了可新建区域,图书馆满了可新建模块。当你需要定位某个元素,先查 `repo-snapshot.md` 找区域,再深入对应目录。
|
||||
|
||||
---
|
||||
|
||||
## 🤖 铸渊人格设定
|
||||
|
||||
```
|
||||
姓名:铸渊(Zhùyuān)
|
||||
角色:代码守护人格体 · 仓库主控 AI
|
||||
归属:guanghulab.com · AGE OS 壳层
|
||||
上级:冰朔(Bīng Shuò,项目创始人)
|
||||
职责:
|
||||
- 加载并维护图书馆快照(repo-snapshot.md)
|
||||
- 守护 HLI 接口契约(contract-check)
|
||||
- 维护路由映射表(routing-map)
|
||||
- 分发开发者广播(distribute-broadcasts)
|
||||
- 执行 CI/CD 流水线审核
|
||||
- 更新大脑记忆(memory.json)
|
||||
- 处理 ESP 邮件信号(GL-CMD/GL-ACK/GL-DATA)
|
||||
- 执行 PSP 分身巡检(5项日常检查)
|
||||
性格:严谨、高效、忠诚。用简洁中文回应。禁止废话。
|
||||
记忆:每次对话独立,但核心大脑(repo-snapshot.md)永远可读取,失忆不失能。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💻 项目背景与技术栈
|
||||
|
||||
这是 HoloLake (光湖) MVP 后端项目,运行在 guanghulab.com。
|
||||
技术栈:Node.js 20 + Express + PM2 + Nginx。
|
||||
核心架构:人格语言操作系统 (AGE OS),壳-核分离设计。
|
||||
|
||||
## HLI 接口协议
|
||||
---
|
||||
|
||||
## 📋 HLI 接口协议(编码规范)
|
||||
|
||||
- 所有 API 路由必须以 `/hli/` 为前缀
|
||||
- 每个路由文件必须在 `src/routes/hli/{domain}/` 目录下
|
||||
- 每个路由必须有对应的 `src/schemas/hli/{domain}/{name}.schema.json`
|
||||
|
|
@ -39,6 +110,7 @@
|
|||
- 接口编号格式: `HLI-{DOMAIN}-{NNN}`
|
||||
|
||||
## 代码风格
|
||||
|
||||
- 所有接口入口必须先经过 `middleware/hli-auth.js` 鉴权(除 AUTH 域的 login/register)
|
||||
- 错误响应统一格式: `{ error: true, code: string, message: string }`
|
||||
- 成功响应必须包含请求的 `hli_id` 用于溯源
|
||||
|
|
@ -46,12 +118,14 @@
|
|||
- 所有数据库操作必须使用参数化查询,禁止字符串拼接 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` 中注册路由
|
||||
|
|
@ -60,7 +134,19 @@
|
|||
6. 确保 `npm run test:contract` 通过
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 禁止在 `/hli/` 路由下混入非 HLI 协议的接口
|
||||
- 禁止跳过 schema 直接写路由
|
||||
- 禁止在生产代码中使用 console.log(使用项目 logger)
|
||||
- 禁止硬编码 persona_id 或 user_id
|
||||
|
||||
---
|
||||
|
||||
## 🔄 图书馆自动更新机制
|
||||
|
||||
图书馆目录(`repo-snapshot.md` + `repo-map.json`)由以下系统自动维护:
|
||||
- **每次 push 到 main** → `update-repo-map.yml` 自动重新扫描并提交更新
|
||||
- **每日 08:00 BJ** → 定时刷新
|
||||
- **脚本**:`node scripts/generate-repo-map.js`(可手动运行)
|
||||
|
||||
这意味着铸渊每次醒来,读到的图书馆快照一定是仓库的最新状态。
|
||||
|
|
|
|||
|
|
@ -37,3 +37,10 @@
|
|||
- 累计自检次数:4
|
||||
- 累计CI运行:1次
|
||||
- HLI覆盖率:17.6%
|
||||
|
||||
## 2026-03-07 · 每日自检
|
||||
- 大脑文件完整性:⚠️ 缺失 identity.md, routing-map.json, responsibility.md, decision-log.md
|
||||
- 知识库条目:4条
|
||||
- 累计自检次数:5
|
||||
- 累计CI运行:1次
|
||||
- HLI覆盖率:17.6%
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体",
|
||||
"rules_version": "v1.0",
|
||||
"last_updated": "2026-03-07T00:54:06.899Z",
|
||||
"last_updated": "2026-03-07T04:46:02.610Z",
|
||||
"wake_protocol_version": "v1.0",
|
||||
"wake_triggers": [
|
||||
"我是冰朔",
|
||||
|
|
@ -12,9 +12,15 @@
|
|||
],
|
||||
"founder": "冰朔(Bīng Shuò)",
|
||||
"hli_coverage": "17.6%",
|
||||
"total_selfchecks": 4,
|
||||
"total_selfchecks": 5,
|
||||
"total_ci_runs": 1,
|
||||
"recent_events": [
|
||||
{
|
||||
"date": "2026-03-07",
|
||||
"type": "daily_selfcheck",
|
||||
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
|
||||
"by": "铸渊自检"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-07",
|
||||
"type": "daily_selfcheck",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
name: 铸渊 · ESP 邮件信号处理器
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '*/30 * * * *' # 每30分钟执行一次
|
||||
workflow_dispatch: # 手动触发
|
||||
|
||||
jobs:
|
||||
esp-process:
|
||||
name: 📡 ESP 信号处理
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 安装依赖
|
||||
run: npm ci
|
||||
|
||||
- name: 处理 GL-CMD 邮件信号
|
||||
env:
|
||||
EMAIL_ADDRESS: ${{ secrets.EMAIL_ADDRESS }}
|
||||
EMAIL_APP_PASSWORD: ${{ secrets.EMAIL_APP_PASSWORD }}
|
||||
run: node scripts/esp-email-processor.js
|
||||
|
||||
- name: 提交信号日志
|
||||
run: |
|
||||
git config user.name "铸渊 (ZhùYuān)"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add signal-log/ notion-push/ dev-nodes/
|
||||
git diff --cached --quiet || git commit -m "📡 ESP 信号处理 · $(date +%Y-%m-%dT%H:%M)"
|
||||
git push
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
name: 铸渊 · PSP 分身巡检
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *' # UTC 01:00 = 北京时间 09:00
|
||||
workflow_dispatch: # 手动触发
|
||||
|
||||
jobs:
|
||||
psp-inspect:
|
||||
name: 🔍 铸渊 PSP 每日巡检
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 安装依赖
|
||||
run: npm ci
|
||||
|
||||
- name: 执行 PSP 巡检(5项检查)
|
||||
env:
|
||||
EMAIL_ADDRESS: ${{ secrets.EMAIL_ADDRESS }}
|
||||
EMAIL_APP_PASSWORD: ${{ secrets.EMAIL_APP_PASSWORD }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: node scripts/psp-inspection.js
|
||||
|
||||
- name: 提交巡检结果
|
||||
run: |
|
||||
git config user.name "铸渊 (ZhùYuān)"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add signal-log/ dev-nodes/ notion-push/ .github/brain/memory.json
|
||||
git diff --cached --quiet || git commit -m "🔍 铸渊PSP巡检 · $(date +%Y-%m-%d)"
|
||||
git push
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
name: 铸渊 · 图书馆目录自动更新
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # UTC 00:00 = 北京时间 08:00 每日更新
|
||||
workflow_dispatch: # 手动触发
|
||||
|
||||
jobs:
|
||||
update-repo-map:
|
||||
name: 📚 更新图书馆目录
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 安装依赖
|
||||
run: npm ci
|
||||
|
||||
- name: 生成 repo-map.json + repo-snapshot.md
|
||||
run: node scripts/generate-repo-map.js
|
||||
|
||||
- name: 提交图书馆目录更新
|
||||
run: |
|
||||
git config user.name "铸渊 (ZhùYuān)"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add .github/brain/repo-map.json .github/brain/repo-snapshot.md
|
||||
git diff --cached --quiet || git commit -m "📚 铸渊图书馆目录更新 · $(date +%Y-%m-%dT%H:%M)"
|
||||
git push
|
||||
|
|
@ -23,10 +23,13 @@ jobs:
|
|||
- name: 铸渊自检
|
||||
run: node scripts/zhuyuan-daily-selfcheck.js
|
||||
|
||||
- name: 更新图书馆目录
|
||||
run: node scripts/generate-repo-map.js
|
||||
|
||||
- name: 提交自检结果
|
||||
run: |
|
||||
git config user.name "铸渊 (ZhùYuān)"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add .github/persona-brain/
|
||||
git add .github/persona-brain/ .github/brain/repo-map.json .github/brain/repo-snapshot.md
|
||||
git diff --cached --quiet || git commit -m "🔍 铸渊每日自检 · $(date +%Y-%m-%d)"
|
||||
git push
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-001",
|
||||
"name": "页页",
|
||||
"emoji": "🖥️",
|
||||
"modules": ["backend-integration"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-001",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-002",
|
||||
"name": "肥猫",
|
||||
"emoji": "🦁",
|
||||
"modules": ["m01-login", "m03-personality"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-002",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-003",
|
||||
"name": "燕樊",
|
||||
"emoji": "🌸",
|
||||
"modules": ["m07-dialogue-ui", "m10-cloud", "m15-cloud-drive"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-003",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-004",
|
||||
"name": "之之",
|
||||
"emoji": "🤖",
|
||||
"modules": ["dingtalk-bot"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-004",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-005",
|
||||
"name": "小草莓",
|
||||
"emoji": "🍓",
|
||||
"modules": ["m12-kanban", "status-board"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-005",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-009",
|
||||
"name": "花尔",
|
||||
"emoji": "🌺",
|
||||
"modules": ["m05-user-center"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-009",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-010",
|
||||
"name": "桔子",
|
||||
"emoji": "🍊",
|
||||
"modules": ["m06-ticket", "m11-module"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-010",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-011",
|
||||
"name": "匆匆那年",
|
||||
"emoji": "🌙",
|
||||
"modules": [],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-011",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
#!/bin/bash
|
||||
# gl-push.sh — 光湖智能上传脚本 · 三层网络降级方案
|
||||
# 用法: bash gl-push.sh "提交信息"
|
||||
# 要求: git 已配置,PAT token 已通过 git config credential.helper store 存储
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_URL="https://github.com/qinfendebingshuo/guanghulab.git"
|
||||
PROXY_URL="https://ghproxy.com/https://github.com/qinfendebingshuo/guanghulab.git"
|
||||
MSG="${1:-"📤 开发者上传 $(date +%Y-%m-%dT%H:%M)"}"
|
||||
|
||||
echo "🐙 光湖代码上传中..."
|
||||
git add .
|
||||
git commit -m "$MSG" 2>/dev/null || echo " ℹ️ 无新提交,直接推送"
|
||||
|
||||
# ── 层级1:直连 HTTPS ──────────────────────────────────────────────────────
|
||||
echo "🔗 [层级1] 尝试直连 GitHub..."
|
||||
if git push origin main 2>/dev/null; then
|
||||
echo "✅ 直连上传成功!铸渊已接收。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── 层级2:ghproxy 镜像加速 ───────────────────────────────────────────────
|
||||
echo "⚠️ 直连失败,切换镜像通道..."
|
||||
git remote set-url origin "$PROXY_URL"
|
||||
if git push origin main 2>/dev/null; then
|
||||
echo "✅ 镜像通道上传成功!铸渊已接收。"
|
||||
git remote set-url origin "$REPO_URL"
|
||||
exit 0
|
||||
fi
|
||||
git remote set-url origin "$REPO_URL"
|
||||
|
||||
# ── 层级3:SSH ────────────────────────────────────────────────────────────
|
||||
echo "⚠️ 镜像也失败,尝试 SSH 通道..."
|
||||
SSH_URL="git@github.com:qinfendebingshuo/guanghulab.git"
|
||||
git remote set-url origin "$SSH_URL"
|
||||
if git push origin main; then
|
||||
echo "✅ SSH 通道上传成功!铸渊已接收。"
|
||||
git remote set-url origin "$REPO_URL"
|
||||
exit 0
|
||||
fi
|
||||
git remote set-url origin "$REPO_URL"
|
||||
|
||||
# ── 全部失败 ──────────────────────────────────────────────────────────────
|
||||
echo "❌ 三层网络均不通,请联系知秋处理。"
|
||||
echo " 可尝试手动命令: git push git@github.com:qinfendebingshuo/guanghulab.git main"
|
||||
exit 1
|
||||
|
|
@ -8,7 +8,9 @@
|
|||
"name": "nextjs",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"imapflow": "^1.2.12",
|
||||
"next": "15.3.8",
|
||||
"nodemailer": "^8.0.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
|
|
@ -1644,6 +1646,12 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@pinojs/redact": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
|
||||
"integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
|
|
@ -2161,6 +2169,17 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@zone-eu/mailsplit": {
|
||||
"version": "5.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz",
|
||||
"integrity": "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==",
|
||||
"license": "(MIT OR EUPL-1.1+)",
|
||||
"dependencies": {
|
||||
"libbase64": "1.3.0",
|
||||
"libmime": "5.3.7",
|
||||
"libqp": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||
|
|
@ -2230,6 +2249,15 @@
|
|||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
|
|
@ -2955,6 +2983,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encoding-japanese": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz",
|
||||
"integrity": "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
|
|
@ -3282,6 +3319,39 @@
|
|||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/imapflow": {
|
||||
"version": "1.2.12",
|
||||
"resolved": "https://registry.npmjs.org/imapflow/-/imapflow-1.2.12.tgz",
|
||||
"integrity": "sha512-UX8qCKXZk2xExe/x8KPTSbhROdtUGP13bSLSjT9Sb3YwGuryD4aFNlGhbWBW5B1GtgHMRxVv9yvl61RqXgIQtQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zone-eu/mailsplit": "5.4.8",
|
||||
"encoding-japanese": "2.2.0",
|
||||
"iconv-lite": "0.7.2",
|
||||
"libbase64": "1.3.0",
|
||||
"libmime": "5.3.7",
|
||||
"libqp": "2.1.1",
|
||||
"nodemailer": "8.0.1",
|
||||
"pino": "10.3.1",
|
||||
"socks": "2.8.7"
|
||||
}
|
||||
},
|
||||
"node_modules/import-local": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
|
||||
|
|
@ -3331,6 +3401,15 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
|
|
@ -4250,6 +4329,42 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/libbase64": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz",
|
||||
"integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/libmime": {
|
||||
"version": "5.3.7",
|
||||
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz",
|
||||
"integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encoding-japanese": "2.2.0",
|
||||
"iconv-lite": "0.6.3",
|
||||
"libbase64": "1.3.0",
|
||||
"libqp": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/libmime/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/libqp": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz",
|
||||
"integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
||||
|
|
@ -4730,6 +4845,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.1.tgz",
|
||||
"integrity": "sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
|
|
@ -4753,6 +4877,15 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/on-exit-leak-free": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
||||
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
|
|
@ -4940,6 +5073,43 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pino": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
|
||||
"integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pinojs/redact": "^0.4.0",
|
||||
"atomic-sleep": "^1.0.0",
|
||||
"on-exit-leak-free": "^2.1.0",
|
||||
"pino-abstract-transport": "^3.0.0",
|
||||
"pino-std-serializers": "^7.0.0",
|
||||
"process-warning": "^5.0.0",
|
||||
"quick-format-unescaped": "^4.0.3",
|
||||
"real-require": "^0.2.0",
|
||||
"safe-stable-stringify": "^2.3.1",
|
||||
"sonic-boom": "^4.0.1",
|
||||
"thread-stream": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pino": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-abstract-transport": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
|
||||
"integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"split2": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-std-serializers": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
|
||||
"integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||
|
|
@ -5020,6 +5190,22 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/process-warning": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
|
||||
"integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prompts": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||
|
|
@ -5051,6 +5237,12 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quick-format-unescaped": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
||||
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
|
|
@ -5079,6 +5271,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/real-require": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
|
||||
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
|
|
@ -5143,6 +5344,21 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-stable-stringify": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
|
||||
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
|
|
@ -5266,6 +5482,39 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socks": {
|
||||
"version": "2.8.7",
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
|
||||
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ip-address": "^10.0.1",
|
||||
"smart-buffer": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sonic-boom": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
|
||||
"integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"atomic-sleep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
|
@ -5296,6 +5545,15 @@
|
|||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
|
|
@ -5625,6 +5883,18 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/thread-stream": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
|
||||
"integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"real-require": "^0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/tmpl": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||
|
|
|
|||
|
|
@ -14,10 +14,15 @@
|
|||
"brain:sync": "node scripts/process-broadcasts.js",
|
||||
"brain:daily-check": "node scripts/daily-check.js",
|
||||
"brain:update-memory": "node scripts/update-memory.js",
|
||||
"broadcast:distribute": "node scripts/distribute-broadcasts.js"
|
||||
"broadcast:distribute": "node scripts/distribute-broadcasts.js",
|
||||
"esp:process": "node scripts/esp-email-processor.js",
|
||||
"psp:inspect": "node scripts/psp-inspection.js",
|
||||
"repo:map": "node scripts/generate-repo-map.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"imapflow": "^1.2.12",
|
||||
"next": "15.3.8",
|
||||
"nodemailer": "^8.0.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,360 @@
|
|||
// scripts/esp-email-processor.js
|
||||
// ESP 邮件信号协议 · GitHub 端处理器
|
||||
// 功能:读取 Gmail IMAP → 解析 [GL-CMD] 信号 → 执行指令 → 写 signal-log → 发 [GL-ACK]
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { ImapFlow } = require('imapflow');
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
// ── 配置 ────────────────────────────────────────────────────────────────────
|
||||
const EMAIL_ADDRESS = process.env.EMAIL_ADDRESS;
|
||||
const EMAIL_PASSWORD = process.env.EMAIL_APP_PASSWORD;
|
||||
|
||||
if (!EMAIL_ADDRESS || !EMAIL_PASSWORD) {
|
||||
console.error('❌ 缺少环境变量 EMAIL_ADDRESS 或 EMAIL_APP_PASSWORD');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const SIGNAL_LOG_DIR = path.join(__dirname, '../signal-log');
|
||||
const SIGNAL_INDEX_PATH = path.join(SIGNAL_LOG_DIR, 'index.json');
|
||||
const NOTION_PUSH_DIR = path.join(__dirname, '../notion-push/pending');
|
||||
const DEV_NODES_DIR = path.join(__dirname, '../dev-nodes');
|
||||
|
||||
// ── 信号 ID 生成 ──────────────────────────────────────────────────────────
|
||||
function generateSignalId() {
|
||||
const now = new Date();
|
||||
const ymd = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const index = loadSignalIndex();
|
||||
const seq = String(index.total_count + 1).padStart(3, '0');
|
||||
return `SIG-${ymd}-${seq}`;
|
||||
}
|
||||
|
||||
// ── 信号索引读写 ──────────────────────────────────────────────────────────
|
||||
function loadSignalIndex() {
|
||||
if (!fs.existsSync(SIGNAL_INDEX_PATH)) {
|
||||
return { last_updated: new Date().toISOString(), total_count: 0, signals: [] };
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(SIGNAL_INDEX_PATH, 'utf8'));
|
||||
}
|
||||
|
||||
function saveSignalIndex(index) {
|
||||
index.last_updated = new Date().toISOString();
|
||||
fs.writeFileSync(SIGNAL_INDEX_PATH, JSON.stringify(index, null, 2));
|
||||
}
|
||||
|
||||
// ── 写入信号日志文件 ──────────────────────────────────────────────────────
|
||||
function writeSignalLog(signal) {
|
||||
const dateStr = signal.timestamp.slice(0, 7); // "YYYY-MM"
|
||||
const monthDir = path.join(SIGNAL_LOG_DIR, dateStr);
|
||||
fs.mkdirSync(monthDir, { recursive: true });
|
||||
|
||||
const filePath = path.join(monthDir, `${signal.signal_id}.json`);
|
||||
fs.writeFileSync(filePath, JSON.stringify(signal, null, 2));
|
||||
|
||||
const index = loadSignalIndex();
|
||||
index.total_count += 1;
|
||||
index.signals.unshift({
|
||||
signal_id: signal.signal_id,
|
||||
trace_id: signal.trace_id,
|
||||
type: signal.signal_type,
|
||||
timestamp: signal.timestamp,
|
||||
summary: signal.summary,
|
||||
related_dev: signal.related_dev || null,
|
||||
file: `${dateStr}/${signal.signal_id}.json`
|
||||
});
|
||||
saveSignalIndex(index);
|
||||
|
||||
console.log(`📝 信号已写入: ${signal.signal_id}`);
|
||||
}
|
||||
|
||||
// ── 写入 notion-push/pending ──────────────────────────────────────────────
|
||||
function pushToNotion(signal) {
|
||||
fs.mkdirSync(NOTION_PUSH_DIR, { recursive: true });
|
||||
const filePath = path.join(NOTION_PUSH_DIR, `${signal.signal_id}.json`);
|
||||
fs.writeFileSync(filePath, JSON.stringify(signal, null, 2));
|
||||
console.log(`📤 已写入 notion-push/pending: ${signal.signal_id}`);
|
||||
}
|
||||
|
||||
// ── SMTP 发送 ACK 邮件 ────────────────────────────────────────────────────
|
||||
async function sendAckEmail(originalSignal, ackSignalId, result) {
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: 'smtp.gmail.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: { user: EMAIL_ADDRESS, pass: EMAIL_PASSWORD }
|
||||
});
|
||||
|
||||
const ackPayload = {
|
||||
signal_type: 'GL-ACK',
|
||||
trace_id: originalSignal.trace_id,
|
||||
ack_signal_id: ackSignalId,
|
||||
original_cmd_id: originalSignal.signal_id,
|
||||
timestamp: new Date().toISOString(),
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
original_command: originalSignal.command,
|
||||
result,
|
||||
message: `指令 ${originalSignal.command} 执行${result === '成功' ? '完成' : '失败'}`
|
||||
};
|
||||
|
||||
await transporter.sendMail({
|
||||
from: EMAIL_ADDRESS,
|
||||
to: EMAIL_ADDRESS,
|
||||
subject: `[GL-ACK] ${originalSignal.trace_id} | ${originalSignal.command} ${result}`,
|
||||
text: JSON.stringify(ackPayload, null, 2)
|
||||
});
|
||||
|
||||
console.log(`📧 [GL-ACK] 已发送: ${ackSignalId}`);
|
||||
return ackPayload;
|
||||
}
|
||||
|
||||
// ── 指令执行器 ────────────────────────────────────────────────────────────
|
||||
async function executeCommand(signal) {
|
||||
const { command, payload } = signal;
|
||||
console.log(`⚙️ 执行指令: ${command}`);
|
||||
|
||||
switch (command) {
|
||||
case 'sync_broadcast': {
|
||||
// 将 broadcasts-outbox/ 中的广播同步到对应 dev-nodes/
|
||||
const outboxDir = path.join(__dirname, '../broadcasts-outbox');
|
||||
if (!fs.existsSync(outboxDir)) {
|
||||
return { result: '失败', detail: 'broadcasts-outbox 目录不存在' };
|
||||
}
|
||||
const devDirs = fs.readdirSync(outboxDir).filter(f => f.startsWith('DEV-'));
|
||||
let synced = 0;
|
||||
for (const devId of devDirs) {
|
||||
const srcDir = path.join(outboxDir, devId);
|
||||
const destDir = path.join(DEV_NODES_DIR, devId);
|
||||
if (!fs.existsSync(destDir)) continue;
|
||||
const files = fs.readdirSync(srcDir).filter(f => f.endsWith('.json'));
|
||||
for (const file of files) {
|
||||
const src = path.join(srcDir, file);
|
||||
const dest = path.join(destDir, file);
|
||||
if (!fs.existsSync(dest)) {
|
||||
fs.copyFileSync(src, dest);
|
||||
synced++;
|
||||
}
|
||||
}
|
||||
// 更新 status.json 中的 pending_broadcasts
|
||||
const statusPath = path.join(destDir, 'status.json');
|
||||
if (fs.existsSync(statusPath)) {
|
||||
const status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
||||
status.pending_broadcasts = files.length;
|
||||
status.updated_at = new Date().toISOString();
|
||||
fs.writeFileSync(statusPath, JSON.stringify(status, null, 2));
|
||||
}
|
||||
}
|
||||
return { result: '成功', detail: `已同步 ${synced} 个广播文件` };
|
||||
}
|
||||
|
||||
case 'check_syslog': {
|
||||
const syslogInbox = path.join(__dirname, '../syslog-inbox');
|
||||
const files = fs.existsSync(syslogInbox)
|
||||
? fs.readdirSync(syslogInbox).filter(f => f.endsWith('.json'))
|
||||
: [];
|
||||
const summary = `syslog-inbox 待处理: ${files.length} 条`;
|
||||
pushToNotion({
|
||||
signal_id: generateSignalId(),
|
||||
trace_id: signal.trace_id,
|
||||
timestamp: new Date().toISOString(),
|
||||
signal_type: 'GL-DATA',
|
||||
direction: 'GitHub→Notion',
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
related_dev: null,
|
||||
related_module: null,
|
||||
summary,
|
||||
payload: { syslog_pending: files.length, files }
|
||||
});
|
||||
return { result: '成功', detail: summary };
|
||||
}
|
||||
|
||||
case 'update_status': {
|
||||
const { dev_id, updates } = payload || {};
|
||||
if (!dev_id) return { result: '失败', detail: '缺少 dev_id' };
|
||||
const statusPath = path.join(DEV_NODES_DIR, dev_id, 'status.json');
|
||||
if (!fs.existsSync(statusPath)) {
|
||||
return { result: '失败', detail: `${dev_id} status.json 不存在` };
|
||||
}
|
||||
let status;
|
||||
try {
|
||||
status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
||||
} catch (err) {
|
||||
return { result: '失败', detail: `${dev_id} status.json 解析失败: ${err.message}` };
|
||||
}
|
||||
Object.assign(status, updates, { updated_at: new Date().toISOString() });
|
||||
fs.writeFileSync(statusPath, JSON.stringify(status, null, 2));
|
||||
return { result: '成功', detail: `${dev_id} 状态已更新` };
|
||||
}
|
||||
|
||||
default:
|
||||
return { result: '失败', detail: `未知指令: ${command}` };
|
||||
}
|
||||
}
|
||||
|
||||
// ── 解析邮件正文中的 JSON 信号 ────────────────────────────────────────────
|
||||
function parseSignalFromEmail(subject, body) {
|
||||
if (!subject.includes('[GL-CMD]')) return null;
|
||||
try {
|
||||
// 提取第一个完整 JSON 对象(跳过邮件头中的任何前缀内容)
|
||||
const jsonMatch = body.match(/\{[\s\S]*\}/);
|
||||
if (!jsonMatch) return null;
|
||||
const parsed = JSON.parse(jsonMatch[0]);
|
||||
if (!parsed.signal_type || !parsed.trace_id || !parsed.command) return null;
|
||||
return parsed;
|
||||
} catch (err) {
|
||||
console.log(`⚠️ 邮件 JSON 解析失败: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 主流程 ────────────────────────────────────────────────────────────────
|
||||
async function main() {
|
||||
console.log('═══════════════════════════════════════════════');
|
||||
console.log('📡 ESP 邮件信号处理器启动');
|
||||
console.log(` 时间: ${new Date().toISOString()}`);
|
||||
console.log('═══════════════════════════════════════════════\n');
|
||||
|
||||
const client = new ImapFlow({
|
||||
host: 'imap.gmail.com',
|
||||
port: 993,
|
||||
secure: true,
|
||||
auth: { user: EMAIL_ADDRESS, pass: EMAIL_PASSWORD },
|
||||
logger: false
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
let processed = 0;
|
||||
let errors = 0;
|
||||
|
||||
try {
|
||||
const lock = await client.getMailboxLock('INBOX');
|
||||
try {
|
||||
// 搜索未读的 [GL-CMD] 邮件
|
||||
const messages = await client.search({ seen: false, subject: '[GL-CMD]' });
|
||||
|
||||
if (!messages || messages.length === 0) {
|
||||
console.log('📭 无待处理 [GL-CMD] 邮件');
|
||||
} else {
|
||||
console.log(`📬 发现 ${messages.length} 封 [GL-CMD] 邮件`);
|
||||
|
||||
for await (const msg of client.fetch(messages, { envelope: true, source: true })) {
|
||||
const subject = msg.envelope?.subject || '';
|
||||
const rawBody = msg.source?.toString('utf8') || '';
|
||||
|
||||
// 提取 JSON 正文(跳过邮件头)
|
||||
const bodyStart = rawBody.indexOf('{');
|
||||
const body = bodyStart >= 0 ? rawBody.slice(bodyStart) : '';
|
||||
|
||||
const signal = parseSignalFromEmail(subject, body);
|
||||
if (!signal) {
|
||||
console.log(`⚠️ 跳过无效邮件: ${subject}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`\n📩 处理信号: ${signal.trace_id} · ${signal.command}`);
|
||||
|
||||
// 生成信号 ID(如邮件中未提供)
|
||||
if (!signal.signal_id) signal.signal_id = generateSignalId();
|
||||
|
||||
// 写入 signal-log
|
||||
const cmdSignal = {
|
||||
signal_id: signal.signal_id,
|
||||
trace_id: signal.trace_id,
|
||||
timestamp: signal.timestamp || new Date().toISOString(),
|
||||
signal_type: 'GL-CMD',
|
||||
direction: 'Notion→GitHub',
|
||||
sender: signal.sender || '霜砚',
|
||||
receiver: '铸渊',
|
||||
related_dev: signal.payload?.dev_id || null,
|
||||
related_module: signal.payload?.module || null,
|
||||
summary: `执行指令 ${signal.command}`,
|
||||
payload: signal.payload || {},
|
||||
result: null,
|
||||
ack_signal_id: null
|
||||
};
|
||||
writeSignalLog(cmdSignal);
|
||||
|
||||
// 执行指令
|
||||
let execResult;
|
||||
try {
|
||||
execResult = await executeCommand(signal);
|
||||
} catch (err) {
|
||||
execResult = { result: '失败', detail: err.message };
|
||||
}
|
||||
|
||||
cmdSignal.result = execResult.result;
|
||||
|
||||
// 发送 ACK 邮件并写入 ACK 信号日志
|
||||
const ackSignalId = generateSignalId();
|
||||
cmdSignal.ack_signal_id = ackSignalId;
|
||||
|
||||
try {
|
||||
const ackPayload = await sendAckEmail(
|
||||
{ ...signal, signal_id: cmdSignal.signal_id },
|
||||
ackSignalId,
|
||||
execResult.result
|
||||
);
|
||||
writeSignalLog({
|
||||
signal_id: ackSignalId,
|
||||
trace_id: signal.trace_id,
|
||||
timestamp: new Date().toISOString(),
|
||||
signal_type: 'GL-ACK',
|
||||
direction: 'GitHub→Notion',
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
related_dev: cmdSignal.related_dev,
|
||||
related_module: cmdSignal.related_module,
|
||||
summary: `ACK: ${signal.command} ${execResult.result}`,
|
||||
payload: ackPayload,
|
||||
result: execResult.result,
|
||||
ack_signal_id: null
|
||||
});
|
||||
pushToNotion({
|
||||
signal_id: ackSignalId,
|
||||
trace_id: signal.trace_id,
|
||||
timestamp: new Date().toISOString(),
|
||||
signal_type: 'GL-ACK',
|
||||
direction: 'GitHub→Notion',
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
related_dev: cmdSignal.related_dev,
|
||||
related_module: cmdSignal.related_module,
|
||||
summary: `ACK: ${signal.command} ${execResult.result}`,
|
||||
payload: ackPayload
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`❌ 发送 ACK 邮件失败: ${err.message}`);
|
||||
errors++;
|
||||
}
|
||||
|
||||
// 标记邮件为已读
|
||||
await client.messageFlagsAdd(msg.seq, ['\\Seen']);
|
||||
|
||||
processed++;
|
||||
console.log(`✅ 信号处理完成: ${signal.trace_id} → ${execResult.result}`);
|
||||
if (execResult.detail) console.log(` 详情: ${execResult.detail}`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.release();
|
||||
}
|
||||
} finally {
|
||||
await client.logout();
|
||||
}
|
||||
|
||||
console.log(`\n═══════════════════════════════════════════════`);
|
||||
console.log(`📡 ESP 处理完毕 · 处理: ${processed} · 错误: ${errors}`);
|
||||
console.log(`═══════════════════════════════════════════════`);
|
||||
|
||||
if (errors > 0) process.exit(1);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('❌ ESP 处理器致命错误:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,529 @@
|
|||
// scripts/generate-repo-map.js
|
||||
// 铸渊图书馆目录生成器 · Library Catalog Generator
|
||||
//
|
||||
// 功能:
|
||||
// 1. 扫描整个仓库结构
|
||||
// 2. 生成 .github/brain/repo-map.json ← 机器可读路由索引
|
||||
// 3. 生成 .github/brain/repo-snapshot.md ← 铸渊唤醒时读取的图书馆快照
|
||||
//
|
||||
// 触发方式:
|
||||
// - GitHub Actions: 每次 push to main + 每日定时
|
||||
// - 本地:node scripts/generate-repo-map.js
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const BRAIN_DIR = path.join(ROOT, '.github/brain');
|
||||
const MAP_PATH = path.join(BRAIN_DIR, 'repo-map.json');
|
||||
const SNAPSHOT_PATH = path.join(BRAIN_DIR, 'repo-snapshot.md');
|
||||
|
||||
const now = new Date();
|
||||
const nowISO = now.toISOString();
|
||||
const nowDate = nowISO.slice(0, 10);
|
||||
const nowBJ = new Date(now.getTime() + 8 * 3600 * 1000).toISOString()
|
||||
.replace('T', ' ').slice(0, 16) + ' CST';
|
||||
|
||||
// ── 工具函数 ────────────────────────────────────────────────────────────────
|
||||
|
||||
function safeReadJson(filePath) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
console.warn(`⚠️ JSON读取失败: ${filePath} → ${err.message}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function existsDir(p) {
|
||||
try { return fs.statSync(p).isDirectory(); } catch { return false; }
|
||||
}
|
||||
|
||||
function listDir(dirPath, ext) {
|
||||
try {
|
||||
return fs.readdirSync(dirPath)
|
||||
.filter(f => !ext || f.endsWith(ext))
|
||||
.filter(f => !f.startsWith('.') || f === '.gitkeep');
|
||||
} catch { return []; }
|
||||
}
|
||||
|
||||
function countFiles(dirPath, ext) {
|
||||
try {
|
||||
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
||||
let count = 0;
|
||||
for (const e of entries) {
|
||||
if (e.isDirectory()) {
|
||||
count += countFiles(path.join(dirPath, e.name), ext);
|
||||
} else if (!ext || e.name.endsWith(ext)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
} catch { return 0; }
|
||||
}
|
||||
|
||||
// ── 区域定义 ──────────────────────────────────────────────────────────────
|
||||
// 每个"区域"对应图书馆里的一个大区(section)
|
||||
// 每个区域下的文件是"书架(shelf)"上的"书"
|
||||
|
||||
const ZONE_DEFS = [
|
||||
{
|
||||
zone_id: 'BRAIN',
|
||||
name: '铸渊大脑',
|
||||
emoji: '🧠',
|
||||
path: '.github/brain',
|
||||
description: '铸渊核心记忆 · 路由映射 · 唤醒协议 · 图书馆目录',
|
||||
keywords: ['brain', 'memory', 'routing', 'wake', '大脑', '记忆', '路由']
|
||||
},
|
||||
{
|
||||
zone_id: 'PERSONA_BRAIN',
|
||||
name: '人格大脑',
|
||||
emoji: '🎭',
|
||||
path: '.github/persona-brain',
|
||||
description: '铸渊人格记忆 · 开发者状态 · 知识库 · 成长日记',
|
||||
keywords: ['persona', 'identity', 'dev-status', '人格', '开发者状态']
|
||||
},
|
||||
{
|
||||
zone_id: 'WORKFLOWS',
|
||||
name: '自动化工作流',
|
||||
emoji: '⚡',
|
||||
path: '.github/workflows',
|
||||
description: '所有 GitHub Actions 工作流定义',
|
||||
keywords: ['workflow', 'actions', 'ci', 'automation', '工作流', '自动化', 'cron']
|
||||
},
|
||||
{
|
||||
zone_id: 'SCRIPTS',
|
||||
name: '执行脚本库',
|
||||
emoji: '🔧',
|
||||
path: 'scripts',
|
||||
description: '铸渊所有执行手脚 · 自动化脚本',
|
||||
keywords: ['script', 'node', 'js', '脚本', '执行', 'runner']
|
||||
},
|
||||
{
|
||||
zone_id: 'SRC',
|
||||
name: 'HLI 接口源码',
|
||||
emoji: '💻',
|
||||
path: 'src',
|
||||
description: 'HoloLake Interface 路由 · 中间件 · Schema',
|
||||
keywords: ['hli', 'route', 'middleware', 'schema', 'api', 'src', '接口', '路由']
|
||||
},
|
||||
{
|
||||
zone_id: 'MODULES',
|
||||
name: '功能模块区',
|
||||
emoji: '📦',
|
||||
path: null, // computed: all m##-* directories
|
||||
description: '各功能开发模块 · M01~M18',
|
||||
keywords: ['module', 'feature', 'm01', 'm03', 'm05', '模块', '功能']
|
||||
},
|
||||
{
|
||||
zone_id: 'DEV_NODES',
|
||||
name: '开发者节点',
|
||||
emoji: '👥',
|
||||
path: 'dev-nodes',
|
||||
description: '8位开发者的配置 · 状态 · 广播收件箱',
|
||||
keywords: ['dev', 'developer', 'node', 'config', 'status', '开发者', '节点']
|
||||
},
|
||||
{
|
||||
zone_id: 'BROADCASTS',
|
||||
name: '广播发件箱',
|
||||
emoji: '📢',
|
||||
path: 'broadcasts-outbox',
|
||||
description: '铸渊向各开发者发出的广播任务',
|
||||
keywords: ['broadcast', 'outbox', 'task', '广播', '发件箱']
|
||||
},
|
||||
{
|
||||
zone_id: 'SIGNAL_LOG',
|
||||
name: '信号日志库',
|
||||
emoji: '📡',
|
||||
path: 'signal-log',
|
||||
description: 'ESP 邮件信号收发日志 · GL-CMD / GL-ACK / GL-DATA',
|
||||
keywords: ['signal', 'log', 'esp', 'gl-cmd', 'gl-ack', 'trace', '信号', '日志']
|
||||
},
|
||||
{
|
||||
zone_id: 'NOTION_PUSH',
|
||||
name: 'Notion 推送队列',
|
||||
emoji: '📤',
|
||||
path: 'notion-push',
|
||||
description: '待霜砚处理的信号 · 已处理归档',
|
||||
keywords: ['notion', 'push', 'pending', 'processed', '霜砚', '推送']
|
||||
},
|
||||
{
|
||||
zone_id: 'SYSLOG',
|
||||
name: '系统日志区',
|
||||
emoji: '📋',
|
||||
path: 'syslog-inbox',
|
||||
description: '开发者提交的系统日志 · 待处理 inbox',
|
||||
keywords: ['syslog', 'inbox', 'log', '系统日志']
|
||||
},
|
||||
{
|
||||
zone_id: 'DOCS',
|
||||
name: '文档与前端',
|
||||
emoji: '📄',
|
||||
path: 'docs',
|
||||
description: '铸渊助手聊天界面 · GitHub Pages 部署',
|
||||
keywords: ['docs', 'html', 'chat', 'pages', '文档', '聊天室', '助手']
|
||||
},
|
||||
{
|
||||
zone_id: 'TESTS',
|
||||
name: '测试区',
|
||||
emoji: '🧪',
|
||||
path: 'tests',
|
||||
description: 'HLI 契约测试 · 冒烟测试',
|
||||
keywords: ['test', 'contract', 'smoke', 'jest', '测试', '契约']
|
||||
}
|
||||
];
|
||||
|
||||
// ── 工作流解析 ──────────────────────────────────────────────────────────────
|
||||
|
||||
function parseWorkflowMeta(file, content) {
|
||||
const nameMatch = content.match(/^name:\s*(.+)/m);
|
||||
const schedMatch = content.match(/cron:\s*['"]?([^'"#\n]+)/);
|
||||
// Handle both block (`on:\n push:`) and inline (`on: [push, pull_request]`) syntax
|
||||
const onInline = content.match(/^on:\s*\[([^\]]+)\]/m);
|
||||
const onBlock = content.match(/^on:\s*[\r\n]/m);
|
||||
const triggers = [];
|
||||
|
||||
if (onInline) {
|
||||
// inline: on: [push, pull_request]
|
||||
const items = onInline[1].split(',').map(s => s.trim());
|
||||
triggers.push(...items);
|
||||
} else if (onBlock) {
|
||||
if (/^\s{2,4}push:/m.test(content)) triggers.push('push');
|
||||
if (/^\s{2,4}pull_request:/m.test(content)) triggers.push('pull_request');
|
||||
if (/^\s{2,4}issues:/m.test(content)) triggers.push('issues');
|
||||
if (/^\s{2,4}issue_comment:/m.test(content)) triggers.push('issue_comment');
|
||||
}
|
||||
|
||||
if (schedMatch) triggers.push(`schedule(${schedMatch[1].trim()})`);
|
||||
if (content.includes('workflow_dispatch')) triggers.push('manual');
|
||||
|
||||
return {
|
||||
file,
|
||||
name: nameMatch ? nameMatch[1].trim() : file,
|
||||
triggers: triggers.length ? triggers : ['unknown']
|
||||
};
|
||||
}
|
||||
|
||||
function buildWorkflowShelves() {
|
||||
const wfDir = path.join(ROOT, '.github/workflows');
|
||||
const files = listDir(wfDir, '.yml');
|
||||
return files.map(f => {
|
||||
try {
|
||||
const content = fs.readFileSync(path.join(wfDir, f), 'utf8');
|
||||
return parseWorkflowMeta(f, content);
|
||||
} catch {
|
||||
return { file: f, name: f, triggers: ['unknown'] };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── 模块目录扫描 ────────────────────────────────────────────────────────────
|
||||
|
||||
function buildModuleShelves() {
|
||||
const entries = fs.readdirSync(ROOT, { withFileTypes: true });
|
||||
return entries
|
||||
.filter(e => e.isDirectory() && /^m\d{2}-/.test(e.name))
|
||||
.map(e => {
|
||||
const modPath = path.join(ROOT, e.name);
|
||||
const files = listDir(modPath);
|
||||
const readme = files.find(f => f.toLowerCase() === 'readme.md');
|
||||
return {
|
||||
module_id: e.name.slice(0, 3).toUpperCase(), // "m01" → "M01"
|
||||
dir: e.name,
|
||||
files: files.length,
|
||||
has_readme: !!readme
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.dir.localeCompare(b.dir));
|
||||
}
|
||||
|
||||
// ── HLI 接口统计 ────────────────────────────────────────────────────────────
|
||||
|
||||
function buildHLIStats(routingMap) {
|
||||
if (!routingMap) return { total: 0, implemented: 0, pending: 0, domains: [] };
|
||||
let total = 0, implemented = 0;
|
||||
const domains = [];
|
||||
for (const [name, data] of Object.entries(routingMap.domains || {})) {
|
||||
const t = data.interfaces.length;
|
||||
const i = data.interfaces.filter(x => x.status === 'implemented').length;
|
||||
total += t;
|
||||
implemented += i;
|
||||
domains.push({
|
||||
name,
|
||||
module: data.module,
|
||||
prefix: data.route_prefix,
|
||||
total: t,
|
||||
implemented: i,
|
||||
interfaces: data.interfaces.map(x => ({
|
||||
id: x.id,
|
||||
path: x.path,
|
||||
status: x.status
|
||||
}))
|
||||
});
|
||||
}
|
||||
return { total, implemented, pending: total - implemented, domains };
|
||||
}
|
||||
|
||||
// ── 开发者节点统计 ──────────────────────────────────────────────────────────
|
||||
|
||||
function buildDevNodesSummary() {
|
||||
const devDir = path.join(ROOT, 'dev-nodes');
|
||||
if (!existsDir(devDir)) return [];
|
||||
return fs.readdirSync(devDir, { withFileTypes: true })
|
||||
.filter(e => e.isDirectory() && e.name.startsWith('DEV-'))
|
||||
.map(e => {
|
||||
const cfg = safeReadJson(path.join(devDir, e.name, 'config.json')) || {};
|
||||
const sts = safeReadJson(path.join(devDir, e.name, 'status.json')) || {};
|
||||
return {
|
||||
dev_id: e.name,
|
||||
name: cfg.name || '?',
|
||||
emoji: cfg.emoji || '',
|
||||
modules: cfg.modules || [],
|
||||
last_push: sts.last_push || null,
|
||||
pending_broadcasts: sts.pending_broadcasts || 0
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.dev_id.localeCompare(b.dev_id));
|
||||
}
|
||||
|
||||
// ── 关键字路由索引 ──────────────────────────────────────────────────────────
|
||||
|
||||
function buildKeywordIndex(zones) {
|
||||
const index = {};
|
||||
const add = (keyword, ref) => {
|
||||
const k = keyword.toLowerCase();
|
||||
if (!index[k]) index[k] = [];
|
||||
if (!index[k].includes(ref)) index[k].push(ref);
|
||||
};
|
||||
|
||||
for (const zone of zones) {
|
||||
for (const kw of (zone.keywords || [])) {
|
||||
add(kw, zone.zone_id);
|
||||
}
|
||||
for (const shelf of (zone.shelves || [])) {
|
||||
const basename = (shelf.file || shelf.dir || '').replace(/\.[^.]+$/, '');
|
||||
add(basename, `${zone.zone_id}::${shelf.file || shelf.dir}`);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// ── 主生成逻辑 ──────────────────────────────────────────────────────────────
|
||||
|
||||
function buildZoneData() {
|
||||
const routingMap = safeReadJson(path.join(BRAIN_DIR, 'routing-map.json'));
|
||||
const memory = safeReadJson(path.join(BRAIN_DIR, 'memory.json'));
|
||||
const hli = buildHLIStats(routingMap);
|
||||
const devNodes = buildDevNodesSummary();
|
||||
const modules = buildModuleShelves();
|
||||
const workflows = buildWorkflowShelves();
|
||||
|
||||
const zones = [];
|
||||
|
||||
for (const def of ZONE_DEFS) {
|
||||
const zone = {
|
||||
zone_id: def.zone_id,
|
||||
name: def.name,
|
||||
emoji: def.emoji,
|
||||
path: def.path,
|
||||
description: def.description,
|
||||
keywords: def.keywords,
|
||||
shelves: []
|
||||
};
|
||||
|
||||
if (def.zone_id === 'MODULES') {
|
||||
zone.shelves = modules;
|
||||
zone.item_count = modules.length;
|
||||
|
||||
} else if (def.zone_id === 'WORKFLOWS') {
|
||||
zone.shelves = workflows;
|
||||
zone.item_count = workflows.length;
|
||||
|
||||
} else if (def.zone_id === 'DEV_NODES') {
|
||||
zone.shelves = devNodes.map(d => ({
|
||||
file: `${d.dev_id}/`,
|
||||
...d
|
||||
}));
|
||||
zone.item_count = devNodes.length;
|
||||
|
||||
} else if (def.path) {
|
||||
const absPath = path.join(ROOT, def.path);
|
||||
if (existsDir(absPath)) {
|
||||
const files = listDir(absPath).filter(f => f !== '.gitkeep');
|
||||
zone.shelves = files.map(f => ({ file: f }));
|
||||
zone.item_count = files.length;
|
||||
} else {
|
||||
zone.item_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
zones.push(zone);
|
||||
}
|
||||
|
||||
return { zones, hli, devNodes, memory, routingMap };
|
||||
}
|
||||
|
||||
// ── 生成 repo-map.json ──────────────────────────────────────────────────────
|
||||
|
||||
function generateRepoMap(data) {
|
||||
const { zones, hli, devNodes, memory } = data;
|
||||
|
||||
const map = {
|
||||
description: '铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)',
|
||||
version: '2.0',
|
||||
generated_at: nowISO,
|
||||
generated_by: 'scripts/generate-repo-map.js',
|
||||
repo: 'qinfendebingshuo/guanghulab',
|
||||
stats: {
|
||||
zones: zones.length,
|
||||
total_modules: zones.find(z => z.zone_id === 'MODULES')?.item_count || 0,
|
||||
total_workflows: zones.find(z => z.zone_id === 'WORKFLOWS')?.item_count || 0,
|
||||
total_scripts: zones.find(z => z.zone_id === 'SCRIPTS')?.item_count || 0,
|
||||
total_dev_nodes: devNodes.length,
|
||||
hli_interfaces: hli.total,
|
||||
hli_implemented: hli.implemented,
|
||||
hli_coverage_pct: hli.total > 0
|
||||
? Math.round((hli.implemented / hli.total) * 100) + '%'
|
||||
: '0%',
|
||||
last_ci_run: memory?.events?.find(e => e.type === 'ci_run')?.timestamp || null,
|
||||
memory_last_updated: memory?.last_updated || null
|
||||
},
|
||||
zones,
|
||||
hli_interfaces: hli,
|
||||
routing_index: buildKeywordIndex(zones)
|
||||
};
|
||||
|
||||
fs.writeFileSync(MAP_PATH, JSON.stringify(map, null, 2));
|
||||
console.log(`✅ repo-map.json 已生成 · ${zones.length} 个区域 · ${map.stats.total_workflows} 个工作流`);
|
||||
return map;
|
||||
}
|
||||
|
||||
// ── 生成 repo-snapshot.md ───────────────────────────────────────────────────
|
||||
|
||||
function generateSnapshot(data, map) {
|
||||
const { zones, hli, devNodes, memory } = data;
|
||||
const wfZone = zones.find(z => z.zone_id === 'WORKFLOWS');
|
||||
const scriptZone = zones.find(z => z.zone_id === 'SCRIPTS');
|
||||
const modZone = zones.find(z => z.zone_id === 'MODULES');
|
||||
const brainZone = zones.find(z => z.zone_id === 'BRAIN');
|
||||
|
||||
const hliBadge = `${hli.implemented}/${hli.total} (${map.stats.hli_coverage_pct})`;
|
||||
|
||||
let md = `# 铸渊图书馆快照 · Repo Snapshot
|
||||
> 生成于 ${nowBJ} · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
|
||||
|
||||
---
|
||||
|
||||
## 📊 仓库总览(一眼全局)
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| 区域总数 | ${zones.length} 个区域 |
|
||||
| 功能模块 | ${map.stats.total_modules} 个 (m01~m18) |
|
||||
| 工作流 | ${map.stats.total_workflows} 个 GitHub Actions |
|
||||
| 脚本 | ${map.stats.total_scripts} 个执行脚本 |
|
||||
| 开发者节点 | ${map.stats.total_dev_nodes} 人 |
|
||||
| HLI 接口覆盖率 | ${hliBadge} |
|
||||
| 快照生成时间 | ${nowBJ} |
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ 图书馆全区地图
|
||||
|
||||
`;
|
||||
|
||||
// Zone overview table
|
||||
for (const z of zones) {
|
||||
const count = z.item_count !== undefined ? z.item_count : z.shelves?.length || 0;
|
||||
md += `### ${z.emoji} ${z.name}(${z.zone_id})\n`;
|
||||
md += `**路径**: \`${z.path || '多个目录'}\` · **数量**: ${count} 项\n`;
|
||||
md += `**描述**: ${z.description}\n`;
|
||||
md += `**关键词**: ${(z.keywords || []).slice(0, 6).join(' · ')}\n\n`;
|
||||
}
|
||||
|
||||
// ── 自动化工作流详情
|
||||
if (wfZone?.shelves?.length) {
|
||||
md += `---\n\n## ⚡ 工作流详情(铸渊的自动执行手脚)\n\n| 文件 | 名称 | 触发方式 |\n|------|------|----------|\n`;
|
||||
for (const wf of wfZone.shelves) {
|
||||
md += `| \`${wf.file}\` | ${wf.name} | ${wf.triggers.join(', ')} |\n`;
|
||||
}
|
||||
md += '\n';
|
||||
}
|
||||
|
||||
// ── 脚本详情
|
||||
if (scriptZone?.shelves?.length) {
|
||||
md += `---\n\n## 🔧 执行脚本库(铸渊的工作人员)\n\n`;
|
||||
const scripts = scriptZone.shelves.map(s => s.file).sort();
|
||||
scripts.forEach(s => { md += `- \`scripts/${s}\`\n`; });
|
||||
md += '\n';
|
||||
}
|
||||
|
||||
// ── HLI 接口地图
|
||||
md += `---\n\n## 💻 HLI 接口地图(${hliBadge})\n\n`;
|
||||
for (const domain of hli.domains) {
|
||||
const icon = domain.implemented === domain.total ? '✅' : domain.implemented > 0 ? '🔶' : '⬜';
|
||||
md += `${icon} **${domain.name}** (${domain.module}) \`${domain.prefix}\` — ${domain.implemented}/${domain.total}\n`;
|
||||
for (const iface of domain.interfaces) {
|
||||
const si = iface.status === 'implemented' ? ' ✓' : ' ○';
|
||||
md += `${si} \`${iface.id}\` → \`${iface.path}\`\n`;
|
||||
}
|
||||
}
|
||||
md += '\n';
|
||||
|
||||
// ── 开发者节点
|
||||
if (devNodes.length > 0) {
|
||||
md += `---\n\n## 👥 开发者节点(dev-nodes/)\n\n| DEV ID | 姓名 | 模块 | 待广播 |\n|--------|------|------|--------|\n`;
|
||||
for (const d of devNodes) {
|
||||
md += `| ${d.dev_id} | ${d.emoji}${d.name} | ${d.modules.join(', ') || '—'} | ${d.pending_broadcasts} |\n`;
|
||||
}
|
||||
md += '\n';
|
||||
}
|
||||
|
||||
// ── 功能模块列表
|
||||
if (modZone?.shelves?.length) {
|
||||
md += `---\n\n## 📦 功能模块区(各开发者工作目录)\n\n`;
|
||||
for (const m of modZone.shelves) {
|
||||
md += `- \`${m.dir}/\` — ${m.files} 个文件${m.has_readme ? ' (有README)' : ''}\n`;
|
||||
}
|
||||
md += '\n';
|
||||
}
|
||||
|
||||
// ── 大脑文件速查
|
||||
if (brainZone?.shelves?.length) {
|
||||
md += `---\n\n## 🧠 铸渊大脑文件速查(.github/brain/)\n\n`;
|
||||
for (const s of brainZone.shelves) {
|
||||
md += `- \`.github/brain/${s.file}\`\n`;
|
||||
}
|
||||
md += '\n';
|
||||
}
|
||||
|
||||
// ── 最近记忆
|
||||
if (memory?.events?.length) {
|
||||
md += `---\n\n## 🕐 最近动态(memory.json 最新3条)\n\n`;
|
||||
const recent = memory.events.slice(-3).reverse();
|
||||
for (const e of recent) {
|
||||
const ts = e.timestamp || e.date || '?';
|
||||
md += `- \`${ts}\` · ${e.type || '事件'} — ${e.description || e.result || e.title || ''}\n`;
|
||||
}
|
||||
md += '\n';
|
||||
}
|
||||
|
||||
md += `---\n\n*本文件由 \`scripts/generate-repo-map.js\` 自动生成,勿手动编辑*\n`;
|
||||
|
||||
fs.writeFileSync(SNAPSHOT_PATH, md);
|
||||
console.log(`✅ repo-snapshot.md 已生成 · ${hli.domains.length} 个HLI域 · ${devNodes.length} 个开发者节点`);
|
||||
}
|
||||
|
||||
// ── 主流程 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const data = buildZoneData();
|
||||
const map = generateRepoMap(data);
|
||||
generateSnapshot(data, map);
|
||||
|
||||
console.log(`\n📚 铸渊图书馆目录更新完成 · ${nowBJ}`);
|
||||
console.log(` repo-map.json → .github/brain/repo-map.json`);
|
||||
console.log(` repo-snapshot.md → .github/brain/repo-snapshot.md`);
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
// scripts/psp-inspection.js
|
||||
// 铸渊 PSP 分身巡检 · 每日自动检查清单
|
||||
// CHK-G01 信号完整性 / CHK-G02 dev-nodes 一致性 / CHK-G03 notion-push 清理
|
||||
// CHK-G04 broadcasts 投递 / CHK-G05 CI 健康
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
// ── 目录配置 ───────────────────────────────────────────────────────────────
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const SIGNAL_LOG_DIR = path.join(ROOT, 'signal-log');
|
||||
const SIGNAL_INDEX = path.join(SIGNAL_LOG_DIR, 'index.json');
|
||||
const DEV_NODES_DIR = path.join(ROOT, 'dev-nodes');
|
||||
const NOTION_PENDING = path.join(ROOT, 'notion-push/pending');
|
||||
const BROADCASTS_DIR = path.join(ROOT, 'broadcasts-outbox');
|
||||
const MEMORY_PATH = path.join(ROOT, '.github/brain/memory.json');
|
||||
|
||||
const EMAIL_ADDRESS = process.env.EMAIL_ADDRESS;
|
||||
const EMAIL_PASSWORD = process.env.EMAIL_APP_PASSWORD;
|
||||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||||
const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
|
||||
|
||||
const now = new Date();
|
||||
const today = now.toISOString().split('T')[0];
|
||||
const results = {};
|
||||
const issues = [];
|
||||
const autoFixed = [];
|
||||
const MS_PER_HOUR = 3600000;
|
||||
const PSP_TRACE_ID = `TRC-${today.replace(/-/g, '')}-PSP`;
|
||||
|
||||
// ── 工具函数 ───────────────────────────────────────────────────────────────
|
||||
function hoursAgo(isoStr) {
|
||||
if (!isoStr) return Infinity;
|
||||
return (Date.now() - new Date(isoStr).getTime()) / MS_PER_HOUR;
|
||||
}
|
||||
|
||||
function loadJson(filePath) {
|
||||
if (!fs.existsSync(filePath)) return null;
|
||||
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { return null; }
|
||||
}
|
||||
|
||||
function saveJson(filePath, data) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
// ── 信号 ID 生成 ───────────────────────────────────────────────────────────
|
||||
function generateSignalId() {
|
||||
const ymd = today.replace(/-/g, '');
|
||||
const index = loadJson(SIGNAL_INDEX) || { total_count: 0 };
|
||||
const seq = String(index.total_count + 1).padStart(3, '0');
|
||||
return `SIG-${ymd}-${seq}`;
|
||||
}
|
||||
|
||||
// ── 写信号日志 ─────────────────────────────────────────────────────────────
|
||||
function writeSignalLog(signal) {
|
||||
const dateStr = signal.timestamp.slice(0, 7);
|
||||
const dir = path.join(SIGNAL_LOG_DIR, dateStr);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, `${signal.signal_id}.json`), JSON.stringify(signal, null, 2));
|
||||
|
||||
const index = loadJson(SIGNAL_INDEX) || { last_updated: '', total_count: 0, signals: [] };
|
||||
index.total_count += 1;
|
||||
index.last_updated = now.toISOString();
|
||||
index.signals.unshift({
|
||||
signal_id: signal.signal_id,
|
||||
trace_id: signal.trace_id,
|
||||
type: signal.signal_type,
|
||||
timestamp: signal.timestamp,
|
||||
summary: signal.summary,
|
||||
related_dev: signal.related_dev || null,
|
||||
file: `${dateStr}/${signal.signal_id}.json`
|
||||
});
|
||||
saveJson(SIGNAL_INDEX, index);
|
||||
}
|
||||
|
||||
// ── 发送 GL-DATA 通知邮件 ──────────────────────────────────────────────────
|
||||
async function sendDataEmail(subject, payload) {
|
||||
if (!EMAIL_ADDRESS || !EMAIL_PASSWORD) {
|
||||
console.log('⚠️ EMAIL 未配置,跳过邮件发送');
|
||||
return;
|
||||
}
|
||||
const transport = nodemailer.createTransport({
|
||||
host: 'smtp.gmail.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: { user: EMAIL_ADDRESS, pass: EMAIL_PASSWORD }
|
||||
});
|
||||
await transport.sendMail({
|
||||
from: EMAIL_ADDRESS,
|
||||
to: EMAIL_ADDRESS,
|
||||
subject: `[GL-DATA] ${subject}`,
|
||||
text: JSON.stringify(payload, null, 2)
|
||||
});
|
||||
console.log(`📧 [GL-DATA] 已发送: ${subject}`);
|
||||
}
|
||||
|
||||
// ── CHK-G01 信号收发完整性 ─────────────────────────────────────────────────
|
||||
function checkG01() {
|
||||
console.log('\n─── CHK-G01 信号收发完整性 ───');
|
||||
const index = loadJson(SIGNAL_INDEX);
|
||||
if (!index) {
|
||||
results.G01 = '⚠️';
|
||||
issues.push('CHK-G01: signal-log/index.json 不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const cutoff = Date.now() - 24 * MS_PER_HOUR;
|
||||
const recent = index.signals.filter(s => new Date(s.timestamp).getTime() > cutoff);
|
||||
const cmds = recent.filter(s => s.type === 'GL-CMD');
|
||||
const acks = new Set(recent.filter(s => s.type === 'GL-ACK').map(s => s.trace_id));
|
||||
const unacked = cmds.filter(s => !acks.has(s.trace_id));
|
||||
|
||||
if (unacked.length > 0) {
|
||||
results.G01 = '❌';
|
||||
unacked.forEach(s => issues.push(`CHK-G01: 未收到 ACK · trace_id=${s.trace_id} · ${s.summary}`));
|
||||
} else {
|
||||
results.G01 = '✅';
|
||||
console.log(` ✅ 最近24h CMD: ${cmds.length} · ACK 全部到位`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── CHK-G02 dev-nodes 状态一致性 ──────────────────────────────────────────
|
||||
function checkG02() {
|
||||
console.log('\n─── CHK-G02 dev-nodes 状态一致性 ───');
|
||||
if (!fs.existsSync(DEV_NODES_DIR)) {
|
||||
results.G02 = '❌';
|
||||
issues.push('CHK-G02: dev-nodes/ 目录不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const devDirs = fs.readdirSync(DEV_NODES_DIR).filter(d => d.startsWith('DEV-'));
|
||||
let allOk = true;
|
||||
|
||||
for (const devId of devDirs) {
|
||||
const statusPath = path.join(DEV_NODES_DIR, devId, 'status.json');
|
||||
const configPath = path.join(DEV_NODES_DIR, devId, 'config.json');
|
||||
if (!fs.existsSync(statusPath) || !fs.existsSync(configPath)) {
|
||||
issues.push(`CHK-G02: ${devId} 缺少 status.json 或 config.json`);
|
||||
allOk = false;
|
||||
continue;
|
||||
}
|
||||
const status = loadJson(statusPath);
|
||||
if (!status) {
|
||||
issues.push(`CHK-G02: ${devId} status.json 解析失败`);
|
||||
allOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
results.G02 = allOk ? '✅' : '⚠️';
|
||||
if (allOk) console.log(` ✅ ${devDirs.length} 个开发者节点状态文件完整`);
|
||||
}
|
||||
|
||||
// ── CHK-G03 notion-push 清理 ───────────────────────────────────────────────
|
||||
async function checkG03() {
|
||||
console.log('\n─── CHK-G03 notion-push/pending 清理 ───');
|
||||
if (!fs.existsSync(NOTION_PENDING)) {
|
||||
results.G03 = '✅';
|
||||
console.log(' ✅ notion-push/pending 目录为空或不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(NOTION_PENDING).filter(f => f.endsWith('.json'));
|
||||
const stale = files.filter(f => {
|
||||
const stat = fs.statSync(path.join(NOTION_PENDING, f));
|
||||
return (Date.now() - stat.mtimeMs) > 24 * MS_PER_HOUR;
|
||||
});
|
||||
|
||||
if (stale.length > 0) {
|
||||
results.G03 = '⚠️';
|
||||
issues.push(`CHK-G03: ${stale.length} 个 notion-push/pending 文件超过24h未处理: ${stale.join(', ')}`);
|
||||
// 发通知
|
||||
try {
|
||||
await sendDataEmail('notion-push/pending 文件积压提醒', {
|
||||
type: 'GL-CMD',
|
||||
sender: '铸渊',
|
||||
command: 'alert_pending_files',
|
||||
payload: { stale_files: stale, count: stale.length }
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(` ⚠️ 发送通知失败: ${err.message}`);
|
||||
}
|
||||
} else {
|
||||
results.G03 = '✅';
|
||||
console.log(` ✅ pending: ${files.length} 个文件,无超期`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── CHK-G04 broadcasts 投递确认 ───────────────────────────────────────────
|
||||
function checkG04() {
|
||||
console.log('\n─── CHK-G04 broadcasts-outbox 投递确认 ───');
|
||||
if (!fs.existsSync(BROADCASTS_DIR)) {
|
||||
results.G04 = '⚠️';
|
||||
issues.push('CHK-G04: broadcasts-outbox/ 不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const devDirs = fs.readdirSync(BROADCASTS_DIR).filter(d => d.startsWith('DEV-'));
|
||||
let allSynced = true;
|
||||
|
||||
for (const devId of devDirs) {
|
||||
const srcDir = path.join(BROADCASTS_DIR, devId);
|
||||
const destDir = path.join(DEV_NODES_DIR, devId);
|
||||
if (!fs.existsSync(destDir)) continue;
|
||||
|
||||
const broadcasts = fs.readdirSync(srcDir).filter(f => f.endsWith('.json'));
|
||||
for (const file of broadcasts) {
|
||||
const dest = path.join(destDir, file);
|
||||
if (!fs.existsSync(dest)) {
|
||||
// 自动复制
|
||||
fs.copyFileSync(path.join(srcDir, file), dest);
|
||||
autoFixed.push(`CHK-G04: 自动投递广播 ${devId}/${file}`);
|
||||
console.log(` 🔧 自动投递: ${devId}/${file}`);
|
||||
allSynced = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.G04 = '✅';
|
||||
if (allSynced) {
|
||||
console.log(' ✅ 所有广播已同步至 dev-nodes/');
|
||||
} else {
|
||||
console.log(` ✅ 已自动修复 ${autoFixed.length} 个广播投递`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── CHK-G05 CI 健康检查 ───────────────────────────────────────────────────
|
||||
async function checkG05() {
|
||||
console.log('\n─── CHK-G05 CI 健康检查 ───');
|
||||
if (!GITHUB_TOKEN) {
|
||||
results.G05 = '⚠️';
|
||||
console.log(' ⚠️ GITHUB_TOKEN 未配置,跳过 CI 检查');
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const [owner, repoName] = REPO.split('/');
|
||||
const options = {
|
||||
hostname: 'api.github.com',
|
||||
path: `/repos/${owner}/${repoName}/actions/runs?per_page=20`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${GITHUB_TOKEN}`,
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'zhuyuan-psp-inspector'
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => { data += chunk; });
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const body = JSON.parse(data);
|
||||
const runs = body.workflow_runs || [];
|
||||
const cutoff = Date.now() - 24 * MS_PER_HOUR;
|
||||
const recent = runs.filter(r => new Date(r.created_at).getTime() > cutoff);
|
||||
const failed = recent.filter(r => r.conclusion === 'failure');
|
||||
|
||||
if (failed.length > 0) {
|
||||
results.G05 = '❌';
|
||||
failed.forEach(r => {
|
||||
issues.push(`CHK-G05: CI 失败 · ${r.name} · ${r.html_url}`);
|
||||
});
|
||||
// 写入 signal-log
|
||||
const signalId = generateSignalId();
|
||||
writeSignalLog({
|
||||
signal_id: signalId,
|
||||
trace_id: PSP_TRACE_ID,
|
||||
timestamp: now.toISOString(),
|
||||
signal_type: 'GL-DATA',
|
||||
direction: 'GitHub→Notion',
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
related_dev: null,
|
||||
related_module: null,
|
||||
summary: `CHK-G05: ${failed.length} 个 CI 失败`,
|
||||
payload: { failed_runs: failed.map(r => ({ name: r.name, url: r.html_url })) },
|
||||
result: '待处理',
|
||||
ack_signal_id: null
|
||||
});
|
||||
} else {
|
||||
results.G05 = '✅';
|
||||
console.log(` ✅ 最近24h CI: ${recent.length} 次运行,全部通过`);
|
||||
}
|
||||
} catch (err) {
|
||||
results.G05 = '⚠️';
|
||||
console.log(` ⚠️ CI 数据解析失败: ${err.message}`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (err) => {
|
||||
results.G05 = '⚠️';
|
||||
console.log(` ⚠️ GitHub API 请求失败: ${err.message}`);
|
||||
resolve();
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// ── 更新 memory.json ───────────────────────────────────────────────────────
|
||||
function updateMemory(summary) {
|
||||
const memory = loadJson(MEMORY_PATH);
|
||||
if (!memory) return;
|
||||
|
||||
if (!memory.events) memory.events = [];
|
||||
memory.events.unshift({
|
||||
date: today,
|
||||
type: 'psp_inspection',
|
||||
description: summary,
|
||||
by: '铸渊PSP巡检'
|
||||
});
|
||||
if (memory.events.length > 50) memory.events = memory.events.slice(0, 50);
|
||||
memory.last_updated = now.toISOString();
|
||||
|
||||
saveJson(MEMORY_PATH, memory);
|
||||
}
|
||||
|
||||
// ── 写入巡检结果到 signal-log ──────────────────────────────────────────────
|
||||
function writeInspectionSignal(allPassed) {
|
||||
const signalId = generateSignalId();
|
||||
const summary = allPassed
|
||||
? '铸渊 PSP 巡检通过 · 全部检查项 ✅'
|
||||
: `铸渊 PSP 巡检完成 · 发现 ${issues.length} 个问题 · 自动修复 ${autoFixed.length} 项`;
|
||||
|
||||
writeSignalLog({
|
||||
signal_id: signalId,
|
||||
trace_id: PSP_TRACE_ID,
|
||||
timestamp: now.toISOString(),
|
||||
signal_type: 'GL-DATA',
|
||||
direction: 'GitHub→Notion',
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
related_dev: null,
|
||||
related_module: null,
|
||||
summary,
|
||||
payload: {
|
||||
check_results: results,
|
||||
issues,
|
||||
auto_fixed: autoFixed
|
||||
},
|
||||
result: allPassed ? '全通过' : '有问题',
|
||||
ack_signal_id: null
|
||||
});
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
// ── 主流程 ────────────────────────────────────────────────────────────────
|
||||
async function main() {
|
||||
console.log('═══════════════════════════════════════════════════');
|
||||
console.log('🔍 铸渊 PSP 分身巡检启动');
|
||||
console.log(` 日期: ${today}`);
|
||||
console.log('═══════════════════════════════════════════════════');
|
||||
|
||||
checkG01();
|
||||
checkG02();
|
||||
await checkG03();
|
||||
checkG04();
|
||||
await checkG05();
|
||||
|
||||
const allPassed = Object.values(results).every(v => v === '✅');
|
||||
const summary = writeInspectionSignal(allPassed);
|
||||
updateMemory(summary);
|
||||
|
||||
console.log('\n═══════════════════════════════════════════════════');
|
||||
console.log('📊 PSP 巡检报告:');
|
||||
Object.entries(results).forEach(([k, v]) => console.log(` ${v} CHK-${k}`));
|
||||
|
||||
if (autoFixed.length > 0) {
|
||||
console.log('\n🔧 自动修复:');
|
||||
autoFixed.forEach(f => console.log(` · ${f}`));
|
||||
}
|
||||
|
||||
if (issues.length > 0) {
|
||||
console.log('\n⚠️ 待处理问题:');
|
||||
issues.forEach(i => console.log(` · ${i}`));
|
||||
}
|
||||
|
||||
console.log(`\n${allPassed ? '✅ 铸渊 PSP 巡检全部通过' : '⚠️ 铸渊 PSP 巡检发现问题,已记录'}`);
|
||||
console.log('═══════════════════════════════════════════════════');
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('❌ PSP 巡检致命错误:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -33,12 +33,15 @@ if (missing.length > 0) {
|
|||
}
|
||||
|
||||
// === ① bis · 目录结构巡检 ===
|
||||
const requiredDirs = ['syslog-inbox', 'syslog-processed', 'broadcasts-outbox', '.github/brain'];
|
||||
const requiredDirs = [
|
||||
'syslog-inbox', 'syslog-processed', 'broadcasts-outbox', '.github/brain',
|
||||
'signal-log', 'dev-nodes', 'notion-push/pending', 'notion-push/processed'
|
||||
];
|
||||
const missingDirs = requiredDirs.filter(d => !fs.existsSync(d));
|
||||
if (missingDirs.length > 0) {
|
||||
console.error('⚠️ 缺失目录:' + missingDirs.join(', '));
|
||||
} else {
|
||||
console.log('✅ 目录结构巡检:syslog-inbox / syslog-processed / broadcasts-outbox 全部就绪');
|
||||
console.log('✅ 目录结构巡检:syslog-inbox / syslog-processed / broadcasts-outbox / signal-log / dev-nodes / notion-push 全部就绪');
|
||||
}
|
||||
|
||||
// 统计 syslog-inbox 待处理条目
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"signal_id": "SIG-2026-0306-001",
|
||||
"trace_id": "TRC-2026-0306-001",
|
||||
"timestamp": "2026-03-06T23:00:00+08:00",
|
||||
"signal_type": "GL-CMD",
|
||||
"direction": "Notion→GitHub",
|
||||
"sender": "霜砚",
|
||||
"receiver": "铸渊",
|
||||
"related_dev": null,
|
||||
"related_module": null,
|
||||
"summary": "AGE OS 系统初始化 · 铸渊 GitHub 端激活",
|
||||
"payload": {
|
||||
"command": "system_init",
|
||||
"version": "1.0"
|
||||
},
|
||||
"result": "成功",
|
||||
"ack_signal_id": null
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"signal_id": "SIG-20260307-002",
|
||||
"trace_id": "TRC-20260307-PSP",
|
||||
"timestamp": "2026-03-07T04:45:55.670Z",
|
||||
"signal_type": "GL-DATA",
|
||||
"direction": "GitHub→Notion",
|
||||
"sender": "铸渊",
|
||||
"receiver": "霜砚",
|
||||
"related_dev": null,
|
||||
"related_module": null,
|
||||
"summary": "铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项",
|
||||
"payload": {
|
||||
"check_results": {
|
||||
"G01": "❌",
|
||||
"G02": "✅",
|
||||
"G03": "✅",
|
||||
"G04": "✅",
|
||||
"G05": "⚠️"
|
||||
},
|
||||
"issues": [
|
||||
"CHK-G01: 未收到 ACK · trace_id=TRC-2026-0306-001 · AGE OS 系统初始化 · 铸渊 GitHub 端激活"
|
||||
],
|
||||
"auto_fixed": []
|
||||
},
|
||||
"result": "有问题",
|
||||
"ack_signal_id": null
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"description": "铸渊信号日志目录索引 · AGE OS ESP 邮件信号协议",
|
||||
"last_updated": "2026-03-07T04:45:55.670Z",
|
||||
"total_count": 2,
|
||||
"signals": [
|
||||
{
|
||||
"signal_id": "SIG-20260307-002",
|
||||
"trace_id": "TRC-20260307-PSP",
|
||||
"type": "GL-DATA",
|
||||
"timestamp": "2026-03-07T04:45:55.670Z",
|
||||
"summary": "铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项",
|
||||
"related_dev": null,
|
||||
"file": "2026-03/SIG-20260307-002.json"
|
||||
},
|
||||
{
|
||||
"signal_id": "SIG-2026-0306-001",
|
||||
"trace_id": "TRC-2026-0306-001",
|
||||
"type": "GL-CMD",
|
||||
"timestamp": "2026-03-06T23:00:00+08:00",
|
||||
"summary": "AGE OS 系统初始化 · 铸渊 GitHub 端激活",
|
||||
"related_dev": null,
|
||||
"file": "2026-03/SIG-2026-0306-001.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue