@arch-guard/core

中文文档
0.11.3发布于 2026/10/30MITTypeScriptES Module近 30 天下载 1695

把架构写成可判定不变量的编码门禁:分层与边界、唯一出处、状态纪律、设计系统与魔法数字、文案、依赖、反退化

中文使用文档
文档目录14 节
  1. 为什么需要它
  2. 安装与运行
  3. 三种用法:用库 / 换库 / 不用库
  4. JSON 报告是对外契约( apiVersion )
  5. 文档与门禁同一份真相
  6. 写之前问契约( --explain )
  7. 检测范围(scope)
  8. 例外:规则级,且必须指名
  9. 引擎不绑宿主
  10. 速度
  11. 质量保障
  12. 文档地图
  13. Roadmap
  14. 许可

arch-guard

把架构写成可判定不变量的编码门禁:分层与边界、唯一出处、状态纪律、设计系统与魔法数字、文案契约、依赖选型、反退化。

本工具不是 linter:单文件语法卫生(any、console、!important、颜色字面量、行数上限…)交给 eslint / oxlint / stylelint / knip;我们只做它们做不到的事 —— 跨文件契约与可判定架构红线 (角色表、域边界、令牌图、CSS Module 双向契约、多语言同构、能力指纹…)。

它不是又一个 linter —— 红线只落在可判定的等级上(L1 路径 / L2 单文件 AST / L3 依赖图),语义判断(「命名好不好」「该不该拆」)一律不写进红线,所以它零误报,能长期活在 CI 里。

有骨架、可跑、已发布形态就绪;规则集在持续补齐(见文末 Roadmap)。

用法只有一处:docs/USAGE.md(安装 / 配置全字段 / 预设与参数 / 命令 / 报告 / CI / 排查)。 本文只留最短可用的一份与文档地图。

为什么需要它

agent 写代码最典型的退化不是语法错误,而是结构退化:

  • 把业务逻辑塞进 shared/lib 的一个「通用」函数文件(依赖规则全过)
  • 复制粘贴一份实现,而不是把共用逻辑提升到共享层
  • 在组件里直接 fetch,query key 随手写字面量
  • 顺手留 TODO / console.log / as any / 空 catch
  • 一个文件长到 800 行

这些 eslint 与 tsc 都管不到。arch-guard 把它们变成可执行的检查。

安装与运行

pnpm add -D @arch-guard/core        # 包名;命令名是 arch-guard

要求 Node ≥ 22.18、TypeScript 5.4 – 6.x(typescript@7 是原生重写,JS 侧不再暴露编译期 API, 装到它会明确报错而不是崩在 undefined)。

在项目根建 arch.config.mjs(最小可用形态):

import { canonical, designSystem, hygiene, reactPack } from '@arch-guard/core/presets'

export default {
  sourceForm: 'react', // 框架包:声明的是**源码形态**,一个项目一个(React 用 reactPack,库 / CLI 用 tsPack)
  presets: [canonical(), designSystem(), hygiene()], // 范式三选一 + 域预设任意子集
}
npx arch-guard                          # 全项目检查(0 = 通过 / 1 = 有 error / 2 = 请求无法满足)
npx arch-guard --scope=changed          # 只报告 git 变更文件(含未跟踪)
npx arch-guard --domain=D --format=json # 只看设计系统,输出 JSON(给 agent / CI;带 apiVersion 契约)
npx arch-guard --explain src/modules/crews/views/CrewsList.tsx  # ★ 写之前问契约

三种用法:用库 / 换库 / 不用库

组件库是可选、可替换的配置轴。引擎里不允许出现任何具体库名:

uiKit(antdKit()) // 用 antd
uiKit(noneKit()) // 不用组件库:vendor / 全局 API / 图标来源相关规则不注册

换库 = 写一份约 30 行的适配器(packages / vendorSelectors / detachedApis / examples)。 data/kit-fingerprints.ts 内置已知组件库指纹,于是换库后旧库残留一条不剩是可判定验收条件。

同形的可替换轴还有 i18n / 路由 / 数据层 / 样式 —— 逐轴的适配器字段与"不声明就明列停用"的规则对照表见 docs/USAGE.md §2。

JSON 报告是对外契约(apiVersion)

