63 个脚本挤一个目录是什么体验

用 AI Agent 自动化日常工作流之后,脚本数量增长得比想象中快得多。健康数据追踪、财务记账、情报监控、内容发布、记忆管理……每个功能背后都是一两个脚本,全部塞在一个 scripts/ 文件夹里。

10 个脚本的时候还好,肉眼能扫完。长到 60 多个的时候,打开目录就是一堵墙。哪个脚本属于哪个功能?不知道。哪些脚本之间有依赖?不知道。想单独迁移"健康追踪"到另一台机器?做梦。

更致命的是配置散落:18 个 Notion 数据库 ID、54 个 Discord 频道 ID,全部硬编码在 30 多个脚本里。这不是技术债,这是定时炸弹。定时任务一跑,有的脚本还在往旧数据库写数据——就像你以为已经关了所有窗户,结果暴雨天发现阳台那扇忘了。

重构的核心约束:不能中断服务

重构目标本身不难想清楚:按功能拆模块、配置集中管理、依赖显式化、可独立测试和迁移。难的是系统在线上跑着,几十个定时任务每小时都在触发。

这就像给飞行中的飞机换引擎。

所以有一条铁律贯穿始终:任何时刻,现有功能不能断。哪怕重构到一半,系统也必须正常工作。 这条约束直接决定了后续所有设计决策。

五个阶段,每一步都能回滚

整个重构不是一口气干完,而是拆成五个独立交付的阶段,出问题随时回滚。

Phase 1:先建基础设施——config-loader

在动任何脚本之前,先写好统一的配置读取模块。它的逻辑很简单:每个模块目录里放一个 config.json,脚本启动时调用 loadModuleConfig(import.meta.url),自动根据当前脚本路径找到所属模块的配置文件。

import.meta.url 的好处是脚本不需要知道自己在哪个目录。从 A 模块移到 B 模块,它自动读 B 的配置,零改动。这个设计是整个重构的基石,后面所有"把硬编码换成配置读取"的工作都建立在它之上。

Phase 2:建目录,移脚本,留 symlink

这是最关键也最容易翻车的一步。创建 13 个模块目录,按功能职责归类,每个模块统一结构:MODULE.md(说明文档)+ config.json(配置)+ scripts/(脚本)。

脚本一移动,原来的路径就失效了,几十个定时任务还指着旧路径。解法是 symlink 过渡:每个脚本移到新位置后,在原来的 scripts/ 目录创建一个 symlink 指向新位置。所有定时任务和外部引用都不受影响——它们访问的路径没变,只是背后的文件位置变了。

这招的精髓在于:把"移动文件"和"更新引用"这两件事解耦了。 不需要一次性改完所有引用,今天改 10 个定时任务的路径,明天改 10 个,每改完一批就删除对应的 symlink。只要 symlink 还在,旧路径就能用,这就是安全网。

Phase 3:替换硬编码

脚本搬完家之后,开始动内部代码。把硬编码的数据库 ID、频道 ID 全部替换成 loadModuleConfig() 读取。不是一次全改,而是一个模块一个模块来,改完跑一遍确认没问题再改下一个。

最终 24 个脚本完成配置迁移,72 个硬编码 ID 收敛到各模块的 config.json 里。现在要换一个数据库 ID?打开对应模块的 config.json,改一行,完事。

Phase 4:更新定时任务路径

大约 50 个定时任务需要从旧路径切换到新路径。最枯燥但最不能出错的一步。策略很直接:改一个,测一个,不批量改。改完后跑一轮全量检查,确认所有任务都指向新路径。

Phase 5:清理收尾

删除所有 symlink,更新文档,创建模块索引。到这一步系统已经完全运行在新架构上,删除 symlink 只是撤掉已经不需要的安全网。

三个值得反复琢磨的设计决策

模块边界怎么划? 最初想过按数据源划分——所有跟 Notion 交互的放一起,所有跟 Google 交互的放一起。但一个健康追踪脚本可能同时读 Google Fit 数据、写 Notion 数据库、发 Discord 通知,按数据源划分它属于哪个模块?最终选择了按业务职责划分:不管调用什么 API,只要服务于"健康追踪"这个目标,就归到健康模块。

13 个模块最终的划分:基础设施、健康追踪、财务管理、记忆系统、内容创作、生活管理、情报监控、运维管理、图书管理、英语学习、安全审计、深度反思、每日简报。最大的模块有 14 个脚本,最小的只有 1 个。大小不均匀完全没关系,模块边界是为了职责清晰,不是为了代码量均分。

跨模块依赖怎么处理? 设一个 shared 基础设施模块,所有公共依赖放在这里,其他模块通过显式引用来使用。关键原则:只有 shared 可以被所有模块引用,模块之间不能互相引用。 如果 A 模块需要调用 B 模块的功能,要么把这个功能抽到 shared,要么说明这两个模块的边界划错了。这条规则避免了最头疼的网状依赖——你想迁移一个模块,结果发现它依赖另外三个模块,而那三个又互相依赖。

渐进迁移还是一步到位? 一步到位的诱惑很大,但全量改动的测试成本是指数级的。改一个脚本,测试这一个就行;一次改 60 个,得测试所有脚本的所有交互,出了 bug 还不知道是哪个改动引入的。渐进迁移的代价是过渡期会有新旧混合的状态,但这个代价远小于一次性改崩的风险。当然,如果你对 AI 的能力有足够信心,也可以让 AI 一步到位地完成。

重构前后的体感差异

  • 改配置:从 grep 全目录逐个文件修改、祈祷没漏掉,到打开一个 config.json 改一行结束
  • 新增功能:从往 scripts/ 里扔一个脚本、过三个月忘了它干嘛的,到在对应模块里加脚本、读 MODULE.md 就知道上下文
  • 排查问题:从在 60 多个脚本里搜索相关的,到直接进对应模块、所有相关脚本都在
  • 迁移部署:从"不可能,拔出萝卜带出泥",到把一个模块目录整个拷走、带上 shared 模块就能独立运行

硬编码残留从 72 个收敛到 1 个(非关键)。整个重构过程零中断,没有一个定时任务失败,没有一条数据丢失。

什么时候该动手

如果你的 Agent 系统只有 5 个脚本,搞模块化纯属过度工程。但如果你遇到了这些信号——改一个配置要搜好几个文件、不敢删任何一个脚本因为不知道有没有别的在引用它、三个月后的自己打开项目完全看不懂结构、想把某个功能单独部署却发现根本拆不出来——那就是时候了。

symlink 过渡是这次重构最值得带走的思路。它的本质是:新路径准备好 → 旧路径 symlink 过去 → 逐步切换引用 → 全部切完删除 symlink。 任何需要"在不中断服务的前提下切换底层实现"的场景都可以借鉴。

最后一个容易忽略的点:重构完成后,记得让 AI 做一轮巡检,检查路径解析在移动后是否出现偏移。重构完成后新增一个模块,从讨论方案到写完脚本上线,只用了 10 分钟。模块化不是让系统变复杂,恰恰相反——它是在系统已经复杂到失控的时候,把复杂度装进盒子里。每个盒子内部可以复杂,但盒子之间的关系必须简单。