#!/usr/bin/env node /** * patch-x07-x08-x09-crlf-fix.js * 统一修复 X07/X08/X09,使用 CRLF 规范化后匹配 */ 'use strict'; const fs = require('fs'); const path = require('path'); const HOOKS_DIR = path.join(__dirname, '..', '..', 'hooks'); let total = 0; function patchFile(filePath, label, patches) { if (!fs.existsSync(filePath)) { console.log('SKIP ' + label + ': not found'); return; } let src = fs.readFileSync(filePath, 'utf8'); const hadCRLF = src.includes('\r\n'); let norm = src.replace(/\r\n/g, '\n'); for (const p of patches) { if (norm.includes(p.sentinel)) { console.log('SKIP ' + label + '/' + p.id + ': already patched'); continue; } if (!norm.includes(p.old)) { console.error('FAIL ' + label + '/' + p.id + ': old pattern not found'); process.exit(1); } norm = norm.replace(p.old, p.new); console.log('OK ' + p.id + ' applied'); total++; } if (hadCRLF) norm = norm.replace(/\n/g, '\r\n'); const tmp = filePath + '.tmp.' + process.pid; fs.writeFileSync(tmp, norm, 'utf8'); fs.renameSync(tmp, filePath); } // === X07: agent-isolation-gate.js seq 步长 === patchFile(path.join(HOOKS_DIR, 'agent-isolation-gate.js'), 'agent-isolation-gate', [{ id: 'X07', sentinel: '[PATCH-X07-SEQ-STEP]', old: ` const seqMatch = c.match(/\\bseq\\s+(\\d+)(?:\\s+(\\d+))?(?:\\s+(\\d+))?/); if (seqMatch) { const a = parseInt(seqMatch[1], 10); const b = seqMatch[2] ? parseInt(seqMatch[2], 10) : null; const z = seqMatch[3] ? parseInt(seqMatch[3], 10) : (b !== null ? b : a); const n = b !== null && z !== null ? Math.max(0, z - a + 1) : a; if (n >= 6) return { rule: 'B2', detail: 'seq ' + n }; }`, new: ` const seqMatch = c.match(/\\bseq\\s+(\\d+)(?:\\s+(\\d+))?(?:\\s+(\\d+))?/); // [PATCH-X07-SEQ-STEP] if (seqMatch) { let n; const g1 = parseInt(seqMatch[1], 10); const g2 = seqMatch[2] ? parseInt(seqMatch[2], 10) : null; const g3 = seqMatch[3] ? parseInt(seqMatch[3], 10) : null; if (g3 !== null) { const step = Math.max(1, g2); n = Math.max(0, Math.floor((g3 - g1) / step) + 1); } else if (g2 !== null) { n = Math.max(0, g2 - g1 + 1); } else { n = g1; } if (n >= 6) return { rule: 'B2', detail: 'seq ' + n }; }` }]); // === X08: session-heartbeat.js 原子写 === patchFile(path.join(HOOKS_DIR, 'session-heartbeat.js'), 'session-heartbeat', [{ id: 'X08-hb', sentinel: '[PATCH-X08-ATOMIC-WRITE]', old: " fs.writeFileSync(STATE_FILE, JSON.stringify(allState), 'utf8');", new: ` const _tmpHb = STATE_FILE + '.tmp.' + process.pid; // [PATCH-X08-ATOMIC-WRITE] fs.writeFileSync(_tmpHb, JSON.stringify(allState), 'utf8'); fs.renameSync(_tmpHb, STATE_FILE);` }]); // === X08b: pre-compact-handoff.js heartbeat 原子写 === patchFile(path.join(HOOKS_DIR, 'pre-compact-handoff.js'), 'pre-compact-handoff', [{ id: 'X08-pch', sentinel: '[PATCH-X08-ATOMIC-WRITE]', old: " fs.writeFileSync(heartbeatPath, JSON.stringify(hbAll), 'utf8');", new: ` const _tmpPch = heartbeatPath + '.tmp.' + process.pid; // [PATCH-X08-ATOMIC-WRITE] fs.writeFileSync(_tmpPch, JSON.stringify(hbAll), 'utf8'); fs.renameSync(_tmpPch, heartbeatPath);` }]); // === X09: context-pressure-monitor.js CJK 范围 === patchFile(path.join(HOOKS_DIR, 'context-pressure-monitor.js'), 'context-pressure-monitor', [{ id: 'X09', sentinel: '[PATCH-X09-CJK-KOREAN]', old: " if (b >= 0xE3 && b <= 0xE9) cjk += 3;", new: " if (b >= 0xE4 && b <= 0xED) cjk += 3; // [PATCH-X09-CJK-KOREAN]" }]); console.log('DONE: ' + total + ' patches applied');