news 2026/9/29 3:34:42

让 Claude Code 越写越像你:用 Hook 自动积累编码规范的实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
让 Claude Code 越写越像你:用 Hook 自动积累编码规范的实践

1. 为什么 AI 写的代码总差那么点意思

用 Claude Code 写代码有一段时间之后,很多人会碰到同一个别扭的地方:功能实现了,逻辑也没错,但代码读起来就是"不像自己写的"。方法命名的习惯不一样,返回值的处理方式不同,注释的位置也不对。每次让 AI 改代码,都要再花时间把风格对齐,或者干脆忍着。

解决这个问题最直接的办法,是把编码规范写进CLAUDE.md,让 AI 每次都照着来。这个方向没错,但有个现实问题——规范是写不完的。能提前写出来的规范都是大原则,真正让代码"有个人味"的那些东西,是在一次次具体改动中体现出来的。比如你偏好把 null 判断前置而不是层层嵌套,比如你不喜欢在 Service 层里写 if/else 分支,比如你觉得方法名里的 do 前缀是多余的。这些东西事先想不到,只有在被触动的那一刻才知道。

所以真正的问题变成了:怎么把这些"被触动的瞬间"自动记录下来,让它们沉淀成规范,再反过来约束 AI 的输出?这篇就围绕 Claude Code 的 Hook 机制,给出一套可跟做的方案:用 Hook 在工具调用前后自动抓取改动,生成摘要归档,定期提炼进CLAUDE.md,形成一个"越写越像你"的反馈回路。适合已经在用 Claude Code、并且希望团队编码规范能持续沉淀的开发者。

2. 前置准备:Hook 机制与 TaoToken 接入

Claude Code 的 Hook 机制允许在特定事件点执行自定义脚本,这是整套方案的技术基础。常用的事件有四个:UserPromptSubmit(用户提交 prompt 时)、PreToolUse(工具调用前)、PostToolUse(工具调用后)、Stop(对话结束时)。脚本通过 stdin 接收 JSON 数据,包含session_id、tool_input、prompt等字段,正常退出码为 0。

在动手写脚本之前,先把模型调用通道准备好。Hook 里的摘要生成需要调用模型,我用的是 TaoToken 提供的接口,它兼容 Anthropic 的调用格式,配置起来比较省事。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api 。

拿到 API Key 的入口在这里:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。如果你还没配好环境,可以先在模型对话页面确认通道是否正常:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。长期跑编码任务、想让 Hook 稳定工作的,可以看下 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。

环境变量建议这样设置,脚本里直接读:

export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="你的_API_Key"

注意:Hook 脚本里调用模型时,不要硬编码 Key,从环境变量读取,避免把密钥写进会被提交的文件。

3. 可复制配置:settings.json 与四个 Hook 脚本

整套系统由四个脚本组成,分别挂在四个 Hook 事件上,数据流是这样的:UserPromptSubmit检测关键词并初始化追踪轮次,PreToolUse在修改前拍快照,PostToolUse在修改后拍快照,Stop生成 diff、调模型摘要、写文件。

3.1 settings.json 配置骨架

Hook 配置写在~/.claude/settings.json的hooks字段下,支持 matcher 正则过滤触发的工具名:

