AI 写代码越来越快,为什么项目却越来越难维护?

如何让 AI Agent 按需读取项目知识:Flow2Spec 的路由设计实践

AI 编程工具进入真实项目后,经常出现一种看似矛盾的情况:模型能完成复杂编码,却会反复询问一些项目里的基础事实。

例如,某个接口是否允许重试、批处理任务用什么幂等键、一个状态字段由哪个模块维护。这些问题通常已经在代码或文档里出现过,但新的会话并不知道上一次为什么这样实现,只能重新搜索仓库。

最直接的解决办法,是把规则都写进 AGENTS.mdCLAUDE.md。项目较小时,这种方式简单有效;项目持续演进后,单文件会越来越长。Agent 每次加载大量无关信息,不但消耗上下文,还可能忽略真正重要的约束。

我们在设计 Flow2Spec 时,把问题重新定义为:

项目知识不应该只是被保存,还应该能够按任务路由、展开依赖、检查缺口,并在代码变化后被重新验证。

本文不展开介绍所有功能,只讨论这套知识路由为什么这样设计,以及多人修改知识时如何处理语义冲突。

先明确三个设计目标

在实现之前,我们给知识层设定了三个目标。

第一,Agent 不应该为了一个支付需求读取整个项目文档。它需要一个低成本入口,先把候选范围缩小。

第二,命中一条业务规则不代表上下文已经完整。支付主题可能依赖账户风控、订单边界和统一错误码,依赖关系需要被显式表达。

第三,知识会随着代码变化。知识层必须能够进入 Git diff 和 Code Review,而不是成为一个无法追踪来源的外部黑盒。

基于这些约束,我们最终采用了仓库内的分层结构:

text 复制代码
.Knowledge/
├── manifest-routing.json   # L0:路由索引
├── matchers/               # L1:关键词分片
├── topics/                 # L2:主题摘要与硬约束
├── stock-docs/             # L3:稳定的架构和能力文档
└── req-docs/               # L3:具体需求与技术方案

这里的重点不是目录名称,而是每一层承担的职责不同。

层级 保存内容 Agent 的读取方式
L0 路由索引 task、topic 路径、依赖和元数据 会话先读取机读入口
L1 匹配分片 一组与任务相关的触发词 只打开可能命中的分片
L2 主题摘要 边界、硬约束和下钻入口 命中后读取并展开依赖
L3 长文档 架构终稿、需求方案和完整背景 信息不足时按需读取

这样做的目的,是让常见问题尽量在 L0 到 L2 解决,只有遇到信息缺口时才继续读取长文档或源码。

路由入口为什么要做成 manifest

manifest-routing.json 是知识层的机读入口。下面是一段经过简化的结构:

json 复制代码
{
  "topicPaths": {
    "flow2spec-collaboration": ".Knowledge/topics/flow2spec-collaboration.md",
    "f2s-task": ".Knowledge/topics/f2s-task.md"
  },
  "topicDependencies": {
    "flow2spec-collaboration": ["f2s-task"]
  },
  "taskToTopicRules": [
    {
      "task": "flow2spec-collaboration",
      "matcherId": "m-flow2spec-collaboration",
      "matcherPath": ".Knowledge/matchers/m-flow2spec-collaboration.json",
      "topics": ["flow2spec-collaboration"]
    }
  ]
}

这里包含三种关系:

  • taskToTopicRules 负责从任务找到 matcher 和候选 topic;
  • topicPaths 负责从 topic id 找到实际文件;
  • topicDependencies 负责声明命中主题前需要补齐哪些前置主题。

路由索引只保存关系,不把所有关键词直接塞进一个大 JSON。关键词被拆到独立 matcher 中:

json 复制代码
{
  "id": "m-flow2spec-collaboration",
  "schema": "flow2spec.matcher.v1",
  "includeAny": [
    "团队协作",
    "多人共用知识库",
    "developerId 隔离",
    "kb-delta",
    "topic revision",
    "revision 冲突",
    "optimistic lock"
  ]
}

分片带来的好处是修改局部规则时,其他路由文件不会产生无意义 diff。Agent 也不需要一次读取所有关键词,只需通过 matcherPath 打开相关分片。

这不是语义检索的替代品,而是一种偏确定性的仓内协议。它牺牲了一部分模糊召回能力,换来可读、可审查和可预测的路由结果。对于权限、幂等、数据边界这类不能靠"相似度大概命中"的约束,这个取舍更合适。

Match 之后不能直接 Act

仅仅命中一个 topic,很容易制造虚假的确定性。

