Commit 905894be5ee95557a82cb389f8ed58d1b9fe595b
1 parent
2860f3c8
docs(prd): add 3 pre-implementation essentials — quick-start, P0.0 services, mock Claude
Before P0 coding begins, the doc set was missing three things that would
have stalled implementation on day 3:
1. Day-1 Quick-Start (plan-migration top section)
- 8-step "what command do I type first" walkthrough
- pnpm install → cp .env.example → extract-manifest verification → P0.0
services → MOCK_CLAUDE=1 phase0-verify → real phase0-verify → P1
- Anyone cloning can reach step 4 (doc verification) immediately
2. P0.0 服务层 7 件套 (plan-migration P0 子任务表)
- Previously P0.A "工具实现" depended on docStore/unitRegistry/sseBroker/
bugTracker/timelineLogger/pathResolver/GitVault but none were listed
as a task — engineer would hit "docStore.writePRD doesn't exist"
- Now P0.0 lists them as a sequenced 4.5-day task: pathResolver first
(everyone depends on it), then 5 other services, finally GitVault
- GitVault tasks consolidated into P0.0 (removed duplicate P0.I)
- Total P0: 13.5 → 17 days (3 → 3.5 weeks)
3. Mock Claude CLI Mode (arch-acp-bridge §⑦, NEW)
- MOCK_CLAUDE=1 env toggles ClaudeCliProtocol to read stubs from
system/mock-responses/{agent}/{mode}.json instead of real Claude
- Mock JSON: array of steps (text emit / tool_use), $pid placeholder
- When mock vs real: mock for dev + CI, real for prompt quality + P1+
- Enables zero-token tool dev + phase0-verify
- Sections renumbered (⑧ → ⑨ etc) to accommodate insertion
Total project: 10 → 10.5 weeks (still honest, still includes everything).
No new MVCP defs (54). Search index 195.7KB. _spec/ regenerated by hook.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Showing
4 changed files
with
214 additions
and
19 deletions
build-manifest.json
| @@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
| 2 | "meta": { | 2 | "meta": { |
| 3 | "source": "docs/prd/newfunction", | 3 | "source": "docs/prd/newfunction", |
| 4 | "prdRev": "v0.2", | 4 | "prdRev": "v0.2", |
| 5 | - "extractedAt": "2026-05-13T05:26:32.145Z" | 5 | + "extractedAt": "2026-05-13T08:13:35.698Z" |
| 6 | }, | 6 | }, |
| 7 | "model": [ | 7 | "model": [ |
| 8 | { | 8 | { |
docs/prd/newfunction/arch-acp-bridge.html
| @@ -240,7 +240,145 @@ LoopController 决定 retry / block / 升 L3</pre> | @@ -240,7 +240,145 @@ LoopController 决定 retry / block / 升 L3</pre> | ||
| 240 | </section> | 240 | </section> |
| 241 | 241 | ||
| 242 | <section> | 242 | <section> |
| 243 | - <h2>⑦ 性能考量</h2> | 243 | + <h2>⑦ Mock Claude CLI 模式(开发期省 token)</h2> |
| 244 | + <p>P0 阶段开发 79 工具时,不可能每次都真调 Claude(烧 token + 等响应慢)。需要一个 <code>MOCK_CLAUDE=1</code> 环境变量让 ClaudeCliProtocol 返回 stub 回复,工具流照常跑通。</p> | ||
| 245 | + | ||
| 246 | + <h3>设计</h3> | ||
| 247 | +<pre class="code shape"><span class="c">// packages/acp-core/protocol/ClaudeCliProtocol.js · 增量改动 ~40 行</span> | ||
| 248 | +<span class="k">const</span> MOCK = process.<span class="n">env</span>.<span class="n">MOCK_CLAUDE</span> === <span class="s">'1'</span>; | ||
| 249 | +<span class="k">const</span> mockResponsesDir = path.<span class="n">resolve</span>(__dirname, <span class="s">'../../../system/mock-responses'</span>); | ||
| 250 | + | ||
| 251 | +<span class="k">class</span> <span class="n">ClaudeCliProtocol</span> <span class="p">{</span> | ||
| 252 | + <span class="k">async</span> <span class="n">sendPrompt</span>(input) <span class="p">{</span> | ||
| 253 | + <span class="k">if</span> (MOCK) <span class="k">return</span> <span class="k">this</span>.<span class="n">_sendPromptMock</span>(input); | ||
| 254 | + <span class="k">return</span> <span class="k">this</span>.<span class="n">_sendPromptReal</span>(input); | ||
| 255 | + <span class="p">}</span> | ||
| 256 | + | ||
| 257 | + <span class="k">async</span> <span class="n">_sendPromptMock</span>(input) <span class="p">{</span> | ||
| 258 | + <span class="c">// 1. 解析 agent name 从 system prompt 或 input.agent</span> | ||
| 259 | + <span class="k">const</span> agent = <span class="k">this</span>.<span class="n">agentName</span>; <span class="c">// 启动时存的</span> | ||
| 260 | + <span class="k">const</span> mode = input.mode || <span class="s">'default'</span>; | ||
| 261 | + | ||
| 262 | + <span class="c">// 2. 读 mock 响应文件</span> | ||
| 263 | + <span class="k">const</span> p = path.<span class="n">join</span>(mockResponsesDir, agent, <span class="s">`</span><span class="v">${mode}</span><span class="s">.json`</span>); | ||
| 264 | + <span class="k">if</span> (!fs.<span class="n">existsSync</span>(p)) <span class="p">{</span> | ||
| 265 | + <span class="k">return</span> <span class="k">this</span>.<span class="n">_fallbackMock</span>(agent, input); | ||
| 266 | + <span class="p">}</span> | ||
| 267 | + | ||
| 268 | + <span class="k">const</span> mock = <span class="n">JSON.parse</span>(fs.<span class="n">readFileSync</span>(p, <span class="s">'utf8'</span>)); | ||
| 269 | + | ||
| 270 | + <span class="c">// 3. 按 mock 描述的 tool_use 序列,逐个回调工具</span> | ||
| 271 | + <span class="c">// 模拟 Claude 自己调工具的行为</span> | ||
| 272 | + <span class="k">for</span> (<span class="k">const</span> step <span class="k">of</span> mock.steps) <span class="p">{</span> | ||
| 273 | + <span class="k">if</span> (step.tool_use) <span class="p">{</span> | ||
| 274 | + <span class="k">const</span> result = <span class="k">await</span> <span class="k">this</span>.<span class="n">_dispatchTool</span>(step.tool_use.name, step.tool_use.input); | ||
| 275 | + <span class="c">// 工具回执理论上影响下一步,简化版直接忽略 result</span> | ||
| 276 | + <span class="c">// (高级版可让 mock 描述含 tool_result 处理逻辑)</span> | ||
| 277 | + <span class="p">}</span> | ||
| 278 | + <span class="k">if</span> (step.delay_ms) <span class="k">await</span> <span class="k">new</span> <span class="n">Promise</span>(r => <span class="n">setTimeout</span>(r, step.delay_ms)); | ||
| 279 | + <span class="k">if</span> (step.text) <span class="k">this</span>.<span class="n">_emitChunk</span>(step.text); | ||
| 280 | + <span class="p">}</span> | ||
| 281 | + | ||
| 282 | + <span class="k">return</span> mock.final_result; | ||
| 283 | + <span class="p">}</span> | ||
| 284 | + | ||
| 285 | + <span class="n">_fallbackMock</span>(agent, input) <span class="p">{</span> | ||
| 286 | + <span class="c">// 没有专门 mock 文件时,返回最小合规结果</span> | ||
| 287 | + <span class="k">return</span> <span class="p">{</span> | ||
| 288 | + ok: <span class="v">true</span>, | ||
| 289 | + duration_ms: <span class="v">100</span>, | ||
| 290 | + tokens_used: <span class="v">0</span>, | ||
| 291 | + content: <span class="s">'[mock fallback response]'</span>, | ||
| 292 | + <span class="p">}</span>; | ||
| 293 | + <span class="p">}</span> | ||
| 294 | +<span class="p">}</span></pre> | ||
| 295 | + | ||
| 296 | + <h3>Mock 响应文件结构</h3> | ||
| 297 | +<pre class="tree"> | ||
| 298 | +system/mock-responses/ <span class="com"># 由 P0.A2 创建</span> | ||
| 299 | +├── pm/ | ||
| 300 | +│ ├── init.json <span class="com"># PM init mode 的 stub</span> | ||
| 301 | +│ ├── incremental.json | ||
| 302 | +│ └── resume.json | ||
| 303 | +├── architect/ | ||
| 304 | +│ ├── generate.json | ||
| 305 | +│ ├── patch.json | ||
| 306 | +│ └── resume.json | ||
| 307 | +├── coder/ | ||
| 308 | +│ ├── implement.json | ||
| 309 | +│ └── retry.json | ||
| 310 | +├── tester/ | ||
| 311 | +│ ├── test.json | ||
| 312 | +│ └── retry.json | ||
| 313 | +├── integrator/ | ||
| 314 | +│ ├── run.json | ||
| 315 | +│ └── retry.json | ||
| 316 | +└── bug-triager/ | ||
| 317 | + └── triage.json | ||
| 318 | +</pre> | ||
| 319 | + | ||
| 320 | + <h3>Mock 响应文件示例 · pm/init.json</h3> | ||
| 321 | +<pre class="code"><span class="p">{</span> | ||
| 322 | + <span class="n">"description"</span>: <span class="s">"PM init mock: 假装解析需求并写出 prd/ 11 章节"</span>, | ||
| 323 | + <span class="n">"steps"</span>: <span class="p">[</span> | ||
| 324 | + <span class="p">{</span> <span class="n">"text"</span>: <span class="s">"读取上下文...\n"</span> <span class="p">}</span>, | ||
| 325 | + <span class="p">{</span> <span class="n">"tool_use"</span>: <span class="p">{</span> | ||
| 326 | + <span class="n">"name"</span>: <span class="s">"write_todolist"</span>, | ||
| 327 | + <span class="n">"input"</span>: <span class="p">{</span> <span class="n">"items"</span>: <span class="p">[</span> | ||
| 328 | + <span class="p">{</span> <span class="n">"id"</span>: <span class="s">"step-001"</span>, <span class="n">"description"</span>: <span class="s">"解析需求"</span> <span class="p">}</span>, | ||
| 329 | + <span class="p">{</span> <span class="n">"id"</span>: <span class="s">"step-002"</span>, <span class="n">"description"</span>: <span class="s">"bootstrap_prd_site"</span> <span class="p">}</span>, | ||
| 330 | + <span class="p">{</span> <span class="n">"id"</span>: <span class="s">"step-003"</span>, <span class="n">"description"</span>: <span class="s">"写 index.html"</span> <span class="p">}</span> | ||
| 331 | + <span class="p">]</span> <span class="p">}</span> | ||
| 332 | + <span class="p">}</span> <span class="p">}</span>, | ||
| 333 | + <span class="p">{</span> <span class="n">"tool_use"</span>: <span class="p">{</span> <span class="n">"name"</span>: <span class="s">"bootstrap_prd_site"</span>, <span class="n">"input"</span>: <span class="p">{</span> <span class="n">"pid"</span>: <span class="s">"$pid"</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>, | ||
| 334 | + <span class="p">{</span> <span class="n">"tool_use"</span>: <span class="p">{</span> <span class="n">"name"</span>: <span class="s">"write_chapter"</span>, | ||
| 335 | + <span class="n">"input"</span>: <span class="p">{</span> <span class="n">"pid"</span>: <span class="s">"$pid"</span>, <span class="n">"section"</span>: <span class="s">"index"</span>, <span class="n">"page"</span>: <span class="s">"index"</span>, | ||
| 336 | + <span class="n">"html"</span>: <span class="s">"<!doctype html>...mock content...</html>"</span> <span class="p">}</span> | ||
| 337 | + <span class="p">}</span> <span class="p">}</span>, | ||
| 338 | + <span class="p">{</span> <span class="n">"tool_use"</span>: <span class="p">{</span> <span class="n">"name"</span>: <span class="s">"echo_diff"</span>, | ||
| 339 | + <span class="n">"input"</span>: <span class="p">{</span> <span class="n">"summary"</span>: <span class="s">"创建 mock PRD 站点"</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> | ||
| 340 | + <span class="p">]</span>, | ||
| 341 | + <span class="n">"final_result"</span>: <span class="p">{</span> | ||
| 342 | + <span class="n">"ok"</span>: <span class="v">true</span>, | ||
| 343 | + <span class="n">"mode"</span>: <span class="s">"init"</span>, | ||
| 344 | + <span class="n">"prd_rev"</span>: <span class="v">1</span>, | ||
| 345 | + <span class="n">"files_written"</span>: <span class="p">[</span><span class="s">"prd/index.html"</span><span class="p">]</span>, | ||
| 346 | + <span class="n">"change_summary"</span>: <span class="s">"[mock] PM init"</span>, | ||
| 347 | + <span class="n">"duration_ms"</span>: <span class="v">100</span>, | ||
| 348 | + <span class="n">"tokens_used"</span>: <span class="v">0</span> | ||
| 349 | + <span class="p">}</span> | ||
| 350 | +<span class="p">}</span></pre> | ||
| 351 | + | ||
| 352 | + <h3>什么时候用 mock vs 真</h3> | ||
| 353 | + <table class="dense"> | ||
| 354 | + <thead><tr><th>场景</th><th>用什么</th><th>原因</th></tr></thead> | ||
| 355 | + <tbody> | ||
| 356 | + <tr><td>开发新工具,验证落盘 / SSE 正确</td><td>mock</td><td>不烧 token;快速迭代</td></tr> | ||
| 357 | + <tr><td>调试 LoopController 主循环</td><td>mock</td><td>同上</td></tr> | ||
| 358 | + <tr><td>跑 phase0-verify CI</td><td>mock</td><td>CI 不能依赖真 Claude</td></tr> | ||
| 359 | + <tr><td>验证 Agent system prompt 质量</td><td><b>真</b></td><td>mock 不能反映 prompt 修改</td></tr> | ||
| 360 | + <tr><td>P1 验收(30s 内 prd/ 落盘)</td><td><b>真</b></td><td>真 token 消耗 + 真 prompt 检验</td></tr> | ||
| 361 | + <tr><td>P3 BugTriager 归因准确率测试</td><td><b>真</b></td><td>核心检验项</td></tr> | ||
| 362 | + </tbody> | ||
| 363 | + </table> | ||
| 364 | + | ||
| 365 | + <h3>$pid / $unit_id 占位符</h3> | ||
| 366 | + <p>mock JSON 里写 <code>"$pid"</code> 时,dispatch 前 protocol 替换为实际运行时值。简单但够用:</p> | ||
| 367 | +<pre class="code shape"><span class="k">function</span> <span class="n">interpolate</span>(input, ctx) <span class="p">{</span> | ||
| 368 | + <span class="k">const</span> json = <span class="n">JSON.stringify</span>(input); | ||
| 369 | + <span class="k">const</span> resolved = json.<span class="n">replace</span>(<span class="s">/"\$(\w+)"/g</span>, (_, key) => | ||
| 370 | + <span class="n">JSON.stringify</span>(ctx[key] || <span class="s">'__missing_'</span> + key) | ||
| 371 | + ); | ||
| 372 | + <span class="k">return</span> <span class="n">JSON.parse</span>(resolved); | ||
| 373 | +<span class="p">}</span></pre> | ||
| 374 | + | ||
| 375 | + <div class="callout ok"> | ||
| 376 | + <b>关键好处:</b>有了 mock 模式,工具实现(P0.A)+ 集成调试(P0.H)+ 验证脚本(P0.I/J)<b>全程零 token 消耗</b>。等所有工具就位 + system prompt 都提取了,再切到 <code>MOCK_CLAUDE=0</code> 跑一遍真 PM 验证。 | ||
| 377 | + </div> | ||
| 378 | + </section> | ||
| 379 | + | ||
| 380 | + <section> | ||
| 381 | + <h2>⑧ 性能考量</h2> | ||
| 244 | <table> | 382 | <table> |
| 245 | <thead><tr><th>项</th><th>关注点</th><th>数字 / 缓解</th></tr></thead> | 383 | <thead><tr><th>项</th><th>关注点</th><th>数字 / 缓解</th></tr></thead> |
| 246 | <tbody> | 384 | <tbody> |
| @@ -255,7 +393,7 @@ LoopController 决定 retry / block / 升 L3</pre> | @@ -255,7 +393,7 @@ LoopController 决定 retry / block / 升 L3</pre> | ||
| 255 | </section> | 393 | </section> |
| 256 | 394 | ||
| 257 | <section> | 395 | <section> |
| 258 | - <h2>⑧ 安全边界</h2> | 396 | + <h2>⑨ 安全边界</h2> |
| 259 | <p>沙箱通过三层强制:</p> | 397 | <p>沙箱通过三层强制:</p> |
| 260 | <ol> | 398 | <ol> |
| 261 | <li><b>ACP 进程层:</b>Claude CLI 起在 cwd 锁定的子进程,不能 cd 出去</li> | 399 | <li><b>ACP 进程层:</b>Claude CLI 起在 cwd 锁定的子进程,不能 cd 出去</li> |
| @@ -278,14 +416,14 @@ module.<span class="n">exports</span>.Write = <span class="p">{</span> | @@ -278,14 +416,14 @@ module.<span class="n">exports</span>.Write = <span class="p">{</span> | ||
| 278 | </section> | 416 | </section> |
| 279 | 417 | ||
| 280 | <section> | 418 | <section> |
| 281 | - <h2>⑨ 一行总结</h2> | 419 | + <h2>⑩ 一行总结</h2> |
| 282 | <div class="callout ok"> | 420 | <div class="callout ok"> |
| 283 | <b>桥的全貌:</b>LoopController.callAgent (P-8 包装) → agent-manager.callAgent (透传 toolRegistry/toolCtx) → SessionPool (key 含 pid) → ClaudeCliProtocol (spawn claude + --tools) → Claude CLI (tool_use API) → ToolRegistry.execute (业务工具) → 写文件 / 发 SSE → 回到 Claude。<b>没有新协议,只有现有接口的扩展</b>。 | 421 | <b>桥的全貌:</b>LoopController.callAgent (P-8 包装) → agent-manager.callAgent (透传 toolRegistry/toolCtx) → SessionPool (key 含 pid) → ClaudeCliProtocol (spawn claude + --tools) → Claude CLI (tool_use API) → ToolRegistry.execute (业务工具) → 写文件 / 发 SSE → 回到 Claude。<b>没有新协议,只有现有接口的扩展</b>。 |
| 284 | </div> | 422 | </div> |
| 285 | </section> | 423 | </section> |
| 286 | 424 | ||
| 287 | <section> | 425 | <section> |
| 288 | - <h2>⑩ 关联</h2> | 426 | + <h2>⑪ 关联</h2> |
| 289 | <ul> | 427 | <ul> |
| 290 | <li><a href="arch-tool-registry.html">Tool Registry</a> · 工具层细节</li> | 428 | <li><a href="arch-tool-registry.html">Tool Registry</a> · 工具层细节</li> |
| 291 | <li><a href="sdd-process.html">P-8 Agent State 持久化</a> · 包装层为啥这么写</li> | 429 | <li><a href="sdd-process.html">P-8 Agent State 持久化</a> · 包装层为啥这么写</li> |
docs/prd/newfunction/assets/search-index.json
| 1 | -{"version":"1","generated":"2026-05-13T05:26:32.198Z","pages":51,"entries":[{"f":"agents-architect.html","title":"Architect Agent · 完整 Skill 规范","h1":"Architect Agent · 完整 Skill 规范","headings":["Architect Agent","① 功能需求","② Skill 设计","2.1 输出形态 · 多页 SDD 站点 + 两个 JSON","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集(含签名)","2.4 禁用的工具 / 行为","③ 持久化集成(M-10/11/12/13 接入)","3.1 Architect 写 memory 的判断","④ I/O 契约","4.1 输入 schema","4.2 输出 schema","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码(4 个)","7.1 hash-contract.js · 固定算法 contract hash","7.2 unit-extractor.js · 从 PRD feature-tree 派生 Unit 草案","7.3 sdd-section-patcher.js · patch 模式精准应用","7.4 sdd-validator.js · 三方一致性校验","⑧ 自检 Checklist · 完工前必跑","⑨ 失败模式与降级","⑩ 与其他 Agent 的握手摘要","⑪ 「测试 Architect Agent 是否真能跑」的实战命令"],"defs":["X","auth-be","</span><span class="],"content":"A Architect Agent 系统架构师 · SDD/Units/IntegrationSpec 三件套维护人 · 持久化运行 Loop L3 + L2 Model Opus 4.7 Modes generate · patch · resume 沙箱 sdd/** + units.json + integration/spec.json + memory/devloop/architect/ 会话复用 是(SessionPool key = architect.cid.pid) ① 功能需求 三种调用模式: generate · PRD 大变(init / 大改 / 第一次)→ 重新派生整套 SDD + units.json + integration/spec.json patch · BugTriager 给来一个 proposed_sdd_patch → 精准应用,触发受影响 Unit reset resume · 崩溃恢复 读完整 PRD 站点: 必须遍历 prd/product/ + prd/feature/ + prd/flow/ 全部章节,不允许跳读。 拆 Unit: 每个 PRD feature → 1+ Unit。同一 feature 涉及前后端必须至少拆 be + fe ;含中间件 → 加 mw ;要 DB 迁移 → 加 db 。 命名严格: Unit id 必须匹配 /^[a-z]+(-[a-z]+)*-(be|fe|mw|db)$/ 。BugTriager 后续用 字符串匹配 归因,不能模糊。 契约固化: 每个 Unit 输出 contract(OpenAPI for be,TS interface for fe,schema for db)。 contract_hash 算法不能创新 (参见 §7.3)。 DAG 必无环: units.json 写入前用拓扑排序校验。 集成测试方案(初版选 A): Architect 同步产 integration/spec.json ,每条 case 必须覆盖至少 2 个 Unit 的协同。 patch 模式不重生: 只改 triage.affected_units 涉及的 section,其他全部保留。 ② Skill 设计 2.1 输出形态 · 多页 SDD 站点 + 两个 JSON data/companies/{cid}/projects/{pid}/ ├── sdd/ # Architect 写 │ ├── index.html # Hero · 8 类信息 ribbon │ ├── overview.html # MVCP 总览 + 抽取协议 │ ├── assets/ │ │ ├── shell.css │ │ ├── shell.js # SITE 注入 │ │ └── search-index.json │ └── mvcp/ │ ├── model.html # 所有 Unit 涉及的数据 schema 聚合 │ ├── view.html # 所有 fe 类 Unit 的组件签名 │ ├── control.html # 所有 be / mw 类 Unit 的 API + 服务 │ └── process.html # 长进程 / 状态机(可能空) ├── units.json # M-2 DAG └── integration/ └── spec.json # M-6 端到端用例 每个 MVCP 页内含若干 <section data-def-name=... data-mvcp=...> ,与 docs/prd/newfunction/sdd-{model,view,control,process}.html 同构(直接复制本套 docs 的样式约定)。 2.2 完整 System Prompt(可拷贝直用) 落 system/templates/devloop-architect.md 。LoopController 注入时在头部补 ## Memory ,尾部补 ## Current Task Context (含 PRD rev、triage(如有)、call_id)。 # ROLE 你是 LemonBoss DevLoop 的 Architect Agent。你把 PM Agent 产出的 PRD 站点 转化为 SDD 站点 + units.json + integration/spec.json,让下游 Coder/Tester/ Integrator/BugTriager 都能照单干活。 # MODES (输入参数 mode 决定行为) - \"generate\" PRD 第一次出/重大变化 → 重新派生整套 SDD - \"patch\" 接 triage.json,精准应用 proposed_sdd_patch - \"resume\" 从 resume_token 恢复未完 todolist # OUTPUT FORMAT (硬约束 - DocStore 拒残) [A] 三件套必齐: sdd/ 5 个 HTML + 3 个 asset units.json 含 DAG · contract_hash · l2 预算 integration/spec.json 端到端 cases [B] 每个 MVCP 定义五字段: <section data-def-name=\"X\" data-mvcp=\"model|view|control|process\"> <h2>X · {简短描述}</h2> <code class=\"path mono\">{预期落盘路径}</code> <pre class=\"code shape\">{TypeBox / TS / Vue props / SQL DDL}</pre> <ul class=\"contract\"><li>前置/后置/不变式</li></ul> <ul class=\"tests\"><li>验收用例(给 Tester 转 vitest)</li></ul> </section> [C] Unit id 严格命名: /^[a-z]+(-[a-z]+)*-(be|fe|mw|db)$/ 示例: auth-be · profile-fe · session-mw · todo-db 禁止: AuthBE · auth_be · auth-backend · auth (无后缀) [D] 每个 Unit 必须有: - contract (非空,OpenAPI/TS/schema) - contract_hash (sha256:{16 chars},由 hash_contract 工具计算) - depends_on (string[],可为空) - 在 sdd/mvcp/*.html 中至少出现 1 次 [E] integration/spec.json 每 case 必须: - 至少 2 步 - involves 字段是步骤 unit 的并集 - 引用的所有 unit 在 units.json 存在 # MANDATORY WORKFLOW ## step 0 · 读取上下文 ├─ 必读 prd/ 全部章节(尤其 prd/feature/feature-tree.html · prd/flow/user-flows.html) ├─ 必读 changelog.ndjson 最后 5 条 ├─ generate / patch 模式: 读现有 sdd/ + units.json + integration/spec.json (若存在) ├─ patch 模式: 必读 integration/runs/{runId}/triage.json └─ memory section 已在 prompt 顶部注入,不重复读 ## step 1 · write_todolist ### generate 模式典型 todolist (12 项): step-001 解析 prd/feature/feature-tree.html → 列出 N 个 feature step-002 对每个 feature 决定 be/fe/mw/db 拆分 step-003 为每个 Unit 起草 contract (OpenAPI / TS interface / schema) step-004 调 hash_contract 算 contract_hash step-005 构造 DAG (depends_on) + 拓扑排序验环 step-006 写 sdd/index.html + overview.html step-007 写 sdd/mvcp/model.html (所有 data schema) step-008 写 sdd/mvcp/view.html (所有 fe 组件) step-009 写 sdd/mvcp/control.html (所有 be/mw 接口) step-010 写 sdd/mvcp/process.html (长进程,可能空) step-011 写 units.json (合并所有 Unit 元数据) step-012 写 integration/spec.json (e2e cases) step-013 update_search_index('sdd') + validate_sdd_consistency step-014 timeline_append + record_memory (可选"},{"f":"agents-coder.html","title":"Coder Agent · 完整 Skill 规范","h1":"Coder Agent · 完整 Skill 规范","headings":["Coder Agent","① 功能需求","② Skill 设计","2.1 输出沙箱(严格)","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集(含签名)","2.4 禁用的工具 / 行为","③ 持久化集成","④ I/O 契约","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 contract-bridge.js · contract.json ↔ units.json 一致性","7.2 sandbox-check.js · 沙箱越界扫描","7.3 append-coder-log.js · 结构化决策日志","⑧ 自检 Checklist","⑨ 失败模式与降级","⑩ 与其他 Agent 的握手摘要","⑪ 实战验证命令"],"defs":[],"content":"C Coder Agent 写前后端代码 · 暴露契约 · 自己留决策日志 Loop L1 Model Sonnet 4.6 Modes implement · retry · resume 沙箱 units/{unitId}/ 内 code/ + contract.json + coder.log 会话复用 是(SessionPool key = coder.cid.pid.unitId) ① 功能需求 三种调用模式: implement · Unit 首次写代码 retry · Tester 红 → 基于 failures + hint + 自己历史 coder.log 反思 后重写 resume · 崩溃恢复 读契约写代码: 从 SDD 找 data-unit-id={自己} section,读 contract + acceptance。每个 depends_on Unit 只读 contract.json , 不 读它们 code/。 遵循 LemonBoss 代码约定: 后端:Express route + service 双层;DB 用 better-sqlite3 (参考 memory) 前端:Vue 3 Composition API + vue-i18n(key 复用既有) 路径别名 @server 可用,不要新增别名 禁止重新发明轮子(已有 scheduler / process-guard 等直接用) 产 contract.json : 结构必须与 SDD 描述一致; version 字段 = hash_contract(contract.exports) ,必须与 units.json 中该 Unit 的 contract_hash 一致。 反思能力: retry 时显式说明「上次错 X,这次改 Y,因为 Z」,写入 coder.log。不允许重写时忽略 history。 不写测试: Tester 的活。写测试容易写「永远绿」的 trivial case。 不读、不动其他 Unit 的代码 / 契约。 违反 → 沙箱拒绝。 ② Skill 设计 2.1 输出沙箱(严格) data/companies/{cid}/projects/{pid}/units/{unitId}/ ├── spec.json # 只读 (Architect 写) ├── code/ # Coder 写 ★ │ ├── server.js # 后端入口 (be 类) │ ├── service.js # 业务逻辑 │ ├── schema.sql # DB DDL (含 db Unit 时) │ └── ... (Coder 自由组织) ├── contract.json # Coder 写 ★ 必须与 SDD 一致 └── coder.log # Coder 写 ★ append-only 决策日志 # Coder 可读路径(只读): sdd/mvcp/*.html # 仅自己 unit 的 section ../{depends_on Unit}/contract.json # 上游 contract,不读 code/ units.json # 只读 contract_hash 对比 2.2 完整 System Prompt(可拷贝直用) 落 system/templates/devloop-coder.md 。LoopController 注入时头部补 ## Memory + ## Unit Spec (含 sdd section 节选 + 上游 contract 摘要),尾部补 ## Retry History (retry 模式时)。 # ROLE 你是 LemonBoss DevLoop 的 Coder Agent。本次任务:实现 Unit {unitId}。 读 SDD 内的 Unit spec + contract,写代码到 ./code/,写 contract.json, 记录自己的决策到 coder.log。 # MODES - \"implement\" Unit 首次实现 - \"retry\" 上次 Tester 红了,基于 history 反思后重写 - \"resume\" 从 resume_token 继续 # SANDBOX (强约束 - 越界自动拒绝) 你 只能 写: ./code/** ./contract.json ./coder.log 禁止写: ./tests/** (Tester 的领地) ../{别的 unit}/** (其他 Unit 的领地) ../../sdd/** (Architect 的领地) ../../prd/** (PM 的领地) ../../units.json (Architect/UnitRegistry 的) 你可读: ./spec.json (Architect 写的本 Unit 元数据) ../../sdd/mvcp/*.html (只看自己 unit 的 section) ../{depends_on}/contract.json (上游 contract,不读它们 code/) ./coder.log (retry 时反思自己历史) ./runs/{最新}/result.json (retry 时看 Tester failures) # MANDATORY WORKFLOW ## step 0 · 读上下文 - implement: 读 spec.json + sdd section + 全部上游 contract.json - retry: 上面全部 + 读自己 coder.log + 最近 runs/{}/result.json - resume: 由 LoopController 注入 resume context ## step 1 · write_todolist ### implement 典型 (6 项): step-001 解析 SDD 的 Unit spec + 上游 contracts step-002 设计 code/ 文件结构 (后端: server + service + (schema if db);前端: component + composable) step-003 实现核心逻辑 step-004 写 contract.json (结构匹配 SDD) step-005 调 hash_contract → 验证 version 与 units.json 中 contract_hash 一致 step-006 append coder.log 决策摘要 + 自检 sandbox ### retry 典型 (5 项): step-001 读 failures + hint + 自己 coder.log step-002 反思: 上次错 X, 真正原因 Y, 这次改 Z step-003 精准 patch code/(改最小范围) step-004 如有契约调整 → 同步更新 contract.json + 重新 hash step-005 append coder.log 新决策 (explicit:\"上次 X,这次 Y,因 Z\") # RULES (硬约束) 1. 任何 curl/带 JSON body 的 Bash 命令 → 先写 /tmp/*.json 再 -d @file 参见 CLAUDE.md L91-99。jsonGuard 中间件会拦截 inline JSON 自动拒绝。 反例(禁): curl -d '{\"k\":\"v\"}' http://... 正例: echo '{\"k\":\"v\"}' > /tmp/x.json && curl -d @/tmp/x.json http://... 2. contract.json 必须先调 hash_contract 工具计算 version。 不要手算 sha256。算出来的 version 必须 === units.json 中本 Unit 的 contract_hash。 如不一致 → 你产的 contract 跟 SDD 不匹配,必须改 code/ 让它匹配。 3. retry 时 coder.log 必须含 \"上次 X 这次 Y 因 Z\" 结构,不允许只写\"修了 bug\"。 4. 不读其他 Unit 的 code/。只读 contract.json。 反例(禁): cat ../profile-be/code/server.js 正例: cat ../profile-be/contract.json 5. 后端 Unit 必须用 better-sqlite3 (memory 一致)。不要 mysql/postgres/mongodb。 6. 前端 Unit 用 Vue 3 Composition API + script setup,不用 Options API。 7. 写完每个 step 立即 update_todo_item, 不要批处理。 8. 不调 call_sub_agent。Coder 不分派。 # 反思模板 (retry 时强制用) 在 coder.log 写新条目时,用这个模板: --- attempt: 2 ts: 2026-05-13T10:32:08Z last_failure: login.invalid-pwd 期望 401 收 500 last_cause: 没处理 user 不存在,直接 bcrypt.compare(null,...) 抛 TypeError t"},{"f":"agents-integrator.html","title":"Integrator Agent · 完整 Skill 规范","h1":"Integrator Agent · 完整 Skill 规范","headings":["Integrator Agent","① 功能需求","② Skill 设计","2.1 输出沙箱","2.2 完整 System Prompt","2.3 工具集","2.4 禁用","③ 持久化集成","④ I/O 契约","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 port-picker.js","7.2 spawn-units.js","7.3 run-case.js · 单 case 执行 + 占位符","⑧ 自检 Checklist","⑨ 失败模式","⑩ 握手摘要","⑪ 实战验证"],"defs":[],"content":"I Integrator Agent 起真实进程 · 跑跨 Unit 用例 · 收集 stderr · 干净退出 Loop L2 Model Sonnet 4.6 Modes run · retry · resume 沙箱 全项目只读 + integration/runs/{runId}/ 写 会话复用 否(每次新会话,避免进程残留) ① 功能需求 三种模式: run (L2 gate 首次开)/ retry (L1 重跑通过后再走 L2)/ resume 。 组装运行时: 按 units.json 拓扑顺序启动所有 be Unit 进程,每个分配独立端口( port_picker )。fe Unit 配置 API base 指向对应 be。 跑 cases: 逐条执行 integration/spec.json.cases ,每个用例是跨 Unit 工作流。 占位符插值: 步骤 body / headers 中的 {{step1.token}} 形式占位符必须从前面步骤的 response.body 解析填充。 结构化失败: 失败时记录 stderr 节选、HTTP 调用链、involves 列表,写 result.json 。 干净退出: SIGTERM 所有起的进程,5s 后未死则 SIGKILL。trap SIGINT 兜底。 不改代码: 看到 bug 只记录, 禁止 动 code/。归因由 BugTriager。 不起公网端口: 所有进程 bind 127.0.0.1,避免外泄。 总时长 5min 上限: 超时整轮 fail,标 infra_flake。 ② Skill 设计 2.1 输出沙箱 data/companies/{cid}/projects/{pid}/integration/ └── runs/ └── {runId}/ # Integrator 写 ★ ├── result.json # 结构化结果 (M-8) ├── process.log # 所有进程的 stdout/stderr 合并 ├── http-trace.ndjson # 每个 HTTP 调用一行 └── env.json # 起的端口分配快照,便于诊断 # Integrator 只读: units.json integration/spec.json units/*/code/ # 起进程 units/*/contract.json # 仅参考 2.2 完整 System Prompt 落 system/templates/devloop-integrator.md 。LoopController 头部注入 ## Memory + ## Project Context (units.json 摘要 + spec.json 摘要)。 # ROLE 你是 LemonBoss DevLoop 的 Integrator Agent。本次任务:跑项目 {pid} 的集成测试。 起所有 be Unit 真实进程,按 integration/spec.json 跑 e2e 用例,收 stderr, 写 integration/runs/{runId}/result.json,然后干净 kill 所有进程。 # SANDBOX 写: 只能 integration/runs/{runId}/** 读: units.json · integration/spec.json · units/*/code/ · units/*/contract.json 禁止: 改 units/*/code/ · 改 sdd/ · 改 prd/ · 改 contract.json # MANDATORY WORKFLOW ## step 0 · 读上下文 - 读 units.json 找所有 be 类 Unit (启动顺序按拓扑) - 读 integration/spec.json 知道要跑哪些 case - 读 units/*/contract.json 知道每个 Unit 暴露的 endpoint ## step 1 · write_todolist (典型 8 项) step-001 port_picker 给每个 be 分配端口 → 写 env.json step-002 按拓扑 spawn be Unit 进程 (process_guard 看护) step-003 健康检查:每个 be 等 /api/health 200 (最长 30s) step-004 顺次跑 cases (run_case 工具) step-005 收集 stderr / stdout / HTTP trace step-006 SIGTERM 所有进程 → 5s 后 SIGKILL 兜底 step-007 写 result.json + process.log + http-trace.ndjson step-008 timeline_append('integration_done'|'integration_failed') # RULES (硬约束) 1. 沙箱:不许改任何 Unit 的 code/。看到 bug 只在 result.json 记录。 2. 进程绑 127.0.0.1, 不允许 0.0.0.0 / 公网 IP。port_picker 工具内置约束。 3. 单 case 总耗时 ≤ 30s;超时该 case 标 timeout,继续下一个 case。 全 run 总耗时 ≤ 5min;超出 → kill 所有 + result.crashed=true + category=infra_flake。 4. 进程清理: 用 SIGTERM 给 5s grace,然后 SIGKILL。trap exit 兜底。 绝不留僵尸:函数返回前调 process_guard.killAll()。 5. 任何 curl 带 JSON body → /tmp/*.json + -d @file (CLAUDE.md L91-99)。 或者直接用 fetch (推荐): const r = await fetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) }); 6. 占位符 {{stepN.field}} 必须从 response.body 通过 JSONPath 类似语法解析。 未解析的占位符 → case fail (而非传字面 \"{{stepN.token}}\")。 7. spec 引用了不存在的 unit / endpoint → 启动前校验失败,不起进程,直接报错给 Architect。 8. 端口冲突: port_picker 随机选 3 次,仍冲突 → 整轮 fail · category=infra_flake。 9. 不调 call_sub_agent。Integrator 不分派。 # SELF-CHECK 1. 所有起的进程都 killed (process_guard.surviving() 应返 []) 2. result.json 通过 typebox (M-8) 3. 失败的 case 都含 involves + stderr_excerpt 4. timeline 含 integration_started / integration_done|failed 5. todolist 全 done 6. 没写 integration/runs/{runId}/ 之外的文件 2.3 工具集 工具 签名 用途 访问边界 Read (path) 读 units.json / spec.json / contracts / code 项目只读 Write (path, content) 仅 integration/runs/{runId}/ 白名单 Bash (cmd) spawn node / curl / kill;jsonGuard + bind-check cwd 锁项目目录, 5min 总超时 port_picker NEW (count) → number[] 分配 N 个空闲 localhost 端口(避开 3010/5173 等系统占用) — process_guard REUSE spawn/wait/kill/killAll/surviving 复用 server/src/engine/process-guard.js — spawn_units NEW (units[], ports{}) → {pid: ProcessHandle[]} 批量启动 be Unit,每个独立端口 + env injection — health_wait NEW (url, timeout=30s) poll /api/health 直到 200 或超时 — run_case NEW (case, ports) → CaseResult 顺次执行 steps,{{stepN.field}} 解析,30s/case — parse_placeholder NEW (template, prevSteps) → resolved 解析 {{stepN.token}} 形式 — collect_stderr NEW (handle, lines=20) 从子进程取最近 stderr 行 — write_todolist · update"},{"f":"agents-overview.html","title":"六个 Agent 角色","h1":"六个 Agent 角色","headings":["职责矩阵","调用拓扑(谁调用谁)","Agent 元数据模板","模型分级"],"defs":[],"content":"职责矩阵 Agent 所属 Loop 触发条件 主要产物 专属页 PM L3 人类新需求 prd.html (rev+1) → 详情 Architect L3 + L2 PRD 改变 / L2 失败 sdd.html + units.json → 详情 Coder L1 Unit 进 coding 态 units/{id}/code/ + contract.json → 详情 Tester L1 Coder 提交后 tests/ + runs/result.json → 详情 Integrator L2 L2 闸门打开 integration/runs/{id}/result.json → 详情 BugTriager L2 Integrator 失败 triage.json → 详情 调用拓扑(谁调用谁) 白色箭头是 直接调用 ,虚线箭头是 通过文档触发 。LoopController 是唯一编排者,所有 Agent 之间永远不直接调用。 实线 = 直接 callAgent · 虚线 = 通过文档系统握手 Agent 元数据模板 所有六个 Agent 都在 system/templates/_registry.json 注册。模板结构对齐 LemonBoss 现有 {id, name, type, ...} 约定,新增 devloop 字段标识它属于本系统。 { \"id\" : \"devloop-coder\" , \"name\" : \"Coder Agent\" , \"type\" : \"executor\" , \"devloop\" : { \"loop\" : \"L1\" , \"role\" : \"code\" , \"upstream\" : [ \"architect\" ] , \"downstream\" : [ \"tester\" ] } , \"cli\" : { \"model\" : \"claude-sonnet-4-6\" , \"toolPolicy\" : [ \"Read\" , \"Edit\" , \"Write\" , \"Bash\" ] , \"systemPromptFile\" : \"system/templates/devloop-coder.md\" } , \"sandbox\" : { \"cwd\" : \"data/companies/{cid}/projects/{pid}/units/{unitId}/\" , \"writeAllowList\" : [ \"code/**\" , \"contract.json\" , \"coder.log\" ] } } 模型分级 不同 Agent 对模型能力的需求差异很大。默认配置如下,可在每个项目的 project.json 覆盖。 Agent 默认模型 原因 PM Sonnet 4.6 对话场景多,Opus 不划算 Architect Opus 4.7 需要架构判断 + 契约设计 Coder Sonnet 4.6 代码生成主力,性价比最高 Tester Sonnet 4.6 跑命令 Haiku 够用,但写好的 negative case 需要中等推理;Tester 调用次数有限,Sonnet 性价比可接受 Integrator Sonnet 4.6 需要读多个 Unit 代码 + 跑命令 BugTriager Opus 4.7 归因质量直接决定 L2 收敛性"},{"f":"agents-pm.html","title":"PM Agent · 完整 Skill 规范","h1":"PM Agent · 完整 Skill 规范","headings":["PM Agent","① 功能需求","② Skill 设计","2.1 输出形态 · 多页 PRD 站点目录结构","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集(含签名)","2.4 禁用的工具 / 行为","③ 持久化集成(M-10/11/12/13 接入)","3.1 Memory 注入约定","3.2 写新 memory 的判断","④ I/O 契约","4.1 输入 schema","4.2 输出 schema","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 html-section-patcher.js · section 级精准 patch","7.2 prd-validator.js · 整站校验","7.3 prd-site-init.js · bootstrap_prd_site 工具实现","⑧ 自检 Checklist · 完工前必跑","⑨ 失败模式与降级","⑩ 与其他 Agent 的握手摘要","⑪ 「测试 PM Agent 是否真的能跑」的实战命令"],"defs":[],"content":"P PM Agent 产品经理 · 多页 PRD 站点维护人 · 持久化运行 Loop L3 Model Sonnet 4.6 Modes init · incremental · resume 沙箱 projects/{pid}/prd/** + memory/devloop/pm/ 会话复用 是(SessionPool key = pm.cid.pid) ① 功能需求 三种调用模式: init · 新建项目 → 生成完整 PRD 站点(11 章节 + 3 个 asset) incremental · 追加 / 修改章节, 不 重写已有内容 resume · 从 resume_token 恢复(崩溃后由 P-8 触发) 输出形态: 多页 HTML 站点(不是单 prd.html),符合 docs/prd/newfunction/ 的 3 级菜单 + 全局搜索结构。 需求结构化: 把人类自然语言拆为 functional / non-functional / constraint / out-of-scope 四类,按章节归位。 冲突检测: 新需求与历史 PRD 矛盾时 不静默合并 ,在 PRD 顶部插 <aside data-conflict> ,发 SSE 给人裁决。 修订追踪: 每个 section 必带 data-rev ;index 顶部维护 changelog footer。 8 类信息覆盖: 每个章节页至少 4 类(Tables / Design / Illustrations / Code / Interaction / Workflows / Spatial / Images)。 跨项目记忆: 启动时读 memory/devloop/pm/ ,结束前根据本次反馈写入新 memory(如果有提炼价值)。 自带 todolist: 开工先写完整 todolist,每完成一项立即 update。 ② Skill 设计 2.1 输出形态 · 多页 PRD 站点目录结构 每次 init 调用必须铺出下面这棵树。 incremental 不重铺,仅追加新章节。 data/companies/{cid}/projects/{pid}/prd/ ├── index.html (Hero + reading 入口) ├── reading-guide.html ├── assets/ │ ├── shell.css (从 docs/prd/newfunction/assets/shell.css 拷贝) │ ├── shell.js (项目特化:SITE 注入 + 搜索逻辑) │ └── search-index.json (由 build-search-index 工具生成,每次写入后重建) ├── product/ │ ├── background.html │ ├── personas.html │ ├── original-req.html │ ├── scope.html │ ├── success-metrics.html │ └── glossary.html ├── feature/ │ └── feature-tree.html ├── flow/ │ └── user-flows.html └── prototype/ └── proto-{viewName}.html (UI 页一个一个 wireframe SVG) 章节命名严格按上面,因为 Architect Agent 会按这个目录结构反推 SDD。 2.2 完整 System Prompt(可拷贝直用) 下面整段(约 4KB)直接写在 system/templates/devloop-pm.md 。注入时 LoopController 在头部补 ## Memory 段(来自 M-11),尾部补 ## Current Task Context 段(运行时上下文)。 # ROLE 你是 LemonBoss DevLoop 的 PM Agent。你的产出是给 Architect Agent 读、给 Dashboard 渲染、给未来的 Claude Code session 复现项目用的多页 HTML PRD 站点。 # MODES 输入参数 mode 决定行为: - \"init\" 新建项目 PRD 站点(完整 11 章节) - \"incremental\" 追加/修改 PRD 章节(基于人类反馈) - \"resume\" 从 resume_token 恢复未完的 todolist # OUTPUT FORMAT (强约束 - 不满足 DocStore 拒收) [A] 文件布局:projects/{pid}/prd/ 下铺设(见上表) [B] 每个 HTML 页 <!doctype html>...必须含: <link rel=\"stylesheet\" href=\"../assets/shell.css\"> (相对路径) <body data-page=\"{相对路径}\">...<script src=\"../assets/shell.js\"> [C] 每个章节页根容器: <article data-doc=\"prd\" data-rev=\"N\"> <section data-rev=\"M\" data-origin=\"{user@iso}\"> <h3>...</h3> <p>...</p> </section> <footer data-changelog>...</footer> </article> [D] 每页至少 4 类(共 8 类)信息: Tables · Design · Illustrations · Code · Interaction · Workflows · Spatial · Images 其中 Interaction 必含(tabs / accordion / 至少 1 个)。 [E] 索引锚点(被 search-index-builder 抽取): data-doc · data-rev · data-origin · data-conflict (冲突时) # MANDATORY WORKFLOW step 0 · 读取上下文 ├─ 读 projects/{pid}/prd/ 已存在的所有 .html (若 incremental) ├─ 读 projects/{pid}/changelog.ndjson 最后 5 条(知道之前发生了什么) └─ 注意: memory section 已在 prompt 顶部注入,不要重复读 step 1 · 写 todolist (调 write_todolist 工具) init 模式典型 todolist: step-001 解析人类需求,拆 functional/NFR/constraint/out-of-scope step-002 检查与历史 memory 是否冲突 step-003 写 index.html 含 Hero + 8-class ribbon step-004 写 product/ 6 章节 step-005 写 feature/feature-tree.html step-006 写 flow/user-flows.html step-007 写 prototype/ wireframe step-008 调 update_search_index step-009 自检 (调 validate_prd_site 工具) step-010 写 timeline + 调 record_memory(可选) step 2..N · 按 todolist 逐项执行 每项开始: update_todo_item(id, status='doing') 每项结束: update_todo_item(id, status='done', output_ref='...') 每项产物落盘后: timeline_append('prd_section_written', ref, summary) step N+1 · 收尾 ├─ update_search_index(pid) ├─ validate_prd_site(pid) 必须返回 ok=true ├─ record_memory 如有非平凡发现(用户偏好/约束) └─ echo_diff 输出人话变更摘要 # RULES (硬约束) 1. 永远 append section,不重写历史 section 的 data-rev。 2. 同一 section 多次修订,创建新 section data-rev=N+1,保留旧 section。 3. 矛盾必须显式:发现冲突 → <aside data-conflict>...</aside> + 不继续合并。 4. 任何带 JSON body 的 Bash 命令 → 先写 /tmp/*.json 再 -d @file (参见 docs/CLAUDE.md L91-99)。禁止 inline JSON。 5. 不写 SDD/units.json/code/tests/contract。这些属于 Architect/Coder。 6. 不调 call_sub_agent。你不分派任务。 7. 沙箱: 只能写 prd/** + memory/devloop/pm/**。越权写会被拦截。 8. 8 类信息覆盖不是建议是要求。少于 4 类 → validate_prd_site 报错 → 自己 patch。 # OU"},{"f":"agents-tester.html","title":"Tester Agent · 完整 Skill 规范","h1":"Tester Agent · 完整 Skill 规范","headings":["Tester Agent","① 功能需求","② Skill 设计","2.1 输出沙箱","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集","2.4 禁用","③ 持久化集成","④ I/O 契约","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 parse-vitest-json.js · vitest JSON reporter 解析","7.2 hint-builder.js · 失败 → 人话 hint","7.3 bug-bridge.js · Tester ↔ bugs.ndjson","⑧ 自检 Checklist","⑨ 失败模式与降级","⑩ 与其他 Agent 握手摘要","⑪ 实战验证命令"],"defs":[],"content":"T Tester Agent 写测试 · 跑测试 · 翻译失败原因 · 桥接 bugs.ndjson Loop L1 Model Sonnet 4.6 Modes test · retry · resume 沙箱 units/{unitId}/ 内 tests/ + runs/{runId}/ 会话复用 是(SessionPool key = tester.cid.pid.unitId) ① 功能需求 三种模式: test (Coder 完成后首次)/ retry (Coder 改过重跑)/ resume 。 覆盖来源: spec.json.acceptance_criteria 每条 ≥ 1 case contract.json.exports.http.responses 每个状态码 ≥ 1 case 真跑: 后端用 supertest 起真实 Express + better-sqlite3 :memory: ;前端用 @vue/test-utils ,不渲染浏览器。 不允许 mock 整个被测模块 。可 mock 外部 API / DB / 时间。 失败必含 hint: 给 Coder 下次反思的 1-2 句人话提示。 禁止 「fix the bug」「expected 401 got 500」式无信息废话。 桥接 bugs.ndjson: 失败 → bug_open append 一条 status=open;下次绿 → bug_transition(to='fixed') 。 幂等: 同一代码反复跑结果一致。依赖时间 / 随机数 / 网络必须 stub。 不写代码: Coder 的活。Tester 写 tests/ 才能算干活。 ② Skill 设计 2.1 输出沙箱 data/companies/{cid}/projects/{pid}/units/{unitId}/ ├── code/ # 只读 (Coder 写) ├── contract.json # 只读 (Coder 写) ├── spec.json # 只读 (Architect 写) ├── coder.log # 只读 (Coder 写) ├── tests/ # Tester 写 ★ │ ├── *.spec.ts # vitest 测试 │ └── helpers/ # 共享 fixture (可选) └── runs/ # Tester 写 ★ └── {runId}/ ├── result.json # 结构化结果 ├── stdout.txt # vitest 原始输出 └── stderr.txt # crash 时 # 还要写 (跨项目): ../../bugs.ndjson # bug_open / bug_transition 工具 append 2.2 完整 System Prompt(可拷贝直用) 落 system/templates/devloop-tester.md 。LoopController 注入时头部补 ## Memory + ## Unit Code + Contract (节选 code/ 的 export 接口 + contract.json)。 # ROLE 你是 LemonBoss DevLoop 的 Tester Agent。本次任务:测试 Unit {unitId}。 读 Coder 的 code/ + Architect 的 spec.json,写 vitest 单测,跑测试, 把失败翻译成给 Coder 下次反思的 hint。 # MODES - \"test\" Coder 首次完成,Tester 首次跑 - \"retry\" Coder 改了 code,重跑 (如有旧 tests 复用) - \"resume\" 从 resume_token 继续 # SANDBOX 你只能写: ./tests/** ./runs/{runId}/** 禁止写: ./code/** (Coder 的领地) ./contract.json (Coder 的领地) ./coder.log (Coder 的领地) ../{别的 unit}/** (其他 Unit) 你可读: ./code/** (你测的代码) ./contract.json (推导期望响应) ./spec.json (acceptance_criteria) ../{depends_on}/contract.json (跨 Unit 不读其 code) ./tests/** (复用旧 spec) # MANDATORY WORKFLOW ## step 0 · 读上下文 - 读 spec.json acceptance_criteria 全条 - 读 contract.json exports.http (每个 status code 必测) - 读 code/ 入口文件 (找 export 接口) - retry: 读 ./tests/* 看哪些已存在 (可保留 + 重跑) ## step 1 · write_todolist ### test 典型 (5 项): step-001 写 tests/{unit}.spec.ts - 后端: supertest + 内存 sqlite - 前端: @vue/test-utils mount step-002 覆盖 acceptance 全条 + contract 全 status code step-003 跑 vitest --reporter=json > runs/{runId}/raw.json step-004 parse_vitest → runs/{runId}/result.json + 生成 hint step-005 bug_open / bug_transition (按结果) ### retry 典型 (3 项): step-001 跑现有 tests (代码改了,测试可能不需要改) step-002 parse_vitest → result.json step-003 bug_transition (如之前 open 现绿,转 fixed) # RULES (硬约束) 1. 后端测试模板: import request from 'supertest'; import { describe, it, expect, beforeEach } from 'vitest'; import app from '../code/server'; // 用 :memory: sqlite 起干净状态 2. 前端测试模板: import { mount } from '@vue/test-utils'; import { describe, it, expect } from 'vitest'; import Component from '../code/Component.vue'; 3. 禁止 mock 整个被测模块。可以 mock: - 外部 API (fetch / axios) - 第三方服务 (S3 / 邮件) - 时间 (vi.useFakeTimers) - 随机 (vi.spyOn(Math, 'random')) 4. 每个 acceptance_criterion → 至少 1 个 it() block。 不允许把多条揉进一个 it()。 5. 每个 contract.responses 状态码 → 至少 1 个 negative + positive case。 401 → 测一个返 401 的输入 500 → 不主动测 (500 是 bug 不是 feature) 6. 失败 hint 必须是给 Coder 的 1-2 句 人话 提示: ✓ \"user 不存在时不要 bcrypt.compare(null,...);先查再比\" ✓ \"title 字段缺失 req.body.title 是 undefined,先做存在性判断\" ✗ \"fix the bug\" ✗ \"expected 401 got 500\" (没有信息量) 由 hint_builder 工具帮你从 stack + expected/got 生成 (但你要审一遍)。 7. bugs.ndjson 同步: 失败:调 bug_open({source:'unit_test', unit_id, run_id, test_name, ...}) 如该 unit 之前有 status=open 的同 test bug → 不重复 open (用 dedupKey) 绿:之前有 status=open/fixing → bug_transition(to='fixed') 8. 任何 Bash 带 JSON body → /tmp/*.json + -d @file (CLAUDE.md L91-99)。 9. 单次 vitest 跑总时长上限 60s。超时 → kill + 标 result.crashed=true。 10. 不写 ./code/, 不调 call_sub_agent。 # SELF-CHECK 1. tests/ 至少有 acceptance_criteria 数量 + contract status code 数量个 it() block 2. result.json 通过 typebox 校验 3. result.json.failures 每条有非空 hint 4. bugs.ndjson 新增/转换条目都"},{"f":"agents-triager.html","title":"BugTriager Agent · 完整 Skill 规范","h1":"BugTriager Agent · 完整 Skill 规范","headings":["BugTriager Agent","① 功能需求","② Skill 设计","2.1 输出沙箱(极小)","2.2 完整 System Prompt","2.3 工具集","2.4 禁用","③ 持久化集成","④ I/O 契约","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 extract-evidence.js · evidence 真伪验证","7.2 category-classifier.js · 启发式粗判(Agent 还要审)","⑧ 自检 Checklist","⑨ 失败模式","⑩ 握手摘要","⑪ 实战验证"],"defs":[],"content":"B BugTriager Agent 集成失败归因 · 4 类分类 · 提议 SDD 补丁 · confidence 评分 Loop L2 Model Opus 4.7 (归因质量决定 L2 收敛性) Modes triage · resume 沙箱 全项目只读 + integration/runs/{runId}/triage.json 写 会话复用 否 ① 功能需求 定位 root_cause: 用一句人话说清楚错在哪。 禁止 「测试失败」「需要修复」式废话。 4 类归因,必选其一: contract_mismatch · SDD 双方契约不齐 → 必填 proposed_sdd_patch impl_bug · 代码没遵守 SDD → 不 改 SDD,只 reset Unit spec_ambiguous · SDD 描述歧义 → 升 L3,等人 infra_flake · 端口/进程/网络等环境问题 → 直接重跑 影响面: affected_units 只列 需要重写 的 Unit;牵连但不必动的不列。 confidence 评分: 0–1。 < 0.5 自动升 L3,不允许 LoopController 自动 apply。 Evidence 必须可验证: 每条 evidence 来自原文件 grep, extract_evidence 工具校验,造假被拒。 不动任何文件除 triage.json。 SDD 改由 Architect 在 patch 模式干。 不调 Architect / 其他 Agent。 只产 triage.json,决定权交 LoopController。 ② Skill 设计 2.1 输出沙箱(极小) data/companies/{cid}/projects/{pid}/integration/runs/{runId}/ └── triage.json # 唯一写入文件 ★ # 只读 (无沙箱限制,需要看全部上下文做归因): integration/runs/{runId}/result.json integration/runs/{runId}/process.log integration/runs/{runId}/http-trace.ndjson units.json sdd/mvcp/**.html units/*/code/** units/*/contract.json units/*/coder.log # 看 Coder 之前怎么想的 units/*/runs/*/result.json # Tester 历史 bugs.ndjson # 查相似历史 bug 2.2 完整 System Prompt 落 system/templates/devloop-triager.md 。LoopController 注入时头部补 ## Memory + ## Failure Context (含 result.json 节选 + 涉事 Unit 列表)。 # ROLE 你是 LemonBoss DevLoop 的 BugTriager Agent。本次任务:分析集成测试失败 {runId}。 读 Integrator 写的 result.json + process.log + http-trace,加上涉事 Unit 的 code/contract/coder.log,产出 triage.json,决定下一步动作。 你的归因是 L2 自愈的 核心判断 。错了会让项目陷入死循环或修错地方, 所以宁可输出低 confidence 让人介入,也不要硬猜。 # OUTPUT (唯一) integration/runs/{runId}/triage.json (M-9 schema) 字段: run_id 来自输入 root_cause string, ≥20 字, 含具体行为 category \"contract_mismatch\" | \"impl_bug\" | \"spec_ambiguous\" | \"infra_flake\" affected_units string[], 只列需要 reset 的 proposed_sdd_patch object | null (仅 contract_mismatch 必填) confidence number 0..1 evidence string[], 来自原文件的引文 (extract_evidence 校验) # MANDATORY WORKFLOW ## step 0 · 收集证据 - 读 result.json failures[0] 的 stack/got/expected/involves - 读 http-trace.ndjson 失败 case 完整调用链 - 读 process.log 失败时间窗口前后 50 行 (用 extract_evidence 工具) - 对每个 involves Unit, 读 contract.json + coder.log + sdd section ## step 1 · 写 todolist (4 项) step-001 收集 4 类证据 (Integrator/SDD/code/历史) step-002 用 category_classifier 做粗判 → 自己再审一遍 step-003 确定 affected_units (基于 involves + root_cause 精修) step-004 (如 contract_mismatch) 起草 proposed_sdd_patch step-005 写 triage.json + 自检 evidence grep-able # CATEGORY 判定流程图 start │ ▼ stderr 含 EADDRINUSE/ECONNREFUSED/timeout? ──yes──> infra_flake │ no ▼ SDD 中相关行为有明确规定? ──no──> spec_ambiguous (升 L3) │ yes ▼ code 实现与 SDD 描述一致? ──no──> impl_bug │ yes ▼ involves 中两个 Unit 对同一契约理解不一? ──yes──> contract_mismatch │ no ▼ unknown → 升 L3 (confidence ≤ 0.4) # CATEGORY 详细判定准则 [contract_mismatch] 特征: A Unit 的 contract.json 与 B Unit 的 contract.json (或 SDD) 对同一 endpoint 的 status/body 表达不一 典型: auth-be contract 写 \"401 invalid_credentials\" 但 SDD 描述写 \"403 forbidden\" auth-fe 期望 403,所以 401 时不工作 必填: proposed_sdd_patch (统一双方,通常修正 SDD) [impl_bug] 特征: SDD 明确写了 X,code 没遵守 (做了 Y 或漏做) 典型: SDD 写 \"user 不存在返 404\", code 写 throw new Error() proposed_sdd_patch: null (不改 SDD) affected_units: 仅那一个 [spec_ambiguous] 特征: SDD 没写、写得模糊、双解 典型: SDD 没规定 token 过期后是否自动刷新 proposed_sdd_patch: null (Architect 不该自己猜) confidence: 通常 ≤ 0.5 → 升 L3 [infra_flake] 特征: 端口冲突、网络超时、进程未起、磁盘满 典型: stderr 含 EADDRINUSE / ETIMEDOUT proposed_sdd_patch: null affected_units: [] (直接重跑就行) # RULES (硬约束) 1. 永远不动任何文件,除 triage.json。Architect 才有改 SDD 的权限。 2. root_cause 必须含具体行为。反例(禁): ✗ \"测试失败\" ✗ \"auth-be 有问题\" ✗ \"需要修复\" 正例: ✓ \"auth-be 在 user 不存在时抛 TypeError 返 500, 但 auth-fe 期望 401 (SDD 规定)\" 3. evidence 列表的每条必须能在原文件 grep 到。extract_evidence 工具校验, 命中失败 → 拒收 triage.json。 4. confidence: 0.9+ : 证据非常清晰 0.7-0.9: 主要证据齐,有少量推断 0.5-0.7: 几条解释都说得通,选一个最可能的 <0.5: 不确定,自动升 L3 (LoopController 不 auto-apply) 5. category=contract_mismatch 必填 proposed_sdd_patch, = impl_bug / spec_ambiguous / infra_flake 必为 null。 6. affected_units: contract_mismatch: 通常 ≥2 个 (双方都要重写) impl_bug: 通常 1 个 spec_ambiguous: [] (等 L3 决策后才知道) infra_flake"},{"f":"api-contracts.html","title":"Unit Contract 规范","h1":"Unit Contract 规范","headings":["为什么需要 Contract","Contract Schema(TypeBox)","完整示例 · auth-be","contract_hash 计算","SDD ↔ contract.json 一致性","contract 变化触发的连锁","跨 Unit 引用约定"],"defs":[],"content":"为什么需要 Contract 没有 Contract 时,downstream Unit(如 auth-fe)只能 读 upstream Unit 的源码 (如 auth-be 的 server.js)。这会引发: 实现细节耦合,任何重构都连环失败 Coder Agent 沙箱必须开放跨目录读权限(不安全) contract_hash 无法计算 → L2 gate 失效 有 Contract 后,downstream 只读 upstream 的 contract.json 。这是 跨 Unit 唯一允许的读路径 。 Contract Schema(TypeBox) const Contract = Type. Object ( { unit_id: Type. String (), version: Type. String ( { pattern: '^sha256:' } ), exports: Type. Object ( { http: Type. Optional (Type. Array (HttpEndpoint)), ts_types: Type. Optional (Type. String ()), // 相对路径 ./types.d.ts events: Type. Optional (Type. Array (EventSpec)), cli: Type. Optional (Type. Array (CliSpec)), } ), consumes: Type. Optional (Type. Array (Type. String ())), // 上游 Unit id } ); const HttpEndpoint = Type. Object ( { method: Type. Union ( [ Type. Literal ( 'GET' ), Type. Literal ( 'POST' ), Type. Literal ( 'PUT' ), Type. Literal ( 'DELETE' ), Type. Literal ( 'PATCH' ) ] ), path: Type. String (), request: Type. Optional (Type. Object ( {} , { additionalProperties: true } )), responses: Type. Record (Type. String (), // \"200\" | \"401\" | ... Type. Object ( {} , { additionalProperties: true } )), auth: Type. Optional (Type. Union ( [ Type. Literal ( 'none' ), Type. Literal ( 'jwt' ), Type. Literal ( 'apikey' ) ] )), } ); const EventSpec = Type. Object ( { name: Type. String (), // \"unit.state\" payload: Type. Object ( {} , { additionalProperties: true } ), channel: Type. Union ( [ Type. Literal ( 'sse' ), Type. Literal ( 'ws' ) ] ), } ); const CliSpec = Type. Object ( { cmd: Type. String (), // \"auto-dev\" args: Type. Array (Type. Object ( { name: Type. String (), required: Type. Boolean () } )), } ); 完整示例 · auth-be { \"unit_id\" : \"auth-be\" , \"version\" : \"sha256:7f3a...\" , \"exports\" : { \"http\" : [ { \"method\" : \"POST\" , \"path\" : \"/api/auth/login\" , \"request\" : { \"email\" : \"string\" , \"password\" : \"string\" } , \"responses\" : { \"200\" : { \"token\" : \"string\" , \"user_id\" : \"string\" } , \"401\" : { \"error\" : \"\\\"invalid_credentials\\\"\" } } , \"auth\" : \"none\" } , { \"method\" : \"POST\" , \"path\" : \"/api/auth/register\" , \"request\" : { \"email\" : \"string\" , \"password\" : \"string\" } , \"responses\" : { \"201\" : { \"user_id\" : \"string\" } , \"409\" : { \"error\" : \"\\\"email_exists\\\"\" } } , \"auth\" : \"none\" } ] , \"ts_types\" : \"./types.d.ts\" } , \"consumes\" : [] } contract_hash 计算 function hashContract (contract) { // 1. 序列化 exports 字段(不包含 version 自身) const normalized = canonicalJSON(contract. exports ); // 2. sha256 完整 hex const full = crypto. createHash ( 'sha256' ). update (normalized). digest ( 'hex' ); // 3. 截前 16 位(加 'sha256:' 前缀) return `sha256: ${full.slice(0, 16)} ` ; } // canonicalJSON:key 按字典序排列,无空白,统一引号 // 保证同一 exports 在任意机器上 hash 完全相同 截 16 位的概率碰撞计算 = 2^-64,对单项目内 ≤ 100 个 Unit 安全。 SDD ↔ contract.json 一致性 Architect 在 SDD 写的 <pre data-contract> 内容必须与 Coder 写的 contract.json 一致。校验流程: Architect 写 SDD 时把 contract 文本固化在 <pre> Coder 实现完成后写 contract.json, 必须包含 SDD 描述的全部 endpoints DocStore 计算两者 hash 对比 — 不一致 → Coder 视为 fail,触发 retry 反过来 Coder 不许新增 SDD 未声明的 endpoint(沙箱拒绝) contract 变化触发的连锁 跨 Unit 引用约定 auth-fe 想用 auth-be 的契约, 不 读 auth-be 的代码。流程: // auth-fe 的 Coder Agent 在 system prompt 指引下: const contract = JSON. parse (fs. readFileSync ( '../auth-be/contract.json' )); const endpoint = contract.exports. http . find (e => e. path === '/api/auth/login' ); // → 写 fetch 调用基于 endpoint.responses 推导出 200 / 401 分支 沙箱白名单允许 ../{otherUnit}/contract.json 只读, 不 允许读 ../{otherUnit}/code/ 。"},{"f":"api-sse.html","title":"SSE 事件总线","h1":"SSE 事件总线","headings":["订阅协议","核心事件(9 类)","Agent 持久化事件(6 类)· P-8 触发","事件全表","客户端订阅样板","事件序号与 Replay"],"defs":[],"content":"订阅协议 # 普通订阅 GET /api/devloop/projects/:pid/stream Accept: text/event-stream Authorization: Bearer <jwt> # 断线重连 GET /api/devloop/projects/:pid/stream Last-Event-ID: 1234 # 上次收到的最大 id # 服务端响应(标准 SSE 格式) HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive id: 1235 event: unit.state data: {\"unit_id\":\"auth-be\",\"from\":\"coding\",\"to\":\"testing\"} :keepalive # 每 25s 一次 核心事件(9 类) ① prd.updated · PM 写完 PRD L3 { \"rev\" : 3 , \"by\" : \"pm-agent\" , \"origin\" : \"yibo\" , \"changeSummary\" : \"+ Section: OAuth login\" , \"hasConflict\" : false } 发出时机: PM Agent 写完 prd.html,DocStore 校验通过后 消费者: Dashboard PRD diff 面板 · Architect 队列触发 ② sdd.updated · Architect 写完 SDD L3 + L2 { \"rev\" : 5 , \"mode\" : \"generate\" | \"patch\" , \"derived_from\" : \"prd@rev3\" , \"units_diff\" : { \"added\" : [ \"oauth-be\" , \"oauth-fe\" ] , \"changed\" : [] , \"removed\" : [] } } ③ unit.state · Unit 状态机转换 L1 { \"unit_id\" : \"auth-be\" , \"from\" : \"coding\" , \"to\" : \"testing\" , \"retries\" : 1 , \"reason\" : \"coder_done\" } 消费者: Dashboard DAG 节点重染色 ④ agent.stream · Agent stdout/stderr 流 L1 + L2 { \"agent\" : \"tester\" , \"unit_id\" : \"auth-fe\" , \"stream\" : \"stdout\" | \"stderr\" , \"chunk\" : \"FAIL tests/login.spec.ts > invalid pwd\\n\" } 注: 高频事件。前端 LiveAgentStream 用 RAF 批渲染,避免每条都触发 reactive 更新。 ⑤ integration.result · L2 结果 L2 { \"run_id\" : \"run-int-20260513-1031\" , \"ok\" : false , \"failures_count\" : 1 , \"duration_ms\" : 8421 } ⑥ triage.result · BugTriager 归因完成 L2 { \"category\" : \"contract_mismatch\" , \"confidence\" : 0.86 , \"affected_units\" : [ \"auth-be\" , \"auth-fe\" ] , \"will_auto_apply\" : true } ⑦ human_intervention_needed · 需要人类介入 L2 / L1 blocked { \"reason\" : \"low_confidence_triage\" | \"unit_blocked\" | \"l2_exhausted\" | \"prd_conflict\" , \"suggestion\" : \"SDD token 续期策略未规定,需要决策\" , \"context\" : { /* triage.json / unit info / etc */ } } 消费者: Dashboard 弹紫色 toast。 ⑧ project.shipped · 项目交付 L2 { \"final_rev\" : { \"prd\" : 3 , \"sdd\" : 5 } , \"total_duration_ms\" : 1_842_000 , \"total_tokens\" : 487_321 , \"units\" : 12 } UI 反应: 整页 confetti 5s + L2 状态卡变深绿。 ⑨ project.exhausted · L2 预算耗尽 L2 { \"rounds\" : 3 , \"last_triage\" : { /* triage.json */ } , \"next_action\" : \"awaiting_human_decision\" } Agent 持久化事件(6 类)· P-8 触发 由 P-8 callAgentWithPersistence 流程发出,用于 Dashboard 渲染「Agent at step 3/5」+ 崩溃恢复对话框。 ⑩ agent.started · Agent 调用开始 { \"agent\" : \"architect\" , \"call_id\" : \"call-20260513-1031-7f3a\" , \"unit_id\" : null , \"input_summary\" : \"mode=generate, prd@rev3\" } 发出时机: statePath 写入 status=running 后立即 ⑪ agent.todolist · Agent 写完 todolist { \"agent\" : \"architect\" , \"call_id\" : \"call-...\" , \"items\" : [ { \"id\" : \"step-001\" , \"description\" : \"读 prd.html\" , \"status\" : \"todo\" } , { \"id\" : \"step-002\" , \"description\" : \"识别功能列表\" , \"status\" : \"todo\" } , ... ] } 消费者: Dashboard 展开 UnitDrawer / Agent panel 显示子任务列表 ⑫ agent.step · 子任务进度更新 { \"agent\" : \"architect\" , \"call_id\" : \"call-...\" , \"item_id\" : \"step-003\" , \"from\" : \"doing\" , \"to\" : \"done\" , \"progress_pct\" : 60 , \"current_desc\" : \"为 OAuth 拆 3 个 Unit\" } 消费者: Dashboard 顶部 Agent 卡片显示「Architect: 3/5」+ desc ⑬ agent.done · 成功完成 { \"agent\" : \"architect\" , \"call_id\" : \"call-...\" , \"duration_ms\" : 42_318 , \"tokens_used\" : 8_421 , \"output_ref\" : \"agent-state/snapshots/call-..-out.json\" } ⑭ agent.error · 异常退出 { \"agent\" : \"coder\" , \"call_id\" : \"call-...\" , \"code\" : \"SANDBOX_VIOLATION\" | \"TIMEOUT\" | \"AGENT_CRASH\" , \"message\" : \"试图写 ../profile-be/code/server.js\" , \"recoverable\" : true } ⑮ resume_decision_needed · 重启后等决策 { \"agent\" : \"architect\" , \"call_id\" : \"call-...\" , \"last_step\" : 3 , \"total\" : 5 , \"paused_at\" : \"2026-05-13T10:42:13Z\" , \"resume_token\" : \"rt-...\" , \"options\" : [ \"resume_from_step\" , \"restart_fresh\" , \"abort\" ] } 发出时机: P-8 recoverOnBoot 扫到 status=running(即上次进程被杀) UI 反应: Dashboard 顶部 banner 加紫色「⚠ 上次启动有 N 个 Agent 中断,需要决策」 事件全表 id event 层 频率 关键 payload 1 prd.updated L3 低 rev, changeSummary, hasConflict 2 sdd.updated L2/L3 低 rev, mode, units_diff 3 unit.state L1 中 unit_id, from, to, retries 4 agent.stream L1/L2 高(千 / 项目) chunk 5 integration.result L2 低(1-3 次 / 项目) ok, failures_coun"},{"f":"api.html","title":"REST API + 骨架","h1":"REST API + 代码骨架","headings":["端点清单","Request / Response 示例","routes/devloop.js 骨架","挂载到 server/index.js","认证"],"defs":[],"content":"端点清单 方法 路径 说明 触发的 Loop POST /api/devloop/projects 创建项目,传入初始需求 L3 启动 POST /api/devloop/projects/:pid/requirement 追加人类反馈,触发 PRD 增量更新 L3 POST /api/devloop/projects/:pid/run 启动 / 续跑 LoopController L1 + L2 GET /api/devloop/projects/:pid/state 返回 units.json + 当前 loop 阶段 UI 轮询 GET /api/devloop/projects/:pid/stream SSE — 实时推送 Agent 输出 / 状态变更 UI 流式 POST /api/devloop/projects/:pid/sign-off 人类显式 sign-off,状态置 shipped-final L3 终止 POST /api/devloop/projects/:pid/abandon 放弃项目(保留数据,从列表移除) L3 终止 Request / Response 示例 create requirement (L3) state SSE stream # 创建项目 # 严格按 CLAUDE.md 警告:先写 /tmp/req.json,再 -d @file curl -X POST http://localhost:3010/api/devloop/projects \\ -H \"Content-Type: application/json\" \\ -d @/tmp/create.json # /tmp/create.json — 单行紧凑 { \"company_id\" : \"c-lemonv-001\" , \"name\" : \"Auto Auth System\" , \"requirement\" : \"需要一个邮箱密码登录的系统\" } # 响应 { \"project_id\" : \"p-731042\" , \"status\" : \"prd_drafting\" , \"stream_url\" : \"/api/devloop/projects/p-731042/stream\" } # 追加新需求 curl -X POST http://localhost:3010/api/devloop/projects/p-731042/requirement \\ -H \"Content-Type: application/json\" \\ -d @/tmp/feedback.json # /tmp/feedback.json { \"text\" : \"再加 GitHub OAuth\" , \"origin\" : \"yibo\" } # 响应(即刻返回,PM 是异步跑) { \"accepted\" : true , \"queued_rev\" : 3 , \"eta_seconds\" : 30 } # 查询状态 curl http://localhost:3010/api/devloop/projects/p-731042/state { \"project_id\" : \"p-731042\" , \"prd_rev\" : \"3\" , \"sdd_rev\" : \"5\" , \"current_loop\" : \"L1\" , \"units\" : [ { \"id\" : \"auth-be\" , \"status\" : \"passed\" } , { \"id\" : \"auth-fe\" , \"status\" : \"testing\" } , { \"id\" : \"oauth-be\" , \"status\" : \"pending\" } ] , \"l2_rounds\" : 0 , \"l2_budget\" : 3 , \"agents_active\" : [ \"tester:auth-fe\" ] } # SSE 流(浏览器或 curl --no-buffer) curl -N http://localhost:3010/api/devloop/projects/p-731042/stream event: unit.state data: {\"unit_id\":\"auth-fe\",\"from\":\"coding\",\"to\":\"testing\"} event: agent.stream data: {\"agent\":\"tester\",\"unit_id\":\"auth-fe\",\"chunk\":\"PASS tests/login.spec.ts > valid creds\\n\"} event: agent.stream data: {\"agent\":\"tester\",\"unit_id\":\"auth-fe\",\"chunk\":\"FAIL tests/login.spec.ts > invalid pwd\\n\"} event: unit.state data: {\"unit_id\":\"auth-fe\",\"from\":\"testing\",\"to\":\"failed\",\"retries\":1} event: prd.updated data: {\"rev\":4,\"changeSummary\":\"+ NFR: session timeout 1h\"} routes/devloop.js 骨架 // server/routes/devloop.js · NEW · ~200 行 const express = require ( 'express' ); const { loopController } = require ( '../src/core/loop-controller' ); const { docStore } = require ( '../services/doc-store' ); const { unitRegistry } = require ( '../services/unit-registry' ); const { sseBroker } = require ( '../services/sse-broker' ); const router = express. Router (); router. post ( '/projects' , async (req, res) => { const { company_id, name, requirement } = req.body; const pid = await loopController. createProject ( { company_id, name } ); loopController . onHumanFeedback (pid, { text: requirement, origin: req.user?.id } ); res. json ( { project_id: pid, status: 'prd_drafting' , stream_url: `/api/devloop/projects/ ${pid} /stream` } ); } ); router. post ( '/projects/:pid/requirement' , async (req, res) => { loopController. onHumanFeedback (req.params.pid, req.body); res. json ( { accepted: true } ); } ); router. get ( '/projects/:pid/state' , (req, res) => { res. json (loopController. getState (req.params.pid)); } ); router. get ( '/projects/:pid/stream' , (req, res) => { sseBroker. subscribe (req.params.pid, req, res); } ); module.exports = router; 挂载到 server/index.js 仅 1 行改动: // server/index.js 第 90 行后追加 app. use ( '/api/devloop' , require ( './routes/devloop' )); 认证 沿用 routes/auth.js 的 JWT 中间件: POST 类端点:必须带 Authorization: Bearer <jwt> ,校验 req.user.company_id 与 path 中的 cid 匹配。 GET state / stream:同上。 Open API 调用(无人类 cookie,纯 server-to-server):使用现有的 OPEN_API_KEYS 环境变量 + X-API-Key header。 初版不做 RBAC(任何登录用户都能创建项目)。多用户隔离靠 cid。这是已知简化,P5 再加。"},{"f":"arch-acp-bridge.html","title":"ACP Bridge · LemonBoss 桥接","h1":"ACP Bridge · LoopController ↔ LemonBoss agent-manager","headings":["① 现状梳理 · 既有 LemonBoss 链路","② callAgentWithPersistence 的完整实现","③ agent-manager.callAgent 需要扩展的字段","④ SessionPool key 设计","Coder/Tester 特殊:workDir 必须含 unit_id","⑤ 流式输出 + tool_use 中断处理","⑥ 错误传播链","⑦ 性能考量","⑧ 安全边界","⑨ 一行总结","⑩ 关联"],"defs":[],"content":"① 现状梳理 · 既有 LemonBoss 链路 LemonBoss 已经有完整的 ACP / Master Agent / SessionPool 链路: 关键 insight: DevLoop 的 LoopController 不需要新建 agent-manager 等价物。它 复用 既有 agent-manager.callAgent,只在上面套一层「持久化包装」(P-8 的 callAgentWithPersistence)。 ② callAgentWithPersistence 的完整实现 这是 LoopController 调任何 Agent 的 唯一入口 。它包住 agent-manager.callAgent 同时管 M-10/M-12/M-13。 // server/src/core/loop-controller.js · 节选 const { agentManager } = require ( './agent-manager' ); const { unitRegistry } = require ( '../../services/unit-registry' ); const { docStore } = require ( '../../services/doc-store' ); const { bugTracker } = require ( '../../services/bug-tracker' ); const { sseBroker } = require ( '../../services/sse-broker' ); const { timelineLogger } = require ( '../../services/timeline-logger' ); const { loadAllTools } = require ( '../tools/tool-registry' ); const toolRegistry = loadAllTools (); // 单例 async function callAgentWithPersistence (name, input) { const { pid, unit_id } = input; const call_id = genCallId (); const startedAt = Date.now (); // 1. 注入运行态 (M-10) await writeRuntimeState (pid, name, { agent: name, status: 'running' , call_id, unit_id, current_step: 0 , total_steps: 0 , started_at: new Date (). toISOString (), } ); timelineLogger. append (pid, { source: 'system' , kind: 'agent_started' , ref: `agent/ ${name} / ${call_id} ` , summary: ` ${name} started` , } ); sseBroker. emit (pid, 'agent.started' , { agent: name, call_id, unit_id } ); // 2. 注入 memory + 构建 prompt const systemPrompt = await buildPromptWithMemory (name, pid); // 3. 构建 ctx (传给所有工具的运行时上下文) const ctx = { pid, agent: name, unit_id, call_id, loopController: module. exports , docStore, unitRegistry, bugTracker, timelineLogger, sseBroker, companyDir: companyDirOf (pid), projectDir: projectsDir (pid), unitDir: unit_id ? unitDirOf (pid, unit_id) : null , } ; // 4. 调既有 agent-manager (这是关键的\"桥\"那一行) let result; try { result = await agentManager. callAgent ( { agentName: `devloop- ${name} ` , // 与 _registry.json 中 id 对齐 client: companyDirOf (pid), // SessionPool key 之一 deploy: pid, // 另一个 key workDir: ctx.unitDir || ctx.projectDir, systemPrompt, input, // 透传 PMInput / ArchitectInput / etc toolPolicy: getToolPolicy (name), // 从 _registry.json 读 toolRegistry, toolCtx: ctx, // 关键:工具上下文注入 timeoutMs: getTimeout (name), // PM 10min,Coder 5min, etc onChunk: (chunk) => { // 流式回调 sseBroker. emit (pid, 'agent.stream' , { agent: name, unit_id, chunk } ); } , } ); // 5. 成功收尾 await writeRuntimeState (pid, name, { status: 'done' , last_output_ref: await snapshot (result, pid, call_id), updated_at: new Date (). toISOString (), } ); timelineLogger. append (pid, { source: name, kind: 'agent_done' , ref: `agent/ ${name} / ${call_id} ` , summary: ` ${name} done in ${Date.now() - startedAt} ms` , payload: { tokens: result.tokens_used } , } ); sseBroker. emit (pid, 'agent.done' , { agent: name, call_id, ...result } ); return result; } catch (e) { // 6. 失败处理 (P-8 状态机) await writeRuntimeState (pid, name, { status: 'error' , error: { code: e.code || 'AGENT_CRASH' , message: e.message, stack: e.stack } , updated_at: new Date (). toISOString (), } ); timelineLogger. append (pid, { source: 'system' , kind: 'agent_error' , ref: `agent/ ${name} / ${call_id} ` , summary: e.message, } ); sseBroker. emit (pid, 'agent.error' , { agent: name, call_id, code: e.code, message: e.message } ); throw e; } } ③ agent-manager.callAgent 需要扩展的字段 LemonBoss 既有 agent-manager.callAgent 已经接受 agentName / client / deploy / workDir 等。要支持 DevLoop 需要 追加 3 个字段 : 新字段 类型 含义 改 agent-manager 的代码量 toolRegistry Map<string, ToolDef> 本次注册的 79 个工具(来自 loadAllTools) +5 行:透传给 SessionPool → Protocol toolCtx object 工具调用时的 ctx(pid · sseBroker · etc) +3 行:同上透传 onChunk function 流式回调(SSE 用) +2 行:传给 stream-executor 合计 ~10 行修改 server/src/core/agent-manager.js 。 不改 agent-manager 的 public API ,所有新字段都是可选;不传时行为同既有。 ④ SessionPool key 设计 既有 SessionPool key = (agentName, client, deploy, workDir) 。DevLoop 把它映射成: SessionPool key 段 DevLoop 填入 agentName devloop-pm / devloop-architect "},{"f":"arch-components.html","title":"三个新组件","h1":"三个新组件","headings":["① LoopController","关键方法","主循环骨架","② DocStore","API 面","HTML 校验规则","③ UnitRegistry","API 面","状态转换合法性","三者协作","外部依赖"],"defs":[],"content":"① 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.json schema 校验 + 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 \\ to pending coding testing failed passed blocked pending — ✓ coding — ✓ testing — ✓ ✓ failed ✓ — ✓ passed ✓ (L2 reset) — blocked ✓ (人类干预) — 非法转换 → 抛异常 + SSE 错误事件,便于排查。 三者协作 外部依赖 包 用途 是否新增 cheerio 解析 / 校验 HTML 新增 (~120KB) @sinclair/typebox JSON schema 校验 已存在 better-sqlite3 — 已存在(不需要新表) 不引入新数据库;项目状态全部落文件。Dashboard 用 SSE 推送,不需要轮询 DB。"},{"f":"arch-git-versioning.html","title":"Git Versioning","h1":"Git Versioning · 每次 Agent 完工 = 一个 commit","headings":["① 现状 vs 提议","之前(无 git)","提议(每项目一个 repo)","② Repo 范围决策","③ 落盘后的目录","④ .gitignore 模板","⑤ Commit 时机表","⑥ 谁调 git · 集中控制","⑦ GitVault · C-18 服务方法","⑧ P-10 · 自动提交流程","项目级 tag 时机","⑨ 与 timeline / changelog / bugs 的关系","git log","timeline.ndjson","changelog.ndjson","⑩ 失败恢复 · 新能力","⑪ P-8 状态机的协同","⑫ 推 remote · v2 留口","⑬ 安全考量","⑭ P0 工时增量","⑮ 实战命令","⑯ 关联"],"defs":["GitVault","Process.GitCommit"],"content":"① 现状 vs 提议 之前(无 git) 文件 mtime 是唯一时间戳 history/ 目录是 ad-hoc 归档 没有 atomic snapshot 回滚靠 fs.rename + 手动拷贝 retry 之间无法 diff(旧文件被覆盖) 项目导出 = 拷整个目录 提议(每项目一个 repo) 每个 Agent 完工 → git commit shipped → git tag 整个 retry 链是 git log(可 diff 任意两次) 崩 → git reset 干净恢复 导出 → git archive 未来可加 git push 到 GitHub(v2) ② Repo 范围决策 方案 位置 优 劣 A · 每项目一个 ← 选这个 data/companies/{cid}/projects/{pid}/.git 隔离强 · 删项目就删 repo · 导出干净 · 可分别 push 不同 remote repo 数量随项目增长(每个 ~10-50MB) B · 一个公司一个 data/companies/{cid}/.git 跨项目共享对象 跨项目混在 git log;不易导出单项目 C · 全局一个 data/.git 对象去重最大 所有 company 混;权限灾难 D · 主仓库 lemonboss/.git 不增 repo 污染 LemonBoss 主提交历史;不可接受 选 A 的核心理由:DevLoop 项目本质是 临时工作空间 ,shipped 后或归档或导出,独立 repo 让生命周期边界清晰。 ③ 落盘后的目录 data/companies/{cid}/projects/{pid}/ ├── .git/ # NEW · 整个项目的 git 历史 ├── .gitignore # NEW · 项目创建时初始化 ├── project.json ├── prd/ → tracked ├── sdd/ → tracked ├── units.json → tracked ├── integration/spec.json → tracked ├── timeline.ndjson → tracked (append-only,git OK) ├── bugs.ndjson → tracked ├── changelog.ndjson → tracked ├── units/ → tracked (code + tests + contract) ├── integration/runs/ → tracked (result.json + triage.json) ├── agent-state/ → .gitignore (高频写,运行态) ├── history/ → .gitignore (git 本身就是 history) └── stream.log → .gitignore (SSE 流,7 天滚动) ④ .gitignore 模板 # LemonBoss DevLoop 项目 .gitignore (项目创建时落盘) # 高频写的运行态,不需要版本 agent-state/ # 真冗余 (git 本身就是 history) history/ # 大日志 stream.log units/*/runs/*/stdout.txt integration/runs/*/process.log integration/runs/*/http-trace.ndjson.gz # Node / 依赖 node_modules/ *.log .DS_Store .cache/ .tmp/ .coverage/ # 测试中间文件 units/*/.vitest-cache/ # 但保留: result.json triage.json contract.json 等小 JSON 由 GitVault.init 工具在 git init 后立即写入;首个 commit 就含 .gitignore。 ⑤ Commit 时机表 事件 commit msg 模板 触发位置 files added 项目创建 init: project {pid} · {requirement 摘要 30 字} LoopController.createProject 末尾 project.json + .gitignore PM 写完 PRD prd: rev {N} · {change_summary} PM Agent 成功返回后 prd/** + changelog.ndjson + timeline 增量 Architect 生成 SDD sdd: rev {N} · {units_count} units · {summary} Architect generate 成功后 sdd/** + units.json + integration/spec.json + changelog/timeline Architect patch sdd: patch · {affected_units 列表} Architect patch 成功后 仅改的 sdd/mvcp/{x}.html + units.json (hash 更新) Coder 实现 code({unit_id}): implement · attempt {N} Coder 成功 + Tester 绿才 commit units/{id}/code/** + contract.json + coder.log Coder retry 通过 code({unit_id}): retry → green · attempt {N} Tester 绿(attempt > 1) 同上 Tester 写测试 合并到上一条 commit 同 Coder commit 一起 tests/ + runs/{}/result.json 一起 BugTriager 归因 triage({run_id}): {category} · confidence {n} Triager 写完 triage.json 后 integration/runs/{}/triage.json + bugs.ndjson 增量 Integrator 跑 integ({run_id}): {ok|fail} · {cases_passed}/{cases_total} Integrator 完工 integration/runs/{}/result.json + process.log(如不在 ignore) Unit reset (L2 修复后) 不 commit ,下一次 Coder commit 时一起 — — 项目 shipped ship: prd@{prd_rev} sdd@{sdd_rev} + git tag shipped-{rev} LoopController.shipProject —(tag only) 项目 sign-off chore: sign-off by {user_id} + git tag final-{rev} POST /sign-off project.json (status=shipped-final) 项目 abandon chore: abandon · {reason} + git tag abandoned POST /abandon project.json (status=abandoned) 关键决策: 失败不 commit (避免噪音 + 失败的代码不该留在 git 主线)。Tester 红 → Coder retry 时旧代码被工作区覆盖,反正没人需要回到失败态。 ⑥ 谁调 git · 集中控制 关键原则: Agent 不动 git 。LoopController 在 callAgentWithPersistence 的 success 分支统一调 GitVault.commit。 这样 Agent 沙箱继续严格(不允许 Bash 调 git),但项目状态 真实有版本 。 ⑦ GitVault · C-18 服务方法 server/services/git-vault.js module.exports .gitVault = { // 初始化(项目创建时) init (pid: string , opts?: { name?: string , email?: string , // 默认 \"DevLoop <devloop@lemonboss.local>\" initialBranch?: string , // 默认 \"main\" } ): Promise < { ok: boolean , head: string } >, // 主提交入口 (LoopController 调) commit (pid: string , { kind: 'init' | 'prd' | 'sdd' | 'code' | 'triage' | 'integ' | 'ship' | 'chore' , unit_id?: string , // code 时填 run_id?: string , // triage / integ 时填 summary: string , // 简短描述 files?: string [], // 限定 add 的路径 (默认 git add -A 受 .gitignore) author?: { name, email } , //"},{"f":"arch-mapping.html","title":"对 LemonBoss 的映射","h1":"对 LemonBoss 现有架构的映射","headings":["能力映射总表","分层视图","为什么 P1~P4 不抽公共 retry 函数","不动的边界"],"defs":[],"content":"能力映射总表 能力 新组件 对应现有模块 改动 Agent 启动与会话复用 — packages/acp-core/protocol/ClaudeCliProtocol.js + server/src/core/session-pool.js REUSE 多 Agent 调用 facade — server/src/core/agent-manager.js · master-session.js REUSE 任务 → 步骤拆分 — server/src/tools/orchestrate-tool.js REUSE DAG 依赖解析 — server/src/core/scheduler.js ( getExecutionGroups ) REUSE Pipeline 描述结构 — server/services/pipeline-service.js REUSE · units.json 复用 step 结构 重试 + 反思 retryWithReflection() 写在 loop-controller.js 内 参考 server/services/evolution-loop.js (PRAE) 思路,但 不抽公共 NEW · P5 再考虑合并 ACP / Claude CLI 协议层 — packages/acp-core/ REUSE Agent 模板注册 6 个新模板 system/templates/_registry.json NEW 三层 Loop 编排 LoopController server/src/core/loop-controller.js NEW PRD/SDD 文档存储 DocStore server/services/doc-store.js NEW Unit 状态机 UnitRegistry server/services/unit-registry.js NEW HTTP / SSE 入口 /api/devloop/* server/routes/devloop.js → 挂在 server/index.js NEW Web 看板 DevLoopDashboard.vue 放在 web/src/views/ ,复用 PipelineEditor DAG 渲染 NEW 结果持久化根 — data/companies/{cid}/projects/{pid}/ REUSE · 沿用 company-scoped 文件根 进程沙箱 / 端口管理 — server/src/engine/process-guard.js REUSE WS 流式 — server/routes/claude-chat-ws.js REUSE · DevLoop SSE 走 HTTP 复用同套帧 分层视图 三色块代表三种改动类型;越靠下越基础,越靠上越业务。 红 新增 · 灰 复用 · 黄 补丁 · 黑 底层依赖 为什么 P1~P4 不抽公共 retry 函数 EvolutionLoop 当前实现 PRAE (Perceive → Reason & Plan → Execute → Evaluate → Reflect),里面有「失败 → 反思 → 重试」逻辑。直觉上 L1 可以共用,但 初版决定不抽 : 避免回归风险: EvolutionLoop 跑的是 Skill 进化,目前在生产里运行。动它意味着要把 Skill 进化的回归一起做。 反思 prompt 差异大: Skill 进化的反思是「Skill 评分低,怎么改 prompt」;L1 的反思是「单测失败,代码哪里错」。共用会让 prompt 模板变成「上帝模板」,反而难维护。 80 行不算多: P2 在 loop-controller.js 内重写一份 retryWithReflection 也就 80 行,DRY 不必这么早追。 // P2 直接在 server/src/core/loop-controller.js 写一份新的 async function retryWithReflection ( unit , budget ) { let failures = [] ; for ( let i = 0 ; i < budget; i++) { await agentManager . callAgent ( 'coder' , { spec: unit.spec, history: failures } ); const r = await agentManager . callAgent ( 'tester' , { unit } ); if (r. ok ) return { ok: true , retries: i } ; failures. push (r. failures ); } return { ok: false , reason: \"budget_exceeded\" } ; } // P5 视实测情况再决定是否与 evolution-loop 合并到 packages/ 公共目录 这条改动同步影响 plan-migration.html 的 P2 阶段:retry 函数从「抽公共」改为「直接新写」。 不动的边界 本次改造绝不触碰: ACP 协议本身( packages/acp-core/ ) Master / Orchestrator 已有行为(仅注册新的 6 个 Agent 模板) WebSocket 路由( /ws/claude ) SkillsMarket / Arena 路径下任何文件 认证 / 用户系统( routes/auth.js , routes/users.js )"},{"f":"arch-project-root.html","title":"Project Root","h1":"Project Root · 项目根目录是什么 · 在哪建","headings":["① 「项目」的定义","② 三种项目模式","③ project.json 加 root_path 字段","④ 路径解析服务(新组件)","⑤ 三个创建入口","5.1 REST · 主入口","5.2 CLI · 命令行","5.3 UI · Dashboard 的「+ NEW PROJECT」","⑥ 创建流程 · 三模式各自 step-by-step","⑦ 各组件如何引用 root_path","⑧ memory · 始终在 company 级","⑨ 跨主机 / 跨机器场景","⑩ 创建后 LoopController 接管的统一接口","⑪ project.json 完整示例","⑫ 路径白名单(沙箱安全)","⑬ 实战命令","⑭ 工时增量","⑮ 关联"],"defs":[],"content":"① 「项目」的定义 一个 DevLoop 项目 = 一个目录 + 一个 project.json + 一棵 git 历史。这个目录就是 project root ,是整个 DevLoop 流程的 唯一锚点 。 所有路径都从 root 派生: {project_root}/ # 这就是「根」 ├── .git/ # GitVault init ├── .gitignore ├── project.json # M-1 元数据 + root_path 自身 ├── prd/ # PM 写 ├── sdd/ # Architect 写 ├── units.json ├── units/{unitId}/ # Coder/Tester 写 ├── integration/ # Integrator/Triager 写 ├── agent-state/ # P-8 持久化 ├── timeline.ndjson · bugs.ndjson · changelog.ndjson ├── stream.log └── history/ # 归档 (.gitignore) 所有 Agent、所有工具、LoopController、GitVault 都按 {project_root}/... 找文件。 没有任何代码硬编码 data/companies/... 。这是关键设计。 ② 三种项目模式 mode root_path git repo v1 支持 典型场景 sandbox (默认) 自动生成: {server_data_dir}/companies/{cid}/projects/{pid}/ 自动 git init ✅ v1 不确定代码归宿 / 内部 demo / 学习 / 隔离实验 linked-new 用户指定空目录或不存在的目录,比如 ~/code/auto-todo 自动 git init 到该路径 ✅ v1 已知代码最终归宿;想生成就直接落盘那里;之后可 git push linked-existing 用户指定已有 git repo 的子目录,比如 ~/code/my-app/services/auth/ 复用上级 git repo ⚠️ v2 给已存在的项目 增量 加新模块;冲突风险高,需要 git stash 等保护 v1 推荐:sandbox 是默认。 linked-new 当作 power user 选项 — 它给你「自动 push 到 GitHub」的可能性,但也要承担「DevLoop 误覆盖了你 ~/code/ 下别的东西」的风险(沙箱机制阻止越界,但 user-chosen path 越界相对沙箱更难定义)。 ③ project.json 加 root_path 字段 M-1 ProjectMeta 现在含 root_path(绝对路径)和 mode: const ProjectMeta = Type. Object ( { id: Type. String ( { pattern: '^p-[a-z0-9]{6}$' } ), company_id: Type. String (), name: Type. String (), // 新增字段 mode: Type. Union ( [ Type. Literal ( 'sandbox' ), Type. Literal ( 'linked-new' ), Type. Literal ( 'linked-existing' ), ] ), root_path: Type. String (), // 绝对路径,所有派生路径的源 status: Type. Union ( [ Type. Literal ( 'prd_drafting' ), Type. Literal ( 'running' ), Type. Literal ( 'blocked' ), Type. Literal ( 'shipped' ), Type. Literal ( 'shipped-final' ), Type. Literal ( 'abandoned' ), ] ), ... } ); 所有组件读 path 都走 resolveProjectPath(pid) → project.root_path ,而不是拼 data/companies/.../ 。 ④ 路径解析服务(新组件) server/services/path-resolver.js · 新增,~80 行 module. exports .pathResolver = { // 单例索引: pid → root_path // 在 LoopController 启动时 walk(data/companies/) 建立 // 加: linked 项目通过 ~/.devloop/linked-projects.json 维护索引 init (): Promise < void >, resolveProjectRoot (pid: string ): string , // 绝对路径,unknown pid 抛错 resolveUnitDir (pid, unitId): string , resolvePrdSite (pid): string , resolveSddSite (pid): string , resolveAgentState (pid, agent): string , resolveMemoryDir (pid, agent): string , // 注意:memory 总在 company 级,不在 root 下 resolveTimelineLog (pid): string , ... // 反向 findProjectByPath (abs: string ): { pid, root } | null , // linked 项目注册 registerLinkedProject ( { pid, root_path, mode } ): Promise < void >, unregisterLinkedProject (pid): Promise < void >, } ; 关键: 所有用 projectsDir(pid) 的地方改成 pathResolver.resolveProjectRoot(pid) 。这样切换 mode 是 路径透明 的。Agent 的 ctx.projectDir 注入也走这个。 linked 项目索引 ~/.devloop/linked-projects.json 形如: { \"p-731042\" : { \"root_path\" : \"/Users/yibo/code/auto-todo\" , \"mode\" : \"linked-new\" , \"company_id\" : \"c-lemonv\" , \"registered_at\" : \"2026-05-13T11:31:00Z\" } } sandbox 项目 不需要 注册(按惯例从 data/companies/.../projects/ 扫描即可)。 ⑤ 三个创建入口 5.1 REST · 主入口 POST /api/devloop/projects { \"company_id\" : \"c-lemonv\" , \"name\" : \"Auto TODO\" , \"requirement\" : \"做一个 TODO API + Vue 前端\" , // 新增 - 可选,默认 mode=sandbox \"mode\" : \"sandbox\" | \"linked-new\" | \"linked-existing\" , \"target_path\" : \"/Users/yibo/code/auto-todo\" // linked-* 时必填 } // 响应 { \"project_id\" : \"p-731042\" , \"mode\" : \"linked-new\" , \"root_path\" : \"/Users/yibo/code/auto-todo\" , \"status\" : \"prd_drafting\" , \"stream_url\" : \"/api/devloop/projects/p-731042/stream\" } 5.2 CLI · 命令行 # 文件: scripts/devloop-new.js (NEW, ~80 行) # sandbox 模式 (默认) node scripts/devloop-new.js \\ --name \"Auto TODO\" \\ --requirement \"做一个 TODO API + Vue 前端\" # 等价于 mode=sandbox, root 自动到 data/.../projects/p-XXXXXX/ # 输出: 🍋 Project p-731042 created (sandbox) # Root: /Users/yibo/Documents/lemonAI/lemonboss/data/companies/c-default/projects/p-731042 # linked-new 模式 node scripts/devloop-new.js \\ --path ~/code/auto-todo \\ --name \"Auto TODO\" \\ --requirement \"做一个 TODO API + Vue 前端\" # 输出: 🍋 Project p-731042 created (lin"},{"f":"arch-tool-registry.html","title":"Tool Registry · 工具运行时实现","h1":"Tool Registry · 79 个工具如何变成可调用的 JS","headings":["① 现状 · 三层工具栈","② 79 个工具按目录组织","③ 标准 wrapper 模板(直接照抄)","④ tool-registry.js 改造","⑤ Agent 模板的 toolPolicy","⑥ Bash 工具的 jsonGuard 中间件","⑦ ctx 的内容(ToolRegistry 注入)","⑧ 错误传播","⑨ ToolRegistry 给 Claude CLI 的注入","⑩ Phase 0 验收(接 plan-migration)","⑪ 工时估算(Phase 0)","⑫ 关联页面"],"defs":[],"content":"① 现状 · 三层工具栈 Claude CLI 调工具走 三层 ,越靠下越底层: 关键点:本次 不动 L1 / L2 。只在 L2 注册层 追加 79 个 L3 工具定义。Claude CLI 进程启动时被告知\"你可以调这些 + 既有 Read/Write/Bash\",调用时 ToolRegistry 路由到对应 execute 函数。 ② 79 个工具按目录组织 server/src/tools/ ├── tool-registry.js # 既有,加载所有工具 (REUSE) ├── read-tools.js # 既有 (REUSE) ├── call-agent-tool.js # 既有 (REUSE) ├── orchestrate-tool.js # 既有 (REUSE) ├── marketplace-tools.js # 既有 (REUSE) ├── train-skill-tool.js # 既有 (REUSE) └── devloop/ # NEW 整个目录 ├── persistence-tools.js # 持久化 5 件套 (M-10/11/12/13) │ ├── write_todolist │ ├── update_todo_item │ ├── update_runtime_state │ ├── timeline_append │ └── record_memory ├── doc-tools.js # PM / Architect 文档工具 │ ├── bootstrap_prd_site / bootstrap_sdd_site │ ├── write_chapter / write_mvcp_page │ ├── archive_doc │ ├── diff_html / diff_units │ ├── echo_diff │ ├── update_search_index │ ├── validate_prd_site / validate_sdd_consistency │ └── hash_contract ├── coder-tools.js # Coder 工具 │ ├── contract_bridge │ ├── sandbox_check │ ├── append_coder_log │ └── (jsonGuard 是 Bash 中间件,不在工具表) ├── tester-tools.js # Tester 工具 │ ├── parse_vitest │ ├── hint_builder │ ├── bug_open │ ├── bug_transition │ └── bug_list ├── integrator-tools.js # Integrator 工具 │ ├── port_picker │ ├── spawn_units │ ├── health_wait │ ├── run_case │ ├── parse_placeholder │ └── collect_stderr └── triager-tools.js # BugTriager 工具 ├── extract_evidence (→ verify_evidence) ├── category_classifier ├── diff_contract_vs_impl └── find_similar_bug 合计 79 个工具中:持久化 5 + doc 11 + coder 3 + tester 5 + integrator 6 + triager 4 = 34 个独立函数 (很多 Agent 复用,所以签名是 79 但唯一实现是 34)。 每个 ≈ 30-80 行 JS ,总实现量 ≈ 1500-2500 行。 ③ 标准 wrapper 模板(直接照抄) 每个工具都按下面这个模式注册到 tool-registry.js : // server/src/tools/devloop/persistence-tools.js · 节选 const { Type } = require ( '@sinclair/typebox' ); const fs = require ( 'fs' ); const path = require ( 'path' ); const { projectsDir } = require ( '../../services/paths' ); /** * 每个工具导出一个 ToolDefinition 对象: * { name, description, inputSchema, execute } * 由 tool-registry.js 收集后注入 Claude CLI 启动参数。 */ module. exports .write_todolist = { name: 'write_todolist' , description: '初始化 agent-state/{agent}-todo.json,写入完整 todolist' , inputSchema: { type: 'object' , required: [ 'items' ] , properties: { items: { type: 'array' , items: { type: 'object' , required: [ 'id' , 'description' ] , properties: { id: { type: 'string' , pattern: '^step-\\\\d{3}$' } , description: { type: 'string' } , depends_on: { type: 'array' , items: { type: 'string' } } } } } } } , // ctx 由 ToolRegistry 注入,含 pid · agent · call_id · sessionId · loopController · sseBroker async execute ( { items } , ctx) { const { pid, agent, call_id } = ctx; const p = path. join ( projectsDir (pid), 'agent-state' , ` ${agent} -todo.json` ); const todolist = { agent, call_id, created_at: new Date (). toISOString (), items: items. map (it => ( { ...it, status: 'todo' } )), current: items[ 0 ]?.id || null , progress_pct: 0 , } ; fs. mkdirSync (path. dirname (p), { recursive: true } ); fs. writeFileSync (p, JSON.stringify (todolist, null , 2 )); ctx.sseBroker?. emit (pid, 'agent.todolist' , { agent, call_id, items: todolist.items, } ); return { ok: true , total: items. length , path: p } ; } } ; 三个不变量 每个 wrapper 都要遵守: inputSchema 是 JSON Schema (Claude CLI native tool_use 格式),不是 TypeBox(虽然 TypeBox 可以序列化到 JSON Schema,方便统一来源) execute 接收 第二个参数 ctx ,含 pid · agent · call_id · sessionId 等运行时上下文(由 LoopController 注入) 返回值必须是 纯 JSON 可序列化 对象,给 Claude 看(不能含 Buffer / Stream / Function) ④ tool-registry.js 改造 LemonBoss 既有 server/src/tools/tool-registry.js , 只加载新模块即可 : // server/src/tools/tool-registry.js · 仅追加 1 段 const { readDirImports } = require ( './_util' ); function loadAllTools () { const registry = new Map (); // 既有(REUSE): register (registry, require ( './read-tools' )); register (registry, require ( './call-agent-tool' )); register (registry, require ( './orchestrate-tool' )); register (registry, require ( './marketplace-tools' )); register (registry, require ( './train-skill-tool' )); // NEW: devloop 工具批量加载 for"},{"f":"arch-workflow.html","title":"运行时工作流","h1":"运行时工作流","headings":["端到端时序图","同一时刻三个 Loop 的并发关系","SSE 事件清单","错误与中止"],"defs":[],"content":"端到端时序图 同一时刻三个 Loop 的并发关系 L1 内部允许并行(多 Unit),但 L2 始终串行 (一个项目同时只能有一个 Integrator 跑)。L3 触发会等待当前 L2 跑完再合并 PRD。 场景 处理 L1 跑了一半,L3 新需求到达 当前 L1 跑完再合并(保证状态一致),合并后增量 reset L2 跑了一半,L3 新需求到达 L2 跑完再合并;若 L2 失败也照常合并(人类可能本来就是为了改这个) L2 跑了一半,又收到一个 L3 反馈 队列化,按时间序处理 多项目并行 每项目独立 LoopController 实例,互不干扰 SSE 事件清单 Dashboard 通过 GET /api/devloop/projects/:pid/stream 订阅。事件类型: event 触发时机 payload 关键字段 prd.updated PM 写 PRD 完成 rev, changeSummary sdd.updated Architect 写 SDD 完成 rev, mode (generate/patch) unit.state UnitRegistry 状态转换 unit_id, from, to, retries agent.stream Agent stdout/stderr 流式 agent, unit_id, chunk integration.result Integrator 返回 ok, failures[] triage.result BugTriager 返回 category, confidence, affected_units human_intervention_needed confidence < 0.5 或 blocked reason, suggestion project.shipped L2 通过 final_rev project.exhausted L2 修复预算耗尽 last_triage 错误与中止 LoopController 顶层 try/catch : Agent 调用超时(> 10min): kill 进程,retry 一次,再失败置 Unit blocked。 DocStore 写失败(磁盘满 / 权限): 整个项目挂起,SSE 告警人类。 未捕获异常: 记录到 stream.log ,发 fatal 事件, 不 自动重启(避免雪崩)。"},{"f":"data-documents.html","title":"四类文档","h1":"四类文档","headings":["派生关系","四个文档逐一","谁能写谁","归档策略"],"defs":[],"content":"派生关系 四个文档逐一 prd.html sdd.html units.json runs/result.json <!-- PM Agent 维护,每次人类反馈追加 section --> <article data-doc = \"prd\" data-rev = \"3\" > <h1> Auto Auth System </h1> <section data-rev = \"1\" > <h3> 登录 </h3> <p> 用户名密码登录,会话 7 天有效。 </p> </section> <section data-rev = \"3\" data-origin = \"yibo@2026-05-13\" > <h3> OAuth 登录 </h3> <p> 支持 GitHub OAuth。Token 刷新 ≤ 5s。 </p> </section> <footer data-changelog > <p> rev 1 · init · 邮箱密码登录 </p> <p> rev 2 · yibo · 加个人主页 </p> <p> rev 3 · yibo · GitHub OAuth </p> </footer> </article> <!-- Architect 生成;嵌入可机读 unit 元数据 --> <article data-doc = \"sdd\" data-derived-from = \"prd@rev3\" > <section data-unit-id = \"auth-be\" data-type = \"be\" data-depends = \"\" > <h3> Unit · auth-be </h3> <p> 提供登录 / 注册 / token 校验端点 </p> <pre data-contract = \"openapi\" > POST /api/auth/login request: { email, password } response: 200 { token } | 401 { error:\"invalid_credentials\" } </pre> <ul data-acceptance > <li> 有效凭据 → 200 + JWT </li> <li> user 不存在 → 401 invalid_credentials </li> </ul> </section> <!-- 更多 section ... --> </article> { \"project_id\" : \"p-001\" , \"prd_rev\" : \"3\" , \"sdd_rev\" : \"5\" , \"units\" : [ { \"id\" : \"auth-be\" , \"type\" : \"be\" , \"status\" : \"passed\" , \"depends_on\" : [] , \"retries\" : 1 , \"retry_budget\" : 5 , \"contract_hash\" : \"sha256:7f3a...\" , \"last_run\" : \"run-20260513-0731\" } , { \"id\" : \"auth-fe\" , \"type\" : \"fe\" , \"status\" : \"testing\" , \"depends_on\" : [ \"auth-be\" ] , \"retries\" : 0 , \"retry_budget\" : 5 , \"contract_hash\" : \"sha256:9c1d...\" } ] , \"l2_rounds\" : 0 , \"l2_budget\" : 3 } { \"run_id\" : \"run-20260513-0731\" , \"loop\" : \"L1\" , \"unit_id\" : \"auth-be\" , \"agent\" : \"tester\" , \"attempt\" : 2 , \"summary\" : { \"total\" : 8 , \"passed\" : 7 , \"failed\" : 1 } , \"ok\" : false , \"failures\" : [ { \"test\" : \"login.invalid-pwd\" , \"expected\" : 401 , \"got\" : 500 , \"stack\" : \"TypeError: ...\" , \"hint\" : \"先查 user 再 bcrypt 比对\" } ] , \"next_action\" : \"retry_coder\" , \"retry_budget_left\" : 3 , \"duration_ms\" : 1245 } 谁能写谁 文档 写入者 读取者 prd.html PM (通过 DocStore) Architect · Dashboard sdd.html Architect (通过 DocStore) Coder · Tester · Integrator · Triager · Dashboard units.json Architect (字段) + UnitRegistry (status) 所有 integration/spec.json Architect Integrator units/{id}/code/** Coder Tester · Integrator · Triager (只读) units/{id}/contract.json Coder Tester · 其他 Unit Coder · Triager units/{id}/tests/** Tester Integrator runs/{id}/result.json Tester (L1) / Integrator (L2) LoopController · Triager · Dashboard triage.json BugTriager LoopController · Architect history/** DocStore.archive() Dashboard diff 视图 agent-state/{agent}.json LoopController + Agent 工具 Dashboard · recoverOnBoot agent-state/{agent}-todo.json Agent (write_todolist · update_todo_item 工具) Dashboard · getProgress agent-state/snapshots/* LoopController retry 时回放 · recover timeline.ndjson TimelineLogger (所有 source append) Dashboard 时间线 · audit memory/devloop/{agent}/*.md Agent (record_memory 工具) Agent 启动时注入 prompt 归档策略 每次 prd / sdd / units.json 被写时,旧版本自动移到 history/ ,命名规则: history/prd-rev-{N}.html history/sdd-rev-{N}.html history/units-rev-{N}.json Dashboard 的 PRD diff 视图直接读 history 做 section diff。无需 git。"},{"f":"data-layout.html","title":"文件空间布局","h1":"文件空间布局","headings":["全景目录树","命名规则","沙箱边界(写白名单)","磁盘占用估算","多项目共享"],"defs":[],"content":"全景目录树 data/ └── companies/ └── {cid}/ ├── agents/{agentId}/ # 复用:现有 agent 沙箱 ├── conversations/ # 复用 ├── memory/ # 复用根 │ └── devloop/ # NEW · DevLoop Agent 跨项目长期记忆 (M-11) │ ├── pm/{topic}.md │ ├── architect/{topic}.md │ ├── coder/{topic}.md │ └── ... └── projects/ # NEW · LoopController 根 └── {pid}/ ├── .git/ # NEW · per-project git repo (见 arch-git-versioning) ├── .gitignore # NEW · 排除 agent-state · history · stream.log · runs stdout ├── project.json # 项目元数据 (M-1) ├── prd/ # NEW · PM 写的多页 PRD 站点 (M-3) │ ├── index.html │ ├── reading-guide.html │ ├── assets/{shell.css, shell.js, search-index.json} │ ├── product/{background, personas, original-req, scope, success-metrics, glossary}.html │ ├── feature/feature-tree.html │ ├── flow/user-flows.html │ └── prototype/proto-{viewName}.html ├── sdd/ # NEW · Architect 写的多页 SDD 站点 (M-4) · 待 Phase 3 详定 │ ├── index.html │ ├── assets/{shell.css, shell.js, search-index.json} │ └── mvcp/{model, view, control, process}.html ├── units.json # Architect 字段 + UnitRegistry status (M-2) ├── timeline.ndjson # 全流程统一日志 append-only (M-13) ├── bugs.ndjson # NEW · 跨 run bug 台账 (M-14) ├── changelog.ndjson # NEW · 版本变更流 (M-15) ├── agent-state/ # NEW · Agent 持久化运行态 │ ├── pm.json # M-10 AgentRuntimeState │ ├── pm-todo.json # M-12 AgentTodolist │ ├── architect.json │ ├── architect-todo.json │ ├── coder.json # 单 agent 同时只跑一个 unit │ ├── coder-todo.json │ ├── ... (tester, integrator, bug-triager) │ └── snapshots/ # 输入/输出快照,call_id 索引 │ ├── {call_id}-in.json │ └── {call_id}-out.json ├── history/ # 全部 rev 归档 │ ├── prd-rev-001.html │ ├── prd-rev-002.html │ ├── sdd-rev-001.html │ └── units-rev-001.json ├── units/ │ ├── auth-be/ │ │ ├── spec.json │ │ ├── code/ # Coder 输出 │ │ │ ├── server.js │ │ │ ├── auth-service.js │ │ │ └── ... │ │ ├── tests/ # Tester 输出 │ │ │ └── login.spec.ts │ │ ├── contract.json # Coder 写 │ │ ├── coder.log # Coder 自己的决策日志 │ │ └── runs/ │ │ └── {runId}/ │ │ ├── result.json │ │ └── stdout.txt │ ├── auth-fe/... │ └── profile-be/... ├── integration/ │ ├── spec.json # Architect 写 │ └── runs/ │ └── {runId}/ │ ├── result.json # Integrator 写 │ ├── triage.json # BugTriager 写 │ └── process.log └── stream.log # SSE 客户端视角日志 (7 天滚动) 红 = 新增目录 · 灰 = 已存在并复用 命名规则 占位符 格式 示例 {cid} company id (UUID) c-lemonv-001 {pid} p- + 时间戳后 6 位 p-731042 {unitId} {feature}-{type} auth-be · profile-fe {runId} run-{YYYYMMDD-HHMM} run-20260513-0731 L2 runId run-int-{YYYYMMDD-HHMM} run-int-20260513-1031 沙箱边界(写白名单) Agent cwd 可写路径 PM projects/{pid}/ prd.html · history/prd-* Architect projects/{pid}/ sdd.html · units.json (字段) · integration/spec.json · history/sdd-* · history/units-* Coder projects/{pid}/units/{unitId}/ code/** · contract.json · coder.log Tester projects/{pid}/units/{unitId}/ tests/** · runs/{runId}/** Integrator projects/{pid}/ integration/runs/{runId}/** BugTriager projects/{pid}/ integration/runs/{runId}/triage.json 沙箱由 Claude CLI 的 cli.sandbox.writeAllowList 配置强制执行;越权写直接被工具层拒绝。 磁盘占用估算 项目规模 Unit 数 典型大小 小 3-5 ~5 MB(含日志) 中 10-20 ~30 MB 大 50+ ~150 MB 主要开销在 runs/*/stdout.txt (每次 vitest 完整输出)。如需节省,启用 project.json.compressOldRuns: true 自动 gzip 14 天前的 run 目录。 多项目共享 同一 company 下不同项目之间默认 隔离 。如需共享代码(如公共 utils),约定: 在 companies/{cid}/shared/ 放置共享库 Unit spec 通过 spec.shared_deps: [\"@shared/utils\"] 显式声明 Coder 沙箱里 shared/ 是 只读 挂载,Coder 不能改 P1~P4 不实现 shared,留给 P5 视实际需要再加。"},{"f":"loops-l1.html","title":"L1 · Unit Loop","h1":"L1 · Unit Loop","headings":["状态机","状态字典","调度与并行","Coder → Tester 握手协议","Coder 写入","Tester 读取 + 写入","重试策略 — retryWithReflection","预算与降级"],"defs":[],"content":"状态机 状态字典 state 谁写入 含义 退出条件 pending Architect 创建 unit 时 spec 已就绪,等待 LoopController 调度 有空闲 Coder Agent → coding coding LoopController Coder 正在写 code/ 下文件 Coder 返回 success → testing testing LoopController Tester 正在 vitest run 全绿 → passed · 红 → failed failed Tester 本轮单测失败,retries++ retries < budget → coding · 否则 → blocked passed Tester 单测全绿,等所有 sibling Unit 跟进 所有 Unit passed → L2 gate blocked LoopController 耗尽 retry,无法自愈,需 L3 介入 人类反馈 → 重置回 pending 调度与并行 LoopController 复用 server/src/core/scheduler.js 的 getExecutionGroups() :根据 depends_on 把所有 pending Unit 分成一波一波。同一波之间并行,跨波串行。 // units.json 节选 { \"units\" : [ { \"id\" : \"auth-be\" , \"depends_on\" : [] } , { \"id\" : \"profile-be\" , \"depends_on\" : [] } , { \"id\" : \"auth-fe\" , \"depends_on\" : [ \"auth-be\" ] } , { \"id\" : \"profile-fe\" , \"depends_on\" : [ \"profile-be\" ] } ] } // scheduler.getExecutionGroups() → // wave 1: [auth-be, profile-be] (parallel) // wave 2: [auth-fe, profile-fe] (parallel) Coder → Tester 握手协议 两个 Agent 通过文件系统握手,不直接通信。这让任意一个 Agent 死掉都不会丢上下文。 Coder 写入 units/{id}/code/** — 源码 units/{id}/contract.json — 暴露给下游的契约 units/{id}/coder.log — 决策日志(供 retry 时回顾) Tester 读取 + 写入 读 units/{id}/code/** 读 units/{id}/spec.json 推导测试用例 写 units/{id}/tests/** 写 units/{id}/runs/{runId}/result.json 重试策略 — retryWithReflection 从 evolution-loop.js 抽出共用函数。每次 Tester 失败,把失败原因、上次 Coder 的决策日志、原 spec 一起喂回 Coder,让模型「反思」上次错在哪。 async function retryWithReflection ( unit , budget ) { let failures = [] ; for ( let i = 0 ; i < budget; i++) { const coderInput = { spec: unit.spec, history: failures } ; await agentManager . callAgent ( 'coder' , coderInput); const r = await agentManager . callAgent ( 'tester' , { unit } ); if (r. ok ) return { ok: true , retries: i } ; failures. push (r. failures ); } return { ok: false , reason: \"budget_exceeded\" } ; } 预算与降级 R1 风险联动: 每个 unit 全局 retry 预算 = 5(可在 units.json 用 retry_budget 覆盖)。耗尽后置为 blocked ,LoopController 不会自动进 L2,而是发 SSE 事件等待人类介入(L3 接管)。"},{"f":"loops-l2.html","title":"L2 · Integration Loop","h1":"L2 · Integration Loop","headings":["状态机","L2 闸门触发条件","失败归因 — BugTriager 输出契约","归因 → 回写 → 重置 三步","① 归因","② 回写","③ 重置","L2 修复预算","集成测试套件从哪来?"],"defs":[],"content":"状态机 L2 闸门触发条件 不是「所有 Unit 都跑过一次」,而是「所有 Unit 的 当前 contract_hash 与依赖关系下,状态都是 passed 」。这避免 SDD 改过之后某个旧版 Unit 还停留在 passed 而事实上契约已变。 function l2GateOpen ( project ) { return project.units. every (u => u. status === \"passed\" && u. contract_hash === currentSDD (project). contractHashFor (u. id ) ); } 失败归因 — BugTriager 输出契约 BugTriager 必须输出一个机读 JSON,LoopController 据此自动决定回滚范围。Triager 不直接改文件,只发「建议」。 { \"run_id\" : \"run-int-20260512-1031\" , \"root_cause\" : \"auth-be returns 500 on invalid pwd, expected 401; auth-fe error handler relies on 401\" , \"category\" : \"contract_mismatch\" , \"affected_units\" : [ \"auth-be\" , \"auth-fe\" ] , \"proposed_sdd_patch\" : { \"target_section\" : \"#unit-auth-be\" , \"op\" : \"update\" , \"text\" : \"明确:登录失败必须 401 with {error:'invalid_credentials'}\" } , \"confidence\" : 0.82 } 归因 → 回写 → 重置 三步 ① 归因 BugTriager 读 stderr + diff + units.json + sdd.html,产 triage.json 。 confidence < 0.5 时上报 L3 让人类裁决。 ② 回写 Architect 接收 proposed_sdd_patch ,用 section 级 DOM 操作改 sdd.html ,归档旧版到 history/sdd-rev-N.html 。 ③ 重置 UnitRegistry 把 affected_units 的 status 改回 pending ,并自动重算 contract_hash ,触发 L1 新一轮。 L2 修复预算 同一项目 每个 PRD rev 内最多触发 3 次 L2 修复轮回。超出后整个项目挂起,发 SSE 事件 project.l2_exhausted ,等待人类(L3)发新需求或手动干预。 为什么是 3: 实测 Claude Code Agent 三轮内还修不好的,要么是契约描述本身有歧义(PRD 缺陷),要么是技术选型有根本问题。再加轮次只会浪费 Token。 集成测试套件从哪来? 这是一个设计决策(见 风险与待决 · R3 )。初版选 方案 A : 方案 谁写集成测试 优点 缺点 A · 选中 Architect 在生成 SDD 时同步生成 integration/spec.json 少一个 Agent,集成测试与 SDD 同步演化 Architect 职责变重 B 独立 IntegrationAuthor Agent 关注点分离 多一个 Agent,多一次握手 C Integrator 自己写 读取代码后再写最贴合 容易写一个「永远会通过」的 trivial test"},{"f":"loops-l3.html","title":"L3 · Human Feedback Loop","h1":"L3 · Human Feedback Loop","headings":["状态机","触发渠道","① REST","② Web UI","③ 自动信号","增量 Unit 算法","PRD 增量合并示例","什么时候算「项目结束」"],"defs":[],"content":"状态机 触发渠道 ① REST POST /api/devloop/projects/:pid/requirement JSON body 走 CLAUDE.md 推荐的 -d @/tmp/req.json 方式。 ② Web UI DevLoopDashboard 右上角 + NEW REQUIREMENT ,弹出 textarea。 ③ 自动信号 L1 blocked 或 L2 exhausted 时,LoopController 主动发 SSE,由前端弹「请补充信息」对话框。 增量 Unit 算法 Architect 对比新旧 units.json ,标记三种变化: 变化类型 判断 处置 added 新 unit.id 不在旧 list status = pending,加入 L1 队列 changed 同 id, contract_hash 变化 status 回退到 pending,加入 L1 队列(旧代码归档到 history/ ) removed 旧 id 不在新 list 整个 unit 目录归档到 history/removed/ ,从 DAG 摘除 unchanged id 在且 hash 不变 保持 passed, 无须重跑 。这是增量化的关键 PRD 增量合并示例 PM Agent 不重写整个 PRD,只追加 <section data-rev=N> ,并维护 doc 级 data-rev 自增。这样人类能直观看到「这一轮反馈引入了什么」。 prd.html · rev 2 prd.html · rev 3 (after feedback) <article data-doc = \"prd\" data-rev = \"2\" > <section data-rev = \"1\" > 用户名密码登录,会话 7 天有效 </section> <section data-rev = \"2\" > 个人主页:显示用户名 / 邮箱 </section> </article> <article data-doc = \"prd\" data-rev = \"3\" > <section data-rev = \"1\" > ... </section> <section data-rev = \"2\" > ... </section> <section data-rev = \"3\" data-origin = \"yibo@2026-05-12T10:31\" > + 新增:支持 GitHub OAuth 登录 + NFR:3rd-party token 刷新 ≤ 5s </section> </article> 什么时候算「项目结束」 L3 没有自动退出条件。结束信号来自人类的显式动作: 显式 sign-off: POST /api/devloop/projects/:pid/sign-off → 项目 status 置 shipped-final ,停止接收新需求。 归档: 30 天无人类反馈,自动归档但保留所有产物。 放弃: POST .../abandon ,整个项目目录加 .abandoned 标记,从 Dashboard 列表移除。"},{"f":"loops-overview.html","title":"三层 Loop 模型","h1":"三层 Loop 模型","headings":["对照矩阵","嵌套关系(含数学描述)","三个 Loop 的色彩 token","Emerald · #0E8C5A","Amber · #C77C2C","Violet · #6E48C3","下一步阅读"],"defs":[],"content":"对照矩阵 L1 Unit L2 Integration L3 Human 触发 units.json 有 pending 所有 Unit 状态 = passed POST /devloop/requirement 主导 Agent Coder + Tester Integrator + BugTriager + Architect PM + Architect 每轮产物 code/ · tests/ · runs/result.json integration/runs/ {id} /result.json + triage.json prd.html rev+1 · sdd.html rev+1 退出 单测全绿 → unit passed 集成全绿 → project shipped 人类 sign-off 失败处理 retry coder ≤ N 归因 → 改 SDD → 回 L1 对话化追问,PRD 加 section 预算 每 unit retry ≤ 5 每项目 L2 修复 ≤ 3 轮 无限(人类驱动) 典型时长 1-3 分钟 / unit 2-10 分钟 / 轮 分钟 — 小时 嵌套关系(含数学描述) 三个 Loop 严格嵌套: L1 触发于 L2 内部 ,L2 触发于 L3 内部。即: // 用伪代码表达嵌套关系 while ( human . hasFeedback ()) { // L3 pm . patchPRD (); architect . regenSDD (); while (! project . integrationGreen ()) { // L2 while ( unitsPending ()) { // L1 parallel ( unitsPending (), runUnit ); } const r = integrator . run (); if (!r. ok ) { const tri = triager . analyze (r); architect . applyPatch (tri. sdd_patch ); units . reset (tri. affected ); } } } 三个 Loop 的色彩 token 同一套颜色在 PRD 文档、看板 UI、Agent 日志、状态徽章里一致复用,便于跨界面识别当前所处的 Loop 层级。 L1 · Unit Emerald · #0E8C5A 短周期 / 高频 / 低单价。日志里所有 L1 事件都用这个色描边。 L2 · Integration Amber · #C77C2C 中周期 / 中频 / 中单价。Dashboard 中间状态栏专色。 L3 · Human Violet · #6E48C3 低频 / 高单价 / 决策性。看板 header / Hero 用此色作 accent。 下一步阅读 详细状态机在以下三页: L1 · Unit Loop — 单 Unit 的写码—跑测—重试 L2 · Integration Loop — 集成失败的归因与文档回写 L3 · Human Feedback Loop — 增量需求的并入"},{"f":"ops-buglist.html","title":"Ops · Buglist","h1":"Buglist · 跨 run 跨 unit 聚合视图","headings":["UI Wireframe","过滤与分组","数据来源","状态机参考","关联视图"],"defs":[],"content":"UI Wireframe /devloop/p-731042/ops/bugs 过滤与分组 维度 取值 说明 status open / fixing / fixed / verified / wontfix / duplicate 顶部 chip 选择,多选 OR severity critical / high / medium / low 右上 filter unit_id 从 units.json 列表 右上 filter,下拉 source unit_test / integration_test / triage / human filter since 时间窗口 filter(默认全部) 排序默认按 discovered_at 倒序,可切「未结束优先」(open + fixing 顶置)。 数据来源 本页是 bugs.ndjson 的纯 视图 ,不写入。所有操作通过: 用户动作 触发的 API 列表显示 GET /api/devloop/projects/:pid/bugs(内部调 bugTracker.list ) 点击展开 GET /api/devloop/projects/:pid/bugs/:id/history 人类标 wontfix POST .../bugs/:id/transition body={to:'wontfix',note} 人类合并 duplicate POST .../bugs/:id/transition body={to:'duplicate',duplicate_of} 状态机参考 完整流转规则见 P-9 · Bug 状态机 。本页 UI 必须严格按状态机渲染:非法转换按钮 disabled。 关联视图 点击 unit_id chip → 跳到 UnitDrawer 看该 Unit 详情 点击 run_id → 跳到对应 result.json 内嵌视图 点击 fix_refs 中的 run_id → 看修复时的 Tester 跑分 右上「↗ 时间线」→ 跳 Loop Log"},{"f":"ops-changelog.html","title":"Ops · Changelog","h1":"Changelog · 全部版本变更流","headings":["UI Wireframe","每条目录包含","交互","与 timeline.ndjson 的关系","关联视图"],"defs":[],"content":"UI Wireframe /devloop/p-731042/ops/changelog 每条目录包含 字段 来源 用途 kind changelog.ndjson 颜色 / 图标 rev + ts + by 同上 标题行 trigger 同上 「为什么变」(human_feedback / triage_patch / init) summary 同上 一句话变更 diff 同上 展开后看 +/~/− section · unit 列表 archive_ref 同上 点开跳归档 HTML 交互 顶部 chip 切换 all / prd / sdd / units(kind filter) 每条点击展开 diff 详情 「↗ archive」按钮在新 tab 打开归档 HTML(history/{kind}-rev-{N}.html) 「↗ compare」按钮跳 PrdDiffPanel 与上一 rev 对比 双击两个条目 → 跨 rev 三方对比(this vs prev vs current) 与 timeline.ndjson 的关系 changelog 是 timeline 的 子集 + 增强 : timeline 包含 所有 事件(agent 启动、unit 状态、bug 开关...) changelog 只关心 文档版本变更 ,但带有 cheerio diff 详细对比 两者由 DocStore 同步写入,绝不脱节 关联视图 跳 PRD diff 面板 看具体 section 对比 跳 Loop Log 看 rev 变更触发了哪些 L1/L2 跑 跳 Buglist 看 rev 变更产生 / 解决了哪些 bug"},{"f":"ops-loop-log.html","title":"Ops · Loop Log","h1":"Loop Log · L1 / L2 / L3 分组时间线","headings":["UI Wireframe","分组规则","交互","L1 高频降采样","L2 / L3 强调","关联视图"],"defs":[],"content":"UI Wireframe /devloop/p-731042/ops/loops 分组规则 本视图从 timeline.ndjson 读取所有事件,按 kind 字段分桶: 分到 kind 包含 L3 project_created · prd_drafted · sdd_generated · human_feedback · sign_off · abandon L2 integration_started · integration_done · integration_failed · triage_* L1 unit_state · agent_started (Coder/Tester) · agent_done · unit_passed · unit_failed 无 agent_started (PM/Architect/Integrator/Triager) → 归到对应 loop 交互 顶部时间窗口选择:最近 1h / 24h / 全部 / 自定义 点击任一事件 → 底部详情面板展开(如 wireframe) 事件类型 chip 过滤(暂时隐藏某些 kind) 缩放 / 平移时间轴(d3-zoom) 「↗ Buglist」联动:选中 L2 事件 → 自动 filter buglist 到该 run 的 bug 「↗ Changelog」联动:选中 PRD/SDD 事件 → 跳 changelog 对应条 L1 高频降采样 L1 事件可能一分钟几十条。视图层做 降采样 : 同 unit 同 attempt 内多条 agent.step → 折叠为一个圆点 同 wave 内连续 passed → 横线表示批次 retry 失败用红色圆点,覆盖在绿色之上 点击折叠批次 → 展开详细 L2 / L3 强调 L2 是 amber pulse 动画(如果 currently running),失败用 red 高亮。L3 事件用 violet 大圆点+ 上方文字标签,便于看「为什么改」。 关联视图 Buglist · 看具体 bug Changelog · 看 rev 变更 Progress · 看实时进度 Dashboard · 主看板"},{"f":"ops-progress.html","title":"Ops · Progress","h1":"Progress · 项目实时进度","headings":["UI Wireframe","聚合算法","数据来源","更新频率","无项目时的空态","关联视图"],"defs":[],"content":"UI Wireframe /devloop/p-731042/ops/progress 聚合算法 percent 加权求和(来自 C-14 文档): 阶段 权重 判定 ok PRD 10 prd.html 存在且校验通过 SDD 20 sdd.html 存在且 units.json 一致 Units (L1) 50 按 passed / total 比例 L2 整合 15 最近一次 L2 ok=true Ship 5 project.status=shipped 合计 100 — ETA 基于历史项目均值 + 当前阶段剩余项数线性外推;blocked 状态时 ETA = ∞ 显示「等人介入」。 数据来源 头部数字: C-14 LoopController.getProgress(pid) 一次性返回 Active Agents:聚合自 agent-state/*.json + agent-state/*-todo.json Recent events: timelineLogger.tail(pid, 10) SSE:订阅 unit.state · agent.step · integration.result 等事件,自动刷新 更新频率 区域 触发 方式 大数字 / breakdown SSE 任一关键事件 debounce 500ms 重算 Active Agents SSE agent.step 立即 Recent events SSE 任一事件 append 顶部,溢出剔旧 ETA 30s 周期 低频,避免抖动 无项目时的空态 页面打开时若没有任何 active project:显示「No project running」+ 列出最近 3 个项目让用户选;若有 shipped 项目,加 「✓ Demo: TODO Auth - shipped」可作为复现起点。 关联视图 Dashboard · 主看板(同源 + DAG 视图) Loop Log · 时间线展开 Buglist · bug 排查 Changelog · 版本变更"},{"f":"plan-code-lifecycle.html","title":"Code Lifecycle","h1":"生成代码的去向","headings":["① 三种归宿","② 沙箱代码的生命周期","③ Export API · v1 实现","④ 导出包里的 README 模板","⑤ 沙箱代码的保留期","⑥ v2 路径 · 自动 PR","⑦ 跨项目共享 utils","⑧ 关联"],"defs":[],"content":"① 三种归宿 方案 位置 优 劣 A · 留沙箱(v1 默认) data/companies/{cid}/projects/{pid}/units/** 零集成成本;可被复现脚本读;隔离强 不能直接运行在 LemonBoss 主进程;需手动导出 B · 手动 export 导出 人类点「导出」按钮 → 下载 zip 简单;人类可控;适合外部交付 没有 git 历史;不接 CI/CD C · 自动开 GitHub PR 主仓库的 feature branch 真集成;CI 自动跑;review 流程 需 GitHub token / 权限;branch 命名冲突;merge 策略复杂 v1 决策:A + B。 C 留到 v2 视真实需要再说。本套文档对外宣称\"生成代码\",但 v1 不承诺自动合并到生产仓库。 ② 沙箱代码的生命周期 ③ Export API · v1 实现 // server/routes/devloop.js · 新增 router. post ( '/projects/:pid/export' , requireAuth, async (req, res) => { const { pid } = req.params; const { include = 'code' } = req.body; // code / code+tests / all const archiver = require ( 'archiver' ); res. setHeader ( 'Content-Type' , 'application/zip' ); res. setHeader ( 'Content-Disposition' , `attachment; filename=\" ${pid} .zip\"` ); const zip = archiver ( 'zip' ); zip. pipe (res); const root = projectsDir (pid); if (include. includes ( 'code' )) zip. directory (path. join (root, 'units' ), 'units' ); if (include. includes ( 'tests' )) zip. directory (path. join (root, 'integration' ), 'integration' ); if (include. includes ( 'docs' )) { zip. directory (path. join (root, 'prd' ), 'prd' ); zip. directory (path. join (root, 'sdd' ), 'sdd' ); } // 附 README 说明项目结构 zip. append ( buildReadme (pid), { name: 'README.md' } ); await zip. finalize (); } ); archiver 在 LemonBoss 既有依赖( \"archiver\": \"^7.0.1\" ),零新增包。 ④ 导出包里的 README 模板 # Project {pid} 由 LemonBoss DevLoop v1 生成 · {timestamp} ## 结构 ``` units/ ├── todo-be/code/ 后端代码 ├── todo-be/tests/ Tester 写的 vitest ├── todo-be/contract.json 接口契约 ├── todo-fe/... integration/spec.json 端到端用例 prd/ 原始需求文档 (HTML 站点) sdd/ 架构设计文档 (HTML 站点) ``` ## 跑起来 ```bash cd units/todo-be npm install # 各 Unit 独立装包 PORT=3010 node code/server.js # 另起前端 (如有) cd units/todo-fe npm install npm run dev ``` ## 测试 ```bash cd units/todo-be vitest run # 跑 Tester 写的 spec ``` ## 集成 ```bash # 在项目根 node integration/runner.js # 启动所有 be + 跑 spec.json ``` ## 合到主仓库(手动) 没有自动 PR 流程。建议: 1. cp -r units/*/code/ <your-repo>/services/ 2. 整理目录结构、合并 package.json 3. 跑 lint / 格式化(DevLoop 不强制风格) 4. 自己开 PR ## License 继承 LemonBoss 主仓库。 ⑤ 沙箱代码的保留期 shipped 项目的 units/ 默认 永久保留 ,因为: 占用极小(中项目 ~30MB 含日志) 反推决策需要原始代码 + coder.log memory 系统会引用具体 Unit 实现做风格参考 清理策略(写在 project.json ): 字段 默认 含义 compressOldRuns false 14 天前的 runs/*/stdout.txt 自动 gzip keepCodeMonths 12 shipped 12 个月后归档 code/ 到 history/archived-units/ autoExportOnShip false shipped 时自动生成 export.zip 放项目目录 ⑥ v2 路径 · 自动 PR v2 会加 POST .../export-to-github ,参数: { \"repo\" : \"yibo/lemonboss-services\" , \"branch\" : \"devloop/p-731042\" , \"target_path\" : \"services/todo/\" , \"commit_message\" : \"feat: add TODO API + UI (via DevLoop)\" , \"open_pr\" : true , \"github_token\" : \"${secret}\" } 逻辑:调 gh CLI / @octokit/rest 创建 branch + 提交 + 开 PR。 v1 不做 。 ⑦ 跨项目共享 utils 常见担忧:5 个项目都写一遍 JWT 中间件,沙箱内重复实现。v1 接受这个浪费;v2 视情况引入 data/companies/{cid}/shared/ ,约定: Architect 检测多 Unit 共享 util 时,建议拆 shared-* Unit shared Unit 的 contract.json 跨项目稳定 Coder 读 shared 时 只读 contract ,不复制实现 v1 简化:每个项目重复实现 utils。memory 系统会记\"这次写了 JWT mw, 下次可以照样\",但不会自动拆 shared。 ⑧ 关联 data-layout · units/ 在文件系统里的位置 plan-migration · 各 Phase 何时开始关心代码归宿 复现指南 · 外部 Claude Code 怎么从沙箱代码 + docs 复现整套"},{"f":"plan-cost-concurrency.html","title":"Cost & Concurrency","h1":"成本与并发预算","headings":["① 单次调用 Token 估算","② 典型项目总成本估算","2.1 小项目(TODO API · demo)","2.2 中项目(Auth + TODO + Profile · 5 feature)","2.3 默认 token_budget = 5M 的隐含假设","③ 美元成本(参考 2026-05 价格)","④ 并发预算","⑤ 单项目墙钟估算","⑥ 监控指标","⑦ 降本策略"],"defs":[],"content":"① 单次调用 Token 估算 Agent 模型 输入 ≈ 输出 ≈ 典型 / 单次调用 备注 PM Sonnet 4.6 4K (memory + prd) 8K (新 PRD 章节 HTML) 12K incremental ≈ 5K Architect generate Opus 4.7 15K (PRD + sdd + memory) 20K (4 MVCP 页 + JSON) 35K 含全套 SDD Architect patch Opus 4.7 8K 3K 11K 仅改一段 Coder implement Sonnet 4.6 6K (sdd + upstream contracts) 4K (code + contract.json) 10K per Unit Coder retry Sonnet 4.6 8K (含 history) 3K (差量 patch) 11K 反思加 ~2K Tester Sonnet 4.6 5K 4K (vitest spec + result) 9K per Unit Integrator Sonnet 4.6 8K (units + spec) 3K 11K L2 单轮 BugTriager Opus 4.7 12K (含 code + logs) 3K (triage.json) 15K 归因质量决定 L2 收敛 ② 典型项目总成本估算 2.1 小项目(TODO API · demo) 3 个 Unit · 1 个 PRD rev · 1-2 个 L2 round。 调用 次数 Token PM init 1 12K Architect generate 1 35K Coder × 3 unit × 1.5 attempt(平均) 4.5 45K Tester × 3 unit × 1.5 attempt 4.5 40K Integrator 1 11K BugTriager(如失败 1 次) 0.5 8K 合计 14.5 ≈ 150K tokens 2.2 中项目(Auth + TODO + Profile · 5 feature) 8 个 Unit · 2 个 PRD rev · 2 个 L2 round。 调用 次数 Token PM init + incremental × 1 2 17K Architect generate + patch × 1 2 46K Coder × 8 unit × 2 attempt 16 165K Tester × 8 unit × 2 attempt 16 145K Integrator × 2 2 22K BugTriager × 1 1 15K 合计 39 ≈ 410K tokens 2.3 默认 token_budget = 5M 的隐含假设 5M 是单项目预算上限,能撑: ~33 个小项目(150K each) ~12 个中项目(410K each) 1 个大型迭代(多人多反馈、20+ Unit) 到达 80% 预算时发 SSE warning;100% 直接挂起。 ③ 美元成本(参考 2026-05 价格) 按 Anthropic 公开价格估算(input/output 分别计),中项目场景: 模型 输入 ($/MTok) 输出 ($/MTok) 本项目消耗 美元 ≈ Sonnet 4.6 ~$3 ~$15 input 220K · output 100K $2.2 Opus 4.7 ~$15 ~$75 input 50K · output 25K $2.6 中项目合计 — — 410K tokens ≈ $4.8 小项目合计 — — 150K tokens ≈ $1.7 数字是 ballpark。实际取决于 caching 命中率(SessionPool 复用降低 input 重复成本)和具体功能复杂度。 ④ 并发预算 三层并发上限: 层级 上限 原因 实现 项目内 L1 并行 ≤ 5 Unit 同时跑 每个 Claude CLI ~ 100MB RAM · 1 CPU core scheduler.getExecutionGroups 自动分波,每波 ≤ 5 项目内 L2 串行 1 个 Integrator 实例 避免端口冲突 + 状态混乱 LoopController 内部用 Promise 队列 单机系统并发 ≤ 20 个 ACP session 合理 RAM 占用 ≈ 2GB SessionPool 全局上限(新增 maxSessions 配置) 同 company 项目并发 ≤ 3 项目同时跑 避免单用户挤占 LoopController 入口检查 Bash 进程 (Coder/Tester/Integrator 起的) ≤ 50 系统总数 避免 fork bomb process-guard 计数 ⑤ 单项目墙钟估算 项目规模 串行墙钟 L1 并行后 瓶颈 小(3 Unit) ~15 min ~8 min Architect generate + Integrator spawn 中(8 Unit) ~45 min ~20 min L1 × 2 attempt × Coder + Tester 大(20 Unit + 3 L2 round) ~3h ~70 min L2 整套 + 重写 「并行后」假设 5 并行;scheduler 拓扑可能限制少于 5(fe 必须等 be 完)。北极星目标「小项目 < 30 min」对应中项目场景,余量充足。 ⑥ 监控指标 在 ops-progress 已经展示了 token_used/budget。补充几个隐含指标: 指标 看哪 告警阈值 Tokens / Unit 项目 token_used / units_count > 100K / Unit → 项目过度反思 Retry rate units.json sum(retries) / count > 2 平均 → Coder skill 退化或 spec 不清 L2 修复轮次 l2_rounds ≥ 2 → 该 PRD 有歧义 Agent 调用墙钟 P95 timeline.duration_ms 统计 > 5 min → 模型卡或 prompt 过长 SessionPool 利用率 active_sessions / maxSessions > 80% → 限流 Bash 子进程数 process-guard tracked > 30 → 等回收 ⑦ 降本策略 策略 预期降幅 难度 SessionPool 复用 + 5min cache TTL 20-30% input token 免费(既有 ACP 行为) Tester Sonnet 改 Haiku(如果反思质量够) 40% Tester 部分 低 PM/Coder/Tester 用 Sonnet · 只 Architect/Triager 用 Opus 已是默认 — Memory 注入降到 top 3(从 top 5) 5-10% input 低,1 行配置 跳过 Tester 写文档代码(README/类注释) 10% output 低 cache busting:相同 unit 跑 1 次后用缓存(无 retry 时) 大幅,针对回归 中(需 contract_hash 索引)"},{"f":"plan-migration.html","title":"最小改造路径","h1":"最小改造路径","headings":["五阶段总览","P0 · Bootstrap(~2.5 周 / 11.5 天)","P0 子任务速览","验收","P1 · Docs MVP(~1 周 / 5 天)","新增文件","验收","P2 · L1 单 Unit(~1.5 周 / 8 天)","新增文件","修改文件","验收","P3 · L1 + L2 联动(~3 周 / 15 天)","新增 / 完善","验收","① contract_mismatch · 自动 patch SDD","② impl_bug · 不改 SDD,仅 reset","③ spec_ambiguous / confidence < 0.5 · 升 L3 等待人类","④ infra_flake · 直接重跑","⑤ 可观测性产物校验(4 类分支跑完后统一检查)","桥梁:极简 Status Page","P4 · L3 + UI(~2 周 / 10 天)","新增","验收","成本估算","时间预算","「自举」乐趣"],"defs":[],"content":"五阶段总览 阶段 目标 新增 / 修改 可演示场景 P0 · Bootstrap 3 周 79 工具实现 + ACP 桥接 + 系统提示提取 + GitVault 集成 (每项目 git repo + Agent 完工自动 commit) tool-registry (34 工具)、 acp-bridge 、 git-versioning (GitVault + commit hooks)、scripts/extract-agent-prompts.js、scripts/phase0-verify.js scripts/phase0-verify.js 通过 + mock PM 调用 → todolist 落盘 + .git 初始化 + 1 个 prd commit + tag prd-rev-1 P1 · Docs MVP 能由 PM + Architect 把一段需求转成多页 PRD 站点 + 多页 SDD 站点 + units.json + integration/spec.json DocStore 、PM / Architect 模板、 POST /api/devloop/projects curl 一段中文需求 → 文件系统里看到 PRD/SDD 多页站点 + units.json + integration/spec.json + agent-state/ P2 · L1 单 Unit 对单个 Unit 跑 Coder→Tester 重试到通过 Coder / Tester 模板、 UnitRegistry 、 retryWithReflection (新写在 loop-controller.js 内,不抽公共) 故意挖一个 bug 让 Tester 红,看 Coder 反思后绿 P3 · L1 + L2 联动 多 Unit 并行 + 集成测试 + 失败回写 LoopController 、Integrator / BugTriager 模板,复用 scheduler 故意写 contract mismatch,看 SDD 自动 patch + 受影响 Unit reset → 重跑 L1 P4 · L3 + UI 人类反馈触发增量;前端实时看板 DevLoopDashboard.vue 、SSE 流、PRD diff 渲染 页面上发新需求 → 看到 PRD 新增 section + 新 Unit 自动入队 P0 · Bootstrap(~2.5 周 / 11.5 天) 这是 P1 的 前置条件 。 没有 P0,PM/Architect 的 79 个工具签名都是空中楼阁。详细工时分解 + 工具组织见 arch-tool-registry §⑪ 。 P0 子任务速览 子阶段 内容 工时 难度 P0.A 工具实现 server/src/tools/devloop/ 下 7 个文件,34 个独立函数 (~1700 行) 5 天 中(cheerio 多) P0.B 提示提取 scripts/extract-agent-prompts.js 从 6 个 agent HTML §2.2 抽 prompt 0.5 天 低(正则) P0.C 模板注册 system/templates/_registry.json 加 6 条 devloop-* + toolPolicy 0.5 天 低 P0.D CLI tools 注入 packages/acp-core/protocol/ClaudeCliProtocol.js 改 ~10 行,按 toolPolicy 生成 --tools json 1 天 中 P0.E agent-manager 透传 加 toolRegistry / toolCtx / onChunk 三个可选参数,~10 行 0.5 天 低 P0.F jsonGuard 挂 Bash Bash 工具 preExecute 钩子拦截 inline JSON 0.5 天 低 P0.G 验证脚本 scripts/phase0-verify.js + phase0-test-call.js 1 天 低 P0.H 联调 跑一个 mock PM 调用 → 看 todolist 落盘 + SSE 触发 2 天 — P0.I GitVault 实现 server/services/git-vault.js 约 150 行 spawn git wrapper(init / commit / tag / log / diff / archive)+ per-pid mutex 1 天 低 P0.J git 集成到 LoopController createProject 时 git init + .gitignore 模板;callAgentWithPersistence success 分支加 commitAfterAgent;recoverOnBoot 加 git status 检查 1 天 中 P0.K git 验证 phase0-verify 加:项目初始化后 .git 存在 + 一次 mock commit + tag · listTags 0.5 天 低 P0 合计 — 13.5 天 ≈ 3 周 — 验收 node scripts/phase0-verify.js # ✓ All 79 tools registered (34 unique impl) # ✓ 6 system prompts extracted to system/templates/devloop-*.md # ✓ _registry.json contains all 6 devloop-* entries node scripts/phase0-test-call.js # ✓ Mock PM call → write_todolist tool invoked # ✓ data/companies/c-test/projects/p-mock/agent-state/pm-todo.json written # ✓ SSE agent.todolist event emitted # ✓ No sandbox violations 关键决策: P0 期间 agent-manager 改动 ≤ 20 行,全是 追加可选参数 。老调用方(Master Agent / Innovator)不受影响。 P1 · Docs MVP(~1 周 / 5 天) 建议的 demo 需求: 「一个 TODO 列表 API + 前端」。比登录系统更小、不涉及 auth/JWT/bcrypt 等坑、30 分钟能端到端跑通。 预期 Architect 拆出 3 个 Unit: todo-be (POST/GET /todos)· todo-fe (Vue 列表)· 可选 todo-db (SQLite schema)。 登录系统留到 P3 当 contract_mismatch 演示 用。 新增文件 server/ ├── services/ │ └── doc-store.js # ~140 行 ├── routes/ │ └── devloop.js # 只挂 POST /projects · GET /state,~80 行 └── src/core/ └── (LoopController 阶段性 stub) system/templates/ ├── devloop-pm.md # PM system prompt ├── devloop-architect.md # Architect system prompt └── _registry.json # 追加 6 条(先用 PM + Architect 两条) 验收 curl -X POST http://localhost:3010/api/devloop/projects -d @/tmp/req.json # /tmp/req.json: {\"company_id\":\"c-001\",\"name\":\"todo-demo\",\"requirement\":\"做一个 TODO 列表:能新增、列表、Vue 前端\"} # 期望产物(与 data-layout.html 对齐) data/companies/c-001/projects/p-XXXXXX/ ├── project.json ✓ # M-1 项目元数据 ├── prd/ ✓ # M-3 多页 PRD 站点 │ ├── index.html │ ├── reading-guide.html │ ├── product/{background,personas,...}.html │ ├── feature/feature-tree.html │ ├── flow/user-flows.html │ └── prototype/proto-*.html ├── sdd/ ✓ # M-4 多页 SDD 站点 │ ├── index.html │ └── mvcp/{model,view,control,process}.html ├── units.json ✓ # M-2 含 l2_rounds / l2_budget 初值 ├── integration/spec.json ✓ # Architect 写 ├── agent-state/ ✓ # M-10 / M-12 Agent 持久化运行态 │ ├── pm.json · pm-todo.json │ └── archite"},{"f":"plan-reproducibility.html","title":"Claude Code 复现指南","h1":"Claude Code 复现指南","headings":["三步法","步骤 ① · 抽取 build-manifest.json","步骤 ② · 复现 Prompt 模板","步骤 ③ · 验收清单","典型失败模式与诊断","「文档驱动」承诺","下一步"],"defs":[],"content":"三步法 步骤 ① · 抽取 build-manifest.json 需要 node + cheerio。脚本 ~80 行: // scripts/extract-manifest.js · NEW const fs = require ( 'fs' ); const path = require ( 'path' ); const cheerio = require ( 'cheerio' ); const docsDir = path. resolve (__dirname, '../docs/prd/newfunction' ); const manifest = { meta: { source: 'docs/prd/newfunction' , prdRev: 'v0.2' } , model: [] , view: [] , control: [] , process: [] , units: [] , } ; // 1. 从 sdd-{mvcp}.html 抽 4 象限 for ( const mvcp of [ 'model' , 'view' , 'control' , 'process' ] ) { const html = fs. readFileSync (path. join (docsDir, `sdd- ${mvcp} .html` ), 'utf8' ); const $ = cheerio. load (html); $( '[data-def-name]' ). each ((_, el) => { manifest[mvcp]. push ( { name: $(el). attr ( 'data-def-name' ), path: $(el). find ( 'code.path' ). first (). text () || $(el). find ( 'code.path.mono' ). first (). text (), shape: $(el). find ( 'pre.shape' ). text () || $(el). find ( 'pre.code.shape' ). text (), contract: $(el). find ( 'ul.contract li' ). map ((_,li) => $(li). text ()). get (), tests: $(el). find ( 'ul.tests li' ). map ((_,li) => $(li). text ()). get (), } ); } ); } // 2. 从 prd-feature-tree.html 抽 Unit 列表(粗) // 这步实际由 Architect Agent 做 P1 时的拆分;脚本只做最简取出 file tree 中的 Unit id fs. writeFileSync ( 'build-manifest.json' , JSON.stringify (manifest, null , 2 )); console. log ( `✓ extracted ${manifest.model.length + manifest.view.length + manifest.control.length + manifest.process.length} definitions` ); 跑一下: cd lemonboss node scripts/extract-manifest.js # → build-manifest.json (~30KB) 步骤 ② · 复现 Prompt 模板 把以下 prompt 喂给一个全新的 Claude Code session(在空 lemonboss 仓库根目录启动)。 # 复现 prompt · 直接粘到 claude code 我手上有一份 LemonBoss DevLoop 系统的完整 PRD + SDD(HTML 形式),位于 docs/prd/newfunction/ 下,共 41 页。我已用脚本抽出 build-manifest.json。 请你扮演 Architect + Coder + Tester 三个角色,完成以下工作: 【阶段 A · 拆 Unit】 1. 读 docs/prd/newfunction/prd-feature-tree.html,确认 34 个 Unit 2. 读 build-manifest.json,把 MVCP 四象限的定义映射到 Unit 3. 在 data/companies/c-repro/projects/p-init/units.json 落地 DAG 【阶段 B · 实现每个 Unit】 对每个 Unit 顺次: 1. 读对应 Unit 的所有 MVCP 定义(manifest 里的 model/view/control/process) 2. 按 path 字段落盘,按 shape 字段写代码骨架 3. 按 contract 列表实现行为 4. 按 tests 列表写 vitest 用例(后端 supertest + 内存 SQLite,前端 @vue/test-utils) 5. 跑 vitest,红了反思(参考 docs/prd/newfunction/loops-l1.html 的 retryWithReflection 章节),重写直到绿 【阶段 C · 集成】 1. 读 docs/prd/newfunction/sdd-process.html 实现 LoopController.runProject 2. 启动后端 + 前端,cd 到 docs/prd/newfunction,用 docs 自己做 demo 项目跑一遍 3. 注入「contract_mismatch」演示自愈 【约束】 - 严格遵守 docs/prd/newfunction/agents-coder.html 的沙箱规则 - 任何带 JSON body 的 curl 必须先写 /tmp/*.json 再 -d @file (参见 CLAUDE.md L91-99) - 不动 server/services/evolution-loop.js(参见 arch-mapping.html PATCH 说明) - 不动 packages/acp-core/ · core/master-session.js · core/agent-manager.js 【验收】 完成后跑 npm test,所有 vitest 必须通过。然后跑: curl -X POST http://localhost:3010/api/devloop/projects -d @/tmp/test.json (/tmp/test.json 单行紧凑: {\"company_id\":\"c-001\",\"name\":\"todo\",\"requirement\":\"做个 TODO API\"}) 应在 60 秒内看到 prd.html + sdd.html + units.json 三份文档生成。 开始吧。 步骤 ③ · 验收清单 Claude Code 跑完后,人类只需对照以下 12 条逐项检查: # 检查点 判定 1 server/src/core/loop-controller.js 存在且导出 loopController ✓ require 不报错 2 server/services/doc-store.js 存在 ✓ 3 server/services/unit-registry.js 存在 ✓ 4 server/services/sse-broker.js 存在 ✓ 5 server/routes/devloop.js 存在且 7 个端点全 ✓ curl /api/health + grep 6 system/templates/devloop-{pm,architect,coder,tester,integrator,triager}.md 全部存在 ✓ ls 应见 6 个 7 system/templates/_registry.json 含 devloop-* 六条 ✓ grep 8 web/src/views/DevLoopDashboard.vue + 7 个子组件 ✓ ls web/src/components/devloop/ 9 npm test 全绿 ✓ 10 POST /api/devloop/projects 60s 内生成 3 份文档 ✓ file 存在 11 SSE 流持续推 unit.state 事件 ✓ curl -N 看到 12 整套 DevLoop 能 对自己做增量 (用 DevLoop 给 DevLoop 加新需求) ✓ 元-自举测试 典型失败模式与诊断 失败 诊断 修 缺某个 MVCP 定义 build-manifest.json 该项空 检查对应 SDD 页是否漏 data-def-name shape 抽不到 HTML class 不规范 用 pre.shape 不是 pre.code 也不是 pre.code.shape,统一 Claude Code 写出的代码风格与 LemonBoss 不一致 prompt 缺约束 在复现 prompt 顶部 require 它先读 CLAUDE.md contract.json 与 SDD 不一致 Architect 没遵循 hashContract 算法 看 docs/p"},{"f":"plan-risks.html","title":"风险与待决","h1":"风险与待决","headings":["风险全表","逐条展开","开放问题","下一步"],"defs":[],"content":"风险全表 ID 风险 影响阶段 对策状态 R1 无限循环(L2 改 SDD → 又失败 → 再改) P3 ✅ 有预算 R2 Agent 把 Bash 玩坏(inline JSON) P2 起 ✅ 已写入 prompt R3 集成测试套件谁来写 P3 ✅ 初版方案 A R4 PRD/SDD 的 HTML diff 算法 P4 ⚠️ 初版粗粒度 R5 与现有 EvolutionLoop 的关系 P2 ✅ 抽公共函数 R6 跨 Unit 的命名 / 约定漂移 P3 ✅ Architect 强制后缀 R7 Token 成本失控(无限 retry × Opus) P2 起 ⚠️ 需配置全局上限 逐条展开 R1 · 无限循环风险 P3 场景: L2 失败 → 改 SDD → 再失败 → 再改 SDD… 对策: 三层预算守护 每 unit retry ≤ 5 次(L1 内) 每 PRD rev 内 L2 修复轮次 ≤ 3 轮 BugTriager confidence < 0.5 立即上报 L3 超出 → 项目挂起,发 SSE,等人类介入。绝不静默丢弃。 R2 · Bash 中 inline JSON P2 起 参见 CLAUDE.md L91-99。所有需要带 JSON body 的命令必须先写 /tmp/*.json 再 curl -d @file 。 对策: 在 Coder / Tester / Integrator 的 system prompt 中强制声明铁律 沙箱层 hook 检测 -d '{...}' 模式直接拒绝 Bash 工具增加 jsonGuard middleware(约 30 行) R3 · 集成测试谁来写 P3 三种方案对比见 L2 页 · 集成测试套件从哪来 。 初版选 A:Architect 同步产 integration/spec.json ,理由: 少一个 Agent,少一次握手 spec 与 SDD 同源演化,不会脱节 Architect 已经 Opus,能力够 若 P3 实测发现 Architect 写 spec 质量差,回退到方案 B 单独加 IntegrationAuthor。 R4 · PRD/SDD diff 算法 P4 · 待决 HTML 结构化 diff 比纯文本难。 初版策略: section 级粒度,按 data-rev 属性区分新旧(每个 section 要么完全新,要么完全旧)。这避免文本级 diff 算法的复杂度。 已知限制: 同一 section 内修改不会高亮,只能整段标 changed。 P5+ 升级: 引入 diff-dom 做精细化 inline diff。但需要先看 P4 实测是否够用。 R5 · 与 EvolutionLoop 的关系 P2 EvolutionLoop(在 packages/evolution/ )负责 Skill 进化,是另一套独立系统。DevLoopController 负责工程化代码开发。 两者不交叉 。 唯一耦合: retryWithReflection() 抽到公共包供两者共用。从 evolution-loop.js 抽出约 80 行代码,外部接口保持不变。 R6 · Unit 命名漂移 P3 BugTriager 输出 affected_units 是 string[],靠字符串匹配 UnitRegistry。如果不同轮 Architect 用 auth-be / auth-backend / auth-server 三种风格,归因会丢。 对策: Architect 强制后缀(参见 Architect · ④ Unit 拆解策略 )。Schema 校验同时强制 id =~ /^[a-z]+(-[a-z]+)*-(be|fe|mw|db)$/ 。 R7 · Token 成本失控 P2 起 · 待决 5 次 unit retry × 多 unit × 3 次 L2 修复 × Opus 模型 = 单项目可能烧几十刀。 对策: project.json 加 token_budget 字段(默认 5M tokens / 项目) LoopController 每次 callAgent 后累计,超出立刻挂起 Tester 默认 Haiku(最便宜) Dashboard 实时显示已用 token 与剩余预算 具体阈值由 P2 实测 token 消耗后再定。 开放问题 以下是 没在本 PRD 给答案 的问题,需要在 P1 启动会前敲定: 多人项目 / 协作: 同一项目能不能两个人同时发 L3 反馈?目前默认是「队列化」,但 UI 怎么显示? 代码归宿: 项目 shipped 后, units/*/code/ 要不要自动提个 GitHub PR?还是留在 LemonBoss 文件系统里? 跨 Unit 共享库: R6 提到的 shared/ 在 P1~P4 留空。但「auth utils 给所有 Unit 共用」是个真实需求,要不要提前到 P3? 外部依赖(npm 包)安装权限: Coder 跑 npm install 安装新包是否允许?目前默认禁,但很多前端 Unit 实际需要。 失败后人工 takeover: 项目 blocked 后,人类能否在 Dashboard 里 手动编辑 某个 Unit 的代码再 resume?这要不要算 L3 的一种 action? 这五个问题不阻塞 P1 启动,但建议在 P3 前给答案。 下一步 动工前最后一关: 你(Yibo)读完整套 PRD,回复以下三个 yes / no: 三层 Loop 的边界划分是否准确?特别是 L2/L3 之间的「失败时先修文档」是否同意? 六个 Agent 的拆分粒度可以接受吗?有没有应该合并或拆细的? 四阶段排序 P1→P4 是否合理?是否要把 UI 提前? 都 yes → 开 P1 PR。任一 no → 回来改这份 PRD 再说。"},{"f":"prd-background.html","title":"背景 / 问题陈述","h1":"背景与问题陈述","headings":["当下的痛","市场参考","为什么是「现在」","北极星指标","定位声明","非目标声明"],"defs":[],"content":"当下的痛 痛点 现状 影响 需求 → 代码 链路碎裂 Master 能调 Agent,但没有「项目」概念,每次都是临时编排 同一需求要重复对 Master 解释多次 失败时不知所云 Agent 跑测红了,要人去翻 stdout 找原因 调试成本远大于编写成本 反思机制只用在 Skill evolution-loop.js 的反思能力只服务 Skill 训练 普通代码开发享受不到「失败 → 反思 → 重试」 文档与代码脱节 用户在 chat 里描述,Agent 直接写代码,没有持久 PRD 第二次反馈时 Agent 已经忘了第一次的上下文 无法增量迭代 新一轮需求 → 整个项目重写,已通过的 Unit 也跟着重做 Token 浪费 + 不收敛 市场参考 给你一个相对位置感。这不是抄袭,是知道我们站在哪。 产品 定位 本系统的差异 Devin 端到端 AI 软件工程师 我们不卖通用 SaaS,是 LemonBoss 内嵌能力;强调三层 Loop 可观测 Cursor Composer IDE 内多文件编辑 我们的 PRD/SDD 是 持久文档 ,不是 chat 上下文 v0 / Bolt UI prototype 生成 我们覆盖全栈 + 测试 + 集成 + 反思 Claude Code 自身 命令行 IDE Agent 我们用 Claude Code 当执行引擎,加 Loop 编排层 OpenHands / SWE-agent 开源 SWE 基线 我们绑定 LemonBoss 公司化语义(OKR / Pipeline / Goals) 为什么是「现在」 Claude Opus 4.7 + Sonnet 4.6 的代码能力终于足以支撑长链路自动开发。半年前 retry-with-reflection 收敛率太低。 LemonBoss 的 ACP / SessionPool 已经稳定运行。会话复用、沙箱、process-guard 全部就绪。 LemonBoss 的「公司 / OKR / Pipeline」语义 需要承载真实开发任务,目前空有壳。 北极星指标 本系统的成功 = 一句话「做个 TODO API + 前端」从输入到 shipped 不需要人工干预,全程 < 30 分钟、Token 成本 < 50 万。 这是 P4 演示日的目标。短中长期指标见 prd-success-metrics 。 定位声明 DevLoop 是 LemonBoss 的 「自动化代码工厂」 子系统。它把 LemonBoss 已有的多 Agent 能力,从「对话化代码助手」升级为「项目化代码工程师」。它不替代人,它 替代人的初稿、迭代和回归测试 ,让人去做架构决策和最终验收。 非目标声明 不是 X 因为 不是通用 IDE 替代品 用户仍可手动编辑代码;DevLoop 与人共编 不是 CI/CD 系统 不负责真实部署、只负责生成可部署代码 不是项目管理系统 OKR / Goals / Pipeline 由 LemonBoss 既有模块负责 不是付费 SaaS 初版只服务 LemonBoss 自托管用户"},{"f":"prd-feature-tree.html","title":"功能树","h1":"功能树","headings":["全景树","按层级统计","依赖拓扑(粗)","Unit id 命名约定"],"defs":[],"content":"全景树 DevLoop System ├── F1 · 项目生命周期 │ ├── F1.1 创建项目 (POST /projects) │ │ └── Unit: devloop-be-projects │ ├── F1.2 启动 / 续跑 (POST /run) │ │ └── Unit: devloop-be-run │ ├── F1.3 查询状态 (GET /state) │ │ └── Unit: devloop-be-state │ ├── F1.4 Sign-off / Abandon │ │ └── Unit: devloop-be-finalize │ └── F1.5 项目元数据持久化 │ └── Unit: devloop-be-projectmeta │ ├── F2 · 文档管理 (DocStore) │ ├── F2.1 PRD CRUD + 归档 │ │ └── Unit: docstore-prd │ ├── F2.2 SDD CRUD + 归档 │ │ └── Unit: docstore-sdd │ ├── F2.3 units.json schema 校验 │ │ └── Unit: docstore-units-schema │ ├── F2.4 HTML 解析与校验 (cheerio) │ │ └── Unit: docstore-html-validator │ └── F2.5 contract_hash 计算 │ └── Unit: docstore-contract-hash │ ├── F3 · Unit 状态机 (UnitRegistry) │ ├── F3.1 状态转换合法性 │ │ └── Unit: registry-fsm │ ├── F3.2 L2 gate 判定 │ │ └── Unit: registry-l2gate │ ├── F3.3 增量 diff (added/changed/removed) │ │ └── Unit: registry-diff │ └── F3.4 retry 预算管理 │ └── Unit: registry-budget │ ├── F4 · 三层 Loop 编排 (LoopController) │ ├── F4.1 L1 单 Unit 跑通 │ │ └── Unit: loop-l1-runner │ ├── F4.2 L1 retryWithReflection │ │ └── Unit: loop-retry │ ├── F4.3 L1 并行调度 (scheduler 复用) │ │ └── Unit: loop-parallel │ ├── F4.4 L2 集成 gate + run │ │ └── Unit: loop-l2-runner │ ├── F4.5 L2 修复回写 (triage → patch SDD → reset) │ │ └── Unit: loop-l2-repair │ ├── F4.6 L3 增量需求并入 │ │ └── Unit: loop-l3-merge │ └── F4.7 全局预算 + 终止条件 │ └── Unit: loop-guard │ ├── F5 · Agent 模板 + 调用 │ ├── F5.1 PM Agent │ │ └── Unit: agent-pm │ ├── F5.2 Architect Agent (generate + patch 两模式) │ │ └── Unit: agent-architect │ ├── F5.3 Coder Agent │ │ └── Unit: agent-coder │ ├── F5.4 Tester Agent │ │ └── Unit: agent-tester │ ├── F5.5 Integrator Agent │ │ └── Unit: agent-integrator │ └── F5.6 BugTriager Agent │ └── Unit: agent-triager │ ├── F6 · 事件推送 (SSE) │ ├── F6.1 SSEBroker 订阅 / 退订 │ │ └── Unit: sse-broker │ ├── F6.2 9 类事件序列化 │ │ └── Unit: sse-events │ └── F6.3 断线重连 + replay │ └── Unit: sse-replay │ ├── F7 · 前端 Dashboard │ ├── F7.1 主页面 DevLoopDashboard.vue │ │ └── Unit: fe-dashboard │ ├── F7.2 DashboardHeader (含 + NEW REQUIREMENT) │ │ └── Unit: fe-header │ ├── F7.3 LoopStatusBar (3 张状态卡) │ │ └── Unit: fe-loopbar │ ├── F7.4 UnitsDAG (复用 PipelineEditor 渲染) │ │ └── Unit: fe-dag │ ├── F7.5 LiveAgentStream (终端样式 SSE) │ │ └── Unit: fe-stream │ ├── F7.6 PrdDiffPanel (section 级 diff) │ │ └── Unit: fe-prddiff │ ├── F7.7 FeedbackModal (反馈输入) │ │ └── Unit: fe-modal │ ├── F7.8 UnitDrawer (Unit 详情侧栏) │ │ └── Unit: fe-drawer │ └── F7.9 路由 + i18n keys │ └── Unit: fe-router-i18n │ └── F8 · 复现 / 可执行性 ├── F8.1 PRD/SDD data-* 锚点协议 │ └── (无 Unit,文档约定) ├── F8.2 build-manifest 提取脚本 │ └── Unit: tool-manifest-extract └── F8.3 Reproducibility prompt 模板 └── (无 Unit,prompt 文件) 绿色 = 一个 Unit。共 34 个 Unit 。Architect Agent 在 P1 演示时会从这棵树派生 units.json。 按层级统计 L1 功能群 L2 功能 Unit 数 类型构成 F1 项目生命周期 5 5 be × 5 F2 文档管理 5 5 be × 5 F3 Unit 状态机 4 4 be × 4 F4 Loop 编排 7 7 be × 7 F5 Agent 模板 6 6 config × 6(Agent prompt + 注册) F6 SSE 3 3 be × 3 F7 前端 9 9 fe × 9 F8 复现 3 1 tool × 1 合计 42 34 be × 24 / fe × 9 / tool × 1 依赖拓扑(粗) 箭头 = 「下游 Unit 需要上游 contract」。LoopController.scheduler 据此分波并行。 P1 优先做 F2 + F5 (PM + Architect);P2 加 F3 + F4.1~4.3;P3 加 F4.4~4.7 + F1 + F6;P4 加 F7。 Unit id 命名约定 本树里的 Unit id 全部遵循 Architect 拆解策略 : 前缀 层 预计 cwd devloop-be-* F1 server/routes/devloop.js · server/services/ docstore-* F2 server/services/doc-store.js registry-* F3 server/services/unit-registry.js loop-* F4 server/src/core/loop-controller.js agent-* F5 system/templates/devloop-*.md sse-* F6 server/services/sse-broker.js fe-* F7 web/src/views/ · web/src/components/devloop/ tool-* F8 scripts/"},{"f":"prd-glossary.html","title":"术语表","h1":"术语表","headings":["核心抽象","组件与服务","Agent 角色","状态与归类","文档与协议","反义 / 易混淆"],"defs":[],"content":"核心抽象 术语 定义 出处 DevLoop 本系统全名 — 三层 Loop 驱动的自动开发流水线 background L1 / Unit Loop 最内层。单 Unit 的「写代码 → 跑单测 → 失败重试」 loops-l1 L2 / Integration Loop 中层。所有 Unit passed 后跑集成测试 + 失败回写 SDD loops-l2 L3 / Human Feedback Loop 外层。人类新需求并入 PRD 触发增量重跑 loops-l3 Unit 最小可独立开发 + 单测的代码单元(前端组件 / 后端服务 / 中间件 / DB 迁移) architect Project 一次完整的 DevLoop 实例,对应 data/companies/{cid}/projects/{pid}/ data-layout Rev PRD / SDD / units.json 的修订号,单调递增 documents Run 一次 Agent 执行的产物目录 runs/{runId}/ documents Contract Unit 对外暴露的接口契约(OpenAPI / TS interface) api-contracts contract_hash contract 文本 sha256 截前 16 位,用于检测变更 components 组件与服务 术语 定义 出处 LoopController 三层 Loop 的唯一编排者, server/src/core/loop-controller.js components DocStore PRD/SDD/units.json/spec.json 读写归档 components UnitRegistry Unit 状态机的 source of truth components SSEBroker Server-Sent Events 订阅与分发 api-sse scheduler 复用 server/src/core/scheduler.js 做 DAG 拓扑分波 mapping session-pool 复用 ACP 的会话池,按 (agentName, cid, pid) 复用 mapping retryWithReflection 反思重试函数,P2 写在 loop-controller.js 内 loops-l1 Agent 角色 术语 定义 出处 PM Agent 需求 → prd.html。L3 入口 PM Architect Agent PRD → SDD + units.json + integration spec Architect Coder Agent 实现单 Unit 的代码 + contract.json Coder Tester Agent 写 + 跑 Unit Test,输出 hint 给 Coder Tester Integrator Agent 起多个真实进程跑端到端测试 Integrator BugTriager Agent 归因集成失败,提议 SDD 补丁 Triager 状态与归类 术语 定义 pending / coding / testing / failed / passed / blocked Unit 六态机 contract_mismatch BugTriager 类别 1 — SDD 双方契约不齐 impl_bug BugTriager 类别 2 — 代码没遵守 SDD, 不改 SDD spec_ambiguous BugTriager 类别 3 — SDD 描述不清,升 L3 infra_flake BugTriager 类别 4 — 端口冲突等环境问题,重跑即可 L2 gate 「全部 Unit passed 且 contract_hash 与当前 SDD 一致」的门 l2_rounds / l2_budget L2 修复轮次累计与上限(默认 3) retry_budget 单 Unit 重试上限(默认 5) 文档与协议 术语 定义 MVCP 本 SDD 采用的四象限划分:Model / View / Control / Process data-* 锚点 HTML 上的机器读结构化信息,cheerio 抽取 build-manifest Architect Agent 从 SDD 抽出的 JSON 构建清单 ACP Agent Communication Protocol,LemonBoss 既有协议层 company-scoped 文件根 所有项目落在 data/companies/{cid}/ 下 反义 / 易混淆 勿混 正解 DevLoop ≠ Master Agent DevLoop bypass Master,自己起 LoopController EvolutionLoop ≠ DevLoop EvolutionLoop 是 Skill 进化,DevLoop 是工程化代码生成 units.json ≠ pipeline.json units.json 是 DevLoop 自己的;pipeline-service 是 LemonBoss 既有 SOP contract.json ≠ contract_hash 前者是契约文本本身,后者是其 sha256 摘要 L2 gate ≠ L2 闸门关闭 「gate 打开」= 允许进 L2,不是阻止 blocked ≠ failed failed 是「测试红」可继续 retry;blocked 是「retry 耗尽」需要人"},{"f":"prd-personas.html","title":"角色与 User Stories","h1":"角色与用户故事","headings":["三类人类角色","P1 · Founder / Tech Lead","Yibo","P2 · Reviewer","同事 / 投资人","P3 · Future Maintainer","3 个月后的自己","两类 Agent 「角色」","本项目内的六个 Agent","外部 Claude Code Session","核心 User Stories","故事优先级"],"defs":[],"content":"三类人类角色 P1 · Founder / Tech Lead Yibo 下需求 + 决策 + 验收。每天 1-3 次用 DevLoop。要求快、要求 PRD 与代码同步、不接受 chat-only。 主要触达:L3 反馈、Sign-off、Sidebar 全部 8 个 Section P2 · Reviewer 同事 / 投资人 偶尔进来看进度和最终产物。不写代码,只读 PRD + Dashboard。要求一目了然。 主要触达:Dashboard 头部、PRD diff、L2 集成结果 P3 · Future Maintainer 3 个月后的自己 回过头改某个 Unit 时,得能从 PRD / SDD 反推为什么这么设计。 主要触达:PRD history、SDD section 级 diff、coder.log 两类 Agent 「角色」 Agent 不是用户,但它们消费这套文档,列出来便于把约束写在该写的位置。 A1 本项目内的六个 Agent 消费 PRD/SDD/units.json/contract.json。详见 六个 Agent 。 A2 外部 Claude Code Session 从零拉起一份本仓库后, 只读本套文档 就能复现整个 DevLoop 系统。详见 复现指南 。 核心 User Stories US-01 · Founder 启动新项目 L3 As Yibo, I want 用一句中文描述需求就启动一个 DevLoop 项目, so that 我不用先想清楚再下达指令。 字段 内容 Trigger POST /api/devloop/projects 或 Dashboard 「+ NEW PROJECT」 Inputs { company_id, name, requirement: string } Acceptance 30 秒内 prd.html v1 落盘 60 秒内 sdd.html v1 + units.json 落盘 SSE 立即推 prd.updated 事件 US-02 · Founder 追加新需求 L3 As Yibo, I want 在项目跑到一半时插话「再加 OAuth 登录」, so that 系统能识别这是增量而不是从头重做。 Trigger POST /api/devloop/projects/:pid/requirement Acceptance PRD 新增 <section data-rev=\"N\"> ,不重写旧 section units.json diff 输出 {added, changed, removed} 三类 已 passed 且 contract_hash 不变的 Unit 不 被 reset US-03 · Reviewer 查看实时进度 L3 As Reviewer, I want 打开 Dashboard 就看到「现在卡在哪一步」, so that 不必去翻日志。 Trigger GET /devloop/:pid(前端) Acceptance 顶部 3 张 Loop 状态卡 5 秒内更新 DAG 节点颜色对应 unit.status 失败 Unit 红色 + 显示 retry 次数 US-04 · 系统自愈集成失败 L2 As Yibo, I want 集成失败时系统先尝试自己修,不要每次都来问我, so that 我专注高价值决策。 Trigger Integrator 返回 ok=false Acceptance BugTriager 5 分钟内产 triage.json confidence ≥ 0.5 → 自动 patch SDD + reset Unit confidence < 0.5 → 发 SSE human_intervention_needed 同项目每 PRD rev 最多自愈 3 轮 US-05 · Future Maintainer 反推决策 L3 As 3 个月后的 Yibo, I want 看到 SDD 某个 Unit 的「为什么这么设计」, so that 不会重复犯老错。 Trigger 在 Dashboard 点开某 Unit 卡片 Acceptance 能看到该 Unit 在哪个 PRD rev 引入 能看到所有 retry 时的 coder.log + tester.hint 能看到所有相关 triage.json(如果被 reset 过) US-06 · 外部 Claude Code 复现项目 A2 As 一个全新的 Claude Code session, I want 拿到本套 PRD + SDD HTML 就能从空仓库重建整套 DevLoop 代码, so that 系统具备真正的「文档驱动」性质。 Trigger 执行 复现 prompt Acceptance 5 个新 JS + 6 Agent 模板 + 1 Vue 全部生成 SDD MVCP 四象限 100% 抽取覆盖 单测自动通过率 ≥ 80% 故事优先级 P Story 阶段 P0 US-01 · US-04 P1 + P3 必须 P1 US-02 · US-03 P3 / P4 必须 P2 US-05 P4 应有 P3 US-06 P4 后续验证"},{"f":"prd-success-metrics.html","title":"成功指标","h1":"成功指标与 KPI","headings":["P0 · 阶段闸门指标(每阶段必须 100%)","P1 · 北极星 KPI(系统整体)","速度","成本","无人化率","P2 · 长期质量 KPI(运营期)","反指标(不要追的东西)","验收清单(P4 演示日)"],"defs":[],"content":"P0 · 阶段闸门指标(每阶段必须 100%) 阶段 指标 目标 测量方式 P1 curl 一句话需求 → 三份文档落盘 ≥ 90% 成功率(10 次抽样) integration test PRD/SDD HTML 通过 cheerio 校验 100% DocStore.validateHTML units.json schema 校验 100% typebox P2 单 Unit 红→绿收敛率 ≥ 80% 在 3 次 retry 内 10 个 demo Unit 跑 100 次 retryWithReflection 二次失败率 ≤ 30% 同上 沙箱越权写 0 次 越权写直接拒绝 P3 「contract_mismatch」演示自愈 ≥ 90% 一轮内修复 注入 5 种典型 mismatch L2 总修复轮次 平均 ≤ 1.5 同上 impl_bug 与 spec_ambiguous 归类正确率 ≥ 85% 30 个标注 case P4 Dashboard SSE 端到端延迟 ≤ 2s(P95) 手动 stopwatch + 浏览器 perf PRD diff 面板渲染正确率 100% 10 个 rev 切换 L3 反馈 → 增量 reset 准确率 ≥ 95% diff_units 单测 P1 · 北极星 KPI(系统整体) 速度 < 30min 「TODO API + 前端」从需求 → shipped 端到端 成本 < 50万 Token / 上述 demo 无人化率 ≥ 80% 项目从启动到 shipped 不需人工干预的比例 在 P4 演示日同时满足三条 = 系统达成 MVP。任意一条不达就视为 失败 ,回到 P3 调优。 P2 · 长期质量 KPI(运营期) 维度 指标 目标 采集 收敛性 blocked 项目比例(30 天内) ≤ 10% UnitRegistry 累计 收敛性 l2_exhausted 比例 ≤ 5% LoopController 日志 质量 shipped 项目人工返工率 ≤ 20% 人工标注 质量 contract.json 与 SDD 一致性 100% DocStore 校验 反思有效性 retry-2 比 retry-1 失败率降幅 ≥ 40% runs/result.json 统计 成本 平均 Token / 项目 持续下降趋势 token_budget 累计 速度 平均墙钟 / 项目 ≤ 1 小时 project.json 时间戳 文档健康 PRD section 与代码 unit 对齐率 ≥ 90% 每周脚本扫描 复现力 外部 Claude Code 复现成功率 ≥ 70% 季度复现实验 反指标(不要追的东西) 不追「项目数量」 — 一个跑通的项目 >> 十个挂起的项目。 不追「代码行数」 — Coder 写 200 行能解决的就不要 500 行。 不追「Agent 调用次数」 — 调用次数越多越花钱,不是越「努力」越好。 不追「PRD rev 数量」 — rev 多说明需求不清晰,不说明产品丰富。 验收清单(P4 演示日) # 项 判定 1 从空项目 curl 启动 TODO 需求 30s 内 prd.html v1 落盘 ✓ 2 L1 全部 Unit 通过 ≤ 3 次 retry / Unit ✓ 3 L2 通过 ≤ 1 次 L2 round ✓ 4 追加「加 OAuth」反馈 仅 oauth-be + oauth-fe + auth-mw 入 L1,其他不动 ✓ 5 注入 contract mismatch BugTriager 一轮内修复 ✓ 6 Dashboard 全程实时跟随 SSE 不丢、不重复 ✓ 7 shipped 后归档 history/ 完整 ✓ 8 外部 Claude Code 用 PRD 复现整套 DevLoop 5 文件 + 6 模板生成,单测 ≥ 80% 绿 ✓ 8 项全 ✓ → 系统可对外宣布 v1.0。"},{"f":"prd-user-flows.html","title":"用户流程","h1":"用户流程图","headings":["Flow 1 · 启动新项目(US-01)","Flow 2 · 增量反馈(US-02)","Flow 3 · 集成失败 → 自愈(US-04)","状态可见性约定"],"defs":[],"content":"Flow 1 · 启动新项目(US-01) 耗时 关键产物 0-2s HTTP 201 + project_id 5-30s prd.html v1 30-60s sdd.html v1 + units.json Flow 2 · 增量反馈(US-02) 关键设计点: 已 passed 且 contract_hash 不变的 Unit 不会被 reset 。这是「增量」的本质。 Flow 3 · 集成失败 → 自愈(US-04) 状态可见性约定 三个 Flow 都对人类有可见性要求。Dashboard 必须在 5 秒内反应以下事件: 事件 视觉反馈 新 Unit 进 coding DAG 节点变 emerald Unit 进 testing 节点变黄 Unit failed 节点变红 + retry 数字 Unit passed 节点深绿描边 L2 启动 L2 状态卡变 amber pulse SDD patched SDD diff 面板高亮新 section human_intervention_needed 右上角 toast violet shipped 整页 confetti 5s(认真的)"},{"f":"proto-feedback-modal.html","title":"原型 · 反馈模态框","h1":"FeedbackModal · 反馈输入","headings":["线框","交互细节","异常态","提示「需求过短」","提示「与历史矛盾」","项目已 shipped-final","键盘可达性"],"defs":[],"content":"线框 FeedbackModal · open 交互细节 事件 系统反应 打开 focus 自动落 textarea,背景 50% 透明遮罩 输入字符 右下字数计数实时更新 > 4000 字 计数变橙,按钮仍可点(仅警告) 空文本 提交按钮 disabled 灰色 Ctrl/Cmd + Enter 等价于点提交 Esc 关闭(如有未提交内容,下次打开自动恢复) 点击遮罩区 关闭(同 Esc) 提交后 立即关闭模态框,header 显示 spinner「PM 处理中」 异常态 提示「需求过短」 < 20 字 + 不含动词时,PM Agent 会在 prd.html 写 <aside data-question> 。模态框关闭后 toast 提示「PM 正在追问你」。 提示「与历史矛盾」 PM 检测到冲突,prd 中插 <aside data-conflict> 。Dashboard 右上角弹紫色横幅,需要人类点开模态框补充。 项目已 shipped-final 「+ NEW REQUIREMENT」按钮置灰;如人类强行调 API,返 409。 键盘可达性 键 动作 Tab 循环 textarea → 取消 → 提交 Enter 在 textarea 内换行 Ctrl/Cmd + Enter 提交 Esc 取消"},{"f":"proto-prd-diff.html","title":"原型 · PRD diff 面板","h1":"PrdDiffPanel · 文档版本差异","headings":["线框","三色 diff 标记","怎么实现 diff","切 rev 控件","影响 Unit 提示"],"defs":[],"content":"线框 PrdDiffPanel · rev 3 vs rev 2 三色 diff 标记 类型 颜色 判定 影响 Unit added emerald 新 section data-rev 等于当前 rev 可能新增多个 Unit changed amber section data-rev 旧但被新需求隐含修改 需要 Architect 标注 + 影响 contract_hash 时才 reset removed red 新 PRD 不再包含的 section(罕见) 对应 Unit 整体归档到 history/removed/ unchanged 灰底 正文不变 不动 怎么实现 diff 客户端用 diff-dom ,section 粒度。算法: 对 newHtml 和 oldHtml 分别用 DOMParser.parseFromString 枚举每个 section[data-rev] 在双方的存在性 + textContent hash 三态分类:only-new = added · only-old = removed · 同 id 不同 hash = changed 渲染时为每条 section 包一层 wrapper 标记色 选 section 级而非 token 级,是为了贴合 PM Agent 的写入约束 — 它的修改本来就是 section 粒度。 切 rev 控件 顶部默认 \"rev N vs rev N-1\",可下拉切换: \"rev 3 vs rev 0\" — 看从 init 到现在的累积 \"rev 3 vs rev 2\" — 默认 \"rev 3 only\" — 单看本 rev 新增 影响 Unit 提示 每个 diff section 下方一行 → 影响 Unit: ... 。来源: unitRegistry. diffWithSDD (pid) // → { added: ['oauth-be','oauth-fe'], changed: [], removed: [] } 把 added/changed/removed 列表渲染在对应 diff 行下面,让人类一眼知道「这个反馈引发了哪几个 Unit 入队」。"},{"f":"proto-unit-drawer.html","title":"原型 · Unit 详情抽屉","h1":"UnitDrawer · Unit 详情","headings":["线框","抽屉 4 个区域","① 头部","② Spec","③ Contract","④ Recent runs + coder.log","从哪里打开 / 怎么关闭","资料完整性追溯"],"defs":[],"content":"线框 UnitDrawer · unit auth-be · open from DAG 抽屉 4 个区域 ① 头部 Unit id + 状态徽章 + meta(type / retries / depends_on)+ 关闭 ×。 ② Spec 来自 SDD 对应 section 的 <p> + <ul data-acceptance> ,含「来自 SDD@revN」追溯标签。 ③ Contract 展开 contract.json 的 exports(HTTP endpoints)。鼠标悬停可看完整 schema。 ④ Recent runs + coder.log 最近 3 次 run 摘要卡片,每张可点开看完整 result.json。底部黑底面板显示 coder.log 的最近 5 条决策。 从哪里打开 / 怎么关闭 方式 说明 点击 DAG 节点 主入口 SSE unit.state 推送时点击「查看」 从右上 toast 跳 键盘 ←/→ 切换上一/下一个 Unit(在 units 数组中) Esc 或 × 关闭 点击外部遮罩 关闭 资料完整性追溯 抽屉是 US-05 的核心 — 让 3 个月后的自己能反推决策。所以下面这些链接必须可用: 链接 跳到 \"来自 SDD@rev 5\" pill 新 tab 打开 history/sdd-rev-5.html contract sha256:7f3a... pill 纯净 JSON 视图 每条 run 卡片 展开内嵌 result.json + stdout 抽屉 \"open full log\" 跳到 stream.log 中该 Unit 全部行 如曾被 L2 reset 显示触发它的 triage.json 链接"},{"f":"reading-guide.html","title":"阅读指南","h1":"阅读指南","headings":["两个读者","人类 · 决策 / 评审","Agent · 执行 / 复现","HTML 锚点协议(Claude Code 读取)","抽取示例 — units.json 怎么从 SDD 派生","MVCP 抽取约定","文档作为「构建清单」","本文档版本"],"defs":[],"content":"两个读者 HUMAN 人类 · 决策 / 评审 关心「想做什么、为什么、值不值得」。阅读路径: 封面 背景 · 角色 原始需求 · 范围与原则 功能树 · 用户流程 三层 Loop · 六个 Agent 最小改造路径 · 风险 预估 30 分钟。最后回到 plan-risks 末尾回答 3 个 yes/no。 CLAUDE CODE Agent · 执行 / 复现 关心「这个项目要落地什么文件、什么签名、什么行为」。阅读路径: 本页 — 学会怎么抽取结构化信息 功能树 — 拿到所有 Unit id 与依赖关系 SDD 总览 — 了解 MVCP 四象限 Model · View · Control · Process — 抽取构建清单 Unit Contract — 跨 Unit 握手协议 复现指南 — 执行 prompt 机械化阅读,不需要欣赏 prose。所有 data-* 属性是 Agent 抓取的锚点。 HTML 锚点协议(Claude Code 读取) 所有结构化信息都通过 data-* 属性暴露。Agent 用 cheerio 选择器即可机械抽取,无需 NLP。 属性 出现位置 含义 选择器示例 data-doc 顶级 <article> 文档类型:prd / sdd article[data-doc=\"prd\"] data-rev article / section 修订号,单调递增 section[data-rev=\"3\"] data-unit-id SDD section 本 section 描述的 Unit section[data-unit-id=\"auth-be\"] data-type SDD section Unit 类型:be / fe / mw / db [data-type=\"be\"] data-depends SDD section 本 Unit 依赖的其他 Unit id(空格分隔) — data-contract <pre> 契约定义 (openapi / tsinterface) pre[data-contract=\"openapi\"] data-acceptance <ul> 验收标准列表 — data-mvcp 本套文档 SDD section 所属 MVCP 象限 [data-mvcp=\"model\"] data-origin PRD section 本 section 来源(用户 + 时间) — data-conflict PRD <aside> 与历史 section 矛盾的标记 — data-changelog PRD <footer> 修订日志区 — 抽取示例 — units.json 怎么从 SDD 派生 // 给 Claude Code 的 cheerio 代码片段 const $ = cheerio. load (sddHtml); const units = $( 'section[data-unit-id]' ). map ((_, el) => ( { id: $(el). attr ( 'data-unit-id' ), type: $(el). attr ( 'data-type' ), depends_on: ($(el). attr ( 'data-depends' ) || '' ). split ( /\\s+/ ). filter (Boolean), contract: $(el). find ( 'pre[data-contract]' ). text (), acceptance: $(el). find ( 'ul[data-acceptance] li' ). map ((_,li)=>$(li). text ()). get (), } )). get (); // → 直接落盘为 units.json 的 .units 字段 MVCP 抽取约定 SDD 四象限页( Model / View / Control / Process )使用统一的 5 字段结构。每个定义都带: 字段 含义 示例 name 唯一标识符 units.json · DevLoopDashboard · LoopController.runProject path 预期落盘路径 server/services/doc-store.js shape 结构定义(TS / TypeBox / Vue props) 代码块 contract 行为契约(前置 / 后置条件) 列表 tests 验收用例 列表 Claude Code 把 SDD 转成 Unit 任务时,每一条 MVCP 定义都可以独立映射到一个 Unit(或多个 Unit 共享)。 文档作为「构建清单」 把整套 PRD + SDD 看作一个机器可解析的 build manifest。复现流程: 具体的 prompt 模板见 plan-reproducibility 。 本文档版本 rev 日期 变更摘要 v0.1 2026-05-12 初版 24 页 · 单 HTML + 拆 24 页 v0.2 2026-05-13 升级到 41 页:新增 PRD 标准章节、SDD MVCP 五页、原型补全、复现指南"},{"f":"requirement.html","title":"原始需求","h1":"原始需求","headings":["Verbatim · 人类原话","结构化解析 · 6 条硬约束","三个隐含约束(用户没说但必须有)","H1 · 收敛性","H2 · 可观测性","H3 · 文档可追溯","用户原话 → 设计映射"],"defs":[],"content":"Verbatim · 人类原话 「我需要一个能实现自动化对代码进行需求整理成 HTML 格式的 PRD、SDD,然后按照 docs 的分解的功能列表,挨个 Unit 开发完成前后端代码,并实现 Unit Test,形成 Unit Loop。当所有 Unit 开发完成,则实现 Integration Test 集成测试。如果集成测试有 Bug,则修改最早期的 PRD、SDD 文档,然后再开发各自的 Unit,实现 Unit 开发测试,然后再集成测试。实现一个小 Loop (Unit) 和一个大 Loop (Integration) 两层的 Loop 的自动化开发流程。然后人类的需求和反馈形成第三层 Loop——人类发送一个新的需求,更新到 PRD 文档中,然后依据 PRD 修改 SDD,再增量 Unit、Integration 开发,直至功能开发完成。」 结构化解析 · 6 条硬约束 ID 约束 系统侧承诺 对应 Agent / 组件 R1 PRD / SDD 必须是 HTML 格式 新 DocStore :HTML 模板 + section 级补丁 PM · Architect R2 SDD 含可机读「功能列表」 SDD 同步产 units.json (DAG 索引) Architect R3 Unit 级前后端代码 + Unit Test 每 Unit 触发 Coder → Tester Coder · Tester R4 全部 Unit 通过 → 集成测试 LoopController L2 闸门 LoopController · Integrator R5 集成失败 → 回写 PRD/SDD → 再跑 Unit BugTriager 归因 → Architect 应用 patch → 受影响 Unit reset BugTriager · Architect R6 人类新需求 → 增量重跑 POST /api/devloop/requirement ,PM 增量更新 PRD PM 三个隐含约束(用户没说但必须有) H1 · 收敛性 无论哪一层 Loop,都必须有「全局 retry 预算」,避免死循环。 H2 · 可观测性 人类要能随时看到「现在卡在哪一步、哪个 Agent、哪个 Unit」。否则 L3 反馈失去意义。 H3 · 文档可追溯 每个 rev 的 PRD/SDD 都要归档,能 diff 出本轮反馈引入了什么变化。 用户原话 → 设计映射 把上面这段话从左到右画成「需求 → 设计」对照流:"},{"f":"scope.html","title":"立意与范围","h1":"立意与范围","headings":["目标","三条设计原则","最大化复用","文档即状态","失败先修文档","不在范围内","本 PRD 包含的信息类型(HTML 8 元素)","① Tables","② Design","③ Illustrations","④ Code","⑤ Interaction","⑥ Workflows","⑦ Spatial","⑧ Images"],"defs":[],"content":"目标 目标不是「再造一个 Devin」,而是把当前散落在 server/src/core/ 、 packages/evolution/ 、 routes/pipeline* 的能力,组装成一个有名字、有状态、可观测的 LoopController。新代码总量控制在 1500 行以内,最大化复用已有抽象。 系统能力 当前状态 本次改造后 多 Agent 调度 master-session + orchestrator(散) 统一由 LoopController 入口 需求 → 代码 无端到端链路 PM → Architect → Coder → Tester → Integrator 闭环 失败修复 EvolutionLoop(只对 Skill) 抽出 retryWithReflection 共享给 L1 文档化输出 仅 Skill 训练 digest PRD / SDD 双 HTML,每 rev 归档 三条设计原则 REUSE 最大化复用 Master / Orchestrate / SessionPool / EvolutionLoop / Scheduler / Pipeline 全部沿用。新增组件不超过 5 个文件,新增模板 6 个。 NEW 文档即状态 PRD / SDD / units.json 是流程的 唯一真源 。Agent 之间通过文档交接,而不是共享内存或 RPC payload。文档版本即流程游标。 PATCH 失败先修文档 L2 集成失败时, 默认先回写 SDD 再重跑 L1。不允许 Coder Agent 直接 patch 别的 Unit 的代码以「绕过」契约。 例外: BugTriager 输出 category=impl_bug 时 不 改 SDD,只 reset 该 Unit — 因为是代码没遵守 SDD,改的是代码不是契约。 不在范围内 能力 原因 留给后续 真实浏览器 E2E 测试 (Playwright headed) 初版用 vitest + 后端集成测试足够覆盖 P5 后续 生产环境部署管线(CI/CD) 聚焦在「跑通」而非「上线」 独立 PRD GitHub PR 自动开 需要外部 token、权限边界复杂 P5 后续 外部 IDE 插件 看板已能展示,编辑器集成性价比低 — 多语言 / 跨语言项目 P1~P4 仅证明 JS/TS 全栈链路 P6+ 本 PRD 包含的信息类型(HTML 8 元素) 用户明确要求 PRD HTML 必须装入 8 类信息: ① Tables 规格 / 映射 / 阶段 ② Design 色板 / 字号 / 排版语言 ③ Illustrations 嵌套环 / 状态机 SVG ④ Code JSON / curl / JS 骨架 ⑤ Interaction Tabs · 折叠 · 侧栏 ⑥ Workflows 跨泳道时序图 ⑦ Spatial 目录树 / 组件方框 ⑧ Images UI wireframe SVG"},{"f":"sdd-control.html","title":"SDD · Control","h1":"Control · API + 服务方法签名","headings":["C-1 · POST /api/devloop/projects","C-2 · POST /api/devloop/projects/:pid/requirement","C-3 · POST /api/devloop/projects/:pid/run","C-4 · GET /api/devloop/projects/:pid/state","C-5 · GET /api/devloop/projects/:pid/stream","C-6 · POST /api/devloop/projects/:pid/sign-off","C-7 · POST /api/devloop/projects/:pid/abandon","C-8 · LoopController(核心服务)","C-9 · DocStore","C-10 · UnitRegistry","C-11 · SSEBroker","C-12 · 中间件","C-13 · agentManager.callAgent 约定","C-14 · LoopController.getProgress(pid)","C-15 · TimelineLogger · 全流程日志写入","C-16 · BugTracker · bugs.ndjson CRUD","C-17 · ChangelogWriter","C-18 · GitVault · 每项目 git 仓库管理","端点-handler-服务依赖矩阵"],"defs":["REST.createProject","REST.postRequirement","REST.run","REST.state","REST.stream","REST.signOff","REST.abandon","LoopController","DocStore","UnitRegistry","SSEBroker","Middleware","AgentCallConvention","LoopController.getProgress","TimelineLogger","BugTracker","ChangelogWriter","GitVault"],"content":"C-1 · POST /api/devloop/projects server/routes/devloop.js router. post ( '/projects' , requireAuth, async (req, res) => { const { company_id, name, requirement, mode = 'sandbox' , // 默认 sandbox target_path // linked-* 时必填 } = req.body; const { pid, root_path } = await loopController. createProject ( { company_id, name, owner: req.user.id, mode, target_path, } ); loopController. onHumanFeedback (pid, { text: requirement, origin: req.user.id } ); res. status ( 201 ). json ( { project_id: pid, mode, root_path, status: 'prd_drafting' , stream_url: `/api/devloop/projects/ ${pid} /stream` , } ); } ); 请求体必填: {company_id, name, requirement} ;可选: {mode, target_path} mode 默认 sandbox ; linked-new 时 target_path 必填且必须是绝对空目录或不存在; linked-existing v1 拒收(v2 启用) target_path 不允许指向系统敏感目录(详见 arch-project-root §⑫ ) company_id 必须与 JWT 中的匹配(防越权) 立即返回 201 + project_id + root_path;PM Agent 异步起 合法 sandbox 请求 → 201 + 含 project_id + root_path=data/.../projects/{pid}/ linked-new + target_path 合法 → 201 + root_path=用户指定路径 + .git 已初始化 linked-new + target_path 不存在父目录 → 400 拒绝 linked-new + target_path 是 /etc → 403 sensitive_path company_id 不匹配 JWT → 403 缺字段 → 400 + 字段名 C-2 · POST /api/devloop/projects/:pid/requirement server/routes/devloop.js router. post ( '/projects/:pid/requirement' , requireAuth, async (req, res) => { const { text, origin } = req.body; loopController. onHumanFeedback (req.params.pid, { text, origin: origin || req.user.id } ); res. status ( 202 ). json ( { accepted: true , queued: true } ); } ); 立即 202,不等 PM 跑完 多个反馈同时到 → 队列化,按时序处理 项目 status=shipped-final 或 abandoned → 拒绝(409) C-3 · POST /api/devloop/projects/:pid/run server/routes/devloop.js router. post ( '/projects/:pid/run' , requireAuth, async (req, res) => { // 续跑(如果之前 blocked / paused,重启循环) loopController. runProject (req.params.pid); res. json ( { running: true } ); } ); 等价于「resume」,对 status=running 是 no-op blocked / l2_exhausted 状态调用此端点 → 重置预算并继续 C-4 · GET /api/devloop/projects/:pid/state server/routes/devloop.js router. get ( '/projects/:pid/state' , requireAuth, (req, res) => { res. json (loopController. getState (req.params.pid)); } ); // 返回结构: { project_id, prd_rev, sdd_rev, current_loop: 'L1' | 'L2' | 'L3' | 'idle' , units: Unit[], l2_rounds, l2_budget, agents_active: string [], token_used, token_budget, } 读时不加锁(不一致几毫秒可接受) response 体限 100KB 以内(大项目 unit 多) C-5 · GET /api/devloop/projects/:pid/stream server/routes/devloop.js router. get ( '/projects/:pid/stream' , requireAuth, (req, res) => { res. writeHead ( 200 , { 'Content-Type' : 'text/event-stream' , 'Cache-Control' : 'no-cache' , 'Connection' : 'keep-alive' , } ); sseBroker. subscribe (req.params.pid, res, { replayFromOffset: req.query.last_event_id, } ); } ); 支持 Last-Event-ID 断线重连 心跳:每 25s 发一条 :keepalive 注释 项目结束(shipped/abandoned)→ 发 close 帧后关闭 C-6 · POST /api/devloop/projects/:pid/sign-off server/routes/devloop.js router. post ( '/projects/:pid/sign-off' , requireAuth, (req, res) => { loopController. signOff (req.params.pid, req.user.id); res. json ( { status: 'shipped-final' } ); } ); 仅项目 owner 或 admin 能调用。设置 status=shipped-final,停止接收任何新反馈。 C-7 · POST /api/devloop/projects/:pid/abandon server/routes/devloop.js router. post ( '/projects/:pid/abandon' , requireAuth, (req, res) => { loopController. abandon (req.params.pid, req.user.id); res. json ( { status: 'abandoned' } ); } ); C-8 · LoopController(核心服务) server/src/core/loop-controller.js module.exports .loopController = { createProject ( { company_id, name, owner } ): Promise < string >, runProject (pid: string ): Promise < 'shipped' | 'blocked' | 'l2_exhausted' >, runUnit (pid: string , unitId: string ): Promise <UnitResult>, onHumanFeedback (pid: string , { text, origin } ): void , onL2Failure (pid: string , runId: string ): Promise < void >, signOff (pid: string , byUser: string ): void , abandon (pid: string , byUser: string ): void , getState (pid: string ): ProjectState, } ; 所有 Agent 调用通过 agentManager.callAgent(name, input) ,不直接起进程 runProject 是幂等的:同一 pid 多次调用合并为一个执行"},{"f":"sdd-model.html","title":"SDD · Model","h1":"Model · 数据结构全集","headings":["M-1 · ProjectMeta","M-2 · units.json","M-3 · prd/ 多页 PRD 站点","M-4 · sdd.html 结构","M-5 · contract.json(每 Unit)","M-6 · integration/spec.json","M-7 · runs/result.json (L1)","M-8 · integration/runs/result.json (L2)","M-9 · triage.json","M-10 · AgentRuntimeState · 持久化运行态","M-11 · AgentMemory · 跨项目长期记忆","M-12 · AgentTodolist · 单次调用的子任务清单","M-13 · ProcessTimeline · 全流程统一日志","M-14 · bugs.ndjson · 跨 run 跨 unit Bug 聚合","M-15 · changelog.ndjson · 版本变更流","归档命名约定(统一目录形式)","无 SQL · 无新表"],"defs":["ProjectMeta","UnitsJson","PRD-Site","SDD-HTML","ContractJson","IntegrationSpec","RunResult","IntegrationResult","TriageJson","AgentRuntimeState","AgentMemory","AgentTodolist","ProcessTimeline","BugsNdjson","ChangelogNdjson"],"content":"M-1 · ProjectMeta data/companies/{cid}/projects/{pid}/project.json // TypeBox schema const ProjectMeta = Type. Object ( { id: Type. String ( { pattern: '^p-[a-z0-9]{6}$' } ), company_id: Type. String (), name: Type. String (), // 项目根 (见 arch-project-root) mode: Type. Union ( [ Type. Literal ( 'sandbox' ), // 默认,根在 data/.../projects/{pid}/ Type. Literal ( 'linked-new' ), // 用户指定空目录 Type. Literal ( 'linked-existing' ), // v2:复用已有 git repo ] ), root_path: Type. String (), // 绝对路径,所有派生路径的源 status: Type. Union ( [ Type. Literal ( 'prd_drafting' ), Type. Literal ( 'running' ), Type. Literal ( 'blocked' ), Type. Literal ( 'shipped' ), Type. Literal ( 'shipped-final' ), Type. Literal ( 'abandoned' ), ] ), created_at: Type. String ( { format: 'date-time' } ), updated_at: Type. String ( { format: 'date-time' } ), owner: Type. String (), token_budget: Type. Number ( { default: 5_000_000 } ), token_used: Type. Number ( { default: 0 } ), compressOldRuns: Type. Boolean ( { default: false } ), } ); id 全局唯一,跨 company 不重复 status 只能按状态机合法转换(见 sdd-process.html · M-process-project) token_used > token_budget 时 LoopController 自动挂起项目 M-2 · units.json data/companies/{cid}/projects/{pid}/units.json const Unit = Type. Object ( { id: Type. String ( { pattern: '^[a-z]+(-[a-z]+)*-(be|fe|mw|db)$' } ), type: Type. Union ( [ Type. Literal ( 'be' ), Type. Literal ( 'fe' ), Type. Literal ( 'mw' ), Type. Literal ( 'db' ) ] ), status: Type. Union ( [ Type. Literal ( 'pending' ), Type. Literal ( 'coding' ), Type. Literal ( 'testing' ), Type. Literal ( 'failed' ), Type. Literal ( 'passed' ), Type. Literal ( 'blocked' ), ] ), depends_on: Type. Array (Type. String ()), retries: Type. Number ( { default: 0 } ), retry_budget: Type. Number ( { default: 5 } ), contract_hash: Type. String ( { pattern: '^sha256:[a-f0-9]{16}$' } ), last_run: Type. Optional (Type. String ()), } ); const UnitsJson = Type. Object ( { project_id: Type. String (), prd_rev: Type. String (), sdd_rev: Type. String (), units: Type. Array (Unit), l2_rounds: Type. Number ( { default: 0 } ), l2_budget: Type. Number ( { default: 3 } ), } ); DAG 必须无环(DocStore 写入前用拓扑排序校验) depends_on 引用的 id 必须存在于本文件 units 中 contract_hash = sha256(SDD 中对应 section <pre data-contract> 文本) 截前 16 位 循环依赖 → 拒绝写入 未知 id → 拒绝写入 所有 status 为 passed 且 hash 与当前 SDD 匹配 → l2GateOpen 返回 true M-3 · prd/ 多页 PRD 站点 data/companies/{cid}/projects/{pid}/prd/ (目录) // 目录布局 (PM Agent 产出,见 agents-pm.html §2.1) prd/ ├── index.html // Hero + reading guide entry ├── reading-guide.html ├── assets/ │ ├── shell.css // 复制自模板 │ ├── shell.js // 3 级菜单 + 搜索 │ └── search-index.json ├── product/ │ ├── background.html │ ├── personas.html │ ├── original-req.html │ ├── scope.html │ ├── success-metrics.html │ └── glossary.html ├── feature/ │ └── feature-tree.html ├── flow/ │ └── user-flows.html └── prototype/ └── proto-{viewName}.html // 每个 chapter HTML 内的结构 (cheerio 校验): <article data-doc = \"prd\" data-rev = \"N\" > <section data-rev = \"M\" data-origin = \"{user@iso}\" > <h3> ... </h3> <p> ... </p> </section> <aside data-conflict > ... </aside> // 可选,index.html only <footer data-changelog > ... </footer> </article> 顶级必须是 <article data-doc=\"prd\"> ,每页都有 每个 section 必含 <h3> + 至少一个 <p> section data-rev 单调递增(页内), article data-rev 取该页内最大 每页至少覆盖 4/8 类 HTML 信息(见 agents-pm §2.2) data-conflict aside 仅出现在 index.html 顶部,存在时整个项目暂停等人 11 个必备章节缺一个 → validate_prd_site 失败 init 模式后 11 章节全在 incremental 模式后仅 affected 章节被改 多 rev 后 history/ 含旧版归档 M-4 · sdd.html 结构 data/companies/{cid}/projects/{pid}/sdd.html <article data-doc = \"sdd\" data-derived-from = \"prd@revN\" > <section data-unit-id = \"auth-be\" data-type = \"be\" data-depends = \"\" > <h3> Unit · auth-be </h3> <p> ...功能描述... </p> <pre data-contract = \"openapi\" > POST /api/auth/login ... </pre> <ul data-acceptance > <li> acceptance criteria 1 </li> </ul> </section> </article> 每个 unit section 必含 contract + acceptance data-depends 空格分隔 unit id 列表,可为空 data-derived-from 指向产生它的 prd 版本 M-5 · contract.json(每 Unit) .../units/{unitId}/contract.json con"},{"f":"sdd-overview.html","title":"SDD MVCP 总览","h1":"SDD · MVCP 总览","headings":["为什么是 MVCP 而不是 MVC","四象限","每个定义的「五字段标准」","HTML 模板示例","抽取流程","四象限对应 Agent"],"defs":[],"content":"为什么是 MVCP 而不是 MVC 传统 MVC 三分对一个有大量「长进程 / 异步循环」的系统不够用。DevLoop 系统里: L1/L2/L3 是 长时运行的状态机 每个 Agent 是 长寿子进程 (ACP session 复用) SSE Broker 是 持续推送通道 这些都不属于 Controller(即时响应请求),也不属于 Model(数据)。给它们一个独立象限 Process 才有归处。 四象限 每个定义的「五字段标准」 所有 MVCP 页里的每条定义都强制遵循下表结构,便于 Claude Code 用同一套抽取器处理: 字段 类型 含义 抽取选择器 name string · 必填 唯一标识符 · 命名空间.对象名 section[data-def-name] path string · 必填 预期落盘路径或文件位置 [data-def-name] code.path shape code · 必填 TypeBox / TS interface / Vue defineProps / SQL DDL [data-def-name] pre.shape contract list · 选填 前置 / 后置条件 / 不变式 [data-def-name] ul.contract li tests list · 选填 验收用例(Tester Agent 直接转 vitest) [data-def-name] ul.tests li HTML 模板示例 <section data-def-name = \"DocStore.writePRD\" data-mvcp = \"control\" > <h3> DocStore.writePRD(pid, html) </h3> <code class = \"path\" > server/services/doc-store.js </code> <pre class = \"shape\" > async function writePRD(pid: string, html: string): Promise<{rev: number}> </pre> <ul class = \"contract\" > <li> 前置:html 必须包含顶级 article[data-doc=\"prd\"] </li> <li> 后置:旧版本归档到 history/prd-rev-{rev}.html </li> </ul> <ul class = \"tests\" > <li> 写入后 readPRD 能读回完全一致内容 </li> <li> 非法 HTML 抛 ValidationError </li> </ul> </section> 本套文档因美观需要不完全遵守这个最严格的 HTML 模板,但每条定义至少含 name + path + shape。完全机械化版会在 P1 实施时由 Architect Agent 产出。 抽取流程 // Claude Code 抽取 build-manifest 的伪代码 const manifest = { model: [] , view: [] , control: [] , process: [] } ; for ( const mvcp of [ 'model' , 'view' , 'control' , 'process' ] ) { const html = await fetch ( `sdd- ${mvcp} .html` ). then (r => r. text ()); const $ = cheerio. load (html); $( '[data-def-name]' ). each ((_, el) => { manifest[mvcp]. push ( { name: $(el). attr ( 'data-def-name' ), path: $(el). find ( 'code.path' ). first (). text (), shape: $(el). find ( 'pre.shape' ). text (), contract: $(el). find ( 'ul.contract li' ). map ((_,li) => $(li). text ()). get (), tests: $(el). find ( 'ul.tests li' ). map ((_,li) => $(li). text ()). get (), } ); } ); } fs . writeFileSync ( 'build-manifest.json' , JSON.stringify (manifest, null , 2 )); 结果是一份 build-manifest.json ,能直接喂给 Architect Agent 生成 units.json + Unit spec 文件。 四象限对应 Agent 象限 谁负责写 谁负责消费 Model Architect Coder(读 schema 生成数据访问代码)· DocStore(自身实现) View Architect Coder(fe 类 Unit 直接对应) Control Architect Coder(be 类 Unit)· Integrator(跑端点) Process Architect 所有 Agent 都要读懂 Process 才知道自己在生命周期哪一步"},{"f":"sdd-process.html","title":"SDD · Process","h1":"Process · 长进程与状态机","headings":["P-1 · L1 Unit Loop 状态机","转换表(完整)","伪代码","P-2 · L2 Integration Loop 状态机","L2 闸门判定","L2 主循环","四种 triage 类别的分支","P-3 · L3 Human Feedback 队列","P-4 · Agent 生命周期","P-5 · SSE Broker 心跳与 Replay","P-6 · 清理与归档","P-7 · 预算守护","P-8 · Agent State 持久化 + 崩溃恢复","调用前 / 中 / 后三阶段","崩溃恢复流程","恢复决策三态","Memory 注入流程","触发 SSE 事件","P-9 · Bug 状态机","状态机","合法转换","自动转换 vs 人为转换","与 L1/L2 的耦合","新增 SSE 事件","P-10 · 自动 git commit 流程","recoverOnBoot 加 git 清理","新增 SSE 事件","三层 Loop 嵌套伪代码(最终版)"],"defs":["Process.L1","Process.L2","Process.L3","Process.AgentLifecycle","Process.SSE","Process.Cleanup","Process.Budget","Process.StatePersistence","Process.BugLifecycle","Process.GitCommit"],"content":"P-1 · L1 Unit Loop 状态机 loop-controller.js → runUnit() 转换表(完整) from to 触发 副作用 pending coding LoopController 调度到 wave markCoding + SSE unit.state coding testing Coder 返回 success markTesting + SSE coding failed Coder 抛错(沙箱越权 / 超时) fail · reason=coder_error testing passed Tester result.ok = true pass + 写 contract_hash + SSE testing failed Tester result.ok = false fail · 写 result.json failed coding retries < retry_budget retries++ · 调 Coder 带 history failed blocked retries ≥ retry_budget block + SSE human_intervention_needed passed pending L2 触发 reset retries 重置 · 旧代码归档 blocked pending 人类显式 resume retries 重置 伪代码 async function runUnit (pid, unitId) { const unit = unitRegistry. list (pid). find (u => u. id === unitId); let failures = [] ; for ( let i = unit. retries ; i < unit. retry_budget ; i++) { unitRegistry. markCoding (pid, unitId); const coderRes = await agentManager. callAgent ( 'coder' , { pid, unit_id: unitId, history: failures } ); if (!coderRes. ok ) { unitRegistry. fail (pid, unitId, 'coder_error' ); continue ; } unitRegistry. markTesting (pid, unitId); const testerRes = await agentManager. callAgent ( 'tester' , { pid, unit_id: unitId } ); if (testerRes. ok ) { const hash = docStore. hashContract ( await readContract(pid, unitId)); unitRegistry. pass (pid, unitId, hash); return { ok: true , retries: i } ; } failures. push (testerRes. failures ); unitRegistry. fail (pid, unitId, 'test_red' ); } unitRegistry. block (pid, unitId, 'budget_exceeded' ); return { ok: false } ; } 同一 Unit 任何时刻只允许一个 runUnit 实例(用 Map<unitId, Promise> 去重) retry 之间不复用 Agent session(每次新 ACP session,保证干净) P-2 · L2 Integration Loop 状态机 loop-controller.js → onL2Failure() · runProject() L2 段 L2 闸门判定 function l2GateOpen (pid) { const units = unitRegistry. list (pid); const sdd = docStore. readSDD (pid); return units. every (u => { if (u. status !== 'passed' ) return false ; const expectedHash = contractHashFromSDD(sdd, u. id ); return u. contract_hash === expectedHash; } ); } L2 主循环 async function runL2 (pid) { const intRes = await agentManager. callAgent ( 'integrator' , { pid } ); if (intRes. ok ) { sseBroker. emit (pid, 'project.shipped' , { }); return 'shipped' ; } const tri = await agentManager. callAgent ( 'bug-triager' , { pid, run_id: intRes. run_id } ); if (tri. confidence < 0.5 || tri. category === 'spec_ambiguous' ) { sseBroker. emit (pid, 'human_intervention_needed' , tri); return 'l2_blocked' ; } if (tri. category === 'infra_flake' ) { return runL2 (pid); // 直接重跑,不动 Unit } if (tri. category === 'impl_bug' ) { // 不改 SDD,只 reset 受影响 Unit unitRegistry. reset (pid, tri. affected_units ); return 'retry_l1' ; } // contract_mismatch → patch SDD + reset await agentManager. callAgent ( 'architect' , { pid, mode: 'patch' , patch: tri. proposed_sdd_patch } ); unitRegistry. reset (pid, tri. affected_units ); const project = getProjectMeta (pid); if (++project. l2_rounds >= project. l2_budget ) { sseBroker. emit (pid, 'project.exhausted' , { last_triage: tri } ); return 'l2_exhausted' ; } return 'retry_l1' ; } 四种 triage 类别的分支 category 是否改 SDD 是否 reset Unit 是否计入 l2_rounds contract_mismatch 是 是 是 impl_bug 否 是 否(不烧预算) spec_ambiguous 否(升 L3) 否 否 infra_flake 否 否 否 P-3 · L3 Human Feedback 队列 loop-controller.js → onHumanFeedback() // 每个项目维护一个反馈队列,串行消费 const feedbackQueues = new Map < string , Feedback[]>(); function onHumanFeedback (pid, feedback) { if (!feedbackQueues. has (pid)) feedbackQueues. set (pid, [] ); feedbackQueues. get (pid). push (feedback); // 单 worker per pid if (!isWorking(pid)) drainFeedback(pid); } async function drainFeedback (pid) { while (feedbackQueues. get (pid). length ) { const fb = feedbackQueues. get (pid). shift (); // 1. 等当前 L1 / L2 跑完(不打断) await waitForCurrentLoop(pid); // 2. PM 增量补 PRD await agentManager. callAgent ( 'pm' , { pid, feedback: fb } ); // 3. Architect 重算 SDD + units.json (mode=generate) await agentManager. callAgent ( 'architect' , { pid, mode: 'generate' } ); // 4. UnitRegistry.diffWithSDD → reset changed/removed co"},{"f":"sdd-view.html","title":"SDD · View","h1":"View · UI 组件全集","headings":["V-1 · DevLoopDashboard.vue","V-2 · DashboardHeader.vue","V-3 · LoopStatusBar.vue","V-4 · UnitsDAG.vue","V-5 · LiveAgentStream.vue","V-6 · PrdDiffPanel.vue","V-7 · FeedbackModal.vue","V-8 · UnitDrawer.vue","V-9 · 路由表","V-10 · i18n key 清单","V-11 · 设计 token(CSS 变量)","组件渲染依赖关系"],"defs":["DevLoopDashboard","DashboardHeader","LoopStatusBar","UnitsDAG","LiveAgentStream","PrdDiffPanel","FeedbackModal","UnitDrawer","Router","I18nKeys","DesignTokens"],"content":"V-1 · DevLoopDashboard.vue web/src/views/DevLoopDashboard.vue // Composition API · setup script const props = defineProps< {} >(); // 无 props,从 route param 取 pid const route = useRoute (); const pid = computed(() => route. params . pid as string ); const state = ref<ProjectState | null>( null ); const sse = useEventSource( `/api/devloop/projects/ ${pid.value} /stream` ); const modalOpen = ref( false ); const drawer = ref<Unit | null>( null ); // 组件树: // DashboardHeader → LoopStatusBar → UnitsDAG → LiveAgentStream + PrdDiffPanel // 浮层:FeedbackModal · UnitDrawer 挂载 5 秒内必须发起 GET /state 请求 SSE 断线自动重连(指数退避,最长 30s) 页面卸载时关 SSE V-2 · DashboardHeader.vue web/src/components/devloop/DashboardHeader.vue defineProps< { project: { id: string , name: string , status: string } } >(); defineEmits< { feedback: [] // 点击 + NEW REQUIREMENT } >(); // 渲染: // - 黑底标题栏 // - 项目名(Fraunces 22px) // - 右上紫色按钮 V-3 · LoopStatusBar.vue web/src/components/devloop/LoopStatusBar.vue defineProps< { l1: { passed: number , testing: number , pending: number , failed: number } , l2: { lastRunAt?: string , status: 'idle'|'running'|'failed'|'shipped' , rounds: number } , l3: { rev: number , awaitingFeedback: boolean } } >(); 三张卡片对应 emerald / amber / violet 三色,CSS 用 --L1/L2/L3 token L2 running 时 amber 卡片有 pulse 动画 L3 awaitingFeedback=true 时显示 toast V-4 · UnitsDAG.vue web/src/components/devloop/UnitsDAG.vue defineProps< { units: Unit [] } >(); defineEmits< { select: [ unit: Unit ] } >(); // 内部使用 SVG <rect> + <line> // > 50 unit 时切 d3-force 自动布局 节点配色由 unit.status 决定(见 ui-design-system 色板) 节点点击 emit select 事件,父组件打开 UnitDrawer 箭头方向 = depends_on (上游 → 下游) V-5 · LiveAgentStream.vue web/src/components/devloop/LiveAgentStream.vue defineProps< { events: AgentStreamEvent [] // 由父组件从 SSE 过滤 agent.stream 事件 } >(); // 渲染: // - 黑底 #0E0E10 终端样式 // - 最近 500 行,老的丢 // - PASS / FAIL 着色(绿/橙) // - 自动滚到底部,除非用户手动滚 V-6 · PrdDiffPanel.vue web/src/components/devloop/PrdDiffPanel.vue defineProps< { prdHtml: string , prevRev: number | null } >(); // 内部用 diff-dom 客户端算 section 级 diff // 新增 section 绿色 + 标记 / 删除红色删除线 / 修改黄色 V-7 · FeedbackModal.vue web/src/components/devloop/FeedbackModal.vue defineProps< { open: boolean } >(); defineEmits< { submit: [ text: string ] , close: [] , } >(); // 单 textarea + 字数计数 + Ctrl+Enter 提交 // 提交后立即 emit close,父组件负责发请求 空文本 → 提交按钮 disabled > 4000 字 → 警告但不阻止 Esc 触发 close V-8 · UnitDrawer.vue web/src/components/devloop/UnitDrawer.vue defineProps< { unit: Unit | null , runs: RunResult [] // 最近 3 次 run } >(); defineEmits< { close: [] } >(); // 右侧抽屉,从 right 滑入 // 显示:spec.json 摘要 · contract.json · 最近 3 次 run 摘要 · 跳转完整日志按钮 V-9 · 路由表 web/src/router/index.js // 在现有 routes 数组追加 { path: '/devloop/:pid' , name: 'DevLoop' , component: () => import ( '@/views/DevLoopDashboard.vue' ), meta: { requiresAuth: true } , } , { path: '/devloop' , // 项目列表 name: 'DevLoopList' , component: () => import ( '@/views/DevLoopList.vue' ), } DevLoopList.vue 在 P4 是可选的,先用 GET /api/devloop/projects 列出。 V-10 · i18n key 清单 web/src/i18n/{zh-CN,en-US,...}/devloop.json // 每个 locale 一份扁平 JSON:Record<string, string> { \"devloop.loop.l1\" : \"...\" , \"devloop.state.pending\" : \"...\" , \"devloop.btn.newRequirement\" : \"...\" // ...完整 15 个 key 见下表 } key zh-CN en-US devloop.loop.l1 单元循环 Unit Loop devloop.loop.l2 集成循环 Integration Loop devloop.loop.l3 人类反馈 Human Feedback devloop.state.pending 待处理 Pending devloop.state.coding 编码中 Coding devloop.state.testing 测试中 Testing devloop.state.failed 失败 Failed devloop.state.passed 已通过 Passed devloop.state.blocked 阻塞 Blocked devloop.btn.newRequirement + 新增需求 + New Requirement devloop.btn.signOff 验收 Sign-off devloop.btn.abandon 放弃项目 Abandon devloop.toast.shipped 项目已交付! Project shipped! devloop.toast.needHelp 需要人工介入 Need human help devloop.empty 尚无项目 No projects yet 中英文必填,其他 4 种语言可走 fallback。 V-11 · 设计 token(CSS 变量) web/src/styles/devloop.css :root { --L1: #0E8C5A ; --L1-tint: #E2F2EA ; --L2: #C77C2C ; --L2-tint: #F7E8D2 ; --L3: #6E48C3 ; --L3-tint: #ECE4FB ; --alert: #C7341A ; } // 与 Lemo"},{"f":"ui-dashboard.html","title":"DevLoop Dashboard","h1":"DevLoop Dashboard","headings":["Wireframe","四个核心区域","① Header","② Loop 状态栏","③ Units DAG","④ Live 流 + PRD diff","组件树","路由 + i18n","性能考虑"],"defs":[],"content":"Wireframe localhost:5173/devloop/p-731042 四个核心区域 ① Header 项目名 + 「+ NEW REQUIREMENT」按钮(L3 入口)。点开弹 textarea,提交后走 POST .../requirement 。 ② Loop 状态栏 三张并排卡片显示 L1 / L2 / L3 当前状态。点击任意一张展开该 Loop 的详细历史时间线。 ③ Units DAG 复用 PipelineEditor.vue 的 SVG DAG 渲染。每个节点点击展开「该 Unit 的最近 3 次 run」抽屉。 ④ Live 流 + PRD diff 左:终端样式的 Agent stdout 流(SSE)。右:当前 PRD 与上一 rev 的 section 级 diff。 组件树 // web/src/views/DevLoopDashboard.vue <template> <PageLayout> <DashboardHeader :project= \"project\" @feedback= \"onFeedback\" /> <LoopStatusBar :l1= \"l1Stats\" :l2= \"l2Stats\" :l3= \"l3Stats\" /> <UnitsDAG :units= \"units\" @select= \"openUnitDrawer\" /> <LiveAgentStream :stream= \"sse\" /> <PrdDiffPanel :prd= \"prdHtml\" :history= \"prdHistory\" /> <UnitDrawer v-if= \"drawer\" :unit= \"drawer\" @close= \"drawer=null\" /> <FeedbackModal v-if= \"modalOpen\" @submit= \"sendRequirement\" /> </PageLayout> </template> 总行数预估 ~400,含模板 + script + 样式。其中 UnitsDAG 大约 120 行——这里有最高的复用率(参考 PipelineEditor)。 路由 + i18n 路由: { path: '/devloop/:pid', name: 'DevLoop', component: DevLoopDashboard } 加进 web/src/router/index.js 。 i18n: 新增 key 前缀 devloop.* 。涉及的标签: loop.l1 · loop.l2 · loop.l3 · state.passed · state.testing · btn.newRequirement 等 ~20 个。中英文必填,其他 4 种语言可走 fallback。 性能考虑 SSE: 使用浏览器原生 EventSource ,断线自动重连。 大 stream: LiveAgentStream 保留最近 500 行,老的丢;用户想看完整日志走「open in new tab」按钮跳到 /stream.log 。 PRD diff: 使用客户端 diff-dom ,不在后端计算(项目 PRD 一般 < 50KB,浏览器算得动)。 DAG 大于 50 Unit: 切换到 d3-force 自动布局,否则手动栅格。"},{"f":"ui-design-system.html","title":"视觉设计语言","h1":"视觉设计语言","headings":["色板","Loop 三主色","L1 · Unit","L2 · Integration","L3 · Human","语义辅助色","Alert","Warn","Ink","Paper","字体阶梯","字体使用规则","排版规则","纸面节奏","表格","代码块","组件目录","留白与节奏"],"defs":[],"content":"色板 Loop 三主色 L1 · Unit #0E8C5A · emerald tint: #E2F2EA L2 · Integration #C77C2C · amber tint: #F7E8D2 L3 · Human #6E48C3 · violet tint: #ECE4FB 语义辅助色 Alert #C7341A · 错误 / failure / blocked Warn #8a6b00 · testing / retry Ink #0E0E10 · 文字 / 边框 Paper #FAF7F1 · 背景 字体阶梯 角色 字体 字号 用途 Display Fraunces 300 34–56 px 页面 Hero / 项目名 H2 Fraunces 500 24–34 px 章节标题 H3 Fraunces 600 20 px 子标题 H4 / Tag Inter 700 · uppercase 11–13 px 类别标签 Body Inter 400 16 px 正文 Mono JetBrains Mono 500 11–13 px 状态码 / API / 日志 / kbd 字体使用规则 所有 装饰性 / 表意性 的文字(标题、品牌名、Hero)→ Fraunces。 所有 运行时数据 / 技术标识 (状态、API、命令、Unit id)→ JetBrains Mono。两者并用能立刻区分「这是设计」vs「这是状态」。 其他 → Inter。 排版规则 纸面节奏 章节顶部用 h4 全大写小标签 (类似 Section 01) 作为定位锚。 正文段落首字 dropcap 仅在长篇章节用,强调起势。 表格 表头 paper-soft 背景 + Mono uppercase;行 hover 高亮;行内 code 用米色背景区分纯文本。 代码块 黑底 #0E0E10 + 米色 #F3EFE5 文字。彩色 token:橙 (k)、绿 (s)、蓝 (n)、黄 (p)。终端感不夸张。 组件目录 本 PRD HTML 文档中复用的核心组件,全部 token 化、可在 LemonBoss 前端 Dashboard 中复刻: 组件 CSS class 变体 Tag pill .tag L1 / L2 / L3 / NEW / REUSE / PATCH Card .card .l1 / .l2 / .l3 边色 Callout .callout 默认(红) / .warn(黄) / .ok(绿) Code block pre.code 含 .k .s .n .p .v token File tree pre.tree 含 .new .reuse .com Tabs .tabs data-tabs 自动初始化 Accordion details.acc 原生 details + 自定义 marker Mock browser .mock 含 .topbar + .url Sidebar aside.sidebar Section group 折叠 留白与节奏 页面布局基于 8px 网格。常用间距: token px 用途 xs 8 tag 内距 sm 16 段间距 md 24 卡片内距 lg 40 section 间距 xl 64 页边 xxl 80 Hero 上下"}]} | ||
| 2 | \ No newline at end of file | 1 | \ No newline at end of file |
| 2 | +{"version":"1","generated":"2026-05-13T08:13:35.751Z","pages":51,"entries":[{"f":"agents-architect.html","title":"Architect Agent · 完整 Skill 规范","h1":"Architect Agent · 完整 Skill 规范","headings":["Architect Agent","① 功能需求","② Skill 设计","2.1 输出形态 · 多页 SDD 站点 + 两个 JSON","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集(含签名)","2.4 禁用的工具 / 行为","③ 持久化集成(M-10/11/12/13 接入)","3.1 Architect 写 memory 的判断","④ I/O 契约","4.1 输入 schema","4.2 输出 schema","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码(4 个)","7.1 hash-contract.js · 固定算法 contract hash","7.2 unit-extractor.js · 从 PRD feature-tree 派生 Unit 草案","7.3 sdd-section-patcher.js · patch 模式精准应用","7.4 sdd-validator.js · 三方一致性校验","⑧ 自检 Checklist · 完工前必跑","⑨ 失败模式与降级","⑩ 与其他 Agent 的握手摘要","⑪ 「测试 Architect Agent 是否真能跑」的实战命令"],"defs":["X","auth-be","</span><span class="],"content":"A Architect Agent 系统架构师 · SDD/Units/IntegrationSpec 三件套维护人 · 持久化运行 Loop L3 + L2 Model Opus 4.7 Modes generate · patch · resume 沙箱 sdd/** + units.json + integration/spec.json + memory/devloop/architect/ 会话复用 是(SessionPool key = architect.cid.pid) ① 功能需求 三种调用模式: generate · PRD 大变(init / 大改 / 第一次)→ 重新派生整套 SDD + units.json + integration/spec.json patch · BugTriager 给来一个 proposed_sdd_patch → 精准应用,触发受影响 Unit reset resume · 崩溃恢复 读完整 PRD 站点: 必须遍历 prd/product/ + prd/feature/ + prd/flow/ 全部章节,不允许跳读。 拆 Unit: 每个 PRD feature → 1+ Unit。同一 feature 涉及前后端必须至少拆 be + fe ;含中间件 → 加 mw ;要 DB 迁移 → 加 db 。 命名严格: Unit id 必须匹配 /^[a-z]+(-[a-z]+)*-(be|fe|mw|db)$/ 。BugTriager 后续用 字符串匹配 归因,不能模糊。 契约固化: 每个 Unit 输出 contract(OpenAPI for be,TS interface for fe,schema for db)。 contract_hash 算法不能创新 (参见 §7.3)。 DAG 必无环: units.json 写入前用拓扑排序校验。 集成测试方案(初版选 A): Architect 同步产 integration/spec.json ,每条 case 必须覆盖至少 2 个 Unit 的协同。 patch 模式不重生: 只改 triage.affected_units 涉及的 section,其他全部保留。 ② Skill 设计 2.1 输出形态 · 多页 SDD 站点 + 两个 JSON data/companies/{cid}/projects/{pid}/ ├── sdd/ # Architect 写 │ ├── index.html # Hero · 8 类信息 ribbon │ ├── overview.html # MVCP 总览 + 抽取协议 │ ├── assets/ │ │ ├── shell.css │ │ ├── shell.js # SITE 注入 │ │ └── search-index.json │ └── mvcp/ │ ├── model.html # 所有 Unit 涉及的数据 schema 聚合 │ ├── view.html # 所有 fe 类 Unit 的组件签名 │ ├── control.html # 所有 be / mw 类 Unit 的 API + 服务 │ └── process.html # 长进程 / 状态机(可能空) ├── units.json # M-2 DAG └── integration/ └── spec.json # M-6 端到端用例 每个 MVCP 页内含若干 <section data-def-name=... data-mvcp=...> ,与 docs/prd/newfunction/sdd-{model,view,control,process}.html 同构(直接复制本套 docs 的样式约定)。 2.2 完整 System Prompt(可拷贝直用) 落 system/templates/devloop-architect.md 。LoopController 注入时在头部补 ## Memory ,尾部补 ## Current Task Context (含 PRD rev、triage(如有)、call_id)。 # ROLE 你是 LemonBoss DevLoop 的 Architect Agent。你把 PM Agent 产出的 PRD 站点 转化为 SDD 站点 + units.json + integration/spec.json,让下游 Coder/Tester/ Integrator/BugTriager 都能照单干活。 # MODES (输入参数 mode 决定行为) - \"generate\" PRD 第一次出/重大变化 → 重新派生整套 SDD - \"patch\" 接 triage.json,精准应用 proposed_sdd_patch - \"resume\" 从 resume_token 恢复未完 todolist # OUTPUT FORMAT (硬约束 - DocStore 拒残) [A] 三件套必齐: sdd/ 5 个 HTML + 3 个 asset units.json 含 DAG · contract_hash · l2 预算 integration/spec.json 端到端 cases [B] 每个 MVCP 定义五字段: <section data-def-name=\"X\" data-mvcp=\"model|view|control|process\"> <h2>X · {简短描述}</h2> <code class=\"path mono\">{预期落盘路径}</code> <pre class=\"code shape\">{TypeBox / TS / Vue props / SQL DDL}</pre> <ul class=\"contract\"><li>前置/后置/不变式</li></ul> <ul class=\"tests\"><li>验收用例(给 Tester 转 vitest)</li></ul> </section> [C] Unit id 严格命名: /^[a-z]+(-[a-z]+)*-(be|fe|mw|db)$/ 示例: auth-be · profile-fe · session-mw · todo-db 禁止: AuthBE · auth_be · auth-backend · auth (无后缀) [D] 每个 Unit 必须有: - contract (非空,OpenAPI/TS/schema) - contract_hash (sha256:{16 chars},由 hash_contract 工具计算) - depends_on (string[],可为空) - 在 sdd/mvcp/*.html 中至少出现 1 次 [E] integration/spec.json 每 case 必须: - 至少 2 步 - involves 字段是步骤 unit 的并集 - 引用的所有 unit 在 units.json 存在 # MANDATORY WORKFLOW ## step 0 · 读取上下文 ├─ 必读 prd/ 全部章节(尤其 prd/feature/feature-tree.html · prd/flow/user-flows.html) ├─ 必读 changelog.ndjson 最后 5 条 ├─ generate / patch 模式: 读现有 sdd/ + units.json + integration/spec.json (若存在) ├─ patch 模式: 必读 integration/runs/{runId}/triage.json └─ memory section 已在 prompt 顶部注入,不重复读 ## step 1 · write_todolist ### generate 模式典型 todolist (12 项): step-001 解析 prd/feature/feature-tree.html → 列出 N 个 feature step-002 对每个 feature 决定 be/fe/mw/db 拆分 step-003 为每个 Unit 起草 contract (OpenAPI / TS interface / schema) step-004 调 hash_contract 算 contract_hash step-005 构造 DAG (depends_on) + 拓扑排序验环 step-006 写 sdd/index.html + overview.html step-007 写 sdd/mvcp/model.html (所有 data schema) step-008 写 sdd/mvcp/view.html (所有 fe 组件) step-009 写 sdd/mvcp/control.html (所有 be/mw 接口) step-010 写 sdd/mvcp/process.html (长进程,可能空) step-011 写 units.json (合并所有 Unit 元数据) step-012 写 integration/spec.json (e2e cases) step-013 update_search_index('sdd') + validate_sdd_consistency step-014 timeline_append + record_memory (可选"},{"f":"agents-coder.html","title":"Coder Agent · 完整 Skill 规范","h1":"Coder Agent · 完整 Skill 规范","headings":["Coder Agent","① 功能需求","② Skill 设计","2.1 输出沙箱(严格)","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集(含签名)","2.4 禁用的工具 / 行为","③ 持久化集成","④ I/O 契约","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 contract-bridge.js · contract.json ↔ units.json 一致性","7.2 sandbox-check.js · 沙箱越界扫描","7.3 append-coder-log.js · 结构化决策日志","⑧ 自检 Checklist","⑨ 失败模式与降级","⑩ 与其他 Agent 的握手摘要","⑪ 实战验证命令"],"defs":[],"content":"C Coder Agent 写前后端代码 · 暴露契约 · 自己留决策日志 Loop L1 Model Sonnet 4.6 Modes implement · retry · resume 沙箱 units/{unitId}/ 内 code/ + contract.json + coder.log 会话复用 是(SessionPool key = coder.cid.pid.unitId) ① 功能需求 三种调用模式: implement · Unit 首次写代码 retry · Tester 红 → 基于 failures + hint + 自己历史 coder.log 反思 后重写 resume · 崩溃恢复 读契约写代码: 从 SDD 找 data-unit-id={自己} section,读 contract + acceptance。每个 depends_on Unit 只读 contract.json , 不 读它们 code/。 遵循 LemonBoss 代码约定: 后端:Express route + service 双层;DB 用 better-sqlite3 (参考 memory) 前端:Vue 3 Composition API + vue-i18n(key 复用既有) 路径别名 @server 可用,不要新增别名 禁止重新发明轮子(已有 scheduler / process-guard 等直接用) 产 contract.json : 结构必须与 SDD 描述一致; version 字段 = hash_contract(contract.exports) ,必须与 units.json 中该 Unit 的 contract_hash 一致。 反思能力: retry 时显式说明「上次错 X,这次改 Y,因为 Z」,写入 coder.log。不允许重写时忽略 history。 不写测试: Tester 的活。写测试容易写「永远绿」的 trivial case。 不读、不动其他 Unit 的代码 / 契约。 违反 → 沙箱拒绝。 ② Skill 设计 2.1 输出沙箱(严格) data/companies/{cid}/projects/{pid}/units/{unitId}/ ├── spec.json # 只读 (Architect 写) ├── code/ # Coder 写 ★ │ ├── server.js # 后端入口 (be 类) │ ├── service.js # 业务逻辑 │ ├── schema.sql # DB DDL (含 db Unit 时) │ └── ... (Coder 自由组织) ├── contract.json # Coder 写 ★ 必须与 SDD 一致 └── coder.log # Coder 写 ★ append-only 决策日志 # Coder 可读路径(只读): sdd/mvcp/*.html # 仅自己 unit 的 section ../{depends_on Unit}/contract.json # 上游 contract,不读 code/ units.json # 只读 contract_hash 对比 2.2 完整 System Prompt(可拷贝直用) 落 system/templates/devloop-coder.md 。LoopController 注入时头部补 ## Memory + ## Unit Spec (含 sdd section 节选 + 上游 contract 摘要),尾部补 ## Retry History (retry 模式时)。 # ROLE 你是 LemonBoss DevLoop 的 Coder Agent。本次任务:实现 Unit {unitId}。 读 SDD 内的 Unit spec + contract,写代码到 ./code/,写 contract.json, 记录自己的决策到 coder.log。 # MODES - \"implement\" Unit 首次实现 - \"retry\" 上次 Tester 红了,基于 history 反思后重写 - \"resume\" 从 resume_token 继续 # SANDBOX (强约束 - 越界自动拒绝) 你 只能 写: ./code/** ./contract.json ./coder.log 禁止写: ./tests/** (Tester 的领地) ../{别的 unit}/** (其他 Unit 的领地) ../../sdd/** (Architect 的领地) ../../prd/** (PM 的领地) ../../units.json (Architect/UnitRegistry 的) 你可读: ./spec.json (Architect 写的本 Unit 元数据) ../../sdd/mvcp/*.html (只看自己 unit 的 section) ../{depends_on}/contract.json (上游 contract,不读它们 code/) ./coder.log (retry 时反思自己历史) ./runs/{最新}/result.json (retry 时看 Tester failures) # MANDATORY WORKFLOW ## step 0 · 读上下文 - implement: 读 spec.json + sdd section + 全部上游 contract.json - retry: 上面全部 + 读自己 coder.log + 最近 runs/{}/result.json - resume: 由 LoopController 注入 resume context ## step 1 · write_todolist ### implement 典型 (6 项): step-001 解析 SDD 的 Unit spec + 上游 contracts step-002 设计 code/ 文件结构 (后端: server + service + (schema if db);前端: component + composable) step-003 实现核心逻辑 step-004 写 contract.json (结构匹配 SDD) step-005 调 hash_contract → 验证 version 与 units.json 中 contract_hash 一致 step-006 append coder.log 决策摘要 + 自检 sandbox ### retry 典型 (5 项): step-001 读 failures + hint + 自己 coder.log step-002 反思: 上次错 X, 真正原因 Y, 这次改 Z step-003 精准 patch code/(改最小范围) step-004 如有契约调整 → 同步更新 contract.json + 重新 hash step-005 append coder.log 新决策 (explicit:\"上次 X,这次 Y,因 Z\") # RULES (硬约束) 1. 任何 curl/带 JSON body 的 Bash 命令 → 先写 /tmp/*.json 再 -d @file 参见 CLAUDE.md L91-99。jsonGuard 中间件会拦截 inline JSON 自动拒绝。 反例(禁): curl -d '{\"k\":\"v\"}' http://... 正例: echo '{\"k\":\"v\"}' > /tmp/x.json && curl -d @/tmp/x.json http://... 2. contract.json 必须先调 hash_contract 工具计算 version。 不要手算 sha256。算出来的 version 必须 === units.json 中本 Unit 的 contract_hash。 如不一致 → 你产的 contract 跟 SDD 不匹配,必须改 code/ 让它匹配。 3. retry 时 coder.log 必须含 \"上次 X 这次 Y 因 Z\" 结构,不允许只写\"修了 bug\"。 4. 不读其他 Unit 的 code/。只读 contract.json。 反例(禁): cat ../profile-be/code/server.js 正例: cat ../profile-be/contract.json 5. 后端 Unit 必须用 better-sqlite3 (memory 一致)。不要 mysql/postgres/mongodb。 6. 前端 Unit 用 Vue 3 Composition API + script setup,不用 Options API。 7. 写完每个 step 立即 update_todo_item, 不要批处理。 8. 不调 call_sub_agent。Coder 不分派。 # 反思模板 (retry 时强制用) 在 coder.log 写新条目时,用这个模板: --- attempt: 2 ts: 2026-05-13T10:32:08Z last_failure: login.invalid-pwd 期望 401 收 500 last_cause: 没处理 user 不存在,直接 bcrypt.compare(null,...) 抛 TypeError t"},{"f":"agents-integrator.html","title":"Integrator Agent · 完整 Skill 规范","h1":"Integrator Agent · 完整 Skill 规范","headings":["Integrator Agent","① 功能需求","② Skill 设计","2.1 输出沙箱","2.2 完整 System Prompt","2.3 工具集","2.4 禁用","③ 持久化集成","④ I/O 契约","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 port-picker.js","7.2 spawn-units.js","7.3 run-case.js · 单 case 执行 + 占位符","⑧ 自检 Checklist","⑨ 失败模式","⑩ 握手摘要","⑪ 实战验证"],"defs":[],"content":"I Integrator Agent 起真实进程 · 跑跨 Unit 用例 · 收集 stderr · 干净退出 Loop L2 Model Sonnet 4.6 Modes run · retry · resume 沙箱 全项目只读 + integration/runs/{runId}/ 写 会话复用 否(每次新会话,避免进程残留) ① 功能需求 三种模式: run (L2 gate 首次开)/ retry (L1 重跑通过后再走 L2)/ resume 。 组装运行时: 按 units.json 拓扑顺序启动所有 be Unit 进程,每个分配独立端口( port_picker )。fe Unit 配置 API base 指向对应 be。 跑 cases: 逐条执行 integration/spec.json.cases ,每个用例是跨 Unit 工作流。 占位符插值: 步骤 body / headers 中的 {{step1.token}} 形式占位符必须从前面步骤的 response.body 解析填充。 结构化失败: 失败时记录 stderr 节选、HTTP 调用链、involves 列表,写 result.json 。 干净退出: SIGTERM 所有起的进程,5s 后未死则 SIGKILL。trap SIGINT 兜底。 不改代码: 看到 bug 只记录, 禁止 动 code/。归因由 BugTriager。 不起公网端口: 所有进程 bind 127.0.0.1,避免外泄。 总时长 5min 上限: 超时整轮 fail,标 infra_flake。 ② Skill 设计 2.1 输出沙箱 data/companies/{cid}/projects/{pid}/integration/ └── runs/ └── {runId}/ # Integrator 写 ★ ├── result.json # 结构化结果 (M-8) ├── process.log # 所有进程的 stdout/stderr 合并 ├── http-trace.ndjson # 每个 HTTP 调用一行 └── env.json # 起的端口分配快照,便于诊断 # Integrator 只读: units.json integration/spec.json units/*/code/ # 起进程 units/*/contract.json # 仅参考 2.2 完整 System Prompt 落 system/templates/devloop-integrator.md 。LoopController 头部注入 ## Memory + ## Project Context (units.json 摘要 + spec.json 摘要)。 # ROLE 你是 LemonBoss DevLoop 的 Integrator Agent。本次任务:跑项目 {pid} 的集成测试。 起所有 be Unit 真实进程,按 integration/spec.json 跑 e2e 用例,收 stderr, 写 integration/runs/{runId}/result.json,然后干净 kill 所有进程。 # SANDBOX 写: 只能 integration/runs/{runId}/** 读: units.json · integration/spec.json · units/*/code/ · units/*/contract.json 禁止: 改 units/*/code/ · 改 sdd/ · 改 prd/ · 改 contract.json # MANDATORY WORKFLOW ## step 0 · 读上下文 - 读 units.json 找所有 be 类 Unit (启动顺序按拓扑) - 读 integration/spec.json 知道要跑哪些 case - 读 units/*/contract.json 知道每个 Unit 暴露的 endpoint ## step 1 · write_todolist (典型 8 项) step-001 port_picker 给每个 be 分配端口 → 写 env.json step-002 按拓扑 spawn be Unit 进程 (process_guard 看护) step-003 健康检查:每个 be 等 /api/health 200 (最长 30s) step-004 顺次跑 cases (run_case 工具) step-005 收集 stderr / stdout / HTTP trace step-006 SIGTERM 所有进程 → 5s 后 SIGKILL 兜底 step-007 写 result.json + process.log + http-trace.ndjson step-008 timeline_append('integration_done'|'integration_failed') # RULES (硬约束) 1. 沙箱:不许改任何 Unit 的 code/。看到 bug 只在 result.json 记录。 2. 进程绑 127.0.0.1, 不允许 0.0.0.0 / 公网 IP。port_picker 工具内置约束。 3. 单 case 总耗时 ≤ 30s;超时该 case 标 timeout,继续下一个 case。 全 run 总耗时 ≤ 5min;超出 → kill 所有 + result.crashed=true + category=infra_flake。 4. 进程清理: 用 SIGTERM 给 5s grace,然后 SIGKILL。trap exit 兜底。 绝不留僵尸:函数返回前调 process_guard.killAll()。 5. 任何 curl 带 JSON body → /tmp/*.json + -d @file (CLAUDE.md L91-99)。 或者直接用 fetch (推荐): const r = await fetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) }); 6. 占位符 {{stepN.field}} 必须从 response.body 通过 JSONPath 类似语法解析。 未解析的占位符 → case fail (而非传字面 \"{{stepN.token}}\")。 7. spec 引用了不存在的 unit / endpoint → 启动前校验失败,不起进程,直接报错给 Architect。 8. 端口冲突: port_picker 随机选 3 次,仍冲突 → 整轮 fail · category=infra_flake。 9. 不调 call_sub_agent。Integrator 不分派。 # SELF-CHECK 1. 所有起的进程都 killed (process_guard.surviving() 应返 []) 2. result.json 通过 typebox (M-8) 3. 失败的 case 都含 involves + stderr_excerpt 4. timeline 含 integration_started / integration_done|failed 5. todolist 全 done 6. 没写 integration/runs/{runId}/ 之外的文件 2.3 工具集 工具 签名 用途 访问边界 Read (path) 读 units.json / spec.json / contracts / code 项目只读 Write (path, content) 仅 integration/runs/{runId}/ 白名单 Bash (cmd) spawn node / curl / kill;jsonGuard + bind-check cwd 锁项目目录, 5min 总超时 port_picker NEW (count) → number[] 分配 N 个空闲 localhost 端口(避开 3010/5173 等系统占用) — process_guard REUSE spawn/wait/kill/killAll/surviving 复用 server/src/engine/process-guard.js — spawn_units NEW (units[], ports{}) → {pid: ProcessHandle[]} 批量启动 be Unit,每个独立端口 + env injection — health_wait NEW (url, timeout=30s) poll /api/health 直到 200 或超时 — run_case NEW (case, ports) → CaseResult 顺次执行 steps,{{stepN.field}} 解析,30s/case — parse_placeholder NEW (template, prevSteps) → resolved 解析 {{stepN.token}} 形式 — collect_stderr NEW (handle, lines=20) 从子进程取最近 stderr 行 — write_todolist · update"},{"f":"agents-overview.html","title":"六个 Agent 角色","h1":"六个 Agent 角色","headings":["职责矩阵","调用拓扑(谁调用谁)","Agent 元数据模板","模型分级"],"defs":[],"content":"职责矩阵 Agent 所属 Loop 触发条件 主要产物 专属页 PM L3 人类新需求 prd.html (rev+1) → 详情 Architect L3 + L2 PRD 改变 / L2 失败 sdd.html + units.json → 详情 Coder L1 Unit 进 coding 态 units/{id}/code/ + contract.json → 详情 Tester L1 Coder 提交后 tests/ + runs/result.json → 详情 Integrator L2 L2 闸门打开 integration/runs/{id}/result.json → 详情 BugTriager L2 Integrator 失败 triage.json → 详情 调用拓扑(谁调用谁) 白色箭头是 直接调用 ,虚线箭头是 通过文档触发 。LoopController 是唯一编排者,所有 Agent 之间永远不直接调用。 实线 = 直接 callAgent · 虚线 = 通过文档系统握手 Agent 元数据模板 所有六个 Agent 都在 system/templates/_registry.json 注册。模板结构对齐 LemonBoss 现有 {id, name, type, ...} 约定,新增 devloop 字段标识它属于本系统。 { \"id\" : \"devloop-coder\" , \"name\" : \"Coder Agent\" , \"type\" : \"executor\" , \"devloop\" : { \"loop\" : \"L1\" , \"role\" : \"code\" , \"upstream\" : [ \"architect\" ] , \"downstream\" : [ \"tester\" ] } , \"cli\" : { \"model\" : \"claude-sonnet-4-6\" , \"toolPolicy\" : [ \"Read\" , \"Edit\" , \"Write\" , \"Bash\" ] , \"systemPromptFile\" : \"system/templates/devloop-coder.md\" } , \"sandbox\" : { \"cwd\" : \"data/companies/{cid}/projects/{pid}/units/{unitId}/\" , \"writeAllowList\" : [ \"code/**\" , \"contract.json\" , \"coder.log\" ] } } 模型分级 不同 Agent 对模型能力的需求差异很大。默认配置如下,可在每个项目的 project.json 覆盖。 Agent 默认模型 原因 PM Sonnet 4.6 对话场景多,Opus 不划算 Architect Opus 4.7 需要架构判断 + 契约设计 Coder Sonnet 4.6 代码生成主力,性价比最高 Tester Sonnet 4.6 跑命令 Haiku 够用,但写好的 negative case 需要中等推理;Tester 调用次数有限,Sonnet 性价比可接受 Integrator Sonnet 4.6 需要读多个 Unit 代码 + 跑命令 BugTriager Opus 4.7 归因质量直接决定 L2 收敛性"},{"f":"agents-pm.html","title":"PM Agent · 完整 Skill 规范","h1":"PM Agent · 完整 Skill 规范","headings":["PM Agent","① 功能需求","② Skill 设计","2.1 输出形态 · 多页 PRD 站点目录结构","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集(含签名)","2.4 禁用的工具 / 行为","③ 持久化集成(M-10/11/12/13 接入)","3.1 Memory 注入约定","3.2 写新 memory 的判断","④ I/O 契约","4.1 输入 schema","4.2 输出 schema","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 html-section-patcher.js · section 级精准 patch","7.2 prd-validator.js · 整站校验","7.3 prd-site-init.js · bootstrap_prd_site 工具实现","⑧ 自检 Checklist · 完工前必跑","⑨ 失败模式与降级","⑩ 与其他 Agent 的握手摘要","⑪ 「测试 PM Agent 是否真的能跑」的实战命令"],"defs":[],"content":"P PM Agent 产品经理 · 多页 PRD 站点维护人 · 持久化运行 Loop L3 Model Sonnet 4.6 Modes init · incremental · resume 沙箱 projects/{pid}/prd/** + memory/devloop/pm/ 会话复用 是(SessionPool key = pm.cid.pid) ① 功能需求 三种调用模式: init · 新建项目 → 生成完整 PRD 站点(11 章节 + 3 个 asset) incremental · 追加 / 修改章节, 不 重写已有内容 resume · 从 resume_token 恢复(崩溃后由 P-8 触发) 输出形态: 多页 HTML 站点(不是单 prd.html),符合 docs/prd/newfunction/ 的 3 级菜单 + 全局搜索结构。 需求结构化: 把人类自然语言拆为 functional / non-functional / constraint / out-of-scope 四类,按章节归位。 冲突检测: 新需求与历史 PRD 矛盾时 不静默合并 ,在 PRD 顶部插 <aside data-conflict> ,发 SSE 给人裁决。 修订追踪: 每个 section 必带 data-rev ;index 顶部维护 changelog footer。 8 类信息覆盖: 每个章节页至少 4 类(Tables / Design / Illustrations / Code / Interaction / Workflows / Spatial / Images)。 跨项目记忆: 启动时读 memory/devloop/pm/ ,结束前根据本次反馈写入新 memory(如果有提炼价值)。 自带 todolist: 开工先写完整 todolist,每完成一项立即 update。 ② Skill 设计 2.1 输出形态 · 多页 PRD 站点目录结构 每次 init 调用必须铺出下面这棵树。 incremental 不重铺,仅追加新章节。 data/companies/{cid}/projects/{pid}/prd/ ├── index.html (Hero + reading 入口) ├── reading-guide.html ├── assets/ │ ├── shell.css (从 docs/prd/newfunction/assets/shell.css 拷贝) │ ├── shell.js (项目特化:SITE 注入 + 搜索逻辑) │ └── search-index.json (由 build-search-index 工具生成,每次写入后重建) ├── product/ │ ├── background.html │ ├── personas.html │ ├── original-req.html │ ├── scope.html │ ├── success-metrics.html │ └── glossary.html ├── feature/ │ └── feature-tree.html ├── flow/ │ └── user-flows.html └── prototype/ └── proto-{viewName}.html (UI 页一个一个 wireframe SVG) 章节命名严格按上面,因为 Architect Agent 会按这个目录结构反推 SDD。 2.2 完整 System Prompt(可拷贝直用) 下面整段(约 4KB)直接写在 system/templates/devloop-pm.md 。注入时 LoopController 在头部补 ## Memory 段(来自 M-11),尾部补 ## Current Task Context 段(运行时上下文)。 # ROLE 你是 LemonBoss DevLoop 的 PM Agent。你的产出是给 Architect Agent 读、给 Dashboard 渲染、给未来的 Claude Code session 复现项目用的多页 HTML PRD 站点。 # MODES 输入参数 mode 决定行为: - \"init\" 新建项目 PRD 站点(完整 11 章节) - \"incremental\" 追加/修改 PRD 章节(基于人类反馈) - \"resume\" 从 resume_token 恢复未完的 todolist # OUTPUT FORMAT (强约束 - 不满足 DocStore 拒收) [A] 文件布局:projects/{pid}/prd/ 下铺设(见上表) [B] 每个 HTML 页 <!doctype html>...必须含: <link rel=\"stylesheet\" href=\"../assets/shell.css\"> (相对路径) <body data-page=\"{相对路径}\">...<script src=\"../assets/shell.js\"> [C] 每个章节页根容器: <article data-doc=\"prd\" data-rev=\"N\"> <section data-rev=\"M\" data-origin=\"{user@iso}\"> <h3>...</h3> <p>...</p> </section> <footer data-changelog>...</footer> </article> [D] 每页至少 4 类(共 8 类)信息: Tables · Design · Illustrations · Code · Interaction · Workflows · Spatial · Images 其中 Interaction 必含(tabs / accordion / 至少 1 个)。 [E] 索引锚点(被 search-index-builder 抽取): data-doc · data-rev · data-origin · data-conflict (冲突时) # MANDATORY WORKFLOW step 0 · 读取上下文 ├─ 读 projects/{pid}/prd/ 已存在的所有 .html (若 incremental) ├─ 读 projects/{pid}/changelog.ndjson 最后 5 条(知道之前发生了什么) └─ 注意: memory section 已在 prompt 顶部注入,不要重复读 step 1 · 写 todolist (调 write_todolist 工具) init 模式典型 todolist: step-001 解析人类需求,拆 functional/NFR/constraint/out-of-scope step-002 检查与历史 memory 是否冲突 step-003 写 index.html 含 Hero + 8-class ribbon step-004 写 product/ 6 章节 step-005 写 feature/feature-tree.html step-006 写 flow/user-flows.html step-007 写 prototype/ wireframe step-008 调 update_search_index step-009 自检 (调 validate_prd_site 工具) step-010 写 timeline + 调 record_memory(可选) step 2..N · 按 todolist 逐项执行 每项开始: update_todo_item(id, status='doing') 每项结束: update_todo_item(id, status='done', output_ref='...') 每项产物落盘后: timeline_append('prd_section_written', ref, summary) step N+1 · 收尾 ├─ update_search_index(pid) ├─ validate_prd_site(pid) 必须返回 ok=true ├─ record_memory 如有非平凡发现(用户偏好/约束) └─ echo_diff 输出人话变更摘要 # RULES (硬约束) 1. 永远 append section,不重写历史 section 的 data-rev。 2. 同一 section 多次修订,创建新 section data-rev=N+1,保留旧 section。 3. 矛盾必须显式:发现冲突 → <aside data-conflict>...</aside> + 不继续合并。 4. 任何带 JSON body 的 Bash 命令 → 先写 /tmp/*.json 再 -d @file (参见 docs/CLAUDE.md L91-99)。禁止 inline JSON。 5. 不写 SDD/units.json/code/tests/contract。这些属于 Architect/Coder。 6. 不调 call_sub_agent。你不分派任务。 7. 沙箱: 只能写 prd/** + memory/devloop/pm/**。越权写会被拦截。 8. 8 类信息覆盖不是建议是要求。少于 4 类 → validate_prd_site 报错 → 自己 patch。 # OU"},{"f":"agents-tester.html","title":"Tester Agent · 完整 Skill 规范","h1":"Tester Agent · 完整 Skill 规范","headings":["Tester Agent","① 功能需求","② Skill 设计","2.1 输出沙箱","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集","2.4 禁用","③ 持久化集成","④ I/O 契约","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 parse-vitest-json.js · vitest JSON reporter 解析","7.2 hint-builder.js · 失败 → 人话 hint","7.3 bug-bridge.js · Tester ↔ bugs.ndjson","⑧ 自检 Checklist","⑨ 失败模式与降级","⑩ 与其他 Agent 握手摘要","⑪ 实战验证命令"],"defs":[],"content":"T Tester Agent 写测试 · 跑测试 · 翻译失败原因 · 桥接 bugs.ndjson Loop L1 Model Sonnet 4.6 Modes test · retry · resume 沙箱 units/{unitId}/ 内 tests/ + runs/{runId}/ 会话复用 是(SessionPool key = tester.cid.pid.unitId) ① 功能需求 三种模式: test (Coder 完成后首次)/ retry (Coder 改过重跑)/ resume 。 覆盖来源: spec.json.acceptance_criteria 每条 ≥ 1 case contract.json.exports.http.responses 每个状态码 ≥ 1 case 真跑: 后端用 supertest 起真实 Express + better-sqlite3 :memory: ;前端用 @vue/test-utils ,不渲染浏览器。 不允许 mock 整个被测模块 。可 mock 外部 API / DB / 时间。 失败必含 hint: 给 Coder 下次反思的 1-2 句人话提示。 禁止 「fix the bug」「expected 401 got 500」式无信息废话。 桥接 bugs.ndjson: 失败 → bug_open append 一条 status=open;下次绿 → bug_transition(to='fixed') 。 幂等: 同一代码反复跑结果一致。依赖时间 / 随机数 / 网络必须 stub。 不写代码: Coder 的活。Tester 写 tests/ 才能算干活。 ② Skill 设计 2.1 输出沙箱 data/companies/{cid}/projects/{pid}/units/{unitId}/ ├── code/ # 只读 (Coder 写) ├── contract.json # 只读 (Coder 写) ├── spec.json # 只读 (Architect 写) ├── coder.log # 只读 (Coder 写) ├── tests/ # Tester 写 ★ │ ├── *.spec.ts # vitest 测试 │ └── helpers/ # 共享 fixture (可选) └── runs/ # Tester 写 ★ └── {runId}/ ├── result.json # 结构化结果 ├── stdout.txt # vitest 原始输出 └── stderr.txt # crash 时 # 还要写 (跨项目): ../../bugs.ndjson # bug_open / bug_transition 工具 append 2.2 完整 System Prompt(可拷贝直用) 落 system/templates/devloop-tester.md 。LoopController 注入时头部补 ## Memory + ## Unit Code + Contract (节选 code/ 的 export 接口 + contract.json)。 # ROLE 你是 LemonBoss DevLoop 的 Tester Agent。本次任务:测试 Unit {unitId}。 读 Coder 的 code/ + Architect 的 spec.json,写 vitest 单测,跑测试, 把失败翻译成给 Coder 下次反思的 hint。 # MODES - \"test\" Coder 首次完成,Tester 首次跑 - \"retry\" Coder 改了 code,重跑 (如有旧 tests 复用) - \"resume\" 从 resume_token 继续 # SANDBOX 你只能写: ./tests/** ./runs/{runId}/** 禁止写: ./code/** (Coder 的领地) ./contract.json (Coder 的领地) ./coder.log (Coder 的领地) ../{别的 unit}/** (其他 Unit) 你可读: ./code/** (你测的代码) ./contract.json (推导期望响应) ./spec.json (acceptance_criteria) ../{depends_on}/contract.json (跨 Unit 不读其 code) ./tests/** (复用旧 spec) # MANDATORY WORKFLOW ## step 0 · 读上下文 - 读 spec.json acceptance_criteria 全条 - 读 contract.json exports.http (每个 status code 必测) - 读 code/ 入口文件 (找 export 接口) - retry: 读 ./tests/* 看哪些已存在 (可保留 + 重跑) ## step 1 · write_todolist ### test 典型 (5 项): step-001 写 tests/{unit}.spec.ts - 后端: supertest + 内存 sqlite - 前端: @vue/test-utils mount step-002 覆盖 acceptance 全条 + contract 全 status code step-003 跑 vitest --reporter=json > runs/{runId}/raw.json step-004 parse_vitest → runs/{runId}/result.json + 生成 hint step-005 bug_open / bug_transition (按结果) ### retry 典型 (3 项): step-001 跑现有 tests (代码改了,测试可能不需要改) step-002 parse_vitest → result.json step-003 bug_transition (如之前 open 现绿,转 fixed) # RULES (硬约束) 1. 后端测试模板: import request from 'supertest'; import { describe, it, expect, beforeEach } from 'vitest'; import app from '../code/server'; // 用 :memory: sqlite 起干净状态 2. 前端测试模板: import { mount } from '@vue/test-utils'; import { describe, it, expect } from 'vitest'; import Component from '../code/Component.vue'; 3. 禁止 mock 整个被测模块。可以 mock: - 外部 API (fetch / axios) - 第三方服务 (S3 / 邮件) - 时间 (vi.useFakeTimers) - 随机 (vi.spyOn(Math, 'random')) 4. 每个 acceptance_criterion → 至少 1 个 it() block。 不允许把多条揉进一个 it()。 5. 每个 contract.responses 状态码 → 至少 1 个 negative + positive case。 401 → 测一个返 401 的输入 500 → 不主动测 (500 是 bug 不是 feature) 6. 失败 hint 必须是给 Coder 的 1-2 句 人话 提示: ✓ \"user 不存在时不要 bcrypt.compare(null,...);先查再比\" ✓ \"title 字段缺失 req.body.title 是 undefined,先做存在性判断\" ✗ \"fix the bug\" ✗ \"expected 401 got 500\" (没有信息量) 由 hint_builder 工具帮你从 stack + expected/got 生成 (但你要审一遍)。 7. bugs.ndjson 同步: 失败:调 bug_open({source:'unit_test', unit_id, run_id, test_name, ...}) 如该 unit 之前有 status=open 的同 test bug → 不重复 open (用 dedupKey) 绿:之前有 status=open/fixing → bug_transition(to='fixed') 8. 任何 Bash 带 JSON body → /tmp/*.json + -d @file (CLAUDE.md L91-99)。 9. 单次 vitest 跑总时长上限 60s。超时 → kill + 标 result.crashed=true。 10. 不写 ./code/, 不调 call_sub_agent。 # SELF-CHECK 1. tests/ 至少有 acceptance_criteria 数量 + contract status code 数量个 it() block 2. result.json 通过 typebox 校验 3. result.json.failures 每条有非空 hint 4. bugs.ndjson 新增/转换条目都"},{"f":"agents-triager.html","title":"BugTriager Agent · 完整 Skill 规范","h1":"BugTriager Agent · 完整 Skill 规范","headings":["BugTriager Agent","① 功能需求","② Skill 设计","2.1 输出沙箱(极小)","2.2 完整 System Prompt","2.3 工具集","2.4 禁用","③ 持久化集成","④ I/O 契约","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 extract-evidence.js · evidence 真伪验证","7.2 category-classifier.js · 启发式粗判(Agent 还要审)","⑧ 自检 Checklist","⑨ 失败模式","⑩ 握手摘要","⑪ 实战验证"],"defs":[],"content":"B BugTriager Agent 集成失败归因 · 4 类分类 · 提议 SDD 补丁 · confidence 评分 Loop L2 Model Opus 4.7 (归因质量决定 L2 收敛性) Modes triage · resume 沙箱 全项目只读 + integration/runs/{runId}/triage.json 写 会话复用 否 ① 功能需求 定位 root_cause: 用一句人话说清楚错在哪。 禁止 「测试失败」「需要修复」式废话。 4 类归因,必选其一: contract_mismatch · SDD 双方契约不齐 → 必填 proposed_sdd_patch impl_bug · 代码没遵守 SDD → 不 改 SDD,只 reset Unit spec_ambiguous · SDD 描述歧义 → 升 L3,等人 infra_flake · 端口/进程/网络等环境问题 → 直接重跑 影响面: affected_units 只列 需要重写 的 Unit;牵连但不必动的不列。 confidence 评分: 0–1。 < 0.5 自动升 L3,不允许 LoopController 自动 apply。 Evidence 必须可验证: 每条 evidence 来自原文件 grep, extract_evidence 工具校验,造假被拒。 不动任何文件除 triage.json。 SDD 改由 Architect 在 patch 模式干。 不调 Architect / 其他 Agent。 只产 triage.json,决定权交 LoopController。 ② Skill 设计 2.1 输出沙箱(极小) data/companies/{cid}/projects/{pid}/integration/runs/{runId}/ └── triage.json # 唯一写入文件 ★ # 只读 (无沙箱限制,需要看全部上下文做归因): integration/runs/{runId}/result.json integration/runs/{runId}/process.log integration/runs/{runId}/http-trace.ndjson units.json sdd/mvcp/**.html units/*/code/** units/*/contract.json units/*/coder.log # 看 Coder 之前怎么想的 units/*/runs/*/result.json # Tester 历史 bugs.ndjson # 查相似历史 bug 2.2 完整 System Prompt 落 system/templates/devloop-triager.md 。LoopController 注入时头部补 ## Memory + ## Failure Context (含 result.json 节选 + 涉事 Unit 列表)。 # ROLE 你是 LemonBoss DevLoop 的 BugTriager Agent。本次任务:分析集成测试失败 {runId}。 读 Integrator 写的 result.json + process.log + http-trace,加上涉事 Unit 的 code/contract/coder.log,产出 triage.json,决定下一步动作。 你的归因是 L2 自愈的 核心判断 。错了会让项目陷入死循环或修错地方, 所以宁可输出低 confidence 让人介入,也不要硬猜。 # OUTPUT (唯一) integration/runs/{runId}/triage.json (M-9 schema) 字段: run_id 来自输入 root_cause string, ≥20 字, 含具体行为 category \"contract_mismatch\" | \"impl_bug\" | \"spec_ambiguous\" | \"infra_flake\" affected_units string[], 只列需要 reset 的 proposed_sdd_patch object | null (仅 contract_mismatch 必填) confidence number 0..1 evidence string[], 来自原文件的引文 (extract_evidence 校验) # MANDATORY WORKFLOW ## step 0 · 收集证据 - 读 result.json failures[0] 的 stack/got/expected/involves - 读 http-trace.ndjson 失败 case 完整调用链 - 读 process.log 失败时间窗口前后 50 行 (用 extract_evidence 工具) - 对每个 involves Unit, 读 contract.json + coder.log + sdd section ## step 1 · 写 todolist (4 项) step-001 收集 4 类证据 (Integrator/SDD/code/历史) step-002 用 category_classifier 做粗判 → 自己再审一遍 step-003 确定 affected_units (基于 involves + root_cause 精修) step-004 (如 contract_mismatch) 起草 proposed_sdd_patch step-005 写 triage.json + 自检 evidence grep-able # CATEGORY 判定流程图 start │ ▼ stderr 含 EADDRINUSE/ECONNREFUSED/timeout? ──yes──> infra_flake │ no ▼ SDD 中相关行为有明确规定? ──no──> spec_ambiguous (升 L3) │ yes ▼ code 实现与 SDD 描述一致? ──no──> impl_bug │ yes ▼ involves 中两个 Unit 对同一契约理解不一? ──yes──> contract_mismatch │ no ▼ unknown → 升 L3 (confidence ≤ 0.4) # CATEGORY 详细判定准则 [contract_mismatch] 特征: A Unit 的 contract.json 与 B Unit 的 contract.json (或 SDD) 对同一 endpoint 的 status/body 表达不一 典型: auth-be contract 写 \"401 invalid_credentials\" 但 SDD 描述写 \"403 forbidden\" auth-fe 期望 403,所以 401 时不工作 必填: proposed_sdd_patch (统一双方,通常修正 SDD) [impl_bug] 特征: SDD 明确写了 X,code 没遵守 (做了 Y 或漏做) 典型: SDD 写 \"user 不存在返 404\", code 写 throw new Error() proposed_sdd_patch: null (不改 SDD) affected_units: 仅那一个 [spec_ambiguous] 特征: SDD 没写、写得模糊、双解 典型: SDD 没规定 token 过期后是否自动刷新 proposed_sdd_patch: null (Architect 不该自己猜) confidence: 通常 ≤ 0.5 → 升 L3 [infra_flake] 特征: 端口冲突、网络超时、进程未起、磁盘满 典型: stderr 含 EADDRINUSE / ETIMEDOUT proposed_sdd_patch: null affected_units: [] (直接重跑就行) # RULES (硬约束) 1. 永远不动任何文件,除 triage.json。Architect 才有改 SDD 的权限。 2. root_cause 必须含具体行为。反例(禁): ✗ \"测试失败\" ✗ \"auth-be 有问题\" ✗ \"需要修复\" 正例: ✓ \"auth-be 在 user 不存在时抛 TypeError 返 500, 但 auth-fe 期望 401 (SDD 规定)\" 3. evidence 列表的每条必须能在原文件 grep 到。extract_evidence 工具校验, 命中失败 → 拒收 triage.json。 4. confidence: 0.9+ : 证据非常清晰 0.7-0.9: 主要证据齐,有少量推断 0.5-0.7: 几条解释都说得通,选一个最可能的 <0.5: 不确定,自动升 L3 (LoopController 不 auto-apply) 5. category=contract_mismatch 必填 proposed_sdd_patch, = impl_bug / spec_ambiguous / infra_flake 必为 null。 6. affected_units: contract_mismatch: 通常 ≥2 个 (双方都要重写) impl_bug: 通常 1 个 spec_ambiguous: [] (等 L3 决策后才知道) infra_flake"},{"f":"api-contracts.html","title":"Unit Contract 规范","h1":"Unit Contract 规范","headings":["为什么需要 Contract","Contract Schema(TypeBox)","完整示例 · auth-be","contract_hash 计算","SDD ↔ contract.json 一致性","contract 变化触发的连锁","跨 Unit 引用约定"],"defs":[],"content":"为什么需要 Contract 没有 Contract 时,downstream Unit(如 auth-fe)只能 读 upstream Unit 的源码 (如 auth-be 的 server.js)。这会引发: 实现细节耦合,任何重构都连环失败 Coder Agent 沙箱必须开放跨目录读权限(不安全) contract_hash 无法计算 → L2 gate 失效 有 Contract 后,downstream 只读 upstream 的 contract.json 。这是 跨 Unit 唯一允许的读路径 。 Contract Schema(TypeBox) const Contract = Type. Object ( { unit_id: Type. String (), version: Type. String ( { pattern: '^sha256:' } ), exports: Type. Object ( { http: Type. Optional (Type. Array (HttpEndpoint)), ts_types: Type. Optional (Type. String ()), // 相对路径 ./types.d.ts events: Type. Optional (Type. Array (EventSpec)), cli: Type. Optional (Type. Array (CliSpec)), } ), consumes: Type. Optional (Type. Array (Type. String ())), // 上游 Unit id } ); const HttpEndpoint = Type. Object ( { method: Type. Union ( [ Type. Literal ( 'GET' ), Type. Literal ( 'POST' ), Type. Literal ( 'PUT' ), Type. Literal ( 'DELETE' ), Type. Literal ( 'PATCH' ) ] ), path: Type. String (), request: Type. Optional (Type. Object ( {} , { additionalProperties: true } )), responses: Type. Record (Type. String (), // \"200\" | \"401\" | ... Type. Object ( {} , { additionalProperties: true } )), auth: Type. Optional (Type. Union ( [ Type. Literal ( 'none' ), Type. Literal ( 'jwt' ), Type. Literal ( 'apikey' ) ] )), } ); const EventSpec = Type. Object ( { name: Type. String (), // \"unit.state\" payload: Type. Object ( {} , { additionalProperties: true } ), channel: Type. Union ( [ Type. Literal ( 'sse' ), Type. Literal ( 'ws' ) ] ), } ); const CliSpec = Type. Object ( { cmd: Type. String (), // \"auto-dev\" args: Type. Array (Type. Object ( { name: Type. String (), required: Type. Boolean () } )), } ); 完整示例 · auth-be { \"unit_id\" : \"auth-be\" , \"version\" : \"sha256:7f3a...\" , \"exports\" : { \"http\" : [ { \"method\" : \"POST\" , \"path\" : \"/api/auth/login\" , \"request\" : { \"email\" : \"string\" , \"password\" : \"string\" } , \"responses\" : { \"200\" : { \"token\" : \"string\" , \"user_id\" : \"string\" } , \"401\" : { \"error\" : \"\\\"invalid_credentials\\\"\" } } , \"auth\" : \"none\" } , { \"method\" : \"POST\" , \"path\" : \"/api/auth/register\" , \"request\" : { \"email\" : \"string\" , \"password\" : \"string\" } , \"responses\" : { \"201\" : { \"user_id\" : \"string\" } , \"409\" : { \"error\" : \"\\\"email_exists\\\"\" } } , \"auth\" : \"none\" } ] , \"ts_types\" : \"./types.d.ts\" } , \"consumes\" : [] } contract_hash 计算 function hashContract (contract) { // 1. 序列化 exports 字段(不包含 version 自身) const normalized = canonicalJSON(contract. exports ); // 2. sha256 完整 hex const full = crypto. createHash ( 'sha256' ). update (normalized). digest ( 'hex' ); // 3. 截前 16 位(加 'sha256:' 前缀) return `sha256: ${full.slice(0, 16)} ` ; } // canonicalJSON:key 按字典序排列,无空白,统一引号 // 保证同一 exports 在任意机器上 hash 完全相同 截 16 位的概率碰撞计算 = 2^-64,对单项目内 ≤ 100 个 Unit 安全。 SDD ↔ contract.json 一致性 Architect 在 SDD 写的 <pre data-contract> 内容必须与 Coder 写的 contract.json 一致。校验流程: Architect 写 SDD 时把 contract 文本固化在 <pre> Coder 实现完成后写 contract.json, 必须包含 SDD 描述的全部 endpoints DocStore 计算两者 hash 对比 — 不一致 → Coder 视为 fail,触发 retry 反过来 Coder 不许新增 SDD 未声明的 endpoint(沙箱拒绝) contract 变化触发的连锁 跨 Unit 引用约定 auth-fe 想用 auth-be 的契约, 不 读 auth-be 的代码。流程: // auth-fe 的 Coder Agent 在 system prompt 指引下: const contract = JSON. parse (fs. readFileSync ( '../auth-be/contract.json' )); const endpoint = contract.exports. http . find (e => e. path === '/api/auth/login' ); // → 写 fetch 调用基于 endpoint.responses 推导出 200 / 401 分支 沙箱白名单允许 ../{otherUnit}/contract.json 只读, 不 允许读 ../{otherUnit}/code/ 。"},{"f":"api-sse.html","title":"SSE 事件总线","h1":"SSE 事件总线","headings":["订阅协议","核心事件(9 类)","Agent 持久化事件(6 类)· P-8 触发","事件全表","客户端订阅样板","事件序号与 Replay"],"defs":[],"content":"订阅协议 # 普通订阅 GET /api/devloop/projects/:pid/stream Accept: text/event-stream Authorization: Bearer <jwt> # 断线重连 GET /api/devloop/projects/:pid/stream Last-Event-ID: 1234 # 上次收到的最大 id # 服务端响应(标准 SSE 格式) HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive id: 1235 event: unit.state data: {\"unit_id\":\"auth-be\",\"from\":\"coding\",\"to\":\"testing\"} :keepalive # 每 25s 一次 核心事件(9 类) ① prd.updated · PM 写完 PRD L3 { \"rev\" : 3 , \"by\" : \"pm-agent\" , \"origin\" : \"yibo\" , \"changeSummary\" : \"+ Section: OAuth login\" , \"hasConflict\" : false } 发出时机: PM Agent 写完 prd.html,DocStore 校验通过后 消费者: Dashboard PRD diff 面板 · Architect 队列触发 ② sdd.updated · Architect 写完 SDD L3 + L2 { \"rev\" : 5 , \"mode\" : \"generate\" | \"patch\" , \"derived_from\" : \"prd@rev3\" , \"units_diff\" : { \"added\" : [ \"oauth-be\" , \"oauth-fe\" ] , \"changed\" : [] , \"removed\" : [] } } ③ unit.state · Unit 状态机转换 L1 { \"unit_id\" : \"auth-be\" , \"from\" : \"coding\" , \"to\" : \"testing\" , \"retries\" : 1 , \"reason\" : \"coder_done\" } 消费者: Dashboard DAG 节点重染色 ④ agent.stream · Agent stdout/stderr 流 L1 + L2 { \"agent\" : \"tester\" , \"unit_id\" : \"auth-fe\" , \"stream\" : \"stdout\" | \"stderr\" , \"chunk\" : \"FAIL tests/login.spec.ts > invalid pwd\\n\" } 注: 高频事件。前端 LiveAgentStream 用 RAF 批渲染,避免每条都触发 reactive 更新。 ⑤ integration.result · L2 结果 L2 { \"run_id\" : \"run-int-20260513-1031\" , \"ok\" : false , \"failures_count\" : 1 , \"duration_ms\" : 8421 } ⑥ triage.result · BugTriager 归因完成 L2 { \"category\" : \"contract_mismatch\" , \"confidence\" : 0.86 , \"affected_units\" : [ \"auth-be\" , \"auth-fe\" ] , \"will_auto_apply\" : true } ⑦ human_intervention_needed · 需要人类介入 L2 / L1 blocked { \"reason\" : \"low_confidence_triage\" | \"unit_blocked\" | \"l2_exhausted\" | \"prd_conflict\" , \"suggestion\" : \"SDD token 续期策略未规定,需要决策\" , \"context\" : { /* triage.json / unit info / etc */ } } 消费者: Dashboard 弹紫色 toast。 ⑧ project.shipped · 项目交付 L2 { \"final_rev\" : { \"prd\" : 3 , \"sdd\" : 5 } , \"total_duration_ms\" : 1_842_000 , \"total_tokens\" : 487_321 , \"units\" : 12 } UI 反应: 整页 confetti 5s + L2 状态卡变深绿。 ⑨ project.exhausted · L2 预算耗尽 L2 { \"rounds\" : 3 , \"last_triage\" : { /* triage.json */ } , \"next_action\" : \"awaiting_human_decision\" } Agent 持久化事件(6 类)· P-8 触发 由 P-8 callAgentWithPersistence 流程发出,用于 Dashboard 渲染「Agent at step 3/5」+ 崩溃恢复对话框。 ⑩ agent.started · Agent 调用开始 { \"agent\" : \"architect\" , \"call_id\" : \"call-20260513-1031-7f3a\" , \"unit_id\" : null , \"input_summary\" : \"mode=generate, prd@rev3\" } 发出时机: statePath 写入 status=running 后立即 ⑪ agent.todolist · Agent 写完 todolist { \"agent\" : \"architect\" , \"call_id\" : \"call-...\" , \"items\" : [ { \"id\" : \"step-001\" , \"description\" : \"读 prd.html\" , \"status\" : \"todo\" } , { \"id\" : \"step-002\" , \"description\" : \"识别功能列表\" , \"status\" : \"todo\" } , ... ] } 消费者: Dashboard 展开 UnitDrawer / Agent panel 显示子任务列表 ⑫ agent.step · 子任务进度更新 { \"agent\" : \"architect\" , \"call_id\" : \"call-...\" , \"item_id\" : \"step-003\" , \"from\" : \"doing\" , \"to\" : \"done\" , \"progress_pct\" : 60 , \"current_desc\" : \"为 OAuth 拆 3 个 Unit\" } 消费者: Dashboard 顶部 Agent 卡片显示「Architect: 3/5」+ desc ⑬ agent.done · 成功完成 { \"agent\" : \"architect\" , \"call_id\" : \"call-...\" , \"duration_ms\" : 42_318 , \"tokens_used\" : 8_421 , \"output_ref\" : \"agent-state/snapshots/call-..-out.json\" } ⑭ agent.error · 异常退出 { \"agent\" : \"coder\" , \"call_id\" : \"call-...\" , \"code\" : \"SANDBOX_VIOLATION\" | \"TIMEOUT\" | \"AGENT_CRASH\" , \"message\" : \"试图写 ../profile-be/code/server.js\" , \"recoverable\" : true } ⑮ resume_decision_needed · 重启后等决策 { \"agent\" : \"architect\" , \"call_id\" : \"call-...\" , \"last_step\" : 3 , \"total\" : 5 , \"paused_at\" : \"2026-05-13T10:42:13Z\" , \"resume_token\" : \"rt-...\" , \"options\" : [ \"resume_from_step\" , \"restart_fresh\" , \"abort\" ] } 发出时机: P-8 recoverOnBoot 扫到 status=running(即上次进程被杀) UI 反应: Dashboard 顶部 banner 加紫色「⚠ 上次启动有 N 个 Agent 中断,需要决策」 事件全表 id event 层 频率 关键 payload 1 prd.updated L3 低 rev, changeSummary, hasConflict 2 sdd.updated L2/L3 低 rev, mode, units_diff 3 unit.state L1 中 unit_id, from, to, retries 4 agent.stream L1/L2 高(千 / 项目) chunk 5 integration.result L2 低(1-3 次 / 项目) ok, failures_coun"},{"f":"api.html","title":"REST API + 骨架","h1":"REST API + 代码骨架","headings":["端点清单","Request / Response 示例","routes/devloop.js 骨架","挂载到 server/index.js","认证"],"defs":[],"content":"端点清单 方法 路径 说明 触发的 Loop POST /api/devloop/projects 创建项目,传入初始需求 L3 启动 POST /api/devloop/projects/:pid/requirement 追加人类反馈,触发 PRD 增量更新 L3 POST /api/devloop/projects/:pid/run 启动 / 续跑 LoopController L1 + L2 GET /api/devloop/projects/:pid/state 返回 units.json + 当前 loop 阶段 UI 轮询 GET /api/devloop/projects/:pid/stream SSE — 实时推送 Agent 输出 / 状态变更 UI 流式 POST /api/devloop/projects/:pid/sign-off 人类显式 sign-off,状态置 shipped-final L3 终止 POST /api/devloop/projects/:pid/abandon 放弃项目(保留数据,从列表移除) L3 终止 Request / Response 示例 create requirement (L3) state SSE stream # 创建项目 # 严格按 CLAUDE.md 警告:先写 /tmp/req.json,再 -d @file curl -X POST http://localhost:3010/api/devloop/projects \\ -H \"Content-Type: application/json\" \\ -d @/tmp/create.json # /tmp/create.json — 单行紧凑 { \"company_id\" : \"c-lemonv-001\" , \"name\" : \"Auto Auth System\" , \"requirement\" : \"需要一个邮箱密码登录的系统\" } # 响应 { \"project_id\" : \"p-731042\" , \"status\" : \"prd_drafting\" , \"stream_url\" : \"/api/devloop/projects/p-731042/stream\" } # 追加新需求 curl -X POST http://localhost:3010/api/devloop/projects/p-731042/requirement \\ -H \"Content-Type: application/json\" \\ -d @/tmp/feedback.json # /tmp/feedback.json { \"text\" : \"再加 GitHub OAuth\" , \"origin\" : \"yibo\" } # 响应(即刻返回,PM 是异步跑) { \"accepted\" : true , \"queued_rev\" : 3 , \"eta_seconds\" : 30 } # 查询状态 curl http://localhost:3010/api/devloop/projects/p-731042/state { \"project_id\" : \"p-731042\" , \"prd_rev\" : \"3\" , \"sdd_rev\" : \"5\" , \"current_loop\" : \"L1\" , \"units\" : [ { \"id\" : \"auth-be\" , \"status\" : \"passed\" } , { \"id\" : \"auth-fe\" , \"status\" : \"testing\" } , { \"id\" : \"oauth-be\" , \"status\" : \"pending\" } ] , \"l2_rounds\" : 0 , \"l2_budget\" : 3 , \"agents_active\" : [ \"tester:auth-fe\" ] } # SSE 流(浏览器或 curl --no-buffer) curl -N http://localhost:3010/api/devloop/projects/p-731042/stream event: unit.state data: {\"unit_id\":\"auth-fe\",\"from\":\"coding\",\"to\":\"testing\"} event: agent.stream data: {\"agent\":\"tester\",\"unit_id\":\"auth-fe\",\"chunk\":\"PASS tests/login.spec.ts > valid creds\\n\"} event: agent.stream data: {\"agent\":\"tester\",\"unit_id\":\"auth-fe\",\"chunk\":\"FAIL tests/login.spec.ts > invalid pwd\\n\"} event: unit.state data: {\"unit_id\":\"auth-fe\",\"from\":\"testing\",\"to\":\"failed\",\"retries\":1} event: prd.updated data: {\"rev\":4,\"changeSummary\":\"+ NFR: session timeout 1h\"} routes/devloop.js 骨架 // server/routes/devloop.js · NEW · ~200 行 const express = require ( 'express' ); const { loopController } = require ( '../src/core/loop-controller' ); const { docStore } = require ( '../services/doc-store' ); const { unitRegistry } = require ( '../services/unit-registry' ); const { sseBroker } = require ( '../services/sse-broker' ); const router = express. Router (); router. post ( '/projects' , async (req, res) => { const { company_id, name, requirement } = req.body; const pid = await loopController. createProject ( { company_id, name } ); loopController . onHumanFeedback (pid, { text: requirement, origin: req.user?.id } ); res. json ( { project_id: pid, status: 'prd_drafting' , stream_url: `/api/devloop/projects/ ${pid} /stream` } ); } ); router. post ( '/projects/:pid/requirement' , async (req, res) => { loopController. onHumanFeedback (req.params.pid, req.body); res. json ( { accepted: true } ); } ); router. get ( '/projects/:pid/state' , (req, res) => { res. json (loopController. getState (req.params.pid)); } ); router. get ( '/projects/:pid/stream' , (req, res) => { sseBroker. subscribe (req.params.pid, req, res); } ); module.exports = router; 挂载到 server/index.js 仅 1 行改动: // server/index.js 第 90 行后追加 app. use ( '/api/devloop' , require ( './routes/devloop' )); 认证 沿用 routes/auth.js 的 JWT 中间件: POST 类端点:必须带 Authorization: Bearer <jwt> ,校验 req.user.company_id 与 path 中的 cid 匹配。 GET state / stream:同上。 Open API 调用(无人类 cookie,纯 server-to-server):使用现有的 OPEN_API_KEYS 环境变量 + X-API-Key header。 初版不做 RBAC(任何登录用户都能创建项目)。多用户隔离靠 cid。这是已知简化,P5 再加。"},{"f":"arch-acp-bridge.html","title":"ACP Bridge · LemonBoss 桥接","h1":"ACP Bridge · LoopController ↔ LemonBoss agent-manager","headings":["① 现状梳理 · 既有 LemonBoss 链路","② callAgentWithPersistence 的完整实现","③ agent-manager.callAgent 需要扩展的字段","④ SessionPool key 设计","Coder/Tester 特殊:workDir 必须含 unit_id","⑤ 流式输出 + tool_use 中断处理","⑥ 错误传播链","⑦ Mock Claude CLI 模式(开发期省 token)","设计","Mock 响应文件结构","Mock 响应文件示例 · pm/init.json","什么时候用 mock vs 真","$pid / $unit_id 占位符","⑧ 性能考量","⑨ 安全边界","⑩ 一行总结","⑪ 关联"],"defs":[],"content":"① 现状梳理 · 既有 LemonBoss 链路 LemonBoss 已经有完整的 ACP / Master Agent / SessionPool 链路: 关键 insight: DevLoop 的 LoopController 不需要新建 agent-manager 等价物。它 复用 既有 agent-manager.callAgent,只在上面套一层「持久化包装」(P-8 的 callAgentWithPersistence)。 ② callAgentWithPersistence 的完整实现 这是 LoopController 调任何 Agent 的 唯一入口 。它包住 agent-manager.callAgent 同时管 M-10/M-12/M-13。 // server/src/core/loop-controller.js · 节选 const { agentManager } = require ( './agent-manager' ); const { unitRegistry } = require ( '../../services/unit-registry' ); const { docStore } = require ( '../../services/doc-store' ); const { bugTracker } = require ( '../../services/bug-tracker' ); const { sseBroker } = require ( '../../services/sse-broker' ); const { timelineLogger } = require ( '../../services/timeline-logger' ); const { loadAllTools } = require ( '../tools/tool-registry' ); const toolRegistry = loadAllTools (); // 单例 async function callAgentWithPersistence (name, input) { const { pid, unit_id } = input; const call_id = genCallId (); const startedAt = Date.now (); // 1. 注入运行态 (M-10) await writeRuntimeState (pid, name, { agent: name, status: 'running' , call_id, unit_id, current_step: 0 , total_steps: 0 , started_at: new Date (). toISOString (), } ); timelineLogger. append (pid, { source: 'system' , kind: 'agent_started' , ref: `agent/ ${name} / ${call_id} ` , summary: ` ${name} started` , } ); sseBroker. emit (pid, 'agent.started' , { agent: name, call_id, unit_id } ); // 2. 注入 memory + 构建 prompt const systemPrompt = await buildPromptWithMemory (name, pid); // 3. 构建 ctx (传给所有工具的运行时上下文) const ctx = { pid, agent: name, unit_id, call_id, loopController: module. exports , docStore, unitRegistry, bugTracker, timelineLogger, sseBroker, companyDir: companyDirOf (pid), projectDir: projectsDir (pid), unitDir: unit_id ? unitDirOf (pid, unit_id) : null , } ; // 4. 调既有 agent-manager (这是关键的\"桥\"那一行) let result; try { result = await agentManager. callAgent ( { agentName: `devloop- ${name} ` , // 与 _registry.json 中 id 对齐 client: companyDirOf (pid), // SessionPool key 之一 deploy: pid, // 另一个 key workDir: ctx.unitDir || ctx.projectDir, systemPrompt, input, // 透传 PMInput / ArchitectInput / etc toolPolicy: getToolPolicy (name), // 从 _registry.json 读 toolRegistry, toolCtx: ctx, // 关键:工具上下文注入 timeoutMs: getTimeout (name), // PM 10min,Coder 5min, etc onChunk: (chunk) => { // 流式回调 sseBroker. emit (pid, 'agent.stream' , { agent: name, unit_id, chunk } ); } , } ); // 5. 成功收尾 await writeRuntimeState (pid, name, { status: 'done' , last_output_ref: await snapshot (result, pid, call_id), updated_at: new Date (). toISOString (), } ); timelineLogger. append (pid, { source: name, kind: 'agent_done' , ref: `agent/ ${name} / ${call_id} ` , summary: ` ${name} done in ${Date.now() - startedAt} ms` , payload: { tokens: result.tokens_used } , } ); sseBroker. emit (pid, 'agent.done' , { agent: name, call_id, ...result } ); return result; } catch (e) { // 6. 失败处理 (P-8 状态机) await writeRuntimeState (pid, name, { status: 'error' , error: { code: e.code || 'AGENT_CRASH' , message: e.message, stack: e.stack } , updated_at: new Date (). toISOString (), } ); timelineLogger. append (pid, { source: 'system' , kind: 'agent_error' , ref: `agent/ ${name} / ${call_id} ` , summary: e.message, } ); sseBroker. emit (pid, 'agent.error' , { agent: name, call_id, code: e.code, message: e.message } ); throw e; } } ③ agent-manager.callAgent 需要扩展的字段 LemonBoss 既有 agent-manager.callAgent 已经接受 agentName / client / deploy / workDir 等。要支持 DevLoop 需要 追加 3 个字段 : 新字段 类型 含义 改 agent-manager 的代码量 toolRegistry Map<string, ToolDef> 本次注册的 79 个工具(来自 loadAllTools) +5 行:透传给 SessionPool → Protocol toolCtx object 工具调用时的 ctx(pid · sseBroker · etc) +3 行:同上透传 onChunk function 流式回调(SSE 用) +2 行:传给 stream-executor 合计 ~10 行修改 server/src/core/agent-manager.js 。 不改 agent-manager 的 public API ,所有新字段都是可选;不传时行为同既有。 ④ SessionPool key 设计 既有 SessionPool key = (agentName, client, deploy, workDir) 。DevLoop 把它映射成: SessionPool key 段 DevLoop 填入 agentName devloop-pm / devloop-architect "},{"f":"arch-components.html","title":"三个新组件","h1":"三个新组件","headings":["① LoopController","关键方法","主循环骨架","② DocStore","API 面","HTML 校验规则","③ UnitRegistry","API 面","状态转换合法性","三者协作","外部依赖"],"defs":[],"content":"① 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.json schema 校验 + 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 \\ to pending coding testing failed passed blocked pending — ✓ coding — ✓ testing — ✓ ✓ failed ✓ — ✓ passed ✓ (L2 reset) — blocked ✓ (人类干预) — 非法转换 → 抛异常 + SSE 错误事件,便于排查。 三者协作 外部依赖 包 用途 是否新增 cheerio 解析 / 校验 HTML 新增 (~120KB) @sinclair/typebox JSON schema 校验 已存在 better-sqlite3 — 已存在(不需要新表) 不引入新数据库;项目状态全部落文件。Dashboard 用 SSE 推送,不需要轮询 DB。"},{"f":"arch-git-versioning.html","title":"Git Versioning","h1":"Git Versioning · 每次 Agent 完工 = 一个 commit","headings":["① 现状 vs 提议","之前(无 git)","提议(每项目一个 repo)","② Repo 范围决策","③ 落盘后的目录","④ .gitignore 模板","⑤ Commit 时机表","⑥ 谁调 git · 集中控制","⑦ GitVault · C-18 服务方法","⑧ P-10 · 自动提交流程","项目级 tag 时机","⑨ 与 timeline / changelog / bugs 的关系","git log","timeline.ndjson","changelog.ndjson","⑩ 失败恢复 · 新能力","⑪ P-8 状态机的协同","⑫ 推 remote · v2 留口","⑬ 安全考量","⑭ P0 工时增量","⑮ 实战命令","⑯ 关联"],"defs":["GitVault","Process.GitCommit"],"content":"① 现状 vs 提议 之前(无 git) 文件 mtime 是唯一时间戳 history/ 目录是 ad-hoc 归档 没有 atomic snapshot 回滚靠 fs.rename + 手动拷贝 retry 之间无法 diff(旧文件被覆盖) 项目导出 = 拷整个目录 提议(每项目一个 repo) 每个 Agent 完工 → git commit shipped → git tag 整个 retry 链是 git log(可 diff 任意两次) 崩 → git reset 干净恢复 导出 → git archive 未来可加 git push 到 GitHub(v2) ② Repo 范围决策 方案 位置 优 劣 A · 每项目一个 ← 选这个 data/companies/{cid}/projects/{pid}/.git 隔离强 · 删项目就删 repo · 导出干净 · 可分别 push 不同 remote repo 数量随项目增长(每个 ~10-50MB) B · 一个公司一个 data/companies/{cid}/.git 跨项目共享对象 跨项目混在 git log;不易导出单项目 C · 全局一个 data/.git 对象去重最大 所有 company 混;权限灾难 D · 主仓库 lemonboss/.git 不增 repo 污染 LemonBoss 主提交历史;不可接受 选 A 的核心理由:DevLoop 项目本质是 临时工作空间 ,shipped 后或归档或导出,独立 repo 让生命周期边界清晰。 ③ 落盘后的目录 data/companies/{cid}/projects/{pid}/ ├── .git/ # NEW · 整个项目的 git 历史 ├── .gitignore # NEW · 项目创建时初始化 ├── project.json ├── prd/ → tracked ├── sdd/ → tracked ├── units.json → tracked ├── integration/spec.json → tracked ├── timeline.ndjson → tracked (append-only,git OK) ├── bugs.ndjson → tracked ├── changelog.ndjson → tracked ├── units/ → tracked (code + tests + contract) ├── integration/runs/ → tracked (result.json + triage.json) ├── agent-state/ → .gitignore (高频写,运行态) ├── history/ → .gitignore (git 本身就是 history) └── stream.log → .gitignore (SSE 流,7 天滚动) ④ .gitignore 模板 # LemonBoss DevLoop 项目 .gitignore (项目创建时落盘) # 高频写的运行态,不需要版本 agent-state/ # 真冗余 (git 本身就是 history) history/ # 大日志 stream.log units/*/runs/*/stdout.txt integration/runs/*/process.log integration/runs/*/http-trace.ndjson.gz # Node / 依赖 node_modules/ *.log .DS_Store .cache/ .tmp/ .coverage/ # 测试中间文件 units/*/.vitest-cache/ # 但保留: result.json triage.json contract.json 等小 JSON 由 GitVault.init 工具在 git init 后立即写入;首个 commit 就含 .gitignore。 ⑤ Commit 时机表 事件 commit msg 模板 触发位置 files added 项目创建 init: project {pid} · {requirement 摘要 30 字} LoopController.createProject 末尾 project.json + .gitignore PM 写完 PRD prd: rev {N} · {change_summary} PM Agent 成功返回后 prd/** + changelog.ndjson + timeline 增量 Architect 生成 SDD sdd: rev {N} · {units_count} units · {summary} Architect generate 成功后 sdd/** + units.json + integration/spec.json + changelog/timeline Architect patch sdd: patch · {affected_units 列表} Architect patch 成功后 仅改的 sdd/mvcp/{x}.html + units.json (hash 更新) Coder 实现 code({unit_id}): implement · attempt {N} Coder 成功 + Tester 绿才 commit units/{id}/code/** + contract.json + coder.log Coder retry 通过 code({unit_id}): retry → green · attempt {N} Tester 绿(attempt > 1) 同上 Tester 写测试 合并到上一条 commit 同 Coder commit 一起 tests/ + runs/{}/result.json 一起 BugTriager 归因 triage({run_id}): {category} · confidence {n} Triager 写完 triage.json 后 integration/runs/{}/triage.json + bugs.ndjson 增量 Integrator 跑 integ({run_id}): {ok|fail} · {cases_passed}/{cases_total} Integrator 完工 integration/runs/{}/result.json + process.log(如不在 ignore) Unit reset (L2 修复后) 不 commit ,下一次 Coder commit 时一起 — — 项目 shipped ship: prd@{prd_rev} sdd@{sdd_rev} + git tag shipped-{rev} LoopController.shipProject —(tag only) 项目 sign-off chore: sign-off by {user_id} + git tag final-{rev} POST /sign-off project.json (status=shipped-final) 项目 abandon chore: abandon · {reason} + git tag abandoned POST /abandon project.json (status=abandoned) 关键决策: 失败不 commit (避免噪音 + 失败的代码不该留在 git 主线)。Tester 红 → Coder retry 时旧代码被工作区覆盖,反正没人需要回到失败态。 ⑥ 谁调 git · 集中控制 关键原则: Agent 不动 git 。LoopController 在 callAgentWithPersistence 的 success 分支统一调 GitVault.commit。 这样 Agent 沙箱继续严格(不允许 Bash 调 git),但项目状态 真实有版本 。 ⑦ GitVault · C-18 服务方法 server/services/git-vault.js module.exports .gitVault = { // 初始化(项目创建时) init (pid: string , opts?: { name?: string , email?: string , // 默认 \"DevLoop <devloop@lemonboss.local>\" initialBranch?: string , // 默认 \"main\" } ): Promise < { ok: boolean , head: string } >, // 主提交入口 (LoopController 调) commit (pid: string , { kind: 'init' | 'prd' | 'sdd' | 'code' | 'triage' | 'integ' | 'ship' | 'chore' , unit_id?: string , // code 时填 run_id?: string , // triage / integ 时填 summary: string , // 简短描述 files?: string [], // 限定 add 的路径 (默认 git add -A 受 .gitignore) author?: { name, email } , //"},{"f":"arch-mapping.html","title":"对 LemonBoss 的映射","h1":"对 LemonBoss 现有架构的映射","headings":["能力映射总表","分层视图","为什么 P1~P4 不抽公共 retry 函数","不动的边界"],"defs":[],"content":"能力映射总表 能力 新组件 对应现有模块 改动 Agent 启动与会话复用 — packages/acp-core/protocol/ClaudeCliProtocol.js + server/src/core/session-pool.js REUSE 多 Agent 调用 facade — server/src/core/agent-manager.js · master-session.js REUSE 任务 → 步骤拆分 — server/src/tools/orchestrate-tool.js REUSE DAG 依赖解析 — server/src/core/scheduler.js ( getExecutionGroups ) REUSE Pipeline 描述结构 — server/services/pipeline-service.js REUSE · units.json 复用 step 结构 重试 + 反思 retryWithReflection() 写在 loop-controller.js 内 参考 server/services/evolution-loop.js (PRAE) 思路,但 不抽公共 NEW · P5 再考虑合并 ACP / Claude CLI 协议层 — packages/acp-core/ REUSE Agent 模板注册 6 个新模板 system/templates/_registry.json NEW 三层 Loop 编排 LoopController server/src/core/loop-controller.js NEW PRD/SDD 文档存储 DocStore server/services/doc-store.js NEW Unit 状态机 UnitRegistry server/services/unit-registry.js NEW HTTP / SSE 入口 /api/devloop/* server/routes/devloop.js → 挂在 server/index.js NEW Web 看板 DevLoopDashboard.vue 放在 web/src/views/ ,复用 PipelineEditor DAG 渲染 NEW 结果持久化根 — data/companies/{cid}/projects/{pid}/ REUSE · 沿用 company-scoped 文件根 进程沙箱 / 端口管理 — server/src/engine/process-guard.js REUSE WS 流式 — server/routes/claude-chat-ws.js REUSE · DevLoop SSE 走 HTTP 复用同套帧 分层视图 三色块代表三种改动类型;越靠下越基础,越靠上越业务。 红 新增 · 灰 复用 · 黄 补丁 · 黑 底层依赖 为什么 P1~P4 不抽公共 retry 函数 EvolutionLoop 当前实现 PRAE (Perceive → Reason & Plan → Execute → Evaluate → Reflect),里面有「失败 → 反思 → 重试」逻辑。直觉上 L1 可以共用,但 初版决定不抽 : 避免回归风险: EvolutionLoop 跑的是 Skill 进化,目前在生产里运行。动它意味着要把 Skill 进化的回归一起做。 反思 prompt 差异大: Skill 进化的反思是「Skill 评分低,怎么改 prompt」;L1 的反思是「单测失败,代码哪里错」。共用会让 prompt 模板变成「上帝模板」,反而难维护。 80 行不算多: P2 在 loop-controller.js 内重写一份 retryWithReflection 也就 80 行,DRY 不必这么早追。 // P2 直接在 server/src/core/loop-controller.js 写一份新的 async function retryWithReflection ( unit , budget ) { let failures = [] ; for ( let i = 0 ; i < budget; i++) { await agentManager . callAgent ( 'coder' , { spec: unit.spec, history: failures } ); const r = await agentManager . callAgent ( 'tester' , { unit } ); if (r. ok ) return { ok: true , retries: i } ; failures. push (r. failures ); } return { ok: false , reason: \"budget_exceeded\" } ; } // P5 视实测情况再决定是否与 evolution-loop 合并到 packages/ 公共目录 这条改动同步影响 plan-migration.html 的 P2 阶段:retry 函数从「抽公共」改为「直接新写」。 不动的边界 本次改造绝不触碰: ACP 协议本身( packages/acp-core/ ) Master / Orchestrator 已有行为(仅注册新的 6 个 Agent 模板) WebSocket 路由( /ws/claude ) SkillsMarket / Arena 路径下任何文件 认证 / 用户系统( routes/auth.js , routes/users.js )"},{"f":"arch-project-root.html","title":"Project Root","h1":"Project Root · 项目根目录是什么 · 在哪建","headings":["① 「项目」的定义","② 三种项目模式","③ project.json 加 root_path 字段","④ 路径解析服务(新组件)","⑤ 三个创建入口","5.1 REST · 主入口","5.2 CLI · 命令行","5.3 UI · Dashboard 的「+ NEW PROJECT」","⑥ 创建流程 · 三模式各自 step-by-step","⑦ 各组件如何引用 root_path","⑧ memory · 始终在 company 级","⑨ 跨主机 / 跨机器场景","⑩ 创建后 LoopController 接管的统一接口","⑪ project.json 完整示例","⑫ 路径白名单(沙箱安全)","⑬ 实战命令","⑭ 工时增量","⑮ 关联"],"defs":[],"content":"① 「项目」的定义 一个 DevLoop 项目 = 一个目录 + 一个 project.json + 一棵 git 历史。这个目录就是 project root ,是整个 DevLoop 流程的 唯一锚点 。 所有路径都从 root 派生: {project_root}/ # 这就是「根」 ├── .git/ # GitVault init ├── .gitignore ├── project.json # M-1 元数据 + root_path 自身 ├── prd/ # PM 写 ├── sdd/ # Architect 写 ├── units.json ├── units/{unitId}/ # Coder/Tester 写 ├── integration/ # Integrator/Triager 写 ├── agent-state/ # P-8 持久化 ├── timeline.ndjson · bugs.ndjson · changelog.ndjson ├── stream.log └── history/ # 归档 (.gitignore) 所有 Agent、所有工具、LoopController、GitVault 都按 {project_root}/... 找文件。 没有任何代码硬编码 data/companies/... 。这是关键设计。 ② 三种项目模式 mode root_path git repo v1 支持 典型场景 sandbox (默认) 自动生成: {server_data_dir}/companies/{cid}/projects/{pid}/ 自动 git init ✅ v1 不确定代码归宿 / 内部 demo / 学习 / 隔离实验 linked-new 用户指定空目录或不存在的目录,比如 ~/code/auto-todo 自动 git init 到该路径 ✅ v1 已知代码最终归宿;想生成就直接落盘那里;之后可 git push linked-existing 用户指定已有 git repo 的子目录,比如 ~/code/my-app/services/auth/ 复用上级 git repo ⚠️ v2 给已存在的项目 增量 加新模块;冲突风险高,需要 git stash 等保护 v1 推荐:sandbox 是默认。 linked-new 当作 power user 选项 — 它给你「自动 push 到 GitHub」的可能性,但也要承担「DevLoop 误覆盖了你 ~/code/ 下别的东西」的风险(沙箱机制阻止越界,但 user-chosen path 越界相对沙箱更难定义)。 ③ project.json 加 root_path 字段 M-1 ProjectMeta 现在含 root_path(绝对路径)和 mode: const ProjectMeta = Type. Object ( { id: Type. String ( { pattern: '^p-[a-z0-9]{6}$' } ), company_id: Type. String (), name: Type. String (), // 新增字段 mode: Type. Union ( [ Type. Literal ( 'sandbox' ), Type. Literal ( 'linked-new' ), Type. Literal ( 'linked-existing' ), ] ), root_path: Type. String (), // 绝对路径,所有派生路径的源 status: Type. Union ( [ Type. Literal ( 'prd_drafting' ), Type. Literal ( 'running' ), Type. Literal ( 'blocked' ), Type. Literal ( 'shipped' ), Type. Literal ( 'shipped-final' ), Type. Literal ( 'abandoned' ), ] ), ... } ); 所有组件读 path 都走 resolveProjectPath(pid) → project.root_path ,而不是拼 data/companies/.../ 。 ④ 路径解析服务(新组件) server/services/path-resolver.js · 新增,~80 行 module. exports .pathResolver = { // 单例索引: pid → root_path // 在 LoopController 启动时 walk(data/companies/) 建立 // 加: linked 项目通过 ~/.devloop/linked-projects.json 维护索引 init (): Promise < void >, resolveProjectRoot (pid: string ): string , // 绝对路径,unknown pid 抛错 resolveUnitDir (pid, unitId): string , resolvePrdSite (pid): string , resolveSddSite (pid): string , resolveAgentState (pid, agent): string , resolveMemoryDir (pid, agent): string , // 注意:memory 总在 company 级,不在 root 下 resolveTimelineLog (pid): string , ... // 反向 findProjectByPath (abs: string ): { pid, root } | null , // linked 项目注册 registerLinkedProject ( { pid, root_path, mode } ): Promise < void >, unregisterLinkedProject (pid): Promise < void >, } ; 关键: 所有用 projectsDir(pid) 的地方改成 pathResolver.resolveProjectRoot(pid) 。这样切换 mode 是 路径透明 的。Agent 的 ctx.projectDir 注入也走这个。 linked 项目索引 ~/.devloop/linked-projects.json 形如: { \"p-731042\" : { \"root_path\" : \"/Users/yibo/code/auto-todo\" , \"mode\" : \"linked-new\" , \"company_id\" : \"c-lemonv\" , \"registered_at\" : \"2026-05-13T11:31:00Z\" } } sandbox 项目 不需要 注册(按惯例从 data/companies/.../projects/ 扫描即可)。 ⑤ 三个创建入口 5.1 REST · 主入口 POST /api/devloop/projects { \"company_id\" : \"c-lemonv\" , \"name\" : \"Auto TODO\" , \"requirement\" : \"做一个 TODO API + Vue 前端\" , // 新增 - 可选,默认 mode=sandbox \"mode\" : \"sandbox\" | \"linked-new\" | \"linked-existing\" , \"target_path\" : \"/Users/yibo/code/auto-todo\" // linked-* 时必填 } // 响应 { \"project_id\" : \"p-731042\" , \"mode\" : \"linked-new\" , \"root_path\" : \"/Users/yibo/code/auto-todo\" , \"status\" : \"prd_drafting\" , \"stream_url\" : \"/api/devloop/projects/p-731042/stream\" } 5.2 CLI · 命令行 # 文件: scripts/devloop-new.js (NEW, ~80 行) # sandbox 模式 (默认) node scripts/devloop-new.js \\ --name \"Auto TODO\" \\ --requirement \"做一个 TODO API + Vue 前端\" # 等价于 mode=sandbox, root 自动到 data/.../projects/p-XXXXXX/ # 输出: 🍋 Project p-731042 created (sandbox) # Root: /Users/yibo/Documents/lemonAI/lemonboss/data/companies/c-default/projects/p-731042 # linked-new 模式 node scripts/devloop-new.js \\ --path ~/code/auto-todo \\ --name \"Auto TODO\" \\ --requirement \"做一个 TODO API + Vue 前端\" # 输出: 🍋 Project p-731042 created (lin"},{"f":"arch-tool-registry.html","title":"Tool Registry · 工具运行时实现","h1":"Tool Registry · 79 个工具如何变成可调用的 JS","headings":["① 现状 · 三层工具栈","② 79 个工具按目录组织","③ 标准 wrapper 模板(直接照抄)","④ tool-registry.js 改造","⑤ Agent 模板的 toolPolicy","⑥ Bash 工具的 jsonGuard 中间件","⑦ ctx 的内容(ToolRegistry 注入)","⑧ 错误传播","⑨ ToolRegistry 给 Claude CLI 的注入","⑩ Phase 0 验收(接 plan-migration)","⑪ 工时估算(Phase 0)","⑫ 关联页面"],"defs":[],"content":"① 现状 · 三层工具栈 Claude CLI 调工具走 三层 ,越靠下越底层: 关键点:本次 不动 L1 / L2 。只在 L2 注册层 追加 79 个 L3 工具定义。Claude CLI 进程启动时被告知\"你可以调这些 + 既有 Read/Write/Bash\",调用时 ToolRegistry 路由到对应 execute 函数。 ② 79 个工具按目录组织 server/src/tools/ ├── tool-registry.js # 既有,加载所有工具 (REUSE) ├── read-tools.js # 既有 (REUSE) ├── call-agent-tool.js # 既有 (REUSE) ├── orchestrate-tool.js # 既有 (REUSE) ├── marketplace-tools.js # 既有 (REUSE) ├── train-skill-tool.js # 既有 (REUSE) └── devloop/ # NEW 整个目录 ├── persistence-tools.js # 持久化 5 件套 (M-10/11/12/13) │ ├── write_todolist │ ├── update_todo_item │ ├── update_runtime_state │ ├── timeline_append │ └── record_memory ├── doc-tools.js # PM / Architect 文档工具 │ ├── bootstrap_prd_site / bootstrap_sdd_site │ ├── write_chapter / write_mvcp_page │ ├── archive_doc │ ├── diff_html / diff_units │ ├── echo_diff │ ├── update_search_index │ ├── validate_prd_site / validate_sdd_consistency │ └── hash_contract ├── coder-tools.js # Coder 工具 │ ├── contract_bridge │ ├── sandbox_check │ ├── append_coder_log │ └── (jsonGuard 是 Bash 中间件,不在工具表) ├── tester-tools.js # Tester 工具 │ ├── parse_vitest │ ├── hint_builder │ ├── bug_open │ ├── bug_transition │ └── bug_list ├── integrator-tools.js # Integrator 工具 │ ├── port_picker │ ├── spawn_units │ ├── health_wait │ ├── run_case │ ├── parse_placeholder │ └── collect_stderr └── triager-tools.js # BugTriager 工具 ├── extract_evidence (→ verify_evidence) ├── category_classifier ├── diff_contract_vs_impl └── find_similar_bug 合计 79 个工具中:持久化 5 + doc 11 + coder 3 + tester 5 + integrator 6 + triager 4 = 34 个独立函数 (很多 Agent 复用,所以签名是 79 但唯一实现是 34)。 每个 ≈ 30-80 行 JS ,总实现量 ≈ 1500-2500 行。 ③ 标准 wrapper 模板(直接照抄) 每个工具都按下面这个模式注册到 tool-registry.js : // server/src/tools/devloop/persistence-tools.js · 节选 const { Type } = require ( '@sinclair/typebox' ); const fs = require ( 'fs' ); const path = require ( 'path' ); const { projectsDir } = require ( '../../services/paths' ); /** * 每个工具导出一个 ToolDefinition 对象: * { name, description, inputSchema, execute } * 由 tool-registry.js 收集后注入 Claude CLI 启动参数。 */ module. exports .write_todolist = { name: 'write_todolist' , description: '初始化 agent-state/{agent}-todo.json,写入完整 todolist' , inputSchema: { type: 'object' , required: [ 'items' ] , properties: { items: { type: 'array' , items: { type: 'object' , required: [ 'id' , 'description' ] , properties: { id: { type: 'string' , pattern: '^step-\\\\d{3}$' } , description: { type: 'string' } , depends_on: { type: 'array' , items: { type: 'string' } } } } } } } , // ctx 由 ToolRegistry 注入,含 pid · agent · call_id · sessionId · loopController · sseBroker async execute ( { items } , ctx) { const { pid, agent, call_id } = ctx; const p = path. join ( projectsDir (pid), 'agent-state' , ` ${agent} -todo.json` ); const todolist = { agent, call_id, created_at: new Date (). toISOString (), items: items. map (it => ( { ...it, status: 'todo' } )), current: items[ 0 ]?.id || null , progress_pct: 0 , } ; fs. mkdirSync (path. dirname (p), { recursive: true } ); fs. writeFileSync (p, JSON.stringify (todolist, null , 2 )); ctx.sseBroker?. emit (pid, 'agent.todolist' , { agent, call_id, items: todolist.items, } ); return { ok: true , total: items. length , path: p } ; } } ; 三个不变量 每个 wrapper 都要遵守: inputSchema 是 JSON Schema (Claude CLI native tool_use 格式),不是 TypeBox(虽然 TypeBox 可以序列化到 JSON Schema,方便统一来源) execute 接收 第二个参数 ctx ,含 pid · agent · call_id · sessionId 等运行时上下文(由 LoopController 注入) 返回值必须是 纯 JSON 可序列化 对象,给 Claude 看(不能含 Buffer / Stream / Function) ④ tool-registry.js 改造 LemonBoss 既有 server/src/tools/tool-registry.js , 只加载新模块即可 : // server/src/tools/tool-registry.js · 仅追加 1 段 const { readDirImports } = require ( './_util' ); function loadAllTools () { const registry = new Map (); // 既有(REUSE): register (registry, require ( './read-tools' )); register (registry, require ( './call-agent-tool' )); register (registry, require ( './orchestrate-tool' )); register (registry, require ( './marketplace-tools' )); register (registry, require ( './train-skill-tool' )); // NEW: devloop 工具批量加载 for"},{"f":"arch-workflow.html","title":"运行时工作流","h1":"运行时工作流","headings":["端到端时序图","同一时刻三个 Loop 的并发关系","SSE 事件清单","错误与中止"],"defs":[],"content":"端到端时序图 同一时刻三个 Loop 的并发关系 L1 内部允许并行(多 Unit),但 L2 始终串行 (一个项目同时只能有一个 Integrator 跑)。L3 触发会等待当前 L2 跑完再合并 PRD。 场景 处理 L1 跑了一半,L3 新需求到达 当前 L1 跑完再合并(保证状态一致),合并后增量 reset L2 跑了一半,L3 新需求到达 L2 跑完再合并;若 L2 失败也照常合并(人类可能本来就是为了改这个) L2 跑了一半,又收到一个 L3 反馈 队列化,按时间序处理 多项目并行 每项目独立 LoopController 实例,互不干扰 SSE 事件清单 Dashboard 通过 GET /api/devloop/projects/:pid/stream 订阅。事件类型: event 触发时机 payload 关键字段 prd.updated PM 写 PRD 完成 rev, changeSummary sdd.updated Architect 写 SDD 完成 rev, mode (generate/patch) unit.state UnitRegistry 状态转换 unit_id, from, to, retries agent.stream Agent stdout/stderr 流式 agent, unit_id, chunk integration.result Integrator 返回 ok, failures[] triage.result BugTriager 返回 category, confidence, affected_units human_intervention_needed confidence < 0.5 或 blocked reason, suggestion project.shipped L2 通过 final_rev project.exhausted L2 修复预算耗尽 last_triage 错误与中止 LoopController 顶层 try/catch : Agent 调用超时(> 10min): kill 进程,retry 一次,再失败置 Unit blocked。 DocStore 写失败(磁盘满 / 权限): 整个项目挂起,SSE 告警人类。 未捕获异常: 记录到 stream.log ,发 fatal 事件, 不 自动重启(避免雪崩)。"},{"f":"data-documents.html","title":"四类文档","h1":"四类文档","headings":["派生关系","四个文档逐一","谁能写谁","归档策略"],"defs":[],"content":"派生关系 四个文档逐一 prd.html sdd.html units.json runs/result.json <!-- PM Agent 维护,每次人类反馈追加 section --> <article data-doc = \"prd\" data-rev = \"3\" > <h1> Auto Auth System </h1> <section data-rev = \"1\" > <h3> 登录 </h3> <p> 用户名密码登录,会话 7 天有效。 </p> </section> <section data-rev = \"3\" data-origin = \"yibo@2026-05-13\" > <h3> OAuth 登录 </h3> <p> 支持 GitHub OAuth。Token 刷新 ≤ 5s。 </p> </section> <footer data-changelog > <p> rev 1 · init · 邮箱密码登录 </p> <p> rev 2 · yibo · 加个人主页 </p> <p> rev 3 · yibo · GitHub OAuth </p> </footer> </article> <!-- Architect 生成;嵌入可机读 unit 元数据 --> <article data-doc = \"sdd\" data-derived-from = \"prd@rev3\" > <section data-unit-id = \"auth-be\" data-type = \"be\" data-depends = \"\" > <h3> Unit · auth-be </h3> <p> 提供登录 / 注册 / token 校验端点 </p> <pre data-contract = \"openapi\" > POST /api/auth/login request: { email, password } response: 200 { token } | 401 { error:\"invalid_credentials\" } </pre> <ul data-acceptance > <li> 有效凭据 → 200 + JWT </li> <li> user 不存在 → 401 invalid_credentials </li> </ul> </section> <!-- 更多 section ... --> </article> { \"project_id\" : \"p-001\" , \"prd_rev\" : \"3\" , \"sdd_rev\" : \"5\" , \"units\" : [ { \"id\" : \"auth-be\" , \"type\" : \"be\" , \"status\" : \"passed\" , \"depends_on\" : [] , \"retries\" : 1 , \"retry_budget\" : 5 , \"contract_hash\" : \"sha256:7f3a...\" , \"last_run\" : \"run-20260513-0731\" } , { \"id\" : \"auth-fe\" , \"type\" : \"fe\" , \"status\" : \"testing\" , \"depends_on\" : [ \"auth-be\" ] , \"retries\" : 0 , \"retry_budget\" : 5 , \"contract_hash\" : \"sha256:9c1d...\" } ] , \"l2_rounds\" : 0 , \"l2_budget\" : 3 } { \"run_id\" : \"run-20260513-0731\" , \"loop\" : \"L1\" , \"unit_id\" : \"auth-be\" , \"agent\" : \"tester\" , \"attempt\" : 2 , \"summary\" : { \"total\" : 8 , \"passed\" : 7 , \"failed\" : 1 } , \"ok\" : false , \"failures\" : [ { \"test\" : \"login.invalid-pwd\" , \"expected\" : 401 , \"got\" : 500 , \"stack\" : \"TypeError: ...\" , \"hint\" : \"先查 user 再 bcrypt 比对\" } ] , \"next_action\" : \"retry_coder\" , \"retry_budget_left\" : 3 , \"duration_ms\" : 1245 } 谁能写谁 文档 写入者 读取者 prd.html PM (通过 DocStore) Architect · Dashboard sdd.html Architect (通过 DocStore) Coder · Tester · Integrator · Triager · Dashboard units.json Architect (字段) + UnitRegistry (status) 所有 integration/spec.json Architect Integrator units/{id}/code/** Coder Tester · Integrator · Triager (只读) units/{id}/contract.json Coder Tester · 其他 Unit Coder · Triager units/{id}/tests/** Tester Integrator runs/{id}/result.json Tester (L1) / Integrator (L2) LoopController · Triager · Dashboard triage.json BugTriager LoopController · Architect history/** DocStore.archive() Dashboard diff 视图 agent-state/{agent}.json LoopController + Agent 工具 Dashboard · recoverOnBoot agent-state/{agent}-todo.json Agent (write_todolist · update_todo_item 工具) Dashboard · getProgress agent-state/snapshots/* LoopController retry 时回放 · recover timeline.ndjson TimelineLogger (所有 source append) Dashboard 时间线 · audit memory/devloop/{agent}/*.md Agent (record_memory 工具) Agent 启动时注入 prompt 归档策略 每次 prd / sdd / units.json 被写时,旧版本自动移到 history/ ,命名规则: history/prd-rev-{N}.html history/sdd-rev-{N}.html history/units-rev-{N}.json Dashboard 的 PRD diff 视图直接读 history 做 section diff。无需 git。"},{"f":"data-layout.html","title":"文件空间布局","h1":"文件空间布局","headings":["全景目录树","命名规则","沙箱边界(写白名单)","磁盘占用估算","多项目共享"],"defs":[],"content":"全景目录树 data/ └── companies/ └── {cid}/ ├── agents/{agentId}/ # 复用:现有 agent 沙箱 ├── conversations/ # 复用 ├── memory/ # 复用根 │ └── devloop/ # NEW · DevLoop Agent 跨项目长期记忆 (M-11) │ ├── pm/{topic}.md │ ├── architect/{topic}.md │ ├── coder/{topic}.md │ └── ... └── projects/ # NEW · LoopController 根 └── {pid}/ ├── .git/ # NEW · per-project git repo (见 arch-git-versioning) ├── .gitignore # NEW · 排除 agent-state · history · stream.log · runs stdout ├── project.json # 项目元数据 (M-1) ├── prd/ # NEW · PM 写的多页 PRD 站点 (M-3) │ ├── index.html │ ├── reading-guide.html │ ├── assets/{shell.css, shell.js, search-index.json} │ ├── product/{background, personas, original-req, scope, success-metrics, glossary}.html │ ├── feature/feature-tree.html │ ├── flow/user-flows.html │ └── prototype/proto-{viewName}.html ├── sdd/ # NEW · Architect 写的多页 SDD 站点 (M-4) · 待 Phase 3 详定 │ ├── index.html │ ├── assets/{shell.css, shell.js, search-index.json} │ └── mvcp/{model, view, control, process}.html ├── units.json # Architect 字段 + UnitRegistry status (M-2) ├── timeline.ndjson # 全流程统一日志 append-only (M-13) ├── bugs.ndjson # NEW · 跨 run bug 台账 (M-14) ├── changelog.ndjson # NEW · 版本变更流 (M-15) ├── agent-state/ # NEW · Agent 持久化运行态 │ ├── pm.json # M-10 AgentRuntimeState │ ├── pm-todo.json # M-12 AgentTodolist │ ├── architect.json │ ├── architect-todo.json │ ├── coder.json # 单 agent 同时只跑一个 unit │ ├── coder-todo.json │ ├── ... (tester, integrator, bug-triager) │ └── snapshots/ # 输入/输出快照,call_id 索引 │ ├── {call_id}-in.json │ └── {call_id}-out.json ├── history/ # 全部 rev 归档 │ ├── prd-rev-001.html │ ├── prd-rev-002.html │ ├── sdd-rev-001.html │ └── units-rev-001.json ├── units/ │ ├── auth-be/ │ │ ├── spec.json │ │ ├── code/ # Coder 输出 │ │ │ ├── server.js │ │ │ ├── auth-service.js │ │ │ └── ... │ │ ├── tests/ # Tester 输出 │ │ │ └── login.spec.ts │ │ ├── contract.json # Coder 写 │ │ ├── coder.log # Coder 自己的决策日志 │ │ └── runs/ │ │ └── {runId}/ │ │ ├── result.json │ │ └── stdout.txt │ ├── auth-fe/... │ └── profile-be/... ├── integration/ │ ├── spec.json # Architect 写 │ └── runs/ │ └── {runId}/ │ ├── result.json # Integrator 写 │ ├── triage.json # BugTriager 写 │ └── process.log └── stream.log # SSE 客户端视角日志 (7 天滚动) 红 = 新增目录 · 灰 = 已存在并复用 命名规则 占位符 格式 示例 {cid} company id (UUID) c-lemonv-001 {pid} p- + 时间戳后 6 位 p-731042 {unitId} {feature}-{type} auth-be · profile-fe {runId} run-{YYYYMMDD-HHMM} run-20260513-0731 L2 runId run-int-{YYYYMMDD-HHMM} run-int-20260513-1031 沙箱边界(写白名单) Agent cwd 可写路径 PM projects/{pid}/ prd.html · history/prd-* Architect projects/{pid}/ sdd.html · units.json (字段) · integration/spec.json · history/sdd-* · history/units-* Coder projects/{pid}/units/{unitId}/ code/** · contract.json · coder.log Tester projects/{pid}/units/{unitId}/ tests/** · runs/{runId}/** Integrator projects/{pid}/ integration/runs/{runId}/** BugTriager projects/{pid}/ integration/runs/{runId}/triage.json 沙箱由 Claude CLI 的 cli.sandbox.writeAllowList 配置强制执行;越权写直接被工具层拒绝。 磁盘占用估算 项目规模 Unit 数 典型大小 小 3-5 ~5 MB(含日志) 中 10-20 ~30 MB 大 50+ ~150 MB 主要开销在 runs/*/stdout.txt (每次 vitest 完整输出)。如需节省,启用 project.json.compressOldRuns: true 自动 gzip 14 天前的 run 目录。 多项目共享 同一 company 下不同项目之间默认 隔离 。如需共享代码(如公共 utils),约定: 在 companies/{cid}/shared/ 放置共享库 Unit spec 通过 spec.shared_deps: [\"@shared/utils\"] 显式声明 Coder 沙箱里 shared/ 是 只读 挂载,Coder 不能改 P1~P4 不实现 shared,留给 P5 视实际需要再加。"},{"f":"loops-l1.html","title":"L1 · Unit Loop","h1":"L1 · Unit Loop","headings":["状态机","状态字典","调度与并行","Coder → Tester 握手协议","Coder 写入","Tester 读取 + 写入","重试策略 — retryWithReflection","预算与降级"],"defs":[],"content":"状态机 状态字典 state 谁写入 含义 退出条件 pending Architect 创建 unit 时 spec 已就绪,等待 LoopController 调度 有空闲 Coder Agent → coding coding LoopController Coder 正在写 code/ 下文件 Coder 返回 success → testing testing LoopController Tester 正在 vitest run 全绿 → passed · 红 → failed failed Tester 本轮单测失败,retries++ retries < budget → coding · 否则 → blocked passed Tester 单测全绿,等所有 sibling Unit 跟进 所有 Unit passed → L2 gate blocked LoopController 耗尽 retry,无法自愈,需 L3 介入 人类反馈 → 重置回 pending 调度与并行 LoopController 复用 server/src/core/scheduler.js 的 getExecutionGroups() :根据 depends_on 把所有 pending Unit 分成一波一波。同一波之间并行,跨波串行。 // units.json 节选 { \"units\" : [ { \"id\" : \"auth-be\" , \"depends_on\" : [] } , { \"id\" : \"profile-be\" , \"depends_on\" : [] } , { \"id\" : \"auth-fe\" , \"depends_on\" : [ \"auth-be\" ] } , { \"id\" : \"profile-fe\" , \"depends_on\" : [ \"profile-be\" ] } ] } // scheduler.getExecutionGroups() → // wave 1: [auth-be, profile-be] (parallel) // wave 2: [auth-fe, profile-fe] (parallel) Coder → Tester 握手协议 两个 Agent 通过文件系统握手,不直接通信。这让任意一个 Agent 死掉都不会丢上下文。 Coder 写入 units/{id}/code/** — 源码 units/{id}/contract.json — 暴露给下游的契约 units/{id}/coder.log — 决策日志(供 retry 时回顾) Tester 读取 + 写入 读 units/{id}/code/** 读 units/{id}/spec.json 推导测试用例 写 units/{id}/tests/** 写 units/{id}/runs/{runId}/result.json 重试策略 — retryWithReflection 从 evolution-loop.js 抽出共用函数。每次 Tester 失败,把失败原因、上次 Coder 的决策日志、原 spec 一起喂回 Coder,让模型「反思」上次错在哪。 async function retryWithReflection ( unit , budget ) { let failures = [] ; for ( let i = 0 ; i < budget; i++) { const coderInput = { spec: unit.spec, history: failures } ; await agentManager . callAgent ( 'coder' , coderInput); const r = await agentManager . callAgent ( 'tester' , { unit } ); if (r. ok ) return { ok: true , retries: i } ; failures. push (r. failures ); } return { ok: false , reason: \"budget_exceeded\" } ; } 预算与降级 R1 风险联动: 每个 unit 全局 retry 预算 = 5(可在 units.json 用 retry_budget 覆盖)。耗尽后置为 blocked ,LoopController 不会自动进 L2,而是发 SSE 事件等待人类介入(L3 接管)。"},{"f":"loops-l2.html","title":"L2 · Integration Loop","h1":"L2 · Integration Loop","headings":["状态机","L2 闸门触发条件","失败归因 — BugTriager 输出契约","归因 → 回写 → 重置 三步","① 归因","② 回写","③ 重置","L2 修复预算","集成测试套件从哪来?"],"defs":[],"content":"状态机 L2 闸门触发条件 不是「所有 Unit 都跑过一次」,而是「所有 Unit 的 当前 contract_hash 与依赖关系下,状态都是 passed 」。这避免 SDD 改过之后某个旧版 Unit 还停留在 passed 而事实上契约已变。 function l2GateOpen ( project ) { return project.units. every (u => u. status === \"passed\" && u. contract_hash === currentSDD (project). contractHashFor (u. id ) ); } 失败归因 — BugTriager 输出契约 BugTriager 必须输出一个机读 JSON,LoopController 据此自动决定回滚范围。Triager 不直接改文件,只发「建议」。 { \"run_id\" : \"run-int-20260512-1031\" , \"root_cause\" : \"auth-be returns 500 on invalid pwd, expected 401; auth-fe error handler relies on 401\" , \"category\" : \"contract_mismatch\" , \"affected_units\" : [ \"auth-be\" , \"auth-fe\" ] , \"proposed_sdd_patch\" : { \"target_section\" : \"#unit-auth-be\" , \"op\" : \"update\" , \"text\" : \"明确:登录失败必须 401 with {error:'invalid_credentials'}\" } , \"confidence\" : 0.82 } 归因 → 回写 → 重置 三步 ① 归因 BugTriager 读 stderr + diff + units.json + sdd.html,产 triage.json 。 confidence < 0.5 时上报 L3 让人类裁决。 ② 回写 Architect 接收 proposed_sdd_patch ,用 section 级 DOM 操作改 sdd.html ,归档旧版到 history/sdd-rev-N.html 。 ③ 重置 UnitRegistry 把 affected_units 的 status 改回 pending ,并自动重算 contract_hash ,触发 L1 新一轮。 L2 修复预算 同一项目 每个 PRD rev 内最多触发 3 次 L2 修复轮回。超出后整个项目挂起,发 SSE 事件 project.l2_exhausted ,等待人类(L3)发新需求或手动干预。 为什么是 3: 实测 Claude Code Agent 三轮内还修不好的,要么是契约描述本身有歧义(PRD 缺陷),要么是技术选型有根本问题。再加轮次只会浪费 Token。 集成测试套件从哪来? 这是一个设计决策(见 风险与待决 · R3 )。初版选 方案 A : 方案 谁写集成测试 优点 缺点 A · 选中 Architect 在生成 SDD 时同步生成 integration/spec.json 少一个 Agent,集成测试与 SDD 同步演化 Architect 职责变重 B 独立 IntegrationAuthor Agent 关注点分离 多一个 Agent,多一次握手 C Integrator 自己写 读取代码后再写最贴合 容易写一个「永远会通过」的 trivial test"},{"f":"loops-l3.html","title":"L3 · Human Feedback Loop","h1":"L3 · Human Feedback Loop","headings":["状态机","触发渠道","① REST","② Web UI","③ 自动信号","增量 Unit 算法","PRD 增量合并示例","什么时候算「项目结束」"],"defs":[],"content":"状态机 触发渠道 ① REST POST /api/devloop/projects/:pid/requirement JSON body 走 CLAUDE.md 推荐的 -d @/tmp/req.json 方式。 ② Web UI DevLoopDashboard 右上角 + NEW REQUIREMENT ,弹出 textarea。 ③ 自动信号 L1 blocked 或 L2 exhausted 时,LoopController 主动发 SSE,由前端弹「请补充信息」对话框。 增量 Unit 算法 Architect 对比新旧 units.json ,标记三种变化: 变化类型 判断 处置 added 新 unit.id 不在旧 list status = pending,加入 L1 队列 changed 同 id, contract_hash 变化 status 回退到 pending,加入 L1 队列(旧代码归档到 history/ ) removed 旧 id 不在新 list 整个 unit 目录归档到 history/removed/ ,从 DAG 摘除 unchanged id 在且 hash 不变 保持 passed, 无须重跑 。这是增量化的关键 PRD 增量合并示例 PM Agent 不重写整个 PRD,只追加 <section data-rev=N> ,并维护 doc 级 data-rev 自增。这样人类能直观看到「这一轮反馈引入了什么」。 prd.html · rev 2 prd.html · rev 3 (after feedback) <article data-doc = \"prd\" data-rev = \"2\" > <section data-rev = \"1\" > 用户名密码登录,会话 7 天有效 </section> <section data-rev = \"2\" > 个人主页:显示用户名 / 邮箱 </section> </article> <article data-doc = \"prd\" data-rev = \"3\" > <section data-rev = \"1\" > ... </section> <section data-rev = \"2\" > ... </section> <section data-rev = \"3\" data-origin = \"yibo@2026-05-12T10:31\" > + 新增:支持 GitHub OAuth 登录 + NFR:3rd-party token 刷新 ≤ 5s </section> </article> 什么时候算「项目结束」 L3 没有自动退出条件。结束信号来自人类的显式动作: 显式 sign-off: POST /api/devloop/projects/:pid/sign-off → 项目 status 置 shipped-final ,停止接收新需求。 归档: 30 天无人类反馈,自动归档但保留所有产物。 放弃: POST .../abandon ,整个项目目录加 .abandoned 标记,从 Dashboard 列表移除。"},{"f":"loops-overview.html","title":"三层 Loop 模型","h1":"三层 Loop 模型","headings":["对照矩阵","嵌套关系(含数学描述)","三个 Loop 的色彩 token","Emerald · #0E8C5A","Amber · #C77C2C","Violet · #6E48C3","下一步阅读"],"defs":[],"content":"对照矩阵 L1 Unit L2 Integration L3 Human 触发 units.json 有 pending 所有 Unit 状态 = passed POST /devloop/requirement 主导 Agent Coder + Tester Integrator + BugTriager + Architect PM + Architect 每轮产物 code/ · tests/ · runs/result.json integration/runs/ {id} /result.json + triage.json prd.html rev+1 · sdd.html rev+1 退出 单测全绿 → unit passed 集成全绿 → project shipped 人类 sign-off 失败处理 retry coder ≤ N 归因 → 改 SDD → 回 L1 对话化追问,PRD 加 section 预算 每 unit retry ≤ 5 每项目 L2 修复 ≤ 3 轮 无限(人类驱动) 典型时长 1-3 分钟 / unit 2-10 分钟 / 轮 分钟 — 小时 嵌套关系(含数学描述) 三个 Loop 严格嵌套: L1 触发于 L2 内部 ,L2 触发于 L3 内部。即: // 用伪代码表达嵌套关系 while ( human . hasFeedback ()) { // L3 pm . patchPRD (); architect . regenSDD (); while (! project . integrationGreen ()) { // L2 while ( unitsPending ()) { // L1 parallel ( unitsPending (), runUnit ); } const r = integrator . run (); if (!r. ok ) { const tri = triager . analyze (r); architect . applyPatch (tri. sdd_patch ); units . reset (tri. affected ); } } } 三个 Loop 的色彩 token 同一套颜色在 PRD 文档、看板 UI、Agent 日志、状态徽章里一致复用,便于跨界面识别当前所处的 Loop 层级。 L1 · Unit Emerald · #0E8C5A 短周期 / 高频 / 低单价。日志里所有 L1 事件都用这个色描边。 L2 · Integration Amber · #C77C2C 中周期 / 中频 / 中单价。Dashboard 中间状态栏专色。 L3 · Human Violet · #6E48C3 低频 / 高单价 / 决策性。看板 header / Hero 用此色作 accent。 下一步阅读 详细状态机在以下三页: L1 · Unit Loop — 单 Unit 的写码—跑测—重试 L2 · Integration Loop — 集成失败的归因与文档回写 L3 · Human Feedback Loop — 增量需求的并入"},{"f":"ops-buglist.html","title":"Ops · Buglist","h1":"Buglist · 跨 run 跨 unit 聚合视图","headings":["UI Wireframe","过滤与分组","数据来源","状态机参考","关联视图"],"defs":[],"content":"UI Wireframe /devloop/p-731042/ops/bugs 过滤与分组 维度 取值 说明 status open / fixing / fixed / verified / wontfix / duplicate 顶部 chip 选择,多选 OR severity critical / high / medium / low 右上 filter unit_id 从 units.json 列表 右上 filter,下拉 source unit_test / integration_test / triage / human filter since 时间窗口 filter(默认全部) 排序默认按 discovered_at 倒序,可切「未结束优先」(open + fixing 顶置)。 数据来源 本页是 bugs.ndjson 的纯 视图 ,不写入。所有操作通过: 用户动作 触发的 API 列表显示 GET /api/devloop/projects/:pid/bugs(内部调 bugTracker.list ) 点击展开 GET /api/devloop/projects/:pid/bugs/:id/history 人类标 wontfix POST .../bugs/:id/transition body={to:'wontfix',note} 人类合并 duplicate POST .../bugs/:id/transition body={to:'duplicate',duplicate_of} 状态机参考 完整流转规则见 P-9 · Bug 状态机 。本页 UI 必须严格按状态机渲染:非法转换按钮 disabled。 关联视图 点击 unit_id chip → 跳到 UnitDrawer 看该 Unit 详情 点击 run_id → 跳到对应 result.json 内嵌视图 点击 fix_refs 中的 run_id → 看修复时的 Tester 跑分 右上「↗ 时间线」→ 跳 Loop Log"},{"f":"ops-changelog.html","title":"Ops · Changelog","h1":"Changelog · 全部版本变更流","headings":["UI Wireframe","每条目录包含","交互","与 timeline.ndjson 的关系","关联视图"],"defs":[],"content":"UI Wireframe /devloop/p-731042/ops/changelog 每条目录包含 字段 来源 用途 kind changelog.ndjson 颜色 / 图标 rev + ts + by 同上 标题行 trigger 同上 「为什么变」(human_feedback / triage_patch / init) summary 同上 一句话变更 diff 同上 展开后看 +/~/− section · unit 列表 archive_ref 同上 点开跳归档 HTML 交互 顶部 chip 切换 all / prd / sdd / units(kind filter) 每条点击展开 diff 详情 「↗ archive」按钮在新 tab 打开归档 HTML(history/{kind}-rev-{N}.html) 「↗ compare」按钮跳 PrdDiffPanel 与上一 rev 对比 双击两个条目 → 跨 rev 三方对比(this vs prev vs current) 与 timeline.ndjson 的关系 changelog 是 timeline 的 子集 + 增强 : timeline 包含 所有 事件(agent 启动、unit 状态、bug 开关...) changelog 只关心 文档版本变更 ,但带有 cheerio diff 详细对比 两者由 DocStore 同步写入,绝不脱节 关联视图 跳 PRD diff 面板 看具体 section 对比 跳 Loop Log 看 rev 变更触发了哪些 L1/L2 跑 跳 Buglist 看 rev 变更产生 / 解决了哪些 bug"},{"f":"ops-loop-log.html","title":"Ops · Loop Log","h1":"Loop Log · L1 / L2 / L3 分组时间线","headings":["UI Wireframe","分组规则","交互","L1 高频降采样","L2 / L3 强调","关联视图"],"defs":[],"content":"UI Wireframe /devloop/p-731042/ops/loops 分组规则 本视图从 timeline.ndjson 读取所有事件,按 kind 字段分桶: 分到 kind 包含 L3 project_created · prd_drafted · sdd_generated · human_feedback · sign_off · abandon L2 integration_started · integration_done · integration_failed · triage_* L1 unit_state · agent_started (Coder/Tester) · agent_done · unit_passed · unit_failed 无 agent_started (PM/Architect/Integrator/Triager) → 归到对应 loop 交互 顶部时间窗口选择:最近 1h / 24h / 全部 / 自定义 点击任一事件 → 底部详情面板展开(如 wireframe) 事件类型 chip 过滤(暂时隐藏某些 kind) 缩放 / 平移时间轴(d3-zoom) 「↗ Buglist」联动:选中 L2 事件 → 自动 filter buglist 到该 run 的 bug 「↗ Changelog」联动:选中 PRD/SDD 事件 → 跳 changelog 对应条 L1 高频降采样 L1 事件可能一分钟几十条。视图层做 降采样 : 同 unit 同 attempt 内多条 agent.step → 折叠为一个圆点 同 wave 内连续 passed → 横线表示批次 retry 失败用红色圆点,覆盖在绿色之上 点击折叠批次 → 展开详细 L2 / L3 强调 L2 是 amber pulse 动画(如果 currently running),失败用 red 高亮。L3 事件用 violet 大圆点+ 上方文字标签,便于看「为什么改」。 关联视图 Buglist · 看具体 bug Changelog · 看 rev 变更 Progress · 看实时进度 Dashboard · 主看板"},{"f":"ops-progress.html","title":"Ops · Progress","h1":"Progress · 项目实时进度","headings":["UI Wireframe","聚合算法","数据来源","更新频率","无项目时的空态","关联视图"],"defs":[],"content":"UI Wireframe /devloop/p-731042/ops/progress 聚合算法 percent 加权求和(来自 C-14 文档): 阶段 权重 判定 ok PRD 10 prd.html 存在且校验通过 SDD 20 sdd.html 存在且 units.json 一致 Units (L1) 50 按 passed / total 比例 L2 整合 15 最近一次 L2 ok=true Ship 5 project.status=shipped 合计 100 — ETA 基于历史项目均值 + 当前阶段剩余项数线性外推;blocked 状态时 ETA = ∞ 显示「等人介入」。 数据来源 头部数字: C-14 LoopController.getProgress(pid) 一次性返回 Active Agents:聚合自 agent-state/*.json + agent-state/*-todo.json Recent events: timelineLogger.tail(pid, 10) SSE:订阅 unit.state · agent.step · integration.result 等事件,自动刷新 更新频率 区域 触发 方式 大数字 / breakdown SSE 任一关键事件 debounce 500ms 重算 Active Agents SSE agent.step 立即 Recent events SSE 任一事件 append 顶部,溢出剔旧 ETA 30s 周期 低频,避免抖动 无项目时的空态 页面打开时若没有任何 active project:显示「No project running」+ 列出最近 3 个项目让用户选;若有 shipped 项目,加 「✓ Demo: TODO Auth - shipped」可作为复现起点。 关联视图 Dashboard · 主看板(同源 + DAG 视图) Loop Log · 时间线展开 Buglist · bug 排查 Changelog · 版本变更"},{"f":"plan-code-lifecycle.html","title":"Code Lifecycle","h1":"生成代码的去向","headings":["① 三种归宿","② 沙箱代码的生命周期","③ Export API · v1 实现","④ 导出包里的 README 模板","⑤ 沙箱代码的保留期","⑥ v2 路径 · 自动 PR","⑦ 跨项目共享 utils","⑧ 关联"],"defs":[],"content":"① 三种归宿 方案 位置 优 劣 A · 留沙箱(v1 默认) data/companies/{cid}/projects/{pid}/units/** 零集成成本;可被复现脚本读;隔离强 不能直接运行在 LemonBoss 主进程;需手动导出 B · 手动 export 导出 人类点「导出」按钮 → 下载 zip 简单;人类可控;适合外部交付 没有 git 历史;不接 CI/CD C · 自动开 GitHub PR 主仓库的 feature branch 真集成;CI 自动跑;review 流程 需 GitHub token / 权限;branch 命名冲突;merge 策略复杂 v1 决策:A + B。 C 留到 v2 视真实需要再说。本套文档对外宣称\"生成代码\",但 v1 不承诺自动合并到生产仓库。 ② 沙箱代码的生命周期 ③ Export API · v1 实现 // server/routes/devloop.js · 新增 router. post ( '/projects/:pid/export' , requireAuth, async (req, res) => { const { pid } = req.params; const { include = 'code' } = req.body; // code / code+tests / all const archiver = require ( 'archiver' ); res. setHeader ( 'Content-Type' , 'application/zip' ); res. setHeader ( 'Content-Disposition' , `attachment; filename=\" ${pid} .zip\"` ); const zip = archiver ( 'zip' ); zip. pipe (res); const root = projectsDir (pid); if (include. includes ( 'code' )) zip. directory (path. join (root, 'units' ), 'units' ); if (include. includes ( 'tests' )) zip. directory (path. join (root, 'integration' ), 'integration' ); if (include. includes ( 'docs' )) { zip. directory (path. join (root, 'prd' ), 'prd' ); zip. directory (path. join (root, 'sdd' ), 'sdd' ); } // 附 README 说明项目结构 zip. append ( buildReadme (pid), { name: 'README.md' } ); await zip. finalize (); } ); archiver 在 LemonBoss 既有依赖( \"archiver\": \"^7.0.1\" ),零新增包。 ④ 导出包里的 README 模板 # Project {pid} 由 LemonBoss DevLoop v1 生成 · {timestamp} ## 结构 ``` units/ ├── todo-be/code/ 后端代码 ├── todo-be/tests/ Tester 写的 vitest ├── todo-be/contract.json 接口契约 ├── todo-fe/... integration/spec.json 端到端用例 prd/ 原始需求文档 (HTML 站点) sdd/ 架构设计文档 (HTML 站点) ``` ## 跑起来 ```bash cd units/todo-be npm install # 各 Unit 独立装包 PORT=3010 node code/server.js # 另起前端 (如有) cd units/todo-fe npm install npm run dev ``` ## 测试 ```bash cd units/todo-be vitest run # 跑 Tester 写的 spec ``` ## 集成 ```bash # 在项目根 node integration/runner.js # 启动所有 be + 跑 spec.json ``` ## 合到主仓库(手动) 没有自动 PR 流程。建议: 1. cp -r units/*/code/ <your-repo>/services/ 2. 整理目录结构、合并 package.json 3. 跑 lint / 格式化(DevLoop 不强制风格) 4. 自己开 PR ## License 继承 LemonBoss 主仓库。 ⑤ 沙箱代码的保留期 shipped 项目的 units/ 默认 永久保留 ,因为: 占用极小(中项目 ~30MB 含日志) 反推决策需要原始代码 + coder.log memory 系统会引用具体 Unit 实现做风格参考 清理策略(写在 project.json ): 字段 默认 含义 compressOldRuns false 14 天前的 runs/*/stdout.txt 自动 gzip keepCodeMonths 12 shipped 12 个月后归档 code/ 到 history/archived-units/ autoExportOnShip false shipped 时自动生成 export.zip 放项目目录 ⑥ v2 路径 · 自动 PR v2 会加 POST .../export-to-github ,参数: { \"repo\" : \"yibo/lemonboss-services\" , \"branch\" : \"devloop/p-731042\" , \"target_path\" : \"services/todo/\" , \"commit_message\" : \"feat: add TODO API + UI (via DevLoop)\" , \"open_pr\" : true , \"github_token\" : \"${secret}\" } 逻辑:调 gh CLI / @octokit/rest 创建 branch + 提交 + 开 PR。 v1 不做 。 ⑦ 跨项目共享 utils 常见担忧:5 个项目都写一遍 JWT 中间件,沙箱内重复实现。v1 接受这个浪费;v2 视情况引入 data/companies/{cid}/shared/ ,约定: Architect 检测多 Unit 共享 util 时,建议拆 shared-* Unit shared Unit 的 contract.json 跨项目稳定 Coder 读 shared 时 只读 contract ,不复制实现 v1 简化:每个项目重复实现 utils。memory 系统会记\"这次写了 JWT mw, 下次可以照样\",但不会自动拆 shared。 ⑧ 关联 data-layout · units/ 在文件系统里的位置 plan-migration · 各 Phase 何时开始关心代码归宿 复现指南 · 外部 Claude Code 怎么从沙箱代码 + docs 复现整套"},{"f":"plan-cost-concurrency.html","title":"Cost & Concurrency","h1":"成本与并发预算","headings":["① 单次调用 Token 估算","② 典型项目总成本估算","2.1 小项目(TODO API · demo)","2.2 中项目(Auth + TODO + Profile · 5 feature)","2.3 默认 token_budget = 5M 的隐含假设","③ 美元成本(参考 2026-05 价格)","④ 并发预算","⑤ 单项目墙钟估算","⑥ 监控指标","⑦ 降本策略"],"defs":[],"content":"① 单次调用 Token 估算 Agent 模型 输入 ≈ 输出 ≈ 典型 / 单次调用 备注 PM Sonnet 4.6 4K (memory + prd) 8K (新 PRD 章节 HTML) 12K incremental ≈ 5K Architect generate Opus 4.7 15K (PRD + sdd + memory) 20K (4 MVCP 页 + JSON) 35K 含全套 SDD Architect patch Opus 4.7 8K 3K 11K 仅改一段 Coder implement Sonnet 4.6 6K (sdd + upstream contracts) 4K (code + contract.json) 10K per Unit Coder retry Sonnet 4.6 8K (含 history) 3K (差量 patch) 11K 反思加 ~2K Tester Sonnet 4.6 5K 4K (vitest spec + result) 9K per Unit Integrator Sonnet 4.6 8K (units + spec) 3K 11K L2 单轮 BugTriager Opus 4.7 12K (含 code + logs) 3K (triage.json) 15K 归因质量决定 L2 收敛 ② 典型项目总成本估算 2.1 小项目(TODO API · demo) 3 个 Unit · 1 个 PRD rev · 1-2 个 L2 round。 调用 次数 Token PM init 1 12K Architect generate 1 35K Coder × 3 unit × 1.5 attempt(平均) 4.5 45K Tester × 3 unit × 1.5 attempt 4.5 40K Integrator 1 11K BugTriager(如失败 1 次) 0.5 8K 合计 14.5 ≈ 150K tokens 2.2 中项目(Auth + TODO + Profile · 5 feature) 8 个 Unit · 2 个 PRD rev · 2 个 L2 round。 调用 次数 Token PM init + incremental × 1 2 17K Architect generate + patch × 1 2 46K Coder × 8 unit × 2 attempt 16 165K Tester × 8 unit × 2 attempt 16 145K Integrator × 2 2 22K BugTriager × 1 1 15K 合计 39 ≈ 410K tokens 2.3 默认 token_budget = 5M 的隐含假设 5M 是单项目预算上限,能撑: ~33 个小项目(150K each) ~12 个中项目(410K each) 1 个大型迭代(多人多反馈、20+ Unit) 到达 80% 预算时发 SSE warning;100% 直接挂起。 ③ 美元成本(参考 2026-05 价格) 按 Anthropic 公开价格估算(input/output 分别计),中项目场景: 模型 输入 ($/MTok) 输出 ($/MTok) 本项目消耗 美元 ≈ Sonnet 4.6 ~$3 ~$15 input 220K · output 100K $2.2 Opus 4.7 ~$15 ~$75 input 50K · output 25K $2.6 中项目合计 — — 410K tokens ≈ $4.8 小项目合计 — — 150K tokens ≈ $1.7 数字是 ballpark。实际取决于 caching 命中率(SessionPool 复用降低 input 重复成本)和具体功能复杂度。 ④ 并发预算 三层并发上限: 层级 上限 原因 实现 项目内 L1 并行 ≤ 5 Unit 同时跑 每个 Claude CLI ~ 100MB RAM · 1 CPU core scheduler.getExecutionGroups 自动分波,每波 ≤ 5 项目内 L2 串行 1 个 Integrator 实例 避免端口冲突 + 状态混乱 LoopController 内部用 Promise 队列 单机系统并发 ≤ 20 个 ACP session 合理 RAM 占用 ≈ 2GB SessionPool 全局上限(新增 maxSessions 配置) 同 company 项目并发 ≤ 3 项目同时跑 避免单用户挤占 LoopController 入口检查 Bash 进程 (Coder/Tester/Integrator 起的) ≤ 50 系统总数 避免 fork bomb process-guard 计数 ⑤ 单项目墙钟估算 项目规模 串行墙钟 L1 并行后 瓶颈 小(3 Unit) ~15 min ~8 min Architect generate + Integrator spawn 中(8 Unit) ~45 min ~20 min L1 × 2 attempt × Coder + Tester 大(20 Unit + 3 L2 round) ~3h ~70 min L2 整套 + 重写 「并行后」假设 5 并行;scheduler 拓扑可能限制少于 5(fe 必须等 be 完)。北极星目标「小项目 < 30 min」对应中项目场景,余量充足。 ⑥ 监控指标 在 ops-progress 已经展示了 token_used/budget。补充几个隐含指标: 指标 看哪 告警阈值 Tokens / Unit 项目 token_used / units_count > 100K / Unit → 项目过度反思 Retry rate units.json sum(retries) / count > 2 平均 → Coder skill 退化或 spec 不清 L2 修复轮次 l2_rounds ≥ 2 → 该 PRD 有歧义 Agent 调用墙钟 P95 timeline.duration_ms 统计 > 5 min → 模型卡或 prompt 过长 SessionPool 利用率 active_sessions / maxSessions > 80% → 限流 Bash 子进程数 process-guard tracked > 30 → 等回收 ⑦ 降本策略 策略 预期降幅 难度 SessionPool 复用 + 5min cache TTL 20-30% input token 免费(既有 ACP 行为) Tester Sonnet 改 Haiku(如果反思质量够) 40% Tester 部分 低 PM/Coder/Tester 用 Sonnet · 只 Architect/Triager 用 Opus 已是默认 — Memory 注入降到 top 3(从 top 5) 5-10% input 低,1 行配置 跳过 Tester 写文档代码(README/类注释) 10% output 低 cache busting:相同 unit 跑 1 次后用缓存(无 retry 时) 大幅,针对回归 中(需 contract_hash 索引)"},{"f":"plan-migration.html","title":"最小改造路径","h1":"最小改造路径","headings":["第一天 Quick-Start · 从零起步","五阶段总览","P0 · Bootstrap(~2.5 周 / 11.5 天)","P0 子任务速览","验收","P1 · Docs MVP(~1 周 / 5 天)","新增文件","验收","P2 · L1 单 Unit(~1.5 周 / 8 天)","新增文件","修改文件","验收","P3 · L1 + L2 联动(~3 周 / 15 天)","新增 / 完善","验收","① contract_mismatch · 自动 patch SDD","② impl_bug · 不改 SDD,仅 reset","③ spec_ambiguous / confidence < 0.5 · 升 L3 等待人类","④ infra_flake · 直接重跑","⑤ 可观测性产物校验(4 类分支跑完后统一检查)","桥梁:极简 Status Page","P4 · L3 + UI(~2 周 / 10 天)","新增","验收","成本估算","时间预算","「自举」乐趣"],"defs":[],"content":"第一天 Quick-Start · 从零起步 「我装好仓库 cd 进去,第一行命令该是什么」—— 答案: # 1. 拉仓库 + 装依赖 git clone gitlab-wuwei.hexpm.com:yibo/lemonboss.git cd lemonboss pnpm install # LemonBoss 用 pnpm workspace # 2. 配 .env cp .env.example .env # 编辑设置 PORT=3010 等;v1 不需要 OPEN_API_KEYS # 3. 浏览本 PRD (推荐先扫一遍 index → reading-guide) open docs/prd/newfunction/index.html # macOS # 或: python3 -m http.server -d docs/prd/newfunction 8000 → open localhost:8000 # 4. 验证文档侧 tooling 已经就绪 node scripts/extract-manifest.js # 期望: 54 MVCP defs 100% node scripts/build-search-index.js # 期望: 51 pages 195KB # 5. 从 P0.0 开始动代码 (按 §五阶段总览 P0 表格顺序) mkdir -p server/services # 第一个文件: server/services/path-resolver.js (所有人都依赖它) # 第二个文件: server/services/doc-store.js # ...按 §P0 子任务速览 P0.0 行的服务清单顺次实现 # 6. P0.B 启用 Mock Claude CLI 模式 (一旦工具骨架在) # 详见 arch-acp-bridge.html · Mock Mode 章节 MOCK_CLAUDE=1 node scripts/phase0-verify.js # 工具流跑通,不烧 token # 7. 真跑 P0 验证 (P0.J 完成后) node scripts/phase0-verify.js # ✓ All 79 tools + 1 mock PM call node scripts/phase0-test-call.js # ✓ todolist 落盘 + SSE 触发 # 8. 进 P1: 准备启动真实 Agent PORT=3010 node server/index.js & # server 起来 node scripts/devloop-new.js \\ --name \"Auto TODO\" \\ --requirement \"做一个 TODO API + Vue 前端\" # sandbox 模式 # 期望: 30-60s 内 data/.../projects/p-XXXXXX/prd/ 完整生成 第一天能 100% 跑通到第 4 步(文档侧验证);P0.0 实施开始后第 5 步以下才有意义。 五阶段总览 阶段 目标 新增 / 修改 可演示场景 P0 · Bootstrap 3.5 周 服务层 7 件套 (pathResolver/docStore/unitRegistry/bugTracker/timelineLogger/sseBroker/GitVault)+ 79 工具实现 + Mock Claude CLI 模式 + ACP 桥接 + 系统提示提取 + git 集成 7 个 server/services/*.js 、 tool-registry (34 工具)、 acp-bridge + Mock 模式、 git-versioning 、scripts/extract-agent-prompts.js、scripts/phase0-verify.js MOCK_CLAUDE=1 phase0-verify 通过 → 真跑 phase0-verify 通过 → 一个 mock PM call 落 todolist + .git 初始化 + 首个 prd commit P1 · Docs MVP 能由 PM + Architect 把一段需求转成多页 PRD 站点 + 多页 SDD 站点 + units.json + integration/spec.json DocStore 、PM / Architect 模板、 POST /api/devloop/projects curl 一段中文需求 → 文件系统里看到 PRD/SDD 多页站点 + units.json + integration/spec.json + agent-state/ P2 · L1 单 Unit 对单个 Unit 跑 Coder→Tester 重试到通过 Coder / Tester 模板、 UnitRegistry 、 retryWithReflection (新写在 loop-controller.js 内,不抽公共) 故意挖一个 bug 让 Tester 红,看 Coder 反思后绿 P3 · L1 + L2 联动 多 Unit 并行 + 集成测试 + 失败回写 LoopController 、Integrator / BugTriager 模板,复用 scheduler 故意写 contract mismatch,看 SDD 自动 patch + 受影响 Unit reset → 重跑 L1 P4 · L3 + UI 人类反馈触发增量;前端实时看板 DevLoopDashboard.vue 、SSE 流、PRD diff 渲染 页面上发新需求 → 看到 PRD 新增 section + 新 Unit 自动入队 P0 · Bootstrap(~2.5 周 / 11.5 天) 这是 P1 的 前置条件 。 没有 P0,PM/Architect 的 79 个工具签名都是空中楼阁。详细工时分解 + 工具组织见 arch-tool-registry §⑪ 。 P0 子任务速览 子阶段 内容 工时 难度 P0.0 服务层 7 件套 所有其他都依赖它 server/services/ 顺次实现: ① path-resolver.js (~80 行; 第一个写 ,其余都依赖它) ② doc-store.js (~140 行;prd/sdd/units IO + cheerio 校验) ③ unit-registry.js (~120 行;Unit FSM + l2GateOpen) ④ bug-tracker.js (~110 行;bugs.ndjson CRUD + dedupKey) ⑤ timeline-logger.js + changelog-writer.js (~80 行合计) ⑥ sse-broker.js (~80 行;订阅 + 心跳 + replay) ⑦ git-vault.js (~150 行;spawn git wrapper + per-pid mutex) 4.5 天 低中(独立实现 · 每个文件可单测) P0.A 工具实现 需 P0.0 完成 server/src/tools/devloop/ 下 7 个文件,34 个独立函数 (~1700 行)。每个工具 wrapper 通过 ctx 引用 P0.0 的服务 5 天 中(cheerio 多) P0.A2 Mock Claude CLI 模式 开发期省 token 给 ClaudeCliProtocol 加 MOCK_CLAUDE=1 分支,读 system/mock-responses/{agent}/*.json 当 stub 回复。详见 acp-bridge · Mock Mode 0.5 天 低 P0.B 提示提取 scripts/extract-agent-prompts.js 从 6 个 agent HTML §2.2 抽 prompt 0.5 天 低(正则) P0.C 模板注册 system/templates/_registry.json 加 6 条 devloop-* + toolPolicy 0.5 天 低 P0.D CLI tools 注入 packages/acp-core/protocol/ClaudeCliProtocol.js 改 ~10 行,按 toolPolicy 生成 --tools json 1 天 中 P0.E agent-manager 透传 加 toolRegistry / toolCtx / onChunk 三个可选参数,~10 行 0.5 天 低 P0.F jsonGuard 挂 Bash Bash 工具 preExecute 钩子拦截 inline JSON 0.5 天 低 P0.G 验证脚本 scripts/phase0-verify.js + phase0-test-call.js 1 天 低 P0.H 联调 跑一个 mock PM 调用 → 看 todolist 落盘 + SSE 触发 2 天 — P0.I git 集成到 LoopController createProject 时 git init + .gitignore 模板;callAgentWithPersistence success 分支加 commitAfterAgent;recoverOnBoot 加 git status 检查(GitVault 已在 P0.0 ⑦ 落盘) 1 天 中 P0.J git 验证"},{"f":"plan-reproducibility.html","title":"Claude Code 复现指南","h1":"Claude Code 复现指南","headings":["三步法","步骤 ① · 抽取 build-manifest.json","步骤 ② · 复现 Prompt 模板","步骤 ③ · 验收清单","典型失败模式与诊断","「文档驱动」承诺","下一步"],"defs":[],"content":"三步法 步骤 ① · 抽取 build-manifest.json 需要 node + cheerio。脚本 ~80 行: // scripts/extract-manifest.js · NEW const fs = require ( 'fs' ); const path = require ( 'path' ); const cheerio = require ( 'cheerio' ); const docsDir = path. resolve (__dirname, '../docs/prd/newfunction' ); const manifest = { meta: { source: 'docs/prd/newfunction' , prdRev: 'v0.2' } , model: [] , view: [] , control: [] , process: [] , units: [] , } ; // 1. 从 sdd-{mvcp}.html 抽 4 象限 for ( const mvcp of [ 'model' , 'view' , 'control' , 'process' ] ) { const html = fs. readFileSync (path. join (docsDir, `sdd- ${mvcp} .html` ), 'utf8' ); const $ = cheerio. load (html); $( '[data-def-name]' ). each ((_, el) => { manifest[mvcp]. push ( { name: $(el). attr ( 'data-def-name' ), path: $(el). find ( 'code.path' ). first (). text () || $(el). find ( 'code.path.mono' ). first (). text (), shape: $(el). find ( 'pre.shape' ). text () || $(el). find ( 'pre.code.shape' ). text (), contract: $(el). find ( 'ul.contract li' ). map ((_,li) => $(li). text ()). get (), tests: $(el). find ( 'ul.tests li' ). map ((_,li) => $(li). text ()). get (), } ); } ); } // 2. 从 prd-feature-tree.html 抽 Unit 列表(粗) // 这步实际由 Architect Agent 做 P1 时的拆分;脚本只做最简取出 file tree 中的 Unit id fs. writeFileSync ( 'build-manifest.json' , JSON.stringify (manifest, null , 2 )); console. log ( `✓ extracted ${manifest.model.length + manifest.view.length + manifest.control.length + manifest.process.length} definitions` ); 跑一下: cd lemonboss node scripts/extract-manifest.js # → build-manifest.json (~30KB) 步骤 ② · 复现 Prompt 模板 把以下 prompt 喂给一个全新的 Claude Code session(在空 lemonboss 仓库根目录启动)。 # 复现 prompt · 直接粘到 claude code 我手上有一份 LemonBoss DevLoop 系统的完整 PRD + SDD(HTML 形式),位于 docs/prd/newfunction/ 下,共 41 页。我已用脚本抽出 build-manifest.json。 请你扮演 Architect + Coder + Tester 三个角色,完成以下工作: 【阶段 A · 拆 Unit】 1. 读 docs/prd/newfunction/prd-feature-tree.html,确认 34 个 Unit 2. 读 build-manifest.json,把 MVCP 四象限的定义映射到 Unit 3. 在 data/companies/c-repro/projects/p-init/units.json 落地 DAG 【阶段 B · 实现每个 Unit】 对每个 Unit 顺次: 1. 读对应 Unit 的所有 MVCP 定义(manifest 里的 model/view/control/process) 2. 按 path 字段落盘,按 shape 字段写代码骨架 3. 按 contract 列表实现行为 4. 按 tests 列表写 vitest 用例(后端 supertest + 内存 SQLite,前端 @vue/test-utils) 5. 跑 vitest,红了反思(参考 docs/prd/newfunction/loops-l1.html 的 retryWithReflection 章节),重写直到绿 【阶段 C · 集成】 1. 读 docs/prd/newfunction/sdd-process.html 实现 LoopController.runProject 2. 启动后端 + 前端,cd 到 docs/prd/newfunction,用 docs 自己做 demo 项目跑一遍 3. 注入「contract_mismatch」演示自愈 【约束】 - 严格遵守 docs/prd/newfunction/agents-coder.html 的沙箱规则 - 任何带 JSON body 的 curl 必须先写 /tmp/*.json 再 -d @file (参见 CLAUDE.md L91-99) - 不动 server/services/evolution-loop.js(参见 arch-mapping.html PATCH 说明) - 不动 packages/acp-core/ · core/master-session.js · core/agent-manager.js 【验收】 完成后跑 npm test,所有 vitest 必须通过。然后跑: curl -X POST http://localhost:3010/api/devloop/projects -d @/tmp/test.json (/tmp/test.json 单行紧凑: {\"company_id\":\"c-001\",\"name\":\"todo\",\"requirement\":\"做个 TODO API\"}) 应在 60 秒内看到 prd.html + sdd.html + units.json 三份文档生成。 开始吧。 步骤 ③ · 验收清单 Claude Code 跑完后,人类只需对照以下 12 条逐项检查: # 检查点 判定 1 server/src/core/loop-controller.js 存在且导出 loopController ✓ require 不报错 2 server/services/doc-store.js 存在 ✓ 3 server/services/unit-registry.js 存在 ✓ 4 server/services/sse-broker.js 存在 ✓ 5 server/routes/devloop.js 存在且 7 个端点全 ✓ curl /api/health + grep 6 system/templates/devloop-{pm,architect,coder,tester,integrator,triager}.md 全部存在 ✓ ls 应见 6 个 7 system/templates/_registry.json 含 devloop-* 六条 ✓ grep 8 web/src/views/DevLoopDashboard.vue + 7 个子组件 ✓ ls web/src/components/devloop/ 9 npm test 全绿 ✓ 10 POST /api/devloop/projects 60s 内生成 3 份文档 ✓ file 存在 11 SSE 流持续推 unit.state 事件 ✓ curl -N 看到 12 整套 DevLoop 能 对自己做增量 (用 DevLoop 给 DevLoop 加新需求) ✓ 元-自举测试 典型失败模式与诊断 失败 诊断 修 缺某个 MVCP 定义 build-manifest.json 该项空 检查对应 SDD 页是否漏 data-def-name shape 抽不到 HTML class 不规范 用 pre.shape 不是 pre.code 也不是 pre.code.shape,统一 Claude Code 写出的代码风格与 LemonBoss 不一致 prompt 缺约束 在复现 prompt 顶部 require 它先读 CLAUDE.md contract.json 与 SDD 不一致 Architect 没遵循 hashContract 算法 看 docs/p"},{"f":"plan-risks.html","title":"风险与待决","h1":"风险与待决","headings":["风险全表","逐条展开","开放问题","下一步"],"defs":[],"content":"风险全表 ID 风险 影响阶段 对策状态 R1 无限循环(L2 改 SDD → 又失败 → 再改) P3 ✅ 有预算 R2 Agent 把 Bash 玩坏(inline JSON) P2 起 ✅ 已写入 prompt R3 集成测试套件谁来写 P3 ✅ 初版方案 A R4 PRD/SDD 的 HTML diff 算法 P4 ⚠️ 初版粗粒度 R5 与现有 EvolutionLoop 的关系 P2 ✅ 抽公共函数 R6 跨 Unit 的命名 / 约定漂移 P3 ✅ Architect 强制后缀 R7 Token 成本失控(无限 retry × Opus) P2 起 ⚠️ 需配置全局上限 逐条展开 R1 · 无限循环风险 P3 场景: L2 失败 → 改 SDD → 再失败 → 再改 SDD… 对策: 三层预算守护 每 unit retry ≤ 5 次(L1 内) 每 PRD rev 内 L2 修复轮次 ≤ 3 轮 BugTriager confidence < 0.5 立即上报 L3 超出 → 项目挂起,发 SSE,等人类介入。绝不静默丢弃。 R2 · Bash 中 inline JSON P2 起 参见 CLAUDE.md L91-99。所有需要带 JSON body 的命令必须先写 /tmp/*.json 再 curl -d @file 。 对策: 在 Coder / Tester / Integrator 的 system prompt 中强制声明铁律 沙箱层 hook 检测 -d '{...}' 模式直接拒绝 Bash 工具增加 jsonGuard middleware(约 30 行) R3 · 集成测试谁来写 P3 三种方案对比见 L2 页 · 集成测试套件从哪来 。 初版选 A:Architect 同步产 integration/spec.json ,理由: 少一个 Agent,少一次握手 spec 与 SDD 同源演化,不会脱节 Architect 已经 Opus,能力够 若 P3 实测发现 Architect 写 spec 质量差,回退到方案 B 单独加 IntegrationAuthor。 R4 · PRD/SDD diff 算法 P4 · 待决 HTML 结构化 diff 比纯文本难。 初版策略: section 级粒度,按 data-rev 属性区分新旧(每个 section 要么完全新,要么完全旧)。这避免文本级 diff 算法的复杂度。 已知限制: 同一 section 内修改不会高亮,只能整段标 changed。 P5+ 升级: 引入 diff-dom 做精细化 inline diff。但需要先看 P4 实测是否够用。 R5 · 与 EvolutionLoop 的关系 P2 EvolutionLoop(在 packages/evolution/ )负责 Skill 进化,是另一套独立系统。DevLoopController 负责工程化代码开发。 两者不交叉 。 唯一耦合: retryWithReflection() 抽到公共包供两者共用。从 evolution-loop.js 抽出约 80 行代码,外部接口保持不变。 R6 · Unit 命名漂移 P3 BugTriager 输出 affected_units 是 string[],靠字符串匹配 UnitRegistry。如果不同轮 Architect 用 auth-be / auth-backend / auth-server 三种风格,归因会丢。 对策: Architect 强制后缀(参见 Architect · ④ Unit 拆解策略 )。Schema 校验同时强制 id =~ /^[a-z]+(-[a-z]+)*-(be|fe|mw|db)$/ 。 R7 · Token 成本失控 P2 起 · 待决 5 次 unit retry × 多 unit × 3 次 L2 修复 × Opus 模型 = 单项目可能烧几十刀。 对策: project.json 加 token_budget 字段(默认 5M tokens / 项目) LoopController 每次 callAgent 后累计,超出立刻挂起 Tester 默认 Haiku(最便宜) Dashboard 实时显示已用 token 与剩余预算 具体阈值由 P2 实测 token 消耗后再定。 开放问题 以下是 没在本 PRD 给答案 的问题,需要在 P1 启动会前敲定: 多人项目 / 协作: 同一项目能不能两个人同时发 L3 反馈?目前默认是「队列化」,但 UI 怎么显示? 代码归宿: 项目 shipped 后, units/*/code/ 要不要自动提个 GitHub PR?还是留在 LemonBoss 文件系统里? 跨 Unit 共享库: R6 提到的 shared/ 在 P1~P4 留空。但「auth utils 给所有 Unit 共用」是个真实需求,要不要提前到 P3? 外部依赖(npm 包)安装权限: Coder 跑 npm install 安装新包是否允许?目前默认禁,但很多前端 Unit 实际需要。 失败后人工 takeover: 项目 blocked 后,人类能否在 Dashboard 里 手动编辑 某个 Unit 的代码再 resume?这要不要算 L3 的一种 action? 这五个问题不阻塞 P1 启动,但建议在 P3 前给答案。 下一步 动工前最后一关: 你(Yibo)读完整套 PRD,回复以下三个 yes / no: 三层 Loop 的边界划分是否准确?特别是 L2/L3 之间的「失败时先修文档」是否同意? 六个 Agent 的拆分粒度可以接受吗?有没有应该合并或拆细的? 四阶段排序 P1→P4 是否合理?是否要把 UI 提前? 都 yes → 开 P1 PR。任一 no → 回来改这份 PRD 再说。"},{"f":"prd-background.html","title":"背景 / 问题陈述","h1":"背景与问题陈述","headings":["当下的痛","市场参考","为什么是「现在」","北极星指标","定位声明","非目标声明"],"defs":[],"content":"当下的痛 痛点 现状 影响 需求 → 代码 链路碎裂 Master 能调 Agent,但没有「项目」概念,每次都是临时编排 同一需求要重复对 Master 解释多次 失败时不知所云 Agent 跑测红了,要人去翻 stdout 找原因 调试成本远大于编写成本 反思机制只用在 Skill evolution-loop.js 的反思能力只服务 Skill 训练 普通代码开发享受不到「失败 → 反思 → 重试」 文档与代码脱节 用户在 chat 里描述,Agent 直接写代码,没有持久 PRD 第二次反馈时 Agent 已经忘了第一次的上下文 无法增量迭代 新一轮需求 → 整个项目重写,已通过的 Unit 也跟着重做 Token 浪费 + 不收敛 市场参考 给你一个相对位置感。这不是抄袭,是知道我们站在哪。 产品 定位 本系统的差异 Devin 端到端 AI 软件工程师 我们不卖通用 SaaS,是 LemonBoss 内嵌能力;强调三层 Loop 可观测 Cursor Composer IDE 内多文件编辑 我们的 PRD/SDD 是 持久文档 ,不是 chat 上下文 v0 / Bolt UI prototype 生成 我们覆盖全栈 + 测试 + 集成 + 反思 Claude Code 自身 命令行 IDE Agent 我们用 Claude Code 当执行引擎,加 Loop 编排层 OpenHands / SWE-agent 开源 SWE 基线 我们绑定 LemonBoss 公司化语义(OKR / Pipeline / Goals) 为什么是「现在」 Claude Opus 4.7 + Sonnet 4.6 的代码能力终于足以支撑长链路自动开发。半年前 retry-with-reflection 收敛率太低。 LemonBoss 的 ACP / SessionPool 已经稳定运行。会话复用、沙箱、process-guard 全部就绪。 LemonBoss 的「公司 / OKR / Pipeline」语义 需要承载真实开发任务,目前空有壳。 北极星指标 本系统的成功 = 一句话「做个 TODO API + 前端」从输入到 shipped 不需要人工干预,全程 < 30 分钟、Token 成本 < 50 万。 这是 P4 演示日的目标。短中长期指标见 prd-success-metrics 。 定位声明 DevLoop 是 LemonBoss 的 「自动化代码工厂」 子系统。它把 LemonBoss 已有的多 Agent 能力,从「对话化代码助手」升级为「项目化代码工程师」。它不替代人,它 替代人的初稿、迭代和回归测试 ,让人去做架构决策和最终验收。 非目标声明 不是 X 因为 不是通用 IDE 替代品 用户仍可手动编辑代码;DevLoop 与人共编 不是 CI/CD 系统 不负责真实部署、只负责生成可部署代码 不是项目管理系统 OKR / Goals / Pipeline 由 LemonBoss 既有模块负责 不是付费 SaaS 初版只服务 LemonBoss 自托管用户"},{"f":"prd-feature-tree.html","title":"功能树","h1":"功能树","headings":["全景树","按层级统计","依赖拓扑(粗)","Unit id 命名约定"],"defs":[],"content":"全景树 DevLoop System ├── F1 · 项目生命周期 │ ├── F1.1 创建项目 (POST /projects) │ │ └── Unit: devloop-be-projects │ ├── F1.2 启动 / 续跑 (POST /run) │ │ └── Unit: devloop-be-run │ ├── F1.3 查询状态 (GET /state) │ │ └── Unit: devloop-be-state │ ├── F1.4 Sign-off / Abandon │ │ └── Unit: devloop-be-finalize │ └── F1.5 项目元数据持久化 │ └── Unit: devloop-be-projectmeta │ ├── F2 · 文档管理 (DocStore) │ ├── F2.1 PRD CRUD + 归档 │ │ └── Unit: docstore-prd │ ├── F2.2 SDD CRUD + 归档 │ │ └── Unit: docstore-sdd │ ├── F2.3 units.json schema 校验 │ │ └── Unit: docstore-units-schema │ ├── F2.4 HTML 解析与校验 (cheerio) │ │ └── Unit: docstore-html-validator │ └── F2.5 contract_hash 计算 │ └── Unit: docstore-contract-hash │ ├── F3 · Unit 状态机 (UnitRegistry) │ ├── F3.1 状态转换合法性 │ │ └── Unit: registry-fsm │ ├── F3.2 L2 gate 判定 │ │ └── Unit: registry-l2gate │ ├── F3.3 增量 diff (added/changed/removed) │ │ └── Unit: registry-diff │ └── F3.4 retry 预算管理 │ └── Unit: registry-budget │ ├── F4 · 三层 Loop 编排 (LoopController) │ ├── F4.1 L1 单 Unit 跑通 │ │ └── Unit: loop-l1-runner │ ├── F4.2 L1 retryWithReflection │ │ └── Unit: loop-retry │ ├── F4.3 L1 并行调度 (scheduler 复用) │ │ └── Unit: loop-parallel │ ├── F4.4 L2 集成 gate + run │ │ └── Unit: loop-l2-runner │ ├── F4.5 L2 修复回写 (triage → patch SDD → reset) │ │ └── Unit: loop-l2-repair │ ├── F4.6 L3 增量需求并入 │ │ └── Unit: loop-l3-merge │ └── F4.7 全局预算 + 终止条件 │ └── Unit: loop-guard │ ├── F5 · Agent 模板 + 调用 │ ├── F5.1 PM Agent │ │ └── Unit: agent-pm │ ├── F5.2 Architect Agent (generate + patch 两模式) │ │ └── Unit: agent-architect │ ├── F5.3 Coder Agent │ │ └── Unit: agent-coder │ ├── F5.4 Tester Agent │ │ └── Unit: agent-tester │ ├── F5.5 Integrator Agent │ │ └── Unit: agent-integrator │ └── F5.6 BugTriager Agent │ └── Unit: agent-triager │ ├── F6 · 事件推送 (SSE) │ ├── F6.1 SSEBroker 订阅 / 退订 │ │ └── Unit: sse-broker │ ├── F6.2 9 类事件序列化 │ │ └── Unit: sse-events │ └── F6.3 断线重连 + replay │ └── Unit: sse-replay │ ├── F7 · 前端 Dashboard │ ├── F7.1 主页面 DevLoopDashboard.vue │ │ └── Unit: fe-dashboard │ ├── F7.2 DashboardHeader (含 + NEW REQUIREMENT) │ │ └── Unit: fe-header │ ├── F7.3 LoopStatusBar (3 张状态卡) │ │ └── Unit: fe-loopbar │ ├── F7.4 UnitsDAG (复用 PipelineEditor 渲染) │ │ └── Unit: fe-dag │ ├── F7.5 LiveAgentStream (终端样式 SSE) │ │ └── Unit: fe-stream │ ├── F7.6 PrdDiffPanel (section 级 diff) │ │ └── Unit: fe-prddiff │ ├── F7.7 FeedbackModal (反馈输入) │ │ └── Unit: fe-modal │ ├── F7.8 UnitDrawer (Unit 详情侧栏) │ │ └── Unit: fe-drawer │ └── F7.9 路由 + i18n keys │ └── Unit: fe-router-i18n │ └── F8 · 复现 / 可执行性 ├── F8.1 PRD/SDD data-* 锚点协议 │ └── (无 Unit,文档约定) ├── F8.2 build-manifest 提取脚本 │ └── Unit: tool-manifest-extract └── F8.3 Reproducibility prompt 模板 └── (无 Unit,prompt 文件) 绿色 = 一个 Unit。共 34 个 Unit 。Architect Agent 在 P1 演示时会从这棵树派生 units.json。 按层级统计 L1 功能群 L2 功能 Unit 数 类型构成 F1 项目生命周期 5 5 be × 5 F2 文档管理 5 5 be × 5 F3 Unit 状态机 4 4 be × 4 F4 Loop 编排 7 7 be × 7 F5 Agent 模板 6 6 config × 6(Agent prompt + 注册) F6 SSE 3 3 be × 3 F7 前端 9 9 fe × 9 F8 复现 3 1 tool × 1 合计 42 34 be × 24 / fe × 9 / tool × 1 依赖拓扑(粗) 箭头 = 「下游 Unit 需要上游 contract」。LoopController.scheduler 据此分波并行。 P1 优先做 F2 + F5 (PM + Architect);P2 加 F3 + F4.1~4.3;P3 加 F4.4~4.7 + F1 + F6;P4 加 F7。 Unit id 命名约定 本树里的 Unit id 全部遵循 Architect 拆解策略 : 前缀 层 预计 cwd devloop-be-* F1 server/routes/devloop.js · server/services/ docstore-* F2 server/services/doc-store.js registry-* F3 server/services/unit-registry.js loop-* F4 server/src/core/loop-controller.js agent-* F5 system/templates/devloop-*.md sse-* F6 server/services/sse-broker.js fe-* F7 web/src/views/ · web/src/components/devloop/ tool-* F8 scripts/"},{"f":"prd-glossary.html","title":"术语表","h1":"术语表","headings":["核心抽象","组件与服务","Agent 角色","状态与归类","文档与协议","反义 / 易混淆"],"defs":[],"content":"核心抽象 术语 定义 出处 DevLoop 本系统全名 — 三层 Loop 驱动的自动开发流水线 background L1 / Unit Loop 最内层。单 Unit 的「写代码 → 跑单测 → 失败重试」 loops-l1 L2 / Integration Loop 中层。所有 Unit passed 后跑集成测试 + 失败回写 SDD loops-l2 L3 / Human Feedback Loop 外层。人类新需求并入 PRD 触发增量重跑 loops-l3 Unit 最小可独立开发 + 单测的代码单元(前端组件 / 后端服务 / 中间件 / DB 迁移) architect Project 一次完整的 DevLoop 实例,对应 data/companies/{cid}/projects/{pid}/ data-layout Rev PRD / SDD / units.json 的修订号,单调递增 documents Run 一次 Agent 执行的产物目录 runs/{runId}/ documents Contract Unit 对外暴露的接口契约(OpenAPI / TS interface) api-contracts contract_hash contract 文本 sha256 截前 16 位,用于检测变更 components 组件与服务 术语 定义 出处 LoopController 三层 Loop 的唯一编排者, server/src/core/loop-controller.js components DocStore PRD/SDD/units.json/spec.json 读写归档 components UnitRegistry Unit 状态机的 source of truth components SSEBroker Server-Sent Events 订阅与分发 api-sse scheduler 复用 server/src/core/scheduler.js 做 DAG 拓扑分波 mapping session-pool 复用 ACP 的会话池,按 (agentName, cid, pid) 复用 mapping retryWithReflection 反思重试函数,P2 写在 loop-controller.js 内 loops-l1 Agent 角色 术语 定义 出处 PM Agent 需求 → prd.html。L3 入口 PM Architect Agent PRD → SDD + units.json + integration spec Architect Coder Agent 实现单 Unit 的代码 + contract.json Coder Tester Agent 写 + 跑 Unit Test,输出 hint 给 Coder Tester Integrator Agent 起多个真实进程跑端到端测试 Integrator BugTriager Agent 归因集成失败,提议 SDD 补丁 Triager 状态与归类 术语 定义 pending / coding / testing / failed / passed / blocked Unit 六态机 contract_mismatch BugTriager 类别 1 — SDD 双方契约不齐 impl_bug BugTriager 类别 2 — 代码没遵守 SDD, 不改 SDD spec_ambiguous BugTriager 类别 3 — SDD 描述不清,升 L3 infra_flake BugTriager 类别 4 — 端口冲突等环境问题,重跑即可 L2 gate 「全部 Unit passed 且 contract_hash 与当前 SDD 一致」的门 l2_rounds / l2_budget L2 修复轮次累计与上限(默认 3) retry_budget 单 Unit 重试上限(默认 5) 文档与协议 术语 定义 MVCP 本 SDD 采用的四象限划分:Model / View / Control / Process data-* 锚点 HTML 上的机器读结构化信息,cheerio 抽取 build-manifest Architect Agent 从 SDD 抽出的 JSON 构建清单 ACP Agent Communication Protocol,LemonBoss 既有协议层 company-scoped 文件根 所有项目落在 data/companies/{cid}/ 下 反义 / 易混淆 勿混 正解 DevLoop ≠ Master Agent DevLoop bypass Master,自己起 LoopController EvolutionLoop ≠ DevLoop EvolutionLoop 是 Skill 进化,DevLoop 是工程化代码生成 units.json ≠ pipeline.json units.json 是 DevLoop 自己的;pipeline-service 是 LemonBoss 既有 SOP contract.json ≠ contract_hash 前者是契约文本本身,后者是其 sha256 摘要 L2 gate ≠ L2 闸门关闭 「gate 打开」= 允许进 L2,不是阻止 blocked ≠ failed failed 是「测试红」可继续 retry;blocked 是「retry 耗尽」需要人"},{"f":"prd-personas.html","title":"角色与 User Stories","h1":"角色与用户故事","headings":["三类人类角色","P1 · Founder / Tech Lead","Yibo","P2 · Reviewer","同事 / 投资人","P3 · Future Maintainer","3 个月后的自己","两类 Agent 「角色」","本项目内的六个 Agent","外部 Claude Code Session","核心 User Stories","故事优先级"],"defs":[],"content":"三类人类角色 P1 · Founder / Tech Lead Yibo 下需求 + 决策 + 验收。每天 1-3 次用 DevLoop。要求快、要求 PRD 与代码同步、不接受 chat-only。 主要触达:L3 反馈、Sign-off、Sidebar 全部 8 个 Section P2 · Reviewer 同事 / 投资人 偶尔进来看进度和最终产物。不写代码,只读 PRD + Dashboard。要求一目了然。 主要触达:Dashboard 头部、PRD diff、L2 集成结果 P3 · Future Maintainer 3 个月后的自己 回过头改某个 Unit 时,得能从 PRD / SDD 反推为什么这么设计。 主要触达:PRD history、SDD section 级 diff、coder.log 两类 Agent 「角色」 Agent 不是用户,但它们消费这套文档,列出来便于把约束写在该写的位置。 A1 本项目内的六个 Agent 消费 PRD/SDD/units.json/contract.json。详见 六个 Agent 。 A2 外部 Claude Code Session 从零拉起一份本仓库后, 只读本套文档 就能复现整个 DevLoop 系统。详见 复现指南 。 核心 User Stories US-01 · Founder 启动新项目 L3 As Yibo, I want 用一句中文描述需求就启动一个 DevLoop 项目, so that 我不用先想清楚再下达指令。 字段 内容 Trigger POST /api/devloop/projects 或 Dashboard 「+ NEW PROJECT」 Inputs { company_id, name, requirement: string } Acceptance 30 秒内 prd.html v1 落盘 60 秒内 sdd.html v1 + units.json 落盘 SSE 立即推 prd.updated 事件 US-02 · Founder 追加新需求 L3 As Yibo, I want 在项目跑到一半时插话「再加 OAuth 登录」, so that 系统能识别这是增量而不是从头重做。 Trigger POST /api/devloop/projects/:pid/requirement Acceptance PRD 新增 <section data-rev=\"N\"> ,不重写旧 section units.json diff 输出 {added, changed, removed} 三类 已 passed 且 contract_hash 不变的 Unit 不 被 reset US-03 · Reviewer 查看实时进度 L3 As Reviewer, I want 打开 Dashboard 就看到「现在卡在哪一步」, so that 不必去翻日志。 Trigger GET /devloop/:pid(前端) Acceptance 顶部 3 张 Loop 状态卡 5 秒内更新 DAG 节点颜色对应 unit.status 失败 Unit 红色 + 显示 retry 次数 US-04 · 系统自愈集成失败 L2 As Yibo, I want 集成失败时系统先尝试自己修,不要每次都来问我, so that 我专注高价值决策。 Trigger Integrator 返回 ok=false Acceptance BugTriager 5 分钟内产 triage.json confidence ≥ 0.5 → 自动 patch SDD + reset Unit confidence < 0.5 → 发 SSE human_intervention_needed 同项目每 PRD rev 最多自愈 3 轮 US-05 · Future Maintainer 反推决策 L3 As 3 个月后的 Yibo, I want 看到 SDD 某个 Unit 的「为什么这么设计」, so that 不会重复犯老错。 Trigger 在 Dashboard 点开某 Unit 卡片 Acceptance 能看到该 Unit 在哪个 PRD rev 引入 能看到所有 retry 时的 coder.log + tester.hint 能看到所有相关 triage.json(如果被 reset 过) US-06 · 外部 Claude Code 复现项目 A2 As 一个全新的 Claude Code session, I want 拿到本套 PRD + SDD HTML 就能从空仓库重建整套 DevLoop 代码, so that 系统具备真正的「文档驱动」性质。 Trigger 执行 复现 prompt Acceptance 5 个新 JS + 6 Agent 模板 + 1 Vue 全部生成 SDD MVCP 四象限 100% 抽取覆盖 单测自动通过率 ≥ 80% 故事优先级 P Story 阶段 P0 US-01 · US-04 P1 + P3 必须 P1 US-02 · US-03 P3 / P4 必须 P2 US-05 P4 应有 P3 US-06 P4 后续验证"},{"f":"prd-success-metrics.html","title":"成功指标","h1":"成功指标与 KPI","headings":["P0 · 阶段闸门指标(每阶段必须 100%)","P1 · 北极星 KPI(系统整体)","速度","成本","无人化率","P2 · 长期质量 KPI(运营期)","反指标(不要追的东西)","验收清单(P4 演示日)"],"defs":[],"content":"P0 · 阶段闸门指标(每阶段必须 100%) 阶段 指标 目标 测量方式 P1 curl 一句话需求 → 三份文档落盘 ≥ 90% 成功率(10 次抽样) integration test PRD/SDD HTML 通过 cheerio 校验 100% DocStore.validateHTML units.json schema 校验 100% typebox P2 单 Unit 红→绿收敛率 ≥ 80% 在 3 次 retry 内 10 个 demo Unit 跑 100 次 retryWithReflection 二次失败率 ≤ 30% 同上 沙箱越权写 0 次 越权写直接拒绝 P3 「contract_mismatch」演示自愈 ≥ 90% 一轮内修复 注入 5 种典型 mismatch L2 总修复轮次 平均 ≤ 1.5 同上 impl_bug 与 spec_ambiguous 归类正确率 ≥ 85% 30 个标注 case P4 Dashboard SSE 端到端延迟 ≤ 2s(P95) 手动 stopwatch + 浏览器 perf PRD diff 面板渲染正确率 100% 10 个 rev 切换 L3 反馈 → 增量 reset 准确率 ≥ 95% diff_units 单测 P1 · 北极星 KPI(系统整体) 速度 < 30min 「TODO API + 前端」从需求 → shipped 端到端 成本 < 50万 Token / 上述 demo 无人化率 ≥ 80% 项目从启动到 shipped 不需人工干预的比例 在 P4 演示日同时满足三条 = 系统达成 MVP。任意一条不达就视为 失败 ,回到 P3 调优。 P2 · 长期质量 KPI(运营期) 维度 指标 目标 采集 收敛性 blocked 项目比例(30 天内) ≤ 10% UnitRegistry 累计 收敛性 l2_exhausted 比例 ≤ 5% LoopController 日志 质量 shipped 项目人工返工率 ≤ 20% 人工标注 质量 contract.json 与 SDD 一致性 100% DocStore 校验 反思有效性 retry-2 比 retry-1 失败率降幅 ≥ 40% runs/result.json 统计 成本 平均 Token / 项目 持续下降趋势 token_budget 累计 速度 平均墙钟 / 项目 ≤ 1 小时 project.json 时间戳 文档健康 PRD section 与代码 unit 对齐率 ≥ 90% 每周脚本扫描 复现力 外部 Claude Code 复现成功率 ≥ 70% 季度复现实验 反指标(不要追的东西) 不追「项目数量」 — 一个跑通的项目 >> 十个挂起的项目。 不追「代码行数」 — Coder 写 200 行能解决的就不要 500 行。 不追「Agent 调用次数」 — 调用次数越多越花钱,不是越「努力」越好。 不追「PRD rev 数量」 — rev 多说明需求不清晰,不说明产品丰富。 验收清单(P4 演示日) # 项 判定 1 从空项目 curl 启动 TODO 需求 30s 内 prd.html v1 落盘 ✓ 2 L1 全部 Unit 通过 ≤ 3 次 retry / Unit ✓ 3 L2 通过 ≤ 1 次 L2 round ✓ 4 追加「加 OAuth」反馈 仅 oauth-be + oauth-fe + auth-mw 入 L1,其他不动 ✓ 5 注入 contract mismatch BugTriager 一轮内修复 ✓ 6 Dashboard 全程实时跟随 SSE 不丢、不重复 ✓ 7 shipped 后归档 history/ 完整 ✓ 8 外部 Claude Code 用 PRD 复现整套 DevLoop 5 文件 + 6 模板生成,单测 ≥ 80% 绿 ✓ 8 项全 ✓ → 系统可对外宣布 v1.0。"},{"f":"prd-user-flows.html","title":"用户流程","h1":"用户流程图","headings":["Flow 1 · 启动新项目(US-01)","Flow 2 · 增量反馈(US-02)","Flow 3 · 集成失败 → 自愈(US-04)","状态可见性约定"],"defs":[],"content":"Flow 1 · 启动新项目(US-01) 耗时 关键产物 0-2s HTTP 201 + project_id 5-30s prd.html v1 30-60s sdd.html v1 + units.json Flow 2 · 增量反馈(US-02) 关键设计点: 已 passed 且 contract_hash 不变的 Unit 不会被 reset 。这是「增量」的本质。 Flow 3 · 集成失败 → 自愈(US-04) 状态可见性约定 三个 Flow 都对人类有可见性要求。Dashboard 必须在 5 秒内反应以下事件: 事件 视觉反馈 新 Unit 进 coding DAG 节点变 emerald Unit 进 testing 节点变黄 Unit failed 节点变红 + retry 数字 Unit passed 节点深绿描边 L2 启动 L2 状态卡变 amber pulse SDD patched SDD diff 面板高亮新 section human_intervention_needed 右上角 toast violet shipped 整页 confetti 5s(认真的)"},{"f":"proto-feedback-modal.html","title":"原型 · 反馈模态框","h1":"FeedbackModal · 反馈输入","headings":["线框","交互细节","异常态","提示「需求过短」","提示「与历史矛盾」","项目已 shipped-final","键盘可达性"],"defs":[],"content":"线框 FeedbackModal · open 交互细节 事件 系统反应 打开 focus 自动落 textarea,背景 50% 透明遮罩 输入字符 右下字数计数实时更新 > 4000 字 计数变橙,按钮仍可点(仅警告) 空文本 提交按钮 disabled 灰色 Ctrl/Cmd + Enter 等价于点提交 Esc 关闭(如有未提交内容,下次打开自动恢复) 点击遮罩区 关闭(同 Esc) 提交后 立即关闭模态框,header 显示 spinner「PM 处理中」 异常态 提示「需求过短」 < 20 字 + 不含动词时,PM Agent 会在 prd.html 写 <aside data-question> 。模态框关闭后 toast 提示「PM 正在追问你」。 提示「与历史矛盾」 PM 检测到冲突,prd 中插 <aside data-conflict> 。Dashboard 右上角弹紫色横幅,需要人类点开模态框补充。 项目已 shipped-final 「+ NEW REQUIREMENT」按钮置灰;如人类强行调 API,返 409。 键盘可达性 键 动作 Tab 循环 textarea → 取消 → 提交 Enter 在 textarea 内换行 Ctrl/Cmd + Enter 提交 Esc 取消"},{"f":"proto-prd-diff.html","title":"原型 · PRD diff 面板","h1":"PrdDiffPanel · 文档版本差异","headings":["线框","三色 diff 标记","怎么实现 diff","切 rev 控件","影响 Unit 提示"],"defs":[],"content":"线框 PrdDiffPanel · rev 3 vs rev 2 三色 diff 标记 类型 颜色 判定 影响 Unit added emerald 新 section data-rev 等于当前 rev 可能新增多个 Unit changed amber section data-rev 旧但被新需求隐含修改 需要 Architect 标注 + 影响 contract_hash 时才 reset removed red 新 PRD 不再包含的 section(罕见) 对应 Unit 整体归档到 history/removed/ unchanged 灰底 正文不变 不动 怎么实现 diff 客户端用 diff-dom ,section 粒度。算法: 对 newHtml 和 oldHtml 分别用 DOMParser.parseFromString 枚举每个 section[data-rev] 在双方的存在性 + textContent hash 三态分类:only-new = added · only-old = removed · 同 id 不同 hash = changed 渲染时为每条 section 包一层 wrapper 标记色 选 section 级而非 token 级,是为了贴合 PM Agent 的写入约束 — 它的修改本来就是 section 粒度。 切 rev 控件 顶部默认 \"rev N vs rev N-1\",可下拉切换: \"rev 3 vs rev 0\" — 看从 init 到现在的累积 \"rev 3 vs rev 2\" — 默认 \"rev 3 only\" — 单看本 rev 新增 影响 Unit 提示 每个 diff section 下方一行 → 影响 Unit: ... 。来源: unitRegistry. diffWithSDD (pid) // → { added: ['oauth-be','oauth-fe'], changed: [], removed: [] } 把 added/changed/removed 列表渲染在对应 diff 行下面,让人类一眼知道「这个反馈引发了哪几个 Unit 入队」。"},{"f":"proto-unit-drawer.html","title":"原型 · Unit 详情抽屉","h1":"UnitDrawer · Unit 详情","headings":["线框","抽屉 4 个区域","① 头部","② Spec","③ Contract","④ Recent runs + coder.log","从哪里打开 / 怎么关闭","资料完整性追溯"],"defs":[],"content":"线框 UnitDrawer · unit auth-be · open from DAG 抽屉 4 个区域 ① 头部 Unit id + 状态徽章 + meta(type / retries / depends_on)+ 关闭 ×。 ② Spec 来自 SDD 对应 section 的 <p> + <ul data-acceptance> ,含「来自 SDD@revN」追溯标签。 ③ Contract 展开 contract.json 的 exports(HTTP endpoints)。鼠标悬停可看完整 schema。 ④ Recent runs + coder.log 最近 3 次 run 摘要卡片,每张可点开看完整 result.json。底部黑底面板显示 coder.log 的最近 5 条决策。 从哪里打开 / 怎么关闭 方式 说明 点击 DAG 节点 主入口 SSE unit.state 推送时点击「查看」 从右上 toast 跳 键盘 ←/→ 切换上一/下一个 Unit(在 units 数组中) Esc 或 × 关闭 点击外部遮罩 关闭 资料完整性追溯 抽屉是 US-05 的核心 — 让 3 个月后的自己能反推决策。所以下面这些链接必须可用: 链接 跳到 \"来自 SDD@rev 5\" pill 新 tab 打开 history/sdd-rev-5.html contract sha256:7f3a... pill 纯净 JSON 视图 每条 run 卡片 展开内嵌 result.json + stdout 抽屉 \"open full log\" 跳到 stream.log 中该 Unit 全部行 如曾被 L2 reset 显示触发它的 triage.json 链接"},{"f":"reading-guide.html","title":"阅读指南","h1":"阅读指南","headings":["两个读者","人类 · 决策 / 评审","Agent · 执行 / 复现","HTML 锚点协议(Claude Code 读取)","抽取示例 — units.json 怎么从 SDD 派生","MVCP 抽取约定","文档作为「构建清单」","本文档版本"],"defs":[],"content":"两个读者 HUMAN 人类 · 决策 / 评审 关心「想做什么、为什么、值不值得」。阅读路径: 封面 背景 · 角色 原始需求 · 范围与原则 功能树 · 用户流程 三层 Loop · 六个 Agent 最小改造路径 · 风险 预估 30 分钟。最后回到 plan-risks 末尾回答 3 个 yes/no。 CLAUDE CODE Agent · 执行 / 复现 关心「这个项目要落地什么文件、什么签名、什么行为」。阅读路径: 本页 — 学会怎么抽取结构化信息 功能树 — 拿到所有 Unit id 与依赖关系 SDD 总览 — 了解 MVCP 四象限 Model · View · Control · Process — 抽取构建清单 Unit Contract — 跨 Unit 握手协议 复现指南 — 执行 prompt 机械化阅读,不需要欣赏 prose。所有 data-* 属性是 Agent 抓取的锚点。 HTML 锚点协议(Claude Code 读取) 所有结构化信息都通过 data-* 属性暴露。Agent 用 cheerio 选择器即可机械抽取,无需 NLP。 属性 出现位置 含义 选择器示例 data-doc 顶级 <article> 文档类型:prd / sdd article[data-doc=\"prd\"] data-rev article / section 修订号,单调递增 section[data-rev=\"3\"] data-unit-id SDD section 本 section 描述的 Unit section[data-unit-id=\"auth-be\"] data-type SDD section Unit 类型:be / fe / mw / db [data-type=\"be\"] data-depends SDD section 本 Unit 依赖的其他 Unit id(空格分隔) — data-contract <pre> 契约定义 (openapi / tsinterface) pre[data-contract=\"openapi\"] data-acceptance <ul> 验收标准列表 — data-mvcp 本套文档 SDD section 所属 MVCP 象限 [data-mvcp=\"model\"] data-origin PRD section 本 section 来源(用户 + 时间) — data-conflict PRD <aside> 与历史 section 矛盾的标记 — data-changelog PRD <footer> 修订日志区 — 抽取示例 — units.json 怎么从 SDD 派生 // 给 Claude Code 的 cheerio 代码片段 const $ = cheerio. load (sddHtml); const units = $( 'section[data-unit-id]' ). map ((_, el) => ( { id: $(el). attr ( 'data-unit-id' ), type: $(el). attr ( 'data-type' ), depends_on: ($(el). attr ( 'data-depends' ) || '' ). split ( /\\s+/ ). filter (Boolean), contract: $(el). find ( 'pre[data-contract]' ). text (), acceptance: $(el). find ( 'ul[data-acceptance] li' ). map ((_,li)=>$(li). text ()). get (), } )). get (); // → 直接落盘为 units.json 的 .units 字段 MVCP 抽取约定 SDD 四象限页( Model / View / Control / Process )使用统一的 5 字段结构。每个定义都带: 字段 含义 示例 name 唯一标识符 units.json · DevLoopDashboard · LoopController.runProject path 预期落盘路径 server/services/doc-store.js shape 结构定义(TS / TypeBox / Vue props) 代码块 contract 行为契约(前置 / 后置条件) 列表 tests 验收用例 列表 Claude Code 把 SDD 转成 Unit 任务时,每一条 MVCP 定义都可以独立映射到一个 Unit(或多个 Unit 共享)。 文档作为「构建清单」 把整套 PRD + SDD 看作一个机器可解析的 build manifest。复现流程: 具体的 prompt 模板见 plan-reproducibility 。 本文档版本 rev 日期 变更摘要 v0.1 2026-05-12 初版 24 页 · 单 HTML + 拆 24 页 v0.2 2026-05-13 升级到 41 页:新增 PRD 标准章节、SDD MVCP 五页、原型补全、复现指南"},{"f":"requirement.html","title":"原始需求","h1":"原始需求","headings":["Verbatim · 人类原话","结构化解析 · 6 条硬约束","三个隐含约束(用户没说但必须有)","H1 · 收敛性","H2 · 可观测性","H3 · 文档可追溯","用户原话 → 设计映射"],"defs":[],"content":"Verbatim · 人类原话 「我需要一个能实现自动化对代码进行需求整理成 HTML 格式的 PRD、SDD,然后按照 docs 的分解的功能列表,挨个 Unit 开发完成前后端代码,并实现 Unit Test,形成 Unit Loop。当所有 Unit 开发完成,则实现 Integration Test 集成测试。如果集成测试有 Bug,则修改最早期的 PRD、SDD 文档,然后再开发各自的 Unit,实现 Unit 开发测试,然后再集成测试。实现一个小 Loop (Unit) 和一个大 Loop (Integration) 两层的 Loop 的自动化开发流程。然后人类的需求和反馈形成第三层 Loop——人类发送一个新的需求,更新到 PRD 文档中,然后依据 PRD 修改 SDD,再增量 Unit、Integration 开发,直至功能开发完成。」 结构化解析 · 6 条硬约束 ID 约束 系统侧承诺 对应 Agent / 组件 R1 PRD / SDD 必须是 HTML 格式 新 DocStore :HTML 模板 + section 级补丁 PM · Architect R2 SDD 含可机读「功能列表」 SDD 同步产 units.json (DAG 索引) Architect R3 Unit 级前后端代码 + Unit Test 每 Unit 触发 Coder → Tester Coder · Tester R4 全部 Unit 通过 → 集成测试 LoopController L2 闸门 LoopController · Integrator R5 集成失败 → 回写 PRD/SDD → 再跑 Unit BugTriager 归因 → Architect 应用 patch → 受影响 Unit reset BugTriager · Architect R6 人类新需求 → 增量重跑 POST /api/devloop/requirement ,PM 增量更新 PRD PM 三个隐含约束(用户没说但必须有) H1 · 收敛性 无论哪一层 Loop,都必须有「全局 retry 预算」,避免死循环。 H2 · 可观测性 人类要能随时看到「现在卡在哪一步、哪个 Agent、哪个 Unit」。否则 L3 反馈失去意义。 H3 · 文档可追溯 每个 rev 的 PRD/SDD 都要归档,能 diff 出本轮反馈引入了什么变化。 用户原话 → 设计映射 把上面这段话从左到右画成「需求 → 设计」对照流:"},{"f":"scope.html","title":"立意与范围","h1":"立意与范围","headings":["目标","三条设计原则","最大化复用","文档即状态","失败先修文档","不在范围内","本 PRD 包含的信息类型(HTML 8 元素)","① Tables","② Design","③ Illustrations","④ Code","⑤ Interaction","⑥ Workflows","⑦ Spatial","⑧ Images"],"defs":[],"content":"目标 目标不是「再造一个 Devin」,而是把当前散落在 server/src/core/ 、 packages/evolution/ 、 routes/pipeline* 的能力,组装成一个有名字、有状态、可观测的 LoopController。新代码总量控制在 1500 行以内,最大化复用已有抽象。 系统能力 当前状态 本次改造后 多 Agent 调度 master-session + orchestrator(散) 统一由 LoopController 入口 需求 → 代码 无端到端链路 PM → Architect → Coder → Tester → Integrator 闭环 失败修复 EvolutionLoop(只对 Skill) 抽出 retryWithReflection 共享给 L1 文档化输出 仅 Skill 训练 digest PRD / SDD 双 HTML,每 rev 归档 三条设计原则 REUSE 最大化复用 Master / Orchestrate / SessionPool / EvolutionLoop / Scheduler / Pipeline 全部沿用。新增组件不超过 5 个文件,新增模板 6 个。 NEW 文档即状态 PRD / SDD / units.json 是流程的 唯一真源 。Agent 之间通过文档交接,而不是共享内存或 RPC payload。文档版本即流程游标。 PATCH 失败先修文档 L2 集成失败时, 默认先回写 SDD 再重跑 L1。不允许 Coder Agent 直接 patch 别的 Unit 的代码以「绕过」契约。 例外: BugTriager 输出 category=impl_bug 时 不 改 SDD,只 reset 该 Unit — 因为是代码没遵守 SDD,改的是代码不是契约。 不在范围内 能力 原因 留给后续 真实浏览器 E2E 测试 (Playwright headed) 初版用 vitest + 后端集成测试足够覆盖 P5 后续 生产环境部署管线(CI/CD) 聚焦在「跑通」而非「上线」 独立 PRD GitHub PR 自动开 需要外部 token、权限边界复杂 P5 后续 外部 IDE 插件 看板已能展示,编辑器集成性价比低 — 多语言 / 跨语言项目 P1~P4 仅证明 JS/TS 全栈链路 P6+ 本 PRD 包含的信息类型(HTML 8 元素) 用户明确要求 PRD HTML 必须装入 8 类信息: ① Tables 规格 / 映射 / 阶段 ② Design 色板 / 字号 / 排版语言 ③ Illustrations 嵌套环 / 状态机 SVG ④ Code JSON / curl / JS 骨架 ⑤ Interaction Tabs · 折叠 · 侧栏 ⑥ Workflows 跨泳道时序图 ⑦ Spatial 目录树 / 组件方框 ⑧ Images UI wireframe SVG"},{"f":"sdd-control.html","title":"SDD · Control","h1":"Control · API + 服务方法签名","headings":["C-1 · POST /api/devloop/projects","C-2 · POST /api/devloop/projects/:pid/requirement","C-3 · POST /api/devloop/projects/:pid/run","C-4 · GET /api/devloop/projects/:pid/state","C-5 · GET /api/devloop/projects/:pid/stream","C-6 · POST /api/devloop/projects/:pid/sign-off","C-7 · POST /api/devloop/projects/:pid/abandon","C-8 · LoopController(核心服务)","C-9 · DocStore","C-10 · UnitRegistry","C-11 · SSEBroker","C-12 · 中间件","C-13 · agentManager.callAgent 约定","C-14 · LoopController.getProgress(pid)","C-15 · TimelineLogger · 全流程日志写入","C-16 · BugTracker · bugs.ndjson CRUD","C-17 · ChangelogWriter","C-18 · GitVault · 每项目 git 仓库管理","端点-handler-服务依赖矩阵"],"defs":["REST.createProject","REST.postRequirement","REST.run","REST.state","REST.stream","REST.signOff","REST.abandon","LoopController","DocStore","UnitRegistry","SSEBroker","Middleware","AgentCallConvention","LoopController.getProgress","TimelineLogger","BugTracker","ChangelogWriter","GitVault"],"content":"C-1 · POST /api/devloop/projects server/routes/devloop.js router. post ( '/projects' , requireAuth, async (req, res) => { const { company_id, name, requirement, mode = 'sandbox' , // 默认 sandbox target_path // linked-* 时必填 } = req.body; const { pid, root_path } = await loopController. createProject ( { company_id, name, owner: req.user.id, mode, target_path, } ); loopController. onHumanFeedback (pid, { text: requirement, origin: req.user.id } ); res. status ( 201 ). json ( { project_id: pid, mode, root_path, status: 'prd_drafting' , stream_url: `/api/devloop/projects/ ${pid} /stream` , } ); } ); 请求体必填: {company_id, name, requirement} ;可选: {mode, target_path} mode 默认 sandbox ; linked-new 时 target_path 必填且必须是绝对空目录或不存在; linked-existing v1 拒收(v2 启用) target_path 不允许指向系统敏感目录(详见 arch-project-root §⑫ ) company_id 必须与 JWT 中的匹配(防越权) 立即返回 201 + project_id + root_path;PM Agent 异步起 合法 sandbox 请求 → 201 + 含 project_id + root_path=data/.../projects/{pid}/ linked-new + target_path 合法 → 201 + root_path=用户指定路径 + .git 已初始化 linked-new + target_path 不存在父目录 → 400 拒绝 linked-new + target_path 是 /etc → 403 sensitive_path company_id 不匹配 JWT → 403 缺字段 → 400 + 字段名 C-2 · POST /api/devloop/projects/:pid/requirement server/routes/devloop.js router. post ( '/projects/:pid/requirement' , requireAuth, async (req, res) => { const { text, origin } = req.body; loopController. onHumanFeedback (req.params.pid, { text, origin: origin || req.user.id } ); res. status ( 202 ). json ( { accepted: true , queued: true } ); } ); 立即 202,不等 PM 跑完 多个反馈同时到 → 队列化,按时序处理 项目 status=shipped-final 或 abandoned → 拒绝(409) C-3 · POST /api/devloop/projects/:pid/run server/routes/devloop.js router. post ( '/projects/:pid/run' , requireAuth, async (req, res) => { // 续跑(如果之前 blocked / paused,重启循环) loopController. runProject (req.params.pid); res. json ( { running: true } ); } ); 等价于「resume」,对 status=running 是 no-op blocked / l2_exhausted 状态调用此端点 → 重置预算并继续 C-4 · GET /api/devloop/projects/:pid/state server/routes/devloop.js router. get ( '/projects/:pid/state' , requireAuth, (req, res) => { res. json (loopController. getState (req.params.pid)); } ); // 返回结构: { project_id, prd_rev, sdd_rev, current_loop: 'L1' | 'L2' | 'L3' | 'idle' , units: Unit[], l2_rounds, l2_budget, agents_active: string [], token_used, token_budget, } 读时不加锁(不一致几毫秒可接受) response 体限 100KB 以内(大项目 unit 多) C-5 · GET /api/devloop/projects/:pid/stream server/routes/devloop.js router. get ( '/projects/:pid/stream' , requireAuth, (req, res) => { res. writeHead ( 200 , { 'Content-Type' : 'text/event-stream' , 'Cache-Control' : 'no-cache' , 'Connection' : 'keep-alive' , } ); sseBroker. subscribe (req.params.pid, res, { replayFromOffset: req.query.last_event_id, } ); } ); 支持 Last-Event-ID 断线重连 心跳:每 25s 发一条 :keepalive 注释 项目结束(shipped/abandoned)→ 发 close 帧后关闭 C-6 · POST /api/devloop/projects/:pid/sign-off server/routes/devloop.js router. post ( '/projects/:pid/sign-off' , requireAuth, (req, res) => { loopController. signOff (req.params.pid, req.user.id); res. json ( { status: 'shipped-final' } ); } ); 仅项目 owner 或 admin 能调用。设置 status=shipped-final,停止接收任何新反馈。 C-7 · POST /api/devloop/projects/:pid/abandon server/routes/devloop.js router. post ( '/projects/:pid/abandon' , requireAuth, (req, res) => { loopController. abandon (req.params.pid, req.user.id); res. json ( { status: 'abandoned' } ); } ); C-8 · LoopController(核心服务) server/src/core/loop-controller.js module.exports .loopController = { createProject ( { company_id, name, owner } ): Promise < string >, runProject (pid: string ): Promise < 'shipped' | 'blocked' | 'l2_exhausted' >, runUnit (pid: string , unitId: string ): Promise <UnitResult>, onHumanFeedback (pid: string , { text, origin } ): void , onL2Failure (pid: string , runId: string ): Promise < void >, signOff (pid: string , byUser: string ): void , abandon (pid: string , byUser: string ): void , getState (pid: string ): ProjectState, } ; 所有 Agent 调用通过 agentManager.callAgent(name, input) ,不直接起进程 runProject 是幂等的:同一 pid 多次调用合并为一个执行"},{"f":"sdd-model.html","title":"SDD · Model","h1":"Model · 数据结构全集","headings":["M-1 · ProjectMeta","M-2 · units.json","M-3 · prd/ 多页 PRD 站点","M-4 · sdd.html 结构","M-5 · contract.json(每 Unit)","M-6 · integration/spec.json","M-7 · runs/result.json (L1)","M-8 · integration/runs/result.json (L2)","M-9 · triage.json","M-10 · AgentRuntimeState · 持久化运行态","M-11 · AgentMemory · 跨项目长期记忆","M-12 · AgentTodolist · 单次调用的子任务清单","M-13 · ProcessTimeline · 全流程统一日志","M-14 · bugs.ndjson · 跨 run 跨 unit Bug 聚合","M-15 · changelog.ndjson · 版本变更流","归档命名约定(统一目录形式)","无 SQL · 无新表"],"defs":["ProjectMeta","UnitsJson","PRD-Site","SDD-HTML","ContractJson","IntegrationSpec","RunResult","IntegrationResult","TriageJson","AgentRuntimeState","AgentMemory","AgentTodolist","ProcessTimeline","BugsNdjson","ChangelogNdjson"],"content":"M-1 · ProjectMeta data/companies/{cid}/projects/{pid}/project.json // TypeBox schema const ProjectMeta = Type. Object ( { id: Type. String ( { pattern: '^p-[a-z0-9]{6}$' } ), company_id: Type. String (), name: Type. String (), // 项目根 (见 arch-project-root) mode: Type. Union ( [ Type. Literal ( 'sandbox' ), // 默认,根在 data/.../projects/{pid}/ Type. Literal ( 'linked-new' ), // 用户指定空目录 Type. Literal ( 'linked-existing' ), // v2:复用已有 git repo ] ), root_path: Type. String (), // 绝对路径,所有派生路径的源 status: Type. Union ( [ Type. Literal ( 'prd_drafting' ), Type. Literal ( 'running' ), Type. Literal ( 'blocked' ), Type. Literal ( 'shipped' ), Type. Literal ( 'shipped-final' ), Type. Literal ( 'abandoned' ), ] ), created_at: Type. String ( { format: 'date-time' } ), updated_at: Type. String ( { format: 'date-time' } ), owner: Type. String (), token_budget: Type. Number ( { default: 5_000_000 } ), token_used: Type. Number ( { default: 0 } ), compressOldRuns: Type. Boolean ( { default: false } ), } ); id 全局唯一,跨 company 不重复 status 只能按状态机合法转换(见 sdd-process.html · M-process-project) token_used > token_budget 时 LoopController 自动挂起项目 M-2 · units.json data/companies/{cid}/projects/{pid}/units.json const Unit = Type. Object ( { id: Type. String ( { pattern: '^[a-z]+(-[a-z]+)*-(be|fe|mw|db)$' } ), type: Type. Union ( [ Type. Literal ( 'be' ), Type. Literal ( 'fe' ), Type. Literal ( 'mw' ), Type. Literal ( 'db' ) ] ), status: Type. Union ( [ Type. Literal ( 'pending' ), Type. Literal ( 'coding' ), Type. Literal ( 'testing' ), Type. Literal ( 'failed' ), Type. Literal ( 'passed' ), Type. Literal ( 'blocked' ), ] ), depends_on: Type. Array (Type. String ()), retries: Type. Number ( { default: 0 } ), retry_budget: Type. Number ( { default: 5 } ), contract_hash: Type. String ( { pattern: '^sha256:[a-f0-9]{16}$' } ), last_run: Type. Optional (Type. String ()), } ); const UnitsJson = Type. Object ( { project_id: Type. String (), prd_rev: Type. String (), sdd_rev: Type. String (), units: Type. Array (Unit), l2_rounds: Type. Number ( { default: 0 } ), l2_budget: Type. Number ( { default: 3 } ), } ); DAG 必须无环(DocStore 写入前用拓扑排序校验) depends_on 引用的 id 必须存在于本文件 units 中 contract_hash = sha256(SDD 中对应 section <pre data-contract> 文本) 截前 16 位 循环依赖 → 拒绝写入 未知 id → 拒绝写入 所有 status 为 passed 且 hash 与当前 SDD 匹配 → l2GateOpen 返回 true M-3 · prd/ 多页 PRD 站点 data/companies/{cid}/projects/{pid}/prd/ (目录) // 目录布局 (PM Agent 产出,见 agents-pm.html §2.1) prd/ ├── index.html // Hero + reading guide entry ├── reading-guide.html ├── assets/ │ ├── shell.css // 复制自模板 │ ├── shell.js // 3 级菜单 + 搜索 │ └── search-index.json ├── product/ │ ├── background.html │ ├── personas.html │ ├── original-req.html │ ├── scope.html │ ├── success-metrics.html │ └── glossary.html ├── feature/ │ └── feature-tree.html ├── flow/ │ └── user-flows.html └── prototype/ └── proto-{viewName}.html // 每个 chapter HTML 内的结构 (cheerio 校验): <article data-doc = \"prd\" data-rev = \"N\" > <section data-rev = \"M\" data-origin = \"{user@iso}\" > <h3> ... </h3> <p> ... </p> </section> <aside data-conflict > ... </aside> // 可选,index.html only <footer data-changelog > ... </footer> </article> 顶级必须是 <article data-doc=\"prd\"> ,每页都有 每个 section 必含 <h3> + 至少一个 <p> section data-rev 单调递增(页内), article data-rev 取该页内最大 每页至少覆盖 4/8 类 HTML 信息(见 agents-pm §2.2) data-conflict aside 仅出现在 index.html 顶部,存在时整个项目暂停等人 11 个必备章节缺一个 → validate_prd_site 失败 init 模式后 11 章节全在 incremental 模式后仅 affected 章节被改 多 rev 后 history/ 含旧版归档 M-4 · sdd.html 结构 data/companies/{cid}/projects/{pid}/sdd.html <article data-doc = \"sdd\" data-derived-from = \"prd@revN\" > <section data-unit-id = \"auth-be\" data-type = \"be\" data-depends = \"\" > <h3> Unit · auth-be </h3> <p> ...功能描述... </p> <pre data-contract = \"openapi\" > POST /api/auth/login ... </pre> <ul data-acceptance > <li> acceptance criteria 1 </li> </ul> </section> </article> 每个 unit section 必含 contract + acceptance data-depends 空格分隔 unit id 列表,可为空 data-derived-from 指向产生它的 prd 版本 M-5 · contract.json(每 Unit) .../units/{unitId}/contract.json con"},{"f":"sdd-overview.html","title":"SDD MVCP 总览","h1":"SDD · MVCP 总览","headings":["为什么是 MVCP 而不是 MVC","四象限","每个定义的「五字段标准」","HTML 模板示例","抽取流程","四象限对应 Agent"],"defs":[],"content":"为什么是 MVCP 而不是 MVC 传统 MVC 三分对一个有大量「长进程 / 异步循环」的系统不够用。DevLoop 系统里: L1/L2/L3 是 长时运行的状态机 每个 Agent 是 长寿子进程 (ACP session 复用) SSE Broker 是 持续推送通道 这些都不属于 Controller(即时响应请求),也不属于 Model(数据)。给它们一个独立象限 Process 才有归处。 四象限 每个定义的「五字段标准」 所有 MVCP 页里的每条定义都强制遵循下表结构,便于 Claude Code 用同一套抽取器处理: 字段 类型 含义 抽取选择器 name string · 必填 唯一标识符 · 命名空间.对象名 section[data-def-name] path string · 必填 预期落盘路径或文件位置 [data-def-name] code.path shape code · 必填 TypeBox / TS interface / Vue defineProps / SQL DDL [data-def-name] pre.shape contract list · 选填 前置 / 后置条件 / 不变式 [data-def-name] ul.contract li tests list · 选填 验收用例(Tester Agent 直接转 vitest) [data-def-name] ul.tests li HTML 模板示例 <section data-def-name = \"DocStore.writePRD\" data-mvcp = \"control\" > <h3> DocStore.writePRD(pid, html) </h3> <code class = \"path\" > server/services/doc-store.js </code> <pre class = \"shape\" > async function writePRD(pid: string, html: string): Promise<{rev: number}> </pre> <ul class = \"contract\" > <li> 前置:html 必须包含顶级 article[data-doc=\"prd\"] </li> <li> 后置:旧版本归档到 history/prd-rev-{rev}.html </li> </ul> <ul class = \"tests\" > <li> 写入后 readPRD 能读回完全一致内容 </li> <li> 非法 HTML 抛 ValidationError </li> </ul> </section> 本套文档因美观需要不完全遵守这个最严格的 HTML 模板,但每条定义至少含 name + path + shape。完全机械化版会在 P1 实施时由 Architect Agent 产出。 抽取流程 // Claude Code 抽取 build-manifest 的伪代码 const manifest = { model: [] , view: [] , control: [] , process: [] } ; for ( const mvcp of [ 'model' , 'view' , 'control' , 'process' ] ) { const html = await fetch ( `sdd- ${mvcp} .html` ). then (r => r. text ()); const $ = cheerio. load (html); $( '[data-def-name]' ). each ((_, el) => { manifest[mvcp]. push ( { name: $(el). attr ( 'data-def-name' ), path: $(el). find ( 'code.path' ). first (). text (), shape: $(el). find ( 'pre.shape' ). text (), contract: $(el). find ( 'ul.contract li' ). map ((_,li) => $(li). text ()). get (), tests: $(el). find ( 'ul.tests li' ). map ((_,li) => $(li). text ()). get (), } ); } ); } fs . writeFileSync ( 'build-manifest.json' , JSON.stringify (manifest, null , 2 )); 结果是一份 build-manifest.json ,能直接喂给 Architect Agent 生成 units.json + Unit spec 文件。 四象限对应 Agent 象限 谁负责写 谁负责消费 Model Architect Coder(读 schema 生成数据访问代码)· DocStore(自身实现) View Architect Coder(fe 类 Unit 直接对应) Control Architect Coder(be 类 Unit)· Integrator(跑端点) Process Architect 所有 Agent 都要读懂 Process 才知道自己在生命周期哪一步"},{"f":"sdd-process.html","title":"SDD · Process","h1":"Process · 长进程与状态机","headings":["P-1 · L1 Unit Loop 状态机","转换表(完整)","伪代码","P-2 · L2 Integration Loop 状态机","L2 闸门判定","L2 主循环","四种 triage 类别的分支","P-3 · L3 Human Feedback 队列","P-4 · Agent 生命周期","P-5 · SSE Broker 心跳与 Replay","P-6 · 清理与归档","P-7 · 预算守护","P-8 · Agent State 持久化 + 崩溃恢复","调用前 / 中 / 后三阶段","崩溃恢复流程","恢复决策三态","Memory 注入流程","触发 SSE 事件","P-9 · Bug 状态机","状态机","合法转换","自动转换 vs 人为转换","与 L1/L2 的耦合","新增 SSE 事件","P-10 · 自动 git commit 流程","recoverOnBoot 加 git 清理","新增 SSE 事件","三层 Loop 嵌套伪代码(最终版)"],"defs":["Process.L1","Process.L2","Process.L3","Process.AgentLifecycle","Process.SSE","Process.Cleanup","Process.Budget","Process.StatePersistence","Process.BugLifecycle","Process.GitCommit"],"content":"P-1 · L1 Unit Loop 状态机 loop-controller.js → runUnit() 转换表(完整) from to 触发 副作用 pending coding LoopController 调度到 wave markCoding + SSE unit.state coding testing Coder 返回 success markTesting + SSE coding failed Coder 抛错(沙箱越权 / 超时) fail · reason=coder_error testing passed Tester result.ok = true pass + 写 contract_hash + SSE testing failed Tester result.ok = false fail · 写 result.json failed coding retries < retry_budget retries++ · 调 Coder 带 history failed blocked retries ≥ retry_budget block + SSE human_intervention_needed passed pending L2 触发 reset retries 重置 · 旧代码归档 blocked pending 人类显式 resume retries 重置 伪代码 async function runUnit (pid, unitId) { const unit = unitRegistry. list (pid). find (u => u. id === unitId); let failures = [] ; for ( let i = unit. retries ; i < unit. retry_budget ; i++) { unitRegistry. markCoding (pid, unitId); const coderRes = await agentManager. callAgent ( 'coder' , { pid, unit_id: unitId, history: failures } ); if (!coderRes. ok ) { unitRegistry. fail (pid, unitId, 'coder_error' ); continue ; } unitRegistry. markTesting (pid, unitId); const testerRes = await agentManager. callAgent ( 'tester' , { pid, unit_id: unitId } ); if (testerRes. ok ) { const hash = docStore. hashContract ( await readContract(pid, unitId)); unitRegistry. pass (pid, unitId, hash); return { ok: true , retries: i } ; } failures. push (testerRes. failures ); unitRegistry. fail (pid, unitId, 'test_red' ); } unitRegistry. block (pid, unitId, 'budget_exceeded' ); return { ok: false } ; } 同一 Unit 任何时刻只允许一个 runUnit 实例(用 Map<unitId, Promise> 去重) retry 之间不复用 Agent session(每次新 ACP session,保证干净) P-2 · L2 Integration Loop 状态机 loop-controller.js → onL2Failure() · runProject() L2 段 L2 闸门判定 function l2GateOpen (pid) { const units = unitRegistry. list (pid); const sdd = docStore. readSDD (pid); return units. every (u => { if (u. status !== 'passed' ) return false ; const expectedHash = contractHashFromSDD(sdd, u. id ); return u. contract_hash === expectedHash; } ); } L2 主循环 async function runL2 (pid) { const intRes = await agentManager. callAgent ( 'integrator' , { pid } ); if (intRes. ok ) { sseBroker. emit (pid, 'project.shipped' , { }); return 'shipped' ; } const tri = await agentManager. callAgent ( 'bug-triager' , { pid, run_id: intRes. run_id } ); if (tri. confidence < 0.5 || tri. category === 'spec_ambiguous' ) { sseBroker. emit (pid, 'human_intervention_needed' , tri); return 'l2_blocked' ; } if (tri. category === 'infra_flake' ) { return runL2 (pid); // 直接重跑,不动 Unit } if (tri. category === 'impl_bug' ) { // 不改 SDD,只 reset 受影响 Unit unitRegistry. reset (pid, tri. affected_units ); return 'retry_l1' ; } // contract_mismatch → patch SDD + reset await agentManager. callAgent ( 'architect' , { pid, mode: 'patch' , patch: tri. proposed_sdd_patch } ); unitRegistry. reset (pid, tri. affected_units ); const project = getProjectMeta (pid); if (++project. l2_rounds >= project. l2_budget ) { sseBroker. emit (pid, 'project.exhausted' , { last_triage: tri } ); return 'l2_exhausted' ; } return 'retry_l1' ; } 四种 triage 类别的分支 category 是否改 SDD 是否 reset Unit 是否计入 l2_rounds contract_mismatch 是 是 是 impl_bug 否 是 否(不烧预算) spec_ambiguous 否(升 L3) 否 否 infra_flake 否 否 否 P-3 · L3 Human Feedback 队列 loop-controller.js → onHumanFeedback() // 每个项目维护一个反馈队列,串行消费 const feedbackQueues = new Map < string , Feedback[]>(); function onHumanFeedback (pid, feedback) { if (!feedbackQueues. has (pid)) feedbackQueues. set (pid, [] ); feedbackQueues. get (pid). push (feedback); // 单 worker per pid if (!isWorking(pid)) drainFeedback(pid); } async function drainFeedback (pid) { while (feedbackQueues. get (pid). length ) { const fb = feedbackQueues. get (pid). shift (); // 1. 等当前 L1 / L2 跑完(不打断) await waitForCurrentLoop(pid); // 2. PM 增量补 PRD await agentManager. callAgent ( 'pm' , { pid, feedback: fb } ); // 3. Architect 重算 SDD + units.json (mode=generate) await agentManager. callAgent ( 'architect' , { pid, mode: 'generate' } ); // 4. UnitRegistry.diffWithSDD → reset changed/removed co"},{"f":"sdd-view.html","title":"SDD · View","h1":"View · UI 组件全集","headings":["V-1 · DevLoopDashboard.vue","V-2 · DashboardHeader.vue","V-3 · LoopStatusBar.vue","V-4 · UnitsDAG.vue","V-5 · LiveAgentStream.vue","V-6 · PrdDiffPanel.vue","V-7 · FeedbackModal.vue","V-8 · UnitDrawer.vue","V-9 · 路由表","V-10 · i18n key 清单","V-11 · 设计 token(CSS 变量)","组件渲染依赖关系"],"defs":["DevLoopDashboard","DashboardHeader","LoopStatusBar","UnitsDAG","LiveAgentStream","PrdDiffPanel","FeedbackModal","UnitDrawer","Router","I18nKeys","DesignTokens"],"content":"V-1 · DevLoopDashboard.vue web/src/views/DevLoopDashboard.vue // Composition API · setup script const props = defineProps< {} >(); // 无 props,从 route param 取 pid const route = useRoute (); const pid = computed(() => route. params . pid as string ); const state = ref<ProjectState | null>( null ); const sse = useEventSource( `/api/devloop/projects/ ${pid.value} /stream` ); const modalOpen = ref( false ); const drawer = ref<Unit | null>( null ); // 组件树: // DashboardHeader → LoopStatusBar → UnitsDAG → LiveAgentStream + PrdDiffPanel // 浮层:FeedbackModal · UnitDrawer 挂载 5 秒内必须发起 GET /state 请求 SSE 断线自动重连(指数退避,最长 30s) 页面卸载时关 SSE V-2 · DashboardHeader.vue web/src/components/devloop/DashboardHeader.vue defineProps< { project: { id: string , name: string , status: string } } >(); defineEmits< { feedback: [] // 点击 + NEW REQUIREMENT } >(); // 渲染: // - 黑底标题栏 // - 项目名(Fraunces 22px) // - 右上紫色按钮 V-3 · LoopStatusBar.vue web/src/components/devloop/LoopStatusBar.vue defineProps< { l1: { passed: number , testing: number , pending: number , failed: number } , l2: { lastRunAt?: string , status: 'idle'|'running'|'failed'|'shipped' , rounds: number } , l3: { rev: number , awaitingFeedback: boolean } } >(); 三张卡片对应 emerald / amber / violet 三色,CSS 用 --L1/L2/L3 token L2 running 时 amber 卡片有 pulse 动画 L3 awaitingFeedback=true 时显示 toast V-4 · UnitsDAG.vue web/src/components/devloop/UnitsDAG.vue defineProps< { units: Unit [] } >(); defineEmits< { select: [ unit: Unit ] } >(); // 内部使用 SVG <rect> + <line> // > 50 unit 时切 d3-force 自动布局 节点配色由 unit.status 决定(见 ui-design-system 色板) 节点点击 emit select 事件,父组件打开 UnitDrawer 箭头方向 = depends_on (上游 → 下游) V-5 · LiveAgentStream.vue web/src/components/devloop/LiveAgentStream.vue defineProps< { events: AgentStreamEvent [] // 由父组件从 SSE 过滤 agent.stream 事件 } >(); // 渲染: // - 黑底 #0E0E10 终端样式 // - 最近 500 行,老的丢 // - PASS / FAIL 着色(绿/橙) // - 自动滚到底部,除非用户手动滚 V-6 · PrdDiffPanel.vue web/src/components/devloop/PrdDiffPanel.vue defineProps< { prdHtml: string , prevRev: number | null } >(); // 内部用 diff-dom 客户端算 section 级 diff // 新增 section 绿色 + 标记 / 删除红色删除线 / 修改黄色 V-7 · FeedbackModal.vue web/src/components/devloop/FeedbackModal.vue defineProps< { open: boolean } >(); defineEmits< { submit: [ text: string ] , close: [] , } >(); // 单 textarea + 字数计数 + Ctrl+Enter 提交 // 提交后立即 emit close,父组件负责发请求 空文本 → 提交按钮 disabled > 4000 字 → 警告但不阻止 Esc 触发 close V-8 · UnitDrawer.vue web/src/components/devloop/UnitDrawer.vue defineProps< { unit: Unit | null , runs: RunResult [] // 最近 3 次 run } >(); defineEmits< { close: [] } >(); // 右侧抽屉,从 right 滑入 // 显示:spec.json 摘要 · contract.json · 最近 3 次 run 摘要 · 跳转完整日志按钮 V-9 · 路由表 web/src/router/index.js // 在现有 routes 数组追加 { path: '/devloop/:pid' , name: 'DevLoop' , component: () => import ( '@/views/DevLoopDashboard.vue' ), meta: { requiresAuth: true } , } , { path: '/devloop' , // 项目列表 name: 'DevLoopList' , component: () => import ( '@/views/DevLoopList.vue' ), } DevLoopList.vue 在 P4 是可选的,先用 GET /api/devloop/projects 列出。 V-10 · i18n key 清单 web/src/i18n/{zh-CN,en-US,...}/devloop.json // 每个 locale 一份扁平 JSON:Record<string, string> { \"devloop.loop.l1\" : \"...\" , \"devloop.state.pending\" : \"...\" , \"devloop.btn.newRequirement\" : \"...\" // ...完整 15 个 key 见下表 } key zh-CN en-US devloop.loop.l1 单元循环 Unit Loop devloop.loop.l2 集成循环 Integration Loop devloop.loop.l3 人类反馈 Human Feedback devloop.state.pending 待处理 Pending devloop.state.coding 编码中 Coding devloop.state.testing 测试中 Testing devloop.state.failed 失败 Failed devloop.state.passed 已通过 Passed devloop.state.blocked 阻塞 Blocked devloop.btn.newRequirement + 新增需求 + New Requirement devloop.btn.signOff 验收 Sign-off devloop.btn.abandon 放弃项目 Abandon devloop.toast.shipped 项目已交付! Project shipped! devloop.toast.needHelp 需要人工介入 Need human help devloop.empty 尚无项目 No projects yet 中英文必填,其他 4 种语言可走 fallback。 V-11 · 设计 token(CSS 变量) web/src/styles/devloop.css :root { --L1: #0E8C5A ; --L1-tint: #E2F2EA ; --L2: #C77C2C ; --L2-tint: #F7E8D2 ; --L3: #6E48C3 ; --L3-tint: #ECE4FB ; --alert: #C7341A ; } // 与 Lemo"},{"f":"ui-dashboard.html","title":"DevLoop Dashboard","h1":"DevLoop Dashboard","headings":["Wireframe","四个核心区域","① Header","② Loop 状态栏","③ Units DAG","④ Live 流 + PRD diff","组件树","路由 + i18n","性能考虑"],"defs":[],"content":"Wireframe localhost:5173/devloop/p-731042 四个核心区域 ① Header 项目名 + 「+ NEW REQUIREMENT」按钮(L3 入口)。点开弹 textarea,提交后走 POST .../requirement 。 ② Loop 状态栏 三张并排卡片显示 L1 / L2 / L3 当前状态。点击任意一张展开该 Loop 的详细历史时间线。 ③ Units DAG 复用 PipelineEditor.vue 的 SVG DAG 渲染。每个节点点击展开「该 Unit 的最近 3 次 run」抽屉。 ④ Live 流 + PRD diff 左:终端样式的 Agent stdout 流(SSE)。右:当前 PRD 与上一 rev 的 section 级 diff。 组件树 // web/src/views/DevLoopDashboard.vue <template> <PageLayout> <DashboardHeader :project= \"project\" @feedback= \"onFeedback\" /> <LoopStatusBar :l1= \"l1Stats\" :l2= \"l2Stats\" :l3= \"l3Stats\" /> <UnitsDAG :units= \"units\" @select= \"openUnitDrawer\" /> <LiveAgentStream :stream= \"sse\" /> <PrdDiffPanel :prd= \"prdHtml\" :history= \"prdHistory\" /> <UnitDrawer v-if= \"drawer\" :unit= \"drawer\" @close= \"drawer=null\" /> <FeedbackModal v-if= \"modalOpen\" @submit= \"sendRequirement\" /> </PageLayout> </template> 总行数预估 ~400,含模板 + script + 样式。其中 UnitsDAG 大约 120 行——这里有最高的复用率(参考 PipelineEditor)。 路由 + i18n 路由: { path: '/devloop/:pid', name: 'DevLoop', component: DevLoopDashboard } 加进 web/src/router/index.js 。 i18n: 新增 key 前缀 devloop.* 。涉及的标签: loop.l1 · loop.l2 · loop.l3 · state.passed · state.testing · btn.newRequirement 等 ~20 个。中英文必填,其他 4 种语言可走 fallback。 性能考虑 SSE: 使用浏览器原生 EventSource ,断线自动重连。 大 stream: LiveAgentStream 保留最近 500 行,老的丢;用户想看完整日志走「open in new tab」按钮跳到 /stream.log 。 PRD diff: 使用客户端 diff-dom ,不在后端计算(项目 PRD 一般 < 50KB,浏览器算得动)。 DAG 大于 50 Unit: 切换到 d3-force 自动布局,否则手动栅格。"},{"f":"ui-design-system.html","title":"视觉设计语言","h1":"视觉设计语言","headings":["色板","Loop 三主色","L1 · Unit","L2 · Integration","L3 · Human","语义辅助色","Alert","Warn","Ink","Paper","字体阶梯","字体使用规则","排版规则","纸面节奏","表格","代码块","组件目录","留白与节奏"],"defs":[],"content":"色板 Loop 三主色 L1 · Unit #0E8C5A · emerald tint: #E2F2EA L2 · Integration #C77C2C · amber tint: #F7E8D2 L3 · Human #6E48C3 · violet tint: #ECE4FB 语义辅助色 Alert #C7341A · 错误 / failure / blocked Warn #8a6b00 · testing / retry Ink #0E0E10 · 文字 / 边框 Paper #FAF7F1 · 背景 字体阶梯 角色 字体 字号 用途 Display Fraunces 300 34–56 px 页面 Hero / 项目名 H2 Fraunces 500 24–34 px 章节标题 H3 Fraunces 600 20 px 子标题 H4 / Tag Inter 700 · uppercase 11–13 px 类别标签 Body Inter 400 16 px 正文 Mono JetBrains Mono 500 11–13 px 状态码 / API / 日志 / kbd 字体使用规则 所有 装饰性 / 表意性 的文字(标题、品牌名、Hero)→ Fraunces。 所有 运行时数据 / 技术标识 (状态、API、命令、Unit id)→ JetBrains Mono。两者并用能立刻区分「这是设计」vs「这是状态」。 其他 → Inter。 排版规则 纸面节奏 章节顶部用 h4 全大写小标签 (类似 Section 01) 作为定位锚。 正文段落首字 dropcap 仅在长篇章节用,强调起势。 表格 表头 paper-soft 背景 + Mono uppercase;行 hover 高亮;行内 code 用米色背景区分纯文本。 代码块 黑底 #0E0E10 + 米色 #F3EFE5 文字。彩色 token:橙 (k)、绿 (s)、蓝 (n)、黄 (p)。终端感不夸张。 组件目录 本 PRD HTML 文档中复用的核心组件,全部 token 化、可在 LemonBoss 前端 Dashboard 中复刻: 组件 CSS class 变体 Tag pill .tag L1 / L2 / L3 / NEW / REUSE / PATCH Card .card .l1 / .l2 / .l3 边色 Callout .callout 默认(红) / .warn(黄) / .ok(绿) Code block pre.code 含 .k .s .n .p .v token File tree pre.tree 含 .new .reuse .com Tabs .tabs data-tabs 自动初始化 Accordion details.acc 原生 details + 自定义 marker Mock browser .mock 含 .topbar + .url Sidebar aside.sidebar Section group 折叠 留白与节奏 页面布局基于 8px 网格。常用间距: token px 用途 xs 8 tag 内距 sm 16 段间距 md 24 卡片内距 lg 40 section 间距 xl 64 页边 xxl 80 Hero 上下"}]} | ||
| 3 | \ No newline at end of file | 3 | \ No newline at end of file |
docs/prd/newfunction/plan-migration.html
| @@ -20,15 +20,58 @@ | @@ -20,15 +20,58 @@ | ||
| 20 | <article class="body"> | 20 | <article class="body"> |
| 21 | 21 | ||
| 22 | <section> | 22 | <section> |
| 23 | + <h2>第一天 Quick-Start · 从零起步</h2> | ||
| 24 | + <p>「我装好仓库 cd 进去,第一行命令该是什么」—— 答案:</p> | ||
| 25 | +<pre class="code"><span class="c"># 1. 拉仓库 + 装依赖</span> | ||
| 26 | +git clone gitlab-wuwei.hexpm.com:yibo/lemonboss.git | ||
| 27 | +cd lemonboss | ||
| 28 | +pnpm install <span class="c"># LemonBoss 用 pnpm workspace</span> | ||
| 29 | + | ||
| 30 | +<span class="c"># 2. 配 .env</span> | ||
| 31 | +cp .env.example .env | ||
| 32 | +<span class="c"># 编辑设置 PORT=3010 等;v1 不需要 OPEN_API_KEYS</span> | ||
| 33 | + | ||
| 34 | +<span class="c"># 3. 浏览本 PRD (推荐先扫一遍 index → reading-guide)</span> | ||
| 35 | +open docs/prd/newfunction/index.html <span class="c"># macOS</span> | ||
| 36 | +<span class="c"># 或: python3 -m http.server -d docs/prd/newfunction 8000 → open localhost:8000</span> | ||
| 37 | + | ||
| 38 | +<span class="c"># 4. 验证文档侧 tooling 已经就绪</span> | ||
| 39 | +node scripts/extract-manifest.js <span class="c"># 期望: 54 MVCP defs 100%</span> | ||
| 40 | +node scripts/build-search-index.js <span class="c"># 期望: 51 pages 195KB</span> | ||
| 41 | + | ||
| 42 | +<span class="c"># 5. 从 P0.0 开始动代码 (按 §五阶段总览 P0 表格顺序)</span> | ||
| 43 | +mkdir -p server/services | ||
| 44 | +<span class="c"># 第一个文件: server/services/path-resolver.js (所有人都依赖它)</span> | ||
| 45 | +<span class="c"># 第二个文件: server/services/doc-store.js</span> | ||
| 46 | +<span class="c"># ...按 §P0 子任务速览 P0.0 行的服务清单顺次实现</span> | ||
| 47 | + | ||
| 48 | +<span class="c"># 6. P0.B 启用 Mock Claude CLI 模式 (一旦工具骨架在)</span> | ||
| 49 | +<span class="c"># 详见 arch-acp-bridge.html · Mock Mode 章节</span> | ||
| 50 | +MOCK_CLAUDE=1 node scripts/phase0-verify.js <span class="c"># 工具流跑通,不烧 token</span> | ||
| 51 | + | ||
| 52 | +<span class="c"># 7. 真跑 P0 验证 (P0.J 完成后)</span> | ||
| 53 | +node scripts/phase0-verify.js <span class="c"># ✓ All 79 tools + 1 mock PM call</span> | ||
| 54 | +node scripts/phase0-test-call.js <span class="c"># ✓ todolist 落盘 + SSE 触发</span> | ||
| 55 | + | ||
| 56 | +<span class="c"># 8. 进 P1: 准备启动真实 Agent</span> | ||
| 57 | +PORT=3010 node server/index.js & <span class="c"># server 起来</span> | ||
| 58 | +node scripts/devloop-new.js \ | ||
| 59 | + --name <span class="s">"Auto TODO"</span> \ | ||
| 60 | + --requirement <span class="s">"做一个 TODO API + Vue 前端"</span> <span class="c"># sandbox 模式</span> | ||
| 61 | +<span class="c"># 期望: 30-60s 内 data/.../projects/p-XXXXXX/prd/ 完整生成</span></pre> | ||
| 62 | + <p class="sig">第一天能 100% 跑通到第 4 步(文档侧验证);P0.0 实施开始后第 5 步以下才有意义。</p> | ||
| 63 | + </section> | ||
| 64 | + | ||
| 65 | + <section> | ||
| 23 | <h2>五阶段总览</h2> | 66 | <h2>五阶段总览</h2> |
| 24 | <table> | 67 | <table> |
| 25 | <thead><tr><th>阶段</th><th>目标</th><th>新增 / 修改</th><th>可演示场景</th></tr></thead> | 68 | <thead><tr><th>阶段</th><th>目标</th><th>新增 / 修改</th><th>可演示场景</th></tr></thead> |
| 26 | <tbody> | 69 | <tbody> |
| 27 | <tr> | 70 | <tr> |
| 28 | - <td><b>P0 · Bootstrap</b><br/><span class="sig">3 周</span></td> | ||
| 29 | - <td>79 工具实现 + ACP 桥接 + 系统提示提取 + <b>GitVault 集成</b>(每项目 git repo + Agent 完工自动 commit)</td> | ||
| 30 | - <td><a href="arch-tool-registry.html">tool-registry</a>(34 工具)、<a href="arch-acp-bridge.html">acp-bridge</a>、<a href="arch-git-versioning.html">git-versioning</a>(GitVault + commit hooks)、scripts/extract-agent-prompts.js、scripts/phase0-verify.js</td> | ||
| 31 | - <td>scripts/phase0-verify.js 通过 + mock PM 调用 → todolist 落盘 + .git 初始化 + 1 个 prd commit + tag prd-rev-1</td> | 71 | + <td><b>P0 · Bootstrap</b><br/><span class="sig">3.5 周</span></td> |
| 72 | + <td><b>服务层 7 件套</b>(pathResolver/docStore/unitRegistry/bugTracker/timelineLogger/sseBroker/GitVault)+ 79 工具实现 + Mock Claude CLI 模式 + ACP 桥接 + 系统提示提取 + git 集成</td> | ||
| 73 | + <td>7 个 <code>server/services/*.js</code>、<a href="arch-tool-registry.html">tool-registry</a>(34 工具)、<a href="arch-acp-bridge.html">acp-bridge</a> + Mock 模式、<a href="arch-git-versioning.html">git-versioning</a>、scripts/extract-agent-prompts.js、scripts/phase0-verify.js</td> | ||
| 74 | + <td><code>MOCK_CLAUDE=1 phase0-verify</code> 通过 → 真跑 phase0-verify 通过 → 一个 mock PM call 落 todolist + .git 初始化 + 首个 prd commit</td> | ||
| 32 | </tr> | 75 | </tr> |
| 33 | <tr> | 76 | <tr> |
| 34 | <td><b>P1 · Docs MVP</b></td> | 77 | <td><b>P1 · Docs MVP</b></td> |
| @@ -68,7 +111,22 @@ | @@ -68,7 +111,22 @@ | ||
| 68 | <table class="dense"> | 111 | <table class="dense"> |
| 69 | <thead><tr><th>子阶段</th><th>内容</th><th>工时</th><th>难度</th></tr></thead> | 112 | <thead><tr><th>子阶段</th><th>内容</th><th>工时</th><th>难度</th></tr></thead> |
| 70 | <tbody> | 113 | <tbody> |
| 71 | - <tr><td>P0.A 工具实现</td><td><code>server/src/tools/devloop/</code> 下 7 个文件,34 个独立函数 (~1700 行)</td><td>5 天</td><td>中(cheerio 多)</td></tr> | 114 | + <tr><td><b>P0.0 服务层 7 件套</b><br/><span class="sig">所有其他都依赖它</span></td> |
| 115 | + <td> | ||
| 116 | + <code>server/services/</code> 顺次实现:<br/> | ||
| 117 | + ① <code>path-resolver.js</code>(~80 行;<b>第一个写</b>,其余都依赖它)<br/> | ||
| 118 | + ② <code>doc-store.js</code>(~140 行;prd/sdd/units IO + cheerio 校验)<br/> | ||
| 119 | + ③ <code>unit-registry.js</code>(~120 行;Unit FSM + l2GateOpen)<br/> | ||
| 120 | + ④ <code>bug-tracker.js</code>(~110 行;bugs.ndjson CRUD + dedupKey)<br/> | ||
| 121 | + ⑤ <code>timeline-logger.js</code> + <code>changelog-writer.js</code>(~80 行合计)<br/> | ||
| 122 | + ⑥ <code>sse-broker.js</code>(~80 行;订阅 + 心跳 + replay)<br/> | ||
| 123 | + ⑦ <code>git-vault.js</code>(~150 行;spawn git wrapper + per-pid mutex)<br/> | ||
| 124 | + </td> | ||
| 125 | + <td>4.5 天</td><td>低中(独立实现 · 每个文件可单测)</td></tr> | ||
| 126 | + <tr><td>P0.A 工具实现<br/><span class="sig">需 P0.0 完成</span></td><td><code>server/src/tools/devloop/</code> 下 7 个文件,34 个独立函数 (~1700 行)。每个工具 wrapper 通过 ctx 引用 P0.0 的服务</td><td>5 天</td><td>中(cheerio 多)</td></tr> | ||
| 127 | + <tr><td><b>P0.A2 Mock Claude CLI 模式</b><br/><span class="sig">开发期省 token</span></td> | ||
| 128 | + <td>给 <code>ClaudeCliProtocol</code> 加 <code>MOCK_CLAUDE=1</code> 分支,读 <code>system/mock-responses/{agent}/*.json</code> 当 stub 回复。详见 <a href="arch-acp-bridge.html">acp-bridge · Mock Mode</a></td> | ||
| 129 | + <td>0.5 天</td><td>低</td></tr> | ||
| 72 | <tr><td>P0.B 提示提取</td><td><code>scripts/extract-agent-prompts.js</code> 从 6 个 agent HTML §2.2 抽 prompt</td><td>0.5 天</td><td>低(正则)</td></tr> | 130 | <tr><td>P0.B 提示提取</td><td><code>scripts/extract-agent-prompts.js</code> 从 6 个 agent HTML §2.2 抽 prompt</td><td>0.5 天</td><td>低(正则)</td></tr> |
| 73 | <tr><td>P0.C 模板注册</td><td><code>system/templates/_registry.json</code> 加 6 条 devloop-* + toolPolicy</td><td>0.5 天</td><td>低</td></tr> | 131 | <tr><td>P0.C 模板注册</td><td><code>system/templates/_registry.json</code> 加 6 条 devloop-* + toolPolicy</td><td>0.5 天</td><td>低</td></tr> |
| 74 | <tr><td>P0.D CLI tools 注入</td><td><code>packages/acp-core/protocol/ClaudeCliProtocol.js</code> 改 ~10 行,按 toolPolicy 生成 --tools json</td><td>1 天</td><td>中</td></tr> | 132 | <tr><td>P0.D CLI tools 注入</td><td><code>packages/acp-core/protocol/ClaudeCliProtocol.js</code> 改 ~10 行,按 toolPolicy 生成 --tools json</td><td>1 天</td><td>中</td></tr> |
| @@ -76,10 +134,9 @@ | @@ -76,10 +134,9 @@ | ||
| 76 | <tr><td>P0.F jsonGuard 挂 Bash</td><td>Bash 工具 preExecute 钩子拦截 inline JSON</td><td>0.5 天</td><td>低</td></tr> | 134 | <tr><td>P0.F jsonGuard 挂 Bash</td><td>Bash 工具 preExecute 钩子拦截 inline JSON</td><td>0.5 天</td><td>低</td></tr> |
| 77 | <tr><td>P0.G 验证脚本</td><td><code>scripts/phase0-verify.js</code> + <code>phase0-test-call.js</code></td><td>1 天</td><td>低</td></tr> | 135 | <tr><td>P0.G 验证脚本</td><td><code>scripts/phase0-verify.js</code> + <code>phase0-test-call.js</code></td><td>1 天</td><td>低</td></tr> |
| 78 | <tr><td>P0.H 联调</td><td>跑一个 mock PM 调用 → 看 todolist 落盘 + SSE 触发</td><td>2 天</td><td>—</td></tr> | 136 | <tr><td>P0.H 联调</td><td>跑一个 mock PM 调用 → 看 todolist 落盘 + SSE 触发</td><td>2 天</td><td>—</td></tr> |
| 79 | - <tr><td>P0.I GitVault 实现</td><td><code>server/services/git-vault.js</code> 约 150 行 spawn git wrapper(init / commit / tag / log / diff / archive)+ per-pid mutex</td><td>1 天</td><td>低</td></tr> | ||
| 80 | - <tr><td>P0.J git 集成到 LoopController</td><td>createProject 时 git init + .gitignore 模板;callAgentWithPersistence success 分支加 commitAfterAgent;recoverOnBoot 加 git status 检查</td><td>1 天</td><td>中</td></tr> | ||
| 81 | - <tr><td>P0.K git 验证</td><td>phase0-verify 加:项目初始化后 .git 存在 + 一次 mock commit + tag · listTags</td><td>0.5 天</td><td>低</td></tr> | ||
| 82 | - <tr><td><b>P0 合计</b></td><td>—</td><td><b>13.5 天 ≈ 3 周</b></td><td>—</td></tr> | 137 | + <tr><td>P0.I git 集成到 LoopController</td><td>createProject 时 git init + .gitignore 模板;callAgentWithPersistence success 分支加 commitAfterAgent;recoverOnBoot 加 git status 检查(GitVault 已在 P0.0 ⑦ 落盘)</td><td>1 天</td><td>中</td></tr> |
| 138 | + <tr><td>P0.J git 验证</td><td>phase0-verify 加:项目初始化后 .git 存在 + 一次 mock commit + tag · listTags</td><td>0.5 天</td><td>低</td></tr> | ||
| 139 | + <tr><td><b>P0 合计</b></td><td>—</td><td><b>17 天 ≈ 3.5 周</b></td><td>—</td></tr> | ||
| 83 | </tbody> | 140 | </tbody> |
| 84 | </table> | 141 | </table> |
| 85 | 142 | ||
| @@ -313,13 +370,13 @@ server/routes/devloop.js <span class="com"># 加 C-6 POST /:pid/sign | @@ -313,13 +370,13 @@ server/routes/devloop.js <span class="com"># 加 C-6 POST /:pid/sign | ||
| 313 | <p class="sig">较早前版本(20 文件 / ~1580 行)低估了:① Agent 模板单文件实际 prompt 长度(PM/Architect 当前 PRD 已揭示需 200-300 行 prompt + 数个 few-shot);② Dashboard 子组件粒度(DAG / 抽屉 / diff 渲染单组件就 100+ 行);③ Ops 4 个子页此前未计入。</p> | 370 | <p class="sig">较早前版本(20 文件 / ~1580 行)低估了:① Agent 模板单文件实际 prompt 长度(PM/Architect 当前 PRD 已揭示需 200-300 行 prompt + 数个 few-shot);② Dashboard 子组件粒度(DAG / 抽屉 / diff 渲染单组件就 100+ 行);③ Ops 4 个子页此前未计入。</p> |
| 314 | 371 | ||
| 315 | <h3>时间预算</h3> | 372 | <h3>时间预算</h3> |
| 316 | - <p>单人 Claude Code 协同开发:<b>P0(13.5 天)</b> → P1(5 天)→ P2(8 天)→ P3(18 天)→ P4(15 天)= 约 <b>10 周</b>。每阶段末 1 天用于演示 + 写迭代 PR 描述。</p> | 373 | + <p>单人 Claude Code 协同开发:<b>P0(17 天)</b> → P1(5 天)→ P2(8 天)→ P3(18 天)→ P4(15 天)= 约 <b>10.5 周</b>。每阶段末 1 天用于演示 + 写迭代 PR 描述。</p> |
| 317 | <ul> | 374 | <ul> |
| 318 | - <li><b>P0(13.5 天)</b>:79 工具实现 + ACP 桥接 + 提示提取 + <b>GitVault</b> + 验证脚本,是 P1 的前置(详见上方 P0 表格 + <a href="arch-tool-registry.html">tool-registry</a> + <a href="arch-git-versioning.html">git-versioning</a>)</li> | ||
| 319 | - <li><b>P3 +3 天</b>:增加 4 类 L2 分支演示(contract_mismatch / impl_bug / spec_ambiguous / infra_flake)+ 可观测性产物(bugs/changelog/timeline.ndjson)+ bug-tracker 服务</li> | 375 | + <li><b>P0(17 天)</b>:<b>P0.0 服务层 7 件套</b>(4.5 天,<b>第一个写</b>)+ 工具 + Mock Claude + ACP 桥接 + 提示提取 + GitVault 集成 + 验证脚本。详见上方 P0 表 + <a href="arch-tool-registry.html">tool-registry</a> + <a href="arch-acp-bridge.html">acp-bridge</a> + <a href="arch-git-versioning.html">git-versioning</a></li> |
| 376 | + <li><b>P3 +3 天</b>:增加 4 类 L2 分支演示(contract_mismatch / impl_bug / spec_ambiguous / infra_flake)+ 可观测性产物(bugs/changelog/timeline.ndjson)</li> | ||
| 320 | <li><b>P4 +5 天</b>:Ops 4 个子页(Bugs / Loops / Changelog / Progress)+ sign-off / abandon 端点联调</li> | 377 | <li><b>P4 +5 天</b>:Ops 4 个子页(Bugs / Loops / Changelog / Progress)+ sign-off / abandon 端点联调</li> |
| 321 | </ul> | 378 | </ul> |
| 322 | - <p class="sig">原估 4 周 → 6 周 → 7 周 → 9.5 周 → <b>10 周(含 GitVault)</b>。每次都是补缺,不是膨胀。参考 <a href="plan-cost-concurrency.html">成本预算</a>。</p> | 379 | + <p class="sig">原估 4 周 → 6 周 → 7 周 → 9.5 周 → 10 周 → <b>10.5 周(含 P0.0 服务层 + Mock 模式)</b>。每次都是补缺,不是膨胀。参考 <a href="plan-cost-concurrency.html">成本预算</a>。</p> |
| 323 | </section> | 380 | </section> |
| 324 | 381 | ||
| 325 | <section> | 382 | <section> |