首页
/ ECC TypeScript/JavaScript Hooks 规则:在 Agent 编辑 JS/TS 文件后自动格式化、类型检查与 console.log 审计

ECC TypeScript/JavaScript Hooks 规则:在 Agent 编辑 JS/TS 文件后自动格式化、类型检查与 console.log 审计

2026-09-06 12:35:20作者:舒璇辛Bertina

本文以 ECC 仓库中的 .cursor/rules/typescript-hooks.md 规则文件为主体,完整解读它为 TypeScript/JavaScript 项目定义的 Hook 策略:在每次编辑文件之后(PostToolUse)执行 Prettier 自动格式化、tsc 类型检查与 console.log 警告,在会话结束前(Stop)对所有修改过的文件做最终的 console.log 审计。读完本文,你将理解这套规则在 ECC 中如何被 .cursor/hooks.json 注册、如何通过 .cursor/hooks/adapter.js 适配到 Claude Code 风格的 hook 协议,以及底层 scripts/hooks 目录下各检查脚本的具体实现逻辑与容错设计。

一、规则文件定位:按 glob 精确生效的 TypeScript 扩展规则

.cursor/rules/typescript-hooks.md 是一个 Cursor 规则(rules)文件,其 frontmatter 决定了两件事——在什么文件上生效、是否总是注入:

---
description: "TypeScript hooks extending common rules"
globs: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"]
alwaysApply: false
---
  • globs 限定了规则只在 TS/TSX/JS/JSX 文件被操作时加载,覆盖典型的前端与 Node.js 项目文件类型;
  • alwaysApply: false 表示该规则不会常驻上下文,只在文件模式命中时按需生效,这是一种控制 token 开销的写法。

文件开头明确说明其定位是"在通用 hooks 规则的基础上扩展 TypeScript/JavaScript 专属内容"。与之配合的是通用规则 .cursor/rules/common-hooks.md,它定义了 Hook 体系的三个基本类型,是本规则文件的"父集":

Hook 类型 触发时机 典型用途
PreToolUse 工具执行前 参数校验、参数修改、拦截危险操作
PostToolUse 工具执行后 自动格式化、质量检查
Stop 会话(响应)结束时 最终验证与收尾审计

typescript-hooks.md 的全部规范内容即围绕 PostToolUse 与 Stop 两类展开,这正是它在通用规则之上"只补差异"的设计意图:通用层定义框架,语言专属层只声明该语言需要哪些具体检查。

二、PostToolUse Hooks:编辑文件后的三道即时防线

规则文件为 PostToolUse 阶段声明了三项配置(文档指出配置入口为 ~/.claude/settings.json):

  1. Prettier:编辑 JS/TS 文件后自动格式化;
  2. TypeScript check:编辑 .ts/.tsx 文件后运行 tsc 做类型检查;
  3. console.log warning:对被编辑文件中的 console.log 发出警告。

这三项并非纸面约定,在仓库中有完整对应的实现链路。

2.1 事件注册:afterFileEdit 钩子

.cursor/hooks.jsonafterFileEdit 事件绑定到 .cursor/hooks/after-file-edit.js,其描述直接对应规则文件中的三条能力:"Auto-format, TypeScript check, console.log warning, and frontend design-quality reminder"。

2.2 适配层:Cursor 事件如何落到共享脚本

.cursor/hooks/after-file-edit.js 本身是一个极薄的分发器:它通过 adapter.js 读取 stdin 中的 Cursor 事件 JSON,转换为 Claude Code 风格的 hook 输入(tool_input.file_path 等字段),然后依次委托给三个共享脚本:

// 累积被编辑路径,供 Stop 时批量 format + typecheck
runExistingHook('post-edit-accumulator.js', claudeStr);
// 对刚编辑的文件做 console.log 即时警告
runExistingHook('post-edit-console-warn.js', claudeStr);
// 前端设计质量提醒(standard/strict profile 下启用)
if (hookEnabled('post:edit:design-quality-check', ['standard', 'strict'])) {
  runExistingHook('design-quality-check.js', claudeStr);
}

.cursor/hooks/adapter.js 中的 transformToClaude 负责字段映射(path/file/args.filePath 归一到 tool_input.file_path),runExistingHook 通过 execFileSync 以 15 秒超时调用 scripts/hooks/ 下的实际脚本,并且会在子进程返回退出码 2 时向上传播该码——这是 hook 协议中"阻断当前操作"的约定。

