#!/usr/bin/env node /** * 跨 Hook 会话追踪 (Phase 3) * * 提供共享 traceId,使 3 个 hooks 的事件可以关联分析。 * 所有消费者必须 try/catch 包裹,fail-open。 */ const fs = require('fs'); const { safeAppendJsonl } = require('../hooks/lib/safe-append.js'); const path = require('path'); // ─── 路径解析 ──────────────────────────────────────── let debugDir; try { const { PATHS } = require('./paths.config.js'); debugDir = PATHS.debugDir; } catch { debugDir = path.resolve(__dirname, '..', 'debug'); } const SESSION_TRACE_FILE = path.join(debugDir, 'session-trace.json'); const SESSION_LOCK_FILE = path.join(debugDir, 'session-active.lock'); /** * 获取当前会话的 traceId(同 session 复用) * @returns {{ sessionId: string, traceId: string, startedAt: string, eventCount: number }} */ function getSessionTrace() { if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true }); // 读取当前 sessionId let sessionId = 'unknown'; try { if (fs.existsSync(SESSION_LOCK_FILE)) { const lock = JSON.parse(fs.readFileSync(SESSION_LOCK_FILE, 'utf8')); sessionId = lock.sessionId || lock.id || 'unknown'; } } catch {} // 读取或创建 trace 状态 let trace = null; try { if (fs.existsSync(SESSION_TRACE_FILE)) { trace = JSON.parse(fs.readFileSync(SESSION_TRACE_FILE, 'utf8')); } } catch {} // 同 session 复用 traceId,否则创建新的 if (trace && trace.sessionId === sessionId && sessionId !== 'unknown') { return trace; } // 新 trace const traceId = `t-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; const newTrace = { sessionId, traceId, startedAt: new Date().toISOString(), eventCount: 0, }; try { // W5: 原子写入 (temp+rename) var tmp = SESSION_TRACE_FILE + '.tmp.' + process.pid; fs.writeFileSync(tmp, JSON.stringify(newTrace, null, 2) + '\n'); fs.renameSync(tmp, SESSION_TRACE_FILE); } catch {} return newTrace; } /** * 追加事件到日期分片的 trace 日志 * @param {string} hookName - 来源 hook 名称 * @param {string} eventType - 事件类型 (detection|block-warn|pass|outcome) * @param {Object} data - 附加数据 */ function appendTraceEvent(hookName, eventType, data) { try { if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true }); const trace = getSessionTrace(); const dateStr = new Date().toISOString().slice(0, 10); const logFile = path.join(debugDir, `trace-${dateStr}.jsonl`); const event = { ts: new Date().toISOString(), traceId: trace.traceId, sessionId: trace.sessionId, hook: hookName, event: eventType, data: data || {}, }; safeAppendJsonl(logFile, event); // 更新 eventCount (原子写入) trace.eventCount = (trace.eventCount || 0) + 1; try { var tmp2 = SESSION_TRACE_FILE + '.tmp.' + process.pid; fs.writeFileSync(tmp2, JSON.stringify(trace, null, 2) + '\n'); fs.renameSync(tmp2, SESSION_TRACE_FILE); } catch {} } catch {} } // ─── 导出 ───────────────────────────────────────────── if (typeof module !== 'undefined') { module.exports = { getSessionTrace, appendTraceEvent }; }