PostHog Monorepo 共享代码治理指南:为什么 `common/` 是“暂存区“而不是“目的地“

发布时间:2026/9/12 13:51:03
PostHog Monorepo 共享代码治理指南:为什么 `common/` 是“暂存区“而不是“目的地“ PostHog Monorepo 共享代码治理指南为什么common/是暂存区而不是目的地【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthogPostHog 是一个横跨 PythonDjango、TypeScript/React、Rust 与 Go 的大型 monorepo如何安置被多个模块共享的代码是它持续面对的组织难题。本文以仓库根目录下 common/AGENTS.md 为核心结合 monorepo 布局文档、products 目录规范、packages/quill 实践 以及 tach.toml、pnpm-workspace.yaml 等配置完整讲解common/目录的定位、风险、准入规则、毕业路径与命名约定。读完你将掌握一套可直接复用的共享代码该放哪里决策框架以及如何避免 catch-all 目录退化为第二个单体。一、common/是什么一个过渡暂存区common/在 PostHog monorepo 中的官方定位非常明确它是一个过渡性的暂存区transitional holding pen而不是代码的最终归宿not a destination。它存放的是先于更好归宿而存在的共享代码——即那些在仓库演进过程中积累下来、目前尚未找到合适归属的跨模块公共代码。从仓库现状看common/下确实聚集了一批性质各异的共享设施common/hogql_parserHogQL 解析器C/WASM/Python 绑定是查询引擎的基础设施common/hogvmHog VM 字节码虚拟机同时提供 Python 与 TypeScript 两套实现common/esbuilderesbuild 加载器工具chunkLoader/cssLoadercommon/plugin_transpiler插件转译器common/replay-shared 与 common/replay-headlessSession Replay 的共享逻辑与无头播放器common/migration_utilsDjango 迁移管理共享工具被hogli/migrations.py与posthog/management/commands/migrate.py共同使用此外还有storybook/、tailwind/、fixtures/、ingestion/等。这些模块的技术栈、被谁消费、演进速度各不相同恰好印证了common/什么都可能被塞进来的现实。但正如文档反复强调的这个目录的目标是收缩shrink而不是增长grow。二、核心风险catch-all 目录会退化成杂物抽屉common/AGENTS.md用一句话点出了问题的本质The name is the smell.名字本身就是异味的信号common公共/通用是一个没有边界的命名。一个什么都能装的common/最终会可靠地腐烂成一个杂物抽屉junk drawer范围不明确unscoped、无强制执行unenforced、被一切模块导入imported by everything——这实际上就是第二个单体a second monolith而且边界比第一个单体更差。为什么说它比原来的单体更危险因为 PostHog 对products/*有机械性的护栏而common/没有products/*受tachPython 依赖边界检查与turboTurborepo 选择性构建/测试双重守护。例如 tach.toml 中为common.hogql_parser、common.hogvm.python等显式声明了模块依赖关系与utility true属性违反依赖方向的代码会被 CI 拦下而common/没有任何机制性守卫唯一让这个目录保持诚实的就是这份AGENTS.md文档本身——靠的是约定convention而约定会随着时间推移被侵蚀。这正是该文档存在的意义在缺少自动化护栏的情况下用一份给 Agent 看的规则来充当common/的守门人。仓库的 common/README.md 与 monorepo 布局文档 中出现了几乎相同的警告段落说明这条纪律在仓库内被反复强调。三、放置新共享代码的决策顺序先找有真实边界的家面对一段需要被多处共享的代码common/AGENTS.md给出了严格的决策顺序。只有在依次排除了以下所有选项之后才允许考虑放入common/优先级归宿适用场景仓库依据1products/name/代码归某个具体产品所有后端 Django app 前端 React 的垂直切片products/README.md 规定每个产品是自包含的垂直切片产品间不互相导入内部实现2tools/开发/CI 工具链不在运行时随产品发布monorepo 布局文档 明确tools/是developer/CI tooling仅构建期、CI、开发者工作流使用3services/独立部署的服务拥有自己的领域与生命周期例如services/llm-gateway、services/mcp、services/oauth-proxy4packages/干净的、可发布风格的叶子包对应用代码没有任何反向依赖no back-edges模板是packages/quill关于第 4 项干净叶子包的含义需要特别强调一个包如果位于packages/它就不能反向 import 应用模块如lib/*、scenes/*。packages/quill/AGENTS.md 展示了这种发布风格叶子的典型形态——它自带package.json、tsconfig.json、独立的 Storybook 应用与四层架构tokens primitives components blocks是干净叶子的样板。值得注意的例外是tools/布局文档特别指出tools/owners虽然位于tools/却是运行时依赖stamphog 通过它解析团队 Slack 频道并被拷贝进生产镜像——这说明目录约定允许例外但例外必须被明确记录在案。四、回退到common/的唯一合法入口即使前四个选项都不合适也不是自动落入common/。文档设置了两个并存的硬条件前面四个归宿都不合适不属于单一产品、不是纯工具、不需要独立部署、还不能成为干净叶子代码目前无法成为干净叶子因为它仍然 import 应用模块lib/*、scenes/*等——也就是说它的脏依赖使其暂时无法进入packages/。同时一旦选择落入common/必须把它当作被记录的债务tracked debt在 PR 描述中明确说明这一点命名预期的毕业目标intended graduation target即将来应该迁往哪个packages/包或哪个产品目录。文档还特别否决了一条常见的偷懒理由它沿用了common/的既有先例It follows an existingcommon/precedent——这本身不构成继续往common/塞代码的理由。先例泛滥恰恰是杂物抽屉形成的机制。五、毕业路径把代码从common/迁出去common/AGENTS.md定义了唯一的成功路径the success path当common/中的某个模块变成干净叶子对应用代码没有反向依赖时把它提升promote到packages/或所属产品目录并从common/中删除。也就是说代码在common/的驻留是临时状态最终目标是毕业。仓库中packages/quill就是走过这条路的实证——它已经作为独立的、可发布风格的包存在于顶层 packages/quill 中并在 pnpm-workspace.yaml 里注册packages/quill、packages/quill/apps/*、packages/quill/packages/*三组 glob。与此呼应布局文档对嵌套 vs 顶层给出了更细的规则pnpm 包按名称解析位置不是访问控制而是所有权信号。因此只被一个产品拥有的共享包 → 放products/product/packages/name/被多个产品/服务真正共享 → 提升到顶层packages/name/提升的标准是真实的第二个消费者出现real usage, not intent而不是将来可能有人用。由于包名posthog/name与位置解耦迁移只是一次路径重命名不会引起 import 变更——这让毕业的成本变得很低也更没有理由让代码赖在common/不走。六、命名约定下划线under_score而非连字符common/AGENTS.md的约定部分只有两条但都很关键文件夹名保持under_score命名——连字符dashes会破坏 Python import。这条规则在 products/README.md 中有完全相同的表述dashes make it hard to import files in some languages (e.g. Python)。这也是为什么仓库中所有 Python 包目录都是hogql_parser、migration_utils、plugin_transpiler这样的下划线命名而不是hogql-parser。类似地布局文档解释了为什么没有顶层platform/目录——顶层 Python 包甚至可以遮蔽标准库模块。人类可读的概览README.md仓库级布局monorepo layout。common/README.md与AGENTS.md互为表里README 面向人类读者AGENTS.md 面向 Agent/LLM内容同一套纪律的两种表述。这条README 给人类、AGENTS.md 给 Agent的双文档模式在packages/quill同时存在 AGENTS.md 与 README.md等目录中被复用是 PostHog 仓库为 AI 编码助手保持目录纪律的通用做法。七、结合源码看common/里的代码长什么样为了理解什么形态的代码会被困在common/可以看一个典型例子common/migration_utils/init.py。它的模块 docstring 写得很直白本模块包含被以下两者共同使用的常量、模式与函数hogli/migrations.pyCLI 工具与posthog/management/commands/migrate.pyDjango 命令扩展。从源码可以看到它提供的核心能力迁移命名与 app 名的正则校验MIGRATION_NAME_PATTERN、APP_NAME_PATTERN并强调这是security-critical——防止路径遍历攻击因为 app/迁移名会被拼进缓存路径common/migration_utils/init.py迁移文件的缓存与回滚cache_migration_file、get_cached_migration、temporary_migration_file等上下文管理器跨核心 app 产品 app的迁移发现CORE_MANAGED_APPS与discover_product_apps。这个例子很好地说明了common/中代码的典型处境它同时被 CLI 工具和 Django 运行时消费既不属于某个产品短期内又因依赖面过宽而难以成为干净叶子——于是暂时栖身common/等待毕业时机。这正是文档描述的shared code that predates a better home。再结合 tach.toml 看common.hogql_parser被声明为depends_on []的utility模块而common.hogvm.python的depends_on则包含posthog——这个差异正说明common/内的模块成熟度参差不齐有的已接近干净叶子可随时毕业有的仍与单体纠缠暂时出不去。八、给 Agent 的实操决策清单综合common/AGENTS.md全文任何一段不知道该放哪的共享代码都应该按以下清单走一遍是否归单一产品所有是 →products/name/必要时用bin/hogli product:bootstrap脚手架见 products/README.md。是否是开发/CI 工具且不随运行时发布是 →tools/。是否需要独立部署、有自己的领域是 →services/。能否成为对应用代码零反向依赖的干净叶子是 →packages/模板参考 packages/quill并记得在 pnpm-workspace.yaml 注册路径。以上都不满足且代码仍 import 应用模块才落入common/且必须在 PR 中标注为 tracked debt、命名毕业目标、不允许用沿用既有先例当理由。常问自己这段代码何时能毕业一旦它不再依赖lib/*、scenes/*等应用模块就把它提升到packages/或所属产品并从common/删除——这是唯一被认可的成功路径。这套清单对大型仓库尤其是 AI/LLM 参与编码的仓库尤其有价值common/AGENTS.md的本质是用一份机器可读的规则文件为没有自动化护栏的 catch-all 目录建立人工边界防止共享代码的熵增。理解并执行它就是在为 monorepo 的长期可维护性投票。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考