mcpField
A
archy

archy

本地服务
github.com开发工具8今天更新

关于 archy

archy 分析 Python 导入和调用关系,检查代码库是否符合声明的架构分层。它面向开发者和编码 Agent,提供缓存图分析、影响范围与受影响测试查询、循环检测,以及可在 CI 中失败退出的检查。

主要功能

  1. 01缓存 Python 导入与调用图
  2. 02查询影响范围和受影响测试
  3. 03检测导入循环
  4. 04检查声明的分层与依赖规则
  5. 05CLI 和 MCP 服务

适用场景

  1. 01在 CI 中检查架构规则
  2. 02评估修改的影响范围
  3. 03查找变更影响的测试
  4. 04提交前检查依赖方向

MCP 相关

archy 可在 Agent 会话中作为 MCP 服务运行,提供基于代码图的架构检查和查询。它与 CLI 使用同一份解析图。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"hslee16-archy": {
"command": "uvx",
"args": [
"archy"
]
}
}
}
启动命令uvx archy

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypiarchy0.50.1

工具

13

收录的工具列表 · 更新于 今天

参数类型必填说明
pathstring是
viewstring否
last_ninteger否
recordboolean否
strictboolean否
internal_onlyboolean否
strict_tolerancenumber否

详细介绍

archy

PyPI Python CI Code style: ruff License Glama

你的文件夹展示了架构,而导入关系决定了架构。 archy 将 Python 导入图转化为 Agent 可用的信息:编辑前查看影响范围、受编辑影响的测试,以及风险最高的模块。如果导入图与声明的分层不一致,它就会报错。无论通过命令行还是 MCP 服务,使用的都是同一张图,并且每次会话和在 CI 中都能使用。 [!IMPORTANT] 状态,2026-09-10:项目活跃,且正聚焦于核心循环,而非扩展表面范围。

archy 的作用。 一个图,两个前端。它只解析一次 Python 项目的导入和调用,并缓存解析结果,回答文件树无法回答的问题:这项更改会影响哪些部分、会影响哪些测试、循环依赖在哪里,以及图是否仍符合你声明的分层。它既可作为 CLI 在 CI 中运行,也可作为 MCP 服务在 Agent 会话中运行,两种方式都基于同一张图;如果声明的架构与实际架构不一致,它就会失败。

项目方向:深入这一循环,而非扩大范围。 2026-09-02 让本项目重新启动的本地模型问题,现已通过六项干预得出结论,结果均不为正,其中两项的成本经测量高于对照组。相关报告是 docs/WHAT_DIDNT_WORK.md 中的 Study 8,也是此处发布的第五项预注册零结果。由此产生的功能 conventions --emit-headers 会保留,因为它是派生生成而非手工编写、成本低,而且 --check 可防止其逐渐失效。只有附加在该功能上的主张被撤回。

这六项零结果确实揭示了失败发生的位置:交付和检索,而非能力。 在固定代码树上的 132 份 Agent 交互记录中,read 在 132 次运行中全部触发,首次编辑前的调用次数中位数为 9.5;archy 则在 132 次运行中触发了 84 次,首次编辑前的调用次数中位数为零。archy 的所有入口都是拉取式的。模型只有在已经作出决定后才会调用它,或者根本不会调用。通过 archy 所拥有的唯一渠道——文件本身——推送的两种负载,结果都比对照组成本更高,这说明问题出在渠道,而非内容。

因此,当前的设计规则是:把答案放进已经在运行的命令的默认输出中,而不是增加一个必须先被发现的入口。当前的工作线索由此而来。一条是空洞结果系列:不可能失败的检查,不应像经过评估且通过的检查那样报告;这一点现已在 contracts 结果中明确说明,并正在其余入口中逐步完善。另一条是对 archy 自身代码进行两项结构重构:一是 archy 自有工具评定为核心且脆弱的模块,二是已经膨胀到足以藏匿问题的两个文件。新增工具和新增输出格式仍须有使用信号才会放行。

项目活跃,并不意味着放松纪律。 阈值会在运行前预注册,任何结果,包括又一项零结果,都会像之前的五项一样发布在 docs/WHAT_DIDNT_WORK.md 中。archy 当前发布的任何内容都不声称能带来本地模型收益;archy brief(v0.46)明确是基于判断而非测量结果发布的,后来测量结果也没有为它背书。

不会改变的内容。 原有用例仍受支持且运作正常:在 CI 中治理分层、为前沿 Agent 分析影响范围和受影响测试,以及 MCP 服务。没有任何内容会被移除或重命名。漏洞仍会修复,拉取请求仍会审查,而 good first issue 工单仍会有意保持开放。

请先阅读:我验证了这个前提,而它是错的

我看到编码 Agent 生成的更改通过了审查,却悄悄破坏了底层导入图,于是构建了 archy。之后我测量了这种情况是否会发生,结果是:几乎不会。

测量对象比率
在风险最高的 SWE-bench 任务中运行的 25 个 Agent循环或违反已声明的分层规则0%(95% 置信上限为 12%)
11 个仓库中的 1,072 次人工提交引入的循环每次提交 0.5%
声明了架构的项目中的 151 组提交对违反契约每次提交 0.66%
同一批项目随时间变化的 107 个样本规则过时、覆盖率下降四项预注册指标均为 null
各自按指定架构构建后端的 25 个 Agent依赖方向错误12%,而让检查器参与后降到了 0%

所以,问题确实存在(我亲眼见过开发者自己的架构规则在实际项目中被破坏),而且对 Agent 和人类来说都很少见。“Agent 会破坏你的导入图”是我提出过、后来又撤回的说法。没人测量过一次违规会造成什么代价,所以我也无法辩称“虽然少见,但代价高昂”。

最后一行说明了 archy 的用途。 所有 25 个未获辅助的 Agent 都正确生成了四个分层目录。每次失败都是导入方向错了:实体反向依赖数据访问层。它们的布局正确,方向却错了,而方向规则以不影响 API 行为为代价,捕获了全部三个案例。

这就是整个问题的特点。文件树中可以看出布局,但无论缩放到什么层级,也无法从任何单个文件中看出方向、传递依赖范围和循环。另一项研究发现,这类问题一旦出现,就永远不会修复:在抽样语料中,没有任何违规得到解决,而且 14 个仓库中有 2 个一直处于契约遭到破坏的状态。它们少见,却会一直存在,并不会自行修复。

这对开发工作的意义。 以“Agent 会破坏你的架构”为前提的功能开发,在这个前提被撤回后就被搁置了,此后也没有任何证据让它重回议程。留下来的范围更窄,也有数据支撑:方向规则、传递契约和循环检测,并在每次会话中进行检查。这是一项真实需求,archy 能满足它。研究反对的是做得更多:五项研究都没有证明缺少的正是另一项功能,而对受上限限制的结果作诚实解读,也意味着下一项功能不会改变这一点。

因此,7 月关闭的路线图仍然关闭,重新恢复活跃也没有让它重启。这里的工作将投入到经验证仍有价值的流程中,而不是已经被否定的功能列表:让 archy 已经计算出的内容出现在 Agent 实际会读取的地方,并如实说明每个结果涵盖和不涵盖的内容。一如既往,我们会修复错误,也欢迎贡献。

完整说明(包括六份测量产物,它们差点让一项失败的研究变成成功案例)见 docs/WHAT_DIDNT_WORK.md。如果这里只读一篇内容,请读这篇。

它能捕获的问题

archy 的分层契约:四层堆叠,cli 位于 policy 之上,policy 位于 graph 之上,graph 位于 parser 之上;依赖只允许向下,graph 层到 cli 的一条禁止向上依赖边标记为违规。

图中的内容没有一项是推导出来的。模块属于哪一层、禁止哪个方向,都是你写在 archy.yaml 中的事实;archy 只负责检查源码是否仍与这些事实一致。

