name: 单向门检查 description: 当创建代表架构决策的新文件时使用此技能 — 数据模型、基础设施配置、认证边界、API合同、CI/CD管道或事件系统。标记不可逆的决策,并在提交前强制讨论权衡。
单向门检查
有些决策容易逆转 — 您可以更改UI组件、重命名变量或交换实用函数,而不会有持久后果。这些是双向门:走进去,如果错了,就走回来。
其他决策产生重力。一旦流量、用户或其他代码依赖于它们,改变方向就变得昂贵。发布后的数据库模式迁移。外部消费者依赖的API合同。塑造整个权限模型的认证边界。这些是单向门。
软件中最昂贵的错误不是bug。它们是过早做出的不可逆架构决策。
被标记的内容
数据模型和数据库模式
匹配的文件:schema.prisma、schema.graphql、*.sql、migration*、models.py、models.ts、entities.py、entities.ts
数据模型是最难逆转的决策。一旦数据库有行,每个模式更改都需要迁移。列重命名会破坏查询。关系更改会级联影响整个应用程序。
要问的问题:
- 您是否映射了所有实体之间的关系?
- 此模式是否支持您需要的查询而没有N+1问题?
- 根据您的读/写模式,您是否适当地进行了规范化?
基础设施和部署配置
匹配的文件:docker-compose*、Dockerfile、*.tf、terraform*、pulumi*、cdk*、cloudformation*、k8s*、kubernetes*、helm*
基础设施选择约束了在其之上构建的一切。从ECS切换到Kubernetes,或从Lambda切换到容器,会影响部署管道、监控、扩展和团队知识。
要问的问题:
- 这是否是满足您需求的最简单基础设施?
- 您的团队对此堆栈的操作经验是什么?
- 故障恢复是什么样子的?
认证和授权
匹配的文件:auth.ts、auth.js、auth.py、firestore.rules、storage.rules、*.rules、rbac*、permissions*、security*
认证边界是承重墙。会话与JWT、基于角色与基于属性、单租户与多租户 — 每个选择塑造您的安全模型、用户体验和合规姿态。
要问的问题:
- 这是否涵盖所有用户类型和访问模式?
- 您将如何处理令牌刷新、会话过期和撤销?
- 您是从一开始就为单租户还是多租户构建?
API合同和服务接口
匹配的文件:openapi*、swagger*、*.proto、*.graphql、api-schema*、routes.ts、routes.js、routes.py
发布的API是对消费者的承诺。破坏性更改需要版本控制、弃用期和迁移指南。服务之间的内部API创建难以解耦的耦合。
要问的问题:
- 谁将使用此API?内部服务、外部开发者,还是两者?
- 您将如何版本化破坏性更改?
- 您是否暴露了应保持私有的实现细节?
事件系统和消息总线
匹配的文件:events.ts、eventbus.ts、eventemitter.py、eventhandler.py、pubsub*、queue*、kafka*、rabbit*
事件模式是生产者和消费者之间的合同。一旦多个服务订阅了事件,更改其形状需要协调部署。事件顺序假设成为架构约束。
要问的问题:
- 您是否定义了事件模式,包括必需与可选字段?
- 当消费者未能处理事件时会发生什么?
- 您需要顺序保证吗?
CI/CD管道
文件在:.github/、.gitlab/、.circleci/,或匹配 Jenkinsfile、.travis.yml、cloudbuild*
CI/CD管道成为您发布过程的核心。团队围绕部署工作流建立肌肉记忆。更改管道结构意味着重新培训,过渡期间的损坏部署可能阻塞整个团队。
要问的问题:
- 此管道是否支持您的分支策略?
- 如果部署失败,回滚程序是什么?
- 秘密是否安全处理?
依赖和包配置
匹配的文件:package.json、Cargo.toml、go.mod、requirements.txt、pyproject.toml、Gemfile
框架和依赖选择波及整个代码库。从React切换到Vue,或从Express切换到Fastify,意味着重写应用程序的大部分。
要问的问题:
- 此依赖是否积极维护?
- 它是否处理您的规模要求?
- 如果您需要切换,迁移路径是什么?
云服务配置
匹配的文件:firebase.json、.firebaserc、firestore.indexes*
云服务配置将您锁定到特定提供商和架构。Firestore索引决定查询性能。Firebase规则定义您的安全边界。
要问的问题:
- 您是否长期舒适于此提供商?
- 您是否针对实际查询模式测试了这些索引?
- 如果您需要迁移,退出策略是什么?
双向门(通过的内容)
这些文件类型可以快速决定并稍后更改:
- UI组件 — React/Vue/Svelte组件、CSS、模板
- 实用函数 — 助手、格式化器、验证器
- 测试文件 — 测试基础设施可以自由重构
- 文档 — README、指南、注释
- 日志和监控 — 日志格式、指标名称
- 配置文件 —
.env、功能标志、应用配置 - 静态资产 — 图像、字体、图标
如何实现
选项1:CLAUDE.md规则
添加到您的项目的 CLAUDE.md:
### 单向门检查
在创建代表架构决策的新文件之前,问:“这些决策中哪些难以逆转?”单向门包括数据模型、服务通信模式、认证边界、租户模型和基础设施配置。这些产生重力 — 一旦流量、用户或其他代码依赖于它们,改变方向就变得昂贵。如果决策是单向门,暂停并在提交前讨论权衡。双向门(UI组件、实用程序、样式)可以快速决定并稍后更改。
选项2:PreToolUse钩子(自动执行)
添加到您的Claude Code settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "/path/to/one-way-door-check.sh"
}
]
}
]
}
}
钩子脚本
#!/bin/sh
# 单向门检查钩子 (PreToolUse:Write)
# 标记难以逆转的架构决策。
INPUT=$(cat)
[ -z "$INPUT" ] && exit 0
# 从tool_input提取文件路径
FILE_PATH=$(echo "$INPUT" | grep -oP '"file_path"\s*:\s*"[^"]*"' | head -1 | sed 's/.*"file_path"\s*:\s*"//;s/"//')
[ -z "$FILE_PATH" ] && exit 0
FILENAME=$(basename "$FILE_PATH")
FILENAME_LOWER=$(echo "$FILENAME" | tr "[:upper:]" "[:lower:]")
DIR=$(dirname "$FILE_PATH")
ONE_WAY=0
REASON=""
# 数据库模式和迁移
if echo "$FILENAME_LOWER" | grep -qE "schema\.(prisma|graphql|sql)|migration|\.sql$|models?\.(py|ts|js)$|entities?\.(py|ts|js)$"; then
ONE_WAY=1
REASON="数据模型 / 数据库模式"
fi
# 基础设施和部署配置
if echo "$FILENAME_LOWER" | grep -qE "^(docker-compose|dockerfile|terraform|pulumi|cdk)|\.tf$|cloudformation|k8s|kubernetes|helm"; then
ONE_WAY=1
REASON="基础设施 / 部署配置"
fi
# 认证和授权
if echo "$FILENAME_LOWER" | grep -qE "auth\.(ts|js|py)|firestore\.rules|storage\.rules|security|\.rules$|rbac|permissions"; then
ONE_WAY=1
REASON="认证 / 安全规则"
fi
# API合同和服务接口
if echo "$FILENAME_LOWER" | grep -qE "openapi|swagger|\.proto$|\.graphql$|api-schema|routes\.(ts|js|py)$"; then
ONE_WAY=1
REASON="API合同 / 服务接口"
fi
# 事件系统和消息队列
if echo "$FILENAME_LOWER" | grep -qE "event(s|bus|emitter|handler)\.(ts|js|py)$|pubsub|queue|kafka|rabbit"; then
ONE_WAY=1
REASON="事件系统 / 消息总线"
fi
# 包管理器配置(依赖选择)
if echo "$FILENAME_LOWER" | grep -qE "^(package\.json|cargo\.toml|go\.mod|requirements\.txt|pyproject\.toml|gemfile)$"; then
ONE_WAY=1
REASON="依赖 / 包配置"
fi
# Firebase和云服务配置
if echo "$FILENAME_LOWER" | grep -qE "^firebase\.json$|^\.firebaserc$|firestore\.indexes"; then
ONE_WAY=1
REASON="云服务配置 (Firebase)"
fi
# CI/CD管道
if echo "$DIR" | grep -qE "\.(github|gitlab|circleci)" || echo "$FILENAME_LOWER" | grep -qE "^(jenkinsfile|\.travis\.yml|cloudbuild)"; then
ONE_WAY=1
REASON="CI/CD管道"
fi
if [ "$ONE_WAY" = "1" ]; then
cat >&2 <<HOOK_MSG
单向门检查:您尝试创建 $FILENAME ($REASON)。此写入已被阻止,因为它是单向门 — 一旦其他代码、数据或用户依赖于它,就难以逆转的决策。
必需操作:在重试此写入之前,您必须使用AskUserQuestion工具。向用户呈现:
1. 此文件的作用以及为什么它是单向门
2. 至少2种替代方法(如果存在)及其权衡
3. 按计划进行的选项
围绕特定架构决策提问,而不仅仅是“我应该创建此文件吗?”用户需要理解他们正在承诺什么。
用户响应后,根据他们的选择进行。
HOOK_MSG
exit 2
fi
exit 0
工作原理:
- 钩子拦截每个
Write工具调用(新文件创建) - 它提取文件路径并根据已知的单向门模式进行检查
- 如果文件匹配,钩子以代码2退出(阻止)并向stderr发送消息
- Claude接收阻止消息,必须使用
AskUserQuestion与用户讨论决策 - 双向门文件静默通过(退出0)
退出代码:
0— 允许(双向门,正常进行)2— 阻止(单向门,需要讨论)
三个问题
在承诺任何单向门之前,问:
- 我承诺了什么? — 此决策约束了什么?什么变得更难更改?
- 有哪些替代方案? — 是否有更简单的方法?更可逆的方法?
- 迁移路径是什么? — 如果这结果是错误的,我们如何改变方向?
如果您不能清晰地回答这些问题,您还没有准备好走过这扇门。