LibreChat 的架构审查 Skill:用"深度模块"词汇表扫描代码库,并生成可交互的 HTML 深化报告
本文基于仓库中 improve-codebase-architecture/SKILL.md 完整展开:这是一个安装在 LibreChat 仓库 .claude/skills/ 目录下的架构审查技能,它会先扫描代码库中"浅模块"(接口与实现几乎一样复杂)的架构摩擦点,把候选重构方案渲染成一份自包含的 HTML 可视化报告,再针对用户选中的候选项进入逐层拷问(grilling)式决策循环。读完本文,你将掌握该技能三阶段工作流(Explore → HTML 报告 → Grilling)的完整操作细节、它依赖的深度模块设计词汇表与判定原则(删除测试、缝、适配器),以及配套的报告排版与图表规范。
1. 这个 Skill 是什么:定位、入口与调用约束
技能的定义文件是 SKILL.md,其 YAML frontmatter 如下:
name: improve-codebase-architecture
description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
disable-model-invocation: true
几个关键事实:
- 目标是"深化"(deepening),而非泛泛重构:技能把重构定义为"把浅模块变成深模块"——即让大量行为藏在一个小接口后面,核心收益是可测试性(testability)与对 AI 的可导航性(AI-navigability)。
- 不可被模型隐式触发:
disable-model-invocation: true意味着该技能必须由用户显式调用。配套的 agents/openai.yaml 也声明了allow_implicit_invocation: false,display_name 为 "Improve Codebase Architecture"。这与 Claude Code 技能的调用策略一致:架构审查属于重量级操作,不应由模型在对话中自行发起。 - 报告不落盘进仓库:HTML 报告只写到操作系统临时目录,避免污染仓库(详见第 3 节)。
- MIT 许可:技能目录下的 LICENSE 显示其采用 MIT 协议。
1.1 技能依赖的两份"外部知识"
SKILL.md 明确声明该命令是"informed by the project's domain model"(由项目领域模型所启发),构建在三份仓库资产之上:
/codebase-design技能:提供架构词汇表(module、interface、depth、seam、adapter、leverage、locality)与设计原则(删除测试、"接口即测试面"、"一个适配器是假设的缝,两个适配器才是真实的缝")。SKILL.md 要求在所有建议中逐字使用这些术语,禁止漂移成 "component"、"service"、"API"、"boundary" 等近似词。该词汇表完整定义在 codebase-design/SKILL.md 中。CONTEXT.md领域词汇表:仓库根目录的 CONTEXT.md 为"好的缝"命名。LibreChat 的 CONTEXT.md 已经是一份实质性的领域词典,例如定义了 Agent execution host(协议中立的运行时模块,拥有 run 准入、断连取消、provider 启动栅栏与终态结算)、Agent execution enrollment(已准入 Agent run 的持久化生命周期权威)等术语——这些正是架构审查时应该用来称呼模块的名字,而不是 "FooBarHandler" 或 "Order service"。docs/adr/中的 ADR:已记录的架构决策记录是"不应重新翻案"的领域。需要注意的是,当前 LibreChat 仓库中尚未创建docs/adr/目录(该技能对 ADR 目录的引用是预期中的可选结构);技能的流程本身支持 ADR 的"惰性创建"——当用户在拷问循环中用一个承重的理由否决某候选项时,技能会主动提议将其记录为 ADR,以便未来的架构审查不再重复建议同一件事。
2. 核心词汇表与设计原则(来自 codebase-design)
由于 SKILL.md 要求报告与建议全程使用这套词汇,理解它们是理解整个技能的前提。以下定义摘自 codebase-design/SKILL.md:
| 术语 | 定义要点 | 禁止替换为 |
|---|---|---|
| Module(模块) | 任何拥有接口与实现的东西。刻意规模无关:可以是一个函数、类、包,甚至横跨多个层的切片 | unit、component、service |
| Interface(接口) | 调用方为正确使用模块必须知道的一切:不只是类型签名,还包括不变式、顺序约束、错误模式、所需配置、性能特征 | API、signature(太窄,只指类型层面) |
| Implementation(实现) | 模块内部的东西。与 Adapter(适配器) 区分:小的适配器可以有大实现(如 Postgres repo),大适配器也可以有小实现(内存 fake) | — |
| Depth(深度) | 接口处的杠杆率:调用方(或测试)每单位"要学的接口"能行使多少行为。大量行为藏在小接口后面即 deep(深);接口与实现几乎一样复杂即 shallow(浅) | — |
| Seam(缝) | 源自 Michael Feathers:一个"可以在不修改该处代码的前提下改变行为"的位置,即模块接口所在之处。缝放在哪里本身就是一个独立的设计决策 | boundary(与 DDD 的 bounded context 概念重载) |
| Adapter(适配器) | 在缝上满足接口的具体实体。描述的是"角色"(填哪个槽位),而非"实体内容" | — |
| Leverage(杠杆) | 调用方从深度中获得的收益:更少的接口知识换取更多能力;一份实现回报于 N 个调用点与 M 个测试 | — |
| Locality(局部性) | 维护者从深度中获得的收益:变更、缺陷、知识与验证集中在一处,"修一次,处处修复" | — |
深与浅的直观对比(SKILL.md 原文的 ASCII 图):
┌─────────────────────┐ ┌─────────────────────────────────┐
│ Small Interface │ ← │ Large Interface │ ←
├─────────────────────┤ ├─────────────────────────────────┤
│ Deep Implementation│ ← │ Thin Implementation │ ←
└─────────────────────┘ └─────────────────────────────────┘
深模块(目标形态) 浅模块(要消除的形态)
设计接口时连续自问三句:能否减少方法数?能否简化参数?能否把更多复杂性藏到内部?
四条设计原则(SKILL.md "Principles" 节逐条给出,improve-codebase-architecture 的每个判断都以此为据):
- 深度是接口的属性,不是实现的属性。 深模块内部可以由小的、可 mock 的、可替换的部件组成,只是它们不属于接口。模块可以有内部缝(私有于实现、供自己的测试使用),也可以有接口处的外部缝。
- 删除测试(deletion test)。 想象删掉这个模块:如果复杂性随之消失,说明它只是透传;如果复杂性会在 N 个调用方身上重新出现,说明它称职。
- 接口即测试面(the interface is the test surface)。 调用方与测试穿过同一条缝。如果你想测试到接口"之后"(穿透内部),说明模块形状大概率不对。
- 一个适配器 = 假设的缝;两个适配器 = 真实的缝。 除非确实有东西在缝两侧变化(典型如生产 HTTP 适配器 + 测试内存适配器),否则不要引入缝——单适配器的缝只是无谓的间接层。
2.1 依赖分类与"替换而非叠加"的测试策略
DEEPENING.md 进一步给出深化候选项时的依赖分类法——类别决定深化后的模块如何跨缝测试:
- In-process(进程内):纯计算、内存状态、无 I/O。总是可以深化——合并模块后直接通过新接口测试,无需适配器。
- Local-substitutable(本地可替代):依赖存在本地测试替身(如 Postgres 对应 PGLite)。深化后由替身在测试套件中运行;缝是内部的,模块外部接口上没有端口。
- Remote but owned(远程但自有,Ports & Adapters):跨网络边界的自有服务。在缝上定义一个端口(接口),深模块拥有逻辑,传输层以适配器注入;测试用内存适配器,生产用 HTTP/gRPC/队列适配器。推荐句式:"在缝上定义端口,生产实现 HTTP 适配器、测试实现内存适配器,让逻辑即使部署在网络上仍位于一个深模块内。"
- True external(真正外部,Mock):不controlled的第三方服务(Stripe、Twilio 等)。深化模块把外部依赖作为注入的端口接收,测试提供 mock 适配器。
配套的缝纪律与测试策略:替换而非叠加——浅模块上旧的单元测试在深化模块的接口级测试存在之后就成为废料,应删除;新测试写在深化模块的接口上,断言通过接口可观察的结果而非内部状态;测试应能存活内部重构,"如果实现一变测试就得改,那它就是在测试接口之外的东西"。
3. 流程第一步:Explore(范围先行,YAGNI)
SKILL.md 的第一个流程节点是探索,核心纪律是 Scope before you scan — YAGNI:深化一个模块的收益体现在"未来改它更容易",因此要对最近变更过的部分加权。先决定"看哪里",再开始看:
- 用户指明了方向(某个模块、子系统、痛点)——直接采纳,跳过下面的推断。
- 否则,用
git log --oneline回溯一段提交历史,找出代码库的热点——反复出现的文件与区域,让这些路径先抓住注意力。若变更分散、没有明显热点,则放宽扫描范围。
开始细读之前,先读该区域的领域词汇表(CONTEXT.md)与相关 ADR。
然后派生一个子代理去遍历代码库。SKILL.md 特意强调"不要遵循僵硬的启发式——有机地探索,并在你体验到摩擦的地方做记号"。它列出五类摩擦信号:
- 理解一个概念需要在许多小模块之间来回跳转吗?
- 哪些模块是浅的——接口几乎与实现一样复杂?
- 哪些纯函数只是为了可测试性被抽出来,但真正的 bug 藏在"它们如何被调用"里(缺乏局部性)?
- 哪些紧耦合模块在彼此的缝上泄漏(leak)?
- 代码库中哪些部分未测试,或通过其当前接口难以测试?
对一切你怀疑是浅模块的东西,应用删除测试:删掉它会集中复杂性,还是只会把复杂性搬走?"会集中"才是你想要的信号。
结合 LibreChat 仓库的结构看,这一阶段天然有明确的扫描锚点:CLAUDE.md 声明了 monorepo 的 workspace 边界——
/api(JS 遗留 Express 层,"minimize changes here")与/packages/api(新后端代码,TS only)。这种"薄 JS 包装调用 TS 服务"的迁移期结构,正是删除测试与缝纪律的典型适用场景:哪些 JS 包装层是纯透传(删除后复杂性集中),哪些 TS 模块的接口还和实现一样宽,都是子代理可以用上述五类摩擦信号直接检验的候选点。
4. 流程第二步:把候选项渲染成 HTML 报告
这是该技能最具辨识度的产出物。SKILL.md "Present candidates as an HTML report" 一节给出了完整的工程约束:
4.1 文件落点与打开方式
- 写到 OS 临时目录,什么都不落入仓库。从
$TMPDIR解析临时目录,回退到/tmp(Windows 用%TEMP%),文件名格式为<tmpdir>/architecture-review-<timestamp>.html——每次运行得到一份新文件,不会互相覆盖。 - 自动打开给用户:Linux 用
xdg-open <path>,macOS 用open <path>,Windows 用start <path>,并把绝对路径告知用户。
4.2 报告技术栈:Tailwind + Mermaid 双 CDN
完整 scaffold 与图表规范在 HTML-REPORT.md 中。骨架要点:
<script src="https://cdn.tailwindcss.com"></script>
<script type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
mermaid.initialize({ startOnLoad: true, theme: "neutral", securityLevel: "loose" });
</script>
<style>
.seam { stroke-dasharray: 4 4; } /* 虚线 = 缝 */
.leak { stroke: #dc2626; } /* 红色 = 泄漏 */
.deep { background: linear-gradient(135deg, #0f172a, #1e293b); } /* 深色 = 深模块 */
</style>
图表策略是混合使用:当关系是图形的(调用图、依赖、时序)时用 Mermaid 的 flowchart/graph/时序图;当想要更"编辑感"的视觉(质量图、横截面、坍缩动画)时用手工 div/SVG。HTML-REPORT.md 原话:"别把 Mermaid 当万能药,那样会显得泛泛"。报告只含 Tailwind CDN 与 Mermaid ESM 两个脚本,其余完全静态,无应用代码。
报告头部只放仓库名、日期和一张紧凑图例:实线框 = 模块,虚线 = 缝,红色箭头 = 泄漏,深色粗框 = 深模块。没有引言段落——直接进入候选项。
4.3 候选项卡片的固定字段
每个候选项一张卡片(一个 <article>),字段清单如下(SKILL.md 与 HTML-REPORT.md 合并后的完整要求):
| 字段 | 内容要求 |
|---|---|
| 标题 | 短句,直接命名这次深化(如 "Collapse the Order intake pipeline") |
| 徽章行 | 推荐强度:Strong(emerald 色)/ Worth exploring(amber 色)/ Speculative(slate 色);外加一个依赖类别标签(in-process、local-substitutable、ports & adapters、mock) |
| Files | 涉及的文件/模块,等宽字体(font-mono text-sm) |
| Problem | 一句话。为什么当前架构造成摩擦 |
| Solution | 一句话,纯英文描述会发生什么变化 |
| Benefits(Wins) | 要点式,每条 ≤6 词,且必须用词汇表术语表达:如 "Tests hit one interface"、"Pricing logic stops leaking"、"Delete 4 shallow wrappers";"locality: bugs concentrate in one module"、"leverage: one interface, N call sites"。禁止写 "easier to maintain"、"cleaner code" 这类不在词汇表里的话 |
| Before / After 图 | 并排双栏的自绘对比图,是全卡片的"中心件"(centrepiece) |
| ADR 冲突提示(如适用) | 一行 amber 色警示框:"contradicts ADR-0007 — but worth reopening because…" |
HTML-REPORT.md 提供了五种可混用的图表模式,每个候选项按自身特点选用("不要把所有图画成一样——多样性本身就是目的之一"):
- Mermaid 图(依赖/调用流的主力):用 classDef 把泄漏边染红、把深模块染深;时序图适合表达 "before: 6 round-trips; after: 1"。
- 手工 boxes-and-arrows:模块用带边框
<div>,箭头用绝对定位的内联 SVG<line>/<path>。适合"after"图要呈现"一个粗边框深模块 + 内部细节灰化"这种 Mermaid 摆不出分量感的形态。 - 横截面(Cross-section):堆叠水平色带展示一次调用穿过的层数。Before:6 层薄带各不做事;After:1 层厚带标注合并后的职责。适合"分层浅薄"。
- 质量图(Mass diagram):每个模块两个矩形——接口表面积与实现。Before:接口矩形几乎和实现一样高(浅);After:接口矩形矮、实现矩形高(深)。适合"接口与实现一样宽"。
- 调用图坍缩(Call-graph collapse):Before 是一棵嵌套函数调用树;After 是同一棵树坍缩成一个盒子,内部调用淡化显示在盒子里。
风格纪律:编辑感而非企业仪表盘;颜色克制——一个强调色(emerald 或 indigo)+ 红色标泄漏 + amber 标警示;图高约 320px 以便 before/after 并排不滚动;模块标签用 text-xs uppercase tracking-wider,读起来像示意图而非 UI。如果一张图需要一段文字才能看懂,那就重画这张图。
4.4 报告收尾与词汇纪律
- 报告以 Top recommendation 一节收尾:一张更大的卡片,给出你会先动哪个候选项及原因,附锚点链接回到其卡片。到此为止。
- 领域用 CONTEXT.md 的词,架构用 codebase-design 的词。 如果 CONTEXT.md 定义了 "Order",就说 "the Order intake module"——不说 "the FooBarHandler",也不说 "the Order service"。
- ADR 冲突的克制原则:只有当摩擦真实到值得重新审视该 ADR 时,才把冲突候选项摆出来并明确标注;不要罗列 ADR 禁止的每一个理论重构。
- 此时不要提出接口("Do NOT propose interfaces yet")。文件写完后只问用户一句:"Which of these would you like to explore?"
5. 流程第三步:Grilling Loop(拷问循环)
用户选定候选项后,SKILL.md 指定运行 /grilling 技能,与其逐层走决策树:约束、依赖、深化后模块的形状、缝后面藏着什么、哪些测试能存活。
决策在对话中固化时,副作用内联发生——通过 /domain-modeling 技能保持领域模型最新,SKILL.md 列出了四条明确的副作用触发规则:
- 深化后的模块以 CONTEXT.md 中没有的概念命名? 把该术语加入
CONTEXT.md;文件不存在就惰性创建。 - 对话中某个模糊术语被厘清了? 当场更新
CONTEXT.md。 - 用户用一个承重理由否决了候选项? 提议记录为 ADR,措辞是:"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"——只在理由确实是未来探索者需要(以避免重复建议同一件事)时才提议;临时性理由("现在不值得")与不言自明的理由跳过。
- 想探索深化模块的替代接口? 运行
/codebase-design技能,使用其 design-it-twice 并行子代理模式。
第 4 条对应的完整模式定义在 DESIGN-IT-TWICE.md(源自 Ousterhout 的 "Design It Twice"——你的第一个想法不太可能是最好的):先向用户展示问题空间(任何新接口要满足的约束、依赖及其类别、一段示意性代码草图);然后并行派生 3 个以上子代理,各自产出一个** radically different(激进地不同)**的接口,每个代理拿到不同的设计约束(代理 1:最小化接口,1–3 个入口;代理 2:最大化灵活性与扩展;代理 3:为最常见调用方优化,默认路径平凡化;代理 4(如适用):围绕端口与适配器设计跨缝依赖);最后逐一呈现、按 depth / locality / seam placement 三个维度对比,并给出有立场的推荐——"用户要的是一个强判断,不是一份菜单"。
6. 在 LibreChat 仓库中的落地条件盘点
把技能声明的外部依赖与仓库现状对照,可以得到一份明确的"就绪清单":
- CONTEXT.md(已就绪):仓库根目录 CONTEXT.md 是实质的领域词典,包含 Agent run envelope、Agent execution host、Subagent thread、Event actor head 等大量带版本化语义的术语。这些术语直接服务于技能第 2、3 步的命名纪律——审查 Agent 执行链路时,报告卡片应写 "the Agent execution enrollment module is shallow" 而非 "the RunController service"。
- codebase-design 词汇技能(已就绪):codebase-design/SKILL.md、DEEPENING.md、DESIGN-IT-TWICE.md 与 improve-codebase-architecture 同目录区,形成完整的词汇—深化—双设计闭环。
docs/adr/(暂缺,惰性创建):当前仓库尚无该目录;按技能流程,首个值得记录的"否决决策"会自然催生它。/grilling与/domain-modeling技能:SKILL.md 引用了这两个技能,但它们不在当前仓库.claude/skills/目录中(该目录仅有codebase-design与improve-codebase-architecture两项),从引用关系看它们属于使用方需自备的配套技能。- 调用方式:由于
disable-model-invocation: true且 agents/openai.yaml 中allow_implicit_invocation: false,该技能只能由用户显式发起(如直接输入技能名/命令),模型不会在对话中自行触发它——这符合架构审查"先授权、后扫描"的定位。
7. 小结:为什么这套流程值得借鉴
improve-codebase-architecture 把一个通常靠"手感"的架构审查,拆成了三段可复现的机械流程:用 git 热点圈定范围 → 用五类摩擦信号与删除测试产出候选 → 用统一词汇表渲染成 before/after 可视化报告 → 在拷问循环中把决策副产品(术语、ADR、双设计)直接写回 CONTEXT.md 与 ADR。它的几个设计决定值得单独指出:报告落 OS 临时目录而非仓库,保证审查过程零副作用;强制词汇表("Use exactly / Never substitute")让架构讨论可积累、可检索;"接口即测试面"与"两个适配器才是真的缝"让"要不要抽接口"有了可判定的标准,而不是风格之争。对维护 LibreChat 这样横跨 /api(JS 遗留层)与 /packages/api(TS 新层)多个 workspace 的代码库,这套流程给出的最大价值,是让每次重构建议都带着证据(文件清单、删除测试结论、ADR 对照)与统一的领域语言出现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00