# graphlemon 构建报告 **六个 Phase,45 个 Unit,一套真正跑起来的"熄灯"软件工厂。** graphlemon 是一个 Level 5 自主研发系统:固定的 Planner → Coder → Validator Org Graph,驱动每个任务 动态生成的 Work Graph,完全跑在 Claude Code 自己的 `claude -p` 后端之上——能写代码、能抓自己的回归, 到本次会话结束时,还能在无人读 diff 的情况下重新部署自己正在运行的进程。 - **状态**:PH0~PH5 全部完成,45 / 45 个 Unit - **测试**:552 个测试全绿 - **Git**:`fc94ae8`(初始基线)→ `59e96d3`(U-043,当前 tip) - **数据模型**:17 张 sqlite 表 - **Unit 分类**:Risk Gate 18 / Value Path 17 / Enabler 10 - **过程中"停下来问用户"的关键决策**:3 次 --- ## Phase 链路 ``` PH0(9 units,Enabler)→ PH1(6 units,MVP 闭环)→ PH2(7 units,质量闭环) → PH3(10 units,控制台)→ PH4(3 units,全量回归收口)→ PH5(10 units,自举能力) ``` --- ## PH0:脚手架与第一个高风险假设 **Unit**:U-001 ~ U-008、U-004.5 15 张核心表的数据模型、Hermes 人设迁移到 `.claude/agents/*.md`、一套受 `RUN_REAL_AGENT_E2E` 环境变量 门控的真实 API 测试 harness,以及本地后端/前端的骨架——后面一切工作的地基。 > **假设验证——不是"干净通过"**:A-2 假设在这里被真正验证:Planner/Validator 的人设几乎可以原样迁移, > 但 Coder 的 `SOUL.md` 不行——里面硬编码了一个真实的飞书 Base API token、整段"认领"机制强绑定 > `lark-cli`、还指示自己再跑一次 `claude --dangerously-skip-permissions`,三处都必须删除或重写才能安全 > 运行。 --- ## PH1:第一条真正跑通的闭环 **Unit**:U-009 ~ U-014 用"实现贪吃蛇游戏"这个目标真实跑通全链路:Planner 产出 BRD→PRD→SDD→Benchmark,Coder 实现并自测, Validator 复核并给出根因归属——架构文档 §11 验收标准第 1-3 条,是真跑出来的,不是模拟的。 > **假设确认**:A-1 假设在这里被确认——一次真实花费 $0.46 的 spike 证明了 Claude Code 的动态工作流原语 > (`agent()`/`parallel()`/`pipeline()`)可以通过 `claude -p` 无交互调用,并返回可解析的结构化结果。 > **平台层怪癖(实测发现)**:`--json-schema` 一旦和 `--agent` 同时传,会被静默忽略——模型退化成把结构化 > 内容塞进自由文本里的一个 ```` ```json ```` 围栏代码块,而不是填充 `structured_output`。官方文档没有 > 说明,是靠真实调用发现的。 --- ## PH2:教会它从自己的错误里恢复 **Unit**:U-015 ~ U-021 三视角根因判定、Org-level Loop 的 `fast_lane`/`full_replan` 路由、双层预算护栏、Claude plan 额度耗尽的 等待重试机制、Graph Engine 断点恢复。人为植入一个 bug,从一端进去,从另一端出来时已经是一次真实的修复、 重新验证、任务关闭。 --- ## PH3:一个不需要构建步骤的控制台 **Unit**:U-022 ~ U-030、U-024.5 项目创建、目标输入、实时仪表盘、任务看板与详情页、DAG 图可视化、实时日志查看器、Artifact/Skill 浏览器、 配置页——纯原生 HTML/CSS/JS,配一个手写的 WebSocket 服务端,因为"纯 stdlib、无第三方依赖、无构建步骤" 这条约束对 graphlemon 自己的控制台同样适用。 > **真实 bug**:HTTP 服务端各请求线程和 WebSocket 轮询线程共享同一个 sqlite 连接时,如果关闭连接没有 > 持有和读写操作同一把锁,会**直接把解释器段错误崩溃**——"关闭"看起来像一次性操作,但对同一个连接对象 > 而言并不天然安全。修复后连续压测多次全部稳定。 > **后期补漏**:一次收口审计中发现,WebSocket 重连后任务看板和任务详情页从未真正重新拉取过状态快照—— > 只有仪表盘做出反应,而且也只是切换了一个提示条。现在三处重连后都会真正刷新,符合 REQ-12 自己的边界 > 用例要求。 --- ## PH4:两轮审计,十个真实缺口 **Unit**:U-031 ~ U-033 把 06 节 REQ-01~REQ-14 的全部验收用例,逐条对照实际测试覆盖重新核对了两遍——而不是相信"单测全绿"就 等于"验收要求真的被覆盖了"。 > **结构性缺口**:Work Graph 的三个核心控制器(规划、执行、单节点运行)各自都做过完整单测,但**从来没有 > 在生产代码里被真正串联调用过**——唯一验证过的闭环(PH1 的贪吃蛇场景)悄悄绕开了它们,走了一条更简单 > 的并行路径。补上这层编排后,真实 API 调用又暴露出两个新 bug(一个 manifest 字段名不匹配、一个缺失的 > 工作目录),都是只有真实调用才会暴露的问题。 > **第二轮审计**:PH5 开工前对验收用例的又一次逐条复核,发现了 5 个新的真实缺口:一处遗漏的中间状态写入、 > 一个从未真正测过的调度器场景、三条"节点被标记 blocked 但从未告知父任务"的失败路径、一个原本完全不存在 > 的 PRD 一致性检查、外加一处偏弱的测试断言。其中一次修复(新增的一致性检查调用)当场抓到模型把 > `consistent` 字段返回成 JSON **字符串** `"false"`,而 Python 的真值判断把它当成了 `True`。 > **假设正式收口**:A-2 假设在这里正式收口,结论仍是"部分成立"——PH0 之后又有四个不同 Unit 里真实调用 > 过迁移后的人设,没有再暴露新的偏差。但结论不会因此升级为"confirmed":那次重写已经发生了,后面跑得再 > 干净也不能把它抹掉。 --- ## PH5:教会它重新部署自己 **Unit**:U-034 ~ U-043 一整套蓝绿自部署机制:在独立的 git worktree 里拉起候选实例、跑三项 cutover 前健康检查、切换生产流量、 在监控窗口内采样四类信号、必要时回滚——包括"回滚本身也失败"这条兜底路径。 > **SDD 内部冲突**:设计文档和正式规范对"到底什么条件会触发一次自部署"有分歧——设计文档说"self-target > 改动通过验收即可",规范原文却把它挂在一条已存在的 `core_engine` bug 记录上——而一次干净的自我改进任务 > 根本不会产生这条记录。没有挑一种理解悄悄往下走,而是直接问了用户。 > **真实 bug**:用 `kill(pid, 0)` 做存活检测分不清"真的在跑"和"已经退出但从未被父进程回收"——一个僵尸 > 进程能让这个检测正常通过。一个真实(未 mock)的、真的等待进程消失的测试因此一直挂起,直到补上正确的 > 进程回收逻辑才修好。 > **其他发现**:缺一个跨重启追踪候选实例 checkout 目录的字段、后端服务完全没有命令行入口、参数表里唯独 > 缺一个固定的生产端口、也没有 `/api/health` 端点给健康检查调用。 --- ## ⚠️ 尚未真实执行的一项 U-042 的验收测试——对本仓库执行一次真实的自举 cutover,真实调用 `claude`、真实替换正在运行的进程——已经 建好并且在 mock 下全部通过,但被一个刻意设置得更严格、和项目里其他真实测试用的环境变量**不同**的开关 (`RUN_REAL_SELF_HOSTING_E2E=1`)挡住,因为这是整个代码库里爆炸半径最大的一个动作:它能替换掉正在运行 的 graphlemon 进程本身,还会在这个真实仓库上执行 git revert。 **这项测试从未被真正执行过。** 是否要真的跑一次,是需要用户决定的事,不是构建过程自己能替你做的决定。 --- ## 假设台账 | ID | 假设 | 结论 | |---|---|---| | A-1 | Claude Code 的动态工作流原语可以通过 `claude -p` 无交互调用,并返回可解析的结构化结果 | **confirmed** | | A-2 | 本地 Hermes 人设文件(`SOUL.md`/`AGENTS.md`)可以原样迁移到 `.claude/agents/*.md`,无需大改 | **部分成立**(已收口) | --- *graphlemon · 纯 stdlib Python 实现,无任何第三方依赖 · 552 个测试 · 17 张表 · `fc94ae8` → `59e96d3`*