bookworm-smart-assistant/hooks/edit-precheck-dispatcher.js

111 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* PreToolUse Hook: Edit/Write 预检派遣器 (v6.2 M2)
* Matcher: Write|Edit
*
* 合并 2 个 PreToolUse 钩子为单进程,减少 ~100ms 串行开销:
* 1. block-sensitive-files (fail-close, 安全优先)
* 2. constitution-precheck (fail-close, ERROR 级规则)
*
* 读取 stdin 一次,依次调用两个子模块的导出函数。
* 退出码: 0=放行 | 2=阻断(deny/ask)
*/
'use strict';
const path = require('path');
const readStdin = require('./lib/read-stdin.js');
function main() {
readStdin({ maxSize: 1024 * 1024 }).then(input => {
// --- 阶段 1: block-sensitive-files (安全优先, fail-close) ---
// 优先加载 block-sensitive-files.js; 若 disabled (.bak) 则尝试 .bak; 都不存在则跳过
try {
let bsf = null;
try { bsf = require('./block-sensitive-files.js'); } catch {}
if (!bsf || !bsf.checkFile) {
try { bsf = require('./block-sensitive-files.js.bak'); } catch {}
}
if (bsf && bsf.checkFile) {
const result = bsf.checkFile(input);
if (result) {
process.stderr.write(JSON.stringify({
hookSpecificOutput: { permissionDecision: result.decision },
systemMessage: result.message,
}));
process.exit(2);
return;
}
}
// 模块不存在或无 checkFile 导出 → 视为 disabled跳过
} catch (e) {
// block-sensitive-files 加载失败 → fail-close: ask
process.stderr.write(JSON.stringify({
hookSpecificOutput: { permissionDecision: 'ask' },
systemMessage: `[安全防护] block-sensitive-files 模块加载失败(${e.message}),请用户确认是否继续。`,
}));
process.exit(2);
return;
}
// --- 阶段 2: constitution-precheck (ERROR 级违规拦截) ---
try {
const { isEnabled } = require('../scripts/feature-flags.js');
if (!isEnabled('constitution-precheck')) {
process.exit(0);
return;
}
} catch {}
try {
const cp = require('./constitution-precheck.js');
if (cp && cp.detectViolation && cp.extractContent && cp.CODE_EXTENSIONS) {
const fp = input.tool_input?.file_path || '';
if (fp && cp.CODE_EXTENSIONS.test(fp)) {
const content = cp.extractContent(input.tool_input);
if (content) {
const v = cp.detectViolation(content);
if (v) {
// 记录安全事件
try {
const { logSecurityEvent } = require('./lib/security-log.js');
logSecurityEvent('deny', 'constitution-precheck', v.id, fp);
} catch {}
process.stderr.write(JSON.stringify({
hookSpecificOutput: { permissionDecision: 'deny' },
systemMessage: [
'[constitution-precheck] ERROR 级违规,已阻断写入',
`文件: ${path.basename(fp)}, 行: ${v.line}`,
`规则: [${v.id}] ${v.label}`,
'请修复后重试。参考: constitution/AI-CONSTITUTION.md 第十一章',
].join('\n'),
}));
process.exit(2);
return;
}
}
}
}
} catch {}
// 全部通过,放行
process.exit(0);
}).catch((e) => {
// Fail-close: stdin 解析异常时 ask
process.stderr.write(JSON.stringify({
hookSpecificOutput: { permissionDecision: 'ask' },
systemMessage: `[edit-precheck-dispatcher] 预检异常(${e.message}),请用户确认是否继续。`,
}));
process.exit(2);
});
}
if (typeof module !== 'undefined') {
module.exports = { main };
}
if (require.main === module) {
main();
}