下面是一行概括的典型问题,也是 archy 为之构建的场景:这是 archy 自己的源码,遵循 archy 自己的分层规则,其中只有一条导入,是 Agent 需要辅助函数、而最近的辅助函数位于更高层时会添加的那种导入:

# src/archy/graph.py
from archy.cli import main    # convenience import. The diff looks harmless.
$ uvx archy check .
# 1 layer violation(s) (config: archy.yaml)

graph -> cli (forbidden):
  archy.graph -> archy.cli  (line: 21)

$ echo $?
1

$ uvx archy cycles .
# 1 cycle(s) found

Cycle of 8 module(s):
  - archy.cli
  - archy.conventions
  - archy.duplicates
  - archy.graph
  - archy.index
  - archy.mcp
  - archy.simulate
  - archy.watcher

$ uvx archy score .
# archy score: 0.647          (0.656 before the edit)
...
acyclicity:  0.942  (1 cycles, tangle=0.058)
# graph: 139 modules, 264 edges     (263 before the edit)

一条导入,一条依赖边。一个违反规则的层间依赖边、一个涉及八个模块的循环,以及下降了 0.009 的分数。差异本身看不出任何这些问题;无论怎么读这个文件都无法发现它们,因为判定违规的规则不在源码中,而是由你提供的。

注意分数变化的幅度。0.009 很小,这才是这个问题的真实表现:单次编辑在数字上看起来都不惊人。循环数从 0 变成 1,以及 check 以 1 退出,才是重要信号;分数则能捕捉六周内重复发生四十次的问题。在将综合评分视为质量门槛之前,请先阅读 docs/SCORING.md。

上面的示例是一个直接违反规则的导入,这属于简单情况:先读取 archy.yaml 的 Agent 不用 archy 也能发现它。更困难、也更贴近实际的情况是传递性违规:编辑没有添加任何违规导入,读取配置也无法让你看出问题。 docs/WALKTHROUGH.md 提供了一个只需一条命令即可复现的示例,并明确说明 archy 的哪些功能可以捕捉到它(一个),哪些无法捕捉(三个)。

在检出代码的副本上复现上面的示例:将该导入添加到 src/archy/graph.py,然后为这三条命令加上 uvx 前缀再运行。必须使用另一个 archy,因为这条导入会造成真正的运行时循环导入,可编辑安装的 archy 因而无法启动并报告自身的问题。archy check 会以 1 退出;这也是它在 CI 中的行为,MCP 服务也会在 Agent 提交之前将此结果报告给它。

archy 演示

**archy 不是什么:**代码导航工具。它无法帮助 Agent 更快地查找和阅读代码;这项工作应由 codegraph 这类符号级、多语言图工具负责,它们在这方面更出色。archy 回答的是另一个问题:你声明这个代码库应当具有这些层、不能有循环,并达到这个分数;Agent 的编辑是否即将破坏这些要求?趋势是否已经连续六周走低? 导航图中没有这些意图,因为意图不在源码里,而是由你提供的。

更直接地说,根据 2026-07-27 对 codegraph 的再次核查:它不提供循环检测,也没有用于声明层或禁止依赖边的配置,更没有在发生违规时以非零状态退出的命令。如果你问对了问题,它很乐意展示 models 导入了 repositories;但它无法告诉你这是错误的,因为判断错误需要声明,而它没有任何地方可以放置这类声明。**描述型工具负责回答问题;archy 则会作出能让构建失败的断言。**二者都是本地 MCP 服务,可以很好地配合使用;可以同时运行。完整对比请参阅 docs/research/CODEGRAPH_COMPETITIVE_ANALYSIS.md,其中也包括 archy 的不足之处,以及这一区别为何是对方主动做出的选择,而非其能力所限。

一条命令即可开始

uvx archy install     # detects Claude Code, Cursor, Codex, opencode, Continue and wires each one up

不会有任何内容写入你的 PATH:它写入的配置会按需运行 uvx archy mcp。更喜欢正式安装?使用 pip install archy、uv tool install archy 或 pipx install archy。无论哪种方式,都可以在不安装任何内容的情况下,在项目上试用:

uvx archy score .        # one-shot architectural health number
uvx archy cycles .       # import cycles, Tarjan SCCs plus self-loops
uvx archy check .        # layer rules from archy.yaml; exits 1 on violation

免费,采用 MIT 许可证,暂无推出商业版本的计划。 由一位维护者使用 Python 独立开发。由 Alex Lee 构建。

状态: v0.50.0,功能可用,已安装并维护。现在可通过以下方式使用:

模式命令
检查archy graph、archy cycles
CI 治理archy check(读取 archy.yaml;--contracts 会将传递导入 lint 的判定嵌入同一输出中,仅作报告,不作为门禁)
传递契约archy contracts(读取 .importlinter(规范位置),或回退到 archy.yaml;需要 archy[contracts])
单次评分archy score
评分趋势archy score --record + archy trend
重构优先级archy hotspots(CC x git 变更频率)、archy what-to-refactor-next(融合热点与编辑风险)
重复项检测archy duplicates(分两层:疑似重复项与降级变体——同类、样板代码、测试代码、供应商代码;仅供参考,不作为评分维度)
项目风格archy conventions(按所属模块划分命名族,按传递基类划分类型族,识别 NAME = Ctor(...) 注册表、镜像界面、与用户错误退出项分开维护的门禁清单、模型清点,以及项目自身的 __all__ 或文档遗漏的成员——这些信息从源码中推导而来,让 Agent 不必再猜测约定;仅供参考,报告成功时始终以 0 退出;--module NAME 可切换为针对单个模块的完整、无排名查询——包括该模块导入的内容、导入该模块的内容,以及它是否被搁置——从而回答排名报告无法回答的否定问题;--emit-headers 会根据同一份报告推导每个模块的区块(该模块负责什么、有哪些模块与之镜像,以及此处的发现是否会触发门禁)并打印出来;--write 会将其写入模块文档字符串;如果内容已过时,--check 会以 1 退出)
变更耦合archy coupling(在 git 历史中共同变更、但没有共享导入或调用边的模块对——隐藏依赖;仅分析源码,仅供参考)
CI 影响查询archy affected(git diff -> 受影响的模块与测试,深度受限)
面向用户的导出archy render --view dsm|trend(生成一个独立的 HTML 文件:无 JS、无 CDN、可离线使用、字节稳定)
任务前简报archy brief(整合约定、层覆盖情况和门禁清单,生成一屏内容供 Agent 开始前阅读,并可选包含 --contracts;仅供参考,始终以 0 退出)
MCP 服务archy mcp(带缓存:即使在拥有 10k+ 模块的代码库中,缓存热图构建也只需几秒)
解析缓存archy index sync / archy index clear(持久化存储在 .archy/index.db;MCP 服务会透明使用)
Agent 安装archy install / archy uninstall(自动检测 Claude Code、Cursor、Codex、opencode、Continue;接入或彻底移除 MCP 服务)

评分的计算方式及其解读方法:docs/SCORING.md。针对 pydantic、fastapi、flask、pytest 和 archy-on-archy 的基准测试:docs/CASE_STUDIES.md。设计理念及与 sentrux 的对比:docs/LEARNINGS.md。

实际使用情况

ADOPTERS.md 目前是空的,除了我以外,还没有人提交过 issue。Pull request 则是另一回事,而且近期就有:2026-07-25 合入了三个,合并了两个。适合初次贡献者的工单都已标记,并特意留给其他人处理。

如果你正在真实代码库上运行 archy,我很想听听它发现了什么,尤其是答案为“没发现有用内容”的情况——现在,这种答案有测量结果支持,而不仅仅是理论上的可能。

为什么

