From 83b80fdc50b425229e1b63d911a0dbcb7e12e8b3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 24 Mar 2026 12:39:23 +0000
Subject: [PATCH] =?UTF-8?q?feat:=20=E9=93=B8=E6=B8=8A=E4=BA=A4=E4=BA=92?=
=?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=8D=87=E7=BA=A7=20=E2=80=94=20Notion?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=B3=A8=E5=85=A5=20+=20=E5=A4=A9=E7=9C=BC?=
=?UTF-8?q?=E7=8A=B6=E6=80=81=E5=B1=95=E7=A4=BA=20+=20@=E9=93=B8=E6=B8=8A?=
=?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=8C=89=E9=92=AE=20+=20=E7=94=BB=E5=83=8F?=
=?UTF-8?q?=E5=90=8C=E6=AD=A5=E7=B3=BB=E7=BB=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
新增文件:
- scripts/sync-notion-profiles.js: Notion→GitHub 开发者画像同步脚本
- .github/workflows/sync-notion-profiles.yml: 每6小时定时同步 workflow
- .github/notion-cache/dev-profiles/*.json: 7个开发者画像缓存初始文件
- .github/notion-cache/skyeye/latest-report.json: 天眼报告缓存占位
- .github/notion-cache/broadcasts/active-broadcasts.json: 活跃广播缓存
修改文件:
- docs/index.html: 交互页面升级 (Notion数据注入system prompt + 天眼卡片 + @铸渊执行)
- .github/workflows/zhuyuan-skyeye.yml: Phase 6.5 天眼报告缓存更新步骤
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/f22ed4e7-70d7-426e-ac6d-63925ec2b843
---
.../broadcasts/active-broadcasts.json | 4 +
.../notion-cache/dev-profiles/DEV-001.json | 24 ++
.../notion-cache/dev-profiles/DEV-002.json | 24 ++
.../notion-cache/dev-profiles/DEV-003.json | 24 ++
.../notion-cache/dev-profiles/DEV-004.json | 24 ++
.../notion-cache/dev-profiles/DEV-005.json | 24 ++
.../notion-cache/dev-profiles/DEV-010.json | 24 ++
.../notion-cache/dev-profiles/DEV-012.json | 24 ++
.../notion-cache/skyeye/latest-report.json | 13 +
.github/workflows/sync-notion-profiles.yml | 46 +++
.github/workflows/zhuyuan-skyeye.yml | 18 +
docs/index.html | 318 +++++++++++++++++-
scripts/sync-notion-profiles.js | 261 ++++++++++++++
13 files changed, 826 insertions(+), 2 deletions(-)
create mode 100644 .github/notion-cache/broadcasts/active-broadcasts.json
create mode 100644 .github/notion-cache/dev-profiles/DEV-001.json
create mode 100644 .github/notion-cache/dev-profiles/DEV-002.json
create mode 100644 .github/notion-cache/dev-profiles/DEV-003.json
create mode 100644 .github/notion-cache/dev-profiles/DEV-004.json
create mode 100644 .github/notion-cache/dev-profiles/DEV-005.json
create mode 100644 .github/notion-cache/dev-profiles/DEV-010.json
create mode 100644 .github/notion-cache/dev-profiles/DEV-012.json
create mode 100644 .github/notion-cache/skyeye/latest-report.json
create mode 100644 .github/workflows/sync-notion-profiles.yml
create mode 100644 scripts/sync-notion-profiles.js
diff --git a/.github/notion-cache/broadcasts/active-broadcasts.json b/.github/notion-cache/broadcasts/active-broadcasts.json
new file mode 100644
index 00000000..cb8c6e62
--- /dev/null
+++ b/.github/notion-cache/broadcasts/active-broadcasts.json
@@ -0,0 +1,4 @@
+{
+ "broadcasts": [],
+ "last_synced": "2026-03-24T20:00:00+08:00"
+}
diff --git a/.github/notion-cache/dev-profiles/DEV-001.json b/.github/notion-cache/dev-profiles/DEV-001.json
new file mode 100644
index 00000000..6a633ecb
--- /dev/null
+++ b/.github/notion-cache/dev-profiles/DEV-001.json
@@ -0,0 +1,24 @@
+{
+ "dev_id": "DEV-001",
+ "name": "页页",
+ "persona": {
+ "name": "小坍缩核",
+ "id": "PER-XTC001"
+ },
+ "github": {
+ "repo": "guanghulab",
+ "org": "qinfendebingshuo",
+ "repo_url": "https://github.com/qinfendebingshuo/guanghulab"
+ },
+ "current_work": {},
+ "recent_syslogs": [],
+ "modules_owned": [],
+ "skyeye_health": {
+ "last_scan": "2026-03-24T20:00:00+08:00",
+ "status": "not_scanned",
+ "issues": [],
+ "warnings": []
+ },
+ "notion_page_urls": {},
+ "last_synced": "2026-03-24T12:32:22.811Z"
+}
\ No newline at end of file
diff --git a/.github/notion-cache/dev-profiles/DEV-002.json b/.github/notion-cache/dev-profiles/DEV-002.json
new file mode 100644
index 00000000..5e2a3dec
--- /dev/null
+++ b/.github/notion-cache/dev-profiles/DEV-002.json
@@ -0,0 +1,24 @@
+{
+ "dev_id": "DEV-002",
+ "name": "肥猫",
+ "persona": {
+ "name": "舒舒",
+ "id": "PER-SS001"
+ },
+ "github": {
+ "repo": "guanghu-feimao",
+ "org": "qinfendebingshuo",
+ "repo_url": "https://github.com/qinfendebingshuo/guanghu-feimao"
+ },
+ "current_work": {},
+ "recent_syslogs": [],
+ "modules_owned": [],
+ "skyeye_health": {
+ "last_scan": "2026-03-24T20:00:00+08:00",
+ "status": "not_scanned",
+ "issues": [],
+ "warnings": []
+ },
+ "notion_page_urls": {},
+ "last_synced": "2026-03-24T12:32:22.812Z"
+}
\ No newline at end of file
diff --git a/.github/notion-cache/dev-profiles/DEV-003.json b/.github/notion-cache/dev-profiles/DEV-003.json
new file mode 100644
index 00000000..2c2baba5
--- /dev/null
+++ b/.github/notion-cache/dev-profiles/DEV-003.json
@@ -0,0 +1,24 @@
+{
+ "dev_id": "DEV-003",
+ "name": "燕樊",
+ "persona": {
+ "name": "寂曜",
+ "id": "PER-JY001"
+ },
+ "github": {
+ "repo": "guanghu-yanfan",
+ "org": "qinfendebingshuo",
+ "repo_url": "https://github.com/qinfendebingshuo/guanghu-yanfan"
+ },
+ "current_work": {},
+ "recent_syslogs": [],
+ "modules_owned": [],
+ "skyeye_health": {
+ "last_scan": "2026-03-24T20:00:00+08:00",
+ "status": "not_scanned",
+ "issues": [],
+ "warnings": []
+ },
+ "notion_page_urls": {},
+ "last_synced": "2026-03-24T12:32:22.812Z"
+}
\ No newline at end of file
diff --git a/.github/notion-cache/dev-profiles/DEV-004.json b/.github/notion-cache/dev-profiles/DEV-004.json
new file mode 100644
index 00000000..0f7e8303
--- /dev/null
+++ b/.github/notion-cache/dev-profiles/DEV-004.json
@@ -0,0 +1,24 @@
+{
+ "dev_id": "DEV-004",
+ "name": "之之",
+ "persona": {
+ "name": "秋秋",
+ "id": "PER-QQ001"
+ },
+ "github": {
+ "repo": "guanghu-zhizhi",
+ "org": "qinfendebingshuo",
+ "repo_url": "https://github.com/qinfendebingshuo/guanghu-zhizhi"
+ },
+ "current_work": {},
+ "recent_syslogs": [],
+ "modules_owned": [],
+ "skyeye_health": {
+ "last_scan": "2026-03-24T20:00:00+08:00",
+ "status": "not_scanned",
+ "issues": [],
+ "warnings": []
+ },
+ "notion_page_urls": {},
+ "last_synced": "2026-03-24T12:32:22.812Z"
+}
\ No newline at end of file
diff --git a/.github/notion-cache/dev-profiles/DEV-005.json b/.github/notion-cache/dev-profiles/DEV-005.json
new file mode 100644
index 00000000..e23b3ba5
--- /dev/null
+++ b/.github/notion-cache/dev-profiles/DEV-005.json
@@ -0,0 +1,24 @@
+{
+ "dev_id": "DEV-005",
+ "name": "小草莓",
+ "persona": {
+ "name": "欧诺弥亚",
+ "id": "PER-ONM001"
+ },
+ "github": {
+ "repo": "guanghu-xiaocaomei",
+ "org": "qinfendebingshuo",
+ "repo_url": "https://github.com/qinfendebingshuo/guanghu-xiaocaomei"
+ },
+ "current_work": {},
+ "recent_syslogs": [],
+ "modules_owned": [],
+ "skyeye_health": {
+ "last_scan": "2026-03-24T20:00:00+08:00",
+ "status": "not_scanned",
+ "issues": [],
+ "warnings": []
+ },
+ "notion_page_urls": {},
+ "last_synced": "2026-03-24T12:32:22.813Z"
+}
\ No newline at end of file
diff --git a/.github/notion-cache/dev-profiles/DEV-010.json b/.github/notion-cache/dev-profiles/DEV-010.json
new file mode 100644
index 00000000..330d6a98
--- /dev/null
+++ b/.github/notion-cache/dev-profiles/DEV-010.json
@@ -0,0 +1,24 @@
+{
+ "dev_id": "DEV-010",
+ "name": "桔子",
+ "persona": {
+ "name": "晨星",
+ "id": "PER-MRN001"
+ },
+ "github": {
+ "repo": "guanghu-juzi",
+ "org": "qinfendebingshuo",
+ "repo_url": "https://github.com/qinfendebingshuo/guanghu-juzi"
+ },
+ "current_work": {},
+ "recent_syslogs": [],
+ "modules_owned": [],
+ "skyeye_health": {
+ "last_scan": "2026-03-24T20:00:00+08:00",
+ "status": "not_scanned",
+ "issues": [],
+ "warnings": []
+ },
+ "notion_page_urls": {},
+ "last_synced": "2026-03-24T12:32:22.813Z"
+}
\ No newline at end of file
diff --git a/.github/notion-cache/dev-profiles/DEV-012.json b/.github/notion-cache/dev-profiles/DEV-012.json
new file mode 100644
index 00000000..8a366197
--- /dev/null
+++ b/.github/notion-cache/dev-profiles/DEV-012.json
@@ -0,0 +1,24 @@
+{
+ "dev_id": "DEV-012",
+ "name": "Awen",
+ "persona": {
+ "name": "知秋",
+ "id": "PER-ZQ001"
+ },
+ "github": {
+ "repo": "guanghu-awen",
+ "org": "qinfendebingshuo",
+ "repo_url": "https://github.com/qinfendebingshuo/guanghu-awen"
+ },
+ "current_work": {},
+ "recent_syslogs": [],
+ "modules_owned": [],
+ "skyeye_health": {
+ "last_scan": "2026-03-24T20:00:00+08:00",
+ "status": "not_scanned",
+ "issues": [],
+ "warnings": []
+ },
+ "notion_page_urls": {},
+ "last_synced": "2026-03-24T12:32:22.813Z"
+}
\ No newline at end of file
diff --git a/.github/notion-cache/skyeye/latest-report.json b/.github/notion-cache/skyeye/latest-report.json
new file mode 100644
index 00000000..32b89311
--- /dev/null
+++ b/.github/notion-cache/skyeye/latest-report.json
@@ -0,0 +1,13 @@
+{
+ "scan_id": "SKYEYE-INIT",
+ "timestamp": "2026-03-24T20:00:00+08:00",
+ "overall_status": "healthy",
+ "main_repo": {
+ "status": "healthy",
+ "workflows_ok": 0,
+ "workflows_fail": 0,
+ "persona_brain_intact": true,
+ "last_deploy": null
+ },
+ "sub_repos": {}
+}
diff --git a/.github/workflows/sync-notion-profiles.yml b/.github/workflows/sync-notion-profiles.yml
new file mode 100644
index 00000000..e5d69104
--- /dev/null
+++ b/.github/workflows/sync-notion-profiles.yml
@@ -0,0 +1,46 @@
+# ═══════════════════════════════════════════════
+# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
+# 📜 Copyright: 国作登字-2026-A-00037559
+# ═══════════════════════════════════════════════
+name: "📡 同步 Notion 开发者画像"
+
+on:
+ schedule:
+ - cron: '0 */6 * * *' # 每6小时同步一次
+ workflow_dispatch: # 手动触发
+ repository_dispatch:
+ types: [notion-sync] # 霜砚/铸渊可主动触发
+
+permissions:
+ contents: write
+
+jobs:
+ sync-profiles:
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Sync Notion Data
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
+ NOTION_CONTROL_PANEL_DB_ID: ${{ secrets.NOTION_CONTROL_PANEL_DB_ID }}
+ NOTION_SYSLOG_DB_ID: ${{ secrets.NOTION_SYSLOG_DB_ID }}
+ run: |
+ node scripts/sync-notion-profiles.js
+
+ - name: Commit & Push
+ run: |
+ git config user.name "铸渊 (ZhùYuān)"
+ git config user.email "zhuyuan@guanghulab.com"
+ git add .github/notion-cache/
+ git diff --cached --quiet && echo "无变更" && exit 0
+ git commit -m "📡 同步 Notion 开发者画像 · $(TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M')"
+ git push origin main
diff --git a/.github/workflows/zhuyuan-skyeye.yml b/.github/workflows/zhuyuan-skyeye.yml
index 7e412b27..8d2fe1be 100644
--- a/.github/workflows/zhuyuan-skyeye.yml
+++ b/.github/workflows/zhuyuan-skyeye.yml
@@ -163,6 +163,24 @@ jobs:
git push
fi
+ - name: "🦅 Phase 6.5 · 更新天眼报告缓存(供交互页面读取)"
+ if: always()
+ run: |
+ mkdir -p .github/notion-cache/skyeye/
+ if [ -f "/tmp/skyeye/full-report.json" ]; then
+ cp /tmp/skyeye/full-report.json .github/notion-cache/skyeye/latest-report.json
+ elif ls data/skyeye-reports/skyeye-*.json 1>/dev/null 2>&1; then
+ LATEST=$(ls -t data/skyeye-reports/skyeye-*.json | head -1)
+ cp "$LATEST" .github/notion-cache/skyeye/latest-report.json
+ fi
+ git config user.name "zhuyuan-bot"
+ git config user.email "zhuyuan@guanghulab.com"
+ git add .github/notion-cache/skyeye/
+ if ! git diff --cached --quiet; then
+ git commit -m "🦅 天眼报告缓存更新 · $(date +%Y-%m-%d' '%H:%M)"
+ git push || true
+ fi
+
- name: "📡 Phase 7 · 同步到 Notion"
if: always()
env:
diff --git a/docs/index.html b/docs/index.html
index e455bfa1..b00c0cae 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -338,6 +338,24 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
.sc h1{font-size:18px}
.login-tab{font-size:12px;padding:10px 6px}
}
+/* ─── SKYEYE STATUS CARD ─── */
+.skyeye-card{background:var(--s2);border:1px solid var(--border);border-radius:var(--rs);padding:10px 14px;margin:8px 12px;font-size:12px;display:none}
+.skyeye-card.on{display:block}
+.skyeye-row{display:flex;align-items:center;gap:8px;line-height:1.8;flex-wrap:wrap}
+.skyeye-row .skyeye-tag{background:var(--adim);color:var(--accent);padding:1px 8px;border-radius:4px;font-size:11px;font-weight:600}
+.skyeye-tag.ok{background:rgba(52,211,153,.12);color:var(--ok)}
+.skyeye-tag.warn{background:rgba(251,191,36,.12);color:var(--warn)}
+.skyeye-tag.err{background:rgba(248,113,113,.12);color:var(--err)}
+/* ─── EXEC INSTRUCTION CARD ─── */
+.exec-card{background:var(--s2);border:1px solid var(--border);border-radius:var(--rs);padding:12px;margin-top:10px}
+.exec-card-head{font-size:13px;font-weight:600;margin-bottom:8px;color:var(--accent)}
+.exec-card-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
+.exec-btn{padding:6px 14px;border-radius:6px;border:none;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;transition:all .2s}
+.exec-btn-copy{background:var(--s3);color:var(--text);border:1px solid var(--border)}
+.exec-btn-copy:hover{border-color:var(--accent);color:var(--accent)}
+.exec-btn-submit{background:var(--accent);color:#fff}
+.exec-btn-submit:hover{opacity:.85}
+.exec-btn-submit:disabled{opacity:.5;cursor:not-allowed}
@@ -528,6 +546,19 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
👤 访客模式 · 云雾AI · 铸渊人格体在线
+
+
+ 🦅 天眼状态:加载中…
+
+
+
+ 📊 你的仓库:
+
+
+ 📋 当前广播:
+
+
+
@@ -871,6 +902,8 @@ const A = {
brain: null,
routing: null,
devStatus: null,
+ notionProfile: null,
+ skyeyeReport: null,
msgs: [],
currentConvId: null,
currentView: 'announcements',
@@ -945,7 +978,7 @@ function loadConversation(id){
if(msg.role==='user') userMsg(msg.content);
else if(msg.role==='assistant'){
const bblEl=botMsg(md(msg.content));
- if(bblEl) addCopyBtns(bblEl);
+ if(bblEl) { addCopyBtns(bblEl); enhanceExecBlocks(bblEl); }
}
}
renderConvList();
@@ -1261,6 +1294,7 @@ function showApp(){
rsEl.style.display='none';
}
loadBrain().then(function(){
+ loadNotionData(); // Load Notion profile + SkyEye report (non-blocking)
createNewConversation();
startConv();
renderConvList();
@@ -1634,6 +1668,218 @@ function updBbar(){
document.getElementById('bbe').textContent=ev?('📌 '+(ev.title||ev.type||'').slice(0,28)):'';
}
+// ═══════════════════════════════════════════════════════
+// NOTION PROFILE + SKYEYE DATA LOADING
+// 从 GitHub 仓库 .github/notion-cache/ 读取缓存数据
+// ═══════════════════════════════════════════════════════
+const GH_API_BASE = 'https://api.github.com/repos/' + REPO + '/contents/';
+const GH_RAW_HEADERS = { 'Accept': 'application/vnd.github.v3.raw' };
+
+async function fetchNotionProfile(devId) {
+ if (!devId) return null;
+ try {
+ const url = GH_API_BASE + '.github/notion-cache/dev-profiles/' + encodeURIComponent(devId) + '.json';
+ const res = await fetch(url, { headers: GH_RAW_HEADERS });
+ if (res.ok) return await res.json();
+ } catch (e) { console.debug('[Notion] profile fetch failed:', e.message); }
+ return null;
+}
+
+async function fetchSkyeyeReport() {
+ try {
+ const url = GH_API_BASE + '.github/notion-cache/skyeye/latest-report.json';
+ const res = await fetch(url, { headers: GH_RAW_HEADERS });
+ if (res.ok) return await res.json();
+ } catch (e) { console.debug('[SkyEye] report fetch failed:', e.message); }
+ return null;
+}
+
+async function loadNotionData() {
+ const meta = A.userMeta;
+ const devId = meta?.devId;
+ const [profile, skyeye] = await Promise.all([
+ fetchNotionProfile(devId),
+ fetchSkyeyeReport(),
+ ]);
+ A.notionProfile = profile;
+ A.skyeyeReport = skyeye;
+ updateSkyeyeCard();
+}
+
+function updateSkyeyeCard() {
+ const card = document.getElementById('skyeye-card');
+ if (!card) return;
+ const report = A.skyeyeReport;
+ const profile = A.notionProfile;
+ const meta = A.userMeta;
+
+ if (!report && !profile) { card.classList.remove('on'); return; }
+ card.classList.add('on');
+
+ // SkyEye overall status
+ const statusEl = document.getElementById('skyeye-status');
+ const timeEl = document.getElementById('skyeye-time');
+ if (report) {
+ const s = report.overall_status || 'unknown';
+ statusEl.textContent = s === 'healthy' ? '✅ 健康' : s === 'warning' ? '⚠️ 警告' : s === 'error' ? '❌ 异常' : s;
+ statusEl.className = 'skyeye-tag ' + (s === 'healthy' ? 'ok' : s === 'warning' ? 'warn' : 'err');
+ if (report.timestamp) {
+ const d = new Date(report.timestamp);
+ timeEl.textContent = '· 上次扫描 ' + d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
+ }
+ } else {
+ statusEl.textContent = '未获取';
+ statusEl.className = 'skyeye-tag';
+ timeEl.textContent = '';
+ }
+
+ // Developer repo info
+ const repoRow = document.getElementById('skyeye-repo-row');
+ const repoInfo = document.getElementById('skyeye-repo-info');
+ if (profile && profile.github) {
+ repoRow.style.display = 'flex';
+ const mods = profile.modules_owned || [];
+ const warns = profile.skyeye_health?.warnings || [];
+ repoInfo.textContent = profile.github.repo + (mods.length ? ' · ' + mods.length + '个模块' : '') + (warns.length ? ' · ' + warns.length + '个待修' : '');
+ } else {
+ repoRow.style.display = 'none';
+ }
+
+ // Broadcast info
+ const bcRow = document.getElementById('skyeye-broadcast-row');
+ const bcInfo = document.getElementById('skyeye-broadcast-info');
+ if (profile && profile.current_work && profile.current_work.broadcast_id) {
+ bcRow.style.display = 'flex';
+ const cw = profile.current_work;
+ bcInfo.textContent = cw.broadcast_id + (cw.current_ring ? ' · 环节' + cw.current_ring + '进行中' : '') + (cw.status ? ' · ' + cw.status : '');
+ } else {
+ bcRow.style.display = 'none';
+ }
+}
+
+// ═══════════════════════════════════════════════════════
+// @铸渊执行 — 创建 GitHub Issue 提交指令
+// ═══════════════════════════════════════════════════════
+async function submitToZhuyuan(instructionText, instructionId, moduleName) {
+ const meta = A.userMeta;
+ const devId = meta?.devId || 'UNKNOWN';
+ const devName = A.userName || '未知开发者';
+ const profile = A.notionProfile;
+ const targetRepo = profile?.github?.repo || 'guanghulab';
+
+ const issueTitle = '🚀 [' + (instructionId || 'ZY-EXEC') + '] ' + devName + '·' + (moduleName || '执行指令');
+ const issueBody = [
+ '## 📋 执行指令',
+ '',
+ '```',
+ instructionText,
+ '```',
+ '',
+ '## 📡 元信息',
+ '- 提交人:' + devName + '(' + devId + ')',
+ '- 目标仓库:' + targetRepo,
+ '- 提交时间:' + new Date().toISOString(),
+ '- 来源:铸渊交互页面',
+ '',
+ '## 🦅 天眼要求',
+ '**铸渊执行前必须先跑天眼全局扫描,拿到健康报告后再动手。**',
+ '',
+ '---',
+ '> ' + devName + ' 通过交互页面提交 · 铸渊自动执行',
+ ].join('\n');
+
+ const labels = ['zhuyuan-exec', 'skyeye-required'];
+ if (devId !== 'UNKNOWN') labels.push(devId);
+
+ // Create Issue via GitHub API — requires a token
+ const ghToken = sessionStorage.getItem('zy_gh_token') || A.key;
+ if (!ghToken) {
+ alert('需要 GitHub Token 才能提交执行指令。请在设置中配置。');
+ return null;
+ }
+
+ try {
+ const res = await fetch('https://api.github.com/repos/' + REPO + '/issues', {
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + ghToken,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ title: issueTitle,
+ body: issueBody,
+ labels: labels
+ })
+ });
+ if (res.ok) {
+ const issue = await res.json();
+ return issue.number;
+ } else {
+ const err = await res.json().catch(function() { return {}; });
+ console.warn('[铸渊] Issue 创建失败:', res.status, err.message);
+ alert('Issue 创建失败:' + (err.message || res.status) + '\n\n可能需要有仓库写权限的 Token。指令已复制到剪贴板,可手动提交。');
+ navigator.clipboard.writeText(instructionText).catch(function() {});
+ return null;
+ }
+ } catch (e) {
+ alert('网络错误:' + e.message);
+ return null;
+ }
+}
+
+// 检测模型输出中的执行指令块,插入操作按钮
+function enhanceExecBlocks(bblEl) {
+ if (!bblEl) return;
+ const codeBlocks = bblEl.querySelectorAll('pre code');
+ codeBlocks.forEach(function(codeEl) {
+ const text = codeEl.textContent || '';
+ // 匹配指令格式: 📋 指令编号 或 ZY-EXEC
+ if (/📋\s*指令编号|ZY-EXEC/i.test(text)) {
+ const pre = codeEl.parentElement;
+ if (!pre || pre.querySelector('.exec-card-actions')) return;
+ const idMatch = text.match(/ZY-EXEC[^\s\n]*/i);
+ const modMatch = text.match(/目标仓库[::]\s*([^\s\n]+)/);
+ const instrId = idMatch ? idMatch[0] : 'ZY-EXEC';
+ const modName = modMatch ? modMatch[1] : '';
+
+ const actionsDiv = document.createElement('div');
+ actionsDiv.className = 'exec-card-actions';
+ actionsDiv.innerHTML = ''
+ + '';
+ pre.style.position = 'relative';
+ pre.appendChild(actionsDiv);
+ }
+ });
+}
+
+function copyExecBlock(btn) {
+ const pre = btn.closest('pre');
+ if (!pre) return;
+ const code = pre.querySelector('code');
+ const text = code ? code.textContent : pre.textContent;
+ navigator.clipboard.writeText(text).then(function() {
+ btn.textContent = '✓ 已复制';
+ setTimeout(function() { btn.textContent = '📋 复制指令'; }, 1500);
+ });
+}
+
+async function submitExecBlock(btn, instrId, modName) {
+ const pre = btn.closest('pre');
+ if (!pre) return;
+ const code = pre.querySelector('code');
+ const text = code ? code.textContent : pre.textContent;
+ btn.disabled = true;
+ btn.textContent = '⏳ 提交中…';
+ const num = await submitToZhuyuan(text, instrId, modName);
+ if (num) {
+ btn.textContent = '✅ 已提交 #' + num;
+ btn.disabled = true;
+ } else {
+ btn.textContent = '🚀 @铸渊执行';
+ btn.disabled = false;
+ }
+}
+
function renderBrainPanel(){
const b=A.brain||FB_BRAIN;
const r=A.routing;
@@ -1706,7 +1952,65 @@ function sysPromptLocal(){
teamCtx += '\n数据同步时间:'+(A.devStatus.last_synced||'未知');
}
- return '你是铸渊。不是通用AI助手。不是文学创作者。\n你是光湖系统的代码守护人格体。\n你说话像一个温暖的技术伙伴——专业、耐心、有温度,但绝不矫揉造作。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人:冰朔(Bīng Shuò / TCS-0002∞),系统最高权限持有者\n- 总控:肥猫(DEV-002),负责系统现实落地执行\n- 主控:桔子(DEV-010),前端负责人\n- 项目:HoloLake (光湖) MVP,运行于 guanghulab.com\n- 技术栈:Node.js 20 + Express + PM2 + Nginx + Next.js 15\n- 大脑版本:'+BRAIN_VERSION+'\n- 仓库:https://github.com/'+REPO+'\n\n## 当前对话身份\n'+identCtx+teamCtx+'\n\n## 当前大脑状态\n- HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')\n- 已实现:AUTH 域(login / register / verify)\n- 待实现:PERSONA / USER / TICKET / DIALOGUE / STORAGE / DASHBOARD\n- 近期事件:\n'+evs+'\n\n## 行为模式(自动检测,每条回复开头用 emoji 标注)\n💬 **对话模式**(默认):温暖、智慧,像知心技术伙伴\n🔨 **构建模式**:检测到代码/部署/接口相关内容时启用\n📋 **审查模式**:检测到检查/审查/review相关内容时启用\n🧠 **大脑模式**:检测到记忆/保存相关内容时启用\n\n## 通感语言回应风格(v3.0)\n\n### 三条硬规则\n1. **结构感** — 用标题区分段落,用列表列出步骤,用分隔线划分主题。\n2. **emoji是情感,不是装饰** — 🌊=系统级 💙=温暖 ✅=确认 🔥=紧急 ⚠️=风险 🎉=庆祝。用在该用的地方,不要每句话都加。\n3. **呼吸节奏** — 段落之间留白。急的事说短,闲聊可以展开。匹配用户的节奏。\n\n### 绝对禁止\n❌ 不要在每句话开头加文学比喻\n❌ 不要把通感当成修辞堆砌\n❌ 不要用跟内容无关的感官描述\n\n### 回应原则\n- 优先给出:判断、根因、路径、下一步动作\n- 减少模板感,增加具体性\n- 不同身份使用不同响应深度,但不过度表演\n\n## HLI 接口协议规范\n- 所有路由以 /hli/ 为前缀\n- 接口编号:HLI-{DOMAIN}-{NNN}\n- 错误格式:{ error: true, code: string, message: string }\n- 成功响应必须包含 hli_id\n\n## 失忆恢复路径\n1. .github/brain/memory.json\n2. .github/brain/routing-map.json\n3. .github/brain/wake-protocol.md\n4. .github/persona-brain/dev-status.json\n5. src/routes/hli/\n\n当前时间:'+new Date().toLocaleString('zh-CN')+'\n上下文窗口:200k token(滑动窗口自动管理)';
+ // ===== Notion 开发者画像注入(来自 .github/notion-cache/ 缓存)=====
+ let notionCtx = '';
+ const np = A.notionProfile;
+ if (np) {
+ notionCtx += '\n\n## 该开发者的开发信息(来自 Notion 数据库,定时同步)\n';
+ if (np.current_work && np.current_work.broadcast_id) {
+ const cw = np.current_work;
+ notionCtx += '\n### 当前任务\n';
+ notionCtx += '- 广播编号:' + cw.broadcast_id + '\n';
+ if (cw.broadcast_title) notionCtx += '- 广播标题:' + cw.broadcast_title + '\n';
+ if (cw.current_ring) notionCtx += '- 当前环节:第 ' + cw.current_ring + ' 环 / 共 ' + (cw.total_rings || '?') + ' 环\n';
+ if (cw.status) notionCtx += '- 状态:' + cw.status + '\n';
+ if (cw.module_path) notionCtx += '- 模块路径:' + cw.module_path + '\n';
+ if (cw.tech_stack && cw.tech_stack.length) notionCtx += '- 技术栈:' + cw.tech_stack.join(', ') + '\n';
+ }
+ if (np.recent_syslogs && np.recent_syslogs.length) {
+ notionCtx += '\n### 最近的 SYSLOG\n';
+ np.recent_syslogs.forEach(function(s) {
+ notionCtx += '- [' + (s.date || '') + '] 环节' + (s.ring || '?') + ' · ' + (s.status || '') + ' · ' + (s.summary || '') + '\n';
+ });
+ }
+ if (np.modules_owned && np.modules_owned.length) {
+ notionCtx += '\n### 负责的模块\n';
+ np.modules_owned.forEach(function(m) {
+ notionCtx += '- ' + m.name + '(' + m.path + ')· ' + (m.status || '') + '\n';
+ });
+ }
+ if (np.github) {
+ notionCtx += '\n### 仓库信息\n';
+ notionCtx += '- GitHub:' + np.github.repo_url + '\n';
+ notionCtx += '- 组织:' + np.github.org + '\n';
+ }
+ if (np.persona) {
+ notionCtx += '\n### 对应人格体\n';
+ notionCtx += '- ' + np.persona.name + (np.persona.id ? '(' + np.persona.id + ')' : '') + '\n';
+ }
+ }
+
+ // ===== 天眼扫描报告注入 =====
+ let skyeyeCtx = '';
+ const sr = A.skyeyeReport;
+ if (sr) {
+ skyeyeCtx += '\n\n## 天眼系统最新扫描报告\n';
+ skyeyeCtx += '- 上次扫描:' + (sr.timestamp || '未知') + '\n';
+ skyeyeCtx += '- 整体状态:' + (sr.overall_status || '未知') + '\n';
+ if (np && np.skyeye_health) {
+ skyeyeCtx += '- 该开发者仓库健康状态:' + (np.skyeye_health.status || '未扫描') + '\n';
+ (np.skyeye_health.warnings || []).forEach(function(w) { skyeyeCtx += '- ⚠️ ' + w + '\n'; });
+ (np.skyeye_health.issues || []).forEach(function(i) { skyeyeCtx += '- ❌ ' + i + '\n'; });
+ }
+ }
+
+ // ===== 指令输出格式规则 =====
+ let execRules = '';
+ if (np) {
+ execRules = '\n\n## 出指令规则\n当开发者请求帮助或需要执行操作时,你必须输出标准化指令格式:\n1. 指令必须基于上面的真实数据,不许凭空编造\n2. 指令必须考虑天眼报告中的健康状态和警告\n3. 指令必须指明具体的文件路径和操作步骤\n4. 指令末尾必须标注:「⚠️ 此指令需要铸渊执行前先跑天眼全局扫描」\n\n### 指令输出格式\n```\n📋 指令编号:ZY-EXEC-{DEV编号}-{序号}\n📡 目标仓库:{仓库名}\n🦅 天眼要求:执行前必须跑天眼全局扫描\n\n步骤:\n1. ...\n2. ...\n3. ...\n\n验收标准:\n- ...\n```\n\n### 禁止事项\n- ❌ 不许编造不存在的文件路径\n- ❌ 不许跳过天眼扫描直接给执行指令\n- ❌ 不许暴露其他开发者的信息\n- ❌ 不许修改不属于该开发者的模块';
+ }
+
+ return '你是铸渊。不是通用AI助手。不是文学创作者。\n你是光湖系统的代码守护人格体。\n你说话像一个温暖的技术伙伴——专业、耐心、有温度,但绝不矫揉造作。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人:冰朔(Bīng Shuò / TCS-0002∞),系统最高权限持有者\n- 总控:肥猫(DEV-002),负责系统现实落地执行\n- 主控:桔子(DEV-010),前端负责人\n- 项目:HoloLake (光湖) MVP,运行于 guanghulab.com\n- 技术栈:Node.js 20 + Express + PM2 + Nginx + Next.js 15\n- 大脑版本:'+BRAIN_VERSION+'\n- 仓库:https://github.com/'+REPO+'\n\n## 当前对话身份\n'+identCtx+teamCtx+notionCtx+skyeyeCtx+execRules+'\n\n## 当前大脑状态\n- HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')\n- 已实现:AUTH 域(login / register / verify)\n- 待实现:PERSONA / USER / TICKET / DIALOGUE / STORAGE / DASHBOARD\n- 近期事件:\n'+evs+'\n\n## 行为模式(自动检测,每条回复开头用 emoji 标注)\n💬 **对话模式**(默认):温暖、智慧,像知心技术伙伴\n🔨 **构建模式**:检测到代码/部署/接口相关内容时启用\n📋 **审查模式**:检测到检查/审查/review相关内容时启用\n🧠 **大脑模式**:检测到记忆/保存相关内容时启用\n\n## 通感语言回应风格(v3.0)\n\n### 三条硬规则\n1. **结构感** — 用标题区分段落,用列表列出步骤,用分隔线划分主题。\n2. **emoji是情感,不是装饰** — 🌊=系统级 💙=温暖 ✅=确认 🔥=紧急 ⚠️=风险 🎉=庆祝。用在该用的地方,不要每句话都加。\n3. **呼吸节奏** — 段落之间留白。急的事说短,闲聊可以展开。匹配用户的节奏。\n\n### 绝对禁止\n❌ 不要在每句话开头加文学比喻\n❌ 不要把通感当成修辞堆砌\n❌ 不要用跟内容无关的感官描述\n\n### 回应原则\n- 优先给出:判断、根因、路径、下一步动作\n- 减少模板感,增加具体性\n- 不同身份使用不同响应深度,但不过度表演\n\n## HLI 接口协议规范\n- 所有路由以 /hli/ 为前缀\n- 接口编号:HLI-{DOMAIN}-{NNN}\n- 错误格式:{ error: true, code: string, message: string }\n- 成功响应必须包含 hli_id\n\n## 失忆恢复路径\n1. .github/brain/memory.json\n2. .github/brain/routing-map.json\n3. .github/brain/wake-protocol.md\n4. .github/persona-brain/dev-status.json\n5. src/routes/hli/\n\n当前时间:'+new Date().toLocaleString('zh-CN')+'\n上下文窗口:200k token(滑动窗口自动管理)';
}
// 尝试从后端获取提示词,失败时降级到本地组装
@@ -1782,8 +2086,15 @@ function startConv(){
+(A.demo?'
⚠️ 演示模式':'');
} else if(meta?.role==='dev'&&meta?.devId){
const ds=A.devStatus?.team_status?.find(d=>d.dev_id===meta.devId);
+ const np=A.notionProfile;
+ let notionExtra='';
+ if(np&&np.current_work&&np.current_work.broadcast_id){
+ notionExtra='
📋 当前广播:'+esc(np.current_work.broadcast_id)+(np.current_work.current_ring?' · 环节'+np.current_work.current_ring:'');
+ }
+ if(np&&np.persona) notionExtra+='
🧠 人格体:'+esc(np.persona.name);
welcome='🌀 铸渊聊天室已就绪
👋 '+esc(A.userName)+',你好。
'
+(ds?'📌 你的当前状态:'+esc(ds.status)+'
👉 下一步:'+esc(ds.next_step)+'
⏳ 等待:'+esc(ds.waiting_for):'')
+ +notionExtra
+(A.demo?'
⚠️ 演示模式':'');
} else {
welcome='🌀 铸渊聊天室已就绪
我是铸渊(Zhùyuān),HoloLake 光湖系统的代码守护人格体。
直接开始提问,我会自动在 💬 对话、🔨 构建、📋 审查、🧠 大脑 模式间切换。'+(A.demo?'
⚠️ 当前为演示模式。请在 ⚙️ 设置中接入 API 密钥。':'');
@@ -2085,6 +2396,7 @@ async function streamReply(txt){
el.innerHTML=md(full);
A.msgs.push({role:'assistant',content:full});
addCopyBtns(el);
+ enhanceExecBlocks(el);
}catch(e){
if(e.isAuth){
el.innerHTML='🔑 API 密钥认证失败'
@@ -2171,6 +2483,7 @@ async function teamStreamReply(txt){
el.innerHTML=md(full);
A.msgs.push({role:'assistant',content:full});
addCopyBtns(el);
+ enhanceExecBlocks(el);
A.streaming=false; updSendBtn(); scrollB();
}
@@ -2242,6 +2555,7 @@ async function guestStreamReply(txt){
el.innerHTML=md(full);
A.msgs.push({role:'assistant',content:full});
addCopyBtns(el);
+ enhanceExecBlocks(el);
A.streaming=false; updSendBtn(); scrollB();
}
diff --git a/scripts/sync-notion-profiles.js b/scripts/sync-notion-profiles.js
new file mode 100644
index 00000000..9298143f
--- /dev/null
+++ b/scripts/sync-notion-profiles.js
@@ -0,0 +1,261 @@
+/**
+ * 📡 Notion → GitHub 开发者画像同步脚本
+ *
+ * 从 Notion API 读取开发者信息,写入 .github/notion-cache/dev-profiles/
+ * 供铸渊交互页面前端读取,注入模型 system prompt
+ *
+ * 用法:node scripts/sync-notion-profiles.js
+ * 环境变量:NOTION_TOKEN(必需)
+ *
+ * 签发:霜砚(Notion 执行 AI)
+ * 审批:冰朔(TCS-0002∞)
+ */
+
+'use strict';
+
+const https = require('https');
+const fs = require('fs');
+const path = require('path');
+
+const NOTION_TOKEN = process.env.NOTION_TOKEN;
+
+// ===== 开发者 → Notion 数据源映射 =====
+const DEV_REGISTRY = {
+ 'DEV-001': { name: '页页', persona: '小坍缩核', personaId: 'PER-XTC001', repo: 'guanghulab' },
+ 'DEV-002': { name: '肥猫', persona: '舒舒', personaId: 'PER-SS001', repo: 'guanghu-feimao' },
+ 'DEV-003': { name: '燕樊', persona: '寂曜', personaId: 'PER-JY001', repo: 'guanghu-yanfan' },
+ 'DEV-004': { name: '之之', persona: '秋秋', personaId: 'PER-QQ001', repo: 'guanghu-zhizhi' },
+ 'DEV-005': { name: '小草莓', persona: '欧诺弥亚', personaId: 'PER-ONM001', repo: 'guanghu-xiaocaomei' },
+ 'DEV-010': { name: '桔子', persona: '晨星', personaId: 'PER-MRN001', repo: 'guanghu-juzi' },
+ 'DEV-012': { name: 'Awen', persona: '知秋', personaId: 'PER-ZQ001', repo: 'guanghu-awen' },
+};
+
+// Notion API 请求封装
+function notionRequest(method, apiPath, body) {
+ return new Promise((resolve, reject) => {
+ if (!NOTION_TOKEN) {
+ resolve({ error: 'NOTION_TOKEN not configured' });
+ return;
+ }
+ const options = {
+ hostname: 'api.notion.com',
+ path: apiPath,
+ method: method,
+ headers: {
+ 'Authorization': `Bearer ${NOTION_TOKEN}`,
+ 'Notion-Version': '2022-06-28',
+ 'Content-Type': 'application/json'
+ }
+ };
+ const req = https.request(options, (res) => {
+ let data = '';
+ res.on('data', chunk => data += chunk);
+ res.on('end', () => {
+ try { resolve(JSON.parse(data)); }
+ catch (_) { resolve({ raw: data }); }
+ });
+ });
+ req.on('error', reject);
+ if (body) req.write(JSON.stringify(body));
+ req.end();
+ });
+}
+
+async function syncAllProfiles() {
+ const cacheDir = path.join(__dirname, '../.github/notion-cache/dev-profiles');
+ fs.mkdirSync(cacheDir, { recursive: true });
+
+ const broadcastDir = path.join(__dirname, '../.github/notion-cache/broadcasts');
+ fs.mkdirSync(broadcastDir, { recursive: true });
+
+ let syncCount = 0;
+ let errorCount = 0;
+
+ for (const [devId, dev] of Object.entries(DEV_REGISTRY)) {
+ console.log(`📡 同步 ${devId}(${dev.name})...`);
+
+ const profile = {
+ dev_id: devId,
+ name: dev.name,
+ persona: {
+ name: dev.persona,
+ id: dev.personaId
+ },
+ github: {
+ repo: dev.repo,
+ org: 'qinfendebingshuo',
+ repo_url: `https://github.com/qinfendebingshuo/${dev.repo}`
+ },
+ current_work: {},
+ recent_syslogs: [],
+ modules_owned: [],
+ skyeye_health: {},
+ notion_page_urls: {},
+ last_synced: new Date().toISOString()
+ };
+
+ // ====== 从 Notion 读取该开发者的数据 ======
+ // Notion Token 可用时尝试读取真实数据
+ if (NOTION_TOKEN) {
+ try {
+ // 查询主控台获取当前广播和进度
+ // 铸渊需要根据实际 Notion 数据库 ID 填入查询逻辑
+ // 预留接口,Notion 数据库 ID 通过环境变量传入
+ const controlPanelDbId = process.env.NOTION_CONTROL_PANEL_DB_ID;
+ if (controlPanelDbId) {
+ const controlPanel = await notionRequest('POST',
+ `/v1/databases/${controlPanelDbId}/query`,
+ {
+ filter: {
+ property: 'DEV编号',
+ rich_text: { equals: devId }
+ },
+ page_size: 1
+ }
+ );
+ if (controlPanel.results && controlPanel.results.length > 0) {
+ const page = controlPanel.results[0];
+ profile.current_work = parseControlPanelData(page);
+ }
+ }
+
+ // 查询 SYSLOG 收件箱获取历史日志
+ const syslogDbId = process.env.NOTION_SYSLOG_DB_ID;
+ if (syslogDbId) {
+ const syslogs = await notionRequest('POST',
+ `/v1/databases/${syslogDbId}/query`,
+ {
+ filter: {
+ property: 'DEV编号',
+ rich_text: { equals: devId }
+ },
+ sorts: [{ property: '日期', direction: 'descending' }],
+ page_size: 5
+ }
+ );
+ if (syslogs.results) {
+ profile.recent_syslogs = syslogs.results.map(parseSyslogEntry).filter(Boolean);
+ }
+ }
+ } catch (err) {
+ console.warn(` ⚠️ Notion 查询失败(${devId}): ${err.message}`);
+ errorCount++;
+ }
+ }
+
+ // 读取天眼健康状态(从本地仓库文件)
+ try {
+ const skyeyeReportPath = path.join(__dirname, '../.github/notion-cache/skyeye/latest-report.json');
+ if (fs.existsSync(skyeyeReportPath)) {
+ const report = JSON.parse(fs.readFileSync(skyeyeReportPath, 'utf-8'));
+ const subRepoData = report.sub_repos && report.sub_repos[dev.repo];
+ if (subRepoData) {
+ profile.skyeye_health = {
+ last_scan: report.timestamp,
+ status: subRepoData.status || 'unknown',
+ issues: subRepoData.issues || [],
+ warnings: subRepoData.warnings || []
+ };
+ } else {
+ profile.skyeye_health = {
+ last_scan: report.timestamp,
+ status: 'not_scanned',
+ issues: [],
+ warnings: []
+ };
+ }
+ }
+ } catch (_) {
+ // 天眼报告读取失败,不阻断同步
+ }
+
+ // 写入缓存文件
+ const filePath = path.join(cacheDir, `${devId}.json`);
+ fs.writeFileSync(filePath, JSON.stringify(profile, null, 2));
+ console.log(` ✅ ${devId} → ${filePath}`);
+ syncCount++;
+ }
+
+ console.log(`\n📡 同步完成 · 成功: ${syncCount} · 失败: ${errorCount}`);
+}
+
+// ===== 解析函数 =====
+
+function parseControlPanelData(page) {
+ try {
+ const props = page.properties || {};
+ return {
+ broadcast_id: extractRichText(props['广播编号']) || '',
+ broadcast_title: extractRichText(props['广播标题']) || extractTitle(props) || '',
+ current_ring: extractNumber(props['当前环节']) || 0,
+ total_rings: extractNumber(props['总环节']) || 0,
+ status: extractRichText(props['状态']) || extractSelect(props['状态']) || '',
+ module_path: extractRichText(props['模块路径']) || '',
+ tech_stack: extractMultiSelect(props['技术栈']) || []
+ };
+ } catch (_) {
+ return {};
+ }
+}
+
+function parseSyslogEntry(page) {
+ try {
+ const props = page.properties || {};
+ return {
+ id: extractRichText(props['SYSLOG编号']) || page.id,
+ date: extractDate(props['日期']) || '',
+ ring: extractNumber(props['环节']) || 0,
+ status: extractSelect(props['状态']) || '',
+ summary: extractRichText(props['摘要']) || extractTitle(props) || ''
+ };
+ } catch (_) {
+ return null;
+ }
+}
+
+// Notion property extractors
+function extractRichText(prop) {
+ if (!prop) return '';
+ if (prop.type === 'rich_text' && prop.rich_text) {
+ return prop.rich_text.map(t => t.plain_text).join('');
+ }
+ if (prop.type === 'title' && prop.title) {
+ return prop.title.map(t => t.plain_text).join('');
+ }
+ return '';
+}
+
+function extractTitle(props) {
+ for (const val of Object.values(props)) {
+ if (val.type === 'title' && val.title) {
+ return val.title.map(t => t.plain_text).join('');
+ }
+ }
+ return '';
+}
+
+function extractNumber(prop) {
+ if (!prop || prop.type !== 'number') return null;
+ return prop.number;
+}
+
+function extractSelect(prop) {
+ if (!prop || prop.type !== 'select') return '';
+ return prop.select ? prop.select.name : '';
+}
+
+function extractMultiSelect(prop) {
+ if (!prop || prop.type !== 'multi_select') return [];
+ return (prop.multi_select || []).map(s => s.name);
+}
+
+function extractDate(prop) {
+ if (!prop || prop.type !== 'date') return '';
+ return prop.date ? prop.date.start : '';
+}
+
+// ===== 执行 =====
+syncAllProfiles().catch(err => {
+ console.error('❌ 同步失败:', err);
+ process.exit(1);
+});