omo/lazycodex 常量文件拆分实战:delegate-task constants.ts 重构执行计划深度解析
2026/9/5 17:56:01 网站建设 项目流程

omo/lazycodex 常量文件拆分实战:delegate-task constants.ts 重构执行计划深度解析

【免费下载链接】oh-my-openagentomo/lazycodex: The coding agent for tokenmaxxers;the one and only agent harness for complex codebases. For your Codex, for your OpenCode项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent

在 omo/lazycodex(oh-my-openagent)这个由大量 TypeScript 包组成的 agent harness 中,task委托工具的constants.ts曾是一个 654 行、身兼 6 种职责的"上帝常量文件"。本文基于仓库中一份真实的重构执行计划(execution-plan),完整还原"如何安全拆分一个被 10+ 处内部/外部模块重度依赖的常量文件"的全流程:从预检分析、职责盘点、导入依赖映射,到逐文件拆分、桶(barrel)重导出与零消费者变更的提交策略,并结合当前仓库源码验证该计划的实际落地形态。读完本文,你能掌握一套可迁移到任何中大型 TypeScript 项目的"零消费者破坏"模块拆分方法论。

一、计划背景:为什么要拆 constants.ts

执行计划的 Context 部分给出了触发拆分的三个硬事实:

  1. src/tools/delegate-task/constants.ts当时为654 行,承载6 种不同职责,违反了项目的200 LOC 模块代码约束规则(modular-code-enforcement);
  2. 其中被普遍引用的CATEGORY_MODEL_REQUIREMENTS实际并不在constants.ts中,而是在src/shared/model-requirements.ts(当时 311 行,同样违反 200 LOC 规则);
  3. 因此本次重构是"双文件拆分":拆分constants.ts本体,同时把CATEGORY_MODEL_REQUIREMENTSmodel-requirements.ts中剥离。

这个文件之所以危险,是因为它集中了task委托工具的全部静态知识:内置分类的默认配置、分类描述、分类专属 prompt 附加段、plan 智能体的系统提示词与身份判定逻辑。任何一个拼写错误或误删导出,都会波及整个委托执行链路。

二、预检分析:职责盘点与 LOC 核算

执行计划的第一步不是动手,而是把两个文件的所有职责逐项列出并估算行数:

constants.ts 的 6 项职责

#职责内容规模
1Category prompt appends8 个模板字符串常量约 274 行 prompt 文本
2DEFAULT_CATEGORIESRecord<string, CategoryConfig>约 10 行
3CATEGORY_PROMPT_APPENDS分类 → prompt 映射约 10 行
4CATEGORY_DESCRIPTIONS分类 → 描述映射约 10 行
5Plan agent prompts2 个模板字符串 + 4 个构建函数约 250 行 prompt 文本
6Plan agent identity utilsisPlanAgentisPlanFamily约 30 行

model-requirements.ts 的 3 项职责

  1. 类型定义(FallbackEntryModelRequirement);
  2. AGENT_MODEL_REQUIREMENTS(约 146 行);
  3. CATEGORY_MODEL_REQUIREMENTS(约 148 行)。

值得注意:prompt 文本类代码虽然行数巨大,但在 modular-code-enforcement 规则下豁免 200 LOC 限制——这一点在计划中针对 2c(约 280 行)与 2d(约 270 行)两个新文件被明确标注。也就是说,拆分目标不是机械地把行数砍到 200 以下,而是按职责边界切分,prompt 文本天然聚类的文件允许超限。

三、导入依赖映射:拆分安全性的前提

计划的核心洞察是:只要保留桶文件(barrel)重导出,所有消费者就一行都不用改。但前提是先把依赖面摸清楚。计划列出了三类消费者:

内部消费者(delegate-task/ 目录内)

文件导入符号
categories.tsDEFAULT_CATEGORIESCATEGORY_PROMPT_APPENDS
tools.tsCATEGORY_DESCRIPTIONS
tools.test.tsDEFAULT_CATEGORIESCATEGORY_PROMPT_APPENDSCATEGORY_DESCRIPTIONSisPlanAgentPLAN_AGENT_NAMESisPlanFamilyPLAN_FAMILY_NAMES
prompt-builder.tsbuildPlanAgentSystemPrependisPlanAgent
subagent-resolver.tsisPlanFamily
sync-continuation.tsisPlanFamily
sync-prompt-sender.tsisPlanFamily
index.tsexport * from "./constants"(桶文件)

