#!/usr/bin/env node // patch-x05: session-start-restore.js heartbeat 重置写扁平结构,破坏 X03 多会话隔离 // 修复: 改为 keyed 模式只重置当前 session 'use strict'; const fs = require('fs'); const path = require('path'); const SENTINEL = '// [PATCH-X05-KEYED-RESET]'; const target = path.join(__dirname, '..', '..', 'hooks', 'session-start-restore.js'); if (!fs.existsSync(target)) { process.stdout.write('[SKIP] not found\n'); process.exit(0); } let raw = fs.readFileSync(target, 'utf8'); if (raw.includes(SENTINEL)) { process.stdout.write('[SKIP] already applied\n'); process.exit(0); } const useCRLF = raw.includes('\r\n'); let content = useCRLF ? raw.replace(/\r\n/g, '\n') : raw; const bak = target + '.bak.x05'; if (!fs.existsSync(bak)) fs.writeFileSync(bak, raw); const OLD = ` // 新会话(/clear 或新窗口),重置 heartbeat 计数器 if (fs.existsSync(HEARTBEAT_FILE)) { fs.writeFileSync(HEARTBEAT_FILE, JSON.stringify({ count: 0, lastActivity: Date.now(), notified: [] }), 'utf8'); }`; const NEW = ` // 新会话: 仅重置当前 session 的 heartbeat (X03 keyed 结构) ${SENTINEL} if (fs.existsSync(HEARTBEAT_FILE)) { try { const hbAll = JSON.parse(fs.readFileSync(HEARTBEAT_FILE, 'utf8')); hbAll[sessionId] = { count: 0, lastActivity: Date.now(), notified: [] }; const tmp = HEARTBEAT_FILE + '.tmp.' + process.pid; fs.writeFileSync(tmp, JSON.stringify(hbAll), 'utf8'); fs.renameSync(tmp, HEARTBEAT_FILE); } catch { // 损坏: 初始化为新 keyed 结构 const init = {}; init[sessionId] = { count: 0, lastActivity: Date.now(), notified: [] }; fs.writeFileSync(HEARTBEAT_FILE, JSON.stringify(init), 'utf8'); } }`; if (!content.includes(OLD)) { process.stdout.write('[ERROR] old block not found\n'); process.exit(1); } content = content.replace(OLD, NEW); const final = useCRLF ? content.replace(/\n/g, '\r\n') : content; fs.writeFileSync(target, final, 'utf8'); const v = fs.readFileSync(target, 'utf8'); const ok = v.includes(SENTINEL) && v.includes('hbAll[sessionId]'); process.stdout.write(ok ? '[DONE] patch-x05 applied\n' : '[ERROR] verify failed\n'); process.exit(ok ? 0 : 1);