Community编程与开发github.com

aviralmansingka/c4

C4 架构视图——从代码库中收集的系统上下文、容器和组件层级,使用侦察子智能体。构建层级阶梯(L0 系统上下文、L1/L1b 容器、L2/L2b 组件),在可视化画布上显示为容器框,每个框外有外部系统,每个层级有一个包裹式描述。

c4 是什么?

该技能通过 Scouts(侦察子智能体)从代码库中自动收集事实,生成 C4 架构视图。它构建了一个层级阶梯:L0(系统上下文,组件作为一个节点,显示其外部交互)、L1/L1b(容器,展开组件内部部署单元)、L2/L2b(组件内部关系)。每个层级都以容器框呈现,外部系统显示在框外,并带有描述。技能严格遵循证据驱动:所有元素、关系和边界都必须有 Scouts 提供的文件行引用,否则不会绘制。它支持 visualize 引擎,在单一画布上生成五个部分(l0, l1, l1b, l2, l2b),并通过优先级规则筛选手绘关系(如跨边界路径、关键控制路径),同时明确记录省略的关系及其原因。适用于 Claude Code 等 AI 代理,能处理从未见过的代码库,因为 Scouts 是唯一的了解途径。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/aviralmansingka/dotfiles/tree/HEAD/agents/.agents/skills/c4

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

c4 是做什么的?

C4 回答“这个系统是什么,由什么组成,谁调用谁” 在三个缩放级别上。表面是 visualize 引擎:每个层级一个 rataflow 画布, 每个层级一个带描述的部分,containers 框表示所有权。阅读 agents/.agents/skills/visualize/SKILL.md 了解渲染器本身——本技能 只涵盖 C4 添加的部分。

C4 添加了三件 visualize 没有做的事情:

  1. 层级阶梯。 L0 上下文,然后 L1/L1b 容器,然后 L2/L2b 组件。每个层级的存在是为了激励下一个层级,读者当前所在的层级 可以从框上看到,而不是从图例中看到。
  2. 事实收集。 C4 必须完整地说明谁调用什么以及为什么。 Scouts 读取代码库并在任何规范存在之前生成一个有证据支持的元素和 关系清单。
  3. 作为声明的关系。 容器框只包含组件自身的部分 而不包含其他任何内容。这是一个所有权声明,因此它需要 来源,并且必须能够经受读者对照代码检查该框的考验。

不要发明元素、关系或边界。如果 Scouts 不能引用它, 它就不会被绘制。

路由

提问使用
真实代码库中某个组件的 C4 层级本技能
从已有事实出发的一张关系图visualize
聊天回复中的图表mermaid 工具
静态 ANSI / PNGmermaid 技能
架构散文页面或演示文稿artifact 技能

/c4 <组件> 或“给我一个 X 的 C4 视图”是调用方式。该技能 能在它从未见过的仓库中工作:Scouts 是其了解任何信息的唯一途径, 因此这里没有任何东西依赖于对代码的先前知识。

步骤 0——确定组件

在任何 Scout 运行之前,陈述:

component: machine manager
repo root: /Users/aviral/modal
anchor:    machine_manager/  (一个包,一个可部署单元)
  • 组件 是读者可以在代码中指向的一件事:一个服务, 一个包,一个可部署单元。如果请求命名了一个跨越多个可部署单元的 子系统,请选择一个可部署单元并说明它引入了哪些邻居作为外部系统。
  • 仓库根目录 是 Scouts 启动的地方。确认检出存在;不要 假设路径。
  • 锚点 是最强的单一证据,证明该组件 是一个整体(包清单、Helm chart、Terraform 模块、proto 服务、main.go)。如果你不能命名一个,那么该组件可能实际上是 两个组件——向人类询问而不是猜测。

最多问一个澄清性问题(组件边界是唯一值得问的问题)。 否则,做出决定并记录。

步骤 1——启动 Scouts

四个提示位于 prompts/ 中;每个都是一个完整的子智能体任务。将它们作为 subagent 调用启动,使用 agent: "scout"(只读侦察——Scouts 不得编辑仓库),所有四个在一个轮次中启动,以便它们并行运行, cwd: 是步骤 0 中解析的仓库根目录,并且组件名称 替换 <component>:

Scout提示返回
Scopeprompts/scope.md元素清单:id、一行角色、入口点、可部署单元、存储、外部服务、参与者、文件路径
Relationshipsprompts/relationships.md发起到 → 目标 : 动词 + 传输 行,每行带有 file:line
Boundaryprompts/boundary.md成员与外部系统 + 证明每个成员关系的模块/部署边界
Levelsprompts/levels.md每个元素的层级分配,以及哪些层级必须拆分为 L1/L1b(L2/L2b)

每个 Scout 返回带有 file:line 引用的证据。没有引用的行是 谣言;删除它或重新向该 Scout 请求来源。

