# graphlemon 开发计划 **版本**: v1.0 **日期**: 2026-07-29 **输入来源**: `graphlemon_architecture.md`(v0.11)+ `docs/sdd/`(00-06 + `sdd-mvcp-contract.json` v1.6.0) **方法论**: 复用本地 Hermes `planning` profile 的 `project-unit-decomposition`(Development Unit 分解 + 依赖 DAG)+ `engineering-work-planning`(Phase/Sub-phase/Unit 三层分类:Epic 级/Feature 级/Task 级,垂直切片优先于技术分层) ## 0. 怎么读这份文档 - **Phase**(大阶段,对应 Epic 级):有自己的验收标准和"完成"定义,多个 Sub-phase 组成。 - **Sub-phase**(小阶段,对应 Feature 级):一个可独立验收的能力切片,多个 Unit 组成。 - **Unit**(子任务,对应 Task 级,即 Development Unit):一个明确的工程产物 + 完成标准,映射到至少一条 SDD 条目(M/V/C/P/REQ),标注风险分类: - **[Value Path]** 主线价值切片,直接对应用户/系统可感知的能力 - **[Risk Gate]** 高风险校验/回路/护栏,不做会在生产阶段暴露风险 - **[Enabler]** 使能型基础设施,本身不直接产出用户可见价值,但阻塞其他 Unit - 每个 Unit 标注 `依赖`(DAG 上游)和 `Done 标准`(可观察、可验证,优先复用 06-验收与测试锚点.md 已有的 REQ 用例,不重新发明验收口径)。 - 机器可读版本见 `docs/sdd/unit_matrix.json`(Unit ID、依赖、REQ/M/V/C/P 覆盖、风险分类,供后续 Coder Agent 或调度器直接消费)。 **关于阶段排序的说明**:Phase 0 是纯 Enabler(无独立用户价值),Phase 1 起才是垂直切片(贪吃蛇场景端到端跑通)。这不违反"避免按技术分层切故事"的原则——Phase 0 内部的确按技术层(数据模型→后端集成→控制台骨架)排列,但这是因为它们是共同阻塞后续所有价值切片的地基工作,不是把一个本该完整的价值切片强行拆成"先做数据库、再做 API、再做前端"三个假装独立的"Phase"。真正的价值切片从 Phase 1 开始,一路贯穿 Org Graph→Work Graph→端到端验收,不按技术层拆分。 ## 1. Phase 总览 | Phase | 目标 | 类型 | 验收锚点 | |---|---|---|---| | PH0 | 基础设施与脚手架就绪 | Enabler | 能创建目标项目、Git 仓库结构符合约定、15 个实体可建表 | | PH1 | MVP 全链路闭环跑通(贪吃蛇场景) | Value Path | 架构文档 §11 验收标准第 1-3 条;REQ-01/02/03/07 | | PH2 | 质量闭环:回路、Skill 迭代、护栏、断点恢复 | Risk Gate | 架构文档 §11 验收标准第 4-5 条;REQ-04/05/06/08/10/14 | | PH3 | 人机交互控制台 | Value Path | REQ-11/12/13 | | PH4 | 全量验收与固化 | Risk Gate | 06 节全部 14 条 REQ 完整回归通过一次 | | PH5(2026-07-30 新增) | 自举能力:Deploy Supervisor + 蓝绿部署自动回滚 | Risk Gate | `docs/supervisor-auto-rollback-design.md` 状态机全部路径(health_check_failed / rolled_back / rollback_failed / finalized)至少各复现一次;`self-development-feasibility.md` 9 步走查场景真实跑通一次 | 依赖顺序:PH0 → PH1 → PH2,PH3 可在 PH1 完成后与 PH2 并行(控制台是对已产生数据的可视化层,不阻塞回路/护栏逻辑本身的开发),PH4 收口需要 PH1/PH2/PH3 全部完成。**PH5 依赖 PH4 收口**(自我修改的安全网必须建立在"系统本身已通过全量验收、行为可信"的前提上,对已知不稳定的系统做自动回滚没有意义),且 PH5 不阻塞、不改变 PH0-PH4 的开发方式——PH0-PH4 期间 graphlemon 自身仍是由人类 + Claude Code 交互式开发(这是任何自举系统的必然前提,见 `self-development-feasibility.md` §1),PH5 只在"PH0-PH4 都跑通之后,graphlemon 开始被允许对自己的代码做自主修改"这个新增能力上生效。 --- ## PH0:基础设施与脚手架(Enabler) **Goal**:为后续所有开发建立可靠地基,本身不产出用户可见能力。 **Phase 验收**:给定一个空 git 仓库路径,能自动初始化出完整的 graphlemon 目录骨架;15 个数据实体全部建表成功并通过约束校验;`claude --agent planner` 之类的 subagent 调用能跑通最简单的一次往返。 ### PH0-S1 数据模型与仓库骨架 可与 PH0-S3 并行。 - **U-001 [Enabler]** 落地 01-M 全部 15 个实体的物理数据库 schema(MVP 单机场景可用 SQLite),含全部枚举约束、外键、唯一索引 - 依赖:无(起点) - Done:15 张表全部建表成功;`project.target_folder_path` 唯一约束、`work_node_execution.status` 枚举约束等关键约束有单测覆盖 - 覆盖:01-M 全部实体 - 源码位置:graphlemon 仓库根目录 `src/`(见 05 节 §5.7"源码目录约定",与 U-002 在目标项目里生成的 `src/` 是两回事) - **U-002 [Enabler]** 目标项目 Git 仓库目录骨架初始化逻辑:只生成 `.graphlemon/` 子目录(含 `runs//`,架构文档 §13.2——01-M `project.graphlemon_subdir_initialized` 字段本身指向的就是这节,不是 §7.3) - **2026-07-31 订正**:development-plan.md 这行原先误引用了架构文档 §7.3 的旧目录树(`.claude/agents/`、`.claude/agent-memory/`、`.claude/workflows/`、`skills/`、顶层 `runs/`)。但 §13.1("已确认"决策,晚于 §7.3)明确写着这些是 graphlemon **平台自己仓库**的资产("graphlemon 平台自己的仓库只保留平台资产:Org/Work Graph 引擎、Skill 库、Agent 人设...不含任何目标项目的业务代码"),属于 U-003 范围,不是目标项目、不是 U-002。§7.3 是"graphlemon 只服务自己开发自己"阶段(自举模式)的旧设计,已被 §13"目标项目是独立代码空间"这个决策取代。目标项目自己的 `src/` 不需要 graphlemon 创建或规定结构("目标项目自己原有的源码目录,graphlemon 不规定具体结构,尊重目标项目原有约定")。用户确认,2026-07-31。 - 依赖:U-001(`project.graphlemon_subdir_initialized` 字段需要有实体支撑) - Done:给定一个空 git 仓库,能生成 `.graphlemon/` 子目录骨架并产生一次初始化 commit;非 git 仓库时按 02-V 2.1 节的确认态流程走(不静默 `git init`) - 覆盖:01-M `project`;03-C `c_project_create` - **U-003 [Enabler]** graphlemon 平台自身的仓库/运行时目录结构(与目标项目仓库物理分离;本地非 Git 的原始日志目录,见架构文档 §13.2) - 依赖:无 - Done:平台目录和任意目标项目目录可以是完全独立的两个路径,互不干扰;日志目录不纳入平台自身 Git 历史 ### PH0-S2 Claude Code 后端集成 依赖 PH0-S1。这是全项目风险最集中的 Enabler 子阶段,直接决定 A-1(已 confirmed)之后的下一个高风险假设 A-2 能否成立。 - **U-004 [Risk Gate]** Hermes profile 内容迁移到 `.claude/agents/{planner,coder,validator}.md`(对应 SDD 00 节 A-2 假设的实际验证动作) - 依赖:U-002 - Done:三个 subagent 定义文件可被 Claude Code 发现,`claude --agent planner -p "..."` 能正常启动且系统提示词内容正确;`skills:`/`memory: project`/`tools:`/`model:` frontmatter 字段配置完整(见 05-实施约束与依赖.md §5.1) - 覆盖:架构文档 §7.2/§9;05 节 §5.1;A-2 假设 - **注意(2026-07-31 已验证,结果:部分成立)**:Planner/Validator 的 SOUL.md 基本原样迁移;Coder 的 SOUL.md 需要实质性重写(原文件硬编码了一个真实飞书 Base API token、整段认领机制强绑定 `lark-cli`/Base 状态机、且指示自己再跑一次 `claude --dangerously-skip-permissions`,与 05 §5.3.1 的 `auto` 权限模式直接冲突)。真实 `claude --agent planner -p` 调用(`--permission-mode auto`,成本 $0.05)验证通过,返回内容准确复述了迁移后的身份与红线。A-2 记为**部分成立**,按本条预先注册的判定标准处理,不是 confirmed;详见 00-SDD-生成记录.md 假设表和 `agents/{planner,coder,validator}/SOUL.md` 各自文件头的迁移说明。U-005/U-011 沿用现有 `.claude/agents/*.md` 内容即可,不需要因为这条重新做 A-2 spike - **U-004.5(2026-07-30 新增)[Enabler]** 搭建 `RUN_REAL_AGENT_E2E` 门控测试 harness(真实 shell out 到 `claude` 的端到端测试默认跳过,需显式环境变量开启;镜像 `CLAUDE.md` Testing 约束里点名的 graphloop 先例,避免 U-005/U-006/U-010 等后续 Unit 各自发明一套) - 依赖:U-004 - Done:仓库里有一个可复用的 skip 装饰器/测试基类(例如 `skip_unless_env("RUN_REAL_AGENT_E2E")`);日常 `python3 -m unittest` 默认跳过任何真实调用 `claude` 子进程的测试;显式设置该环境变量时,对 U-004 落地的某个 subagent 做一次真实 `-p` 往返调用能跑通 - 覆盖:`CLAUDE.md` Testing 约束 - **U-005 [Enabler]** `c_org_node_trigger` 的 `claude --agent -p` 子进程调用封装(骨架,不含完整状态机) - 依赖:U-004, U-004.5 - Done:手工调用能触发三个 Org Agent 之一并拿到文本响应 - **U-006 [Enabler]** Work Graph 执行层调用封装(workflow 脚本写入 + `claude -p` 调用,直接复用 `spikes/a1-dynamic-workflows/` 已验证的代码路径,见 03-C 3.5 节实现契约) - 依赖:U-004, U-004.5 - Done:封装成可被服务端函数调用的接口,单测通过,`--permission-mode auto` 生效(不是 `--bare`,不是 `bypassPermissions`,见 05 节 §5.3.1) ### PH0-S3 控制台骨架 可与 PH0-S1/S2 并行,无相互依赖。 - **U-007 [Enabler]** 本地后端进程骨架(纯 stdlib Python `http.server`/`socketserver` 实现 HTTP API + WebSocket/SSE,不引入 Flask/FastAPI 等第三方包,见 05 节 §5.7)+ `c_pick_folder` 的 macOS 实现(`osascript`,Windows/Linux 留到 PH3 补齐) - 依赖:无 - Done:后端进程可启停;macOS 上 `/api/system/pick-folder` 能真实弹出系统对话框并返回路径;后端代码零第三方依赖(`pip list`/`import` 检查可验证) - 覆盖:03-C `c_pick_folder`;05 节 §5.4.1, §5.7 - **U-008 [Enabler]** 前端骨架(本地 Web 应用,原生 HTML/CSS/JS,无构建步骤,无 npm 依赖,见 05 节 §5.7) - 依赖:U-007 - Done:浏览器访问 `localhost` 能看到路由跳转正常的空壳应用;前端不依赖 npm install/构建工具链 --- ## PH1:MVP 全链路闭环(Value Path,贪吃蛇场景) **Goal**:端到端跑通一次从"提出目标"到"任务完成"的完整闭环,不要求 UI,可以用手工 API 调用驱动。 **Phase 验收**:REQ-01/02/03/07 的 happy path 全部通过;架构文档 §11 验收标准第 1-3 条达成。 ### PH1-S1 Org Graph 层单点触发 - **U-009 [Value Path]** `c_task_create` 实现,`task` 状态机(`p_org_task_lifecycle`)落地到 `queued` 态(`c_project_create` 已经在 U-002 完整实现,这里不重做) - 依赖:U-001, U-002 - Done(**2026-07-31 订正**:原来写"REQ-01 happy/boundary/failure 三个用例全部通过"不准确——happy 用例("依次触发 planner/coder/validator 三个 org_node,task.status 按状态机正确流转")需要 `c_org_node_trigger` 完整实现,U-009 本身不碰这个,做不到;U-010 的 Done 标准原话几乎就是这句 happy 用例。boundary(并发上限不阻塞任务*创建*本身)和 failure(`INVALID_PARAM`)两条在 U-009 范围内是真实可测的):boundary/failure 两个用例通过;happy 用例随 U-010 落地后累计满足,不在本 Unit 单独打勾 - 覆盖:01-M `task`;03-C `c_task_create`;04-P `p_org_task_lifecycle`;REQ-01(boundary/failure) - **U-010 [Value Path]** `c_org_node_trigger` 完整实现(含 `TASK_NOT_READY`/`CONCURRENCY_LIMIT_REACHED`/`USAGE_LIMIT_REACHED` 错误处理) - 依赖:U-005, U-009 - Done:手工依次触发 planner/coder/validator 三个 Org Agent,`task.status` 按状态机正确流转(**2026-07-31 订正引用**:原来写"对应架构文档 §11'手工调用验证'里程碑",但架构文档 §11 里根本没有这个短语;真正出处是 05 节 §5.5"上线顺序与兼容性"第 2 步:"再落地 Org Graph 层接口...此时可先用手工调用验证 Planner/Coder/Validator 三个 Org Agent **分别**可被触发"——注意是"分别",即逐个验证能被触发,不是要求一次串联跑通带真实 Artifact 校验的完整链路,那是 05 节 §5.5 第 3 步"Work Graph 层集成"之后的事,此处不做) - **范围说明(2026-07-31,实现后补充)**:本 Unit 只落地 `p_org_task_lifecycle`(04-P)里由单次 `c_org_node_trigger` 调用直接触发的三段前向状态迁移——`queued→planning_brd_prd_sdd`(触发 planner)、`planning_brd_prd_sdd→coding`(触发 coder,需要 BRD/PRD/SDD/Benchmark 四类 Artifact 均 `validation_status=passed`)、`coding→validating`(触发 validator,需要 `code_diff`/`test_report` 均 `validation_status=passed`)。`planning_failed`/`coding_failed` 重试、Validator PASS/FAIL 判定与 `bug_record`/`c_org_loop_route` 路由、`blocked_pending_human` 升级,都需要真实 Work Graph 产出(U-011 起)或 Org-level Loop(U-015/U-016,PH2)才能实现,本 Unit 不做,也做不到——由于没有真实 Work Graph 产出 Artifact,测试里的 Artifact 校验前置条件是直接 seed 数据进去的,不是跑出来的,这是意料之中的(上游契约还没实现,消费方先按契约测) - 覆盖:03-C `c_org_node_trigger` ### PH1-S2 Work Graph 层集成(本项目风险最高的 Sub-phase) - **U-011 [Value Path]** `c_work_graph_plan` 实现(Planning Skill 调用,输出 `graph_manifest`) - 依赖:U-004, U-010 - Done(**2026-07-31 订正**:原来写"REQ-02 happy 用例通过(Planner 产出 BRD/PRD/SDD/Benchmark 四类 artifact 且血缘完整)",但 03-C §3.4 里 `c_work_graph_plan` 自己的契约明确写着"不执行,只规划"——它只产出 `graph_manifest`(结构/计划),不产出真实 Artifact 内容;REQ-02 happy 用例要求的"四类 artifact 且血缘完整"需要真正执行(U-012/U-013)加上 `c_artifact_write`(见下方缺口),单靠 U-011 做不到,也不该做,这是同一类 Done 标准过头的问题,U-009/U-010 已经出现过两次):`c_work_graph_plan` 真实调用 `claude --agent planner -p` 产出一个通过结构校验的 `graph_manifest`(四个节点对应 brd/prd/sdd/benchmark,依赖链 sdd→prd→brd 正确),落一条 `work_graph_run(status='planning')` 记录;REQ-02 happy 用例整体随 U-012/U-013 落地后累计满足 - **已知缺口(2026-07-31,实现时发现)**:`c_artifact_write`(03-C §3.7)是 REQ-02 happy/failure 两个用例都直接依赖的控制器(failure 用例原话:"`c_artifact_write` 应拒绝写入"),但翻遍 development-plan.md 全部 43 个 Unit,**没有任何一个 Unit 的"覆盖"字段提到 `c_artifact_write`**——不是分散在别处,是真的没人认领。等做到 U-012/U-013(或不管哪个 Unit 第一个需要"把 Agent 写的文件登记成 artifact 行")时需要显式补上,不要假设它已经存在 - **技术发现(2026-07-31)**:`--agent` 和 `--json-schema` 同时使用时,`--json-schema` 会被静默忽略(`structured_output` 完全不出现),已记录到 05 节 §5.4.1.1,`plan_work_graph` 改成从 `result` 里解析 ```` ```json ```` 围栏代码块 - 覆盖:01-M `artifact`/`work_graph_run`;03-C `c_work_graph_plan`;REQ-02(happy 用例的"计划"部分,非全部) - **U-012 [Value Path]** `c_work_graph_execute` 完整实现(`auto` 权限模式、`USAGE_LIMIT_REACHED` 退避重试 `p_usage_limit_wait`、`CLASSIFIER_BLOCKED` 处理) - 依赖:U-006, U-011 - Done(**2026-07-31 订正**:原来写"REQ-07 全部用例通过",但 REQ-07 happy 用例原话是"Claude Code ACP 后端下完整跑通 REQ-01~06 全部场景"——这需要 REQ-04/05/06(Validator 根因归属、Org-level Loop、断点恢复)全部就绪,那是 PH2 的事,U-012 单靠 U-006+U-011 做不到,是同一类 Done 标准过头问题第四次出现):REQ-07 **boundary**(不加 `--bare`,冷启动开销可接受——U-006 已满足,本 Unit 未改变这点)和 **failure**(`CLASSIFIER_BLOCKED` 检测正确路由)通过;happy 用例随 PH2 收口后累计满足。REQ-14 happy 用例通过(boundary/failure 场景在 PH2-S3/U-020 补齐完整覆盖)——这条原来就写对了,不用改 - **范围说明(2026-07-31,实现后补充)**:`p_usage_limit_wait` 退避重试落在整个 `execute_work_graph` 调用级别,不是逐个 `work_node_execution` 记录级别——U-013(`c_work_node_run`,真正拥有 `work_node_execution` 行)还没做,节点级别的额度等待追踪要等 U-013 才有意义。`CLASSIFIER_BLOCKED` 判定用单次 `-p` 调用自带的 `permission_denials` 数组长度 ≥3 做近似(03-C 原话"连续 3 次或累计 20 次",跨调用的真正累计计数需要持久化,也是等 U-013) - 覆盖:03-C `c_work_graph_execute`;04-P `p_work_graph_execution`/`p_usage_limit_wait`;REQ-07(boundary/failure)/REQ-14(happy) - **U-013 [Value Path]** `c_work_node_run` + `git_worktree` 隔离(并行写代码节点) - 依赖:U-012 - Done(**2026-07-31 订正**:原来写"REQ-03 happy/boundary 通过",但 happy 用例("产出 code_diff 与 test_report...unit+integration test 全部通过")需要真实 Coder 会话写代码 + `c_artifact_write`(U-011 已记录的缺口,至今没有 Unit 认领),U-013 单靠 U-012 做不到——第五次出现同一类问题):REQ-03 **boundary** 通过(两个独立节点各自用 `git worktree add` 隔离,`run_fn` 各写一个文件并 commit,`merge --no-ff` 合并回主分支,`git log` 确认两次提交都在,且 `work_node_execution.git_worktree_ref` 各不相同);happy 用例随 `c_artifact_write` 落地后累计满足 - **精度提升(2026-07-31,实现后补充)**:03-C §3.6 给出了 `USAGE_LIMIT_REACHED` 的精确探测正则 `/hit your (session|weekly|Opus) limit/i`,比 U-010/U-012 当时用的短语近似匹配更准——U-013 按这条正则实现,且真正写回 `work_node_execution.quota_wait_started_at`/`quota_limit_type`(U-010/U-012 阶段这两张表字段还没有真实归属)。`c_work_node_run` 是内部接口(03-C 原话"不对外暴露 HTTP,供 c_work_graph_execute 调用"),退避重试从 U-012 的整图级别下沉到这里的单节点级别 - 覆盖:01-M `work_node_execution`;03-C `c_work_node_run`;REQ-03(boundary) ### PH1-S3 端到端验收(收口) - **U-014 [Value Path]** 完整跑通"实现贪吃蛇游戏"验收场景,从 `goal_text` 到 `task.status=done` - 依赖:U-011, U-012, U-013 - Done:架构文档 §11 MVP 验收标准第 1-3 条全部达成(Planner 出四件套,Coder 自测通过,Validator 归因产出 bug list) - **实现补充(2026-07-31)**:这是前面 U-009~U-013 五次"Done 标准过头"里被推迟的 happy 用例真正兑现的地方。落地时顺手把 U-011 记录的 `c_artifact_write`(03-C §3.7)缺口补上了——`src/artifact_write.py`,之前没有任何 Unit 认领。编排代码是 `src/e2e_pipeline.py`:`plan_documents`(真实调用 planner 依次产出 BRD→PRD→SDD→Benchmark,各自用 `c_artifact_write` 落盘+提交+建血缘)、`implement_code`(真实调用 coder,用会话前后的 `git diff` 作为 `code_diff`、coder 自己的回复文本作为 `test_report`)、`validate_implementation`(真实调用 validator,解析约定的 `VERDICT: PASS|FAIL|INCONCLUSIVE` 行)、`run_end_to_end`(串联全部四步) - **真实验证的范围(2026-07-31)**:`plan_documents` 这一段(4 次真实 planner 调用产出真实 BRD/PRD/SDD/Benchmark,血缘链验证)成本和之前几个 Unit 同量级,已经真实跑过。`run_end_to_end` 的 Coder/Validator 那一半——真实写代码、真实跑测试——量级完全不同(可能是分钟级、真金白银的开销,而且结果不确定),没有不请示直接跑,等用户决定要不要真的跑一次完整闭环 --- ## PH2:质量闭环(Risk Gate,回路与护栏) **Goal**:系统出错时能自主定位、自主修复,或安全地转人工,不会无限空转也不会裸奔超支。 **Phase 验收**:完整跑通一次 Org-level Loop(人为植入 bug → 定位 → 修复);REQ-04/05/06/08/09/10/14 全部通过。 PH2-S1/S2/S3/S4 之间除标注的依赖外相互独立,可并行推进。 ### PH2-S1 Validator 回路与根因溯源 - **U-015 [Risk Gate]** `p_validator_root_cause_trace` 实现(三态判定 PASS/FAIL/INCONCLUSIVE,双向可追溯矩阵) - 依赖:U-013 - Done:REQ-04 happy/boundary/failure 通过 - **实现补充(2026-07-31)**:这是这几轮里少有的一次不用订正 Done 标准的 Unit——REQ-04 的三个用例都只依赖"给定 lineage 上下文后的多视角判定",没有牵扯到 U-015 自身依赖之外的能力。"血缘读取"(读 SDD/PRD/BRD/Coder 实现记录)刻意设计成调用方职责(`perspective_fn` 闭包自带上下文),`judge_root_cause` 本身只管三视角≥2/3 多数表决。"双向可追溯矩阵"是 Validator 人设自己的内部推理框架(`qa-task-validation` skill + 迁移后的 AGENTS.md "claim—风险—实现—测试—证据矩阵",U-004 时已经迁移),不是本 Unit 需要单独建的后端能力 - **已知缺口,2026-07-31 U-018 已解决**:`root_cause_category=skill_defect` 时 03-C §3.10 要求"必须同时创建 skill_iteration_suggestion",当时 `skill_iteration` 表要求的 `before_git_commit_sha`/`after_git_commit_sha` 还没有真实来源。做 U-018 时按 04-P §4.3 本身的状态表重新确认:`bug_recorded`(`c_bug_record_create` 单独达成)和 `skill_iteration_triggered`(触发 `c_skill_update`)是两个先后独立的状态迁移,不是同一次调用——"必须同时创建"读成"同一条流程线里最终要有"而不是"同一次 API 调用里同步造出来",`bug_record.skill_iteration_suggestion_id` 从 null 起步,`c_skill_update` 跑完后回填。已放开 `bug_record.py` 接受 `skill_defect` - 覆盖:01-M `bug_record`;03-C `c_bug_record_create`;04-P `p_validator_root_cause_trace`;REQ-04 - **U-016 [Risk Gate]** `c_org_loop_route` + `fast_lane`/`full_replan` 快速通道 - 依赖:U-015 - Done:REQ-05/REQ-09 全部用例通过(含 3 轮上限 → `blocked_pending_human`) - **实现补充(2026-07-31)**:顺手补上了 `validating→done`(validator_pass 分支,04-P 原话动作是"关闭任务",没有挂具体 `c_*` 控制器名字,之前也没有任何代码把 `task.status` 真正写成过 `done`)——`close_task_done()`,和 `c_org_loop_route` 是同一个"validating 状态怎么退出"决策的另一半,REQ-05 happy 用例要求"最终 task.status=done",不补这个到不了终点 - **简化,如实标注(2026-07-31)**:04-P 表格把 fast_lane/full_replan 拆成两跳(`validating→routed_fast_lane→coding`),但 `c_org_loop_route` 自己的 03-C 契约直接输出最终态 `{new_status, new_assigned_org_node}`,且没有第二跳的独立控制器名字——实现上把两跳合并成一次原子调用,`routed_fast_lane`/`routed_full_replan` 这两个 `task.status` 枚举值实际不会被落库,改成写一条 `execution_event(event_type=loop_routed)` 记录路由决策本身。REQ-05/REQ-09 的验收文字都只看最终落点,不检查这个中间态,不影响 Done;以后 PH3 看板如果要真的展示这个瞬时状态,需要回头改 - 覆盖:03-C `c_org_loop_route`;REQ-05/REQ-09 - **U-017 [Risk Gate]** 人为植入 bug 完整闭环验证(架构文档 §11 验收标准第 4 条) - 依赖:U-016 - Done:引入一个人为植入的 bug,系统自主定位根因并驱动 Planner/Coder 完成修复闭环 - **实现补充(2026-07-31)**:`src/org_level_loop.py` 的 `run_fix_loop` 把 U-015(三视角判定)+ U-016(路由)串成一个闭环:判定→建 `bug_record`→路由→真实触发 Coder/Planner 修复→真实触发 Validator 复核→复核通过则 `close_task_done`。`category=coder→fast_lane`、`category=planning→full_replan` 是这里定的固定映射规则(`bug_record.suggested_route` 本来是 Validator 独立判定的字段,但 REQ-05/架构文档 §11 的验收场景里两者天然对应,没有再额外调一次 Agent 单独决定 route)。真实验证复用了 U-015 那个撞墙误判场景,但这次真的让 Coder 编辑仓库里的 `collision.py` 文件、真的让 Validator 读文件复核,不是只判定不动手 ### PH2-S2 Skill 自主迭代 - **U-018 [Risk Gate]** `c_skill_update` + `c_skill_rollback` + Git commit 留痕 - 依赖:U-015(需要 `root_cause_category=skill_defect` 触发) - Done:REQ-08 全部用例通过 - **实现补充(2026-07-31)**:Skill 文件物理落在 graphlemon 平台自己仓库(`skills//`,架构文档 §13.1 平台资产,和 U-004 时 `agents/` 的归属判断一致),不是目标项目——`repo_path` 参数**没有默认值**,强制调用方每次显式传,避免测试不小心忘记覆盖就误提交进 graphlemon 自己的真实仓库(写代码时先犯过这个错,当场改掉了)。`c_skill_rollback` 不是历史重写,是"读回滚前那个 commit 的内容,作为一次新的 commit + 新的 `skill_iteration` 记录"提交,回滚记录的 `triggered_by_bug_id` 复用被回滚那次迭代原本的 bug_id(`c_skill_rollback` 输入契约本来就没有单独给 bug_id 的位置,`skill_iteration.triggered_by_bug_id` 又是非空 FK,只有这一个来源可用) - **已知缺口,不是本 Unit 的事**:Hermes 迁移过来的 Skill 内容本身还没有真正搬过来(05§5.1 只列了要迁移的技能名字,`agent_profile.py` 的 `skills:` frontmatter 目前只是引用名字,`skills//` 在仓库里还没有真实文件)——测试里跟 U-010/U-011 对没建成的上游产物的处理方式一样,自己 seed 一个占位 Skill 文件 - 覆盖:01-M `skill`/`skill_iteration`;03-C `c_skill_update`/`c_skill_rollback`;REQ-08 ### PH2-S3 护栏双层机制 - **U-019 [Risk Gate]** `budget_guardrail_config` + `c_budget_check`(人工配置层) - 依赖:U-012 - Done:REQ-10 happy/boundary 通过 - **实现补充(2026-07-31)**:boundary 用例(调高额度后 `paused` 态任务恢复调度)需要的"改配置+恢复调度"能力,本 Unit 只建了最小可用版本 `update_budget_limit_and_resume`——完整设置页控制器 `c_budget_config_update`(含完整字段校验、UI)是 U-030 的事;这里直接复用 U-016 的 `manual_resume`(`org_loop_route.route_org_loop`)做实际的状态恢复,没有重新实现一遍。`c_budget_check` 超限时直接抛 `BudgetExceededError` 并把 `blocked_reason` 设成 `budget_exceeded`(不是返回 `allowed=false` 让调用方自己处理),对齐 03-C 原文"触发后对应任务转 blocked_pending_human"这句话描述的是 `c_budget_check` 自己的副作用 - 覆盖:01-M `budget_guardrail_config`;03-C `c_budget_check`;04-P `p_budget_guardrail`;REQ-10 - **U-020 [Risk Gate]** `p_usage_limit_wait` 完整验收(`session`/`weekly`/`opus` 三种 `quota_limit_type` + 超时兜底) - 依赖:U-012(骨架已在 U-012 实现,本 Unit 补齐边界/失败场景) - Done:REQ-14 全部用例通过(含超时转 `blocked_pending_human, blocked_reason='usage_limit_wait_timeout'`) - **实现补充(2026-07-31)**:这次不只是补测试——重新对照 REQ-14 boundary 用例原文发现,U-013 时 `work_node_run.py` 的 opus 处理只做到"不退避",没做到"直接降级到非 Opus 模型重试"(原来是探测到 opus 额度直接抛错,从没真正重试过)。补上了:`run_fn` 签名改成 `run_fn(cwd, model=None)`,探测到 opus 信号后立即(不等待)用 `opus_downgrade_model`(默认 `claude-sonnet-5`)重试一次;如果降级后还是收到 opus 信号(不合理,非 opus 模型不该撞 opus 专属额度),直接判超时,不会无限重试。顺手把之前漏测的 `weekly` 额度类型(探测恢复 + 9 天超时)也补齐了。`work_graph_execute.py`(U-012)里同名的 opus 立即失败逻辑**没有**跟着改——REQ-14 覆盖列表只写了 `c_work_node_run`/`c_org_node_trigger`,没有 `c_work_graph_execute`,而且那条路径是多 Agent 动态工作流,没有一个单一的"the model"可以整体降级,属于更深的 A-1 动态工作流问题,本 Unit 不动 - 覆盖:04-P `p_usage_limit_wait`;REQ-14 ### PH2-S4 断点恢复 - **U-021 [Risk Gate]** Graph Engine 持久化状态 + 断点恢复 - 依赖:U-012, U-013 - Done:REQ-06 happy/boundary/failure 通过(执行中途中断进程后从持久化状态恢复,不重跑已完成节点) - **实现补充(2026-07-31)**:核心问题是 U-013 `run_work_node` 的 `now` 默认值是 `time.monotonic`——单调时钟在进程重启后没有固定的墙钟含义,REQ-06 恰恰要求"进程重启后继续计算 quota 等待已经过去多久",两者根本不兼容。改法:`now` 默认改回 `time.time`(墙钟),新增 `_parse_sqlite_timestamp()` 把 SQLite `CURRENT_TIMESTAMP` 的 UTC 字符串转成可比的 epoch 值;`run_work_node` 一开始就读出该 node_exec_id 已持久化的 `status`/`retry_count`/`quota_wait_started_at`/`git_worktree_ref`,`status='done'` 直接返回存量 `output_payload`(不重跑 `run_fn`),`retrying`/`waiting_for_quota` 从持久化的 `retry_count`/`quota_wait_started_at` 续算而不是清零重来,`needs_worktree=True` 时若已存在对应 worktree 目录则复用而不是重新 `git worktree add`(会直接失败)。新增 `resume_work_graph_run(conn, run_id)`:返回一次 `work_graph_run` 里还没 `done` 的 `node_exec_id` 列表,供调用方决定重新调度谁;对内部不一致的持久化状态(无效的 `work_graph_run.status`/节点 `status`,`status='done'` 但 `output_payload` 为空,`status='waiting_for_quota'` 但 `quota_wait_started_at` 为空)统一抛 `CorruptPersistedStateError`,不会返回一份看似正常但实际有问题的恢复计划。这几类"状态不一致"用正常写入路径其实到不了(各字段都有 `CHECK` 约束),测试里用 `PRAGMA ignore_check_constraints=ON` 模拟外部损坏的行来触发。 - 覆盖:01-M `work_graph_run`/`work_node_execution`;04-P `p_work_graph_execution`;REQ-06 --- ## PH3:人机交互控制台(Value Path,可观测性与操作入口) **Goal**:人可以通过浏览器创建项目、提出目标、反馈 bug、实时看到运行过程、管理 Skill 库。 **Phase 验收**:REQ-11/12/13 全部通过。 **并行关系**:可在 PH1 完成后与 PH2 并行开工(控制台是对已产生数据的可视化/操作层,不阻塞回路/护栏本身的开发逻辑)。PH3-S2(实时看板)分两级:U-024(推送链路本身)只需 U-012/U-013 落地即可开工,比原计划更早;REQ-12 的完整验收挪到 U-024.5,需要排在 PH2 大部分 Unit 完成、`execution_event` 各类场景都已产生真实数据之后再联调。 ### PH3-S1 项目与目标入口 - **U-022 [Value Path]** `v_project_setup` + `c_pick_folder` 全平台补齐(Windows `PowerShell`/Linux `zenity`/`kdialog`) - 依赖:U-007, U-008 - Done:REQ-11 happy/boundary/failure 通过 - **实现补充(2026-07-31)**:Done 行字面写的是"REQ-11 全部通过",但 REQ-11 happy 用例完整走完还需要 `v_goal_composer`/`c_task_create` 创建 task 那一步——那是 U-023 的范围,本 Unit 依赖只到 U-007/U-008,不可能单独满足。本 Unit 实际完成的是 REQ-11 里 `v_project_setup`/`c_pick_folder`/`c_project_create` 那部分:① `pick_folder()` 补齐 Windows(PowerShell `FolderBrowserDialog` 脚本)/Linux(`zenity` 优先,缺失则退回 `kdialog`,都不在则 `DIALOG_UNAVAILABLE`);② `c_project_create`(`POST /api/projects`)此前只有 U-002 写好的 `project_scaffold.create_project()` 函数本体、从未接到 HTTP 层(`backend_server.py` 因此第一次需要持有一个 sqlite 连接——`ThreadingHTTPServer` 按请求起线程,给 `schema.connect()` 加了 `check_same_thread=False` 参数 + 一把 `server.conn_lock` 避免并发请求踩到同一个连接);③ `frontend/app.js` 补上 `#/setup` 真实交互(选文件夹 → 校验 Git 仓库 → 非 Git 仓库时二次确认 → 创建 → 跳转仪表盘),DOM class 命名对齐 `docs/wireframes/v_project_setup.html`。REQ-11 的完整 happy 用例(含创建 task 那一步)等 U-023 落地后再验收。浏览器扩展在本次会话不可用,UI 部分改为对着真实运行的后端做完整 HTTP 级冒烟(`curl` 走一遍选文件夹之外的全部接口调用序列,`node --check` 过一遍 `app.js` 语法),不是真人点击浏览器验证过的。 - 覆盖:02-V `v_project_setup`;03-C `c_pick_folder`;05 节 §5.4.1;REQ-11 - **U-023 [Value Path]** `v_goal_composer` + `v_bug_feedback` - 依赖:U-022 - Done:能通过 UI 创建 `task` 和人工发起的 `bug_record`(`c_bug_report_create`) - **实现补充(2026-07-31)**:① `c_task_create`(U-009 已实现的 `task_create.create_task()`)第一次接上 `POST /api/tasks`;② `c_bug_report_create`(03-C §3.1.1)此前完全没有实现,本 Unit 新增 `bug_record.create_bug_report()` + `POST /api/bug-reports`。**发现并解决一个 03-C 内部小缺口**:`c_bug_report_create` 的 `input_contract` 只列了 `{description, repro_steps, related_task_id}`,没有 `project_id`;但 `execution_event.project_id` 是 NOT NULL(01-M 1.9),`bug_record` 表自己又没有 `project_id` 列可以在 `related_task_id` 留空时兜底推导——"留空则视为新发现的独立问题"(02-V §2.4)这条路径字面上无法满足 `execution_event` 的约束。判断这是文档遗漏而非架构决策点(同类判断依据见 U-022 的 `c_project_create` HTTP 接线、U-014 的 `c_artifact_write`),直接让 `project_id` 和 `c_task_create` 一样成为必填参数,未停下来单独确认。③ 控制台目前没有"当前项目"这个概念的载体(`v_dashboard` 是 U-025 才做),`v_goal_composer`/`v_bug_feedback` 需要的 `project_id` 暂存到 `localStorage`(`v_project_setup` 创建成功时写入),没有选中项目时两个页面直接引导回 `#/setup`。④ `related_task_id` 在 02-V 原型里是下拉选择器,但 `c_task_list`(列出当前项目下的任务供选择)没有被任何 Unit 认领,本 Unit 简化成纯文本 task_id 输入框,避免顺手做一个不属于自己范围的接口;等 U-025 补上任务列表后再换成真下拉。⑤ 提交成功后 02-V 写的是"跳转 `v_task_detail`",但那个页面是 U-025 才建,本 Unit 改成在原页面展示一条带 task_id/bug_id 的确认信息 + 返回仪表盘链接,等 U-025 落地后再切回真跳转。⑥ 对着真实运行的后端做端到端冒烟时发现一个真 bug:`create_bug_report()` 一开始没校验 `project_id`/`related_task_id` 是否存在,传入不存在的 `project_id` 会在 `execution_event` 插入时触发未捕获的 `sqlite3.IntegrityError`,请求直接 500(无干净 JSON 错误体)——补了存在性校验,改为清晰的 `INVALID_PARAM`/400,并加了回归测试。 - 覆盖:02-V `v_goal_composer`/`v_bug_feedback`;03-C `c_task_create`/`c_bug_report_create` ### PH3-S2 实时看板 - **U-024 [Value Path]** `c_ws_subscribe` + `execution_event` 实时推送(最小连通版) - 依赖:U-012, U-013(work graph 执行、work node run 是最早、最稳定产出 `execution_event` 的两个来源,足够验证推送链路本身是否通) - Done:`c_ws_subscribe` 对真实 `execution_event` 做实时推送,≤2 秒延迟达成;REQ-12 happy 用例通过(boundary/failure——尤其是断线重连拉快照在多样事件场景下的表现——留给 U-024.5 补齐完整覆盖) - **实现补充(2026-07-31)**:Python stdlib 没有 WebSocket 支持,`src/ws_subscribe.py` 手写了 RFC 6455 握手(`Sec-WebSocket-Accept` 用 hashlib+base64 算)+ 帧编解码(`socket` 原始读写,服务端帧不加掩码,客户端帧强制解掩码),没有引入 `websockets` 之类的第三方库(05 §5.7)。推送机制是**轮询**,不是写路径 hook——`execution_event` 的写入分散在 `org_node_trigger`/`work_graph_execute`/`work_node_run`/`task_create`/`bug_record` 等十几个调用点,没有单一写入路径可以插桩,Unit 名字里的"最小连通版"本身也在暗示这个方向;`_handle_ws_subscribe` 用 `ThreadingHTTPServer` 劫持连接后每 0.5 秒查一次 `execution_event`(按 `rowid` 增量,`project_id`/可选 `task_id` 过滤),轻松满足 REQ-12 的 ≤2 秒目标(该数字本身写的是"建议"而非硬 SLA)。03-C §3.14 自己写明断线重连走 REST 快照补齐、不在 WebSocket 层做消息重放缓冲,所以新连接只看"连接那一刻起"的新事件,不做历史重放。给 `ThreadingHTTPServer` 加了 `daemon_threads = True`,避免长连接的 WS 线程阻塞进程退出。端到端验证:`src/test_backend_server.py` 用原始 socket 走真实握手+推送延迟断言;另外用 Node 原生 `WebSocket` 客户端(跨实现的真实互操作性验证,不是同一套代码自己测自己)连真实运行的服务器,从建连到收到 `c_task_create` 触发的真实 `task_status_changed` 推送全程走通。 - 覆盖:01-M `execution_event`;03-C `c_ws_subscribe`;REQ-12(部分) - **U-024.5(2026-07-30 新增)[Value Path]** REQ-12 完整验收(事件多样性联调) - 依赖:U-024, U-015, U-016, U-018, U-019, U-020, U-021(需要 PH2 大部分 Unit 已上线,事件类型覆盖足够丰富才能真正验证 boundary/failure 场景——例如 usage-limit 等待、护栏拦截、Skill 回滚各自产生的 `execution_event` 都要真实出现过) - Done:REQ-12 全部 happy/boundary/failure 用例通过;PH3 Phase 验收里的 REQ-12 以本 Unit 为准,U-024 本身只解决"链路通不通" - 覆盖:REQ-12(完整) - **U-025 [Value Path]** `v_dashboard` + `v_task_board` + `v_task_detail` - 依赖:U-024 - Done:三个页面可正常展示实时数据(原文档缺 Done/覆盖行,2026-07-31 补上) - **实现补充(2026-07-31)**:这三个页面需要的读接口此前完全不存在,本 Unit 顺带把它们建了:① `c_task_list`/`c_task_get`(03-C §3.2,此前无 Unit 认领)新增 `src/task_query.py`,`c_task_get` 的返回比 03-C 字面写的"task + artifact 列表 + bug_record 列表"更完整——额外带上 `work_graph_run`(含解析后的 `graph_manifest`)+ 每个 run 的 `work_node_execution` 列表,因为 `v_task_detail` 的 to-do 树(01-M 1.3 补充说明、02-V §2.6 2026-07-30 新增)就是要渲染这份数据,字面契约低估了这个视图真正需要的东西;② `c_org_loop_route`(03-C §3.11,U-016 已实现 `route_org_loop()` 但从未接 HTTP)第一次接上 `POST /api/tasks/:task_id/route`,`v_task_detail` 的"人工干预(manual_resume)"按钮需要它;③ 预算护栏没有任何读接口(03-C §3.13 只写了 PUT,读是 U-030 配置页的隐含前提但没人建),新增 `budget_guardrail.list_budget_configs()` + `GET /api/settings/budget` 给仪表盘的预算条用。前端:`frontend/app.js` 补上真实的 `#/`(org graph 三节点卡片、当前任务概要、预算条、事件时间线,全部通过 `c_ws_subscribe` 实时刷新)、`#/tasks`(关键字/状态/org_node 过滤 + 点击行跳详情)、`#/tasks/:task_id`(task 字段、to-do 树、关联 artifact/bug_record、`blocked_pending_human` 告警条 + manual_resume 按钮);路由改成对象形式支持挂载态清理(`registerRouteCleanup`),每次切页面先关掉上一个页面开的 WebSocket,避免连接堆积。`v_task_detail` 里 `v_graph_visualizer`(DAG 视图)和 `v_log_viewer`(原始日志)两个子模块明确标成"随 U-026/U-027 补上"的占位提示,不是假造内容。顺带把 U-023 里因为 `c_task_list` 当时还不存在而简化成纯文本输入的 `related_task_id` 换回了真下拉(`v_goal_composer`/`v_bug_feedback` 提交成功后也从"页面内展示确认信息"改回 02-V 原文写的"跳转 `v_task_detail`",这个页面现在是真的了)。浏览器扩展本次会话不可用,验证方式:全部新增 HTTP 路由的真实请求/响应断言(`src/test_backend_server.py`)+ 对着真实运行的服务器用 `curl`/Node 原生 `WebSocket`/`fetch` 走一遍数据形状(尤其是 to-do 树需要的嵌套 JSON 结构,逐字段核对过 JS 渲染逻辑会怎么消费它)+ `node --check` 过语法,不是真人点击浏览器验证过的。 - 覆盖:02-V `v_dashboard`/`v_task_board`/`v_task_detail`;03-C `c_task_list`/`c_task_get`/`c_org_loop_route`(HTTP 接线) - **U-026 [Value Path]** `v_graph_visualizer`(Org Graph + Work Graph 双层可视化,含 `waiting_for_quota` 信息态展示) - 依赖:U-024 - Done:Org/Work Graph 双层可视化含 `waiting_for_quota` 信息态(原文档缺 Done/覆盖行,2026-07-31 补上) - **实现补充(2026-07-31)**:纯前端 Unit,不需要新接口——数据已经在 U-025 的 `c_task_get` 里(`work_graph_run.graph_manifest` + `work_node_execution` 列表),只补了两个 hover tooltip 要用但 U-025 当时没取的字段(`input_schema_ref`/`output_schema_ref`/`quota_limit_type`/`quota_wait_started_at`,见 `task_query._work_nodes_for_run`)。`frontend/app.js` 新增 `computeGraphLayers()`:按 `graph_manifest` 节点的 `depends_on` 边做拓扑分层(没有依赖的节点在第 0 层,每个节点的层号 = 1 + 依赖节点里最大层号),渲染成按执行层级排布的行(`work-row`),fan-out/fan-in 都能正确分层——用真实运行的服务器 + 一个 fan-out/fan-in 混合的 `graph_manifest`(n1→[n2,n3]→n4)验证过,直接从 `app.js` 里 `eval` 出 `computeGraphLayers` 本体(不是重新实现一份逻辑再测)跑真实拿到的 JSON,分层结果 `[["n1"],["n2","n3"],["n4"]]` 完全正确。节点按 03-C §3.6 的六态配色(`done`/`running`/`reflecting|retrying`/`waiting_for_quota`/`blocked|failed`/`pending`),`waiting_for_quota` 额外挂 `data-quota-limit-type`/`data-quota-wait-since`,hover tooltip 展示 skill/`quota_limit_type`/已等待时长(客户端用 `quota_wait_started_at` 和 `Date.now()` 算,服务端只存原始时间戳不算相对时长)。02-V §2.7 原描述还包含一份 Org Graph 层,但 `v_task_detail`(U-025)页面顶部已经有等价的 `org-strip` 面板(同款 class/data 结构),紧接着 to-do 树下面再画一份完全一样的东西纯属重复滚动,所以这里只做了 Work Graph DAG 层,Org Graph 复用页面已有的那个——不是漏做,是判断为无意义重复后跳过,写在这里说明。嵌入位置:`v_task_detail` 里原来"随 U-026 补上"的占位提示换成了真实的 DAG 面板。浏览器扩展本次会话不可用,验证方式同 U-025:真实服务器 + 真实数据 + 直接跑生产代码里的分层函数,不是真人点击浏览器验证过的。 - 覆盖:02-V `v_graph_visualizer`;03-C 3.6 节六态配色语义 ### PH3-S3 日志与 Artifact/Skill 管理 - **U-027 [Value Path]** `execution_log_file` + `c_execution_log_get` + `c_ws_log_tail` + `v_log_viewer` - 依赖:U-024 - Done:REQ-13 happy/boundary/failure 通过 - **实现补充(2026-07-31)**:`execution_log_file` 的行生命周期(`active`→`closed`)U-013 早就管好了,但从来没有代码真的往日志文件里写过内容——`src/execution_log.py` 新增 `append_log()` 补上这条缺失的写路径,设计成 `run_fn`(真正干活的那个函数)在执行过程中随时可调用,不关心节点在做什么,和 `work_node_run.py` 自己对 `run_fn` 划的边界一致。`get_log()` 按 offset/limit 分页读,`download=true` 时走原始文件流。`c_ws_log_tail`(`WS /api/ws/work-nodes/:node_exec_id/log`)复用 U-024 手写的 RFC 6455 机制,同样是轮询 `execution_event(event_type=log_appended)`;按 03-C §3.6.2 原文要求,节点一旦转终态(`done`/`blocked`/`failed`)服务端主动发关闭帧断开连接,不是让客户端一直轮询下去。`p_log_retention_cleanup`(04-P §4.5)实现成一个纯函数 `run_log_retention_cleanup()`——过期扫描 + `max_total_size_mb` 超限时按最旧优先追加候选 + 单个文件删除失败只记录不阻塞其余项,全部按 04-P 原文逻辑写;没有配调度器/cron(那不在本 Unit 覆盖范围内,`c_execution_log_get`/`c_ws_log_tail` 才是),留给未来的调度进程周期调用。**过程中意外抓到一个真的并发 bug**:`backend_server.py` 的 `_Server.server_close()` 关闭共享 sqlite 连接时原来没拿 `conn_lock`,如果这时候正好有个 WS 轮询线程在另一边持锁执行 SQL,两个线程无同步地并发操作同一个 C 级 sqlite3 连接对象,实测直接把解释器崩了(`exit code 139`,SIGSEGV)——不是新代码引入的,U-024 就已经有这个隐患,只是这次高频率的 WS 测试把它的时序窗口撞开了;修好后连续跑几十次全部稳定通过。前端:`frontend/app.js` 的 `v_log_viewer` 挂在 `v_task_detail` 每个 to-do 节点的展开详情里(不是页面底部一个孤立面板,`node_exec_id` 对应哪个节点这份日志就该长在哪个节点旁边),首次展开才建连接(懒加载),运行中态走 `c_ws_log_tail` 实时滚动、终态走 `c_execution_log_get` 一次性静态拉取;活跃的 log-tail 连接会在任务级刷新/离开页面时统一关闭,避免连接泄漏。真实服务器 + Node 原生 `WebSocket`/`fetch` 端到端验证过实时追加与终态自动断开的完整链路(静态回填先拿到已有内容,WS 只推增量,二者拼起来等于最终静态内容)。 - 覆盖:01-M `execution_log_file`/`log_retention_config`;02-V `v_log_viewer`;03-C `c_execution_log_get`/`c_ws_log_tail`;REQ-13 - **U-028 [Value Path]** `v_artifact_browser` - 依赖:U-011 - Done:Artifact 列表+预览+血缘链可用(原文档缺 Done/覆盖行,2026-07-31 补上) - **实现补充(2026-07-31)**:`c_artifact_get` 在机器可读的 `sdd-mvcp-contract.json` 里其实定义过(`GET /api/artifacts/:artifact_id`),只是 03-C 的正文一直没写出来;`c_artifact_list` 连 MVCP JSON 里都没有 operation 定义,纯粹是 `v_artifact_browser` 自己的过滤字段(`artifact_type_filter`/`task_id_filter`)倒逼出来的缺口,和 U-025 补 `c_task_list` 是同一类"读接口没人建"问题。新增 `src/artifact_query.py`:`list_artifacts()` 返回裸数组(跟 `list_tasks()` 保持同款风格,不跟 MVCP JSON 里 `c_task_list` 的 `{items,total}` 包一层——本地单项目 MVP 不需要分页,跟自己代码库已有约定保持一致比跟一份机器生成的镜像文件保持一致更重要);`get_artifact()` 顺带把血缘链(`lineage_chain()`,按 `depends_on_artifact_ids` 往回走,多个依赖时只走第一个——原型图和这条流水线的实际产出都只有线性 BRD→PRD→SDD 链,没有真正的多依赖分叉场景)和文件内容(`artifact` 表本身没有 content 列,内容活在磁盘上,走 `project.target_folder_path` + `artifact.file_path` 拼出绝对路径现读)一起返回,预览面板一次请求就够。前端 `v_artifact_browser` 的预览统一用 `
` 纯文本展示,不做 Markdown 渲染/代码高亮——02-V 原文想要那个效果,但这需要引入第一个前端第三方库(图表/高亮/markdown 解析器),和这个控制台"纯 vanilla JS、无构建步骤"的一贯做法(05 §5.7 精神延伸到前端)冲突,判断为有意简化而非漏做。顺手把 `frontend/index.html` 的常驻导航栏加上了"产物"链接(之前只有 Dashboard/Tasks/Settings 三个占位项)。真实服务器验证过三级血缘链(BRD→PRD→SDD)返回顺序正确。
  - 覆盖:02-V `v_artifact_browser`;03-C `c_artifact_get`(正文订正补全)+ 新增 `c_artifact_list`

- **U-029 [Value Path]** `v_skill_library` + `v_skill_audit_log`
  - 依赖:U-018
  - Done:Skill 库与变更审计日志可查看(原文档缺 Done/覆盖行,2026-07-31 补上)
  - **实现补充(2026-07-31)**:`c_skill_diff` 和 U-028 的 `c_artifact_get` 一样,只存在于机器可读的 `sdd-mvcp-contract.json`(`GET /api/skills/diff?before=&after=`),03-C 正文从未写出来,本 Unit 订正补全;list/get 系读接口(`c_skill_list`/`c_skill_get`)两份文档都没定义,跟 `c_task_list`/`c_artifact_list` 是同一类"视图倒逼出来的缺口"。新增 `src/skill_query.py`:`list_skills()`、`get_skill_audit_log()`、`get_diff()`(`git diff  `——`skill_update.py` 的 `_write_and_commit` 把每次迭代做成原子的单文件 commit,所以整仓库范围的 diff 天然就只涉及这一个 Skill 文件,不需要额外传 path 过滤)。**关键安全考量**:Skill 库活在 graphlemon 自己的仓库里(不是某个 target project 的),`c_skill_rollback` 会真的在这个仓库里跑 `git commit`——如果 HTTP 层写死指向真实仓库根目录,任何针对这条路由的测试都会在正在跑的这个 graphlemon 仓库里留下真实提交。参照 `db_path` 已有的做法,给 `create_server()` 加了 `skill_repo_path` 参数(默认 `skill_query._REPO_ROOT`,即 graphlemon 真实仓库根,生产环境下这就是对的行为;测试传一个 tempfile 建的临时仓库,绝不碰真实历史),不通过 HTTP 暴露这个参数(只在进程/测试构造时可设,避免变成一个可以让客户端指定任意写入目标的口子)。真实服务器 + 临时仓库端到端验证过完整的 update→diff→rollback 闭环,并且确认过跑完之后 graphlemon 自己仓库的 git log 完全没变化。前端 `v_skill_library`/`v_skill_audit_log` 两个页面不检查"当前项目"(Skill 是平台级资产,跟项目无关,是这个控制台里唯一不需要 `getCurrentProjectId()` 守卫的真实页面)。
  - 覆盖:02-V `v_skill_library`/`v_skill_audit_log`;03-C `c_skill_diff`(正文订正补全)+ 新增 `c_skill_list`/`c_skill_get`;`c_skill_rollback`(U-018 已实现 `rollback_skill()`)首次接上 HTTP

### PH3-S4 配置页

- **U-030 [Value Path]** `v_concurrency_settings` + `v_budget_settings` + `v_log_retention_settings`
  - 依赖:U-019(预算配置)、U-021 涉及的 `log_retention_config`(U-001 已建表,此处只是配置页 UI)
  - Done:三个配置页可用且改动生效(原文档缺 Done/覆盖行,2026-07-31 补上)
  - **实现补充(2026-07-31)**:三个都是 `config_id='default'` 单例配置,首次使用即创建、之后原地更新(upsert),不需要单独的"初始化"流程。① `c_concurrency_config_update`:新增 `src/settings_config.py`(`org_concurrency_config` 之前完全没人碰过),校验 `max_concurrent_goals` 落在 1-50;② `c_budget_config_update`:`budget_guardrail.py` 里新增 `upsert_budget_config()`,跟 U-019 已有的 `update_budget_limit_and_resume()`(只更新已存在的行,不建新的)不是一回事——`upsert_budget_config()` 是真正完整的设置页控制器,支持首次创建,而且沿用了同一套"额度调高后自动恢复 `budget_exceeded` 阻塞任务"的副作用(04-P §4.4),复用了同一段恢复逻辑(提取成 `_resume_budget_blocked_tasks()` 共享);③ `c_log_retention_config_update`:同样在 `settings_config.py` 里,校验 `retention_days > 0`。三处的数值/枚举校验都在业务层做完整检查(而不是让请求体里的 `None`/超范围值直接怼到 SQL 层,被动等 `CHECK` 约束或 `TypeError` 崩掉),HTTP 层额外兜了一层 `except (InvalidParamError, TypeError)`——payload 里数值字段缺失时 `None <= 0` 这类比较会抛 `TypeError` 而不是业务异常,两个都要接住才能保证返回干净的 400 而不是裸 500。backend_server.py 第一次加了 `do_PUT`(之前只有 GET/POST)。前端三个页面共享一个 `renderSettingsSubnav()` 小组件模拟 02-V 原型里的"设置"下拉菜单(这个控制台的单层 `#nav` 顶栏放不下嵌套下拉,属于纯 UI 简化,不影响信息结构)。真实服务器端到端验证过三个 PUT/GET 的完整读写循环。
  - 覆盖:02-V `v_concurrency_settings`/`v_budget_settings`/`v_log_retention_settings`;03-C `c_concurrency_config_update`/`c_budget_config_update`/`c_log_retention_config_update`

