☰
Desloppify 五层架构完整剖析:base、engine、framework 到 app 的单向依赖设计
2026/9/30 22:06:13 网站建设 项目流程

Desloppify 五层架构完整剖析:base、engine、framework 到 app 的单向依赖设计

【免费下载链接】desloppifyAgent harness to make your slop code well-engineered and beautiful.项目地址: https://gitcode.com/gh_mirrors/de/desloppify

Desloppify 是一款面向 AI 编程 Agent 的代码质量提升工具(agent harness):它用机械检测器找出死代码、重复与复杂度问题,再结合 LLM 主观评审(命名、抽象、模块边界),驱动 Agent 进入"修复 → 解决 → 复扫"的循环,直到分数达标。

这篇文章不聊功能,只聊一件事:这个 2.9 万行的 Python 项目是如何用五层架构 + 单向依赖,让"检测引擎"与"语言插件"、"LLM 评审"与"CLI 命令"各归其位的。理解它,比学会一条scan命令更有价值。

为什么代码质量工具需要五层架构 🧱

Desloppify 支持 29 种语言、几十个检测器、LLM 评审、计划队列、评分记分卡……如果没有强制的分层,这种项目三个月后就会变成"哪里都能 import 哪里"的泥球。

整个包 desloppify/ 只有五个业务层加一个测试层,依赖方向严格从上到下:

app ──→ intelligence ──→ languages ──→ engine ──→ base CLI 命令 LLM 评审 29 语言插件 检测/评分/规划 共享基建
  • 上层可以 import 下层,下层不能 import 上层;
  • tests/被所有运行时层禁止 import(由 CI 契约强制);
  • 动态 import 只允许出现在指定的扩展点(如 desloppify/languages/init.py 的语言注册、desloppify/engine/hook_registry.py)。

这条规则不是写在博客里的一句口号,而是写进了项目的架构契约(见文末"如何保证"一节)。

base:零依赖的基建层,公共代码放哪看这里

desloppify/base/ 是整栋楼的地基,存放所有层都要用、但谁都不该依赖具体业务的"公共代码":

子模块职责
base/config/配置加载与 schema 迁移
base/discovery/文件路径发现、源码定位
base/output/终端输出、用户消息、问题契约
base/registry/目录注册表(catalog)
base/search/版本化的 grep/查询封装

关键设计是:base 的__init__.py是空的(desloppify/base/init.py),它明确要求"从具体子模块导入,而不是从包根重导出"。这样任何一层写from desloppify.base.text import ...时,依赖关系是精确到模块的,方便架构检查工具追踪,也避免包根导入把整个基建"拉爆"进 import 图。

engine:检测、评分与规划的引擎层

desloppify/engine/ 是项目的大脑,包 docstring 一句话概括:"Engine-layer packages: detectors, scoring, planning, policy, and state internals."

  • engine/detectors/:复杂度、重复、God 对象、孤儿代码、安全、测试覆盖率等机械检测器,全部面向"语言无关的抽象"编写;
  • engine/_scoring/:把每个问题按维度(File health、Code quality、Test health…)计分,并设计成"防刷分"——wontfix 会拉大宽松分与严格分的差距;
  • engine/_work_queue/:next命令背后的执行队列,目录里还放了一份 README.md 说明排队策略;
  • engine/_state/:持久化状态的唯一所有者。命令层只通过它的 API 读写,不允许自己发明字段——这条规则写进了 dev/DEVELOPMENT_PHILOSOPHY.md 的"Architectural boundaries"一节。

实测 import 数据很能说明问题:engine 内部有78 处from desloppify.base ...,而 engine → app / engine → intelligence 的顶层 import 为0。

languages + framework:29 种语言的插件框架 🌍

"framework"在 Desloppify 里不是一层,而是 desloppify/languages/_framework/ 这套语言插件框架:每加一种语言 = 写一个插件包,不用改引擎。

  • 每个语言包(desloppify/languages/typescript/、desloppify/languages/python/、desloppify/languages/rust/ 等)都有统一结构:detectors/、phases.py、extractors.py、review_data/,由 desloppify/languages/framework.py 定义LangConfig契约;
  • 注册入口只有一个:register_lang装饰器(desloppify/languages/init.py),它会先校验目录结构、再校验契约、最后才存入注册表;
  • tree-sitter 解析统一收口在 languages/_framework/treesitter/。

插件层对下层的依赖是单向的:实测有257 处languages → base、164 处languages → engine。反过来,engine 几乎不知道具体语言的存在——它只依赖 languages/framework.py 里少数几个类型(如ScanCoverageRecord),把"语言"当抽象消费。这正是分层的核心收益:评分策略和扫描流水线永远不认识 TypeScript,只认识插件契约。

