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 部分给出了触发拆分的三个硬事实:
src/tools/delegate-task/constants.ts当时为654 行,承载6 种不同职责,违反了项目的200 LOC 模块代码约束规则(modular-code-enforcement);- 其中被普遍引用的
CATEGORY_MODEL_REQUIREMENTS实际并不在constants.ts中,而是在src/shared/model-requirements.ts(当时 311 行,同样违反 200 LOC 规则); - 因此本次重构是"双文件拆分":拆分
constants.ts本体,同时把CATEGORY_MODEL_REQUIREMENTS从model-requirements.ts中剥离。
这个文件之所以危险,是因为它集中了task委托工具的全部静态知识:内置分类的默认配置、分类描述、分类专属 prompt 附加段、plan 智能体的系统提示词与身份判定逻辑。任何一个拼写错误或误删导出,都会波及整个委托执行链路。
二、预检分析:职责盘点与 LOC 核算
执行计划的第一步不是动手,而是把两个文件的所有职责逐项列出并估算行数:
constants.ts 的 6 项职责
| # | 职责 | 内容 | 规模 |
|---|---|---|---|
| 1 | Category prompt appends | 8 个模板字符串常量 | 约 274 行 prompt 文本 |
| 2 | DEFAULT_CATEGORIES | Record<string, CategoryConfig> | 约 10 行 |
| 3 | CATEGORY_PROMPT_APPENDS | 分类 → prompt 映射 | 约 10 行 |
| 4 | CATEGORY_DESCRIPTIONS | 分类 → 描述映射 | 约 10 行 |
| 5 | Plan agent prompts | 2 个模板字符串 + 4 个构建函数 | 约 250 行 prompt 文本 |
| 6 | Plan agent identity utils | isPlanAgent、isPlanFamily | 约 30 行 |
model-requirements.ts 的 3 项职责
- 类型定义(
FallbackEntry、ModelRequirement); AGENT_MODEL_REQUIREMENTS(约 146 行);CATEGORY_MODEL_REQUIREMENTS(约 148 行)。
值得注意:prompt 文本类代码虽然行数巨大,但在 modular-code-enforcement 规则下豁免 200 LOC 限制——这一点在计划中针对 2c(约 280 行)与 2d(约 270 行)两个新文件被明确标注。也就是说,拆分目标不是机械地把行数砍到 200 以下,而是按职责边界切分,prompt 文本天然聚类的文件允许超限。
三、导入依赖映射:拆分安全性的前提
计划的核心洞察是:只要保留桶文件(barrel)重导出,所有消费者就一行都不用改。但前提是先把依赖面摸清楚。计划列出了三类消费者:
内部消费者(delegate-task/ 目录内)
| 文件 | 导入符号 |
|---|---|
categories.ts | DEFAULT_CATEGORIES、CATEGORY_PROMPT_APPENDS |
tools.ts | CATEGORY_DESCRIPTIONS |
tools.test.ts | DEFAULT_CATEGORIES、CATEGORY_PROMPT_APPENDS、CATEGORY_DESCRIPTIONS、isPlanAgent、PLAN_AGENT_NAMES、isPlanFamily、PLAN_FAMILY_NAMES |
prompt-builder.ts | buildPlanAgentSystemPrepend、isPlanAgent |
subagent-resolver.ts | isPlanFamily |
sync-continuation.ts | isPlanFamily |
sync-prompt-sender.ts | isPlanFamily |
index.ts | export * from "./constants"(桶文件) |
外部消费者(import from"../../tools/delegate-task/constants")
| 文件 | 导入符号 |
|---|---|
agents/atlas/prompt-section-builder.ts | CATEGORY_DESCRIPTIONS |
agents/builtin-agents.ts | CATEGORY_DESCRIPTIONS |
plugin/available-categories.ts | CATEGORY_DESCRIPTIONS |
plugin-handlers/category-config-resolver.ts | DEFAULT_CATEGORIES |
shared/merge-categories.ts | DEFAULT_CATEGORIES |
shared/merge-categories.test.ts | DEFAULT_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 的
AvailableCategory、AvailableSkill类型,来自 shared 的truncateDescription; - 约 270 行(以 prompt 文本为主,豁免)。
2e.plan-agent-identity.ts
- 移入
PLAN_AGENT_NAMES、isPlanAgent(); - 移入
PLAN_FAMILY_NAMES、isPlanFamily(); - 无依赖;
- 约 35 行。
拆分顺序暗含依赖考量:2a–2c、2e 完全无依赖、可独立验证;2d 是唯一带跨模块类型依赖的文件,因此放在最后,且计划中显式标注了它需要导入的类型来源。
Step 3:把 constants.ts 改写为桶重导出文件
原文件全部内容替换为对 5 个新文件的 re-export。这一步是整个计划的枢纽:它让所有既有导入者保持 100% 向后兼容——内部消费者、外部消费者、桶文件index.ts的export *全部照常工作。
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"; - 保留类型(
FallbackEntry、ModelRequirement)与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 仓库中已被(部分)落地,且实际演化与计划同构但略有差异,这本身就是"计划驱动重构 + 后续持续演化"的真实样本:
分类 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_CATEGORIES、CATEGORY_PROMPT_APPENDS、CATEGORY_DESCRIPTIONS等 record 已迁入 builtin-categories.ts。值得注意的是,实际实现没有把三份 record 机械拆成三个文件,而是收敛为一份BUILTIN_CATEGORY_DEFINITION[](按 Google/OpenAI/Anthropic/Kimi 分组),再通过统一的buildCategoryRecord()工厂派生出各 record——从源码结构看,这是比计划更进一步的"单一数据源"设计,消除了计划中三份 record 各自维护的潜在漂移风险。constants.ts 保留了计划 2d/2e 对应的职责:
PLAN_AGENT_SYSTEM_PREPEND_STATIC_BEFORE_SKILLS/AFTER_SKILLS两大模板字符串、renderPlanAgentCategoryRows、renderPlanAgentSkillRows、buildPlanAgentSkillsSection、buildPlanAgentSystemPrepend,以及PLAN_AGENT_NAMES、isPlanAgent、PLAN_FAMILY_NAMES、isPlanFamily等身份工具仍留在该文件中。plan agent 的系统提示词本身就是"先派发 explore/librarian 智能体收集上下文、再输出依赖图 + 并行执行波次 + 分类/技能推荐"的强制协议——这也解释了为什么这类 prompt 文本天然豁免 200 LOC 限制。model-requirements 拆分已完成并进一步下沉到共享包。当前 shared/model-requirements.ts 已是一个仅 5 行的重导出垫片(shim),把
FallbackEntry、ModelRequirement类型与AGENT_MODEL_REQUIREMENTS、CATEGORY_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])——消费者零变更原则在真实代码中得到兑现。委托链文档与计划相互印证。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.ts、sync-continuation.ts、categories.ts都是这条链上的一环,说明拆分所触及的正是委托执行链的核心静态层。
七、方法论提炼:如何安全拆分一个"上帝常量文件"
把这份执行计划抽象成可复用流程,共五步:
- 先盘点职责,再动手:为源文件逐条列出职责与行数,明确哪些内容豁免行数规则(如 prompt 文本),避免按行数机械切分导致语义割裂;
- 画完整导入依赖图:区分内部消费者、外部消费者、桶文件三类,按符号粒度记录(哪个文件 import 了哪些导出)。这一步决定了桶文件必须保留哪些重导出;
- 按职责建文件,无依赖者优先:无外部依赖的 record/常量先拆(可独立验证),带跨模块类型依赖的模块最后拆,并在计划中显式列出依赖来源;
- 桶重导出兜底:源文件改写为 re-export 层,实现"消费者零变更";被拆分出的大块(如
CATEGORY_MODEL_REQUIREMENTS)同样在新家加类型导入、在旧位置加重导出; - 三层验证 + 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),仅供参考