---

## PH4:全量验收与固化(Risk Gate,收口)

**Goal**:确认 MVP 验收标准(架构文档 §11)全部达成,系统可以长期无人值守运行。
**Phase 验收**:06-验收与测试锚点.md 全部 14 条 REQ 的全部用例(happy/boundary/failure)完整回归通过一次。

- **U-031 [Risk Gate]** REQ-01~14 全量自动化回归套件
  - 依赖:PH1/PH2/PH3 全部 Unit

- **U-032 [Risk Gate]** 多 Goal 并发(`org_concurrency_config`)+ 长时间运行稳定性压测
  - 依赖:U-031
  - 覆盖:01-M `org_concurrency_config`;架构文档 §7.4"多任务并发"决策

- **U-033 [Risk Gate]** A-2 假设收口:Hermes SOUL.md 迁移效果复核,确认 `confirmed` 或记录偏差
  - 依赖:U-004(此时已有足够真实运行数据判断人设迁移是否真的"不需要大改")

---

## PH5:自举能力(Risk Gate,Deploy Supervisor + 蓝绿部署自动回滚)

**命名说明**:本 Phase 的"Deploy Supervisor"和架构文档里"Supervisor(调度子 Agent,Coder Work Graph 内部监控节点)"是两个不同概念,详见 `supervisor-auto-rollback-design.md` 开头的命名说明,不要混用。

