关于 structure-codebase
该技能指导开发者依据代码库的实际职责、边界和变更关系规划并约束源代码结构,适用于审查或调整文件位置、包结构、导出和导入方向。
主要功能
- 01围绕实际职责组织源代码
- 02区分能力与技术边界
- 03提案前检查代码和依赖
- 04制定文件放置与依赖规则
- 05规划迁移和自动化约束
适用场景
- 01审查文件夹、包、导出或导入方向
- 02为代码库提出目标源代码树
- 03规划代码迁移及边界约束
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ citypaul/ .dotfiles --skill structure-codebase安装后在 Claude Code 中输入/structure-codebase确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/backend-patterns.mdenforcement-and-migration.mdfrontend-patterns.mdresearch-notes.mdvisible-hexagon.md详细介绍
组织代码库
将源代码树设计成一张如实反映含义、所有权、运行时边界和变更内聚性的地图。不要从通用模板出发。选择最精简的结构,让项目的真实边界一目了然,并尽可能难以通过机制违反这些边界。
将此技能用于文件的物理放置和依赖约束,包括专门针对源代码树、文件夹、包、导出项或导入方向的审查。当问题是某个模块应负责哪项内聚职责,以及应提供什么面向调用方的契约时,加载 codebase-design;当尚未解决的问题是值得进行哪项更广泛的架构投入以及为何现在要做时,加载 improve-codebase-architecture。当扁平化目录树旨在支持有证据支撑的主张,即实际依赖、跳转、状态或运维机制有所减少时,加载 reduce-system-complexity——仅仅移动文件并不算减少复杂度。只有当单体仓库、构建、工作区或边界约束工具仍存在实质性未决问题时,才加载 evaluate-existing-solutions。端口和适配器的实现请加载 hexagonal-architecture,领域建模请加载 domain-driven-design。这些模式彼此独立;切勿推断使用其中一种就必须使用另一种。
在生成目标目录树之前,先阅读相关参考资料:
- 对于采用端口和适配器的后端、DDD 加六边形架构,或涉及端口、测试参与方、读模型和包隔离的问题,请阅读
references/visible-hexagon.md。 - 对于 BFF、普通的特性优先服务、受框架约束的后端、运维工具、CLI 或小型包,请阅读
references/backend-patterns.md。 - 对于前端、设计系统、客户端特性架构、元框架 UI 或全栈请求,请阅读
references/frontend-patterns.md。 - 在移动现有代码、更改包的目录层级或设计导入/包约束机制之前,请阅读
references/enforcement-and-migration.md。 - 在解释命名方法或源代码设计依据时,请阅读
references/research-notes.md。
核心模型
阅读结构良好的代码库时,应从最广泛的含义逐步深入到最具体的细节:
- 产品或能力——代码为何存在,由谁负责。
- 架构边界——内部与外部之分,但前提是该边界确实存在。
- 特性、领域概念、用例、工作流或端点——哪些内容会一起变化。
- 实现细节——框架、持久化、SDK、协议和小型辅助工具。
将“尖叫式架构”视为一个适用性问题,而不是文件夹分类法:新手能否看出系统的用途?hexagon、adapters、driving、driven 和 composition 等技术名称,在真实边界处很有用;只有当它们取代了产品含义,或声称存在实际上并不存在的边界时,才会造成危害。
文件夹名称会传达主张。包、导出项、导入、测试和自动化边界规则则能证明这些主张。
区分不同维度
| 维度 | 回答的问题 | 不要与之混淆 |
|---|---|---|
| 能力 | 这段代码为何存在并发生变化? | 技术层 |
| 有界上下文 | 语言和模型的权威归属在哪里变化? | 名词、表、团队标签或为方便而设的文件夹 |
| 六边形 | 哪些策略必须能独立于外部技术进行测试? | DDD、泛化的 core,或每个纯库 |
| 特性/用例 | 哪些行为会一起变化? | 必需的包或有界上下文 |
| 端点 | BFF 请求从何处进入? | 共享工作流状态的所有者 |
| 包/模块 | 工具可以强制约束哪种边界? | 其自身构成的业务边界 |
| 组合根 | 在何处构建并管理运行时对象图? | 每个库都必须包含的文件夹 |
工作流
1. 保持在用户要求的范围内
判断用户要求的是审计、提案、迁移计划还是实现。仅提出建议的请求,不要移动文件。未经授权,不要将文件夹调整扩大为领域重设计。
2. 先检查,再命名
充分了解系统的实际情况,避免只凭文件名设计:
- 项目规则、架构决策、术语表、路线图和包索引。
- 路由、屏幕、命令、作业、组件、状态/数据所有者、测试、公开导出内容以及面向用户的语言。
- 包清单、源代码导入、运行时/部署单元和提供方依赖。
- 框架要求的位置、生成代码、工作区 glob、构建/测试配置和边界工具。
- 能揭示哪些文件会一起变化的近期变更或所有权信号。
记录不确定之处。候选限界上下文和包拆分都只是假设;只有语言、不变量、所有权或依赖方向能够佐证时,才能将其视为依据。
3. 选择最小且名实相符的结构
| 项目信号 | 主要结构 |
|---|---|
| 微型包或早期原型 | 平铺或近似平铺 |
| 不采用端口/适配器的普通后端 | 以功能或用例为先 |
| 不采用六边形架构的 DDD | 以限界上下文和领域语言为先 |
| 明确采用六边形架构的后端 | 先按能力划分,再明确内外边界,最后组织丰富的内部结构 |
| BFF 或端点密集的边缘服务 | 以 URL/资源为先组织端点,并列组织工作流和组合逻辑 |
| 受框架约束的后端 | 保持必需入口精简,将行为委托给按功能组织的模块 |
| 小型前端 | 使用框架原生路由并进行浅层就近组织 |
| 功能丰富的前端 | 路由/屏幕组合,加上产品功能模块和具名基础模块 |
| 元框架全栈应用 | 使用框架路由树,并明确划分客户端/服务端模块边界 |
| 已成熟且结构一致的前端 | 根据请求范围和证据保留或演进现有结构 |
| 前端单仓库或设计系统 | 按应用/产品范围组织,并采用经过验证且公开 API 受约束的包 |
| 运维、迁移或自动化包 | 以工作流或命令为先 |
| 单仓库 | 按产品/能力分组,并划定真实的叶子包边界 |
这些结构可以组合使用。单仓库是一个强制执行层。BFF 往往是其他六边形架构的驱动宿主,但本身不一定是六边形架构。DDD 可以采用或不采用端口/适配器。
4. 检验六边形架构是否名副其实
只有当项目明确选择端口/适配器,且拟议边界具备以下条件时,才采用清晰可见的六边形结构:
- 具有有意义的应用策略,必须能够脱离真实外部系统运行。
- 至少与一个驱动方或被驱动方进行有目的的交互。
- 端口使用应用领域语言表达,而不是照搬 SDK API。
- 每个端口都有一个测试交互器,或有具体计划进行补充。
- 有能够约束内部与外部边界的导入、包或架构测试。
不要为适配器封装、提供方集成、简单 CRUD、运维脚本或仅按词汇划分的包套上形式化的六边形结构。
5. 同时给出结构及其规则
每份提案都必须包含:
- 所选结构及支持该结构的证据。
- 带注释的 ASCII 树,区分分组目录、真实包和可执行宿主。只展示有证据支持的目标路径:展示的每个新目录都必须承载至少一个具名文件、包或具体职责。将未知候选项合并到文字说明中;仅对现有且不变的子树或明确重复的既有模式使用省略号。
- 新文件的放置规则。
- 允许和禁止的依赖方向。
- 与项目规模相称的自动化约束。
- 现有树结构需要变更时,分阶段的迁移方案。
- 假设、刻意延后的决策,以及有意不引入的结构。
可见的六边形后端
对于主动采用此能力的单体仓库,优先使用以下语法:
<capability>/ # grouping directory, not a package
├── hexagon/ # unmistakable INSIDE
│ └── <inside-package>/ # real workspace package
│ └── src/
│ └── <feature-or-use-case>/
├── adapters/ # unmistakable OUTSIDE
│ ├── driving/
│ │ └── <transport-adapter>/ # real workspace package
│ └── driven/
│ └── <provider-adapter>/ # real workspace package
└── testing/ # outside test interactors
└── <fake-or-contract-suite>/ # real workspace package when reusable
应用以下语义:
- 将纯业务规则放在领域模块中。
- 将不依赖提供方的应用策略和编排放在六边形内部,即使它会调用注入的端口,且不具备引用透明性。
- 将每个端口放在拥有该交互的最内层使用方处。驱动端口和面向基础设施的仓储/网关端口通常归应用层所有;只有当领域模型本身使用某个端口时,该端口才归领域层所有。
- 将所有具体传输、数据库、文件系统、队列、时钟、UUID 生成器、云服务和提供方 SDK 放在外部。
- 将可复用的伪实现、测试驱动和适配器行为契约放在
testing/下,绝不要放在生产六边形内部。将普通行为测试与其验证的代码放在一起。 - 将具体构造、配置、资源所有权和关闭逻辑放在可执行宿主的组合根中。
- 在包内按功能、领域概念或用例组织。不要用扁平的分层目录替代一个扁平的巨型文件。
- 仅当依赖边界的收益足以抵消成本时,才将领域层和应用层设为独立包。使用一个内聚的内部包也完全可行。
- 在单包服务中,优先按能力组织(例如,将
src/ordering/hexagon/放在src/ordering/adapters/旁边),并通过基于路径的导入测试或架构测试加以约束。不要用根级技术分类目录(如src/hexagon/和src/adapters/)替代仓库范围的包边界。
DDD 放置原则
由限界上下文定义语言和模型权威;由六边形定义技术测试边界。二者并非彼此必然相关。
- 仅当术语、不变量、生命周期、所有权或集成契约存在差异时,才使用独立上下文。
- 将领域概念作为名词,将用例/工作流作为动词。
- 通过明确的契约、事件或防腐层在上下文之间通信。
- 将任何共享内核保持在最小范围内、确保稳定,并指定一个明确的所有者;绝不要使用仓库根目录下的兜底目录。
- 当上下文证据不足时,将某个概念作为功能模块保留在现有上下文中。
加载 domain-driven-design,而不是在此处重复聚合、实体、值对象、事件或通用语言方面的指导。
前端结构
将前端架构视为一等模式。在选择目录结构前,阅读 references/frontend-patterns.md,并确定路由归属、产品功能内聚性、UI 复用级别、状态/数据所有权、运行时边界和包所有权。只有在请求范围或证据支持保留现有前端时才保留;如果任务要求重新设计,则进行重新设计。不要将后端的 hexagon/、端口、适配器、DDD 实体或组合根术语套用到客户端代码上,除非前端明确采用这些模式来处理真实边界。在全栈仓库中,应独立分类客户端和服务器模块,并遵守框架的客户端/服务器依赖图。
如果前端使用状态图,则状态/数据所有权还包括由哪一层拥有状态机和参与者。请明确声明并像其他导入规则一样加以约束,因为 Agent 在禁止的层中生成其他方面都正确的状态机是常见故障;而本仓库宁愿通过边界测试发现此问题,也不愿等到 CI 才发现。xstate 遵循本技能和仓库中声明的规则。
组合根
只有可执行应用才有组合根。将具体实现的选择放在入口点附近,不要放在其他地方。
- 当宿主具有复杂的依赖关系图、多组配置、自有资源或生命周期/关闭行为时,使用
composition/。 - 如果依赖关系图很简单,可让
main.ts、框架启动文件或小型工厂内联承担这一职责。 - 不要让库、限界上下文或每个功能都包含空的
composition/文件夹。 - 不要让普通端点、用例或适配器构造其具体协作者。如果无服务器框架将处理器或消费者作为可执行部署入口点,该入口点也可以内联承担简单的组合职责;应让装配与转换逻辑清晰分离,并在依赖关系图被共享、有状态或变得复杂时立即将其提取出来。
内聚与共享
- 将会一起变更的文件放在一起,包括由同一切片负责的专用测试、架构、测试夹具和映射器。
- 从每个包或跨功能模块中公开小而明确的公共 API。
- 禁止同级功能导入彼此的内部实现。
- 只有当多个当前所有者都需要某段代码,且所提升的能力有稳定、特定用途的名称时,才提升该代码。
- 为复用的概念指定用途明确的所有者,例如
monetary-values、runtime-clock或order-event-contracts;不要将所有权隐藏在根目录下的shared、common、utils、helpers或services文件夹中。 - 如果拆分文件夹能改善导航或变更内聚性,就拆分文件夹;如果依赖关系、所有权、发布或信任边界需要通过机制强制执行,就拆分包。
适度执行约束
- 对于小型包,公共导出和代码审查可能就足够了。
- 对于单个应用,使用路径别名、受限导入、架构测试和框架边界规则。
- 对于单体仓库,验证清单、项目引用、公共导出、工作区发现和依赖方向。
- 对于大型/深层工作区,应根据物理角色或小型包角色注册表推导规则,而不是手动维护包允许列表。
- 在将包移入建议的最深路径之前,先测试递归发现能力。
阅读 references/enforcement-and-migration.md,了解依赖矩阵和迁移门槛。
反模式
应拒绝以下结构:
- 在混杂提供方代码或允许向外部导入的包中设置装饰性的
src/hexagon。 - 跨越无关能力的全局
domain、application、controllers、services或repositories文件夹。 - 为每个名词或微型接口都创建包、限界上下文、端口或六边形架构。
- 复刻 SQL、HTTP、ORM 或 SDK API 的端口。
- 放在
hexagon/下的测试适配器和可复用伪造实现。 - 隐藏在端点处理器中的业务行为、提供方构造或共享运行时状态。
- 埋在能力或六边形文件夹中,而非公共接口树(
endpoints/、commands/)中的 HTTP 路由末端或 CLI 命令入口。 - 绕过路由目录挂载的生产 BFF 端点,或没有明确访问分类的端点末端(
bff-entry-points负责分类和注册器模型)。 - 导入仅供开发使用的路由或测试包的生产代码。
- 无明确归属的
shared、common、utils、helpers或services垃圾堆。 - 为了形式对称而添加的空文件夹骨架。
- 工作区、构建、测试、覆盖率和 lint 工具无法发现的深层目录树。
- 同时进行行为变更、依赖倒置和大规模文件移动的一次性大迁移。
- 将所有纯格式化器或工具函数都视为领域策略。
- 不保留必需框架目录的精简结构,反而与之对抗。
- 模糊前端产品归属的全局
components、hooks、stores或api文件夹。 - 能访问仅限服务端的模块、机密信息或特权提供方代码的客户端包。
完成检查
- 新手能否在了解框架之前识别产品功能?
- 如果采用六边形架构,内部与外部是否界限分明且符合实际?
- 内部是否按领域概念或行为组织,便于导航,而非散落在平铺的文件中?
- 端点和框架入口是否足够精简?
- 具体对象的构建是否由应用入口负责?
- 有界上下文是否有语言和模型权威性作为依据?
- 是否没有让规模较小且非六边形架构的项目承担尚未适用的架构?
- 在前端中,路由、功能、UI 复用、状态/数据和运行时分别由谁负责,是否清晰明确且没有仪式化分层?
- 包发现和边界检查是否支持所提议的层次深度?
- 提案是否说明下一个新文件应放在哪里,以及它可以导入什么?
- 展示的每个新目录现在是否都有实际的负责人,而不是空置或推测性的占位目录?