04 · ARCHITECTURE/三个新组件

三个新组件

整套改造的核心只有三个新对象:LoopController、DocStore、UnitRegistry。它们是新增 1500 行代码的主要载体。

① LoopController

路径:server/src/core/loop-controller.js · ~180 行

编排 L1 / L2 / L3 三层状态机。所有 Agent 调用统一从这里出。它本身无状态,所有状态从 UnitRegistry / DocStore 读。

关键方法

方法调用时机返回
runProject(pid)启动 / 续跑项目'shipped' | 'blocked' | 'l2_exhausted'
runUnit(unit)L1 内部UnitResult
onHumanFeedback(pid, req)L3 入口(HTTP)void · 异步触发 runProject
onL2Failure(pid, runId)Integrator 返回 fail 后void
getState(pid)Dashboard 轮询ProjectState 快照

主循环骨架

async function runProject(pid) {
  while (true) {
    // L1: pending → coding → testing → passed
    const waves = scheduler.getExecutionGroups(unitRegistry.pending(pid));
    for (const w of waves) await Promise.all(w.map(runUnit));

    if (unitRegistry.anyBlocked(pid)) return 'blocked';

    // L2 gate
    if (!l2GateOpen(pid)) continue;
    const r = await agentManager.callAgent('integrator', { pid });
    if (r.ok) return 'shipped';

    // L2 failed → triage → patch SDD → reset units
    const tri = await agentManager.callAgent('bug-triager', r.failure);
    if (tri.confidence < 0.5) {
      sse.emit(pid, 'human_intervention_needed', tri); return 'l2_blocked';
    }
    await agentManager.callAgent('architect', { mode: 'patch', patch: tri.proposed_sdd_patch });
    unitRegistry.reset(pid, tri.affected_units);

    if (project.l2Rounds++ >= 3) return 'l2_exhausted';
  }
}

② DocStore

路径:server/services/doc-store.js · ~140 行

负责 prd.html / sdd.html / units.json / integration/spec.json 的读写、归档、校验。所有写操作走它,没有 Agent 能直接 Write 这些文件。

API 面

module.exports = {
  // 读
  readPRD(pid), readSDD(pid), readUnits(pid), readIntegrationSpec(pid),

  // 写(自动归档旧版)
  writePRD(pid, html),
  writeSDD(pid, html),
  writeUnits(pid, json),
  writeIntegrationSpec(pid, json),

  // 校验
  validateHTML(html),               // 用 cheerio + 必含 section
  validateUnitsSchema(json),         // 用 typebox

  // diff / 归档
  diffPRD(pid, fromRev, toRev),
  archive(pid, kind, oldHtml),       // → history/{kind}-rev-N.html
};

HTML 校验规则

文档必含元素校验工具
prd.html顶级 <article data-doc="prd" data-rev>,至少 1 <section>cheerio
sdd.html每 <section data-unit-id> 含 <h3> + <pre data-contract>cheerio
units.jsonschema 校验 + DAG 无环typebox + 拓扑排序
integration/spec.json每 case 的 involves 都在 units.json 中存在自写

③ UnitRegistry

路径:server/services/unit-registry.js · ~120 行

Unit 状态机的 source of truth。状态转换都走它,不允许 Agent 自己改 units.json 的 status 字段。

API 面

module.exports = {
  list(pid),                                  // 全部 unit + 状态快照
  pending(pid), passed(pid), blocked(pid),
  l2GateOpen(pid),                           // 全 passed 且 hash 一致

  // 状态转换
  markCoding(pid, unitId),
  markTesting(pid, unitId),
  pass(pid, unitId, contractHash),
  fail(pid, unitId, reason),
  block(pid, unitId, reason),
  reset(pid, unitIds),                         // L2 修复后

  // diff (用于 L3 增量)
  diffWithSDD(pid),                          // → { added, changed, removed }
};

状态转换合法性

from \ topendingcodingtestingfailedpassedblocked
pending—✓
coding—✓
testing—✓✓
failed✓—✓
passed✓ (L2 reset)—
blocked✓ (人类干预)—

非法转换 → 抛异常 + SSE 错误事件,便于排查。

三者协作

LoopController 编排所有 Loop DocStore PRD · SDD · units.json IO UnitRegistry Unit 状态机 read/write docs update unit state UnitRegistry reads units.json via DocStore

外部依赖

包用途是否新增
cheerio解析 / 校验 HTML新增 (~120KB)
@sinclair/typeboxJSON schema 校验已存在
better-sqlite3—已存在(不需要新表)

不引入新数据库;项目状态全部落文件。Dashboard 用 SSE 推送,不需要轮询 DB。