**Goal**:graphlemon 具备安全地自主修改自己代码(含 Org Agent 身份文件、调度器/执行器核心逻辑)的能力——出问题时自动回滚,不需要人工评审代码内容把关。
**Phase 验收**:`docs/supervisor-auto-rollback-design.md` 状态机的四个终态(`health_check_failed`、`rolled_back`、`rollback_failed → blocked_pending_human`、`finalized`)都能真实触发一次;`self-development-feasibility.md` 第 3 节的 9 步走查场景改成真实执行(不是模拟),从"人类发现 bug"到"Deploy Supervisor 自动 finalize"全程无需人工介入代码评审。
**设计依据**:`docs/supervisor-design-comparison.md`(方案选型)+ `docs/supervisor-auto-rollback-design.md`(状态机/数据模型/健康检查/回滚流程/MVP 参数)。

### PH5-S1 数据模型与治理规则

- **U-034 [Enabler]** 01-M 扩展:新增 `deployment`、`deploy_health_signal` 两个实体建表;`bug_record.root_cause_category` 新增 `core_engine` 枚举值
  - 依赖:U-001
  - Done:两张新表建表成功并通过约束校验;`bug_record` 现有记录/查询逻辑对新枚举值向后兼容(不破坏 U-015/U-018 已有测试)
  - 覆盖:`supervisor-auto-rollback-design.md` §3;`self-development-feasibility.md` 缺口 3