契约的权威表述(字段 / code / 退出码语义与变更分级)在 docs/DESIGN.md §6.9; 消费方的接法(穷举映射 / 未知 code 守卫)见 docs/USAGE.md §7。

--format=json 给 CI 注解 / PR bot / IDE 插件 / agent 用,所以它和规则一样是契约:

  • 带 apiVersion(当前 2):消费方启动时断言自己认识的版本;不认识就明说"不认识这版报告",别少读几个字段装绿。
  • 自述是结构不是散文:notices: [{ code, text }] —— 按 code 判,文案随便改都不破坏契约; code 清单由 NOTICE_CODES / isNoticeCode 导出(消费方用穷举映射,我们新增 code 时它的构建会红)。
  • 机读 ⊇ 人读:摘要行里的每个数字,JSON 里都有。
  • ok ≠ 退出码:ok 答"结论是不是通过"(判过的没 error 且确实判了);退出码答"要不要拦" (受 --report-only / --local-only 影响)。只看 stdout 的消费方以 ok 为准。
  • 「没判任何东西」可判定:--paths 一个都没匹配上 → paths.matched = 0 + code: 'paths-no-match' + 退出码 2 + ok: false。
  • 契约变更(增删顶层字段 / 增删 code)必须动 apiVersion,而这是被 tests/report-contract.test.mjs 冻结住的。

文档与门禁同一份真相

arch.config.mjs 是唯一机读真相,但文档里的表是手抄的 —— 改一处忘一处就漂移。把要同步的片段包起来:

<!-- arch-guard:begin deps -->

(本节由 `arch-guard --render-docs` 生成,别手改)
<!-- arch-guard:end deps -->
arch-guard --render-docs   # 按 arch.config.mjs 重写块内容
arch-guard --check-docs    # 只校验:不一致即红(进 pnpm check 链路)

可用块名:deps · thresholds · layout · structure · scan-scope · roles · params · exceptions。 块名拼错直接报错(否则"文档已同步"是假象)。宿主架构说明的起手模板见 docs/templates/ARCHITECTURE.md.template。

写之前问契约(--explain)

门禁平时只在事后说"你错了",agent 于是靠试错逼近规范。--explain 把约束前移:

arch-guard --explain src/modules/crews/views/CrewsList.tsx
#    角色       module:views · 层 10 · 槽位 views · 域 crews · 组 crews
#    依赖       层序单向:只许依赖层号 ≤ 10 的文件(S21)
#    命名       hook 前缀 `use` · 页面后缀 `Page`
#    启用规则   18 条(结构 11 · 依赖 5 · 度量 2)→ 逐条给出 id / 标题 / 修法
#    因能力停用 P05 / P11 / …(缺能力,不是通过)
  • 一条规则都不用跑:数据全部来自角色表 + 布局 + 结构声明 + params,所以零误报;
  • 路径还没写也照样能问:命中不了任何角色 → 它会告诉你该放哪;
  • 域外 / ignore / 歧义都会明确说清(歧义会点出全部命中角色);
  • --format=json 输出结构化结果,适合 agent 消费;退出码恒为 0(这是查询,不是判决)。

检测范围(scope)

**scope 只过滤报告,不过滤正确性。**语义的权威表述在 PARADIGM.md §9, 缓存与增量机制在 docs/ARCHITECTURE.md §8。

场景命令
CI / 提交前--scope=full(默认)
pre-commit--scope=staged(读 index 内容,不读工作区)
agent 迭代--scope=changed(含未跟踪文件)
PR--scope=since:origin/main

facts 按文件缓存(增量),图与全局谓词每轮全量重建(不会 stale-cache 假绿); 不可归属的全局违规默认仍然失败(只有显式 --local-only 才允许跳过并列出条数);无 git / 空 diff 会明确提示降级,绝不静默。

例外:规则级,且必须指名

权威在 PARADIGM.md §7:规则级例外是唯一的宽松通道,且必须指名。

违规没有存量豁免:没有基线、没有"一键把当前违规记下来"。不合规就是红 —— 这是刻意的取舍, 因为任何"先把存量记下来"的通道最终都会变成日常动作,而不是例外。

