单向门检查Skill one-way-door

此技能用于在软件开发中识别和标记不可逆的架构决策文件,如数据模型、API合同、基础设施配置等,强制团队在提交前讨论权衡,避免未来高昂的修改成本。关键词:架构决策、代码审查、不可逆决定、软件开发、CI/CD、数据模型。

架构设计 0 次安装 0 次浏览 更新于 3/15/2026

name: 单向门检查 description: 当创建代表架构决策的新文件时使用此技能 — 数据模型、基础设施配置、认证边界、API合同、CI/CD管道或事件系统。标记不可逆的决策,并在提交前强制讨论权衡。

单向门检查

有些决策容易逆转 — 您可以更改UI组件、重命名变量或交换实用函数,而不会有持久后果。这些是双向门:走进去,如果错了,就走回来。

其他决策产生重力。一旦流量、用户或其他代码依赖于它们,改变方向就变得昂贵。发布后的数据库模式迁移。外部消费者依赖的API合同。塑造整个权限模型的认证边界。这些是单向门

软件中最昂贵的错误不是bug。它们是过早做出的不可逆架构决策。

被标记的内容

数据模型和数据库模式

匹配的文件:schema.prismaschema.graphql*.sqlmigration*models.pymodels.tsentities.pyentities.ts

数据模型是最难逆转的决策。一旦数据库有行,每个模式更改都需要迁移。列重命名会破坏查询。关系更改会级联影响整个应用程序。

要问的问题:

  • 您是否映射了所有实体之间的关系?
  • 此模式是否支持您需要的查询而没有N+1问题?
  • 根据您的读/写模式,您是否适当地进行了规范化?

基础设施和部署配置

匹配的文件:docker-compose*Dockerfile*.tfterraform*pulumi*cdk*cloudformation*k8s*kubernetes*helm*

基础设施选择约束了在其之上构建的一切。从ECS切换到Kubernetes,或从Lambda切换到容器,会影响部署管道、监控、扩展和团队知识。

要问的问题:

  • 这是否是满足您需求的最简单基础设施?
  • 您的团队对此堆栈的操作经验是什么?
  • 故障恢复是什么样子的?

认证和授权

匹配的文件:auth.tsauth.jsauth.pyfirestore.rulesstorage.rules*.rulesrbac*permissions*security*

认证边界是承重墙。会话与JWT、基于角色与基于属性、单租户与多租户 — 每个选择塑造您的安全模型、用户体验和合规姿态。

要问的问题:

  • 这是否涵盖所有用户类型和访问模式?
  • 您将如何处理令牌刷新、会话过期和撤销?
  • 您是从一开始就为单租户还是多租户构建?

API合同和服务接口

匹配的文件:openapi*swagger**.proto*.graphqlapi-schema*routes.tsroutes.jsroutes.py

发布的API是对消费者的承诺。破坏性更改需要版本控制、弃用期和迁移指南。服务之间的内部API创建难以解耦的耦合。

要问的问题:

  • 谁将使用此API?内部服务、外部开发者,还是两者?
  • 您将如何版本化破坏性更改?
  • 您是否暴露了应保持私有的实现细节?

事件系统和消息总线

匹配的文件:events.tseventbus.tseventemitter.pyeventhandler.pypubsub*queue*kafka*rabbit*

事件模式是生产者和消费者之间的合同。一旦多个服务订阅了事件,更改其形状需要协调部署。事件顺序假设成为架构约束。

要问的问题:

  • 您是否定义了事件模式,包括必需与可选字段?
  • 当消费者未能处理事件时会发生什么?
  • 您需要顺序保证吗?

CI/CD管道

文件在:.github/.gitlab/.circleci/,或匹配 Jenkinsfile.travis.ymlcloudbuild*

CI/CD管道成为您发布过程的核心。团队围绕部署工作流建立肌肉记忆。更改管道结构意味着重新培训,过渡期间的损坏部署可能阻塞整个团队。

要问的问题:

  • 此管道是否支持您的分支策略?
  • 如果部署失败,回滚程序是什么?
  • 秘密是否安全处理?

依赖和包配置

匹配的文件:package.jsonCargo.tomlgo.modrequirements.txtpyproject.tomlGemfile

框架和依赖选择波及整个代码库。从React切换到Vue,或从Express切换到Fastify,意味着重写应用程序的大部分。

要问的问题:

  • 此依赖是否积极维护?
  • 它是否处理您的规模要求?
  • 如果您需要切换,迁移路径是什么?

云服务配置

匹配的文件:firebase.json.firebasercfirestore.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

工作原理:

  1. 钩子拦截每个 Write 工具调用(新文件创建)
  2. 它提取文件路径并根据已知的单向门模式进行检查
  3. 如果文件匹配,钩子以代码2退出(阻止)并向stderr发送消息
  4. Claude接收阻止消息,必须使用 AskUserQuestion 与用户讨论决策
  5. 双向门文件静默通过(退出0)

退出代码:

  • 0 — 允许(双向门,正常进行)
  • 2 — 阻止(单向门,需要讨论)

三个问题

在承诺任何单向门之前,问:

  1. 我承诺了什么? — 此决策约束了什么?什么变得更难更改?
  2. 有哪些替代方案? — 是否有更简单的方法?更可逆的方法?
  3. 迁移路径是什么? — 如果这结果是错误的,我们如何改变方向?

如果您不能清晰地回答这些问题,您还没有准备好走过这扇门。