ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Claude Code + Python 自动生成设计文档:用 pre-commit hook 让 AI 替你还“意图债“(附完整代码)

Claude Code + Python 自动生成设计文档:用 pre-commit hook 让 AI 替你还“意图债“(附完整代码) 1. 代码能跑但没人知道为什么这就是意图债你有没有遇到过这种场景翻到半年前自己写的一段缓存逻辑代码本身干净利落测试也全绿但你就是想不起来当初为什么没用数据库自带的物化视图为什么偏偏选了 Redis为什么这个接口要做成异步。代码能跑可为什么这么写的信息已经彻底蒸发了。Addy Osmani 把这类缺失叫做意图债Intent Debt它和代码债、认知债并列却是三种债里最隐蔽的一种——lint 查不出来测试覆盖不到重构也修不好因为丢失的不是代码质量而是决策上下文。更麻烦的是AI 辅助编码正在加速意图债的累积。Claude Code 几分钟就能生成两三百行结构完整的代码你 review 一遍觉得没问题就合入了但生成过程中那些隐含的架构取舍、被否决的替代方案、当时的环境约束全都没有落到任何文件里。三个月后连你自己都要靠猜。这篇要解决的就是让每次 commit 都自动附带一份为什么这么做的说明用 pre-commit hook 触发 Claude Code 加一个 Python 脚本自动产出架构决策记录ADR。适合正在用 Claude Code 写业务代码、又不想让设计意图随提交一起消失的开发者跟着做大概二十分钟能跑通第一条自动 ADR。2. 前置准备Claude Code 与 TaoToken 接入整套流水线依赖两个东西本地能非交互调用 Claude Code以及一个稳定的模型接入端点。Claude Code 的安装很直接npm install -g anthropic-ai/claude-code claude --version如果你希望把模型调用统一走一个可管理的入口可以用 TaoToken 的 API 端点来承接。它的 API 地址是https://taotoken.net/api控制台在https://taotoken.net/consoleAPI Keys 管理页在https://taotoken.net/api-keys。拿到 Key 之后把它写进环境变量避免硬编码进脚本export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的key注意环境变量建议写进~/.zshrc或~/.bashrc不要提交到仓库。脚本里只读os.environ不出现明文密钥。模型对话能力可以在https://taotoken.net/models对应的对话入口先验证一下确认 Key 可用、模型能正常返回再往下搭 hook。如果你后续要把这套机制扩展到长期编码或 Agent 场景可以了解 Coding Planhttps://taotoken.net/coding-plan。接入文档在https://taotoken.net/docClaude Code 相关的说明在https://taotoken.net/claudecode-anthropic。项目初始化mkdir intent-debt-free cd intent-debt-free git init mkdir -p docs/adr scripts3. 可复制配置Python 脚本 pre-commit hook核心是一个 Python 脚本它做三件事抓暂存区的 diff、把 diff 和提交上下文喂给 Claude Code、把返回的 ADR 存成 Markdown 文件。创建scripts/generate-adr.py#!/usr/bin/env python3 分析暂存区 diff调用 Claude Code 生成 ADR 草稿 import subprocess import os import sys from datetime import datetime from pathlib import Path REPO_ROOT Path(__file__).resolve().parent.parent ADR_DIR REPO_ROOT / docs / adr CODE_GLOBS [*.py, *.ts, *.tsx, *.js, *.go, *.rs, *.java, *.yaml, *.yml, *.sql] def get_git_diff(): try: result subprocess.run( [git, diff, --cached, --diff-filterAM, --] CODE_GLOBS, capture_outputTrue, textTrue, cwdREPO_ROOT ) return result.stdout.strip() except Exception as e: print(f获取 diff 失败: {e}, filesys.stderr) return def get_commit_context(): try: branch subprocess.run( [git, rev-parse, --abbrev-ref, HEAD], capture_outputTrue, textTrue, cwdREPO_ROOT ).stdout.strip() recent subprocess.run( [git, log, --oneline, -5], capture_outputTrue, textTrue, cwdREPO_ROOT ).stdout.strip() return f分支: {branch}\n最近提交:\n{recent} except Exception: return 无上下文信息 def generate_adr_with_claude(diff_text, context): if not diff_text: print(没有代码变更跳过 ADR 生成) return None prompt f你是技术文档工程师。分析下面的代码变更生成一份架构决策记录ADR。 格式要求中文简洁每个字段不超过 5 行 标题[一句话概括这次变更做了什么] 背景[变更前的状态触发变更的需求或问题] 决策[选了什么方案为什么简要说明被否决的替代方案] 后果[积极影响需要注意的新约束或风险] 上下文信息 {context} 代码变更git diff {diff_text} 只输出 ADR 内容不要额外解释。 try: result subprocess.run( [claude, --print, --output-format, text, prompt], capture_outputTrue, textTrue, cwdREPO_ROOT, timeout120 ) return result.stdout.strip() except subprocess.TimeoutExpired: print(Claude Code 超时diff 可能太大缩小提交范围, filesys.stderr) return None except Exception as e: print(fClaude Code 调用失败: {e}, filesys.stderr) return None def save_adr(adr_content): ADR_DIR.mkdir(parentsTrue, exist_okTrue) date_str datetime.now().strftime(%Y-%m-%d) first_line adr_content.strip().split(\n)[0] slug first_line.replace(# 标题, ).replace(# , ).strip() slug .join(c if c.isalnum() or c in _- else for c in slug) slug slug.lower().replace( , -)[:60] filepath ADR_DIR / f{date_str}-{slug}.md with open(filepath, w, encodingutf-8) as f: f.write(f# {first_line.replace(#, ).strip()}\n\n) f.write(f日期: {date_str}\n状态: 提议中\n\n) f.write(adr_content) print(fADR 已生成: {filepath}) return str(filepath) if __name__ __main__: diff get_git_diff() context get_commit_context() adr generate_adr_with_claude(diff, context) if adr: path save_adr(adr) print(f\n请 review 后决定是否合入{path}) else: print(未生成 ADR无变更或调用失败)接着创建.git/hooks/pre-commit#!/bin/bash echo 分析变更并生成架构决策记录... python3 scripts/generate-adr.py ADR_FILE$(ls -t docs/adr/*.md 2/dev/null | head -1) if [ -n $ADR_FILE ]; then git add $ADR_FILE echo ADR 已暂存: $ADR_FILE fi exit 0赋予执行权限chmod x .git/hooks/pre-commit这里有个关键设计hook 最后exit 0不阻塞提交。ADR 是辅助产物如果因为模型超时或网络抖动就阻止 commit工作流会被打断反而让人想关掉它。让 ADR 生成失败时静默跳过比强制拦截更可持续。4. 验证请求跑一次真实提交看结果先造一个真实变更。新建cache.pyimport redis r redis.Redis(hostlocalhost, port6379, decode_responsesTrue) def get_user_profile(user_id: int): key fuser:profile:{user_id} cached r.get(key) if cached: return cached # 从数据库读取并回填缓存 profile fetch_from_db(user_id) r.setex(key, 3600, profile) return profile然后提交git add cache.py git commit -m feat: 用户资料加 Redis 缓存终端会先打印分析变更并生成架构决策记录...几秒后出现ADR 已生成: docs/adr/2026-xx-xx-....md。打开这个文件你应该能看到类似这样的内容# 用户资料查询引入 Redis 缓存层 日期: 2026-xx-xx 状态: 提议中 标题用户资料查询引入 Redis 缓存层 背景用户资料读取频率远高于写入频率直接查库造成重复 IO。 决策采用 Redis 做缓存层设置 1 小时过期。未选用数据库物化视图 因为资料字段变更频繁物化视图刷新成本高。 后果读取延迟下降需注意缓存与数据库一致性过期时间需按业务调整。如果内容基本对得上说明整条链路通了。你可以直接编辑这份 ADR 修正措辞再git add一次它就和代码一起进了同一个 commit。想验证模型侧是否正常可以到模型对话入口发一条测试消息确认返回稳定。5. 本篇常见错排查报错claude: command not foundhook 执行时的 PATH 和你终端里的可能不一致。在 pre-commit 里用绝对路径或者先which claude拿到路径再写死。ADR 内容空泛、抓不住重点多半是 diff 太大。一次提交改二十个文件、五百行模型很难提炼出单一决策。养成小步提交的习惯让 Claude Code 每完成一个独立功能就提醒你 commit。超时或返回空检查ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY是否在 hook 的运行环境里可见。hook 不加载交互式 shell 的配置必要时在脚本里显式读取一个.env文件。ADR 文件越堆越多建一个docs/adr/INDEX.md按时间倒序列出所有记录并给每份加状态标签提议中 / 已采纳 / 已废弃。用一个简单脚本在生成后自动追加索引行即可。提交频率太高导致调用开销大把 hook 从pre-commit挪到pre-push或者只在合并到 main 时触发。一天几十次提交的场景没必要每次都生成。不要用 ADR 替代真正的设计讨论ADR 是记录工具不是决策工具。涉及多团队的架构取舍先在文档或会议里讨论清楚再用 ADR 记录结论别反着来。6. 用 CLAUDE.md 做项目级意图基线每次提交生成 ADR 是事后记录还有一层更治本的做法维护一份项目级的CLAUDE.md作为所有 AI 工具共享的意图基线。Claude Code 启动时会自动加载它生成代码时就会尊重已有决策而不是每次重新发明轮子。# 项目意图基线 ## 架构决策 - 选择 FastAPI 而非 Django团队对异步有强需求 - Redis 做缓存层读频率远高于写频率 - 放弃微服务团队 3 人运维成本远超收益 ## 技术约束 - Python 版本 ≥ 3.11使用了 match-case - 部署在容器环境不能用依赖本地持久化的方案 - 第三方 API 每分钟 500 次批量操作必须有 rate limiter ## 已否决的方案 - 不用 Celery太重Redis Queue 足够 - 不用 Kubernetes团队无 K8s 运维经验这份文件有两个作用一是让 Claude Code 每次生成代码时都带着项目的历史约束减少它又选了一个我们早就否决的方案这类返工二是它本身就是一份意图文档新人入职第一件事就是读它。配合每次提交自动生成的 ADR短期决策和长期基线就都有了着落。搭好这套东西之后最直观的变化是每个 PR 都自带一份为什么这么改的说明reviewer 不用再翻聊天记录。半年后你回来看一段代码ADR 就躺在代码旁边。意图债不会因为你注意了就消失它需要机制来对抗。这套脚本真正的价值不是 AI 写得多好而是它让每次提交都提醒你一句这段代码的意图我记下来了吗。
返回列表