guanghulab/scripts/generate-session-summary.js

146 lines
5.0 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
// scripts/generate-session-summary.js
// 铸渊 · 会话摘要生成器
//
// 在 Notion Agent 巡检前 10 分钟自动运行,生成 latest-summary.json
// 供 Notion 侧「铸渊·桥接巡检引擎」读取
//
// 用法node scripts/generate-session-summary.js
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const TELEMETRY_DIR = path.join(ROOT, 'persona-telemetry');
const SUMMARY_FILE = path.join(TELEMETRY_DIR, 'latest-summary.json');
const SYSLOG_INBOX = path.join(ROOT, 'syslog-inbox');
const DEV_STATUS = path.join(ROOT, '.github', 'persona-brain', 'dev-status.json');
const STYLE_CONFIG = path.join(ROOT, '.github', 'persona-brain', 'style-config.json');
const MEMORY_FILE = path.join(ROOT, '.github', 'persona-brain', 'memory.json');
const TUNING_QUEUE = path.join(TELEMETRY_DIR, 'tuning-queue');
// ══════════════════════════════════════════════════════════
// 辅助函数
// ══════════════════════════════════════════════════════════
function safeReadJSON(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch {
return null;
}
}
function countFiles(dir, ext) {
if (!fs.existsSync(dir)) return 0;
return fs.readdirSync(dir).filter(f => {
if (ext) return f.endsWith(ext);
return !f.startsWith('.') && f !== 'README.md';
}).length;
}
function countPendingTuningOrders() {
if (!fs.existsSync(TUNING_QUEUE)) return 0;
return fs.readdirSync(TUNING_QUEUE).filter(f =>
f.startsWith('TUNE-') && f.endsWith('.json')
).length;
}
// ══════════════════════════════════════════════════════════
// 主流程
// ══════════════════════════════════════════════════════════
function generateSummary() {
const now = new Date().toISOString();
// 读取开发者状态
const devStatus = safeReadJSON(DEV_STATUS);
const activeDevs = devStatus?.team_status
?.filter(d => d.status && d.status.includes('🟢'))
.map(d => d.dev_id) || [];
// 读取记忆文件
const memory = safeReadJSON(MEMORY_FILE);
// 读取风格配置
const styleConfig = safeReadJSON(STYLE_CONFIG);
// 统计未处理 SYSLOG
const syslogCount = countFiles(SYSLOG_INBOX, '.json');
// 统计待处理微调工单
const pendingOrders = countPendingTuningOrders();
// 构建摘要
const summary = {
version: '1.0',
timestamp: now,
sessions: {
total_24h: 0,
active_devs: activeDevs,
total_messages: 0,
avg_session_length_min: 0,
},
persona_state: {
active_persona: '铸渊',
style_profile: '通感语言·守护者',
style_drift_score: 0.0,
memory_depth: (memory?.total_selfchecks || 0) + ' selfchecks',
last_brain_update: memory?.last_updated || now,
},
dev_progress: {
syslog_submitted: syslogCount,
modules_uploaded: 0,
issues_raised: 0,
},
tuning_status: {
pending_orders: pendingOrders,
last_completed: null,
next_scheduled: pendingOrders > 0 ? 'next-patrol' : null,
},
repo_health: {
hli_coverage: memory?.hli_coverage || '17.6%',
total_team_members: devStatus?.team_status?.length || 0,
green_status_count: activeDevs.length,
},
alerts: [],
};
// 检查是否有开发者连续 72 小时无提交(告警)
// 此处为占位逻辑,实际需要 git log 分析
if (devStatus?.team_status) {
const blocked = devStatus.team_status.filter(d =>
d.status && d.status.includes('🔴')
);
blocked.forEach(d => {
summary.alerts.push({
type: 'dev_blocked',
dev_id: d.dev_id,
message: d.name + ' 当前状态为阻塞',
});
});
}
// 确保目录存在
if (!fs.existsSync(TELEMETRY_DIR)) {
fs.mkdirSync(TELEMETRY_DIR, { recursive: true });
}
// 写入文件
fs.writeFileSync(SUMMARY_FILE, JSON.stringify(summary, null, 2) + '\n', 'utf8');
console.log('✅ 会话摘要已生成:', SUMMARY_FILE);
console.log(' 时间:', now);
console.log(' 活跃开发者:', activeDevs.length);
console.log(' 待处理 SYSLOG:', syslogCount);
console.log(' 待处理微调工单:', pendingOrders);
console.log(' 告警数:', summary.alerts.length);
}
// ══════════════════════════════════════════════════════════
// 入口
// ══════════════════════════════════════════════════════════
generateSummary();