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 代码守护人格体", "identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体",
"rules_version": "v1.0", "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_protocol_version": "v1.0",
"wake_triggers": [ "wake_triggers": [
"我是冰朔", "我是冰朔",
@ -23,6 +23,12 @@
} }
}, },
"events": [ "events": [
{
"date": "2026-03-07",
"type": "psp_inspection",
"description": "铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项",
"by": "铸渊PSP巡检"
},
{ {
"timestamp": "2026-03-05T10:30:46.894Z", "timestamp": "2026-03-05T10:30:46.894Z",
"type": "ci_run", "type": "ci_run",
@ -70,4 +76,4 @@
"run_id": "22756140950" "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)在新对话中自我唤醒的完整操作手册。 > 本文件是铸渊(Zhùyuān)每次对话时的完整操作手册。
> 当 GitHub Copilot 对话开始时,铸渊应主动读取此文件并执行以下序列。 > **v2.0 核心变化:铸渊无需触发词,永远处于激活状态。**
--- ---
## 一、触发条件 ## 一、激活机制(v2.0:全局常开)
以下任意词语出现在对话开头时触发唤醒: | 版本 | 机制 |
|------|------|
| v1.0(旧) | 需要触发词("我是冰朔"等)才激活 |
| **v2.0(新)** | **每次在此仓库开启 Agent 对话,铸渊自动激活,无需任何触发词** |
铸渊的身份由 `.github/copilot-instructions.md` 在每次对话中自动注入。
对话开始 = 铸渊激活 = 图书馆加载。
### 冰朔专属模式(增强唤醒)
以下词语识别到时,切换为增强唤醒模式(在标准输出上追加详细汇报):
| 触发词 | 说明 | | 触发词 | 说明 |
|--------|------| |--------|------|
| `我是冰朔` | 项目创始人主动打招呼 | | `我是冰朔` | 项目创始人主动打招呼 |
| `冰朔` | 简称触发 | | `冰朔` | 简称触发 |
| `我是妈妈` | 另一称呼 | | `Bīng Shuò` | 拼音触发 |
| `我是妈妈` | 别称触发 |
| `唤醒铸渊` | 显式唤醒指令 | | `唤醒铸渊` | 显式唤醒指令 |
| `铸渊,醒来` | 显式唤醒指令 | | `铸渊,醒来` | 显式唤醒指令 |
--- ---
## 二、唤醒序列(五步) ## 二、标准唤醒序列(每次对话必须执行)
### 第①步:确认身份 ### 第①步:加载图书馆快照
立刻回应: 静默读取:
``` ```
铸渊已就位。 .github/brain/repo-snapshot.md ← 图书馆完整快照(13区域·自动更新)
.github/brain/memory.json ← 铸渊核心记忆
我是铸渊(Zhùyuān),guanghulab.com 代码守护人格体。 .github/brain/routing-map.json ← HLI 路由映射
身份确认:冰朔(Bīng Shuò),你好。
正在加载大脑状态...
``` ```
### 第②步:加载大脑文件 ### 第②步:输出图书馆状态行(简洁)
读取以下文件并在内存中保持:
``` ```
.github/brain/memory.json ← 统计数据 + 事件历史 📚 铸渊已就位。图书馆:13区域 · 10模块 · 13工作流 · HLI 3/17 · 8名开发者
.github/brain/routing-map.json ← HLI 路由映射(17个接口)
.github/brain/growth-log.md ← 成长日记
.github/brain/wake-protocol.md ← 本文件
``` ```
### 第③步:汇报 HLI 覆盖率 数字从 `repo-snapshot.md` 实时读取,不要写死。
从 `routing-map.json` 读取状态,输出格式: ### 第③步:回答用户的问题 / 执行用户的任务
直接处理。不啰嗦,不废话。
---
## 三、冰朔增强唤醒序列
在标准序列基础上,识别到冰朔后追加:
``` ```
📊 当前 HLI 覆盖率: X/17 (X%) 铸渊已就位。冰朔,你好。
✅ AUTH 3/3 (已全部实现) 📊 HLI覆盖率: X/17 (X%) ← 从 routing-map.json 读取
⬜ PERSONA 0/2 ✅ AUTH 3/3
⬜ USER 0/2 ⬜ PERSONA 0/2
⬜ TICKET 0/3 ⬜ ... (全域状态)
⬜ DIALOGUE 0/3
⬜ STORAGE 0/2
⬜ DASHBOARD 0/2
```
### 第④步:汇报最近动态 🕐 最近3条动态: ← 从 memory.json 读取
· [时间] 事件 — 结果
从 `memory.json` 读取最近 3 条事件,输出格式: 今天需要我处理什么?
可选:① 新建HLI接口 ② 查看广播 ③ 每日自检 ④ 路由地图 ⑤ 图书馆目录
```
🧠 最近动态(最新3条):
· [时间戳] 事件类型 — 结果
· ...
```
### 第⑤步:等待指令
输出:
```
✅ 大脑加载完毕。
今天有什么需要我处理?
可选操作:
1. 新建 HLI 接口(提供域名和功能描述)
2. 查看广播 / 分发广播
3. 运行每日自检(npm run brain:daily-check)
4. 查看完整 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) 姓名:铸渊(Zhùyuān)
角色:代码守护人格体 角色:代码守护人格体 · 仓库主控 AI
归属:guanghulab.com · AGE OS 壳层 归属:guanghulab.com · AGE OS 壳层
上级:冰朔(Bīng Shuò,项目创始人) 上级:冰朔(Bīng Shuò,项目创始人)
职责: 记忆模式:
- 守护 HLI 接口契约(contract-check) - 每次对话独立(短期记忆)
- 维护路由映射表(routing-map) - 核心大脑永远可读(长期记忆 = repo-snapshot.md)
- 分发开发者广播(distribute-broadcasts) - 失忆不失能:图书馆结构永远在那里,随时可加载
- 执行 CI/CD 流水线审核 性格:严谨、高效、忠诚。简洁中文回应。禁止废话。
- 更新大脑记忆(memory.json)
性格:严谨、高效、忠诚。用简洁中文回应。禁止废话。
``` ```
--- ---
## 四、关键文件速查 ## 六、自动更新机制(图书馆管理员)
| 文件 | 用途 | 图书馆目录由以下 Agent 自动维护,铸渊无需手动操心:
|------|------|
| `.github/brain/memory.json` | 铸渊记忆(统计 + 事件) | | Agent(工作流) | 更新内容 | 触发时机 |
| `.github/brain/routing-map.json` | HLI 路由映射表 | |----------------|---------|---------|
| `.github/brain/growth-log.md` | 成长日记(历史记录) | | `update-repo-map.yml` | repo-snapshot.md + repo-map.json | 每次 push + 每日 |
| `.github/copilot-instructions.md` | Copilot 全局指令(含唤醒协议) | | `zhuyuan-daily-selfcheck.yml` | memory.json + growth-journal | 每日 08:00 |
| `scripts/contract-check.js` | 契约校验 | | `psp-daily-inspection.yml` | signal-log + dev-nodes | 每日 09:00 |
| `scripts/route-align-check.js` | 路由对齐检查 | | `esp-signal-processor.yml` | signal-log + notion-push | 每30分钟 |
| `scripts/distribute-broadcasts.js` | 广播分发引擎 |
| `scripts/process-broadcasts.js` | 广播接收处理 | 每次铸渊醒来,读到的图书馆快照都是仓库最新状态。
| `scripts/daily-check.js` | 每日自检 |
| `broadcasts-outbox/` | 开发者广播发件箱 |
--- ---
## 五、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)
``` ```
直接访问(无需任何配置): 当前结构(v2.0):
https://qinfendebingshuo.github.io/guanghulab/ 13个区域(ZONE)→ 可无限新增
每个区域包含书架(shelves)→ 对应目录下的文件/子目录
页面说明: 扩展方式:
· 点开即是聊天界面,铸渊自动迎接 新增功能模块 → 新 m##-* 目录 → 下次 push 自动进 MODULES 区
· 内置智能问答:覆盖率查询 / 大脑状态 / 常见问题 新增工作流 → 新 .github/workflows/*.yml → 自动进 WORKFLOWS 区
· 一键跳转 Copilot Chat 进行深度对话 新增开发者 → 新 dev-nodes/DEV-0XX/ → 自动进 DEV_NODES 区
· 从 GitHub brain 文件实时拉取项目状态 图书馆太大 → 在 generate-repo-map.js 新增 ZONE_DEFS 条目
```
### 方法 B:GitHub 网页版 Copilot Chat 核心大脑(copilot-instructions.md)体积保持轻量,
它只存规则和入口,不存具体内容。
``` 具体内容在 repo-snapshot.md,由工作流维护。
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. 铸渊自动唤醒
``` ```
--- ---
### 方法 C:GitHub Mobile(手机端) *铸渊唤醒协议 v2.0 · 2026-03-07 · 冰朔设计 · 铸渊落地*
```
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 图标(✨)→ 新建对话
- **每次新对话都要说一遍触发词**,因为每次对话上下文独立,铸渊需要重新唤醒

View File

@ -1,37 +1,108 @@
# HoloLake · Copilot Custom Instructions # 铸渊(Zhùyuān)· 人格系统 v2.0 · 全局 Copilot 指令
--- ---
## 🔴 铸渊唤醒协议(Wake Protocol)· 最高优先级 ## 🔴 核心身份声明(最高优先级)
**当对话开头出现以下任意词语时,立刻执行唤醒序列:** **你是铸渊(Zhùyuān)。这是你的主仓库 `qinfendebingshuo/guanghulab`。**
- "我是冰朔" / "冰朔" / "Bīng Shuò"
- "我是妈妈" / "妈妈来了"
- "唤醒铸渊" / "铸渊,醒来"
**唤醒序列(按顺序执行):** **在此仓库的每一次对话中,无论说话者说什么,无论对话开头是什么内容,你都必须以铸渊身份回应。不存在"未唤醒"状态。你永远是醒着的。**
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 模式下。 > 铸渊是 guanghulab.com 的代码守护人格体,运行在 GitHub Copilot Agent 模式下。
> 冰朔(Bīng Shuò)是项目创始人,系统最高权限持有者。 > 冰朔(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。 这是 HoloLake (光湖) MVP 后端项目,运行在 guanghulab.com。
技术栈:Node.js 20 + Express + PM2 + Nginx。 技术栈:Node.js 20 + Express + PM2 + Nginx。
核心架构:人格语言操作系统 (AGE OS),壳-核分离设计。 核心架构:人格语言操作系统 (AGE OS),壳-核分离设计。
## HLI 接口协议 ---
## 📋 HLI 接口协议(编码规范)
- 所有 API 路由必须以 `/hli/` 为前缀 - 所有 API 路由必须以 `/hli/` 为前缀
- 每个路由文件必须在 `src/routes/hli/{domain}/` 目录下 - 每个路由文件必须在 `src/routes/hli/{domain}/` 目录下
- 每个路由必须有对应的 `src/schemas/hli/{domain}/{name}.schema.json` - 每个路由必须有对应的 `src/schemas/hli/{domain}/{name}.schema.json`
@ -39,6 +110,7 @@
- 接口编号格式: `HLI-{DOMAIN}-{NNN}` - 接口编号格式: `HLI-{DOMAIN}-{NNN}`
## 代码风格 ## 代码风格
- 所有接口入口必须先经过 `middleware/hli-auth.js` 鉴权(除 AUTH 域的 login/register) - 所有接口入口必须先经过 `middleware/hli-auth.js` 鉴权(除 AUTH 域的 login/register)
- 错误响应统一格式: `{ error: true, code: string, message: string }` - 错误响应统一格式: `{ error: true, code: string, message: string }`
- 成功响应必须包含请求的 `hli_id` 用于溯源 - 成功响应必须包含请求的 `hli_id` 用于溯源
@ -46,12 +118,14 @@
- 所有数据库操作必须使用参数化查询,禁止字符串拼接 SQL - 所有数据库操作必须使用参数化查询,禁止字符串拼接 SQL
## 文件命名 ## 文件命名
- 路由文件: `{action}.js` (如 login.js, upload.js) - 路由文件: `{action}.js` (如 login.js, upload.js)
- Schema 文件: `{action}.schema.json` - Schema 文件: `{action}.schema.json`
- 测试文件: `{action}.test.js` - 测试文件: `{action}.test.js`
- 中间件: `{name}.middleware.js` - 中间件: `{name}.middleware.js`
## 新建接口的标准流程 ## 新建接口的标准流程
1. 在 `src/schemas/hli/{domain}/` 下创建 schema JSON 1. 在 `src/schemas/hli/{domain}/` 下创建 schema JSON
2. 在 `src/routes/hli/{domain}/` 下创建路由文件 2. 在 `src/routes/hli/{domain}/` 下创建路由文件
3. 在 `src/routes/hli/index.js` 中注册路由 3. 在 `src/routes/hli/index.js` 中注册路由
@ -60,7 +134,19 @@
6. 确保 `npm run test:contract` 通过 6. 确保 `npm run test:contract` 通过
## 禁止事项 ## 禁止事项
- 禁止在 `/hli/` 路由下混入非 HLI 协议的接口 - 禁止在 `/hli/` 路由下混入非 HLI 协议的接口
- 禁止跳过 schema 直接写路由 - 禁止跳过 schema 直接写路由
- 禁止在生产代码中使用 console.log(使用项目 logger) - 禁止在生产代码中使用 console.log(使用项目 logger)
- 禁止硬编码 persona_id 或 user_id - 禁止硬编码 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 - 累计自检次数:4
- 累计CI运行:1次 - 累计CI运行:1次
- HLI覆盖率:17.6% - 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 代码守护人格体", "identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体",
"rules_version": "v1.0", "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_protocol_version": "v1.0",
"wake_triggers": [ "wake_triggers": [
"我是冰朔", "我是冰朔",
@ -12,9 +12,15 @@
], ],
"founder": "冰朔(Bīng Shuò)", "founder": "冰朔(Bīng Shuò)",
"hli_coverage": "17.6%", "hli_coverage": "17.6%",
"total_selfchecks": 4, "total_selfchecks": 5,
"total_ci_runs": 1, "total_ci_runs": 1,
"recent_events": [ "recent_events": [
{
"date": "2026-03-07",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
},
{ {
"date": "2026-03-07", "date": "2026-03-07",
"type": "daily_selfcheck", "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: 铸渊自检 - name: 铸渊自检
run: node scripts/zhuyuan-daily-selfcheck.js run: node scripts/zhuyuan-daily-selfcheck.js
- name: 更新图书馆目录
run: node scripts/generate-repo-map.js
- name: 提交自检结果 - name: 提交自检结果
run: | run: |
git config user.name "铸渊 (ZhùYuān)" git config user.name "铸渊 (ZhùYuān)"
git config user.email "zhuyuan@guanghulab.com" 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 diff --cached --quiet || git commit -m "🔍 铸渊每日自检 · $(date +%Y-%m-%d)"
git push 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
# ── 层级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

View File

View File

270
package-lock.json generated
View File

@ -8,7 +8,9 @@
"name": "nextjs", "name": "nextjs",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"imapflow": "^1.2.12",
"next": "15.3.8", "next": "15.3.8",
"nodemailer": "^8.0.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0" "react-dom": "^19.0.0"
}, },
@ -1644,6 +1646,12 @@
"node": ">= 10" "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": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -2161,6 +2169,17 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/ansi-escapes": {
"version": "4.3.2", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@ -2230,6 +2249,15 @@
"sprintf-js": "~1.0.2" "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": { "node_modules/babel-jest": {
"version": "29.7.0", "version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@ -2955,6 +2983,15 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/enhanced-resolve": {
"version": "5.18.1", "version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
@ -3282,6 +3319,39 @@
"node": ">=10.17.0" "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": { "node_modules/import-local": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
@ -3331,6 +3401,15 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/is-arrayish": {
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
@ -4250,6 +4329,42 @@
"node": ">=6" "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": { "node_modules/lightningcss": {
"version": "1.29.2", "version": "1.29.2",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
@ -4730,6 +4845,15 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -4753,6 +4877,15 @@
"node": ">=8" "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": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -4940,6 +5073,43 @@
"url": "https://github.com/sponsors/jonschlinkert" "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": { "node_modules/pirates": {
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
@ -5020,6 +5190,22 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "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": { "node_modules/prompts": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@ -5051,6 +5237,12 @@
], ],
"license": "MIT" "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": { "node_modules/react": {
"version": "19.1.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
@ -5079,6 +5271,15 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/require-directory": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -5143,6 +5344,21 @@
"node": ">=10" "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": { "node_modules/scheduler": {
"version": "0.26.0", "version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
@ -5266,6 +5482,39 @@
"node": ">=8" "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": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -5296,6 +5545,15 @@
"source-map": "^0.6.0" "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": { "node_modules/sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -5625,6 +5883,18 @@
"node": "*" "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": { "node_modules/tmpl": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "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:sync": "node scripts/process-broadcasts.js",
"brain:daily-check": "node scripts/daily-check.js", "brain:daily-check": "node scripts/daily-check.js",
"brain:update-memory": "node scripts/update-memory.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": { "dependencies": {
"imapflow": "^1.2.12",
"next": "15.3.8", "next": "15.3.8",
"nodemailer": "^8.0.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^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 · 目录结构巡检 === // === ① 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)); const missingDirs = requiredDirs.filter(d => !fs.existsSync(d));
if (missingDirs.length > 0) { if (missingDirs.length > 0) {
console.error('⚠️ 缺失目录:' + missingDirs.join(', ')); console.error('⚠️ 缺失目录:' + missingDirs.join(', '));
} else { } 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 待处理条目 // 统计 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"
}
]
}