#!/usr/bin/env node /** * patch-p1-fast-cache-lib.js * * P1.5: 创建 hooks/lib/fast-cache.js * * 启动性能优化: 通过 mtime 签名缓存合并多次 readFileSync。 * 对齐 OpenClaw entry.version-fast-path + module.enableCompileCache 思路。 * * Bookworm 的 hooks 每次冷启动都读 stats-compiled / settings / route-stats / * SESSION_LOCK / STATE_FILE,2 次 statSync + 缓存命中可省 15-30ms/次。 */ 'use strict'; const fs = require('fs'); const path = require('path'); const TARGET = path.join(__dirname, '..', '..', 'hooks', 'lib', 'fast-cache.js'); const SENTINEL = 'P1-FAST-CACHE-V1'; const CONTENT = `'use strict'; /** * fast-cache.js — 启动期热数据快路径缓存 (${SENTINEL}) * * 通过 mtime 签名: 所有源文件未变 → 直接返回上次缓存。 * 仅缓存只读字段子集,避免缓存整个大 JSON 文件。 * * 借鉴: OpenClaw entry.version-fast-path.ts (零模块加载快退出) * * Usage: * const { readFastCache } = require('./lib/fast-cache.js'); * const cache = readFastCache() || {}; * const skillCount = cache.skillCount || 0; */ const fs = require('fs'); const path = require('path'); const ROOT = path.join(__dirname, '..', '..'); const CACHE_FILE = path.join(ROOT, 'debug', '.hook-fast-cache.json'); const SOURCES = [ { file: path.join(ROOT, 'stats-compiled.json'), fields: ['summary', 'version'] }, { file: path.join(ROOT, 'settings.json'), fields: ['mcpServers'] }, ]; function readFastCache() { try { const mtimes = SOURCES.map(function(s) { try { return fs.statSync(s.file).mtimeMs; } catch (_) { return 0; } }); const sig = mtimes.join(':'); if (fs.existsSync(CACHE_FILE)) { try { const cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')); if (cache && cache._sig === sig) return cache; } catch (_) { /* malformed cache, rebuild */ } } const rebuilt = { _sig: sig, _builtAt: Date.now() }; for (let i = 0; i < SOURCES.length; i++) { const { file, fields } = SOURCES[i]; try { const data = JSON.parse(fs.readFileSync(file, 'utf8')); for (let j = 0; j < fields.length; j++) { rebuilt[fields[j]] = data[fields[j]]; } } catch (_) { /* missing file ok */ } } rebuilt.mcpCount = Object.keys(rebuilt.mcpServers || {}).length; rebuilt.skillCount = (rebuilt.summary || {}).skills || 0; rebuilt.hookCount = (rebuilt.summary || {}).hooksRegistered || (rebuilt.summary || {}).hooks || 0; rebuilt.agentCount = (rebuilt.summary || {}).agents || 0; // 异步写回 (不阻塞主流程) setImmediate(function() { try { const cacheDir = path.dirname(CACHE_FILE); if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true }); const tmp = CACHE_FILE + '.tmp.' + process.pid; fs.writeFileSync(tmp, JSON.stringify(rebuilt)); fs.renameSync(tmp, CACHE_FILE); } catch (_) { /* best effort */ } }); return rebuilt; } catch (_) { return null; } } function enableCompileCacheBestEffort() { try { const mod = require('node:module'); if (mod.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) { mod.enableCompileCache(); return true; } } catch (_) { /* unsupported */ } return false; } module.exports = { readFastCache, enableCompileCacheBestEffort, __sentinel: '${SENTINEL}', }; `; function main() { const dir = path.dirname(TARGET); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); if (fs.existsSync(TARGET)) { const cur = fs.readFileSync(TARGET, 'utf8'); if (cur.includes(SENTINEL)) { process.stdout.write('[SKIP] already deployed\n'); process.exit(0); } const ts = new Date().toISOString().replace(/[:.]/g, '-'); fs.copyFileSync(TARGET, TARGET + '.bak.' + ts); process.stdout.write('[BACKUP] ' + TARGET + '.bak.' + ts + '\n'); } const tmpPath = TARGET + '.tmp.' + process.pid; fs.writeFileSync(tmpPath, CONTENT); try { delete require.cache[require.resolve(tmpPath)]; const mod = require(tmpPath); // 自检 1: readFastCache 工作 const c = mod.readFastCache(); if (c === null) throw new Error('readFastCache returned null'); if (typeof c.skillCount !== 'number') throw new Error('skillCount not number'); // 自检 2: enableCompileCache 不抛 const enabled = mod.enableCompileCacheBestEffort(); process.stdout.write(' compile cache: ' + (enabled ? 'ENABLED' : 'unavailable') + '\n'); fs.renameSync(tmpPath, TARGET); process.stdout.write('[OK] hooks/lib/fast-cache.js deployed\n'); process.stdout.write(' skills=' + c.skillCount + ' hooks=' + c.hookCount + ' mcp=' + c.mcpCount + '\n'); } catch (e) { fs.unlinkSync(tmpPath); process.stderr.write('[ERROR] self-test failed: ' + e.message + '\n'); process.exit(1); } } if (require.main === module) main();