2.3 console.log 即时警告的实现

scripts/hooks/post-edit-console-warn.js 是规则中"console.log warning"的直接实现。它对编辑路径做扩展名过滤(\.(ts|tsx|js|jsx)$),命中后逐行扫描文件内容,输出带行号的警告:

const matches = content
  .split('\n')
  .map((line, index) => ({ line, index }))
  .filter(item => /console\.log/.test(item.line))
  .map(item => `${item.index + 1}: ${item.line.trim()}`);

if (matches.length > 0) {
  warnings.push(`[Hook] WARNING: console.log found in ${filePath}`);
  warnings.push(...matches.slice(0, 5)); // 最多展示前 5 处
  warnings.push('[Hook] Remove console.log before committing');
}

值得注意的两个细节:警告写入 stderr、原始 stdin JSON 原样回写 stdout(保持 hook 链路透传约定),且任何输入异常都静默降级为 pass-through——保证检查失败永远不会阻塞编辑操作。

2.4 "批量延迟"策略:格式化和 tsc 为何不在每次编辑后跑

规则文档写的是"编辑后自动 Prettier、编辑 .ts/.tsx 后运行 tsc",但实现上采取了延迟批量策略,其动机在 scripts/hooks/post-edit-accumulator.js 的头注释中说明得很清楚:每次编辑都跑格式化 + 类型检查会产生可观的延迟,因此该脚本只负责把编辑过的 JS/TS 路径按行追加写入一个会话级临时文件($TMPDIR/ecc-edited-<sessionId>.txt),由 Stop 阶段的 stop-format-typecheck.js 一次性对全部文件执行 format + typecheck。

从源码结构看,这里的工程考量包括:

  • 使用 appendFileSync 追加写入,使并发 hook 进程互不覆盖;
  • 会话 ID 取自 CLAUDE_SESSION_ID,缺失时退化为 cwd 的 SHA1 前 12 位,并对 ID 做字符清洗(replace(/[^a-zA-Z0-9_-]/g, '_'))防止路径注入;
  • 同时处理 Edit/Write(单个 file_path)与 MultiEditedits 数组)两种工具输入;
  • 去重被刻意推迟到 Stop 阶段完成。

这也解释了 hooks/hooks.json 中 Claude Code 侧 Stop 事件里 stop:format-typecheck 条目的描述:"Batch format (Biome/Prettier) and typecheck (tsc) all JS/TS files edited this response — runs once at Stop instead of after every Edit",其超时被放宽到 300 秒,因为批量 tsc 在大型项目中耗时较长。

三、Stop Hooks:会话结束前的 console.log 全量审计

规则文件的第二段要求"在会话结束前检查所有被修改文件的 console.log"。这一能力的注册与实现如下:

3.1 事件注册与 profile 门控

.cursor/hooks.jsonstop 事件绑定到 .cursor/hooks/stop.js,描述为 "Console.log audit on all modified files"。stop.js 在分发前通过 hookEnabled 做 profile 门控:stop:check-console-log 仅在 standard/strict profile 下运行。

hookEnabled 的判定逻辑(见 adapter.js)由两个环境变量控制:

