feat: AGE OS 跨平台全链路自动化闭环系统 GitHub 端落地实施

This commit is contained in:
copilot-swe-agent[bot] 2026-03-07 04:52:07 +00:00
parent d3c834ad5d
commit 3503923a4a
33 changed files with 1404 additions and 7 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"
}
]
}
}

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

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

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