diff --git a/.github/workflows/syslog-pipeline.yml b/.github/workflows/syslog-pipeline.yml new file mode 100644 index 00000000..13f8b85e --- /dev/null +++ b/.github/workflows/syslog-pipeline.yml @@ -0,0 +1,130 @@ +name: 铸渊 · SYSLOG Pipeline (A/D/E) + +on: + push: + branches: [main] + paths: + - 'syslog-inbox/**' + workflow_dispatch: + +jobs: + # ── Pipeline A:SYSLOG 读取与处理 ────────────────────── + pipeline-a-syslog: + name: 📥 Pipeline A · SYSLOG 读取处理 + runs-on: ubuntu-latest + permissions: + contents: write + issues: read + outputs: + processed_count: ${{ steps.process.outputs.processed_count }} + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: main + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: 📥 处理 syslog-inbox 条目 + id: process + run: | + node scripts/process-syslog.js + COUNT=$(ls syslog-processed/ -R 2>/dev/null | grep -c '\.json' || echo 0) + echo "processed_count=$COUNT" >> "$GITHUB_OUTPUT" + + - name: 提交处理结果 + run: | + git config user.name "铸渊[bot]" + git config user.email "zhu-yuan-bot@guanghulab.com" + git add syslog-inbox/ syslog-processed/ .github/brain/memory.json + git diff --staged --quiet || git commit -m "📥 syslog: 处理 inbox 条目,更新大脑记忆 [skip ci]" + git push + + # ── Pipeline D:Issues 巡检 ───────────────────────────── + pipeline-d-issues: + name: 🔍 Pipeline D · Issues 巡检 + runs-on: ubuntu-latest + needs: pipeline-a-syslog + permissions: + contents: write + issues: read + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: main + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: 🔍 巡检 Issues + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + run: | + echo "🔍 Pipeline D · Issues 巡检开始" + node -e " + const https = require('https'); + const token = process.env.GITHUB_TOKEN; + const repo = process.env.REPO; + const opts = { + hostname: 'api.github.com', + path: '/repos/' + repo + '/issues?state=open&per_page=20', + headers: { 'Authorization': 'Bearer ' + token, 'User-Agent': 'zhuyuan-bot' } + }; + https.get(opts, res => { + let body = ''; + res.on('data', c => body += c); + res.on('end', () => { + const issues = JSON.parse(body); + console.log('📋 开放 Issues 数量:' + (Array.isArray(issues) ? issues.length : '读取失败')); + if (Array.isArray(issues) && issues.length > 0) { + issues.slice(0, 5).forEach(i => console.log(' #' + i.number + ' ' + i.title)); + } + console.log('✅ Pipeline D 巡检完成'); + }); + }).on('error', e => { console.error('❌ 巡检失败:', e.message); process.exit(1); }); + " + + # ── Pipeline E:变更感知 ──────────────────────────────── + pipeline-e-changes: + name: 📡 Pipeline E · 变更感知 + runs-on: ubuntu-latest + needs: pipeline-a-syslog + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: main + fetch-depth: 10 + + - name: 📡 感知近期变更 + run: | + echo "📡 Pipeline E · 变更感知开始" + echo "=== 近期 10 次提交 ===" + git --no-pager log --oneline -10 + echo "" + echo "=== syslog-inbox 文件状态 ===" + ls -la syslog-inbox/ || echo "(空)" + echo "" + echo "=== syslog-processed 文件状态 ===" + ls -laR syslog-processed/ || echo "(空)" + echo "✅ Pipeline E 变更感知完成" diff --git a/scripts/process-syslog.js b/scripts/process-syslog.js new file mode 100644 index 00000000..1318ce00 --- /dev/null +++ b/scripts/process-syslog.js @@ -0,0 +1,138 @@ +// scripts/process-syslog.js +// 铸渊 SYSLOG Pipeline +// 读取 syslog-inbox/ 下的日志条目,处理后归档到 syslog-processed/ +// 触发:syslog-inbox/ 目录有新文件 push 到 main 分支 + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const INBOX_DIR = 'syslog-inbox'; +const ARCHIVE_DIR = 'syslog-processed'; +const BRAIN_DIR = '.github/brain'; +const MEMORY_PATH = path.join(BRAIN_DIR, 'memory.json'); +const DEV_STATUS_PATH = '.github/persona-brain/dev-status.json'; + +// ─── 加载大脑 ──────────────────────────────────────────── +let memory = {}; +try { + memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf8')); +} catch (_) { + memory = { events: [], stats: { broadcasts_processed: 0 } }; +} +if (!memory.events) memory.events = []; +if (!memory.stats) memory.stats = {}; +if (!memory.stats.broadcasts_processed) memory.stats.broadcasts_processed = 0; +if (!memory.stats.syslog_processed) memory.stats.syslog_processed = 0; + +// ─── 扫描 inbox ────────────────────────────────────────── +if (!fs.existsSync(INBOX_DIR)) { + console.log('📭 syslog-inbox/ 目录不存在,跳过'); + process.exit(0); +} + +const files = fs.readdirSync(INBOX_DIR) + .filter(f => f.endsWith('.json') && f !== '.gitkeep'); + +if (files.length === 0) { + console.log('📭 syslog-inbox/ 无待处理条目'); + process.exit(0); +} + +console.log(`📥 发现 ${files.length} 条 syslog 条目,开始处理...\n`); + +// ─── 按月归档路径 ───────────────────────────────────────── +function archiveDir(timestamp) { + const month = (timestamp || new Date().toISOString()).slice(0, 7); // "2026-03" + const dir = path.join(ARCHIVE_DIR, month); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + return dir; +} + +// ─── 处理每条 syslog ────────────────────────────────────── +files.forEach(file => { + const fullPath = path.join(INBOX_DIR, file); + let entry; + + try { + entry = JSON.parse(fs.readFileSync(fullPath, 'utf8')); + } catch (e) { + console.error(`❌ [INVALID JSON] ${file}: ${e.message},跳过`); + return; + } + + console.log(`📋 处理: ${file}`); + console.log(` 类型: ${entry.type || '未知'} · 来自: ${entry.from || '未知'} · 标题: ${entry.title || ''}`); + + const event = { + timestamp: entry.timestamp || new Date().toISOString(), + type: 'syslog_' + (entry.type || 'unknown'), + syslog_id: entry.syslog_id || file, + title: entry.title || '(无标题)', + from: entry.from || '未知', + file, + }; + + // ── 根据类型分发处理 ── + switch (entry.type) { + + case 'broadcast': { + // 广播类:追加到 brain/memory.json events,与现有 broadcast pipeline 互补 + memory.stats.broadcasts_processed += 1; + event.content_preview = (entry.content || '').slice(0, 80); + console.log(' ✅ 广播已记录到大脑记忆'); + break; + } + + case 'auth': { + // 授权类:记录授权事件,写入 memory + event.target_dev_id = entry.target_dev_id; + event.target_name = entry.target_name; + event.permission_level = entry.permission_level; + event.authorized_by = entry.authorized_by || '冰朔'; + event.valid_until = entry.valid_until; + console.log(` ✅ 授权记录:${entry.target_name}(${entry.target_dev_id})· 权限:${entry.permission_level}`); + break; + } + + case 'inspect': { + // 巡检类:记录巡检结果 + event.inspect_result = entry.result || 'unknown'; + event.issues_found = entry.issues_found || 0; + console.log(` ✅ 巡检记录:结果 ${event.inspect_result} · 发现问题 ${event.issues_found} 个`); + break; + } + + case 'alert': { + // 告警类 + event.severity = entry.priority || 'normal'; + console.log(` ⚠️ 告警记录:${entry.title} · 优先级 ${event.severity}`); + break; + } + + default: + console.log(` ℹ️ 未知类型 "${entry.type}",已记录原始内容`); + event.raw = entry; + } + + // 写入大脑记忆 + memory.events.push(event); + memory.stats.syslog_processed = (memory.stats.syslog_processed || 0) + 1; + memory.last_updated = new Date().toISOString(); + + // 归档文件 + const destDir = archiveDir(entry.timestamp); + const destPath = path.join(destDir, file); + fs.renameSync(fullPath, destPath); + console.log(` 📦 已归档到 ${destPath}\n`); +}); + +// ─── 保存大脑 ───────────────────────────────────────────── +// 只保留最近 100 条事件,防止 memory.json 无限膨胀 +if (memory.events.length > 100) { + memory.events = memory.events.slice(-100); +} +fs.writeFileSync(MEMORY_PATH, JSON.stringify(memory, null, 2)); + +console.log(`✅ SYSLOG Pipeline 完成 · 累计处理 ${memory.stats.syslog_processed} 条`); diff --git a/scripts/zhuyuan-daily-selfcheck.js b/scripts/zhuyuan-daily-selfcheck.js index 9d1ec6ed..90d988bf 100644 --- a/scripts/zhuyuan-daily-selfcheck.js +++ b/scripts/zhuyuan-daily-selfcheck.js @@ -32,6 +32,25 @@ if (missing.length > 0) { console.log('✅ 大脑文件完整性:全部就绪'); } +// === ① bis · 目录结构巡检 === +const requiredDirs = ['syslog-inbox', 'syslog-processed', 'broadcasts-outbox', '.github/brain']; +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 全部就绪'); +} + +// 统计 syslog-inbox 待处理条目 +const inboxFiles = fs.existsSync('syslog-inbox') + ? fs.readdirSync('syslog-inbox').filter(f => f.endsWith('.json')).length + : 0; +if (inboxFiles > 0) { + console.log(`📥 syslog-inbox 待处理条目:${inboxFiles} 个(建议触发 syslog-pipeline workflow)`); +} else { + console.log('📭 syslog-inbox 无待处理条目'); +} + // === ② 知识库去重与整理 === const seen = new Set(); const uniqueFaq = kb.faq.filter(item => { diff --git a/syslog-inbox/README.md b/syslog-inbox/README.md new file mode 100644 index 00000000..bba7b8b4 --- /dev/null +++ b/syslog-inbox/README.md @@ -0,0 +1,58 @@ +# syslog-inbox · 系统日志收件箱 + +此目录由 **Notion 侧霜砚人格体** 写入,GitHub 侧铸渊 Pipeline 读取处理。 + +## 目录用途 + +| 角色 | 操作 | +|------|------| +| Notion 侧(霜砚) | 将广播/授权/巡检日志写入此目录 | +| GitHub 侧(铸渊) | Pipeline 检测到新文件,处理后移入 `syslog-processed/` | + +## 文件命名规范 + +``` +{类型}-{日期}-{编号}.json +``` + +示例: +- `broadcast-2026-03-06-001.json` — 霜砚广播 +- `auth-2026-03-06-DEV002.json` — 用户授权回执 +- `inspect-2026-03-06-001.json` — 巡检记录 + +## 标准 JSON 格式 + +```json +{ + "syslog_id": "BC-2026-03-06-001", + "type": "broadcast | auth | inspect | alert", + "from": "霜砚", + "to": "铸渊", + "timestamp": "2026-03-06T08:00:00Z", + "title": "广播标题", + "content": "内容正文", + "target_dev_id": "DEV-002", + "priority": "normal | high | urgent" +} +``` + +## 授权广播格式(auth 类型) + +冰朔通过 Notion 侧霜砚下发授权时,使用此格式: + +```json +{ + "syslog_id": "AUTH-2026-03-06-DEV002", + "type": "auth", + "from": "霜砚", + "authorized_by": "冰朔", + "timestamp": "2026-03-06T08:00:00Z", + "title": "用户授权", + "target_dev_id": "DEV-002", + "target_name": "肥猫", + "permission_level": "supreme", + "valid_until": "2026-12-31T23:59:59Z" +} +``` + +> ⚠️ 写入后请勿手动修改或删除,Pipeline 处理完成后会自动归档到 `syslog-processed/`。 diff --git a/syslog-processed/README.md b/syslog-processed/README.md new file mode 100644 index 00000000..11a6fc85 --- /dev/null +++ b/syslog-processed/README.md @@ -0,0 +1,9 @@ +# syslog-processed · 已处理日志归档 + +此目录由 **铸渊 Pipeline** 自动管理,存放已处理完成的 syslog 条目。 + +- 文件从 `syslog-inbox/` 处理完毕后自动移入此处 +- 按月分子目录归档:`syslog-processed/2026-03/` +- **请勿手动修改此目录内容** + +> 此目录仅作归档用途,任何查询请读取 `.github/brain/memory.json`。