{ "hooks": { "UserPromptSubmit": [ { "matcher": "", "hooks": [ { "type": "command", "command": "python3 ~/.claude/hooks/opt_track.py" } ] } ], "PreToolUse": [ { "matcher": "Edit|Write|MultiEdit", "hooks": [ { "type": "command", "command": "python3 ~/.claude/hooks/opt_capture_before.py" } ] } ], "PostToolUse": [ { "matcher": "Edit|Write|MultiEdit", "hooks": [ { "type": "command", "command": "python3 ~/.claude/hooks/opt_capture_after.py" } ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": "python3 ~/.claude/hooks/opt_write_record.py" } ] } ] } }

3.2 opt_track.py:关键词触发追踪

并不是每一轮对话都值得记录,只抓"有意识地让 AI 改代码"的时刻,用关键词做过滤:

import json, sys, os, time KEYWORDS = ['优化', '重构', '改进'] def main(): data = json.load(sys.stdin) session_id = data.get('session_id', 'default') prompt = data.get('prompt', '') session_dir = f'/tmp/claude-opt-{session_id}' os.makedirs(session_dir, exist_ok=True) index_file = os.path.join(session_dir, 'index') idx = int(open(index_file).read()) if os.path.exists(index_file) else 0 if any(kw in prompt for kw in KEYWORDS): idx += 1 open(index_file, 'w').write(str(idx)) os.makedirs(os.path.join(session_dir, 'changes', str(idx)), exist_ok=True) with open(os.path.join(session_dir, f'prompt-{idx}.txt'), 'w') as f: f.write(prompt) elif idx > 0: with open(os.path.join(session_dir, f'steps-{idx}.txt'), 'a') as f: f.write(prompt + '\n') sys.exit(0) main()

命中关键词时创建新的追踪轮次,记录 prompt 和当前工作目录;没命中但当前有活跃追踪的,把这条 prompt 追加为"执行步骤",方便后续理解完整上下文。所有中间状态写在/tmp/claude-opt-{session_id}/里,按 session 隔离。

3.3 opt_capture_before.py:修改前快照

每次 AI 调用 Edit/Write/MultiEdit 前,把文件当前内容备份下来:

import json, sys, os, shutil def main(): data = json.load(sys.stdin) session_id = data.get('session_id', 'default') session_dir = f'/tmp/claude-opt-{session_id}' index_file = os.path.join(session_dir, 'index') if not os.path.exists(index_file): sys.exit(0) # 没有活跃追踪,直接放行 idx = open(index_file).read().strip() tool_input = data.get('tool_input', {}) file_path = tool_input.get('file_path') if not file_path or not os.path.exists(file_path): sys.exit(0) safe_name = file_path.replace('/', '_') before = os.path.join(session_dir, 'changes', idx, f'{safe_name}.before') if not os.path.exists(before): # 每个文件每轮只拍一次 shutil.copy2(file_path, before) sys.exit(0) main()

有个细节值得强调:每个文件每轮只拍一次。如果 AI 对同一个文件做了多次修改,只保留最原始的版本,这样生成的 diff 才能反映完整的变化量。

3.4 opt_capture_after.py:修改后快照

每次修改完,把文件内容备份到.after文件,这里是每次都覆盖,保留最终状态:

import json, sys, os, shutil def main(): data = json.load(sys.stdin) session_id = data.get('session_id', 'default') session_dir = f'/tmp/claude-opt-{session_id}' index_file = os.path.join(session_dir, 'index') if not os.path.exists(index_file): sys.exit(0) idx = open(index_file).read().strip() tool_input = data.get('tool_input', {}) file_path = tool_input.get('file_path') if not file_path or not os.path.exists(file_path): sys.exit(0) safe_name = file_path.replace('/', '_') after = os.path.join(session_dir, 'changes', idx, f'{safe_name}.after') shutil.copy2(file_path, after) sys.exit(0) main()

before 和 after 凑成一对,就能算出本轮的完整 diff。

3.5 opt_write_record.py:生成记录

对话结束时触发,做三件事:生成 diff、调模型生成摘要、写入 Markdown 文件。

import json, sys, os, difflib, datetime, urllib.request def gen_diff(before_path, after_path): before = open(before_path).readlines() after = open(after_path).readlines() return ''.join(difflib.unified_diff( before, after, fromfile='修改前', tofile='修改后', n=3)) def summarize(context): payload = json.dumps({ "model": "claude-haiku-4-5", "max_tokens": 300, "messages": [{"role": "user", "content": "以下是一次代码优化会话的内容,请用 2~4 句话总结:" "核心改动是什么,体现了哪些代码设计原则或最佳实践。\n\n" + context + "\n\n只输出总结内容,不要其他说明。"}] }).encode() req = urllib.request.Request( os.environ['ANTHROPIC_BASE_URL'] + '/v1/messages', data=payload, headers={ 'content-type': 'application/json', 'x-api-key': os.environ['ANTHROPIC_API_KEY'], 'anthropic-version': '2023-06-01' }) resp = json.loads(urllib.request.urlopen(req).read()) return resp['content'][0]['text'] def main(): data = json.load(sys.stdin) session_id = data.get('session_id', 'default') session_dir = f'/tmp/claude-opt-{session_id}' index_file = os.path.join(session_dir, 'index') if not os.path.exists(index_file): sys.exit(0) idx = open(index_file).read().strip() changes_dir = os.path.join(session_dir, 'changes', idx) if not os.path.isdir(changes_dir): sys.exit(0) now = datetime.datetime.now() out_dir = os.path.join('代码规范积累', now.strftime('%Y-%m')) os.makedirs(out_dir, exist_ok=True) out_file = os.path.join(out_dir, now.strftime('%Y%m%d-%H%M%S') + '.md') blocks = [] for name in os.listdir(changes_dir): if name.endswith('.before'): base = name[:-7] after = os.path.join(changes_dir, base + '.after') if os.path.exists(after): diff = gen_diff(os.path.join(changes_dir, name), after) blocks.append(f"### 文件:`{base}`\n\n```diff\n{diff}\n```") context = '\n\n'.join(blocks) summary = summarize(context) if context else '本轮无有效改动' with open(out_file, 'w') as f: f.write(f"# 代码优化记录 {now.strftime('%Y-%m-%d %H:%M:%S')}\n\n") f.write(f"## 摘要\n\n{summary}\n\n---\n\n") f.write(context) sys.exit(0) main()

用 Haiku 而不是主模型,是为了省成本——摘要任务不需要很强的模型。归档目录按月份组织:

代码规范积累/ └── 2026-04/ ├── 20260401-143022.md ├── 20260415-090531.md └── 20260428-165847.md

4. 验证请求:一次触发与结果确认

配置好之后,需要验证 Hook 是否真的生效。下面走一遍完整流程。

第一步,确认脚本可执行、路径正确:

chmod +x ~/.claude/hooks/*.py ls -la ~/.claude/hooks/

第二步,在 Claude Code 里提交一个带关键词的 prompt,比如"帮我重构一下 UserService,把 null 判断统一提到方法入口"。这一步会触发UserPromptSubmit,opt_track.py创建追踪轮次。

第三步,让 AI 实际修改文件。此时PreToolUse和PostToolUse会分别拍下 before 和 after 快照。检查临时目录:

ls /tmp/claude-opt-*/changes/1/ # 期望看到类似: # src_main_java_UserService.java.before # src_main_java_UserService.java.after

第四步,结束对话,触发Stop事件。检查归档目录:

ls 代码规范积累/2026-04/ cat 代码规范积累/2026-04/20260428-165847.md

期望看到的内容结构大致是:

# 代码优化记录 2026-04-28 16:58:47 ## 摘要 本次优化将 Service 层中散落的 null 判断统一前置,避免了深层嵌套。 提取了重复的校验逻辑为私有方法,体现了单一职责原则。 --- ### 文件:`src_main_java_UserService.java` ```diff - if (user != null) { - if (user.getStatus() != null) { + Objects.requireNonNull(user, "user must not be null"); + if (user.getStatus() == null) {
如果摘要和 diff 都正常出现,说明 Hook 生效了。接下来把提炼出的规则写进 `CLAUDE.md`,让后续会话读取。`CLAUDE.md` 放在项目根目录,写法上建议按"规则 + 反例"组织: ```markdown ## 编码规范 ### 空值处理 - 方法入口统一用 Objects.requireNonNull 做前置校验 - 禁止多层嵌套的 null 判断,优先提前返回 ### 命名 - 方法名不加 do 前缀 - Service 层方法名体现业务动作,不体现技术实现 ### 分层 - Service 层不写 if/else 分支,分支逻辑下沉到策略类

这样下一次会话开始时,Claude Code 会自动读取CLAUDE.md,AI 的初稿就会向这些规则靠拢。整个循环跑起来之后,规范会越来越准确,需要手动对齐的次数自然减少。

5. 本篇常见错排查

5.1 Hook 没触发,临时目录是空的

先确认settings.json的 JSON 格式合法,可以用python3 -m json.tool ~/.claude/settings.json校验。再确认脚本路径是绝对路径或~展开正确,Hook 执行时的工作目录不一定是你以为的那个。另外检查脚本是否有可执行权限。

5.2 PreToolUse 脚本报错阻断了工具调用

PreToolUse的脚本若返回非 0,会阻断当次工具调用。所以opt_capture_before.py在找不到 session 追踪时直接sys.exit(0)而不报错。如果你自己改脚本,务必保证所有异常分支都exit(0),否则 AI 会改不动文件。

5.3 摘要生成失败或超时

多半是环境变量没读到。Hook 执行时的环境和你终端里的环境可能不同,建议在脚本里显式读取,或者把变量写进 shell 的启动文件。另外确认ANTHROPIC_BASE_URL指向的是 https://taotoken.net/api ,不要带多余路径。如果一直失败,可以先在模型对话页面单独测一下通道是否通。

5.4 diff 是空的

检查 before 和 after 文件是否成对存在。常见原因是PostToolUse的 matcher 没覆盖到实际使用的工具名,比如你用的是MultiEdit但 matcher 里漏了。matcher 是正则,Edit|Write|MultiEdit这种写法要确认没有拼写错误。

5.5 关键词过滤漏记录

用"优化/重构/改进"触发追踪,会漏掉一些有价值的改动。比如直接说"把这个方法改一下",就不会被记录。后续可以考虑换成"所有有文件修改的对话都记录",让整理阶段来做筛选,而不是记录阶段。这个取舍看你对记录量的容忍度。

6. 把规范沉淀变成被动发生的事

这套方案的本质是个反馈回路:用 AI 改代码,自动记录,人工提炼,写进规范,AI 再按规范写代码。规范的准确性会随着时间提升,因为它是从真实行为里归纳出来的,而不是提前猜测的。

我大概每两周做一次整理:翻看代码规范积累/下最近的文件,找出多次出现的同类改动,这意味着这是我真实的习惯,然后提炼成一条规则写进代码风格.md,再同步到工作项目的CLAUDE.md里作为 AI 的编码约束。

如果你也在用 Claude Code,可以把这几个脚本直接拿去用,按自己的关键词触发逻辑和归档路径改一改就行。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,控制台在 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,需要管理密钥的去 API Keys 页面:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。跑通之后你会发现,记录比想象中多,原来在无意识的情况下,已经在持续地用 AI 做小规模的代码调整了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 3:33:39

ADB从入门到进阶:安装连接、常用命令与实战排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 3:30:40

虚函数与虚表:多态的成本到底花在哪

① 钩子:同样一行 b->f(),三种命运 同样一行 b->f(),编译器有时直接跳转,有时绕两个弯,有时还会"自作聪明"地在调用前先检查一遍虚表。 多态的代价到底花在哪?答案全在这几行汇编里。 这一…

作者头像 李华
网站建设 2026/9/29 3:29:06

【数据结构】图与树 · 算法手记与练习

#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <math.h>#define MAXN 1010int iMaxLength 0;//最长路径长度 int iCurrentLength 0;//当前路径长度 typedef int ElemType; ElemType stMax_Path[MAXN];//最长路径元素 ElemT…

作者头像 李华
网站建设 2026/9/29 3:28:27

Go gRPC 生产级部署:连接池 + 重试 + 超时 + 熔断全攻略

Go gRPC 生产级部署&#xff1a;连接池 重试 超时 熔断全攻略微服务架构离不开 gRPC&#xff0c;但默认 client-server 配置远不能满足生产需要。本文详解 gRPC 的连接管理、错误恢复与可观测性。一、连接池&#xff1a;gRPC 单连接复用 不同于 HTTP 池化&#xff0c;gRPC 默…

作者头像 李华
网站建设 2026/9/29 3:26:51

FanControl 上手指南:3步用温度曲线压住风扇噪音

FanControl 上手指南&#xff1a;3步用温度曲线压住风扇噪音 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/fa/FanC…

作者头像 李华
网站建设 2026/9/29 3:26:08

PHP内存分配剖析:从emalloc与pemalloc看FPM进程内存泄漏

1. 从一次线上事故说起&#xff1a;为什么你需要重新认识 PHP 的内存分配大概半年前&#xff0c;我接手了一个基于 PHP-FPM 的老项目。业务逻辑本身不复杂&#xff0c;但上线的第一个月&#xff0c;运维同学就找上门了&#xff1a;每天早上八点半&#xff0c;高峰流量一上来&am…

作者头像 李华