bookworm-smart-assistant/scripts/patches/patch-w3-slow-log-alert.js

118 lines
4.2 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* W3 补丁 2026-04-16 audit-2026-04-16 WARNING
*
* 问题: stop-dispatcher.js L172 Stop 事件 (>2s) 静默写入 hook-slow.log
* stderr 输出Claude Code 界面无法感知log-rotator 已轮转 (P3+)
* 但用户不知道问题存在
*
* 修复方案:
* (a) appendFileSync 后追加 process.stderr.write 告警
* (b) 基于文件 mtime 60s 冷却窗口防刷屏
* (c) 冷却 sentinel 路径: debug/.slow-alert.sentinel
*
* 冷却逻辑:
* - sentinel mtime now - mtime < 60000ms 则只写文件不输出 stderr
* - 否则输出 stderr + 更新 sentinel mtime
* - 文件不存在视为冷却已过
*
* 文件: hooks/stop-dispatcher.js (L168-179 )
*
* 幂等: sentinel = 'slow-alert.sentinel'
*/
'use strict';
const fs = require('fs');
const path = require('path');
const TARGET = path.join(__dirname, '..', '..', 'hooks', 'stop-dispatcher.js');
const SENTINEL = 'slow-alert.sentinel';
function main() {
if (!fs.existsSync(TARGET)) {
console.error('[patch-w3] 目标文件不存在');
process.exit(1);
}
const before = fs.readFileSync(TARGET, 'utf8');
if (before.includes(SENTINEL)) {
console.log('[patch-w3] 已打过补丁,跳过');
process.exit(0);
}
const anchor =
' // P2.1 总耗时记录: runAll 超过 2s 视为慢 Stop写 slow.log 辅助诊断\n' +
' try {\n' +
' const _total = Date.now() - _perf_start;\n' +
' if (_total > 2000) {\n' +
' const errLog = path.join(require(\'./lib/root.js\'), \'debug\', \'hook-slow.log\');\n' +
' fs.appendFileSync(errLog, JSON.stringify({\n' +
' ts: new Date().toISOString(),\n' +
' hook: \'stop-dispatcher\',\n' +
' totalMs: _total,\n' +
' }) + \'\\n\');\n' +
' }\n' +
' } catch {}';
const replacement =
' // P2.1 总耗时记录: runAll 超过 2s 视为慢 Stop写 slow.log 辅助诊断\n' +
' // W3 (2026-04-16): 追加 stderr 告警 + 60s 冷却窗口防刷屏\n' +
' try {\n' +
' const _total = Date.now() - _perf_start;\n' +
' if (_total > 2000) {\n' +
' const debugDir = path.join(require(\'./lib/root.js\'), \'debug\');\n' +
' const errLog = path.join(debugDir, \'hook-slow.log\');\n' +
' fs.appendFileSync(errLog, JSON.stringify({\n' +
' ts: new Date().toISOString(),\n' +
' hook: \'stop-dispatcher\',\n' +
' totalMs: _total,\n' +
' }) + \'\\n\');\n' +
' // W3: 冷却聚合 — 60s 内仅告警 1 次防刷屏\n' +
' try {\n' +
' const alertSentinel = path.join(debugDir, \'.slow-alert.sentinel\');\n' +
' const now = Date.now();\n' +
' let lastAlert = 0;\n' +
' try {\n' +
' if (fs.existsSync(alertSentinel)) lastAlert = fs.statSync(alertSentinel).mtimeMs || 0;\n' +
' } catch {}\n' +
' if (now - lastAlert > 60000) {\n' +
' process.stderr.write(\'[stop-dispatcher] 慢 Stop 事件 (\' + _total + \'ms) — 详见 debug/hook-slow.log\\n\');\n' +
' try {\n' +
' const _tmp = alertSentinel + \'.tmp.\' + process.pid;\n' +
' fs.writeFileSync(_tmp, String(now));\n' +
' fs.renameSync(_tmp, alertSentinel);\n' +
' } catch {}\n' +
' }\n' +
' } catch {}\n' +
' }\n' +
' } catch {}';
if (!before.includes(anchor)) {
console.error('[patch-w3] 锚点未匹配');
process.exit(2);
}
const after = before.replace(anchor, replacement);
if (after === before) {
console.error('[patch-w3] 替换无效果');
process.exit(3);
}
const backup = TARGET + '.bak.w3.' + Date.now();
fs.writeFileSync(backup, before);
const _tmp = TARGET + '.tmp.' + process.pid;
fs.writeFileSync(_tmp, after);
fs.renameSync(_tmp, TARGET);
console.log('[patch-w3] ✓ stop-dispatcher.js 慢 Stop 告警通道已加装');
console.log('[patch-w3] 备份: ' + path.basename(backup));
console.log('[patch-w3] 冷却窗口: 60s, sentinel = debug/.slow-alert.sentinel');
}
try {
main();
} catch (e) {
console.error('[patch-w3] 异常:', e.message);
process.exit(99);
}