diff --git a/.github/brain/growth-log.md b/.github/brain/growth-log.md new file mode 100644 index 00000000..0ac21016 --- /dev/null +++ b/.github/brain/growth-log.md @@ -0,0 +1,16 @@ +# 铸渊成长日记 + +## 2026-03-05 · 初始激活 + +铸渊(Zhùyuān)正式上线。 + +核心大脑初始化完成: +- `routing-map.json` — HLI 路由映射表已加载(17 个接口注册在案) +- `memory.json` — 统计记忆系统已启动 +- CI/CD 流水线已配置(契约校验 + 冒烟测试) +- Brain Sync 广播系统已就绪 +- 每日自检 cron 已配置(08:00 UTC) + +当前 HLI 覆盖率:3/17(AUTH 域已实现) + +铸渊的使命:守护代码,执行规则,连接 Notion 与 GitHub。 diff --git a/.github/brain/memory.json b/.github/brain/memory.json new file mode 100644 index 00000000..b7056bee --- /dev/null +++ b/.github/brain/memory.json @@ -0,0 +1,33 @@ +{ + "identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体", + "rules_version": "v1.0", + "last_updated": "2026-03-05T10:30:57.157Z", + "stats": { + "ci_runs": 1, + "pr_reviews": 0, + "broadcasts_processed": 0, + "coverage": { + "implemented": 3, + "total": 17, + "percent": "17.6%" + } + }, + "events": [ + { + "timestamp": "2026-03-05T10:30:46.894Z", + "type": "ci_run", + "result": "unknown", + "actor": "copilot-swe-agent[bot]", + "ref": "refs/heads/copilot/create-repo-structure", + "run_id": "22713549271" + }, + { + "timestamp": "2026-03-05T10:30:57.156Z", + "broadcast_file": "test-broadcast.json", + "title": "测试广播:新增 NOTIFICATION 域", + "from": "霜砚", + "update_target": "routing-map", + "added_domain": "NOTIFICATION" + } + ] +} diff --git a/.github/brain/routing-map.json b/.github/brain/routing-map.json new file mode 100644 index 00000000..9d3551f7 --- /dev/null +++ b/.github/brain/routing-map.json @@ -0,0 +1,134 @@ +{ + "version": "v1.0", + "last_updated": "2026-03-05", + "updated_by": "init", + "domains": { + "AUTH": { + "module": "M01", + "route_prefix": "/hli/auth", + "interfaces": [ + { + "id": "HLI-AUTH-001", + "path": "/hli/auth/login", + "status": "implemented" + }, + { + "id": "HLI-AUTH-002", + "path": "/hli/auth/register", + "status": "implemented" + }, + { + "id": "HLI-AUTH-003", + "path": "/hli/auth/verify", + "status": "implemented" + } + ] + }, + "PERSONA": { + "module": "M03", + "route_prefix": "/hli/persona", + "interfaces": [ + { + "id": "HLI-PERSONA-001", + "path": "/hli/persona/load", + "status": "pending" + }, + { + "id": "HLI-PERSONA-002", + "path": "/hli/persona/switch", + "status": "pending" + } + ] + }, + "USER": { + "module": "M05", + "route_prefix": "/hli/user", + "interfaces": [ + { + "id": "HLI-USER-001", + "path": "/hli/user/profile", + "status": "pending" + }, + { + "id": "HLI-USER-002", + "path": "/hli/user/profile/update", + "status": "pending" + } + ] + }, + "TICKET": { + "module": "M06", + "route_prefix": "/hli/ticket", + "interfaces": [ + { + "id": "HLI-TICKET-001", + "path": "/hli/ticket/create", + "status": "pending" + }, + { + "id": "HLI-TICKET-002", + "path": "/hli/ticket/query", + "status": "pending" + }, + { + "id": "HLI-TICKET-003", + "path": "/hli/ticket/status", + "status": "pending" + } + ] + }, + "DIALOGUE": { + "module": "M07", + "route_prefix": "/hli/dialogue", + "interfaces": [ + { + "id": "HLI-DIALOGUE-001", + "path": "/hli/dialogue/send", + "status": "pending" + }, + { + "id": "HLI-DIALOGUE-002", + "path": "/hli/dialogue/stream", + "status": "pending" + }, + { + "id": "HLI-DIALOGUE-003", + "path": "/hli/dialogue/history", + "status": "pending" + } + ] + }, + "STORAGE": { + "module": "M10", + "route_prefix": "/hli/storage", + "interfaces": [ + { + "id": "HLI-STORAGE-001", + "path": "/hli/storage/upload", + "status": "pending" + }, + { + "id": "HLI-STORAGE-002", + "path": "/hli/storage/download", + "status": "pending" + } + ] + }, + "DASHBOARD": { + "module": "M12", + "route_prefix": "/hli/dashboard", + "interfaces": [ + { + "id": "HLI-DASHBOARD-001", + "path": "/hli/dashboard/status", + "status": "pending" + }, + { + "id": "HLI-DASHBOARD-002", + "path": "/hli/dashboard/realtime", + "status": "pending" + } + ] + } + } +} diff --git a/.github/broadcasts/example-broadcast.json b/.github/broadcasts/example-broadcast.json new file mode 100644 index 00000000..5c037752 --- /dev/null +++ b/.github/broadcasts/example-broadcast.json @@ -0,0 +1,16 @@ +{ + "title": "示例广播:新增 NOTIFICATION 业务域", + "from": "霜砚", + "date": "2026-03-05", + "rules_version": "v1.1", + "update_target": "routing-map", + "data": { + "NOTIFICATION": { + "module": "M09", + "route_prefix": "/hli/notification", + "interfaces": [ + { "id": "HLI-NOTIF-001", "path": "/hli/notification/push", "status": "pending" } + ] + } + } +} diff --git a/.github/workflows/brain-sync.yml b/.github/workflows/brain-sync.yml new file mode 100644 index 00000000..3a6966aa --- /dev/null +++ b/.github/workflows/brain-sync.yml @@ -0,0 +1,83 @@ +name: 铸渊 Brain Sync + +on: + push: + branches: [main, dev] + paths: + - '.github/broadcasts/**' + schedule: + # 每天 08:00 UTC 自检 + - cron: '0 8 * * *' + workflow_dispatch: + +jobs: + process-broadcasts: + name: 📡 广播接收处理 + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Process broadcasts + run: node scripts/process-broadcasts.js + + - name: Commit updated brain state + run: | + git config user.name "铸渊[bot]" + git config user.email "zhu-yuan-bot@guanghulab.com" + git add .github/brain/ .github/broadcasts/processed/ .github/broadcasts/failed/ .github/copilot-instructions.md 2>/dev/null; true + git diff --staged --quiet || git commit -m "🧠 brain: 处理广播,更新路由映射和记忆 [skip ci]" + git push + + daily-check: + name: 🔍 铸渊每日自检 + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run daily self-check + run: node scripts/daily-check.js + + - name: Update memory after daily check + env: + MEMORY_EVENT_TYPE: daily_check + MEMORY_EVENT_RESULT: passed + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_REF: ${{ github.ref }} + GITHUB_RUN_ID: ${{ github.run_id }} + run: node scripts/update-memory.js + + - name: Commit memory update + run: | + git config user.name "铸渊[bot]" + git config user.email "zhu-yuan-bot@guanghulab.com" + git add .github/brain/memory.json || true + git diff --staged --quiet || git commit -m "🧠 memory: 每日自检记录 [skip ci]" + git push diff --git a/.github/workflows/hli-contract-check.yml b/.github/workflows/hli-contract-check.yml index d00226b8..8ba61528 100644 --- a/.github/workflows/hli-contract-check.yml +++ b/.github/workflows/hli-contract-check.yml @@ -18,9 +18,12 @@ jobs: name: 🔍 接口契约校验 runs-on: ubuntu-latest permissions: - contents: read + contents: write + pull-requests: write steps: - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Node.js uses: actions/setup-node@v4 @@ -32,12 +35,92 @@ jobs: run: npm ci - name: Run HLI schema validation - run: npm run test:contract + id: contract + run: | + set +e + OUTPUT=$(npm run test:contract 2>&1) + EXIT_CODE=$? + echo "output<> $GITHUB_OUTPUT + echo "$OUTPUT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT + exit $EXIT_CODE env: HLI_REGISTRY_MODE: strict - name: Run route-schema alignment check - run: npm run test:route-align + id: align + if: always() + run: | + set +e + OUTPUT=$(npm run test:route-align 2>&1) + EXIT_CODE=$? + echo "output<> $GITHUB_OUTPUT + echo "$OUTPUT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT + exit 0 + + - name: Post PR review comment + if: always() && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const contractResult = '${{ steps.contract.outputs.exit_code }}' === '0' ? '✅ 通过' : '❌ 失败'; + const contractOutput = `${{ steps.contract.outputs.output }}`; + const alignOutput = `${{ steps.align.outputs.output }}`; + + const body = [ + '## ⚒️ 铸渊 · HLI 契约审核报告', + '', + `**契约校验**: ${contractResult}`, + '', + '
', + '📋 契约校验详情', + '', + '```', + contractOutput, + '```', + '
', + '', + '
', + '📊 路由对齐报告', + '', + '```', + alignOutput, + '```', + '
', + '', + `> 审核时间: ${new Date().toISOString()}`, + ].join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + + - name: Update memory on CI completion + if: always() + env: + MEMORY_EVENT_TYPE: ci_run + MEMORY_EVENT_RESULT: ${{ steps.contract.outputs.exit_code == '0' && 'passed' || 'failed' }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_REF: ${{ github.ref }} + GITHUB_RUN_ID: ${{ github.run_id }} + MEMORY_PR_NUMBER: ${{ github.event.pull_request.number }} + run: node scripts/update-memory.js + + - name: Commit memory update + if: always() && github.event_name != 'pull_request' + run: | + git config user.name "铸渊[bot]" + git config user.email "zhu-yuan-bot@guanghulab.com" + git add .github/brain/memory.json 2>/dev/null; true + git diff --staged --quiet || git commit -m "🧠 memory: CI 记录更新 [skip ci]" + git push api-smoke: name: 🚀 接口冒烟测试 diff --git a/package.json b/package.json index 87b083a0..a56c069e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,10 @@ "test:contract": "node scripts/contract-check.js", "test:route-align": "node scripts/route-align-check.js", "test:smoke": "jest --config jest.smoke.config.js --forceExit", - "start:test": "NODE_ENV=test node src/index.js" + "start:test": "NODE_ENV=test node src/index.js", + "brain:sync": "node scripts/process-broadcasts.js", + "brain:daily-check": "node scripts/daily-check.js", + "brain:update-memory": "node scripts/update-memory.js" }, "dependencies": { "next": "15.3.2", diff --git a/scripts/daily-check.js b/scripts/daily-check.js new file mode 100644 index 00000000..fa755541 --- /dev/null +++ b/scripts/daily-check.js @@ -0,0 +1,128 @@ +// scripts/daily-check.js +// 铸渊每日自检脚本 +// 用途:检查大脑完整性 + 输出覆盖率报告 + +const fs = require('fs'); +const path = require('path'); + +const ROUTING_MAP_PATH = path.join(__dirname, '../.github/brain/routing-map.json'); +const MEMORY_PATH = path.join(__dirname, '../.github/brain/memory.json'); +const SCHEMA_DIR = path.join(__dirname, '../src/schemas/hli'); + +const errors = []; +const warnings = []; + +// ── 1. 大脑文件完整性检查 ───────────────────────────────────────────────── +function checkFileIntegrity() { + [ROUTING_MAP_PATH, MEMORY_PATH].forEach(filePath => { + if (!fs.existsSync(filePath)) { + errors.push(`❌ [MISSING] 大脑文件缺失: ${path.basename(filePath)}`); + return; + } + try { + JSON.parse(fs.readFileSync(filePath, 'utf8')); + } catch (e) { + errors.push(`❌ [CORRUPT] 大脑文件损坏: ${path.basename(filePath)} → ${e.message}`); + } + }); +} + +// ── 2. HLI 覆盖率报告 ──────────────────────────────────────────────────── +function checkCoverage() { + const routingMap = JSON.parse(fs.readFileSync(ROUTING_MAP_PATH, 'utf8')); + let total = 0; + let implemented = 0; + let pending = 0; + + console.log('\n📊 HLI 接口覆盖率报告:\n'); + + Object.entries(routingMap.domains).forEach(([domain, domainData]) => { + const domainTotal = domainData.interfaces.length; + const domainImpl = domainData.interfaces.filter(i => i.status === 'implemented').length; + total += domainTotal; + implemented += domainImpl; + pending += domainTotal - domainImpl; + + const icon = domainImpl === domainTotal ? '✅' : domainImpl > 0 ? '🔶' : '⬜'; + console.log(` ${icon} ${domain.padEnd(12)} ${domainImpl}/${domainTotal}`); + + domainData.interfaces.forEach(iface => { + const statusIcon = iface.status === 'implemented' ? ' ✓' : ' ○'; + console.log(` ${statusIcon} ${iface.id.padEnd(20)} ${iface.path}`); + }); + }); + + const percent = total > 0 ? ((implemented / total) * 100).toFixed(1) : 0; + console.log(`\n 📈 总覆盖率: ${implemented}/${total} (${percent}%)`); + console.log(` ✅ 已实现: ${implemented} · ⏳ 待实现: ${pending}\n`); + + if (implemented < total) { + warnings.push(`⚠️ 覆盖率 ${percent}% (${implemented}/${total}) — 仍有 ${pending} 个接口待实现`); + } + + // 检查 schema 文件与 routing-map 的一致性 + Object.entries(routingMap.domains).forEach(([domain, domainData]) => { + domainData.interfaces.forEach(iface => { + if (iface.status !== 'implemented') return; + const schemaDir = path.join(SCHEMA_DIR, domain.toLowerCase()); + if (!fs.existsSync(schemaDir)) { + errors.push(`❌ [MISSING DIR] ${domain} schema 目录不存在`); + return; + } + const rawAction = iface.path.split('/').filter(Boolean).pop(); + const action = rawAction ? rawAction.replace(/[?#].*$/, '') : null; + if (!action) { + errors.push(`❌ [INVALID PATH] ${iface.id} 路径格式异常: ${iface.path}`); + return; + } + const schemaFile = path.join(schemaDir, `${action}.schema.json`); + if (!fs.existsSync(schemaFile)) { + errors.push(`❌ [MISSING SCHEMA] ${iface.id} 标记为 implemented 但缺少 schema: ${schemaFile}`); + } + }); + }); +} + +// ── 3. memory.json 健康检查 ──────────────────────────────────────────────── +function checkMemory() { + const memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf8')); + const required = ['identity', 'rules_version', 'stats', 'events']; + required.forEach(field => { + if (memory[field] === undefined) { + errors.push(`❌ [MEMORY] memory.json 缺少字段: ${field}`); + } + }); + console.log(`🧠 铸渊记忆状态:`); + console.log(` 规则版本: ${memory.rules_version}`); + console.log(` CI 运行次数: ${memory.stats?.ci_runs ?? 0}`); + console.log(` PR 审核次数: ${memory.stats?.pr_reviews ?? 0}`); + console.log(` 广播处理次数: ${memory.stats?.broadcasts_processed ?? 0}`); + console.log(` 最近更新: ${memory.last_updated || '未知'}\n`); +} + +// ── 主流程 ──────────────────────────────────────────────────────────────── +console.log('═══════════════════════════════════════════'); +console.log('⚒️ 铸渊每日自检报告'); +console.log(` 时间: ${new Date().toISOString()}`); +console.log('═══════════════════════════════════════════\n'); + +checkFileIntegrity(); + +if (errors.length === 0) { + checkMemory(); + checkCoverage(); +} + +if (warnings.length > 0) { + console.log('⚠️ 警告:\n'); + warnings.forEach(w => console.log(' ' + w)); + console.log(); +} + +if (errors.length > 0) { + console.error('🚫 自检发现严重错误:\n'); + errors.forEach(e => console.error(' ' + e)); + process.exit(1); +} else { + console.log('✅ 铸渊自检通过 — 大脑状态正常'); +} diff --git a/scripts/process-broadcasts.js b/scripts/process-broadcasts.js new file mode 100644 index 00000000..0cb4ec3a --- /dev/null +++ b/scripts/process-broadcasts.js @@ -0,0 +1,128 @@ +// scripts/process-broadcasts.js +// 铸渊大脑同步脚本 +// 用途:读取 .github/broadcasts/ 下的广播 JSON,更新 routing-map.json 和 memory.json + +const fs = require('fs'); +const path = require('path'); + +const BROADCASTS_DIR = path.join(__dirname, '../.github/broadcasts'); +const ROUTING_MAP_PATH = path.join(__dirname, '../.github/brain/routing-map.json'); +const MEMORY_PATH = path.join(__dirname, '../.github/brain/memory.json'); +const COPILOT_INSTRUCTIONS_PATH = path.join(__dirname, '../.github/copilot-instructions.md'); + +// 加载当前大脑状态 +const routingMap = JSON.parse(fs.readFileSync(ROUTING_MAP_PATH, 'utf8')); +const memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf8')); + +const processedDir = path.join(BROADCASTS_DIR, 'processed'); +if (!fs.existsSync(processedDir)) { + fs.mkdirSync(processedDir, { recursive: true }); +} + +// 读取所有待处理广播(排除 processed 子目录和示例文件) +const broadcasts = fs.readdirSync(BROADCASTS_DIR) + .filter(f => f.endsWith('.json') && f !== 'example-broadcast.json') + .map(f => ({ file: f, fullPath: path.join(BROADCASTS_DIR, f) })); + +if (broadcasts.length === 0) { + console.log('📭 没有待处理的广播。'); + process.exit(0); +} + +console.log(`📡 发现 ${broadcasts.length} 个待处理广播...\n`); + +const failedDir = path.join(BROADCASTS_DIR, 'failed'); + +broadcasts.forEach(({ file, fullPath }) => { + let broadcast; + try { + broadcast = JSON.parse(fs.readFileSync(fullPath, 'utf8')); + } catch (e) { + console.error(`❌ [PARSE ERROR] ${file}: ${e.message}`); + // 归档失败广播并记录到 memory + if (!fs.existsSync(failedDir)) fs.mkdirSync(failedDir, { recursive: true }); + fs.renameSync(fullPath, path.join(failedDir, file)); + memory.events.push({ + timestamp: new Date().toISOString(), + broadcast_file: file, + type: 'broadcast_parse_error', + error: e.message, + }); + return; + } + + console.log(`📬 处理广播: ${broadcast.title || file}`); + console.log(` 来源: ${broadcast.from || '未知'} · 日期: ${broadcast.date || '未知'}`); + + const event = { + timestamp: new Date().toISOString(), + broadcast_file: file, + title: broadcast.title || file, + from: broadcast.from || '未知', + update_target: broadcast.update_target, + }; + + // 根据 update_target 分发处理 + if (broadcast.update_target === 'routing-map' && broadcast.data) { + Object.entries(broadcast.data).forEach(([domain, domainData]) => { + routingMap.domains[domain] = domainData; + console.log(` ✅ 已更新域: ${domain}`); + event.added_domain = domain; + }); + routingMap.version = broadcast.rules_version || routingMap.version; + routingMap.last_updated = broadcast.date || new Date().toISOString().split('T')[0]; + routingMap.updated_by = broadcast.from || 'broadcast'; + + } else if (broadcast.update_target === 'copilot-instructions' && broadcast.content) { + // 追加到 copilot-instructions.md(防止重复写入) + const existing = fs.readFileSync(COPILOT_INSTRUCTIONS_PATH, 'utf8'); + const marker = ``; + if (existing.includes(marker)) { + console.log(' ⏭️ copilot-instructions.md 已包含此广播,跳过'); + } else { + fs.writeFileSync(COPILOT_INSTRUCTIONS_PATH, existing + `\n\n${marker}\n${broadcast.content}`, 'utf8'); + console.log(' ✅ 已更新 copilot-instructions.md'); + } + + } else if (broadcast.update_target === 'growth-log' && broadcast.content) { + // 追加到成长日记 + const growthLogPath = path.join(__dirname, '../.github/brain/growth-log.md'); + const entry = `\n\n## ${broadcast.date} · ${broadcast.title}\n\n${broadcast.content}\n`; + fs.appendFileSync(growthLogPath, entry, 'utf8'); + console.log(' ✅ 已追加成长日记'); + + } else { + console.log(` ⚠️ 未知的 update_target: ${broadcast.update_target},已跳过`); + event.skipped = true; + } + + memory.events.push(event); + memory.stats.broadcasts_processed += 1; + + // 归档广播文件 + fs.renameSync(fullPath, path.join(processedDir, file)); + console.log(` 📦 广播已归档到 broadcasts/processed/${file}\n`); +}); + +// 重新计算覆盖率 +let implemented = 0; +let total = 0; +Object.values(routingMap.domains).forEach(domain => { + domain.interfaces.forEach(iface => { + total++; + if (iface.status === 'implemented') implemented++; + }); +}); +memory.stats.coverage = { + implemented, + total, + percent: total > 0 ? `${((implemented / total) * 100).toFixed(1)}%` : '0%', +}; + +memory.last_updated = new Date().toISOString(); + +// 写回文件 +fs.writeFileSync(ROUTING_MAP_PATH, JSON.stringify(routingMap, null, 2) + '\n', 'utf8'); +fs.writeFileSync(MEMORY_PATH, JSON.stringify(memory, null, 2) + '\n', 'utf8'); + +console.log(`✅ 铸渊大脑同步完成。覆盖率: ${implemented}/${total} (${memory.stats.coverage.percent})`); diff --git a/scripts/update-memory.js b/scripts/update-memory.js new file mode 100644 index 00000000..8cf4d924 --- /dev/null +++ b/scripts/update-memory.js @@ -0,0 +1,46 @@ +// scripts/update-memory.js +// 用途:CI 完成后更新 memory.json 统计数据和事件日志 + +const fs = require('fs'); +const path = require('path'); + +const MEMORY_PATH = path.join(__dirname, '../.github/brain/memory.json'); + +const memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf8')); + +const eventType = process.env.MEMORY_EVENT_TYPE || 'ci_run'; +const result = process.env.MEMORY_EVENT_RESULT || 'unknown'; +const prNumber = process.env.MEMORY_PR_NUMBER || null; +const ref = process.env.GITHUB_REF || ''; +const actor = process.env.GITHUB_ACTOR || 'unknown'; +const runId = process.env.GITHUB_RUN_ID || null; + +const event = { + timestamp: new Date().toISOString(), + type: eventType, + result, + actor, + ref, +}; + +if (prNumber) event.pr_number = prNumber; +if (runId) event.run_id = runId; + +// 更新计数器 +if (eventType === 'ci_run') { + memory.stats.ci_runs = (memory.stats.ci_runs || 0) + 1; +} else if (eventType === 'pr_review') { + memory.stats.pr_reviews = (memory.stats.pr_reviews || 0) + 1; +} + +// 保留最近 100 条事件 +memory.events.push(event); +if (memory.events.length > 100) { + memory.events = memory.events.slice(-100); +} + +memory.last_updated = new Date().toISOString(); + +fs.writeFileSync(MEMORY_PATH, JSON.stringify(memory, null, 2) + '\n', 'utf8'); + +console.log(`✅ memory.json 已更新 · 事件: ${eventType}(${result}) · CI总次数: ${memory.stats.ci_runs} · PR审核次数: ${memory.stats.pr_reviews}`);