overrides: {
  exceptions: [
    // 「这条规则对这类文件不适用」——不是「这个文件免检」
    { rule: 'H03', glob: 'src/engine/output.ts', reason: '门禁的唯一输出出口', expires: '2026-12-31' },
  ],
}
  • rule 必须真实存在(拼错直接报错);reason 必填;expires 写了就过期即红;
  • 文件照常有角色、进依赖图、被其它规则判定 —— 只有指名的那条规则被摘掉;
  • 每次运行都会指名列出每条例外(命中几处 / 未命中),未命中的会提示"可能可以删掉";
  • 没有内联豁免注释(连 eslint-disable 都是红线 H02)。

别和 覆盖率棘轮(M04)混淆:它比的是覆盖率快照(--update-coverage 维护),不豁免任何违规。

引擎不绑宿主

为什么 P1 是审查门而不是「零依赖洁癖」见 docs/DESIGN.md §1.2;检查清单本身见 docs/ARCHITECTURE.md §7。

本包以 npm 包发布,宿主只写 arch.config.mjs。四条可机检的不变式由 arch-guard --self-check-portability 强制:

不变式内容为什么
P1依赖必须显式登记(node:* 与白名单第三方)门禁读你全量源码、跑在 CI:新增依赖要有理由,且不得把宿主拖进版本冲突
P2不得出现宿主项目字面量(宿主名、绝对路径)换宿主不用改引擎
P3引擎层不得假设项目布局(engine/** 不许出现 'src/' 这类字面量)范式可换:canonical / library / 自定义目录都靠它
P4库名只许出现在数据表(data/*)与适配器面(presets/<面>/*)同一引擎要能服务多种宿主与技术选型;名单从允许位置自己长出来

P1 是审查门,不是"零依赖"洁癖:要加依赖就改 portability.ts 的白名单并说清理由 —— 它挡的是"顺手引进来的依赖没人看过"。P2/P3 与发布方式无关,是引擎能同时服务多种范式的根本;P4 是"同一引擎服务多种技术选型"的总保证。

于是「换一个宿主」是加法:换 arch.config.mjs 即可,引擎一行不改。

速度

解析(把每个文件拆成规则能读的事实)是全流程里唯一昂贵的一步,所以它的结果按文件缓存 —— 键 = rel + role + 文件内容哈希,整份缓存的键 = 事实模型版本 + TypeScript 版本;图与全局谓词每轮全量重建。 缓存位置跟 Vite 一样(node_modules/.arch-guard-cache/facts.json.gz,没有 node_modules 就退回项目根), --no-cache 可关。实测(3043 个 ts 文件 / 21.5 万行):冷跑 5.9s → 热跑 1.2s。

测量方法与「换 parser 的代价」见 docs/DESIGN.md §6.1.1 · docs/ARCHITECTURE.md §8。

质量保障

这些命令各自防什么、由谁强制,见 docs/ARCHITECTURE.md §7(元门禁)。

pnpm build                     # pagoda-cli 构建(es/ ESM + lib/ CJS + d.ts)
pnpm typecheck                 # tsc --noEmit
pnpm lint                      # eslint(禁 any / 非空断言 / console)
pnpm test                      # node --test(引擎 API + 夹具端到端 + 单元分支)
pnpm coverage                  # 覆盖率报告(Node 内置)
pnpm check                     # ★ 提交前的完整门禁:build + 类型 + lint + 格式 + 测试 + 覆盖率 + 三项自检 + 示例宿主 + 狗粮
pnpm self-test                 # 夹具回归:每条规则违规必报 × 合规不报
pnpm self-check-portability    # P1 / P2 / P3 / P4
pnpm guard:sample              # 拿 examples/minimal 当宿主跑一遍
pnpm guard:self                # 狗粮:门禁跑自己(library() 范式)

文档地图

这份表是文档地图的唯一来源(docs/ARCHITECTURE.md §10 与 REQUIREMENTS.md 附录都只给指针)。 「唯一来源」= 那件事只在那里写,别处不许复制第二份。

文档读者回答什么唯一来源范围
REQUIREMENTS.md所有人痛点(带例子)/ 期望行为 / 状态 / 这条由谁来做(本体 or 委派)✅ 需求
docs/USAGE.md宿主团队怎么用:安装 / 配置全字段 / 预设与参数 / 命令 / 报告 / CI / 排查✅ 使用说明
docs/ARCHITECTURE.md改本体的人三层分离 / 数据流 / 模块职责 / 不能破的边界 / 扩展点 / 元门禁✅ 本体架构
PARADIGM.md宿主团队目录契约 / 三条公理 / 原语 / 判定等级 / 适配器契约(可整篇搬走)✅ 宿主规范
docs/DESIGN.md改规则的人规则清单 / 判定等级 / 配置协议 / 报告契约 / 选型纪律规则与协议
CONTEXT.md所有人词汇表(角色 / 组 / 落点 / 方案面 / 唯一出处…)✅ 词汇
docs/adr/所有人决策的理由(不可变记录;其中的数量是当时快照)✅ 决策
docs/ALTERNATIVES.md · ECOSYSTEM-AUDIT.md选型的人与生态的对比 / 逐条覆盖审计(0.3.x 快照 + 修订段)分析
docs/DELEGATION-REVIEW.md选型的人委派评审:委出去的那几条,宿主开启可能性多高、哪几条该收回来分析
docs/agents/agent协作约定(开发流程 / 需求只讲场景 / issue tracker / triage / domain)✅ 约定
.scratch/<slug>/spec.md做那件事的人单个需求的实现规格(场景 / 验收 / 边界 / Comments)单特性
docs/templates/ARCHITECTURE.md.template宿主团队宿主架构说明的模板(约 60 行,带可渲染块):抄到仓库根,人读文档与机读配置同源模板
AGENTS.mdagent改代码前 / 提交前怎么做(含 --render-docs 生成的配置块)操作指南
README.md · CHANGELOG.md外部读者概览与进度 / 每个版本实际发生了什么概览与历史

Roadmap

需求的全貌(痛点 / 期望行为 / 状态 / 由谁来做)在 REQUIREMENTS.md(唯一来源); 每条规则的判据在 docs/DESIGN.md §5。这里只记里程碑,不逐条抄规则。

  • P0 骨架:配置 / 扫描 / 事实模型 / 图 / 适配器 / 能力协商 / 例外通道 / scope / 自检
  • 六个域全部落地(结构 · 设计系统 · 文案 · 依赖 · 反退化 · 度量):共 107 条规则,107/107 有夹具(夹具全部 exact:多报也报错)
  • 声明驱动:目录契约、结构阈值、方案面形态都是数据(canonical / library / fsd 三范式 + stack() 组合)
  • 可替换面 + 开放注册:ui-kit · i18n · router · data-layer · styles · call-sites(新面由预设登记,加面不改引擎;一个面只能有一个方案)
  • 唯一出处:色值 · 令牌 · 路由路径 · 缓存键 · 副作用调用 · 策略数字 —— 手写第二处就报
  • 与 lint 生态不交叉:单文件语法卫生、颜色 / !important / 数值白名单、幽灵依赖、裸文案等按 docs/DESIGN.md §4.9 委派出去
  • 待做与优先级:见 REQUIREMENTS.md 第七节(上帝域 · 状态纪律 · 权限散落 · 路由守卫 · 迁移单向 · monorepo…)

许可

MIT

版本与发布记录

@arch-guard/core 共发布 26 个版本,首次发布于 2026/09/23;过去 12 个月发布了 26 个版本。

版本发布日期距今
0.11.3latest2026/10/3010 天前
0.11.22026/10/2911 天前
0.11.12026/10/2515 天前
0.11.02026/10/081 个月前
0.10.12026/09/281 个月前
0.10.02026/09/271 个月前

关于 @arch-guard/core 的常见问题

@arch-guard/core 是什么?

把架构写成可判定不变量的编码门禁:分层与边界、唯一出处、状态纪律、设计系统与魔法数字、文案、依赖、反退化

如何安装 @arch-guard/core?

在项目目录运行 npm install @arch-guard/core,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:typescript(>=5.4.0 <7)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 arch-guard,可以用 npx arch-guard 直接运行。

@arch-guard/core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@arch-guard/core 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@arch-guard/core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.18.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@arch-guard/core 可以商用吗?

@arch-guard/core 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@arch-guard/core 的下载量有多少?

根据 npm 官方下载统计,@arch-guard/core 在 2026/10/07 – 2026/11/05 这 30 天内被下载 1,695 次,日均约 57 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@arch-guard/core 还在维护吗?

最新版本 0.11.3 发布于 2026/10/30(10 天前);至今共发布 26 个版本,其中过去 12 个月发布了 26 个;首次发布于 2026/09/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。