页面顶部的故障正是 archy 存在的全部理由:我想要一个能提前捕捉到它的单一提交指标。

AI Agent 以机器速度生成代码,于是我推断:如果没有针对结构健康状况(模块耦合、导入循环、分层违规)的反馈循环,那么即使每次改动单独看来都通过了审查,代码库在架构上也会逐渐偏离。

我检验后发现,这一推断站不住脚。 25 次 Agent 运行中,结构回归次数为零;人工提交违反自身声明规则的比例也只有 0.66%。长期来看,这种偏离可能仍然存在,但单次编辑的度量无法观测到;然而我没有证据支持这种说法,因此不会断言它成立。本节其余内容是我最初提出的论据;尽管我从中得出的推论并不成立,但引用本身准确,所以我仍将它们保留在这里。

反馈循环确实发挥作用的场景更窄:是在编写代码的那一刻,而不是事后巡查。 按指定架构构建新的后端时,25 个未获辅助的 Agent 中有 3 个弄错了依赖方向;在检查器参与反馈后,没有一个弄错,而且 API 的表现同样出色。这只涉及一个模型、一个框架,也是 Constraint Decay 论文所设条件中最温和的一种,因此不能据此得出普遍结论。但它确实说明,在代码生成时启用反馈循环是值得的,因为错误此时很容易预防,而且根据衰减研究,此后不会再得到修正。

archy 的架构:将你的 Python 源码解析为导入图,再由该图为分析模块和你在 archy.yaml 中声明的分层策略提供输入,所有结果都会通过一组共享界面传达给你。

这带来的好处是部署位置灵活:它可以在 CI、pre-commit 中运行,也可以作为 MCP 服务(archy mcp)运行,因此编码 Agent 能在提交之前读取自身对架构的影响,而不必等到审查之后。

2025-2026 年的研究为 Agent 反馈这一观点提供了实证支持:Navigation Paradox 论文表明,大型 LLM 上下文窗口并不能消除对结构化图导航的需求;LocAgent 的消融研究发现,图边能显著提升代码定位准确率;Constraint Decay 论文(arxiv:2605.06445)发现,随着架构约束不断累积,Agent 的通过率会下降约 30 个百分点(在测试的开放模型和中端模型上,仅 Clean Architecture 分层一项就造成 -9.1 个百分点的影响),并指出其用于验证真实分层和依赖方向的工具,本质上就是 archy check;而编码 Agent 失败模式相关文献则列出了架构反馈循环旨在捕捉的具体模式(范围偏移、跨文件推理失败)。引用、失败模式与 archy 能力的映射,以及由此得出的路线图优先级,见 docs/research/RESEARCH_METRICS.md §14c。

背后的机制

实证论据之下,还有一个结构性原因。Anthony Hobday 在探讨软件质量时,精准地指出了这一点:“事物的数量增加时,关系的数量增长得更快。最终,人们无法再妥善考虑所有这些关系。” 当这些关系仍能维持整体连贯时,系统便具有一致性;随着系统增长,这种一致性会持续丧失,也就是熵。单个作者通过记住每一条关联来维持代码库的一致性。以机器速度生成代码的 Agent 做不到,超过一定规模的团队也一样。

archy 读取的正是这种关系负担。耦合、DSM、导入循环和变更耦合,衡量的都是图偏离“一个人能把它记在脑子里”的程度。archy 将这种记忆外化为一个数字和一条趋势,让关系数量的增长始终清晰可见,而不是等到重构时才发现问题,并导致一切崩溃。

范围

  • 仅支持 Python。 跨语言支持由 sentrux 负责;分工已敲定。archy 深耕 Python(传递性契约、SDP、NCCD、if TYPE_CHECKING: 语义),而非广泛支持各种语言;参见 docs/LEARNINGS.md §“竞争格局”。
  • 由 Tree-sitter 驱动。 能稳健处理正在编辑的内容和不完整文件;即使出现会导致 ast 崩溃的语法错误,也能正常运行。
  • 随时间变化的评分。 每次提交生成一个分数,持久化并绘制趋势图。趋势比绝对值更重要。
  • 使用 YAML 定义规则。 “X 层不能导入 Y。”目前没有 DSL,也不支持插件。

非目标

  • 多语言分析
  • 替代代码检查器、类型检查器或测试运行器
  • 生成代码或自动修复违规

快速开始

上文的一条命令即可开始已介绍过;本节将详细说明。

需要 Python 3.10+(archy 依赖 mcp>=1.28.1,而它仅支持 Python 3.10 及以上版本)。如果你的系统只有 Python 3.9 或更早版本,请先安装较新的 Python,或使用 uv,它会为你管理版本,uvx 也由它提供。

pip install archy
# or: uv tool install archy
# or: pipx install archy
# or nothing at all: prefix any command with `uvx`, e.g. `uvx archy score .`

想在 AI 编码 Agent 中将 archy 用作 MCP 服务?无需手动配置,运行 uvx archy install 即可自动将其接入 Claude Code、Cursor、Codex、opencode 或 Continue,并写入调用 uvx archy mcp 的配置,因此 archy 无需加入 PATH。参见 docs/INSTALL.md。

以下示例均使用已安装的 archy 命令。如果你正在使用检出版本,请在命令前加上 uv run(例如 uv run archy graph .)。

参见 docs/SIXTY_SECOND_TOUR.md,了解从零开始到获取首个评分的复制粘贴操作流程。

检查依赖图
archy graph path/to/project --internal-only
archy graph path/to/project --format json > graph.json
archy graph path/to/project --format dot | dot -Tsvg > graph.svg
查找导入循环

按依赖深度排列的三个模块,其中 pkg.a 导入 pkg.b,pkg.b 导入 pkg.c,而从 pkg.c 指回 pkg.a 的反向边构成一个循环。

对模块进行排序,循环才会显现:每条普通导入都指向较低的排名,因此唯一一条指回较高排名的边就是整个问题所在。

Tarjan 强连通分量(SCC)大小 >= 2,另加自环(模块导入自身)。在 CI 中使用 --strict,即可在出现任何循环时失败。

archy cycles path/to/project
archy cycles path/to/project --format json
archy cycles path/to/project --strict
强制执行分层规则

从仓库根目录读取 archy.yaml。出现任何违规时以状态码 1 退出。参见下文的分层规则。

archy check path/to/project
archy check path/to/project --format json
archy check path/to/project --config custom.yaml
传递式契约(archy contracts)

archy check 只检查直接边。archy contracts 封装了 import-linter,以传递方式强制执行相同的分层规则(A → B → C 仍算作 A 可达 C)。对于分层会通过间接路径泄漏的项目,这是提高严格性的选择。

pip install 'archy[contracts]'
archy contracts path/to/project
archy contracts path/to/project --format json

archy check --contracts 会在 check 输出中嵌套运行相同的传递式判定。它不会改变 check 自身的退出代码:如果可选依赖缺失就能让通过的检查失败,那么在 CI 中启用这样的标志并不安全。如果要让传递式结果本身决定构建是否通过,请使用独立的 archy contracts。

保留的契约不一定提供保护。 如果契约的模块表达式与图中的任何内容都不匹配,那么无论代码如何,它都不会失败,因此 all_kept 会将其视为已保留,尽管实际上从未检查过任何内容。archy contracts 在 verified(已评估且符合要求)不成立时以非零状态退出,而不是在 all_kept 不成立时退出;出于同样的原因,archy check --contracts 会将 transitive_checked: false 作为结果报告:无法判定失败的规则不会被评估。文本输出会将此类契约标记为 ?? 而不是 OK,并指出哪些表达式没有匹配到任何模块;--format json 会在顶层添加 verified 和 unverifiable,与 kept/broken 并列,并在每个契约中添加 matched_nothing 和 unmatched_expressions。.importlinter 中 type = layers 的契约不需要此类标志:当必需层缺少模块时,import-linter 本身会拒绝运行,archy 会将其报告为配置错误;而可选层(写作 (name))则是按设计缺省的。