intelligence:LLM 主观评审的智能层

desloppify/intelligence/ 负责机器规则管不了的部分——"这段代码读起来像不像资深工程师写的":

  • intelligence/review/:LLM 评审流水线,包含上下文构建(context_builder.py)、批量切分、维度评估(命名、抽象、错误处理、模块边界)、批量修复建议;
  • intelligence/narrative/:把扫描结果翻译成给 Agent 的"叙述性指令"——行动引擎、策略引擎、提醒规则,决定next命令"说话"的方式;
  • intelligence/integrity.py:主观维度数据的完整性校验,防止 LLM 产出污染状态。

它向下依赖 engine(实测 40 处)拿到评分与队列,但 engine 在顶层从不 import 它——唯一的例外是 engine 里两处函数内的延迟导入,见下一节的"逃生舱"。

app:应用层,唯一面向 CLI 的门面

desloppify/app/ 是整条依赖链的顶点:scan、next、plan、review、resolve、backlog、move、status等全部命令都住在这里。

  • app/cli.py 与 app/commands/ 遵循"命令入口是薄编排器"原则——参数解析、流程编排放在commands/下的模块里,重逻辑一律下沉到 engine / intelligence;
  • app/output/:记分卡渲染(scorecard.py)、可视化 HTML(visualize.py),把五层的能力汇总成一张图、一个分数;
  • 实测 app 层有336 处base import、117 处engine/intelligence import——它是唯一"什么都看得见"的层,也因此是唯一允许组装一切的层。

入口文件 desloppify/cli.py 和 desloppify/main.py 加起来很薄,真正的路由在 app/registry.py。

单向依赖怎么保证不被破坏 🔒

分层设计最怕"口头约定,三个月破功"。Desloppify 用了三道闸门:

  1. CI 架构契约:Makefile 里的make arch目标会运行lint-imports,加载 .github/importlinter.ini。其中runtime_no_tests契约禁止 app / base / engine / intelligence / languages 五个运行时层 importdesloppify.tests;CI 的arch-contractsjob 在每次 PR 都会执行它(绑定关系由 desloppify/tests/ci/test_ci_contracts.py 做回归测试,防止有人改了 CI 忘了改 make 目标)。
  2. 代码评审提示词:项目的 AI 评审规范里直接把依赖方向写成检查项——dev/review/prompts/1-review-agent.md 要求审查者确认"Import direction: Does it respect the project's layering? (base/imports nothing fromengine/;engine/imports nothing fromapp/orintelligence/)";dev/review/prompts/2-devils-advocate.md 更进一步给出简表:base/ → nothing; engine/ → base/ only。
  3. 架构回归测试:dev/DEVELOPMENT_PHILOSOPHY.md 明确"Major boundaries have regression tests so refactors don't silently break things"——边界本身是被测试覆盖的对象。

还有一个值得学习的务实逃生舱:base 层确实需要用到上层的"数据"(比如主观维度清单),但又不允许顶层依赖。解法在 desloppify/base/subjective_dimensions_providers.py——把上层 import 全部放进函数体内延迟执行,并提供可替换的 provider 状态(SubjectiveProviderState),让 base 在测试中可以用 mock 数据注入。依赖方向在 import 图上依然是干净的。

自己动手验证:5 分钟读懂这套架构

不用相信本文,三条命令就能自己核实(任何支持 ripgrep 的环境即可):

# 1. base 是否 import 了上层?(顶层应无结果) rg -n "from desloppify\.(app|engine|intelligence|languages)" desloppify/base/ # 2. engine 是否 import 了 app / intelligence?(顶层应无结果) rg -n "from desloppify\.(app|intelligence)" desloppify/engine/ # 3. 查看架构契约本身 cat .github/importlinter.ini

再配合 dev/ci_plan.md 了解 CI 各 job 的分工、desloppify/README.md 了解包内约定,整个五层结构就完全透明了。

小结:Desloppify 的架构哲学可以浓缩为一句话——检测引擎不认语言,语言插件不碰评分,LLM 评审不碰 CLI,CLI 不发明状态。五层、单向、契约化,这套设计对任何"核心引擎 + 大量插件 + AI 能力"的 Python 项目,都是一份可直接抄的模板。

【免费下载链接】desloppifyAgent harness to make your slop code well-engineered and beautiful.项目地址: https://gitcode.com/gh_mirrors/de/desloppify

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

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

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

立即咨询