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

111 lines
3.7 KiB
JavaScript
Raw Normal View History

#!/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();
}