Francis Du.

💻Data Engineer | 🦀 Rustacean | 📷 Photographer | 🤖Vibe Coder

26 2026年8月

我为什么把 wcode 的需求写进仓库

本文目录5 个章节

做 wcode 一段时间以后,我越来越不喜欢一种状态:代码里明明有很多约束,但这些约束只活在人脑里。

比如 Workspace Root 为什么不能随便放宽,为什么 Symlink 要单独挡,为什么一个旧 SHA 不能继续写文件。代码看得出来“怎么做”,但不一定看得出来“为什么不能改成别的样子”。

人长期待在项目里还好,换一个 Agent 进来,它看到的通常只有当前源码。

于是我开始把一部分“为什么”写进仓库。

不是 README,也不是另起一个 Wiki,而是一份机器也能读的 Design State。

其实就是几份 YAML#

现在结构很普通:

.wcode/
├── project.yaml
└── design/
    ├── product.yaml
    ├── requirements.yaml
    ├── components.yaml
    ├── constraints.yaml
    ├── acceptance.yaml
    └── decisions.yaml

关键不在 YAML,而在稳定 ID。

例如 Workspace Root Isolation 可以有一个 Requirement:

- schema_version: 1
  id: REQ-SEC-001
  title: Workspace root isolation
  intent: Remote models must never escape the configured workspace root.
  priority: critical
  implemented_by:
    - component:workspace-security
  acceptance:
    - AC-SEC-001
  constraints:
    - CONSTRAINT-ROOT-ISOLATION

Component 再指到真实实现。现在 wcode 的目录已经拆过几轮,Workspace Root 相关实现就在:

- schema_version: 1
  id: component:workspace-security
  name: Workspace Security
  implementation:
    - kind: symbol
      path: src/workspace/roots.rs
      symbol: Workspace::existing_path

Acceptance 最后落到测试:

- schema_version: 1
  id: AC-SEC-001
  title: Workspace traversal is blocked
  verification:
    - kind: test
      path: src/workspace/mod.rs
      symbol: tests::blocks_path_traversal_and_stale_writes

最后能顺着一条真实链走下去:

REQ-SEC-001
component:workspace-security
src/workspace/roots.rs::Workspace::existing_path
AC-SEC-001
src/workspace/mod.rs::tests::blocks_path_traversal_and_stale_writes

这件事看起来很朴素,但它改变了我给 Agent 下任务的方式。

以前会说:

去看一下 workspace 相关代码,路径安全这里改一下。

现在可以先从 Requirement 开始:

看一下 workspace root isolation 现在的实现、约束和验证。

文件名反而是后面的事情。

我不想维护第二份源码#

Design State 最容易做过头。

如果每个函数、每个类型、每条调用关系都要手工抄进 YAML,那这东西一定会烂掉。代码一重构,Design State 马上过期,最后大家只能一起假装它还可信。

所以我现在只放那些值得稳定命名的东西:

Requirement
Component responsibility
Constraint
Acceptance Criterion
重要 Decision

至于“这个函数现在调用谁”“某个模块里有哪些 Symbol”,让代码索引和 Software Graph 自己算。

Design State 负责的是“应该是什么”,不是给源码做一份手写镜像。

Product Scope 是后来补的一层#

项目拆大以后,我还遇到过另一个问题。

Design State 能告诉我 Requirement 属于什么能力,但 Agent 做源码导航时仍然可能在整个仓库里乱跑。

所以后来 wcode 又有了 Product Scope。

它和 Design State 不是一回事。

Design State 管稳定的产品意图,Product Scope 更像源码架构上的边界。现在 scope_status 会检查源码落在哪些 Scope,还有没有没归类的文件;software_context(scopes=...) 可以真的只在选定 Scope 里找相关源码。

我自己用下来,两个东西刚好互补:

Design State:为什么有这个能力
Product Scope:这类能力大致落在哪块源码
Software Graph:代码现在实际怎么连

比起把所有东西都塞进一张图里,这样更容易维护。

Tree-sitter 解析到了,也只能说明解析到了#

Component 和 Acceptance 可以引用 Symbol,但基础解析还是 Tree-sitter。

所以结果会明确带:

provider = tree-sitter
precision = syntax

这表示“这里有这个语法定义”,不是“编译器已经证明这就是最终绑定到的实现”。

这种区别有时候很烦,尤其写展示页面时,直接写成“已解析”会好看很多。

但我宁愿页面上多一个 syntax,也不想让 Design State 借着结构化格式显得比底层事实更可靠。

我最后还是让 wcode 管自己#

Design State 真正变得有用,是我开始拿它 Dogfood wcode 自己以后。

源码一移动,Traceability 会暴露旧路径;Requirement 加了 Acceptance 但测试没接上,也会直接出现 Gap。现在 Project Observatory 里还能从 Requirement 一路看到 Component、当前实现、Verification 和这次 Git Change。

这时候 .wcode/design 才不是“又多了几份文档”。

它真的进入了开发流程。

当然它也会带来维护成本。改架构时,有时候代码改完还得回来修 Design mapping。这个成本我现在愿意付,因为不付的代价通常是几个月以后重新猜一遍为什么当初这么设计。

下一篇是 Software Graph。Design State 解决“应该是什么”,Graph 解决的是另一个更麻烦的问题:代码现在到底是什么,而且我们对这个答案有多大把握。