从仓库根目录启动 Scouts——在那里运行会话(或者使 cwd: 与会话已经所在的目录相同)。cwd 与生成会话不同的 Scout 可能会在其进程启动前死亡,并且故障会显示为“其窗格中没有出现 pi 进程”。

不要轮询子智能体。结果会自动到达;当所有四个都返回时继续。 如果其中一个失败,请说明是哪个,并要么重试它,要么在生成记录中 记录这个缺口——永远不要从内存中填补空白。

步骤 2——协调成一个账本

将四个返回合并到生成记录中的三个表中。这就是 整个事实基础;规范可能不包含任何不在此处的内容。

elements:   id | label | role | class (actor|external|member|store) | evidence
relations:  id | from → to | verb + transport | level | evidence
boundary:   id | inside/outside | justifying boundary | evidence
  • 冲突 通过引用更强的来源来解决(清单或 部署图表优于注释;调用站点优于文档字符串),并且 失败的声明被记录,而不是删除。
  • 未解决的冲突 和没有幸存来源的关系被记录为 开放问题。沉默是一个缺陷;可见的疑问不是。
  • 每个元素保持一个 id 并在层级间重复使用,这样读者跟踪 q → reg 在 L1 和 L2 看到相同的两个框。

步骤 3——绘制什么(优先级)

C4 视图是关于重要性的判断,而不是调用图的转储。 账本包含 Scout 找到的每个关系;图表包含读者需要的那些, 并且会明确说明它省略了什么。

当关系至少满足以下条件之一时,它获得一席之地:

id测试示例
I1它跨越组件边界(成员 ↔ 外部系统,外部系统 ↔ 外部系统)webapi → Latitude API,machines → sshx.io
I2它是源组件调用的主要请求、控制或数据路径(配置、心跳、审计、协调、启动)control plane → private gRPC,River queue → workers
I3它携带读者需要的协议或限定符:RPC 动词、传输、端口、存储桶、间隔、超时register + heartbeat every 1 min,gcore sync every 1 min
I4它解释了读者无法从框中推断出的数据存储或参与者store → Postgres,ops + mmctl → webapi
I5它是必须保持可计数的冗余或并行路径的一条分支machines → boot 和 boot → machines;两个独立的拉取

删除类别——关系仅作为以下类别之一从图表中移除:

id类别是什么
C1内部细节完全在一个容器内部,不增加边界、协议或参与者信息(当存储的写入者已经绘制时,worker → store 每个查询的行)
C2重复调用点与已绘制关系相同的跳数,仅作业类型或参数不同,而已绘制标签已包含
C3辅助元素其唯一工作是重新进入同一容器的元素(两个已绘制框之间的客户端包装器)
C4冗余分支一个路径的进一步并行分支,读者不需要其可数性(来自同一存储桶、同一传输的第二个工件)

保持判断诚实的规则:

  • 一个箭头可以承载多个调用点,当标签携带它们的 动词时(create / terminate / repair)。这是 C2 折叠,而不是删除。
  • 边界跨越在平局时获胜(I1 优先于 I3 优先于 I2)。一个为了给内部路径腾出空间而删除外部跳数的图表 已经颠倒了自身的优先级。
  • 有疑问时,绘制它,并让层级的描述说明它为什么在那里。 疑问不是删除原因;C1–C4 是。
  • 永远不要无声删除。 账本中的每一行都以以下状态之一结束:已绘制、 折叠到 <drawn relation>(折叠理由为 C2),或 已省略 — C1|C2|C3|C4 带一行原因。覆盖图(步骤 7) 说明四个计数:已绘制、已折叠、按类别省略、未说明。
  • 范围缩减是不同的东西,需要调用者的话。 “仅控制平面视图”或“最多 20 个关系”是一个范围请求:将其记录为 基础(basis: caller scope, N relations)以及计数的账本。优先级化是默认行为;子集 是调用者做出的决定, 而不是生成器推断的。

步骤 4——层级阶梯

目标形状是五个部分,从上到下:

L0 · 系统上下文      一个节点表示组件及其邻居
L1 · 组件            组件打开一次,容器包围其部件
L1b · <第二个侧面>    相同深度,系统的另一侧
L2 · 内部—<切片 A>   成员之间的关系,容器包围它们
L2b · 内部—<切片 B>  相同深度,另一个切片

这就是阶梯:一个五个部分的画布是可交付成果。更多部分 是一个缺陷,除非下面的拆分规范得到满足并有数字。

只在上述层级存在后才绘制每个层级,并在进入下一个层级前给出每个层级的描述。