配置解析。 archy contracts 按以下顺序读取:

  1. 传入的 --config 参数。
  2. 项目根目录中的 .importlinter:规范的契约配置。
  3. archy.yaml:尽力回退。每条 forbid: 规则都会转换为一个以传递方式检查的 Forbidden 契约。此路径无法表达 ignore_imports,因此会发出 UserWarning:任何合理的传递边(例如服务层通过经批准的 app.libs.db.* 模块访问 psycopg)都会被报告为违规,且无法将其加入白名单。

两个配置,各司其职:

  • archy.yaml 负责层定义、直接边检查(archy check)、必需可达规则(required:)、sdp:、exclude: 和 roots:。
  • .importlinter 负责传递式契约:全部五种契约类型(Forbidden、Layers、Independence、Protected、AcyclicSiblings)以及 ignore_imports 白名单。

只要需要进行传递式强制检查,就应使用 .importlinter;archy.yaml 回退方案只是零配置的入门方式,并非功能目标。请参阅本仓库中的 .importlinter 了解实际示例,并参阅 import-linter 契约类型参考 了解完整语法。

常见情况:禁止服务访问 psycopg,但允许经批准的 db 库访问:

[importlinter]
root_package = app

[importlinter:contract:services-must-not-reach-psycopg]
name = services must not reach psycopg
type = forbidden
source_modules =
    app.services
forbidden_modules =
    psycopg
ignore_imports =
    app.libs.db.engine -> psycopg
计算质量分数

综合模块化、无环性、深度、均衡性和复杂度(五个维度的几何平均值)。公式及各项细分的解读方法,请参阅 docs/SCORING.md。这五个维度是在考察了软件包度量文献中的约 15 种替代指标后选定的(Martin 的 I/A/D、Lakos 的 NCCD、MacCormack 的传播成本、Structure101 的 fat/tangle、反思模型、认知复杂度、热点、逻辑耦合、死代码/重复代码检测);Martin 的 I 和稳定依赖原则检查也作为逐模块诊断和 archy check 规则提供。完整验证结果、已发布的内容,以及延期内容及其原因,请参阅 docs/research/RESEARCH_METRICS.md。

archy score path/to/project
archy score path/to/project --format json
跟踪分数变化

将每次提交的分数保存到 .archy/history.jsonl,并绘制趋势图。

archy score path/to/project --record
archy trend path/to/project
archy trend path/to/project --last 30 --format json
回归门禁

如果当前分数比最近一次记录的运行结果低超过 --strict-tolerance(默认值为 0.02),则失败。

archy score path/to/project --strict
archy score path/to/project --strict --record           # check then record
archy score path/to/project --strict --strict-tolerance 0.0
影响范围

列出传递依赖于指定文件的内部模块。在重构或移除模块前很有用。

archy impact path/to/project --file app/libs/db.py
archy impact path/to/project --file app/libs/db.py --file app/services/auth.py --format json
受影响的测试(CI 门禁)

archy affected 是 archy impact 的 CI 风格版本:给定已更改的文件,它会返回受影响的模块,并预先分类为测试和其他下游代码,同时设置深度上限(默认 5 跳),避免在大型单体仓库中,一行代码的编辑就扩散到数千个节点。可直接接收 git diff 的管道输入:

git diff --name-only HEAD | archy affected . --stdin
git diff --name-only HEAD | archy affected . --stdin --quiet | xargs pytest
archy affected . src/foo.py --filter "tests/integration/**" --json

测试分类默认遵循 pytest 约定(test_*.py、*_test.py,以及 tests/ 目录下的所有文件);可使用 --filter <glob> 覆盖。仅追踪内部模块;不会追踪 vendored 或第三方代码。

设计结构矩阵(archy dsm)

DSM 按所选顺序在两个轴上列出模块;当 source 导入 target 时,单元格 (row=source, col=target) 非空。按位置阅读可以揭示任何单一标量都无法体现的特性:按社区分组时的块对角内聚性、按拓扑排序时的对角线上方回边,以及按层分组时的跨块层泄漏。仅用于可视化(关于为何没有标量纳入评分,请参阅 docs/research/DSM_EMPIRICS.md)。

archy dsm path/to/project --group community     # block-diagonal orientation
archy dsm path/to/project --group topological   # back-edges sit above diagonal
archy dsm path/to/project --group layer --weight calls   # cross-layer call traffic
archy dsm path/to/project --focus pkg.module --focus-depth 1   # focal neighborhood
archy dsm path/to/project --format json > .archy/dsm-before.json
# ... edit code ...
archy dsm path/to/project --group topological --diff .archy/dsm-before.json
# prints any new back-edges the edit introduced

对于大于 --max-nodes(默认值为 80)的项目,archy dsm 会拒绝进行 ASCII 渲染,并给出可操作的错误提示,指向 --focus、--package 或 --format json。

静态 HTML 导出(archy render)

archy 的其他所有界面都面向 Agent。archy render 则面向审阅 Agent 工作成果的人:生成一个独立的 HTML 文件,可附加到 PR、放入文档,或离线打开。它不使用 JavaScript、CDN、vendored bundle 或服务器;对于固定输入,输出的字节内容保持稳定,因此两次导出可以干净地进行差异比较。

archy render path/to/project --view dsm --out dsm.html     # the matrix, flagged cells in red
archy render path/to/project --view dsm --group topological --out cycles.html
archy render path/to/project --view trend --out trend.html # five axes over .archy/history.jsonl
archy render path/to/project --view dsm                    # HTML to stdout

红色的含义取决于你选择的排序方式,因为只有一种排序能编码其含义:在 --group=topological 下,红色表示回边(循环种子);而在 --group=community 或 --group=layer 下,块顺序并非依赖顺序,因此红色表示跨越块边界的边。对于大于 --max-nodes(默认值为 300)的矩阵,DSM 视图会拒绝渲染,以免生成无法阅读的文件。

没有 graph 视图。节点链接图是唯一需要 vendored 布局引擎的视图,也是三种视图中信号最弱的一种;在获得使用信号之前,暂不实现(参见 docs/SPEC_VISUALIZATION.md)。

快照与差异比较(Agent 反馈循环)

在编辑会话开始时捕获基线,然后在编辑后进行差异比较,以准确查看发生变化的循环或层规则。完整操作指南请参阅 docs/AGENT_LOOP.md(也可通过 MCP 服务的 loop 提示词获取)。

archy snapshot path/to/project   # writes .archy/baseline.json
# ... edit code ...
archy diff path/to/project       # risk-weighted summary + score deltas + added/resolved cycles & violations
作为 MCP 服务运行

使用 Stdio 传输,以便 AI Agent 直接调用 archy。请参阅下文的 MCP 服务。

archy mcp

MCP 服务(archy mcp)

该服务由持久化解析缓存(.archy/index.db)提供支持:每次调用工具时,只会重新解析自上次调用以来内容发生变化的文件,因此即使在非常大的仓库中,预热后的图构建也只需几秒钟(基准测试:在 Home Assistant 的 17,299 个模块上,冷启动耗时 21.5 秒,预热后耗时 2.5 秒)。缓存透明且可随时删除;删除 .archy/index.db 只会导致一次冷启动重建。图始终会根据当前文件重新生成,因此缓存结果不会过时。archy index sync 可显式预热缓存;archy index clear 可将其删除。

archy mcp 向支持 MCP 的 AI Agent(Claude Code、Anthropic API 等)提供十三个工具和一个提示词:

