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

124 lines
4.2 KiB
JavaScript
Raw Permalink 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: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);
});