- **U-035 [Risk Gate]** self-target 场景 worktree 无条件强制规则(缺口 1)+ `root_cause_category=core_engine` 命中时路由到蓝绿部署流程而非 `fast_lane`(`c_org_loop_route` 扩展)
  - 依赖:U-013(worktree 机制)、U-016(`c_org_loop_route`)、U-034(`core_engine` 枚举)
  - Done:`target_folder_path == graphlemon 自身安装路径` 时,即使是单节点串行修改也强制走 worktree(不再要求"仅并行时才强制");`root_cause_category=core_engine` 的 bug 被 `c_org_loop_route` 正确路由到 PH5-S2/S3 的蓝绿流程;**且改动落地后必须重跑一次 U-013/REQ-03(worktree 隔离)现有回归用例全部通过**——这是 PH4 全量收口之后第一个真正 touch 生产核心执行路径的 Unit,不能假设"没测出问题就是没问题",否则等于在 PH5 一开工就悄悄打破 PH4 刚建立起来的信任前提
  - 覆盖:REQ-03(回归)

### PH5-S2 Deploy Supervisor 核心机制

- **U-036 [Enabler]** `supervisor/isolation/` 四模块补齐单元测试(当前从 graphloop 复制过来的 `isolated_workspace.py`/`workspace_security.py`/`change_manifest.py`/`selective_apply.py` 只做过一次手工冒烟测试,没有正式 `unittest` 套件)+ Deploy Supervisor 进程骨架(独立操作系统进程,纯 stdlib,不依赖任何 LLM CLI,PID/状态文件约定参考 graphloop `hermes_watchdog.py` 的原子写模式)
  - 依赖:无(代码已存在,是补测试和补骨架,不是从零开发)
  - Done:四模块各自有对应 `test_*.py`(可直接照搬/改造 graphloop 的 `test_change_manifest.py`/`test_selective_apply.py`/`test_workspace_security.py` 等,逻辑未改动,测试也基本不用改);Deploy Supervisor 骨架能独立启停,不受 Live/Candidate 进程崩溃影响
  - **待定(2026-07-31 订正)**:此前这里写的是"`supervisor/` 目录该归入 U-002 的目标项目树(`.claude/`、`skills/`、`src/` 那套)还是 U-003 的平台目录"——但 U-002 的范围已经订正为只创建 `.graphlemon/`(见该 Unit 2026-07-31 订正说明),`.claude/`、`skills/` 从来就不是目标项目的东西,是 graphlemon 平台自己仓库的资产。所以这个选择题已经不成立,`supervisor/` 只可能归属"platform 自身工具"(当前的实际存放位置,`supervisor/isolation/`),不存在真正的"归入目标项目"选项;保留这条记录是为了让后来者理解为什么这里曾经有过一次误判