工具用途
archy_score计算五项指标得分(模块化、无环性、深度、均等性、复杂度、几何平均值);可选 record=True 和 strict=True,实现与 CLI 相同的回归门禁行为。传入 record=True 可记录会话开始时的基线(替代已移除的 archy_record_baseline)。view="history" 返回 .archy/history.jsonl 中近期的得分历史(最多 last_n 行,按时间从早到晚排列,用于比较随时间变化的差值;替代已移除的 archy_trend)。
archy_cycles查找导入环。
archy_check根据 archy.yaml 运行直接边层规则。传入 contracts=True 还会运行传递式 import-linter 契约(Layers、Forbidden、Independence、Protected、AcyclicSiblings;比直接边规则更严格),结果嵌套在 contracts 字段中(替代已移除的 archy_contracts;需要 archy[contracts],如果缺少此额外依赖,则会给出提示,并降级为 available=false)。未找到 archy.yaml 时会跳过契约(首先会收到 CheckErrorPayload)。始终返回 coverage:规则实际覆盖的模块和导入边数量,因此可以区分 passed=true 与几乎没有管辖范围的配置。transitive_checked 表示本次运行是否实际传递式评估了 forbid 规则;如果未请求契约、契约未得出判定,或者任何契约都不可能失败(因为三项契约都未评估这些规则),则为 false。此时 transitive_unverified_reason 会说明原因:如果未请求契约,则会指出需要使用 archy_check(contracts=True);如果未得出判定,则会指出实际失败原因;如果契约未匹配到任何内容,则会说明匹配不到的契约数量。因此,干净通过时绝不会默默隐去仅检查了直接边这一事实。archy.yaml 中的 min_layers_present: 也会影响这里的 passed,而不仅是 CLI 退出码。required_violations 报告未满足的 required: 规则(模块 X 必须传递式到达 Y,并计入隐式的包 __init__ 导入),也会影响 passed;修复方法是添加缺失的导入,而不是删除规则。在 contracts 中,应查看 verified,而不是 all_kept:模块表达式与图中任何内容都不匹配的契约会被视为 kept,因为它不可能失败;unverifiable 会统计这些契约,每个契约都带有 matched_nothing 和 unmatched_expressions,用于列出未匹配到模块的模式。
archy_impact根据发生变更的文件路径,返回受其影响的内容。mode="blast"(默认)返回传递式导入这些文件的模块(影响范围),以及 chains:通往变更模块的最短导入路径(含行号),解释各模块为何受影响。mode="affected"(替代已移除的 archy_affected)则是面向 CI 的查询:将模块预先分类为 impacted_tests 和 impacted_modules,并限制深度(默认 5 跳),避免单行编辑扩散到数千个节点;test_filter 可通过递归 glob 覆盖 pytest 测试检测。co_change=true(blast 模式,可选)会添加 co_changed 列表:这些模块在 git 中历史上常与此次编辑一起变更,但与之没有导入/调用边,因此结构性影响范围会遗漏它们(archy coupling 限定于此次编辑;仅分析源文件,尽力而为,也是唯一读取 git 的路径)。
archy_snapshot将得分、环和违规项捕获到 .archy/baseline.json。在会话开始时调用。还会返回 invariant_brief(声明的层、禁止边、声明的 required: 到达规则、无环不变量、基线得分、承重模块),供首次编辑前查看。
archy_diff将当前状态与快照进行比较;返回新增/已解决的环和违规项、各组件得分差值,以及风险加权的 summary,其中的各项带有 prompt,将每个差值改写为一个判断问题(“新增环 a -> b;这是有意的吗,还是应该反转一条边?”)。使用 baseline(已提交的文件)时,strict=True 会添加 strict 判定:如果出现基线中不存在的任何环或层违规,passed 就为 false,reason 会列出闭合环的边。
archy_simulate编辑前的反事实检查:给定建议的导入边差异(添加/移除 {from, to} 对),返回可能产生的环、反向边、层/SDP/必需可达性违规、各轴得分差值及影响范围变化,不写入任何文件。提交重构前先测试假设。
archy_graph检查依赖图。不指定 focus 时,response_format="summary"(默认)会返回按入度/出度/PageRank 排序的 Top-N 概览,以及排名靠前的外部依赖(替代已移除的 archy_graph_summary;top_n 控制 N);response_format="full" 会返回与 archy graph --format json 相同的完整节点/边转储;如果图超过 max_nodes(默认 500),则会明确拒绝并返回 GraphTooLargePayload。传入 focus=[...](替代已移除的 archy_graph_focus),可查看围绕一个或多个模块(限定名称或文件路径)的有界子图:depth 限制跳数,direction 为 in/out/both,每条边都带有导入行号,且不适用 response_format/max_nodes/top_n。
archy_what_to_refactor_next按优先级排列的重构建议列表(通过 lens 替代已移除的 archy_hotspots 和 archy_high_risk_modules)。lens="fused"(默认)会将行为视角(CC x 变更频率)与结构视角(编辑风险:中心性 + 脆弱性)相加,得出 priority,因此同时被两种视角标记的模块通常会排在相近的单一视角模块之前(但占主导地位的单一视角信号仍可能排在最前)。lens="behavioral" 仅按 CC x 变更频率对热点排序(需要 git);lens="structural" 仅按编辑风险综合值排序(无需 git;传入 min_risk=0 可取消下限)。每个条目都会指出触发了哪些视角,并附带一行 rationale。空列表加上 note 也是有效答复。
archy_dsm导入图的设计结构矩阵视图。response_format="summary"(默认)会返回精简概览(块结构、计数、反向边、跨块耦合),不包含完整单元格列表。response_format="full" 会返回位置矩阵(当 source 导入 target 时,非空单元格为 (row=source, col=target));如果矩阵超过 DEFAULT_MAX_DSM_CELLS 个单元格,则会拒绝并返回 DSMTooLargePayload。group_by 控制行/列的排序(community 用于显示块对角线凝聚性,layer 用于分析层违规,topological 用于定位反向边)。weight 为 imports 或 calls。大型项目可通过 focus=<qualname> + focus_depth 或 package=<prefix> 缩小范围。提供 baseline_path 时,会返回结构化差异(无论 response_format 的取值如何),其中 new_back_edges 字段会标记编辑刚引入的环。仅用于可视化;请参阅 docs/research/DSM_EMPIRICS.md。
archy_duplicates将函数按归一化后的函数体形状聚为两层:duplicates(可能是真正重复的代码,值得调查)和 variants(降级处理的可能合理变体集群——同类/样板代码/测试/供应商代码/independent,每项都有 variant_reason)。co_change=true(默认,需要 git)会添加 independent 降级项:在积极维护的文件中,从未共同变更的副本(有意保持并行的实现);这是原则性的精确率调节手段,可将主要层级提升到约 50% 的语法检测上限之上(§12f)。在主要层级中,exact=true 会标记字节级完全相同的(Type-1)集群,即置信度最高的子集。near_miss=true(可选,速度较慢)会添加置信度较低的 near_miss 层:Type-3(有间隙)克隆通过 token 多重集合重叠查找精确形状哈希未命中的内容(§12h)。response_format="summary"(默认)返回排序字段和每个集群的一个样本成员;"full" 返回成员列表。这是辅助发现工具,而非得分轴:能否重构取决于语义判断(参见 docs/research/RESEARCH_METRICS.md §12c/§12f/§12h)。min_nodes(默认 30)会跳过简单的存根代码。
archy_conventions根据项目源代码报告项目自身的规范:naming(按所属模块分组的类名后缀系列,因此即将编辑的模块会显示其自身模式)、surfaces(对应的辅助函数集合,如 _x_to_text/_x_to_json,以及在多个模块中定义的名称——即“将这些项一并接入”的列表)、gates(发现未通过时会以非零状态退出的位置,每项都带有其字面退出 code 和控制开关:flag:--strict、config:<attr>、param:<name>、hardcoded)、errors(单独列出的用户错误退出项——配置错误、文件缺失——与门禁分开统计,以保证门禁计数有意义)、models(基类 / 冻结类 / 元组与列表的统计)、bases(按传递式继承来源分组的类别系列,仅统计项目自行定义的基类,并报告后缀是否一致,因此如果某个系列的名称不符合规则,也会如实说明)、registries(NAME = Ctor(...) 系列,以及传入的每个关键字的分布——展示项目在不使用类时如何声明代码或标志)、export_gaps(未包含在 __all__ 或 from .x import Y as Y 语句块中的成员,而这些语句块已经列出了它们的同类成员)和 doc_gaps(项目自身的 .rst/.md 文档表面未提及的成员)。在你不了解的项目中添加类、字段或命令之前调用。仅供参考:永远不会失败,也不会修改任何内容。min_family(默认 2)会提高报告门槛;include_tests 也会统计测试模块(默认 false,因为测试固定数据会冲淡真实系列)。
archy_module_view查看关于单个模块的所有已知信息,完整且不排序——对应 archy_conventions 排序报告的查询功能。由于报告会截断,某个模块未出现在报告中并不代表任何含义;这里列出的每项内容对指定模块都是完整的,因此可以直接据此回答“risk 是否导入 hotspots”或“是否仍有任何内容能到达 layers”。返回 imports_internal(已解析相对导入)、imported_by(即使测试通常被排除,也会计入测试导入者,因为仅被测试导入的模块仍然属于被导入)、classes、functions、exports(模块未声明 __all__ 且没有显式重导出时为 null——这与空列表不同)、suffix_families、gates 和 status;后者说明模块是否已统计或被排除,以及原因,因此不会把空视图误认为“没有内容可报告”。未知的模块名称会触发错误,而不是返回空视图。仅供参考:不会修改任何内容。
服务器还提供了一个 loop 提示词,其中包含 Agent 反馈循环手册(开始时拍摄快照、编辑前评估影响、编辑后查看差异)。可通过标准 MCP prompts/list 调用发现。人类可读版本请参见 docs/AGENT_LOOP.md。

