你让 Claude Code 加一个新 feature。PR 功能上没问题,但架构上歪了:3 个内部 helper 被抽成「shared service」、引入了你没要的 dependency injection container、明明用函数它给你写成 class、单体 Astro app 上提议把新 feature 拆成独立微服务。
架构不合是 Claude 把通用「best practice」套到你团队刻意决定的架构上。修法是让约定不可避免——用「该做 / 不该做」配对锚到真实文件,让具体例子打败泛型先验。
常见原因
按命中率从高到低:
1. CLAUDE.md 缺架构或太虚
没明确规则(「我们是刻意单体;不要提拆分」),Claude 默认走”训练数据里看着专业的”——往往就是微服务、DI、repository pattern。
如何判断:grep -i "architecture\|monolith\|microservice\|pattern" CLAUDE.md 空——架构是隐式的,没写下来。
2. 训练先验比你的约定强
Claude 训练时见过 10 万个「抽成 class + DI」例子,你的”只用函数”在它见过的 0 个例子里。没显式 override,先验取胜。
如何判断:Claude 建议镜像某个流行框架模式(NestJS DI、Spring bean)——你拿到的是框架默认,不是项目默认。
3. 项目内模式混杂、agent 挑它最熟的
代码库一半 Redux 一半 Zustand(在迁移中),Claude 看到两种、不知哪个 canonical,默认 Redux——因为训练里更常见。
如何判断:建议用的是更老 / 更主流的模式,忽略了更新 / 项目 canonical 那个——迁移歧义。
4. CLAUDE.md 抽象规则没例子
「优先组合不要继承」——原则没错,但 Claude 没具体抄的文件。抽象规则不迁移、具体例子才迁移。
如何判断:CLAUDE.md 都是声明、没文件指针——给每条配一个「canonical: path/to/file.ts」。
5. 新 task 措辞暗示新模式
「Build a billing service」——「service」一词暗示 service-oriented 设计。「在 src/features/ 加 billing 功能」暗示沿用现有 feature 结构。
如何判断:你 prompt 里出现了项目不用的框架词(“service”、“controller”、“repository”)——Claude 套了那个框架。
6. CLAUDE.md 过期了
架构以前是那样的,CLAUDE.md 还描述旧方案。Claude 按 CLAUDE.md 干、代码和新(真)架构矛盾、你怪 Claude。
如何判断:CLAUDE.md 6+ 月没更新而架构有演进——过期 doc 在骗 Claude。
最短修复路径
按收益从高到低。前 3 步在耐久层锁住正确模式。
Step 1:写显式「我们用 / 我们不用」规则
CLAUDE.md:
## 架构
我们用:
- 单 Astro app(单体),一次部署
- 函数优于 class(不写 class-based service)
- Drizzle 直查 DB(不用 repository pattern,不要 ORM 抽象)
- 局部组件 state + URL state(不用 Redux/Zustand 全局 store)
- Server actions 做 mutation(first-party 调用不走 API route)
- Vitest 测试(不 Jest)
我们不用:
- 微服务——单进程 Astro 是刻意的
- Dependency injection container
- Repository / unit-of-work 模式
- GraphQL——REST + server action only
- Class-based component——只 functional
如果你打算提议任何「不用」清单上的模式,先停下来问。
显式 don’t 和 do 一样重要——它们 override 训练先验。
Step 2:每条规则配 canonical example
抽象规则会滑掉,具体例子会粘住:
## Canonical examples
- Feature 模块:src/features/auth/
- index.ts(公开 API)
- actions.ts(server actions)
- components/(UI)
- DB 访问:src/db/queries/users.ts(直 Drizzle,无 repo)
- 组件 state:src/features/billing/components/CheckoutForm.tsx(useState + URL params)
Claude 读这些文件、抄它们的形状——打败抽象先验。
Step 3:task 里点名 canonical
加 billing feature。
架构:完全照 `src/features/auth/`——同文件结构、同模式。
不要提议:
- 抽什么进独立 service
- 加 DI 层
- 函数转 class
- 全局 state store
如果 task 真需要其中之一,先停下来问再改方向。
「先停下来问」是安全阀——Claude 能 flag 真正的架构需要,而不是私自引入。
Step 4:review 时拒架构漂移
Claude 提了发散设计,推回去而不是接受:
你的 PR 引入了 `BillingService` class + DI。
和 `src/features/auth/` + CLAUDE.md 矛盾。
重构成:
- 函数在 `src/features/billing/actions.ts`
- 没有 service class
- 没有 DI
再提交。
每次拒收训练本 session 的剩余部分。
Step 5:季度审 CLAUDE.md vs 现状
过期 doc 在骗 Claude。定季度刷新:
# CLAUDE.md 最后更新 vs 主架构改动时间
git log -1 --format="%ai" CLAUDE.md
git log --since="6 months ago" --oneline -- src/features/ | head
CLAUDE.md 更新后又有大量 feature 改动——doc 可能不反映现状,刷新。
Step 6:prompt 里别用框架词汇
词汇塑造设计:
差:"Build a billing service with a controller and repository."
→ Claude 给你 NestJS 风。
好:"在 src/features/billing/ 加 billing 功能。
照 src/features/auth/。"
→ Claude 沿用你的约定。
必须用框架词时(“API endpoint”),配上你项目的具体形状(“API endpoint = Astro server action in actions.ts”)。
预防建议
- CLAUDE.md 写显式「我们用」+「我们不用」清单,季度刷新
- 每条架构规则配 canonical example 文件让 Claude 读 + 抄
- task prompt 里点 canonical 并显式禁掉常见发散模式
- repo 内模式不一致先收敛(钦定一个、迁移另一个)——Claude 才不会挑错
- 别在 prompt 里用框架词汇,让 example 文件设形状
- 把架构漂移 PR 当 CLAUDE.md 文档缺口修,不只是单次拒收
相关阅读
- Claude Code 不理解整个项目
- Claude Code 重构 scope 太大
- Claude Code 中途丢了项目上下文
- Claude Code 新手入门
- Claude Code 工作流
- Claude Code 项目配置
标签: #排查 #Claude Code #排查 #架构