get-shit-done 的 ADR 治理实践:docs/adr 索引、SDK 缝架构图(ADR 0005/0006)与结构化测试守护
【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done
本文基于变更集3271-sdk-adr-structure.md(Enhancement,PR #3302,对应 issue #3271)展开,介绍 get-shit-done(GSD)如何为docs/adr/目录建立带索引的架构决策记录(ADR)体系:新增 docs/adr/README.md 作为全部 ADR 的索引入口;ADR 0005 给出 SDK 顶层架构缝(seam)地图;ADR 0006 规范 SDK query handler 的规划路径投影策略。读完本篇,你能掌握 ADR 的命名与索引规范、SDK 缝模块的归属边界,以及“规划路径如何从 cwd 投影到.planning/<project>/...”这一策略在源码与结构化测试中的完整落地方式。
变更内容概览
按变更集 3271-sdk-adr-structure.md 的记载,本次增强包含三部分:
- 索引入口:新增 docs/adr/README.md 作为所有 ADR 的索引化入口,按文件名链接全部 ADR;
- ADR 0005:记录 SDK 顶层架构缝地图,覆盖 Dispatch Policy Module、Model Catalog Module、Planning Workspace Module、SDK Package Seam Module、Planning Path Projection Module 五个缝模块的归属边界;
- ADR 0006:记录 SDK query handler 如何投影规划路径(
cwd → effectiveRoot → .planning/<project>/...); - 结构化测试:tests/enh-3271-sdk-adr-structure.test.cjs 断言每份 ADR 具备必需的标题结构与 Status/Date 元数据,且 README 按文件名链接每一份 ADR 文件。
ADR 索引:docs/adr/README.md的组织方式
docs/adr/README.md 首先声明了 ADR 的基本约定:每份 ADR 记录一个架构决策——决定了什么、为什么、带来什么后果;ADRs 是append-only的,修订(amendment)通过带日期的新增章节扩展既有 ADR,而不是替换原文。
命名约定:issue# 前缀
新 ADR 采用issue#-prefix slug命名:
docs/adr/<issue#>-<kebab-slug>.mdREADME 中给出了一个很有说服力的理由:两名开发者各自在本地基于main计算“下一个 ADR 序号”时,会独立地取到同一个整数并同时提交——而磁盘上的冲突已有实证,0010-*存在两份、0011-*存在三份(例如 0010-file-operation-engine-module.md 与 0010-skill-surface-budget-module.md 并存)。GitHub issue 编号是服务端原子分配的:issue 一旦打开,该编号即被全局保留。两个都改CHANGELOG.md的 PR 在合并时必然冲突,而两个使用不同 issue# 前缀的 ADR 文件永远不会碰撞——“同形状的问题,同解法”。
同时 README 明确0001-*至0011-*为不可变的遗留历史记录,重号文件是旧约定的残留而非应效仿的模式,不得重新编号。完整的提交流程(开 issue、等待批准、命名文件、提交 PR)指向 CONTRIBUTING.md 的 "Proposing an ADR or PRD" 章节。
索引表与缝地图导读
README 的 Index 表按ADR | Title | Status三列登记全部 ADR。需要说明的是:变更集写作时索引链接“七个 ADR”,而当前仓库的索引已扩展到 16 条记录(例如 0012-command-routing-hub.md、3524-cjs-sdk-hard-seam.md、3660-runtime-artifact-layout-module.md),Status 覆盖 Accepted / Proposed / Superseded / Reference 等状态——这正是结构化测试“README 必须链接目录内每一份 ADR”这一断言的价值所在:索引是自我维护的,新增 ADR 后测试会强制索引同步。
README 后半部分的 "Seam map" 章节充当导航摘要:ADR 0005 是顶层 SDK 缝索引,是理解 SDK 模块归属的入口;ADR 0006 与 Planning Workspace Module(ADR 0004)交叉引用 workstream 指针策略;ADR 0008 / 0009 / 0010 / 0011 分别登记安装器迁移、shell 命令投影、文件操作引擎、技能表面预算等缝模块。
ADR 0005:SDK 架构缝地图
0005-sdk-architecture-seam-map.md(Status: Accepted,Date: 2026-05-09)解决的问题是:SDK 的功能逻辑一旦散落在 query handler、runtime adapter 与兼容性 shim 之间,就没有稳定的归属边界可依。它决定显式地以缝模块(seam Module)组合构建 SDK,作为顶层地图规定各缝的归属边界。
Decision
- 将 SDK 视为一组显式缝 Module 的组合,调用点只保留薄 Adapter;
- 包布局兼容性策略隔离在SDK Package Seam Module之后(见 0007-sdk-package-seam-module.md);
- dispatch 传输/结果策略放在Dispatch Policy Module与SDK Runtime Bridge Module之后(见 0001-dispatch-policy-module.md 的修订);
- model/runtime profile 解析放在Model Catalog Module之后(见 0003-model-catalog-module.md);
- planning/worktree/workstream 的路径与状态策略放在Planning Workspace Module之后(见 0004-worktree-workstream-seam-module.md);
- 规划路径投影策略显式集中,详见 0006-planning-path-projection-module.md。
Consequences
- SDK 调用方(
init*、query handler、runtime 入口)保持稳定接口之上的薄 Adapter; - 包布局兼容性、dispatch 传输、model 策略、规划路径策略的变更各自局部化在所属 Module 内;
- 架构评审可以快速分类漂移:如果行为变化发生在归属缝 Module 之外,即为设计违规——这一条为代码评审提供了可操作的判定标准。
这与 ADR 0004 中 Planning Workspace Module 的定义形成呼应:该 Module 是planningDir/planningRoot/planningPaths、活跃 workstream 指针策略、锁语义的权威接口,而 ADR 0005 把“路径投影”单独抽成 0006 一节,进一步加深了这一层的缝。
ADR 0006:规划路径投影模块
0006-planning-path-projection-module.md(Status: Accepted,Date: 2026-05-09)针对的问题是:如果每个 handler 都用临时拼接(ad-hoc join)重建.planning路径,路径策略就会在 helper 层与调用方层之间产生漂移。决定是在单一 Module 接口后集中规划路径投影。
Decision(完整继承)
helpers.planningPaths(projectDir, workstream?)是 SDK 规划路径投影的规范接口;helpers.planningPaths将策略委托给workspacePlanningPaths+resolveWorkspaceContext,而不是重复本地的路径组合;- 策略优先级显式且稳定:
explicit workstream > env workstream > env project > root; - query/init handler(
initExecutePhase、initPlanPhase、initPhaseOp、initMilestoneOp)必须消费planningPaths(...).planning,而不是直接relPlanningPath拼接; - SDK 的规划 project 作用域是
.planning/<project>(绝不是.planning/projects/<project>),与 CJS 规划工作区行为对齐。
Consequences
- 规划路径策略的一处修复即可更新所有 handler,缩小回归面;
- 测试可以针对缝行为(
workspace.test.ts、helpers.test.ts、init handler 测试)而不是 source-grep 启发式; - SDK 与 CJS 之间的规划路径解析跨包一致性 bug 更容易被发现与修正。
源码印证:投影策略在 SDK 中的真实实现
ADR 0006 的条款可以在 sdk/src/query/helpers.ts 与 sdk/src/query/workspace.ts 中逐条对上。
planningPaths:规范投影接口
sdk/src/query/helpers.ts 中planningPaths(projectDir, workstream?)的实现正是 ADR 描述的优先级链:
- 通过
resolveWorkspaceContext()读取GSD_WORKSTREAM/GSD_PROJECT环境变量(workspace.ts L95-L100 中即直接读process.env,未设置时为null); - 显式 workstream 参数优先于 env workstream(
workstream ?? validEnvWorkstream); - 若两者皆无但存在 env project,则委托给
workspacePlanningPaths(projectDir, { workstream: null, project: envCtx.project })——对应优先级链中的“env project”一档; - 兜底回退到根
.planning/。
返回值是统一的PlanningPaths结构:planning(基目录)、state(STATE.md)、roadmap(ROADMAP.md)、project(PROJECT.md)、config(config.json)、phases(phases/)、requirements(REQUIREMENTS.md),全部以 POSIX 格式返回。
环境变量的防御性处理
从源码结构看,实现比 ADR 多了一层防御:GSD_WORKSTREAM在使用前会先经过validateWorkstreamName校验,非法值静默回退到根.planning/而不是崩溃或路由到坏路径(helpers.ts 注释标明这是 bug-2791 的契约:非法 env 必须保持 #3269 之前的行为)。相应地,GSD_PROJECT/GSD_WORKSTREAM作为 workspace/project 名进入workspacePlanningPaths时,会拒绝空名、路径分隔符与..路径穿越(workspace.ts L65-L84 的validateWorkspaceName),防止环境变量被用于把规划路径构造出项目目录之外。
.planning/<project>而非.planning/projects/<project>
ADR 0006 的最后一条 Decision 在 workspace.ts L118-L145 中得到逐字印证:
- context 带 workstream 时,基目录为
.planning/workstreams/<ws>/; - context 带 project 时,基目录为
.planning/<project>/,源码注释明确写着“Match CJSplanningDir()policy: project scopes under.planning/<project>/(not.planning/projects/<project>/)”; - context 为空时回退根
.planning/。
这解释了为什么 ADR 要专门用一条 Decision 固化该细节:跨包(SDK 与 CJS)的路径作用域一旦不一致,就会出现“状态在一个目录写入、在另一个目录读取”的隐蔽漂移,而结构性约定加测试可以把这类 bug 提前拦截。
结构化测试:用代码锁定 ADR 文档质量
tests/enh-3271-sdk-adr-structure.test.cjs 是本次变更的可执行保障,基于node:test对 ADR 文档做解析式断言,而不是对散文做字符串匹配。
解析器设计
parseAdr(filePath)按标题行切分 Markdown,返回类型化记录{ title, headings, status, date }:
- 捕获第一个 H1 作为标题,收集全部 H2 并转小写;
- 从
**Status:**与**Date:**行提取元数据; - 行切分使用
/\r?\n/容忍 CRLF——测试注释解释了原因:Windows 检出(autocrlf=true)下\r\n会令## Decision匹配出"decision\r",破坏标题相等性判断。这是文档结构测试在跨平台 CI 上容易踩的真实坑。
parseReadmeIndex(filePath)则用链接正则/\[.*?\]\(\.?\/?([^)]+\.md)\)/g提取 README 中所有 Markdown 链接的文件名(basename)。
针对 ADR 0005 的断言
- 文件存在;
- 具有非空 H1 标题;
- 具有
**Status:**行与**Date:**行; - 具有
## Decision小节(缺失时错误信息会列出实际找到的标题集合); - 具有
## Consequences小节; - 至少交叉引用两份其他 ADR:从正文中提取形如
(\d{4}-....md)的链接目标与`0xxx-....md`代码段引用,剔除自身后要求数量 ≥ 2——这确保“顶层地图”确实是一张引用各缝 ADR 的地图,而不是孤立文本。
针对 ADR 0006 与 README 索引的断言
- ADR 0006 同样要求文件存在、H1 标题、Status/Date 元数据、
## Decision与## Consequences小节; - docs/adr/README.md 必须存在,且按文件名链接
0005-sdk-architecture-seam-map.md与0006-planning-path-projection-module.md; - 最强的一条:遍历 ADR 目录中所有匹配
/^\d{4}-.*\.md$/的文件,逐一断言 README 已链接——即“索引漏链任何一份 ADR 都会使测试失败”。这条断言让索引表随 ADR 数量增长而自动保持完整。
小结:文档治理与架构治理互为表里
这次 #3271 变更的价值可以用三句话概括:
- 索引可发现:docs/adr/README.md 让 ADR 从“散落文件”变成带命名规范(issue# 前缀)、状态列与缝地图导读的注册表,新决策的落点与流程都有章可循;
- 归属可判定:ADR 0005 的顶层地图 + ADR 0006 的路径投影条款,使“行为变化是否发生在归属缝 Module 之外”成为可执行的架构评审标准,而
planningPaths → workspacePlanningPaths + resolveWorkspaceContext的委托链正是该标准在 sdk/src/query/helpers.ts 中的落地; - 结构可回归:结构化测试把 ADR 的标题结构、元数据、交叉引用与索引完整性全部变成可回归的断言,文档漂移与代码漂移在同一套 CI 中被同时拦截。
对维护者而言,后续新增 ADR 的完整路径是:开 issue → 获批后以 issue# 命名文件 → 按 H1 + Status/Date + Decision + Consequences 结构撰写 → 在 README 索引表登记,任何一步缺失都会由 tests/enh-3271-sdk-adr-structure.test.cjs 的相应断言暴露出来。
【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考