archy mcp 服务仍会保留一个带防抖的文件系统监视器,为 .archy/index.db 预热,以确保图构建速度快;每个工具也会按需同步,确保结果不会过时。曾经作为 MCP 工具提供的索引新鲜度读数 archy_status,现在已移至 CLI archy index status(#267):新鲜度是诊断性底层信息,Agent 在任务过程中很少需要查看,不应作为每次编辑的决策依据。

工具输出约定(结构化输出)

每个工具都在 tools/list 中声明 outputSchema(根据其返回模型生成的 JSON Schema);每次 tools/call 都会返回一个 structuredContent 对象(经过该架构验证),以及一个包含相同 JSON 的文本块,符合 2025-06-18 MCP 结构化输出规范。除 archy_snapshot 外,每个工具都标注为 readOnlyHint: true(作用域封闭、幂等、非破坏性),因此受信任的客户端可以自动批准 archy 的调用,而不必在每次读取时都提示。archy_snapshot 会覆盖 .archy/baseline.json,因此声明了 readOnlyHint: false, destructiveHint: true,客户端可能会在运行前征求确认。序列返回值(archy_cycles、archy_score(view="history"))和联合返回值(archy_diff、archy_graph、archy_dsm)会封装在顶层 result 键下,因为 structuredContent 必须是 JSON 对象;对于联合类型,每个分支(包括带内的 *ErrorPayload 形状)都是符合规范的 anyOf 成员。

错误模型(恢复约定)

archy 按照统一约定,将故障映射到 MCP 的两种错误机制中,使 Agent 只需遵循一套恢复约定:

  • 用法错误 → isError: true(抛出异常):参数 值 无效(例如 response_format="xml"、last_n=0)、archy.yaml 格式错误,或项目超出扫描上限。调用方必须修正调用或环境。
  • 可恢复 / 提示性情况 → 带内结果(isError: false):未满足但可恢复的预期前置条件,或有效但降级的结果。这些是 Agent 会根据情况分支处理的正常结果。若无可用结果,则使用 联合类型变体(没有基线 → DiffErrorPayload、输出过大 → *TooLargePayload、没有配置 → CheckErrorPayload、没有 DSM 快照 → DSMErrorPayload);否则使用有效载荷上的 提示字段(ContractsPayload.available=false、WhatToRefactorPayload.git_available / WhatToRefactorPayload.note)。判断“无可用结果”变体的标志:有效载荷包含 error 字段且没有成功数据。
  • 协议错误(JSON-RPC):未知工具,或缺少必需参数 / 参数类型错误,由框架处理。
将其接入你的 Agent

运行一条命令即可检测已安装的客户端(Claude Code、Cursor、Codex CLI、opencode、Continue)并逐一完成接入:

uvx archy install        # detect, confirm, register the MCP server in each client
uvx archy uninstall      # the exact inverse; --dry-run to preview

此操作会注册 uvx archy mcp 服务、写入简短的规则文件,告知 Agent 何时调用这些工具,并且(在 Claude Code 中)填充 permissions.allow 允许列表。它不会安装二进制文件或 Claude 插件。完整指南包括各客户端的路径矩阵、适用于未知客户端的手动配置、插件与安装程序的选择建议以及故障排查,详见 docs/INSTALL.md。

对于 Claude Code,最省事的方式是使用内置插件 plugins/claude/:在 Claude Code 内运行 /plugin marketplace add hslee16/archy,然后运行 /plugin install archy@archy(或在检出目录中运行 claude --plugin-dir /path/to/archy/plugins/claude)。关于何时应优先选择插件而非安装程序,请参见 docs/INSTALL.md。

回归门禁语义

--strict 会读取 .archy/history.jsonl 的最后一行,并将当前得分与之比较。下降幅度超过容差时,命令会以退出代码 1 失败。默认容差(0.02)与 sentrux 的 gate 使用的阈值一致。这让 archy 能够实现与 sentrux 相同的回归门禁用例,同时保留长期 JSONL 历史记录,供 archy trend 使用。

CI 集成

GitHub Action

archy 提供了一个组合式 Action,可添加到任何工作流中:

- uses: hslee16/[email protected]
  with:
    command: score      # score | check | cycles | diff
    path: .
    strict: "true"      # fail on regression (score) or any cycle (cycles)

输入项(除非另有说明,否则均为可选):

输入项默认值说明
commandscorescore、check、cycles 或 diff
path.要分析的项目根目录
stricttruescore/cycles/diff:在回归/出现任何循环/发现新问题时失败
baselinearchy-baseline.jsondiff:用于逐步设定门槛的已提交基线
strict-tolerance0.02score --strict 的容差
recordfalsescore:将结果追加到 .archy/history.jsonl
config(自动)check:archy.yaml 的路径
python-version3.10要安装的 Python 版本
Pre-commit 钩子

添加到 .pre-commit-config.yaml:

repos:
  - repo: https://github.com/hslee16/archy
    rev: v0.50.0
    hooks:
      - id: archy-check          # layer rules from archy.yaml
      - id: archy-score-strict   # regression gate against last recorded score
      - id: archy-cycles         # fail on any import cycle
      - id: archy-no-new-cycles  # fail only on cycles/violations the baseline lacks

archy-score-strict 会读取 .archy/history.jsonl;先运行 archy score . --record 并提交基线。

在已有循环的代码库中采用

archy-cycles 遇到任何循环都会失败,因此在大多数现有代码库中,它从第一天起就会失败,而总是失败的门禁最终会被删除。请改用逐步设定门槛的方式:提交当前已有的问题,然后仅在出现新问题时失败。

archy snapshot . --findings-only --out archy-baseline.json
git add archy-baseline.json

然后使用 archy-no-new-cycles 钩子,或在 CI 中运行 archy diff . --strict --baseline archy-baseline.json(Action 的 command: diff)。如果出现基线中没有的循环或 archy.yaml 层级违规,它会以退出代码 1 退出,并将闭合导入边标记为 path:line;它绝不会因得分而失败。该文件经过排序,不包含得分、行号或 SDP 违规(它们的不稳定比例会随无关编辑而变化),因此在偿还技术债后重新生成,会得到便于审查的差异。

哪些情况算作新问题:位于所有基线循环之外的模块之间形成循环;现有模块被纳入循环;或两个基线循环合并。缩小或拆分循环会通过;重命名、将新文件加入现有循环,以及在已处于同一循环中的两个模块之间新增导入也会通过。--strict 始终需要 --baseline,并且即使指定了名称,也会拒绝 .archy/baseline.json,因为 archy snapshot 和 MCP archy_snapshot 工具会覆盖它。成熟代码库中出现新问题的情况很少见(根据 archy 自身的测量结果,占提交的 0.66%),因此应将其视为一种成本低廉的保障,让门禁得以保留,而非回归检测器。

层级规则(archy check)

在仓库根目录放置 archy.yaml,声明层级和禁止的方向:

layers:
  domain:
    modules:
      - "myapp.domain.**"
  application:
    modules:
      - "myapp.application.**"
  infra:
    modules:
      - "myapp.infra.**"
      - "myapp.adapters.**"

forbid:
  - {from: domain, to: application}
  - {from: domain, to: infra}
  - {from: application, to: infra}

模式语法。 点分隔名称的 glob:* 匹配一个片段,** 匹配零个或多个片段。myapp.domain.** 覆盖该包本身及所有后代,而单独的 myapp.domain 只精确匹配该模块,并会悄然将其子模块留在层级之外(相比之下,import-linter 契约会同时匹配指定包及其后代)。如果单独的名称模式遗漏了后代,archy check 会将其标记出来;请参见下文的 Coverage。层级模式可以以 **. 开头,表示“无论该包位于何处”(**.services.** 会匹配 services、conduit.services.user,但不会匹配 myservices.x);archy contracts 无法从此类层级推导出 import-linter 契约,因此当 forbid 规则使用此类模式时会拒绝并显示提示信息。每个模块最多只能属于一个层级。

必需依赖方向(required:)。 与 forbid: 相反。forbid 规则会捕获不应存在的边;required 规则会捕获应该存在但实际不存在的边,这是 forbid 无法表达的:

required:
  - source: "app.commands.*"
    must_reach: app.core.database.model_registry
    reason: standalone entrypoints need the full mapper registry

每个匹配 source 的模块都必须传递地到达 must_reach,并计入 Python 保证会发生的隐式包 __init__ 导入(导入 a.b.c 时,会先运行 a/b/__init__.py)。因此,在 app/commands/__init__.py 中导入一次,就能满足所有命令模块的规则,这通常才是正确的修复方式——直接导入规则会在修复后将它们全部报告为违规。

这源于一次生产事故:34 个命令模块可独立运行,每个模块都需要在首次配置映射器之前导入 SQLAlchemy 模型注册表。11 个模块导入了它,21 个在运行时崩溃,另有 2 个仅仅因为碰巧通过无关导入间接触达它才运行成功。这 2 个模块正是规则按可达性而非导入来定义的原因。

reason 会出现在每种输出中,因为“X 无法到达 Y”只是图中的事实,并非解释;无法说明理由的规则最终会被删除,而不是被满足。匹配不到任何内容的规则会被报告为违规,而不会被略过。注意,pkg.** 包含 pkg/__init__.py 本身;要将规则限定为子模块,请使用 pkg.*。

请如实看待这项功能:archy 无法推导出这类要求(这是框架语义,而非图结构)。必须有人了解这一约束并将其写下来。规则的作用是找出所有其他违反约束的模块,并防止后续编辑撤销修复——它是一种棘轮,而非检测器。

派生入口点(source: "@entrypoints")。 glob 只涵盖规则编写者已知的入口点。之后新增在其范围之外的脚本(例如与 app/commands/ 同级的 app/worker.py)不受约束,而这恰恰是最可能漏掉引导导入的模块。@entrypoints 会绑定 archy 派生出的可运行模块:

required:
  - source: "@entrypoints"
    within: "app.**"          # optional: keep only entrypoints matching this
    must_reach: app.core.database.model_registry
    reason: standalone entrypoints need the full mapper registry

入口点包括含有顶层 if __name__ == "__main__": 守卫的模块(带括号或通过 and 组合的形式也计入)、__main__.py,以及 [project.scripts]、[project.gui-scripts] 或 [tool.poetry.scripts] 中的目标。脚本目标会从扫描模块与仓库根目录之间的每个 pyproject.toml 读取(仓库根目录是包含 .git、.hg、.jj、.svn 或 .sl 的最近目录;如果不存在,则不会读取扫描目录之上的任何文件),因此 archy check src 能看到项目根目录中声明的内容,且每个 monorepo 子项目也会各自贡献其内容。每条违规都会说明模块是因上述哪一项而成为入口点。派生过程会找出所有可运行文件,包括带守卫的测试模块和一次性脚本,因此大多数规则都应使用 within: 将范围限定为承担该义务的目录树部分;其他 source 不允许使用 within,因为编辑模式即可达到相同效果。

未派生出任何内容的规则会被报告为违规。无法解析的 pyproject.toml 也会被报告为违规,因为忽略其中的脚本目标会使规则略过这些目标对应的模块。与任何已扫描模块都不匹配的脚本目标(例如位于 exclude: 目录中的目标或重新导出的第三方工具)会被跳过;对于没有 __init__.py 的命名空间包,acme.tool.cli 会匹配到 archy 扫描的 tool.cli。不会读取:setup.py 和 setup.cfg 中的 console_scripts,以及位于所有包之外的 .py 文件,因为它们在 archy 的图中没有模块名称。

排除目录。 添加可选的 exclude: 目录基名列表,以跳过代码生成输出、供应商代码等。每个名称都会在项目树中的任意位置进行匹配(机制与内置跳过项 .venv、node_modules、__pycache__ 相同):

exclude:
  - baml_client
  - generated

exclude: 适用于每种分析(graph、cycles、score、check)以及对应的 MCP 工具。 扫描规模保护(max_modules:)。 archy 会在扫描模块数超过上限时拒绝启动扫描,因此,命名的 exclude: 未能排除的误入第三方代码、缓存或生成目录不会再让扫描悄无声息地卡上几分钟。默认值(10,000)远高于最大的实际项目;触发上限的扫描会停止,并提示使用 exclude: 或缩小路径范围。覆盖或禁用它:

max_modules: 25000   # raise the ceiling for a genuinely large monorepo
# max_modules: 0     # disable the guard entirely

命名空间包(roots:)。 archy 通过遍历 __init__.py 文件来发现包。默认情况下,PEP 420 命名空间包(没有 __init__.py)不可见。将它们声明为根目录,以便后代获得限定名称:

roots:
  - app           # `app/main.py` becomes `app.main`
  - src/service   # `src/service/db.py` becomes `service.db`

如果没有 roots:,像 app/libs/db.py 这样的项目(没有 app/__init__.py)要么会被完全跳过,要么会显示为顶层的 libs.db,导致 app.libs.** 这样的层规则无法匹配任何内容。

层存在性(min_layers_present:)。 禁止层之间的依赖边,并不能说明这些层是否存在。一个将四层折叠成单个模块的代码库,由于根本没有跨层依赖边,会满足所有 forbid 规则,并且不报错。设置一个下限来检测这种情况:

min_layers_present: 3   # at least 3 of the declared layers must contain a module

无论如何,已声明但为空的层都会被报告,因为任何提及它们的规则都不起作用:

#   layers present: 2 of 4 declared; empty: repositories, models
#   FAIL: 2 layer(s) present, min_layers_present is 3

默认未设置,因此现有配置的退出码保持不变。其形式取自 Constraint Decay 论文(arxiv:2605.06445),该论文中的架构验证器将依赖方向规则与这一存在性下限配对(“4 个规范层中至少有 3 个以独立目录形式存在”)。bench/fixtures/conduit_clean/ 复现了论文中的三个案例。

这只是兜底检查,而非重点,测量结果也说明了这一点。 在 50 个由 Agent 生成的后端中,每一个都生成了全部四个层目录:该检查一次也没有触发,而方向检查则捕获了所有失败。保留它,用于检测其针对的层被折叠成单个模块的情况;但如果你在考虑如何为配置投入精力,应优先编写 forbid 规则。

发现配置。 除非指定 --config,否则 archy check 会沿 PATH 向上查找 archy.yaml。违规时退出码为 1。

覆盖率。 每次 check 都会报告规则实际覆盖了多少代码,无论检查通过还是失败:

$ archy check .
# No layer violations (config: archy.yaml).
#   layer coverage: 9 of 42 modules (21%), 16 of 117 internal edges (14%); 33 module(s) match no layer (`archy check --show-unlayered`)

之所以显示这一行,是因为无法触发的规则集与干净的代码库无法区分:如果没有它,覆盖 14% 导入边的配置会打印与覆盖全部导入边的配置相同的“No layer violations”。应重点关注边覆盖率,因为配置可以将大多数模块划入各层,却几乎不约束它们之间的任何边。覆盖率仅统计模式指定的根包(如果某一层使用浮动的 **. 模式,则会统计每个模块),因此,位于包旁边的脚本和基准测试会单独统计,不会拉低这个数字。--show-unlayered 会列出不匹配任何层的模块。

当覆盖范围退化时,结果会直接说明,而不是留待下一行提示。若配置使用裸限定名称作为模式,使其每个只匹配一个模块,而不匹配其下的任何代码,则会显示如下结果(退出码不变):

$ archy check .
# No layer violations, but this config governs 0 of 1 internal edges (0%), so no forbid rule can fire (config: archy.yaml).
#   layer coverage: 2 of 5 modules (40%), 0 of 1 internal edges (0%); 3 module(s) match no layer (`archy check --show-unlayered`)
#   layer 'domain' matches myapp.domain exactly; 1 descendant module is unlayered (myapp.domain.models). Did you mean "myapp.domain.**"?
#   layer 'web' matches myapp.web exactly; 1 descendant module is unlayered (myapp.web.views). Did you mean "myapp.web.**"?

如果树中没有任何模块属于配置所命名的根目录,结果会说明没有任何规则能够触发。

以上数字来自 archy 自身,而且并不好看。之所以在此展示,是因为另一种选择就是一无所知。

archy 以这种方式强制执行自身的架构;请参阅仓库根目录中的 archy.yaml,以及 .github/workflows/ci.yml 中的 archy check . 步骤。 稳定性检查(sdp:)。 可选启用 Robert Martin 的稳定依赖原则:模块不应导入比自身 稳定性更低 的模块。稳定性为 I = Ce / (Ce + Ca),其中 Ce 是传出的内部导入数,Ca 是传入的内部导入数,因此 I = 0 表示“被依赖,不依赖任何模块”(最稳定),而 I = 1 表示“依赖很多模块,无人依赖此模块”(最不稳定)。

sdp:
  enabled: true
  tolerance: 0.0   # ignore violations within this I gap; default 0
  mode: error      # 'error' fails the gate (default); 'warn' reports but exits 0

启用后,archy check 会标记每条目标模块的 I 严格高于源模块(加上容差)的内部导入边。无论是否启用 sdp:,每个模块的 I 也会显示在 archy graph --format json 中,因此你可以先审查,再启用强制检查。

渐进式采用。 现有代码库在开始时通常会违反 SDP。设置 mode: warn,让输出(以及 archy_check 的 sdp_violations 负载)报告违规,但不让检查门禁失败;待违规数降为零后,再切换为 mode: error。无论 sdp.mode 如何设置,分层规则违规始终会导致检查门禁失败。

开发

uv sync                    # install runtime + dev deps from uv.lock
uv run ruff check          # lint
uv run ruff format         # format
uv run ty check            # type check
uv run pytest              # tests

有一个 pytest 测试用例(test_pagerank_matches_networkx_when_available)会将 archy 手动实现的 _pagerank 与 nx.pagerank 进行比较,该测试需要 numpy/scipy。此依赖有意未包含在默认安装中(archy 不依赖科学计算栈);若要在本地运行该测试,请同步可选的 parity 组:

uv sync --group parity     # pulls in numpy + scipy for the parity test
uv run pytest              # the test now runs instead of being skipped

路线图

此路线图已关闭。以下内容均无计划实施,项目重新活跃也不会使其恢复:当前工作是本页顶部状态说明中所述的循环,而不是此列表。docs/ROADMAP.md 和 docs/FUTURE.md 也说明了相同的关闭决定及其原因。

索引与安装工作的两个阶段均已发布(第 1 阶段安装体验改进已在 v0.25.0 / v0.26.0 发布,第 2 阶段持久化索引和监视器已在 v0.27.0 发布)。以下内容仅作为曾考虑过哪些事项及其原因的记录保留,并非计划。其中几项基于一个后来已撤回的前提,因此在着手之前,请先阅读 docs/WHAT_DIDNT_WORK.md。欢迎任何人参与。

曾考虑但从未启动的事项:

  • 按模块拆分评分,让 Agent 可以询问“我的修改是否让这个模块变差了?”,而不是“整个项目是否退步了?”。与 archy_diff 搭配使用。
  • 可选择启用的 Agent 钩子(archy install --hooks):在 Agent 客户端中注册生命周期钩子(Claude 的 Stop、Cursor 的 afterFileEdit 等),在修改后自动运行 archy 门禁,这样无论 Agent 是否记得调用工具,循环都会触发。规范:docs/SPEC_INSTALL_HOOKS.md。
  • 静态脆弱性代理指标(高不稳定性 × 高扇入),作为无需使用 git 的热点替代指标。仅供参考,不作为评分维度。(重复函数检测已作为 archy duplicates CLI 命令发布:采用两级结果展示,主要列表列出“可能重复项”,降级列表列出“同类 / 样板变体”。文献综述确认,对于任何仅基于相似度的检测器,重构适用性精确率的预期上限约为 50%,因此语义判断交由 Agent 处理;变更历史中的共同变更分析是提升精确率的环节,已作为变更耦合机制中的 demote_independent(#242)发布 #131。CLI(archy duplicates)和 MCP(archy_duplicates,第 14 个工具)均已提供此功能。)

已发布的历史记录已移至 docs/SHIPPED.md,并按各功能所属的工具部分分组。它记录了已构建的内容,并不能证明这些内容有所帮助。

经实际验证后否决(保留在此,以免再次提出):任何形式的类型提示覆盖率、将 calls_per_edge 作为第 6 个维度、面向 Agent 的命令输出 HTML、死函数检测、多语言分析。各项的依据见 docs/ROADMAP.md。

更完整的列表见 docs/FUTURE.md,设计说明见 docs/LEARNINGS.md。

贡献

样式规则见 CONTRIBUTING.md。特别注意:仓库中任何位置都不得使用长破折号字符(U+2014)。

报告安全问题

请通过 Security 选项卡私下报告漏洞,不要将其作为公开议题提交。范围和响应目标见 SECURITY.md。

许可证

MIT,详见 LICENSE。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。