- **U-037 [Risk Gate]** `c_deploy_supervisor_stage`:候选实例拉起(全新目录 checkout,不 `git pull` 原地改)+ 一次性 DB 副本机制(候选实例验证期间绝不接触生产库)
  - 依赖:U-034, U-036
  - Done:给定一个已合并到主分支的 commit,能生成 `deployment` 记录(`status=staged`)并拉起一个监听临时端口、连接一次性 DB 副本的候选进程

- **U-038 [Risk Gate]** `c_deploy_supervisor_health_check`:`process_alive`/`http_health`/`canary_task` 三项 pre-cutover 检查
  - 依赖:U-037
  - Done:三项检查全部通过 → `status=health_check_passed`;任一失败/超时 → `status=health_check_failed`,Live 全程未受影响(对应回滚路径 A)

- **U-039 [Risk Gate]** `c_deploy_supervisor_cutover`:蓝绿切换本体(Live 停止接受新触发 → 候选切换到生产端口/生产库 → 旧 Live checkout 保留为 `previous_instance_dir`)
  - 依赖:U-038
  - Done:健康检查通过后能完成真实切换,切换过程中在途 `work_node_execution` 能在重启后从持久化状态恢复(复用 U-021 断点恢复机制),`previous_instance_dir` 在监控窗口结束前不被清理