环境变量 取值 作用
ECC_HOOK_PROFILE minimal / standard / strict(默认 standard,非法值回退 standard 决定哪些 profile 允许该 hook 运行
ECC_DISABLED_HOOKS 逗号分隔的 hook ID 列表 显式禁用指定 hook,优先级最高

这意味着用户可以按项目阶段整体降档(如 minimal 只保留最核心的会话生命周期钩子),或单独关闭某一项检查,而无需改动任何 hook 配置文件。

3.2 审计脚本:只查"工作区实际改过的文件"

scripts/hooks/check-console-log.js 是 Stop 审计的实现,其工作方式与编辑后的即时警告形成互补:

  • 数据来源不是 hook 事件里的单个文件,而是 getGitModifiedFiles(['\\.tsx?$', '\\.jsx?$']),即通过 git 获取当前工作区所有已修改的 JS/TS 文件——这正对应规则文档中"Check all modified files"的语义;
  • 内置排除规则 EXCLUDED_PATTERNS,测试文件(*.test.**.spec.*__tests__/__mocks__/)、配置文件(*.config.*)以及 scripts/ 目录下的文件不触发警告,因为"console.log 在这些地方往往是有意为之";
  • 命中时输出两条日志:WARNING: console.log found in <file> 与收尾提示 Remove console.log statements before committing
  • 非 git 仓库直接跳过检查。

容错设计同样值得一提:脚本对 stdin 设置 1MB 上限(MAX_STDIN),若输入被截断则不回传 stdin 数据——注释说明这是为避免半截 JSON 被 harness 判定为 Stop hook 校验失败(对应 issue #2090);任何异常都走 fail-open,最终 passThroughAndExit 保证 hook 永远不会阻断会话结束。

3.3 两级检查的分工

综合源码可以看到,console.log 治理实际上是一个两级漏斗:

  1. 编辑后即时层(PostToolUse → post-edit-console-warn.js):只看刚编辑的那一个文件,输出精确行号,最多展示 5 处命中,帮助 Agent/开发者在编码当下立即清理;
  2. 会话收尾层(Stop → check-console-log.js):基于 git 修改清单做全量兜底审计,覆盖即时层可能漏掉的文件(例如通过 shell 命令修改的文件),并以测试/配置目录白名单降低误报。

四、与 Claude Code 侧配置的对应关系

ECC 的 hook 体系是"一套底层脚本、多端注册"的结构:Cursor 侧通过 .cursor/hooks.json + .cursor/hooks 薄适配器注册,Claude Code 侧则通过 hooks/hooks.json 直接以 matcher 匹配工具名注册。以本文主题相关的检查为例,Claude 侧的对应条目包括:

  • PostToolUsepost:dispatcher:sync / post:dispatcher:async:将同步与后台 PostToolUse 检查收敛到 scripts/hooks/posttooluse-dispatcher.js 单进程内分发,同步通道 30 秒、异步通道 45 秒超时;
  • Stopstop:check-console-log(调用 check-console-log.js)、stop:format-typecheck(调用 stop-format-typecheck.js,300 秒超时)、stop:session-endstop:evaluate-sessionstop:cost-tracker 等,均带 minimal,standard,strictstandard,strict 的 profile 门控;
  • 此外 PreToolUse 中还有 pre:config-protection 等互补机制——阻断 Agent 修改 linter/formatter 配置文件,"引导 Agent 去修代码而不是削弱配置",与本文的自动格式化 hook 构成一攻一防的组合。

需要说明的适用前提:.cursor/ 目录下的 hook 依赖 Cursor 的 hooks 能力加载 .cursor/hooks.json;而规则文件本身(frontmatter + 自然语言)主要服务于 Cursor 的规则注入场景。~/.claude/settings.json 是 Claude Code 的配置入口,ECC 仓库提供的 hooks/hooks.json(其 $schema 指向 claude-code-settings)即面向该入口的等价实现。两端事件命名不同(如 afterFileEdit vs PostToolUse),但委托的底层脚本是同一批,行为保持一致。

五、要点总结与实践参考

.cursor/rules/typescript-hooks.md 这一份不到 20 行的规则文件出发,结合仓库源码可以提炼出 ECC 处理"Agent 写 TS/JS 代码"的完整质量闭环:

阶段 能力 注册位置 底层实现
PostToolUse 编辑后 console.log 行级警告 .cursor/hooks.json afterFileEdit scripts/hooks/post-edit-console-warn.js
PostToolUse 累积编辑路径(供批量处理) 同上 scripts/hooks/post-edit-accumulator.js
Stop 批量 Prettier/Biome 格式化 + tsc 类型检查 hooks/hooks.json stop:format-typecheck scripts/hooks/stop-format-typecheck.js
Stop 全量修改文件 console.log 审计 两端 Stop 事件 scripts/hooks/check-console-log.js

其设计取舍对搭建自有 Agent hook 体系有直接参考价值:高频轻检查(正则扫描)放在编辑后即时执行并给出行号;重检查(tsc)通过路径累积文件延迟到 Stop 批量执行以消除逐次延迟;所有检查脚本统一 1MB stdin 上限、fail-open 容错与"原样回传 stdin"的链路透传约定;检查项通过 ECC_HOOK_PROFILEECC_DISABLED_HOOKS 环境变量实现 profile 降档与按 ID 的精细禁用;敏感目录(测试、脚本、配置)以白名单排除误报。这些机制均可在 scripts/hooks 目录下逐文件查证,测试用例位于 teststests/hooks 对应文件中,可作为行为验证的入口。

登录后查看全文
热门项目推荐
相关项目推荐