baodan/.agents/skills/impeccable/scripts/hook.mjs
wsb1224 e3479f0546 上线阻断问题全部修复
#	问题	修复	文件
1	前端构建失败(引号错误)	size="small type=" → size="small" type="	PosterHistoryPage.vue
2	migrate_014 ORM vs 缺失列	全部改为原始 SQL,不再引用 ORM 模型	migrate_014.py
3	cleanup 字段名错误	output_path → ppt_path	cleanup.py
4	文案生成 case 越权	添加 case.user_id != user_id 校验	poster/service.py
5	存储路径未接通持久化卷	全部改用 get_storage_root()(默认 /app/api/storage/insurance)	config.py, ppt/routes.py, poster/service.py, poster/tasks.py
高风险问题修复
#	问题	修复	文件
6	migrate_019 rollback 撤销成功字段	每个 ALTER 后立即 commit,失败只回滚当前语句	migrate_019.py
7	迁移锁 Windows 不兼容 + 句柄未持久化	全局变量保存锁句柄,支持 Windows msvcrt	api/insurance/db/__init__.py
8	PDF 校验异常时放行	异常返回 False(文件损坏)	security.py
9	健康检查始终返回成功	缺少关键资源时返回 503 + missing 列表	poster/routes.py
10	短密钥掩码泄露原值	≤4 字符返回 ****	ppt_admin_service.py
11	设置无键名白名单	添加 _ALLOWED_SETTING_KEYS 白名单	ppt_admin_service.py
12	容器重启任务永久 stuck	添加 recover_stale_tasks() 启动恢复函数	poster/tasks.py, ppt/parse_worker.py
2026-07-27 13:52:09 +08:00

79 lines
2.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Impeccable design hook — PostToolUse + Stop entry point.
*
* Reads the Claude Code / Codex / Cursor hook event from stdin and routes by
* `hook_event_name`:
*
* - PostToolUse: runs the immediate-tier detector rules against the touched
* file and emits a system reminder via
* `hookSpecificOutput.additionalContext` when findings exist.
* - Stop: runs the FULL detector rule set over every UI file touched this
* session (the deep pass), deduped against what the per-edit pass already
* surfaced, and emits once via the Stop additionalContext channel.
*
* Contract: never break a turn. Always exit 0. Clean files emit a small ack
* unless quiet mode is enabled; a clean Stop pass is silent.
*
* Most logic lives in `hook-lib.mjs` so it is unit-testable without a
* subprocess. This file is the thin stdin/stdout adapter.
*/
import { runHook, runStopHook, writeAuditLog } from './hook-lib.mjs';
async function readStdin() {
if (process.stdin.isTTY) return '';
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
return Buffer.concat(chunks).toString('utf-8');
}
function isStopEvent(stdinJson) {
try {
const event = JSON.parse(stdinJson);
return event && typeof event === 'object' && event.hook_event_name === 'Stop';
} catch {
// Malformed stdin falls through to runHook, which audits the skip.
return false;
}
}
async function main() {
// Snapshot the inherited env FIRST so the re-entrancy guard checks the
// parent's value, not the value we are about to export for any child
// processes the hook might ever spawn.
const inheritedEnv = { ...process.env };
process.env.IMPECCABLE_HOOK_DEPTH = process.env.IMPECCABLE_HOOK_DEPTH || '1';
let stdinJson = '';
try { stdinJson = await readStdin(); } catch { /* fall through */ }
const run = isStopEvent(stdinJson) ? runStopHook : runHook;
const result = await run({
stdinJson,
env: inheritedEnv,
cwd: process.cwd(),
});
writeAuditLog(process.env, result.audit, process.cwd());
if (result.stdout) process.stdout.write(result.stdout);
process.exit(result.exitCode || 0);
}
main().catch((err) => {
// Last-ditch: never break the agent's turn even if something we did not
// anticipate goes wrong. Audit-log the failure if logging is enabled.
try {
writeAuditLog(process.env, {
ts: new Date().toISOString(),
event: 'hook-error',
error: String(err && err.message ? err.message : err),
});
} catch { /* swallow */ }
if (process.env.IMPECCABLE_HOOK_DEBUG) {
process.stderr.write(`[impeccable-hook] ${err}\n`);
}
process.exit(0);
});