### PH5-S3 监控与回滚

- **U-040 [Risk Gate]** `c_deploy_supervisor_monitor`:post-cutover 监控窗口内四类信号采样(`process_alive`/`http_health`/`canary_task`/`validator_approval_rate`)
  - 依赖:U-039
  - Done:`validator_approval_rate` 信号能从 `execution_event`/`work_node_execution` 正确计算滚动窗口内 Validator 的 approve 占比,人为把该占比推到偏离基线阈值之外时能触发 `anomaly_detected`

- **U-041 [Risk Gate]** `c_deploy_supervisor_rollback`:回滚路径 A(pre-cutover,终止候选+`git revert`+发 `bug_record`)与路径 B(post-cutover,换回 `previous_instance_dir`+健康检查+`git revert`)完整实现,含"回滚本身失败"兜底转 `blocked_pending_human`
  - 依赖:U-038, U-040
  - Done:两条回滚路径各自跑通一次;人为制造"旧版本也起不来"的场景能正确终态到 `blocked_pending_human` 而不是无限重试或静默失败;`git revert` 冲突场景(`supervisor-auto-rollback-design.md` §10 开放问题 3)有明确处理,不能是本 Unit 唯一遗留的未定义行为

- **U-042 [Risk Gate]** 自举闭环端到端验收:真实复现 `self-development-feasibility.md` 第 3 节的 9 步场景(修复 graphlemon 自己 `p_org_task_lifecycle` 状态机里的一个真实植入 bug),走完整"部署→健康检查→cutover→监控→finalize"或"检测异常→自动回滚"两条路径中至少一条
  - 依赖:U-035, U-041
  - Done:全程无需人工评审代码内容;参照 graphloop `RUN_REAL_AGENT_E2E=1` 的先例,真实拉起两个 graphlemon 实例的端到端用例默认跳过,需要显式环境变量开启,日常回归只跑 mock 版本