假设用户说:"两个人同时更新同一个业务主题时,知识库会不会把内容覆盖掉?"matcher 可以命中协作主题,但真正回答之前还需要确认:

  • 当前 topic 是否覆盖并发写入;
  • 是否需要先读取任务状态的所有权规则;
  • 用户问的是 Git 文本冲突,还是业务语义冲突;
  • topic 中的描述是否仍与当前实现一致。

因此完整流水线被拆成四步:

text 复制代码
match → expand → verify → act

Match:缩小候选范围

根据用户任务读取对应 matcher,得到主候选 topic,而不是遍历全部知识文件。

Expand:展开依赖

读取主 topic 声明的依赖。例如协作主题依赖任务主题,因为知识合并前必须先知道当前开发者的 TASK_ROOT 在哪里。

Verify:检查缺口

判断现有知识是否真的覆盖问题。覆盖不足时继续读取 stock-docs/req-docs/ 或源码;需求本身不清楚时先向用户确认。

Act:执行任务

只有依赖完整、关键事实得到确认后,才进入回答、修改代码或提交变更。

verify 是这条链路里最容易被省略、也最重要的一步。检索解决的是"可能相关",缺口检查解决的才是"是否足以行动"。

Topic 为什么只保存精简事实

topic 的目标不是复制完整文档,而是提供 Agent 最常用的硬约束和下钻入口。一个真实 topic 的 frontmatter 类似这样:

yaml 复制代码
---
id: flow2spec-collaboration
revision: 0
summary: 任务状态本地隔离、共享知识 delta 合入与 revision 冲突处理
dependsOn: [f2s-task]
primary: feature
confidence: manual
tags: [policy]
---

正文只保留协作边界、知识合入规则、团队观察面和长文档路径。这使 topic 足够短,可以在一次任务中组合多个主题;完整解释仍然放在长文档里。

这里还有一个容易踩坑的地方:摘要不能写成宣传文案。诸如"强大、智能、高效"对路由没有帮助。更有效的摘要应该直接描述事实、适用范围和限制。

开发完成后,知识怎样写回来

只读知识还不够。Agent 在实现过程中经常会从源码确认新的限制,例如退款只能原路返回,或者某个锁的 TTL 是 10 分钟。如果这些事实只留在当前会话里,下次仍然要重新搜索。

直接让 Agent 修改 topic 虽然简单,但多人协作时会出现两个问题:

  1. Git 只能看到文本变化,不知道这次修改的意图;
  2. 两段文本可以自动合并,不代表两条业务规则在语义上兼容。

因此知识变更先写成结构化 kb-delta.json

json 复制代码
{
  "taskId": "add-payment-rule",
  "developerId": "alice",
  "baseRevisions": {
    "payment-rules": 3
  },
  "changes": [
    {
      "type": "appendBody",
      "targetTopic": "payment-rules",
      "summary": "补充退款时限",
      "content": "## 退款时限\n\n审核通过后 3 个工作日内原路退回。"
    }
  ]
}

delta 目前只允许四种动作:

类型 含义
appendBody 在已有 topic 末尾追加内容
replaceBody 替换 topic 正文
updateFrontmatter 更新 topic 元数据
createTopic 创建 topic,并按需建立 matcher 和路由

动作白名单让 CLI 可以在落盘前校验目标、字段和版本,也让 Code Review 更容易理解这次知识变更的意图。

用 topic revision 阻止过期写入

baseRevisions 记录生成 delta 时看到的 topic 版本。执行 plan 时,CLI 会把它与磁盘上的 revision 比较:

text 复制代码
baseRevision == diskRevision  → 可以 apply,写入后 revision +1
baseRevision != diskRevision  → 停止,要求重新读取最新内容

例如 Alice 和 Bob 都基于 payment-rules revision: 3 开始修改。Alice 先合入后,磁盘版本变为 4。Bob 的 delta 再执行 plan 时会得到 revision mismatch,而不是继续写入。

Bob 此时必须阅读 revision 4 的正文,再判断两份规则应该并列、改写,还是放弃其中一份。这里故意没有做自动文本拼接,因为"两个段落都能插进去"和"两个业务结论不矛盾"是两回事。

revision 是磁盘乐观锁,不是远程分布式锁。它有明确边界:

  • 它只能保护通过 delta 通道提交的知识变更;
  • 它不知道队友尚未拉取的远端提交;
  • 直接手改 topic 会绕过 revision 预检;
  • 正常的 Git pull、分支同步和 Code Review 仍然不可省略。

这个机制没有消灭冲突,而是尽量把冲突提前到 plan 阶段,并把语义裁决留给掌握业务上下文的人。

任务状态和项目知识为什么要分开

多人使用 Agent 时,仓库里会同时出现两类状态:

  • "这轮会话做到哪一步"属于个人过程;
  • "系统当前有哪些约束"属于团队事实。

