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

124 lines
4.2 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* PreToolUse:Bash 合并调度器
* 合并 block-dangerous-commands + code-quality-gate + commit-message-lint
* 减少 3 Node.js 进程启动为 1 (节省 ~80-100ms)
*
* 优先级:
* 1. block-dangerous-commands (安全, fail-close)
* 2. code-quality-gate (构建追踪, fail-open)
* 3. commit-message-lint (commit 规范, fail-open)
*
* 退出码: 0=放行, 2=阻断/ask (stderr 输出 JSON)
*/
const readStdin = require('./lib/read-stdin.js');
readStdin({ maxSize: 1024 * 1024 }).then(input => {
const command = (input.tool_input && input.tool_input.command) || '';
if (!command) {
process.exit(0);
return;
}
// ─── 1. block-dangerous-commands (最高优先级, fail-close) ───
// 安全检查: 异常时 ask (fail-closed)
try {
const bdc = require('./block-dangerous-commands.js');
if (bdc.checkCommand) {
const result = bdc.checkCommand(command, input);
if (result) {
// B5: CLAUDE_SAFETY_MODE=strict 时 ask 升级为 deny
let decision = result.decision; // 'deny' 或 'ask'
if (decision === 'ask' && process.env.CLAUDE_SAFETY_MODE === 'strict') {
decision = 'deny';
result.message += '\n(CLAUDE_SAFETY_MODE=strict: ask 已升级为 deny)';
}
const output = {
hookSpecificOutput: {
permissionDecision: decision,
},
systemMessage: result.message,
};
process.stderr.write(JSON.stringify(output));
process.exit(2);
return;
}
}
} catch (e) {
// fail-closed: 安全模块异常时请求用户确认 (strict 模式下直接 deny)
const failDecision = process.env.CLAUDE_SAFETY_MODE === 'strict' ? 'deny' : 'ask';
const failSuffix = failDecision === 'deny' ? '\n(CLAUDE_SAFETY_MODE=strict: 异常时直接拒绝)' : '';
process.stderr.write(JSON.stringify({
hookSpecificOutput: { permissionDecision: failDecision },
systemMessage: `[安全防护] 命令检查模块异常(${(e.message || '').slice(0, 80)})${failDecision === 'deny' ? '已拒绝执行' : '请用户确认是否执行'}${failSuffix}`,
}));
process.exit(2);
return;
}
// ─── 2. code-quality-gate (构建命令, fail-open) ───
try {
const cqg = require('./code-quality-gate.js');
if (cqg.checkBuild) {
const result = cqg.checkBuild(command, input);
if (result) {
if (result.decision === 'deny') {
// enforce 模式阻断
process.stderr.write(JSON.stringify({
hookSpecificOutput: { permissionDecision: 'deny' },
systemMessage: result.message,
}));
process.exit(2);
return;
}
if (result.decision === 'warn') {
// warn 模式: 放行但附带提醒,继续后续检查
// (warn 消息将与后续检查结果合并)
const output = {
continue: true,
systemMessage: result.message,
};
process.stdout.write(JSON.stringify(output));
process.exit(0);
return;
}
}
}
} catch {
// fail-open: 构建检查异常时静默放行
}
// ─── 3. commit-message-lint (git commit, fail-open) ───
try {
const cml = require('./commit-message-lint.js');
if (cml.checkCommit) {
const result = cml.checkCommit(command, input);
if (result) {
process.stderr.write(JSON.stringify({
hookSpecificOutput: {
permissionDecision: result.decision, // 'ask'
},
systemMessage: result.message,
}));
process.exit(2);
return;
}
}
} catch {
// fail-open: commit-lint 异常时静默放行
}
// 全部通过 → 放行
process.exit(0);
}).catch(() => {
// stdin 读取/解析失败 → fail-closed (strict 模式下升级为 deny)
const isStrict = process.env.CLAUDE_SAFETY_MODE === 'strict';
const decision = isStrict ? 'deny' : 'ask';
process.stderr.write(JSON.stringify({
hookSpecificOutput: { permissionDecision: decision },
systemMessage: `[bash-precheck-dispatcher] stdin 解析异常,${isStrict ? '已拒绝执行' : '请用户确认是否执行'}`,
}));
process.exit(2);
});