层级绘制内容框行为
L0 系统上下文组件作为一个普通节点,加上周围的每个参与者和外部系统,以及它们之间的粗箭头没有容器,没有 focus,没有双边框—重点是周围的世界,所以组件看起来像任何其他框
L1 容器组件打开一层:其自身的可部署/运行时单元在一个容器内部;L0 的外部系统保留在外部容器框包围成员
L1b 侧面视图可选第二个切片,当一个 L1 确实无法容纳流程时(舰队端、数据平面、批处理路径)—有测量依据,绝不是新主题容器,相同规则
L2 组件一个容器内部的关系—L1 的成员,现在相互调用容器框包围它们
L2b相同深度的可选第二个切片,当一个 L2 无法路由其关系时,或者两个故事需要分别排列时容器,相同规则

不可协商的层级规则:

  • L0 是组件作为一个普通节点。 整个组件一个框, 正常边框,加上周围的参与者和外部系统。 不是它的部件,不是它的监听器,不是它的表—那些是下一个层级。 如果 L0 有多个属于组件内部的节点,则它不是上下文视图。 L0 上的包含关系会声明读者尚未看到的内部内容,并且 focus 会指向一个还不存在的层级。
  • L1/L1b 打开 L0 的框。 每个成员位于容器内部; 每个在 L0 是外部的元素都位于容器外部。一个无法绘制在框的边界框外部的成员需要自己的容器条目 (参见连续性规则)—绝不是一个模糊的条目。
  • L2/L2b 保持在一个框内部。 L2 关系的两个端点都是 容器成员。如果关系离开框,它属于 L1/L1b。
  • 层级描述自身。 标题简短(L0 · 系统上下文); 描述说明框内部是什么,框外部是什么,以及它如何与其相邻层级 关联。目标是 2-3 个包装行。
  • 动效不是 C4 的属性。 层级 0-1b 是静态的(dots: 0)。一个内部层级,其关系是实时流程,可以每条路线恰好携带一个点 (flow: true, dots: 1);记录这个选择。永远不要动画化外部箭头来装饰阶梯。

拆分规范——层级是深度,不是主题

一个 C4 层级在一个缩放级别回答一个问题。相同深度的两个层级 存在只是因为一个部分无法容纳内容;一个新的主题不是 添加部分的原因。

  • 永远不要添加 L1c、L1d、L1e 或第五个侧面。 阶梯有三个 深度(上下文、容器、组件),并且在第一个深度以下最多在每个深度有两个 切片。一个随着每个子系统或每个提及而增加切片的画布是一个 戴着层级标题的主题列表。
  • 第二个切片必须命名其度量标准: 路由 --check 拒绝的路由,它报告的共享走廊,或冲突的排列(两个外部端没有共同的外部系统)。“感觉拥挤”不是一个度量标准。
  • 相同深度的切片共享框、标签和 id。 L1 和 L1b 绘制相同的组件,使用相同的成员 id;L2 和 L2b 绘制相同的 成员。只有边集和排列不同,并且每个描述说明它属于哪个切片。
  • 切片命名为 b(l1b,l2b);永远不要发明深度。
  • 不绘制 L2 之外的深度。 如果组件内部仍然可分解, 在记录中说明并停止:代码类和函数不是读者要求的 C4 层级。 阶梯在请求结束的地方结束。
  • 账本包含而阶梯未绘制的所有内容都是一个范围 决定,要写下来(步骤 6),而不是无声删除,也不是新部分。

步骤 5——规范机制

一个画布,一个阅读方向,每个层级一个部分:

{
  "direction": "lr",
  "theme": "gruvbox",
  "diagrams": [
    {
      "id": "l0",
      "title": "L0 · 系统上下文",
      "description": "系统上下文:管理器是一个框。...",
      "nodes": [ ... ],
      "edges": [ ... ]
    },
    {
      "id": "l1",
      "title": "L1 · 管理器",
      "description": "L0 的框打开一层:...",
      "containers": [ { "label": "Machine manager", "members": ["api", "q", "db"] } ],
      "layers": [ ["actor"], ["api", "q"], ["db"], ["prov"] ],
      "nodes": [ ... ],
      "edges": [ ... ]
    }
  ]
}
  • direction: "lr" 一次,在画布上。永远不要在单个图表中覆盖: 在每个部分都重新定向的读者会丢失阶梯。
  • 五个部分。 l0,l1,l1b,l2,l2b,按此顺序。第六个 部分需要记录步骤 3 的度量标准;十个或二十个部分的画布是 主题列表,而不是阶梯——将其折叠回三个深度,每个深度最多两个切片。
  • containers 是按图表列出的列表,包含 {label, members}。框是 围绕成员生长的真正双边框矩形,标签 嵌入其顶部边缘。每个图表允许多个框。
  • 成员 id 必须存在于该图表的 nodes 中。 命名了不存在的节点的成员 id 会被解析器静默删除—然后框将围绕比声明的更少的节点绘制,并且 --check 不会警告。根据你声明的成员计数实际绘制的框。
  • 连续性规则。 容器是一个矩形。

…

[因翻译需要已截断]

相关技能