### PH5-S4 可观测性(非阻塞,可延后)

- **U-043 [Value Path]** 控制台展示 `deployment` 状态与健康信号(02-V 新增视图)
  - 依赖:U-024, U-040
  - Done:能在控制台看到当前是否有 deployment 在进行中、处于状态机的哪个状态、最近一次健康信号采样结果

### PH5 已知交叉缺口(不阻塞设计定稿,实现前需要留意)

1. `supervisor/isolation/` 现有代码目前只做过一次手工冒烟测试(本次会话里跑的 isolate → diff → apply 往返验证),不是正式 `unittest` 套件——U-036 里已列为待办,暂不算"已完成"。
2. `supervisor/` 目录归属 U-002 还是 U-003 的问题目前只在 U-036 里留了一个临时决定,还没有真正的架构决策支撑,PH0 真正实现 U-002/U-003 时应该回头确认这个临时决定是否仍然成立。
3. U-001 的"15 张表"这个数字会在 PH5 落地后变成 17 张(`deployment`/`deploy_health_signal`),后续如果有人只看 PH0 的验收标准会觉得和实际情况对不上,属于文档层面需要注意的地方,不是设计缺陷。
4. `supervisor-auto-rollback-design.md` 里的参数草案值(健康检查超时、监控窗口时长等)明确标注"工程经验值,需要实测校准",U-036~U-042 实现时不要把这些数字当成已经验证过的常量直接硬编码进测试断言里,要留出可调整的空间。