外部消费者(import from"../../tools/delegate-task/constants"

文件导入符号
agents/atlas/prompt-section-builder.tsCATEGORY_DESCRIPTIONS
agents/builtin-agents.tsCATEGORY_DESCRIPTIONS
plugin/available-categories.tsCATEGORY_DESCRIPTIONS
plugin-handlers/category-config-resolver.tsDEFAULT_CATEGORIES
shared/merge-categories.tsDEFAULT_CATEGORIES
shared/merge-categories.test.tsDEFAULT_CATEGORIES

CATEGORY_MODEL_REQUIREMENTS 的消费者

文件导入路径
tools/delegate-task/categories.ts../../shared/model-requirements

这些依赖在当前仓库中依然可以逐一验证,说明该计划的预检映射是准确的:

  • prompt-builder.ts 中import { buildPlanAgentSystemPrepend, isPlanAgent } from "./constants",并在 prompt 组装时以isPlanAgent(agentName)为开关注入 plan 系统提示词;
  • sync-continuation.ts 与 sync-prompt-sender.ts 都从./constants引入isPlanFamily,分别用于"恢复会话时是否允许 task 工具"(const allowTask = isPlanFamily(resumeAgent))和"同步 prompt 路由时是否放行 task";
  • 外部消费者 builtin-agents.ts、available-categories.ts、atlas/prompt-section-builder.ts 至今仍从../tools/delegate-task/constants导入CATEGORY_DESCRIPTIONS,category-config-resolver.ts 导入DEFAULT_CATEGORIES并实现"用户自定义分类优先、内置分类兜底"(userCategories?.[categoryName] ?? DEFAULT_CATEGORIES[categoryName])。

依赖图还揭示了一个跨层语义isPlanFamily不只是提示词注入开关,它同时控制"互斥委托阻断"与"task 工具权限"——plan 家族(plan + prometheus)是编排者,可以下发task调用,这正是后续 constants.ts 中PLAN_FAMILY_NAMES = ["plan", "prometheus"]COORDINATOR_AGENT_NAMES守卫存在的根源。

四、分步执行:从建分支到提 PR

Step 1:创建分支

git checkout -b refactor/split-category-constants dev

分支命名直接体现重构意图与目标(refactor/split-*),基线为dev

Step 2:把 constants.ts 拆分为 5 个职责单一的文件

2a.default-categories.ts
  • 移入DEFAULT_CATEGORIESrecord;
  • 从 config schema 导入CategoryConfig类型;
  • 约 15 行。
2b.category-descriptions.ts
  • 移入CATEGORY_DESCRIPTIONSrecord;
  • 无依赖;
  • 约 12 行。
2c.category-prompt-appends.ts
  • 移入全部 8 个*_CATEGORY_PROMPT_APPEND模板字符串常量;
  • 移入CATEGORY_PROMPT_APPENDS映射 record;
  • 无依赖(全部是自带上下文、自包含的模板字符串);
  • 约 280 行(以 prompt 文本为主,豁免 200 LOC 限制)。
2d.plan-agent-prompt.ts
  • 移入PLAN_AGENT_SYSTEM_PREPEND_STATIC_BEFORE_SKILLS
  • 移入PLAN_AGENT_SYSTEM_PREPEND_STATIC_AFTER_SKILLS
  • 移入renderPlanAgentCategoryRows()renderPlanAgentSkillRows()
  • 移入buildPlanAgentSkillsSection()buildPlanAgentSystemPrepend()
  • 依赖:来自 agents 的AvailableCategoryAvailableSkill类型,来自 shared 的truncateDescription
  • 约 270 行(以 prompt 文本为主,豁免)。
2e.plan-agent-identity.ts
  • 移入PLAN_AGENT_NAMESisPlanAgent()
  • 移入PLAN_FAMILY_NAMESisPlanFamily()
  • 无依赖;
  • 约 35 行。

拆分顺序暗含依赖考量:2a–2c、2e 完全无依赖、可独立验证;2d 是唯一带跨模块类型依赖的文件,因此放在最后,且计划中显式标注了它需要导入的类型来源。

Step 3:把 constants.ts 改写为桶重导出文件

原文件全部内容替换为对 5 个新文件的 re-export。这一步是整个计划的枢纽:它让所有既有导入者保持 100% 向后兼容——内部消费者、外部消费者、桶文件index.tsexport *全部照常工作。

Step 4:拆分 model-requirements.ts

4a. 新建src/shared/category-model-requirements.ts
  • 移入CATEGORY_MODEL_REQUIREMENTSrecord;
  • ./model-requirements导入ModelRequirement类型;
  • 约 150 行。
4b. 更新src/shared/model-requirements.ts
  • 删除CATEGORY_MODEL_REQUIREMENTS
  • 增加重导出:export { CATEGORY_MODEL_REQUIREMENTS } from "./category-model-requirements"
  • 保留类型(FallbackEntryModelRequirement)与AGENT_MODEL_REQUIREMENTS
  • 收敛到约 165 行(低于 200 行红线)。

Step 5:验证导入无破坏

  • bun run typecheck—— 确认所有 import 可解析;
  • bun test—— 确认无行为回归;
  • bun run build—— 确认构建成功。

Step 6:LSP 诊断检查

对所有新建与修改文件检查lsp_diagnostics是否为空。这一条把验证从"编译器/测试通过"进一步收紧到"无诊断告警",防止死代码、未使用导出等隐性问题进入主干。

Step 7:提交并创建 PR

  • 单个原子提交:refactor: split delegate-task constants and category model requirements into focused modules
  • 附 PR 描述。

原子提交保证 code review 时 diff 只有"移动 + 重导出",没有任何行为混入,reviewer 可以确信这是一次纯结构性重构。

五、文件变更清单与"零消费者变更"原则

文件动作
src/tools/delegate-task/constants.ts改写为桶重导出
src/tools/delegate-task/default-categories.ts新增
src/tools/delegate-task/category-descriptions.ts新增
src/tools/delegate-task/category-prompt-appends.ts新增
src/tools/delegate-task/plan-agent-prompt.ts新增
src/tools/delegate-task/plan-agent-identity.ts新增
src/shared/model-requirements.ts删除 CATEGORY_MODEL_REQUIREMENTS,增加重导出
src/shared/category-model-requirements.ts新增

对任何消费者文件零修改。全部既有导入经由桶重导出继续生效。这是本文最值得内化的一条原则:重构的安全边界不靠小心修改消费者来维持,而靠重导出契约来维持——消费者越多,这一原则的价值越大。

六、当前仓库验证:计划的实际落地形态

从源码结构看,该执行计划在 omo/lazycodex 仓库中已被(部分)落地,且实际演化与计划同构但略有差异,这本身就是"计划驱动重构 + 后续持续演化"的真实样本:

  1. 分类 record 已迁出 constants.ts。当前 constants.ts 只有 414 行(从 654 行收敛),其开头就是标准的桶重导出:

    export { BUILTIN_CATEGORY_REQUIRES_MODEL, CATEGORY_DESCRIPTIONS, CATEGORY_PROMPT_APPENDS, CATEGORY_PROMPT_APPEND_RESOLVERS, DEFAULT_CATEGORIES, } from "./builtin-categories"

    DEFAULT_CATEGORIESCATEGORY_PROMPT_APPENDSCATEGORY_DESCRIPTIONS等 record 已迁入 builtin-categories.ts。值得注意的是,实际实现没有把三份 record 机械拆成三个文件,而是收敛为一份BUILTIN_CATEGORY_DEFINITION[](按 Google/OpenAI/Anthropic/Kimi 分组),再通过统一的buildCategoryRecord()工厂派生出各 record——从源码结构看,这是比计划更进一步的"单一数据源"设计,消除了计划中三份 record 各自维护的潜在漂移风险。

  2. constants.ts 保留了计划 2d/2e 对应的职责PLAN_AGENT_SYSTEM_PREPEND_STATIC_BEFORE_SKILLS/AFTER_SKILLS两大模板字符串、renderPlanAgentCategoryRowsrenderPlanAgentSkillRowsbuildPlanAgentSkillsSectionbuildPlanAgentSystemPrepend,以及PLAN_AGENT_NAMESisPlanAgentPLAN_FAMILY_NAMESisPlanFamily等身份工具仍留在该文件中。plan agent 的系统提示词本身就是"先派发 explore/librarian 智能体收集上下文、再输出依赖图 + 并行执行波次 + 分类/技能推荐"的强制协议——这也解释了为什么这类 prompt 文本天然豁免 200 LOC 限制。

  3. model-requirements 拆分已完成并进一步下沉到共享包。当前 shared/model-requirements.ts 已是一个仅 5 行的重导出垫片(shim),把FallbackEntryModelRequirement类型与AGENT_MODEL_REQUIREMENTSCATEGORY_MODEL_REQUIREMENTS全部转发到@oh-my-opencode/model-core。而 packages/model-core/src/category-model-requirements.ts(131 行)正是计划 Step 4a 所描述的新家:CATEGORY_MODEL_REQUIREMENTSrecord 按分类提供fallbackChain,例如visual-engineering分类的降级链为claude-opus-5(max)kimi-k3(max)glm-5.2(max)gpt-5.6-sol(medium),每项都带 providers 白名单与推理档位(variant)。类型定义则落在 model-requirement-types.ts。消费端 categories.ts 仍然按计划中的路径../../shared/model-requirements导入CATEGORY_MODEL_REQUIREMENTS并在分类解析时查表(const categoryReq = CATEGORY_MODEL_REQUIREMENTS[categoryName])——消费者零变更原则在真实代码中得到兑现

  4. 委托链文档与计划相互印证。delegate-task 目录的 AGENTS.md 描述了该目录的双执行模式(background/sync)、同步执行链(sync-task.ts → sync-session-creator.ts → sync-prompt-sender.ts → sync-session-poller.ts → sync-result-fetcher.ts)与分类解析流程(用户自定义分类优先,回退到内置分类)。计划中依赖映射涉及的sync-prompt-sender.tssync-continuation.tscategories.ts都是这条链上的一环,说明拆分所触及的正是委托执行链的核心静态层。

七、方法论提炼:如何安全拆分一个"上帝常量文件"

把这份执行计划抽象成可复用流程,共五步:

  1. 先盘点职责,再动手:为源文件逐条列出职责与行数,明确哪些内容豁免行数规则(如 prompt 文本),避免按行数机械切分导致语义割裂;
  2. 画完整导入依赖图:区分内部消费者、外部消费者、桶文件三类,按符号粒度记录(哪个文件 import 了哪些导出)。这一步决定了桶文件必须保留哪些重导出;
  3. 按职责建文件,无依赖者优先:无外部依赖的 record/常量先拆(可独立验证),带跨模块类型依赖的模块最后拆,并在计划中显式列出依赖来源;
  4. 桶重导出兜底:源文件改写为 re-export 层,实现"消费者零变更";被拆分出的大块(如CATEGORY_MODEL_REQUIREMENTS)同样在新家加类型导入、在旧位置加重导出;
  5. 三层验证 + LSP 诊断typecheck(导入可解析)→test(行为无回归)→build(构建成功)→lsp_diagnostics(无隐性问题),全部通过后再以单个原子提交 + PR 描述收尾。

这套流程在 omo/lazycodex 的实际演化中经受住了检验:即使后续架构调整(内置分类改为按 provider 分组的定义数组、模型需求下沉到 model-core 共享包)偏离了原计划的文件名与文件数,重导出契约始终保护着所有消费者——这正是"以文档为计划、以源码为契约"的重构工程实践。

【免费下载链接】oh-my-openagentomo/lazycodex: The coding agent for tokenmaxxers;the one and only agent harness for complex codebases. For your Codex, for your OpenCode项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent

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

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

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

立即咨询