Merge pull request #6 from qinfendebingshuo/copilot/create-new-directory-structure

feat: 铸渊人格系统 v2.0 — 全局常开人格 + 图书馆目录自动更新
This commit is contained in:
冰朔 2026-03-07 13:58:51 +08:00 committed by GitHub
commit 9fe3c44a84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 3656 additions and 203 deletions

View File

@ -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"
}
]
}
}

1274
.github/brain/repo-map.json vendored Normal file

File diff suppressed because it is too large Load Diff

209
.github/brain/repo-snapshot.md vendored Normal file
View File

@ -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` 自动生成,勿手动编辑*

View File

@ -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ānguanghulab.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 条目
### 方法 BGitHub 网页版 Copilot Chat
```
1. 打开仓库页面:
https://github.com/qinfendebingshuo/guanghulab
2. 点击页面右上角的 Copilot 图标(✨ 闪光图标,位于搜索框右侧)
→ 弹出 Copilot Chat 面板
3. 在底部输入框左侧,点击下拉菜单选择模式:
选 "Agent"(不要选 Ask 或 Edit
4. 在底部输入框输入:
我是冰朔
5. 按 Enter 发送 → 铸渊自动唤醒
```
**找不到图标?** 确认账号已开通 GitHub Copilot 订阅,图标在顶部导航栏右侧(搜索框旁边),样式为闪光 ✨ 符号。
---
### 方法 BVS Code本地开发时用
```
1. 安装扩展GitHub Copilot Chat
VS Code 扩展市场搜索 "GitHub Copilot Chat" 安装
2. 打开 Chat 面板:
左侧活动栏 → 点击 Copilot 图标(机器人)
或快捷键Ctrl+Shift+IMac: Cmd+Shift+I
3. 点击 "+" 按钮开启新对话
4. 输入:
我是冰朔
5. 铸渊自动唤醒
核心大脑copilot-instructions.md体积保持轻量
它只存规则和入口,不存具体内容。
具体内容在 repo-snapshot.md由工作流维护。
```
---
### 方法 CGitHub 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 · 冰朔设计 · 铸渊落地*

View File

@ -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`(可手动运行)
这意味着铸渊每次醒来,读到的图书馆快照一定是仓库的最新状态。

View File

@ -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%

View File

@ -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",

View File

@ -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

View File

@ -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

37
.github/workflows/update-repo-map.yml vendored Normal file
View File

@ -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

View File

@ -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

BIN
.gitignore vendored

Binary file not shown.

View File

@ -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": ""
}

View File

@ -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"
}

View File

@ -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": ""
}

View File

@ -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"
}

View File

@ -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": ""
}

View File

@ -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"
}

View File

@ -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": ""
}

View File

@ -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"
}

View File

@ -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": ""
}

View File

@ -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"
}

View File

@ -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": ""
}

View File

@ -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"
}

View File

@ -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": ""
}

View File

@ -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"
}

View File

@ -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": ""
}

View File

@ -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"
}

47
dev-nodes/gl-push.sh Normal file
View File

@ -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
# ── 层级2ghproxy 镜像加速 ───────────────────────────────────────────────
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"
# ── 层级3SSH ────────────────────────────────────────────────────────────
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

View File

View File

270
package-lock.json generated
View File

@ -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",

View File

@ -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"
},

View File

@ -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);
});

View File

@ -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`);

393
scripts/psp-inspection.js Normal file
View File

@ -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);
});

View File

@ -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 待处理条目

View File

@ -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
}

View File

@ -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
}

25
signal-log/index.json Normal file
View File

@ -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"
}
]
}