如果把两者都提交到 Git,个人 checklist、临时判断和待办项会频繁冲突。反过来,如果两者都放在本地,已经验证的业务知识又无法共享。

我们采用的边界是:

text 复制代码
.task/<developerId>/   本地任务现场,默认不进 Git
.Knowledge/            团队项目事实,随代码进入 Git

.task/ 保存 checklist、会话上下文和本轮 delta,用于跨会话续作;.Knowledge/ 只接收已经确认的事实。团队进度仍然通过 PR、commit、issue 和里程碑观察,不把个人 Agent 会话同步成另一套项目管理系统。

一次最小初始化实测

为了确认这套结构不是只存在于文档里,我在一个空 Git 仓库中执行了 Codex 初始化:

bash 复制代码
npx @double-coding/flow2spec@latest init codex --locale zh-CN --yes
npx @double-coding/flow2spec@latest doctor

初始化生成了 .Knowledge/.codex/、根 AGENTS.mdflow2spec.config.json,同时把 .task/ 加入 .gitignore

doctor 的检查结果为 8 项通过、0 个警告、0 个错误,覆盖:

  • Node.js 版本;
  • 项目配置;
  • Agent 入口;
  • 知识库入口;
  • Codex 配置完整性;
  • developerId 和 TASK_ROOT
  • .task/ 忽略规则;
  • topic 校验与 routing 漂移。

这只能说明初始化链路和基础结构工作正常,不能证明路由质量。路由是否真正有效,仍然取决于团队有没有把 topic 写成明确事实、matcher 是否覆盖真实表达,以及代码变化后是否及时同步知识。

实际使用中的几个限制

这套方案并不是零成本的。

首先,matcher 使用显式触发词,结果容易解释,但对团队从未预料过的表达召回能力有限。必要时仍然要靠普通代码搜索或其他检索方式兜底。

其次,topic 越大,并发修改时 revision 冲突面越大;topic 拆得太细,又会导致依赖关系复杂。比较可行的标准是让一个 topic 围绕一组稳定、能独立判断的业务约束,而不是按文件数量机械拆分。

再次,知识正确性最终仍然来自代码、测试和人工确认。confidence、revision 和校验命令只能帮助管理知识,不能把未经验证的推断变成事实。

最后,小项目未必需要这套结构。一次性脚本或只有少量文件的个人项目,用一份简洁的规则文件通常更直接。只有当重复搜索、上下文漂移和多人知识冲突开始产生明显成本时,分层路由才值得维护。

总结

让 AI 了解项目,不等于把更多文本塞进上下文。更关键的问题是:怎样找到相关事实、怎样补齐依赖、怎样判断信息足够,以及事实变化后怎样安全写回。

Flow2Spec 当前给出的答案可以概括为三点:

  1. 用 manifest、matcher、topic 和长文档组成渐进式知识层;
  2. match → expand → verify → act 把缺口检查放在执行之前;
  3. 用结构化 delta 和 topic revision 管理多人知识变更。

这套设计仍然需要在更多真实项目中验证,尤其是 matcher 的长期维护成本、topic 拆分粒度和跨分支语义冲突。但至少有一点已经比较明确:项目上下文不能只被当成提示词,它应该成为可以被版本控制和持续维护的工程资产。

文中的完整实现和 schema 可以在 Flow2Spec 仓库 中查看。

相关推荐
冬奇Lab1 小时前
代码库知识库系列(12):Git 历史是第四条检索路径
人工智能
空堂与归1 小时前
复杂推理总翻车、Prompt 被注入怎么办?六种进阶技术从 CoT 到 ReAct 实战全解析
人工智能
lucas_AI1 小时前
Q-CueGraph:你的多模态大模型会 zoom,但真的知道该看哪儿吗?
人工智能·算法
OpenMiniServer1 小时前
时空电磁场分量理论 ——从光子传播态到粒子结构态的形成模型
人工智能
冬奇Lab2 小时前
开源项目第182期:Graphify — 把整个代码库变成可查询知识图谱,让 AI 编程助手真正「懂」你的项目
人工智能·开源·资讯
liulilittle2 小时前
MOE路由:路由(logits: top-k/8)
c++·人工智能·算法·机器学习·llm
振浩微433射频芯片2 小时前
用TU2303B双向无线模块打通标准化智能家居接入:433MHz方案的落地优势指南
服务器·网络·人工智能
m4Rk_2 小时前
【论文阅读】Agent 记忆机制(34):MemoryBank——用遗忘曲线管理可强化的长期对话记忆
论文阅读·人工智能·学习·开源·github
初禾w-w2 小时前
阿里云开源 UModel 并发起 USS 倡议:构建企业级通用语义标准,重塑 AI 交互底座
人工智能·阿里云·开源·企业ai·对象图语义·语义割裂