ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

CJS↔SDK 硬接缝迁移完全指南:get-shit-done 如何用“单一事实源”消灭配置漂移缺陷

CJS↔SDK 硬接缝迁移完全指南:get-shit-done 如何用“单一事实源”消灭配置漂移缺陷 CJS↔SDK 硬接缝迁移完全指南get-shit-done 如何用“单一事实源”消灭配置漂移缺陷【免费下载链接】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-doneget-shit-done简称 GSD同时维护着一条 CJS 命令行实现get-shit-done/与一套 TypeScript SDKsdk/通过gsd-sdk query暴露能力。两套代码一旦对同一份配置 schema 或同一段业务逻辑各写一份就会在不知不觉中漂移并最终表现为“文档说这个键合法、命令却拒绝写入”一类的疑难缺陷。本文以仓库内 docs/agents/cjs-sdk-seam.mdIssue #3575父 Issue #3524为核心完整讲解这次CJS↔SDK 硬接缝hard-seam迁移它用哪些强制层handsync lint、freshness check、manifest 数据隔离、per-Module drift lint、runtime-bridge 委托在每一个“曾经 CJS 与 SDK 分道扬镳”的决策点上建立单一事实源以及如何一步步新增一个 Shared Module 或一个新的 canonical 命令。读完后你将掌握该仓库抵御“配置 schema 漂移类缺陷”的完整工程方法并能直接照搬其中的代码生成、新鲜度检查与奇偶校验流水线。迁移概览六阶段硬接缝落地硬接缝迁移的总目标是把 CJS 与 SDK 之间“手写平行实现”的关系替换为“SDK 单一事实源 生成器 CJS 适配产物”的关系。迁移共分六个阶段推进每阶段都以一个 PR 交付其中阶段 1state-document还充当了整套模式的样板工程阶段PR交付内容Phase 1PR #3531state-documentShared Module —— 事实源为 SDK 侧 TS 模块配套生成器、新鲜度检查与 CJS 适配产物state-document.generated.cjs作为整套模式的样板Phase 2PR #3540configurationShared Module —— 以sdk/shared/config-schema.manifest.jsonsdk/shared/config-defaults.manifest.json两个数据 manifest 作为单一事实源配套生成器 新鲜度检查 CJS 适配层Phase 3PR #3548workstream-inventoryShared Module —— 事实源位于sdk/src/workstream-inventory/配套 builder、生成器、新鲜度检查与 CJS 适配层Phase 4PR #3554project-rootShared Module —— 事实源位于sdk/src/project-root/配套生成器、新鲜度检查与 CJS 适配层Phase 5.0PR #3558runtime-bridge-syncworker —— 打通“CJS 侧执行 SDK 原生 handler”的能力state.*家族路由首批通过executeForCjs委托给 SDKPhase 5.1PR #3574state.*路由委托收尾 —— 全部已知 state 子命令经executeForCjs委托同时修复 Phase 5.0 worker 的 bugPhase 6PR #3577关闭 #3575强制层加固与最终收尾 —— hand-sync 漂移 lint、CODEOWNERS、6 个家族路由迁移、5 个 Shared Module 迁移plan-scan、secrets、schema-detect、decisions、workstream-name-policy、workstream 原生支持与奇偶性修复。迁移功能完成态22 个 cooperating siblings、0 个 backlog pairs这套“数据 manifest / TS 模块作为单一事实源 → 生成器产出跨运行时工件 → CI 用新鲜度脚本保证不漂移 → CJS 通过适配层消费生成物”的链路是后文所有机制的总骨架。问题动机15 个 config-schema 漂移缺陷的完整复盘迁移的直接动因是一类反复出现的缺陷CJS 与 SDK 各自维护配置校验器/allowlist只要其中一侧漏加一个键、改了一处归一化逻辑另一侧就会悄悄脱节。文档逐一记录了 15 个此类缺陷的“漂移点、修复方式、以及 Phase 6 哪一层强制机制本来可以拦住它”。由于其中多对缺陷共享同一个修复 PR下面按“缺陷簇”组织逐条保留每个 issue 的完整记录。簇 1#1535 未识别键被静默丢弃修复 PR #1542漂移点loadConfig会静默忽略.planning/config.json中所有不在VALID_CONFIG_KEYS里的顶层键——当用户手改配置、或外部工具写入自定义键时得不到任何反馈改动“无声失效”。修复PR #1542 在 stderr 输出一条警告列出所有未被识别的键。本可被拦截handsync lint。一个接缝感知的 lint 会禁止 CJSconfig.cjs与 SDKconfig-mutation.ts各自手写一份可能悄悄分叉的平行配置校验器。附带修复 issue#1542该 issue 本身就是修复 #1535 的 PR对它的防御点在于per-Module drift lintCJS 与 SDK 两条配置路径都从同一个 schema 模块重新生成从根上消除“静默丢弃”的风险。簇 2#2047 / #2052intel.enabled文档与运行时都认、allowlist 不认修复 PR #2021漂移点intel.enabled既出现在 workflow 文档中、也在运行时代码中被门控intel.cjs:58却漏在config.cjs的VALID_CONFIG_KEYS里于是config-set intel.enabled true被直接拒绝。修复PR #2021 把intel.enabled补进 CJS 的VALID_CONFIG_KEYS。本可被拦截handsync lint——强制约束“凡是在运行时代码中被读取、或写进 workflow 文档的配置键必须同时出现在校验器 allowlist 中”。簇 3#2638 / #2655sub_repos仍被写入顶层而非planning.sub_repos修复 PR #2668漂移点在 #2561 把sub_repos规范化为planning.sub_repos之后loadConfig里的遗留迁移逻辑与文件系统自动同步仍向顶层parsed.sub_repos写入随即被新校验器判定为“未知键”。修复PR #2668 重写两条写入路径使其落到parsed.planning.sub_repos并删除遗留的顶层副本。本可被拦截per-Module drift lint针对 config 形态的新鲜度检查。若sub_repos的规范位置被写进 schema任何向它写入的代码路径都要在 lint 时对照 schema 校验就不会出现新旧两层并存。附带修复 issue#2655与 #2638 同 PR #2668。簇 4#2653 / #2670 SDKconfig-set拒绝 CJSconfig-set接受的合法键修复 PR #2670漂移点SDK 的config-mutation.ts维护着一份手工VALID_CONFIG_KEYS它与 CJSconfig-schema.cjs相比漂移了 28 个键。于是文档明确支持的gsd-sdk query config-set planning.sub_repos等命令被 SDK 侧直接拒绝。修复PR #2670 抽取共享的sdk/src/query/config-schema.ts模块与 CJS 逐字对齐并新增奇偶性parity测试一旦未来再漂移测试即失败。仓库现存的 config-schema-sdk-parity.test.cjs 正是这一“以测试锁死集合相等”思路的落地可对照 config-schema.manifest.json 头部的_comment其中明确写了两侧集合由该测试强制相等。本可被拦截manifest 数据隔离。config schema 只应存在一处如sdk/shared/config.manifest.jsonCJS 与 SDK 都去读它独立漂移在结构上就不存在了。附带修复 issue#2670修复 #2653 的同一个 PR。簇 5#2687 / #2706 合法动态键被误报“未知键”修复 PR #2706漂移点review.models.cli这类键已登记在config-schema.cjs的DYNAMIC_KEY_PATTERNS中却不在core.cjs手工维护的KNOWN_TOP_LEVEL集合里导致.planning/config.json出现合法动态容器时触发误报。修复PR #2706 为DYNAMIC_KEY_PATTERNS条目增加topLevel字段让KNOWN_TOP_LEVEL直接从 schema 推导而非手工维护。本可被拦截per-Module drift lint——负责构造KNOWN_TOP_LEVEL的校验器每次运行都从 schema 重新生成而不是依赖手写副本。附带修复 issue#2706修复 #2687 的同一个 PR。簇 6#2798 / #2816context_window不在任何 allowlist 中修复 PR #2816漂移点context_window已在 workflow 文档中出现、且被 SDK 运行时代码读取init.js:190、validate.js:575却同时缺席config-mutation.ts与config-schema.cjs的 allowlist写入被拒。修复PR #2816 在 SDK 与 CJS 两侧都补上context_window。本可被拦截handsync lint——强制“运行中读到的每个键都必须在 allowlist 中”。附带修复 issue#2816修复 #2798 的同一个 PR。簇 7#3055 / #3116 顶层branching_strategy被悄悄归一化成none修复 PR #3116漂移点.planning/config.json里写了顶层branching_strategy: phase却被校验器判为未知键并丢弃loadConfig只得回退到none默认值——phase 提交最终落在了操作者当前分支上而不是创建gsd/phase-{N}分支。这是同类缺陷里破坏性最直观的一个。修复PR #3116SDK 侧在mergeDefaults()里先做遗留归一化把顶层值嫁接到规范的git.branching_strategy槽位再进入校验避免“值被校验器提前剥掉”。本可被拦截per-Module drift lint——branching_strategy的规范位置应被 schema 固化校验器不应当在迁移逻辑有机会运行前就把值剥掉更进一步CJS 与 SDK 应当共享同一份迁移代码而不是各写一份、各修一次。附带修复 issue#3116同 PR且注释中明确指出“若通过接缝层共享SDK 侧修复会自动同步到 CJS无需单独移植”。簇 8#3523 CJS 一边警告“该键将被忽略”、一边实际读取该键修复 PR #3527漂移点这是“单向移植造成的新不对称”。PR #3116 修好 SDK 后CJS 路径对同一个遗留顶层键仍打印错误的 “will be ignored” 警告。原因是KNOWN_TOP_LEVEL的推导逻辑从VALID_CONFIG_KEYS只含git.branching_strategy、不含branching_strategy抽取顶层名而警告本身与事实相悖——core.cjs:485实际会通过 fallback 逻辑读取这个遗留值。修复PR #3527 在KNOWN_TOP_LEVEL手工列表的 deprecated-keys 桶中加入branching_strategy压制这条虚假警告。本可被拦截runtime-bridge 委托。如果 CJS 与 SDK 的配置加载共享同一条归一化例程通过executeForCjs或共享 seam 模块#3116 的修复会自动作用于 CJS也就根本不存在“CJS 侧单独写一份警告逻辑”的可能。Surprises无15 个缺陷全部属于货真价实的 CJS↔SDK schema/validation 漂移正是接缝迁移要消灭的那一类。文档明确记录为None。接缝迁移的五层强制机制对上面每一簇“本可被拦截”做归类可以收敛出五层强制机制。它们彼此正交、互相兜底构成了迁移的核心方法论handsync lintscripts/lint-shared-module-handsync.cjs 禁止 CJS/SDK 两侧并行手写校验器。凡需要两侧共用逻辑的地方都要求走生成/适配而不是各写一份。可拦截#1535、#2047、#2798。freshness checksdk/scripts/check-module-fresh.mjs一族 每次运行把生成器重放到临时目录与已提交的生成物做 diff不一致即以非零码退出并给出清晰提示。CI 跑的就是它。可拦截#2687、#3055。仓库内已有 check-state-document-fresh.mjs、check-configuration-fresh.mjs、check-decisions-fresh.mjs 等一整套。manifest 数据隔离sdk/shared/*.manifest.json schema 类数据只存在于 sdk/shared/config-schema.manifest.json、sdk/shared/config-defaults.manifest.json 这类数据清单里两侧共同消费从结构上消灭“28 键漂移”的可能。可拦截#2653。per-Module drift lint “新鲜度检查 由 schema 推导的 allowlist”的组合形态强调派生数据如KNOWN_TOP_LEVEL一律从 schema 生成而非手写。可拦截#2638、#2687、#3055。runtime-bridge 委托executeForCjs 共享 seam 模块 消灭“同一条逻辑在 CJS/SDK 各实现一遍”。CJS 路由把命令委托给 SDK 原生 handler自然不再存在可独立漂移的 CJS 侧实现。可拦截#3523。这五层之所以能整体消灭该缺陷族核心是同一句话在每个决策点上强制执行单一事实源。深入原理executeForCjs同步桥是怎么工作的五层机制中技术含量最高的是runtime-bridge-sync。CJS 模块化体系有“同步 require、无顶层 await”的限制无法直接调用基于 Promise 的异步 SDK 桥接层。executeForCjs的存在就是让 CJS 调用方可以同步拿到 SDK 命令的执行结果。其实现位于 sdk/src/runtime-bridge-sync/index.ts关键机制是基于synckit内部由Atomics.waitSharedArrayBufferworker_threads组成把异步的QueryRuntimeBridge.execute()放到一个 worker 线程中执行调用线程则阻塞等待结果worker 在首次调用时懒启动之后被复用。源文件注释给出的量级是首次调用worker 启动 原生桥构建约 80 ms稳态下单次调用约 0.1 ms不含 handler 自身开销。它的消费方式CJS 侧形如const { executeForCjs } require(gsd-build/sdk/dist/runtime-bridge-sync/index.js); const result executeForCjs({ registryCommand: generate-slug, registryArgs: [My Phase], ... }); if (result.ok) console.log(result.data); // { slug: my-phase }返回结果是一个可辨识联合discriminated unionok: true时携带data与exitCode: 0ok: false时携带exitCode、errorKind六类错误之一来自 ADR-0001 Dispatch Policy Moduleunknown_command、native_failure、native_timeout、fallback_failure、validation_error、internal_error、errorDetails与stderrLines。必须特别小心的一条红线源码 JSDoc 反复强调executeForCjs底层用了Atomics.wait会阻塞调用线程。如果从主线程事件循环上的async函数内部调用它事件循环被阻塞后将无法处理 worker 回传的消息直接死锁。安全调用方只有两类require 期同步模块初始化求值的 CJS 模块、以及不使用事件循环的 worker 线程。这也是为什么 Phase 5.0 起要配套独立的 workersdk/src/runtime-bridge-sync/worker.ts来承载原生桥执行。实操指南一新增一个 Shared Module7 步当你要把一段“CJS 与 SDK 目前各写一份”的数据或逻辑抽取为共享模块时按下面 7 步走。Phase 1 的state-document迁移就是完整样板。Step 1 —— 创建单一事实源文件sdk/src/module/index.ts这是规范定义可以导出 schema、键集合、类型或数据对象。约束不得 import CJS 代码也不得 import 任何生成文件。Phase 1 的实际样板可参考 sdk/src/query/state-document.ts 及配套生成器 sdk/scripts/gen-state-document.ts。Step 2 —— 编写生成器脚本sdk/scripts/gen-module.mjs生成器读取sdk/src/module/index.ts纯数据模块则读sdk/shared/module.manifest.json产出生成文件要么是sdk/src/module.generated.ts要么是 CJS 侧适配物get-shit-done/bin/lib/module.generated.cjs并以退出码 0 收尾。生成器必须幂等连续运行两次输出完全一致。仓库中已落地的生成物如 state-document.generated.cjs、configuration.generated.cjs、decisions.generated.cjs 等都在get-shit-done/bin/lib/下。Step 3 —— 编写新鲜度检查sdk/scripts/check-module-fresh.mjs把生成器重跑进临时目录与已提交文件 diff一旦分叉就以退出码 1 清晰信息失败。CI 跑的就是它。Step 4 —— 编写奇偶性测试建议但不强制tests/module-parity.test.cjs断言“CJS 适配层”与“SDK 单一事实源”在每一个关键字段上完全一致键集合、默认值、schema 形态。它能捕获新鲜度检查抓不到的生成器 bug。样板见 state-document-generator.test.cjs、configuration-generator.test.cjs。Step 5 —— 接入 CI在.github/workflows/test.yml中、现有 freshness-check 区块之后“Run tests with coverage”之前追加一步门控条件为matrix.os ubuntu-latest matrix.node-version 24- name: SDK generated module artifact drift check if: matrix.os ubuntu-latest matrix.node-version 24 shell: bash run: node sdk/scripts/check-module-fresh.mjsStep 6 —— 更新 inventory 登记如果该模块会影响CONTEXT.md的模块清单同步更新对应小节同时更新 scripts/shared-module-handsync-allowlist.json把相关条目从migrateMeBacklog移到cooperatingSiblings若 CJS 手写副本已被删除则直接移除。Step 7 —— 更新 CODEOWNERS把新的单一事实源路径加入.github/CODEOWNERS的 Phase 6 区块让架构归属在所有权层面显式化。参考样板Phase 1 PR #3531 ——state-document迁移。实操指南二新增一个 canonical 命令4 步当你要新增一条gsd-sdk query family.subcommand、且希望它在 SDK 内原生处理而不是回委托给 CJS时按下面 4 步走。Phase 5.1 的state.update迁移PR #3574是样板。Step 1 —— 在命令 manifest 中声明把命令定义加入sdk/src/query/command-manifest.family.ts包含完整参数 schema 与handler引用。仓库中按家族拆分的 manifest 实例如 command-manifest.state.ts、command-manifest.phase.ts、command-manifest.roadmap.ts。Step 2 —— 实现 SDK handlerhandler 写在sdk/src/query/subcommand.ts简单场景也可内联进 manifest 文件。handler 接收经过校验的参数与运行时上下文不得 shell-out 回 CJS。state 家族的原生实现可参考 sdk/src/query/state-mutation.ts。Step 3 —— 添加 CJS 路由委托Phase 5.1 模式在家族对应的 CJS 命令路由如state家族的state-command-router.cjs中加一个 delegate case调用cjs-command-router-adapter.cjs暴露的executeForCjs(subcommand, args)。这样 CJS 二进制会把命令派发给 SDK 原生 handler而不是自己再实现一遍逻辑——这正是消灭“可独立漂移的第二份实现”的关键动作。Step 4 —— 添加 golden 奇偶性测试在tests/family-command-router.test.cjs若该家族尚无测试则新建文件中加入断言走 SDK query 路径调用命令走 CJS router 路径调用命令断言两者输出完全一致。该测试强制委托路径与原生 handler 始终保持对齐。参考样板Phase 5.1 PR #3574 ——state.update委托。Phase 6 最终完成汇总Phase 6issue #3575PR #3577已经 feature-complete迁移整体宣告完成。最终态的关键数据22 个 cooperating siblings、0 个 backlog pairs。Phase 6 的交付清单本轮迁移的 Shared Module共 5 个plan-scan、secrets、schema-detect、decisions、workstream-name-policy。每个都走完整模式SDK 单一事实源 → 生成器gen-name.mjs→ 新鲜度检查check-name-fresh.mjs→ 生成的 CJS 工件name.generated.cjs→ CJS shim 再导出 → 奇偶性测试 → CI 步骤 → pre-commit 钩子 → CODEOWNERS 条目。对应脚本在 sdk/scripts 下可逐一核对如 gen-decisions.mjs 与 check-decisions-fresh.mjs。workstream 原生支持同步桥 worker 现在能把workstream正确穿透到registry.dispatch()GSDTransport不再对 workstream 作用域的请求强制走子进程。workstream 作用域的 state 命令以原生方式执行。state 奇偶性分叉已解决state.record-metric与state.prune的 SDK handler 现在与 CJS 语义完全一致。MIGRATE_ME 对已清零decisions与workstream-name-policy以ADAPTER-OVER-MODULE模式从migrateMeBacklog晋升到cooperatingSiblings。lint 终态22 个 cooperating siblings、0 个 backlog pairs。decisions 迁移细节B1SDK 侧 decisions 源文件的解析正则与 CJS 对齐为D-([A-Za-z0-9_-])从而接受D-INFRA-01这类字母数字 IDSDK 返回更丰富的{id, text, category, tags, trackable}而只消费{id, text}的 CJS 调用方可安全忽略多余字段向后兼容。奇偶性测试见 tests/decisions-generator.test.cjs15 个用例覆盖数字 ID、字母数字 ID 与更丰富 schema 字段。workstream-name-policy 迁移细节B2SDK 侧 sdk/src/workstream-name-policy.ts 新增hasInvalidPathSegment与isValidActiveWorkstreamNamevalidateWorkstreamName现为isValidActiveWorkstreamName的别名与 CJS 语义一致配套 19 个奇偶性用例覆盖全部四个导出。生成/检查脚本为 gen-workstream-name-policy.mjs 与 check-workstream-name-policy-fresh.mjs。遗留的开放事项Open follow-ups文档明确说明没有遗留的迁移项。以下三项属于“未来质量候选”而非缺陷config.cjs/ sdk/src/config.ts按 allowlist 分类属于CJS-CLI-ONLY。config.cjs只含使用同步 CJS API 的 CLI 命令 handlersdk/src/config.ts提供 async SDK 层二者服务互不相交的 surface。未来迁移需要把 CLI handler 转成 async SDK 模式属超出本迁移周期范围的大型重构。intel.cjs/sdk/src/query/intel.ts属于刻意的架构分叉CJS 与 SDK 采用不同的文件命名约定已在 allowlist 中记录。未来迁移需要调和INTEL_FILES命名而这会对现有消费者构成破坏性变更。model-catalog.cjs/ sdk/src/model-catalog.ts两侧各自独立读取 sdk/shared/model-catalog.jsonADAPTER-OVER-MODULE 模式这是有意为之——共享 JSON 就是单一事实源CJS 与 SDK 消费方之间并不存在逻辑重复。结语从“各写一份”到“只写一份”回看这次迁移最有价值的产出或许不是代码本身而是一套可复用的治理纪律凡是两类运行时都要用的 schema 与逻辑就只允许存在一个事实源其余一律是它的生成物或适配层。handsync lint在提交阶段挡住手写平行实现freshness check 在 CI 阶段抓住生成物过期manifest 数据隔离从结构上让“两套清单”无从谈起executeForCjs则让 CJS 直接复用 SDK 原生实现、彻底消灭“第二份实现”。若你也在维护多语言、双运行时或 monorepo 中多处消费同一配置 schema 的系统get-shit-done 在 docs/agents/cjs-sdk-seam.md 里沉淀的这 15 个缺陷样本、五层强制机制与两份操作指南是一份极具参照价值的工程档案。【免费下载链接】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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进