---

## 2. Unit 分类汇总

| 分类 | Unit 数量 | Unit 列表 |
|---|---|---|
| Value Path(主线) | 17 | U-009~U-014, U-022~U-030, U-024.5, U-043 |
| Risk Gate(高风险校验/护栏/收口) | 18 | U-004, U-015~U-021, U-031~U-033, U-035, U-037~U-042 |
| Enabler(使能基础设施) | 10 | U-001~U-003, U-004.5, U-005~U-008, U-034, U-036 |

(PH0-PH4 原有 33 个 Unit 分类不变,PH5 新增 10 个:U-034~U-043,其中 Value Path +1、Risk Gate +7、Enabler +2。
2026-07-30 计划评审追加 2 个 Unit:`U-004.5`[Enabler](RUN_REAL_AGENT_E2E harness 归属)、`U-024.5`[Value Path](U-024 依赖分级后的 REQ-12 完整验收),不改变原有编号顺序,只插入在对应 Unit 之后)

## 3. 关键路径(Critical Path)

风险最高、返工代价最大的一条依赖链(PH0-PH4 主线):

```
U-001 → U-002 → U-004(A-2 假设验证)→ U-004.5(RUN_REAL_AGENT_E2E harness)→ U-005/U-006 → U-010 → U-011 → U-012(A-1 已 confirmed,实现契约已验证)→ U-013 → U-014(PH1 收口)
```

U-004 是全链路里唯一还未经实测验证的假设节点(A-2 pending)。建议 U-004 完成后立即跑一次 U-005(最小触发验证),而不是等到 U-010/U-011 完整实现后才发现人设迁移有问题——这是应用 03-C/05 节里对 A-1 采用的"前置 spike 验证"同一原则。U-004.5 的插入不改变关键路径的性质,只是把"门控 e2e 测试模式怎么搭"这个原本会摊到 U-005/U-006/U-010 各自解决的隐性工作,显式化成一个前置节点,避免重复发明。

**PH5 关键路径**(与上面这条主线不合并——PH5 依赖 PH4 全部收口,是完全独立的第二条链):

```
U-034 → U-035 → U-036 → U-037 → U-038 → U-039 → U-040 → U-041 → U-042(PH5 收口)
```

U-041(回滚路径实现,含 §10 提到的 `git revert` 冲突处理)是 PH5 里返工风险最集中的一环,因为"回滚"本身失败没有再下一层的自动兜底,只能转人工——建议 U-041 完成后立即用人为制造的"旧版本也起不来"场景验证兜底路径,而不是假设它"大概率不会发生"就跳过测试。

## 4. 与 06-验收与测试锚点.md 的对应关系

本文档不重新定义验收标准,Unit 的 Done 标准直接引用 06 节已有的 REQ-01~14 用例(happy/boundary/failure)。如果后续 06 节的验收用例发生变更,本文档对应 Unit 的 Done 标准需要同步更新,避免两份文档各说各话。