☰
gsd-core TypeScript 迁移 Batch 8(ADR-457):命令路由器、surface 与 roadmap-upgrade 模块的 build-at-publish 编译链路
2026/9/25 6:02:43 网站建设 项目流程

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载

本篇技术文章围绕 gsd-core 仓库中归档的变更集 .changeset/archived/migration-batch-8-ts.md 展开:Batch 8 将cjs-command-router-adapter、phase-command-router、surface、roadmap-upgrade四个模块从手写 CommonJS 迁移为 TypeScript 源码(src/*.cts),由tsc在发布时编译为被 git 忽略的.cjs产物。读完本文,你将掌握 ADR-457「build-at-publish」生成模型的决策依据、四个模块的迁移落点与关键源码结构,以及tsconfig.build.json中.cts → .cjs的编译链路细节。

一、变更集原文:Batch 8 迁了什么

关联文档 migration-batch-8-ts.md 是一份已归档的 changeset,完整内容如下(frontmatter + 正文 + 文档豁免注释):

type: Changed pr: 537 Migrate 4 modules from hand-written CommonJS to TypeScript source of truth per ADR-457 (#537). Modules migrated: cjs-command-router-adapter, phase-command-router, surface, and roadmap-upgrade. Each src/<m>.cts compiles to a gitignored get-shit-done/bin/lib/<m>.cjs with behaviour preserved byte-for-behaviour; only strict types are added. <!-- docs-exempt: Internal ADR-457 build-at-publish source migration; behaviourally-identical gitignored artifacts at same require() paths; no user-facing change. -->

可以从中提取出本批次迁移的五个关键事实:

  1. 变更类型为Changed(行为保持不变的能力变更,而非 Breaking);
  2. 迁移对象为 4 个模块:cjs-command-router-adapter、phase-command-router、surface、roadmap-upgrade;
  3. 单一事实源从bin/lib/<m>.cjs(手写)转移到src/<m>.cts(TypeScript);
  4. 行为契约是 "byte-for-behaviour"(行为逐字节等价),只增加 strict 类型,不改运行时语义;
  5. 对用户无感——产物落在与原require()路径相同的位置,因此文档系统标注docs-exempt,无需更新任何用户面向文档。

从归档目录结构看,这类迁移是成系列推进的:.changeset/archived/下存在 migration-batch-2-ts.md 至 migration-batch-15-ts.md 等一批同构 changeset,例如 Batch 7 迁移的是planning-workspace、runtime-artifact-layout、command-routing-hub、drift(见 migration-batch-7-ts.md)。Batch 8 是这个逐模块增量迁移序列中的一个标准批次。

二、ADR-457:为什么选择 build-at-publish 生成模型

Batch 8 的依据是 docs/adr/457-generated-cjs-single-source.md(状态 Accepted)。理解这份 ADR 是理解整批迁移的前提,因为它解释了"为什么产物不入 git"。

2.1 两种都叫"生成"的技术,只有一种是刚需

ADR-457 首先厘清了一个容易混淆的点——仓库里"生成"一词指代两种完全不同的技术:

  • 值烘焙(value baking,已存在且被迫):唯一的@generated文件 gsd-core/bin/lib 中的package-identity.cjs由 scripts/generate-package-identity.cjs 生成——一个读取package.json并把坐标字面值"烤进" CJS 模块的纯 Node 脚本。其存在是被安装树结构强制的:安装后的目录中不存在带.name的package.json,运行时require('package.json').name要么undefined要么MODULE_NOT_FOUND,值只能在构建期烘焙。删除生成器,复杂度会散布到每个消费者,因此它是一个"深层接缝"。
  • 转译(transpilation,本次提议):把bin/lib的逻辑用 TS 编写、由tsc输出.cjs。ADR 的"删除测试"结论很尖锐:删掉转译管线后什么都不会重新出现——手写的.cjs与tsc产出的.cjs在运行时行为完全一致。转译的接缝不购买任何运行时杠杆,其全部价值在于编写期与 CI 的类型检查。

因此package-identity不构成转译工作的先例;二者是不同的技术、不同的强制函数。

2.2 三种"生成物归宿"模型与决策

ADR 把承重问题归结为:生成的.cjs是入库还是作为构建产物?

模型做法评价
1. 双入库.ts源码与.cjs产物都提交 git制造永久性"两份副本必须一致"不变量,需要 parity 测试、双提交、CI 漂移门禁;对转译而言纯属无运行时收益的摩擦,拒绝
2. 发布时构建(采纳)bin/lib/*.cjs成为 gitignored 构建产物,由tsc从src/TS 树产出,npm 发布构建后的产物无漂移不变量、无 parity 测试、无双提交;代价是本地开发与 CI 需要先构建再测试,采纳
3. 安装时构建用户安装时编译跨 Node 版本与平台脆弱、拖慢每次安装,拒绝

ADR 采纳模型 2 的可行性论证:package.json的files数组已经发布gsd-core与scripts目录,且已有prepublishOnly预发布构建步骤——.cjs输出钩挂进同一步骤即可,npm pack会包含磁盘上的产物而不管.gitignore。

决策清单共五条:以 TSsrc/树 +tsc追求类型安全(模型 2);值烘焙独立保留(package-identity.cjs维持现状);增量迁移、从耦合最低的模块开始,先以一个试点 PR 建立src/树与构建接线;先把 lint 配置与现实对齐(修正对 12 个手写文件的"已生成"误标);随模块逐个转为 TS 而接入类型感知 lint,直到最后一个手写.cjs消失才退役tsconfig.lint.json。

2.3 对测试行为的影响

ADR 明确指出主要行为变化:测试若 importbin/lib/*.cjs,只有构建已执行时才能工作——测试命令必须依赖构建步骤。这与".cjs永远在树中"的旧状态不同。这也是为什么本批次 changeset 能标注docs-exempt:require()路径未变,产物行为一致,用户侧零感知,而内部约束(先构建后测试)由 CI 接线承担。

三、Batch 8 四个模块的源码落点

四个模块迁移后均位于src/下,文件扩展名为.cts(CommonJS TypeScript),且每个文件头部都带有同一段 ADR-457 迁移说明注释,这是本系列迁移的统一标记规范:

ADR-457 build-at-publish: the hand-writtenbin/lib/<m>.cjscollapsed to a TypeScript source of truth. Behaviour is preserved byte-for-behaviour from the prior hand-written.cjs; only types are added.

3.1cjs-command-router-adapter:CJS 命令族路由适配器

源码:src/cjs-command-router-adapter.cts(共 196 行,全文可见)。

该模块为gsd-tools.cjs命令族提供兼容路由,核心导出两个函数(文件末尾export = { routeCjsCommandFamily, routeHubCommandFamily }):

  • routeCjsCommandFamily:以family: '__legacy_cjs_family__'委托给 Hub 路由;
  • routeHubCommandFamily:基于CommandRoutingHub的族路由适配器,把各路由器的"查找 + 错误处理"分支收敛为 Hub 的带类型Result契约(见 src/command-routing-hub.cts)。

从源码结构看,迁移时新增的类型集中在 src/cjs-command-router-adapter.cts#L36-L64:Handler类型与RouteCjsCommandFamilyOptions/RouteHubCommandFamilyOptions两个选项接口,后者对error回调签名做了"诚实化"加宽——由单参加宽为(message: string, reason?: string) => void,以匹配io.cts中error()实际接受第二参ERROR_REASON的运行时契约。

另一个迁移中保留的精细行为细节(src/cjs-command-router-adapter.cts#L145-L152):向 Hub 注入观测日志器仅在isAuditEnabled()为真时进行,否则注入undefined让 Hub 回退到 no-op logger——这正是"byte-for-behaviour"承诺的具体体现:审计未开启时,默认分派输出(含--json-errors包络)保持逐字节静默。

3.2phase-command-router:phase 子命令的路由器

源码:src/phase-command-router.cts(共 328 行)。

文件头注释(L1-L16)说明了模块职责:清单驱动的 phase 子命令路由器,保持gsd-tools.cjs薄化且现有命令语义不变;scaffold不由本路由器处理(走顶层 scaffold 命令);mvp-mode、tdd-applicable是 CJS-only 子命令,在 Hub 之前直接分派。

迁移新增的类型包括 L33-L50 的PhaseHandlers接口——把cmdPhaseAdd、cmdPhaseInsert(含allocation?: 'nested' | 'sibling')、cmdPhaseComplete、cmdPhaseUatPassed(含policy选项)等 10 个 handler 的签名全部显式化,以及RoutePhaseCommandOptions。这些类型把此前隐藏在函数体里的参数契约(例如opts: { force: boolean })固化为可被tsc --strict检查的声明。

3.3surface:运行时能力面状态管理

源码:src/surface.cts(共 825 行,是本批次体量最大的模块)。

该模块管理运行时的启用/禁用"能力面"状态——每个运行时配置目录根(如~/.claude)中的.gsd-surface.json标记,与安装期的 profile 标记(.gsd-profile)相互独立(见文件头 L1-L29)。有效技能集的算法在头注释中一句话给出:

Effective skill set = base profile ∪ explicitAdds − disabledClusters − explicitRemoves,再经 manifest 传递闭包。

导出面为readSurface/writeSurface/resolveSurface/applySurface/listSurface/pruneSkillDirs六个函数。头注释还记录了 ADR-857 phase 4c 引入的可选registry参数:提供能力注册表对象时,capability 簇会并入有效簇映射;缺省时行为与注册表出现前完全一致。

从源码结构看,迁移后该模块依赖关系全部类型化声明:installProfiles、real-home-guard以import x = require('./...cjs')的 CJS 互操作形式引入(配@typescript-eslint/no-require-imports禁用注释),CLUSTERS与ClusterMap类型分别从./clusters.cjs按值/按类型导入——export =风格模块与import x = require()的配合,正是 ADR 中"tsc CJS 互操作细节"这一开放问题在落地代码中的直接答案。

3.4roadmap-upgrade:Phase ID 迁移工具

源码:src/roadmap-upgrade.cts(共 654 行)。

该模块负责把遗留的Phase N形式 phase ID 转换为带里程碑前缀的Phase M-NN形式。迁移后可见的核心正则(L23-L35):

  • LEGACY_PHASE_HEADING_RE:匹配遗留标题### Phase N: Name(含小数形式Phase 2.1:,可选[...]方括号前缀),分组捕获哈希数、阶段号与行内其余部分;
  • MIGRATED_PHASE_HEADING_RE:匹配已迁移的### Phase M-NN: Name,用于幂等判断;
  • MILESTONE_HEADING_RE:匹配## v1.0、## Roadmap v2.0、## ✅ v1.0等里程碑小节标题。

配套的ParsedPhaseEntry/AssignedMapping接口(L39-L50)把"哪一行、旧编号是什么、已迁移与否"解析为结构化数据。值得注意的是,这些正则并非迁移时新写——PHASE_NUMBER_TOKEN_SOURCE直接复用自 src/phase-id.cts,避免同一模式在升级工具与 ID 解析器中双写漂移。

四、构建接线:.cts如何编译为 gitignored.cjs

Batch 8 的编译行为由 tsconfig.build.json 定义,文件头注释直接点明其角色:

ADR-457 build-at-publish: compile TS runtime sources in src/ to gitignored .cjs artifacts under gsd-core/bin/lib/. Source uses the .cts extension so tsc emits .cjs natively. As modules migrate, they move from hand-written bin/lib/.cjs into src/.cts here.

关键编译器选项:

选项取值作用
rootDir/outDirsrc/gsd-core/bin/libTS 源树与产物目录一一对应,src/<m>.cts落到bin/lib/<m>.cjs,保持原require()路径
module/moduleResolutionnodenext按 Node 原生规则解析,.cts扩展名在此模式下天然产出.cjs(无需改扩展名)
target/libES2022/["ES2022", "ES2025.RegExp"]运行时基线为 ES2022;额外拉入 ES2025 正则类型(如roadmap-upgrade用到的正则特性)
stricttrue即 changeset 所承诺的 "only strict types are added"
esModuleInteroptrue支撑源码中import x = require(...)与export =互操作
noEmitOnErrortrue类型错误即阻断产物输出,防止带病.cjs进入发布包
incremental/tsBuildInfoFiletrue/tsconfig.build.tsbuildinfo增量编译,加速逐批次迁移期间的构建
declaration/sourceMapfalse/false产物最小化,不附带 d.ts 与 sourcemap

另有 tsconfig.json 继承tsconfig.build.json但叠加"noEmit": true,作为编辑器/CI 的类型检查配置——即开发期tsc只查错不出产物,出产物仅发生在发布构建链路中。两个配置共同落实了 ADR-457 "发布时构建"模型:类型检查与产物产出分离。

一个命名细节需要说明:changeset 原文写产物路径为get-shit-done/bin/lib/<m>.cjs,而当前 tsconfig.build.json 的outDir是gsd-core/bin/lib。从归档 changeset 604-rename-get-shit-done-to-gsd-core.md 的存在可推断,该目录经历过get-shit-done→gsd-core的重命名;两者指向的是同一类产物目录,阅读旧 changeset 时应按此对应。

五、迁移纪律与行为不变式

Batch 8 的四个文件共同体现了一套可复用的迁移纪律,也是后续 batch(9–15)沿用的模式:

  1. 头注释契约:每个迁移文件头部的 ADR-457 注释块是审计锚点,明确记录"原手写文件已坍缩为 TS 单一事实源、行为逐字节保留、仅加类型"。搜索ADR-457 build-at-publish即可定位全部已迁移模块。
  2. 类型只加不改:以phase-command-router为例,PhaseHandlers接口是把既有运行时参数形态显式化,而非改变分派逻辑;surface的六个导出函数签名保持不变。
  3. lint 豁免的最小化使用:源码中import x = require('./xxx.cjs')形式仅在被导入模块使用export =值导出时出现,并逐行附// eslint-disable-next-line @typescript-eslint/no-require-imports——豁免是行级、可审计的,不是目录级放行。
  4. 观测行为冻结:isAuditEnabled()决定是否注入日志器(见 3.1 节),保证默认输出(含 stderr 行数)与迁移前一致。
  5. 验证闭环:产物路径不变意味着既有针对bin/lib/*.cjs的测试在构建后继续有效;ADR-457 同时要求测试命令依赖构建步骤,这是该模型下唯一的流程变化点。
  6. 类型感知 lint 渐进接入:按 ADR 决策第 5 条,随着src/*.cts模块增多,类型感知 lint 接入真实的tsconfig.json;配套的 ESLint 配置与自定义规则集见 eslint.config.mjs 及 eslint-rules/ 目录(ADR-452 是其前置,状态 Accepted)。

六、小结

.changeset/archived/migration-batch-8-ts.md 记录的不是一次孤立的"换语言",而是 ADR-457 所确立的 build-at-publish 生成模型在运行时表面上的标准推进动作:src/<m>.cts成为单一事实源,tsc(配置见 tsconfig.build.json)在发布链路中输出 gitignored 的bin/lib/<m>.cjs,require()路径与运行时行为逐字节不变。Batch 8 覆盖的cjs-command-router-adapter、phase-command-router、surface.cts、roadmap-upgrade.cts 分别对应命令族路由、phase 子命令分派、运行时能力面状态、Phase ID 升级工具四条关键链路——读者可沿文中给出的相对路径在当前仓库中逐一核对类型声明、正则契约与互操作写法,验证"只加类型、不改行为"这一迁移承诺的落地方式。

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载
上一篇:WebLLM WASM:怎么定制浏览器端大模型
下一篇:Ornith-1.0-9B:解决智能编码代理部署复杂性的开源方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询