1. 场景与痛点
在 AI 辅助开发越来越普及的今天,一个典型场景正在高频出现:开发者把仓库结构、一段核心代码或者一段「系统如何工作」的中文描述丢给 AI,希望它直接产出一张能看懂、能点、还能回到源码的架构图 / 时序图 / 数据流图。
但现实往往是:AI 返回一小段 Mermaid 或 PlantUML 文本,渲染后节点无法点击;或者返回一张截图,既不能缩放、不能搜索,更无法追溯某个节点对应的是哪一段代码。想要「可交互 + 可溯源」,通常还得手工搬运到 Draw.io 或自研前端,链路又长又脆。
tt-a1i/archify(日增 ≈3.9k–14.9k,JS)正试图把这条链路做短、做确定:让 Agent 只负责产出结构化数据,让编译器负责从数据到图表的确定渲染。
2. archify 解决什么问题
archify 的核心主张可以概括为一句话:
把「画图」从 Agent 手里拿回来,交给确定性的编译器。
传统做法中,如果你让 LLM 直接生成 Mermaid 源码,存在几个典型问题:
- 语法不稳定:节点文本里出现
(、:、&、中文、空格时,Mermaid 经常解析失败,Agent 需要反复试错。 - 样式不可控:同一份语义,两次请求可能画出完全不同的布局,难以沉淀为团队规范。
- 无法溯源:图上的框只是字符串,没有办法反查到它来自哪一行代码、哪个函数、哪段系统描述。
archify 的方案是:Agent 输出带类型的 JSON IR(中间表示),再由确定性编译器把它编译成自包含的 HTML/SVG。类型系统约束了「哪些节点、哪些边、哪些属性」是合法的,渲染层则保证同样的 IR 永远得到同样风格的图。
{"type":"architecture","nodes":[{"id":"gateway","label":"API Gateway","kind":"service","source":{"repo":"my-app","file":"src/index.ts","line":42}},{"id":"orders","label":"Order Service","kind":"service","source":{"repo":"my-app","file":"src/orders/main.ts","line":15}},{"id":"db","label":"PostgreSQL","kind":"storage","source":{"repo":"my-app","file":"infra/compose.yml","line":8}}],"edges":[{"from":"gateway","to":"orders","label":"REST /order"},{"from":"orders","to":"db","label":"SQL"}]}3. 核心原理:类型化 JSON IR
archify 位于中间的那一层,就是这篇文章的重点——类型化的 JSON IR。
它不是简单的 JSON 字符串拼接,而是一套带 Schema 的中间表示:
type区分图种类:architecture(架构图)、sequence(时序图)、dataflow(数据流图)。nodes是带类型的节点集合,kind限定节点类别,如service、storage、queue、client、function。edges描述关系,label是带语义的连线说明,而不是随意画一条箭头。source是溯源锚点,记录repo、file、line,让每个节点都能回到原始代码位置。
为什么强调「带类型」?因为有了类型,上层可以被验证、被补全、被自动补默认样式;一旦 Agent 生成了不合法的 IR,编译器能给出确定性的错误信息,而不是产出半张烂图。这也让 LLM 的 Function Calling / Tool Use 模式特别适配:把 IR 的 Schema 当作工具的入参 JSON Schema,Agent 的产出天然就是合法数据。
4. 确定性编译:从 IR 到自包含 HTML/SVG
「确定性编译」是 archify 与「让 LLM 直接画图」的本质区别。
编译器的职责是:输入同一份 IR,输出同一个 HTML 文档。布局、配色、节点形状、连线路由都由代码决定,不交给随机采样或模型猜测。这样带来的好处是:
- 可重复:同样的系统描述,每次渲染结果一致,便于 CI 中回归比对。
- 可版本化:IR 是纯文本,可以放进 Git,diff 清晰;图只是 IR 的「构建产物」。
- 自包含:输出是单文件 HTML/SVG,内联样式与脚本,发给同事双击就能打开,不依赖在线服务。
编译产物支持交互:缩放、拖拽、节点高亮、连线走向展示、点击节点查看详情与溯源信息。这些行为也都由编译器固定生成,而不是前端运行时临时拼装。
5. 可交互设计
一张「能交互的图」至少要解决三个问题:看得清、走得动、点得开。
archify 的交互层围绕节点和边来设计:
- 缩放与平移:大图不糊,局部细节可以放大查看。
- 节点聚焦:点击某个服务,高亮它关联的所有边,弱化无关节点,快速看清上下游。
- 连线含义即图:边上的
label不是装饰,而是接口、消息、数据流向等语义标注,悬停可见。 - 布局自适应:根据节点数量与关系密度选择适合的布局策略,避免 100 个节点挤成一团。
因为交互行为在编译期就固化进产物,所以分享出去的 HTML 不需要后端服务器,也就能稳定地复现这些体验。
6. 可溯源:节点反查源码
「可溯源」是 archify 与一般绘图库最值得单拎出来讲的能力。
每个节点可以携带source元数据,记录它来自哪个仓库、哪个文件、甚至第几行。当读者在图上点击某个节点时,产物里的脚本体可以:
- 展示该节点对应的代码定位信息;
- 若在浏览器环境中,可拼接出 GitHub 链接跳转到对应行;
- 在开发者本地,可跳转到编辑器打开对应文件位置。
这恰好补上了「看完图想去读代码」的最后一步:图不再是孤立的视觉结果,而是一张可以反向索引回代码的地图。对架构评审、新人 onboarding、遗留系统梳理尤其有价值——人们一边看图,一边就能定位到真实实现。
constir={type:"architecture",nodes:[{id:"auth",label:"Auth Middleware",kind:"service",source:{repo:"my-app",file:"src/middleware/auth.ts",line:7}}],edges:[]};consthtml=compile(ir);// 自包含 HTML,节点可点击并反查源码7. 与 AI 结合:Agent 是主编,编译器是排版
在 archify 的定位里,AI 和编译器的分工很清晰:
- Agent 负责「理解」:读代码、读文档、读用户描述,提取出有哪些系统组件、它们之间怎么调用、数据如何流动,然后把理解结果结构化成 IR。
- 编译器负责「渲染」:把 IR 变成可交互、可溯源、风格统一的可视化产物。
这个分工的价值在于,它把模型最容易出错的部分(生成图形的文本语法)从循环里拿掉。模型只需要产出 JSON,这是它最擅长、也最容易被 Schema 约束的任务;图形的正确性和美观度则由确定性代码保证。于是 AI 生成架构图这件事,就从「碰运气式地写 Mermaid」变成了「可审校、可验证、可自动化」的结构化产出流程。
8. 快速上手
archify 是 JS 生态项目,安装与调用都比较轻量:
npminstallarchify核心用法是把 IR 交给编译函数,得到 HTML 产物:
import{compile}from"archify";constir={type:"sequence",nodes:[{id:"client",label:"Browser",kind:"client"},{id:"api",label:"API",kind:"service"},{id:"worker",label:"Job Worker",kind:"queue"}],edges:[{from:"client",to:"api",label:"POST /jobs"},{from:"api",to:"worker",label:"enqueue"}]};consthtml=compile(ir);生成的结果可以直接写入文件并在浏览器中打开,也可以在网页里作为 Iframe 或独立页面嵌入。由于产物自包含,部署到静态托管即可,无需额外服务。
10. 实战:从代码仓库生成架构图rom "a"或missing required property “type”时,通常不是随机错误,而是 Schema 校验没有通过。关键是看错误里的路径与枚举值:node[3].kind说明问题出现在第 3 个节点的kind字段;unknown kind则多半是大小写或拼写不符合 IR 规范。先用node和edge` 下标收缩到出问题的对象,再对照 IR 类型定义修正字段值,通常就能从“半张烂图”恢复到可编译状态。
节点 source 定位不准的调整方法
如果图上节点能生成,但点击后跳到了错误文件或错误行,优先检查扫描器能否拿到可靠的baseDir / root。source.file最好以仓库根为基准、使用相对路径;如果扫描器没有统一路径,就可能把同一模块识别成两个节点或指错文件。对于动态导入、路由注册等隐式调用,可以让扫描器输出采样到的符号与位置,人工确认后再把解析规则收口,而不是直接接受自动结果。
扫描 include 规则优化建议
include不宜直接写**/*,否则会把测试、构建产物、Node_modules 都卷进来,节点多且杂。更稳的做法是按目录聚焦,比如只扫routes/**/*.ts、services/**/*.ts、data/**/*.ts,并用exclude排除*.test.ts、*.spec.ts、dist/**、node_modules/**。扫描前可以先加--dry-run输出命中文件清单,确认没有漏掉核心模块,也没有把噪声文件纳入 IR。
编译产物在浏览器中无法交互的排查步骤
如果打开 HTML 后只能看到静态图、不能缩放或点击,建议按顺序排查:
- 确认调用
compile时传入了{ interactive: true },否则产物可能退化为静态 SVG。 - 检查输出文件是否为单文件自包含产物,内联脚本没有被构建流程、邮件或网盘预览过滤掉。
- 尽量避免直接双击使用
file://打开,改在项目目录启动静态服务器,例如python -m http.server 8080,再访问http://localhost:8080/architecture.html。 - 打开浏览器 DevTools 的 Console 面板,查看是否有脚本报错,根据红色错误定位是资源缺失还是交互层初始化失败。
10. 一个更完整的示例:订单处理数据流这里用一个 Express 单体应用为例,展示如何先用 archify 扫描真实代码仓库得到 IR,再把 IR 编译成可交互 HTML。
my-express-app/ ├── src/ │ ├── app.ts │ ├── routes/ │ │ ├── orders.ts │ │ └── users.ts │ ├── services/ │ │ ├── orderService.ts │ │ ├── userService.ts │ │ └── paymentService.ts │ └── data/ │ ├── db.ts │ └── redis.ts └── package.json假设 archify 的扫描器会按文件路径与导入调用关系识别节点:routes下的文件是入口路由,services下的文件是业务服务,data下的文件是存储依赖;调用关系来自import/require与函数调用。
9.1 使用 CLI 扫描并编译
# 扫描 src 目录,生成类型化 IRnpx archify scan ./src-fexpress-oarchitecture.ir.json# 把 IR 编译为自包含 HTMLnpx archify compile architecture.ir.json-oarchitecture.html执行后终端会输出类似:
Scanned 12 files, extracted 8 nodes and 8 edges. Wrote architecture.ir.json Wrote architecture.html (23.6 KB)9.2 使用 Node.js API 完成同样的事
如果你需要把扫描、IR 清洗、编译串进 CI 或自己的脚本,可以用 Node.js API:
import{scanProject,compile}from"archify";import{writeFile}from"node:fs/promises";asyncfunctionmain(){// 1. 扫描代码仓库,自动提取 service 节点与调用关系constir=awaitscanProject("./src",{format:"express",include:["routes/**/*.ts","services/**/*.ts","data/**/*.ts"]});// 2. 可选:打印 IR,便于 Code Review 或保存版本console.log(JSON.stringify(ir,null,2));// 3. 确定性地编译为自包含 HTMLconsthtml=compile(ir,{interactive:true});// 4. 写入本地文件awaitwriteFile("architecture.html",html,"utf8");console.log("Generated architecture.html");}main().catch(console.error);9.3 自动生成的 IR 片段
上述扫描可能得到这样的中间表示:路由、服务、存储被分为不同类型的节点,连线标签则保留真实调用语义。
{"type":"architecture","nodes":[{"id":"app","label":"Express App","kind":"service","source":{"file":"src/app.ts","line":1}},{"id":"routes_orders","label":"Order Routes","kind":"service","source":{"file":"src/routes/orders.ts","line":8}},{"id":"routes_users","label":"User Routes","kind":"service","source":{"file":"src/routes/users.ts","line":6}},{"id":"order_service","label":"Order Service","kind":"service","source":{"file":"src/services/orderService.ts","line":12}},{"id":"user_service","label":"User Service","kind":"service","source":{"file":"src/services/userService.ts","line":9}},{"id":"payment_service","label":"Payment Service","kind":"service","source":{"file":"src/services/paymentService.ts","line":5}},{"id":"db","label":"PostgreSQL","kind":"storage","source":{"file":"src/data/db.ts","line":3}},{"id":"redis","label":"Redis","kind":"storage","source":{"file":"src/data/redis.ts","line":2}}],"edges":[{"from":"app","to":"routes_orders","label":"mount /orders"},{"from":"app","to":"routes_users","label":"mount /users"},{"from":"routes_orders","to":"order_service","label":"createOrder"},{"from":"order_service","to":"payment_service","label":"charge"},{"from":"order_service","to":"db","label":"SQL"},{"from":"routes_users","to":"user_service","label":"getUser"},{"from":"user_service","to":"db","label":"SQL"},{"from":"user_service","to":"redis","label":"cache"}]}这里最关键的是:生成的 HTML 中的每个服务节点都带有source信息,点击「Order Service」就能直接跳回src/services/orderService.ts:12,架构图和真实代码形成闭环。
9.4 运行结果说明
打开architecture.html后,你会得到:
- 一张可缩放、可拖拽的架构图,节点按类型自动配色;
- 点击「Order Service」时,会高亮它到
Payment Service、PostgreSQL的调用关系,并展示源码位置; - 产物是单文件 HTML,可以直接提交到 Git 仓库、放到静态站点或发给同事,不依赖 archify 服务端。
如果扫描结果不够准确,通常只需要调整include规则、补几张白名单表或修改少量 IR,不需要手工重画整张图。这也正是「扫描/Agent 生成 IR + 确定性编译」模式比直接生成图表源码更可靠的地方。
11. 一个更完整的示例:订单处理数据流
下面用一个「下单 → 扣库存 → 发消息」的场景,展示如何用 IR 描述数据流,并让编译器生成可交互图谱。
{"type":"dataflow","nodes":[{"id":"web","label":"Web 前端","kind":"client","source":{"file":"src/pages/order.tsx","line":12}},{"id":"order_api","label":"订单 API","kind":"service","source":{"file":"src/order/api.ts","line":30}},{"id":"inventory","label":"库存服务","kind":"service","source":{"file":"src/inventory/service.ts","line":58}},{"id":"mq","label":"消息队列","kind":"queue","source":{"file":"infra/broker.yml","line":4}}],"edges":[{"from":"web","to":"order_api","label":"提交订单"},{"from":"order_api","to":"inventory","label":"预占库存"},{"from":"inventory","to":"mq","label":"发布库存扣减事件"}]}同样的数据交给编译器,就得到一张每个节点都能点回源码的数据流图。团队在评审时,看到「库存服务」就能直接跳到对应实现,讨论会更聚焦。
12. 与 Mermaid / PlantUML / Draw.io 的对比它们不是替代关系,而是侧重点不同,这里做一个简要对照:
| 方案 | 表达方式 | 可交互 | 可溯源 | 布局可控性 |
|---|---|---|---|---|
| Mermaid | 文本 DSL | 部分(点击/链接) | 弱 | 依赖渲染器 |
| PlantUML | 文本 DSL | 弱 | 弱 | 较强 |
| Draw.io | 手工/XML | 强 | 弱 | 强但手工 |
| archify | 类型化 JSON IR | 强 | 原生支持 | 编译器确定 |
Mermaid 和 PlantUML 的优势是输入简单、生态成熟,适合快速草图;Draw.io 适合人手工精修。archify 的差异化在于「给 AI 生成」这个场景:输入是结构化 JSON IR,产物是自包含 HTML/SVG,并且把源码溯源作为一等能力。它瞄准的不是把图「画得更漂亮」,而是把「AI 理解系统 → 生成图谱」这条链路变得可靠、可验证、可追溯。
13. 总结archify 的思路值得记录:当 AI 开始参与工程可视化时,与其让模型去「猜」一套容易出错的图形语法,不如把它约束在结构化数据上,把渲染交给确定性编译器。
于是整条链路变成:
- AI 理解代码或系统描述;
- 产出类型化的 JSON IR;
- 编译器确定性地编译为自包含 HTML/SVG;
- 读者得到一个可交互、可溯源、可分享的架构/时序/数据流图谱。
对于需要频繁用 AI 生成架构图、又希望产物稳定可追溯的团队来说,这套「IR + 确定性编译」的范式,比直接生成 Mermaid 截图要实用得多,也更接近工程化的长期形态。