get-shit-done 文件操作安全策略演进实录:从 ADR-0010「文件操作引擎模块」到 Shell 命令投影接缝的收敛设计
导读
本指南解读 get-shit-done(GSD,一个面向 Claude Code 的 meta-prompting 与 spec-driven 开发系统)中的一份关键架构决策文档:ADR-0010 提出的 File Operation Engine Module(文件操作引擎模块)。文档记录了安装器、迁移与规划链路中文件读写的安全策略(原子写、路径包含、锁文件、备份/回滚)如何从多处分叉收敛为单一接缝,以及该提案为何在次日被 ADR-0009(Shell Command Projection Module)的 Phase 3–4 扩充所吸收取代。读完本文,你将掌握 GSD 当前统一的原子写 / .md 规范化 / 锁文件语义的实现位置与调用方式,理解"为什么计划被简化掉"的架构判断过程,并可直接在源码中定位 platformWriteSync、withPlanningLock 等真实实现。
ADR-0010 的定位与命运:被吸收而非被推翻
ADR-0010 的状态字段给出了理解全文的第一把钥匙:
- Status: Superseded by ADR-0009(Shell Command Projection Module 扩充,Phase 3–4,
#3467–#3468) - Date: 2026-05-12
- Superseded: 2026-05-13
也就是说,这份 ADR 从提出到被取代仅隔一天。但它不是一份失败文档,其价值恰恰在于完整记录了被放弃的设计路线(typed plan IR 引擎)以及最终采纳的更简替代方案。Supersession note 明确写道:
与其构建独立的 File Operation Engine,本 ADR 提议的文件变更安全策略被并入 Shell Command Projection Module(ADR-0009)。Phase 3(
#3467)为该接缝新增了platformWriteSync/platformReadSync/platformEnsureDir/normalizeContent,以单一的平台条件化表面统一持有原子写(tmp+rename)、.md规范化与目录创建;Phase 4(#3468)则从core.cjs移除了重复的atomicWriteFileSync/safeReadFile/normalizeMd包装函数。下文提议的applyFileMutationPlan/ typed plan IR 设计并未构建——对实际漂移点而言,更简单的按调用接缝已被证明足够。
这段结论是阅读全文后最值得带回的信息:架构演进中,被证明更简单的替代方案同样是一种胜利。
问题背景:跨三条链路的文件变更漂移
ADR 开篇描述了驱动该提案的现状问题:
今天,文件变更行为在
bin/install.js、get-shit-done/bin/lib/installer-migrations.cjs以及多个规划模块中重复出现,且原子写保证、路径安全检查与归属分类存在漂移。
具体表现为三类文件变更职责散落在多个模块:
- installer(
bin/install.js):本地自带atomicWriteFileSync与临时文件清理注册表;运行时配置与 hook 中存在大段内联的 read/modify/write + backup/rollback 逻辑。 - migration(
get-shit-done/bin/lib/installer-migrations.cjs):维护一套单独的writeFileAtomicSync、回滚日志、锁处理与路径包含检查。 - planning(
get-shit-done/bin/lib/planning-workspace.cjs、state.cjs等):重复实现锁文件的创建/释放/删除模式与 best-effort 清理语义。
同一类操作(比如"安全地覆写一个配置文件")在三条链路上各有各的写法,导致原子性保证、.md 内容规范化和归属判定(managed vs user 文件)逐步发散,形成独立的 bug 类别。
决策内容:设立单一接缝、双轨迁移
ADR-0010 的 Decision 提出四件事:
- 在
get-shit-done/bin/lib/下新增 File Operation Engine Module,作为文件变更安全策略的单一接缝(single seam)。 - 命令文本投影继续留在 Shell Command Projection Module(ADR-0009),但投影相邻的 hook 文件变更统一走共享的 managed-hook 归属策略。
- 文件操作适配器分两条轨道迁移:
- Track A(投影相邻,projection-adjacent):运行时配置的 hook 命令检测/重写/删除路径,消费投影接缝的共享 managed-hook 策略。
- Track B(方案级,solution-wide):共享文件操作引擎统一持有原子写、路径包含、锁行为、回滚记账与 best-effort 清理策略。
- 内部子进程执行不进入该接缝(与 ADR-0009 同边界):这是文件操作接缝,不是命令执行器。
初始范围:四个统一目标
Initial Scope 将上述意图细化为四条可验收的收敛目标:
- 统一 managed-hook 归属分类:install/uninstall/migration 各处的 hook 配置重写所依赖的"这条 hook 命令是不是 GSD 管理的"判定逻辑。
- 统一原子写行为:目前散落在 installer/core/migration 路径中、行为互不一致的原子写实现。
- 统一锁文件生命周期策略:规划工作区与安装器迁移日志流程共用的锁创建/释放/清理。
- 暴露类型化的文件变更计划 IR 供测试断言:包含
rewrite-json、rewrite-text(带toml/markdown/plain三种格式)、delete-file、backup-file、restore-file、ensure-dir等操作类型。
迁移盘点:精确到文件的漂移清单
ADR 用两节逐一列出现存的漂移点,这是全文最具"审计价值"的部分,也是后续代码重构的直接输入。
Track A:投影相邻的文件变更漂移
| 位置 | 漂移内容 |
|---|---|
bin/install.js |
hook 清理命令检测(isGsdHookCommand);过期 Codex hook 剥离 basename 列表(STALE_HOOK_BASENAMES);settings/config 中 hook 条目的修剪/重写路径 |
get-shit-done/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs |
isManagedCodexHookCommand 的正则/路径检测,是从安装器自有 hook 策略复制而来 |
get-shit-done/bin/lib/shell-command-projection.cjs |
isManagedHookBasename 已持有该策略的一部分,应成为规范归属方 |
Track B:方案级文件操作漂移
| 位置 | 漂移内容 |
|---|---|
bin/install.js |
本地 atomicWriteFileSync 与临时清理注册表;运行时配置与 hook 的大段内联 read/modify/write + 备份/回滚逻辑 |
get-shit-done/bin/lib/core.cjs |
atomicWriteFileSync 辅助函数在回退行为上与 installer/migration 变体分叉 |
get-shit-done/bin/lib/installer-migrations.cjs |
独立的 writeFileAtomicSync、回滚日志、锁处理与路径包含检查 |
get-shit-done/bin/lib/planning-workspace.cjs、state.cjs |
重复的锁文件 create/release/remove 模式与 best-effort 清理语义 |
roadmap.cjs、phase.cjs、milestone.cjs、frontmatter.cjs、drift.cjs |
直接的 read/modify/write 流程,原子性与规范化策略应用不一致 |
注意一个结构细节:state.cjs、roadmap.cjs、frontmatter.cjs、drift.cjs 等模块都处理 Markdown 状态文档或需求文档,它们的"直接改写"与 ADR-0009 后来提供的 .md 感知规范化(normalizeContent)密切相关——这正是 Track B 清单将规范化策略纳入统一范围的原因。
接口草案:类型化文件变更计划 IR
ADR 为未竟的方案给出了接口草图。规划引擎应当暴露类型化的变更计划与执行器:
planFileMutations({
rootDir,
operations: [
{ type: 'rewrite-json', relPath, mutate },
{ type: 'rewrite-text', relPath, mutate, format: 'toml' | 'markdown' | 'plain' },
{ type: 'delete-file', relPath },
{ type: 'ensure-dir', relPath },
],
ownership: { mode: 'managed-only' | 'allow-user', classifier },
})
applyFileMutationPlan({
plan,
atomic: true,
rollback: true,
lock: { scope: 'config' | 'planning', id: '...' },
})
面向投影相邻路径,适配器应当消费投影策略:
isManagedHookCommand(commandText, { surface, configDir })
该草案的亮点在于设计意图本身:把"要做什么变更"(类型化 IR)与"以何种安全策略执行"(原子性、回滚、锁)分离,使测试可以直接对计划 IR 与 reason code 断言,而非依赖源码正则搜索或复制一份判定谓词镜像。
后果与开放问题:这份 ADR 留下的判断框架
Consequences
- 文件变更安全策略收敛到单一模块,降低 installer/migration/planning 三条路径之间的漂移。
- Shell 命令投影与 hook 归属分类在同一接缝家族内保持对齐。
- 测试可断言类型化 mutation IR 与 reason code,取代源码 grep 与重复谓词镜像。
- 初始迁移面广;排序上应优先处理投影相邻的 hook 配置路径,随后再收敛原子写与锁语义。
Open questions(后文逐一给出走向)
- 锁语义应作为 installer + planning 的单一共享策略,还是基于同一锁原语的两个适配器?——最终答案见下文
withPlanningLock。 - SDK query 写路径是否应在第一轮就消费同一引擎,还是等待 CJS 收敛后跟进?
- 文件变更遥测(按操作的 reason codes 与回滚事件)是否应对所有引擎适配器强制要求?
最终落地:被 ADR-0009 吸收后的真实接缝
这一节是文档从"提案"走向"现实"的桥梁。ADR-0009 的 Update(2026-05-13)记录了三项范围扩充,而 ADR-0010 的文件变更安全策略正落于其中两项:
- Phase 3(
#3467):平台文件 I/O ——platformWriteSync/platformReadSync/platformEnsureDir/normalizeContent。 - Phase 4(
#3468):从core.cjs移除遗留包装atomicWriteFileSync/safeReadFile/normalizeMd。
现状一:平台文件 I/O 已收归 shell-command-projection.cjs
在 get-shit-done/bin/lib/shell-command-projection.cjs 中,ADR 提议的原子写与规范化语义以四个函数收束(约 第 484–524 行):
normalizeContent(filePath, content, opts):根据扩展名分流——.md文件走 Markdown 规范化(补齐标题/围栏/列表前后的空行并压缩多余换行),其余文本统一\r\n→\n并确保文件以单个\n结尾;opts.encoding默认utf-8。platformWriteSync(filePath, content, opts):实现原子写tmp + rename——先mkdirSync(dirname, { recursive: true }),写入filePath + '.tmp.' + process.pid后再renameSync覆盖目标;若 rename 失败则尽力删除临时文件并回退为直接写入(保证无悬挂临时文件)。platformReadSync(filePath, opts):读取文件;ENOENT时若opts.required为真则抛错,否则返回null。platformEnsureDir(dirPath):mkdirSync(dirPath, { recursive: true })。
它们的导出位于该模块导出表(第 526–552 行),同表还包含投影核心的 isManagedHookBasename(第 148 行)与 isManagedHookCommand(第 164 行)——这正是 ADR-0010 期望 shell-command-projection.cjs "成为 managed-hook 归属策略规范归属方"的兑现。
现状二:锁文件语义仍由 withPlanningLock 独立持有
ADR-0010 的 Supersession note 对 Open question 1 给出了明确答案:锁文件生命周期没有并入共享引擎,仍由 planning-workspace.cjs 中的 withPlanningLock 持有。理由很本质:锁的 { flag: 'wx' } 排他创建语义与原子写的 rename 语义不同,不应强行合并为一种策略。
在 planning-workspace.cjs 中可看到实际接线:模块顶部从投影接缝导入 probeTty, platformWriteSync, platformReadSync, platformEnsureDir(第 14 行),而 withPlanningLock 定义于第 248 行,核心在 第 262 行 使用 { flag: 'wx' } 创建锁文件。也就是说:原子写走共享接缝、锁文件走专用原语、二者各司其职,正是 ADR 讨论后收敛出的最终架构形态。
现状三:core.cjs 遗留包装已移除、消费面铺开
Phase 4 移除的 atomicWriteFileSync / safeReadFile / normalizeMd 已被 platformWriteSync / platformReadSync / normalizeContent 替代。从源码结构看,当前仓库中 core.cjs、state.cjs、roadmap.cjs、phase.cjs、milestone.cjs、frontmatter.cjs、drift.cjs、template.cjs、verify.cjs、audit.cjs、graphify.cjs、surface.cjs、intel.cjs、learnings.cjs、commands.cjs、config.cjs 等二十余个 bin/lib 模块均已引用或间接消费上述文件操作/规范化能力——ADR-0010 盘点表中的 Track B 漂移点大体完成了向统一语义的迁移。
测试与后续验证方向
ADR 的两处措辞隐含了可验证断言,也呼应仓库中既有的测试结构:
- typed IR 断言优于源码 grep:可在
tests/shell-command-projection-dispatch.test.cjs、tests/installer-migrations.test.cjs一类测试中观察接缝行为如何被直接验证。 - 遗留回归 bug 清单:ADR 参考文献列出
#1755、#2866、#2979、#3002、#3017、#3439等历史缺陷。其中 hook 绝对路径、跨 shell 引号、SDK 路径诊断等类别在仓库中均有对应回归测试(如tests/bug-2979-hook-absolute-node.test.cjs、tests/bug-3011-sdk-path-diagnostic.test.cjs、tests/bug-3017-codex-hook-absolute-node.test.cjs),可作为继续追踪这一 bug 类收敛效果的切入点。
给架构读者的经验总结
- 接缝的取舍以"是否值得独立成模块"为准:ADR-0010 提议的类型化计划引擎最终未构建,因为实际漂移点的复杂度没有高到需要一层 IR 抽象——逐调用的
platformWriteSync等函数即足够。 - 语义不同的操作不要强行共用一个抽象:原子写(rename 语义)与锁文件(
wx排他创建语义)最终保留为两种独立策略,这本身就是一个 ADR 级结论。 - 被取代的 ADR 依然有档案价值:它完整记录了漂移现状(可作为 drift 审计底稿)、替代方案的否定理由与收敛后的归属关系,是理解
shell-command-projection.cjs为何同时持有"投影 + 子进程 + 文件 I/O"三项职责的第一手资料。
关联文档与延伸阅读
- 取代本 ADR 的决策:docs/adr/0009-shell-command-projection-module.md(含 Phase 3–4 扩充的完整说明)
- 相邻的安装器迁移模块决策:docs/adr/0008-installer-migration-module.md
- 当前接缝实现:get-shit-done/bin/lib/shell-command-projection.cjs
- 锁文件生命周期实现:get-shit-done/bin/lib/planning-workspace.cjs
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00