Commit f7103fd723bee7a0795e63c6f5832bfdc08ade08

Authored by bboonnee
1 parent 41a0492e

Org Graph unification Phase 2c §7.1: add work-graph-node-review skill

New skills/work-graph-node-review/SKILL.md documents the methodology for
judging a self-planned Work Graph node's output (real structured content
vs. narrative prose standing in for it) -- distilled from the real
narrative-output incident (docs/todo/validator-test-script-narrative-
output-issue.md §6) and the conditional-not-assertive phrasing lesson
already applied in _NARRATION_RETRY_HINT. Scoped explicitly against
planner-output-review (Planner's fixed doc chain, task-level gate),
qa-task-validation (overall task acceptance), and skill-defect-remediation
(rewriting a Skill file itself) -- this one judges a single self-planned
node's output, nothing broader.

Wired into roles/planner_reviewer, roles/coder, roles/validator role.json
skills lists. .claude/agents/*.md left untouched -- that's a deploy-time
artifact compiled from role.json by agent_profile.deploy_agent_definitions(),
not hand-synced (confirmed stale already: coder.md is missing an even
earlier Phase 2a skill addition).

This completes Phase 2c (§2.1/§2.2/§6/§7.1 all implemented; §7.2's
analysis was completed earlier without needing a code change).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/todo/org-graph-unification-phase2-universal-self-planning-design.md
... ... @@ -5,7 +5,11 @@
5 5 **依赖**: 硬依赖 Phase 1(`docs/done/org-graph-unification-phase1-review-gate-design.md`)——已实现(commit `ea19c7d`,2026-08-08)。§9 接口核对清单已于 2026-08-12 逐条核对完成:三项全部吻合(`plan_and_review_documents()` 经 `_review_planner_output_with_retries()` 透传 `plan_documents()` 的 `{"run_id", "artifact_ids"}` 返回形状,含 `run_id`;`task.status` 新值确认是 `reviewing_planner_output`;`roles/planner_reviewer/role.json` 目前 `skills: ["planner-output-review", "unattended-agent-operations"]`,未预留 §7.1 新 skill 位置,Phase 2 实施时新增一行即可,不是不兼容)。
6 6 **状态**: Phase 2a(commit `86fc0bb`)+ Phase 2b(commit `7c46a2f`)已实现,2026-08-12。Phase 2c 的 Validator 自规划改造(§6 核心部分)已实现,2026-08-12:`validate_implementation_with_own_graph()` 改为 `plan_work_graph(conn, "validator", ..., append_to_run_id=run_id)` 追加进 Planner/Coder 共享的同一个 `work_graph_run`,终审 verdict 变成图内一个真实节点(`node_type='final_verdict'`,`judgment.kind='mechanical'`、`check_ref='verdict_says_pass'`,`on_fail.action='dynamic_loop_back'`——Validator 图里唯一允许用这个 action 的节点),硬门槛(脚本真实非零退出码强制 FAIL,不听 LLM 辩解)通过 `_verdict_node_build_prompt`/`_verdict_says_pass_check` 的共享可变状态闭包保留,Python 强制而非只是 prompt 里说说。`_VALIDATOR_MANIFEST`/`_VALIDATOR_DOC_PROMPTS`/`_VALIDATOR_VERDICT_PROMPT`/`_validator_prompt_builder` 保留但不再使用(同 `_CODER_PROMPT` 先例)。§7.2 的收口验证已完成分析(见 §10 Phase 2c 小节末尾:一旦 verdict 节点自己的 `dynamic_loop_back` 内部消化重试,`validate_implementation_with_own_graph()` 正常返回时 verdict 必为 PASS,`org_level_loop.py` 的旧粗粒度路由对这条新路径自然不可达,不需要显式新旧流程判断——决定保留旧分支代码不删,已在 `scheduler.py::finish_task_lifecycle` 加注释说明)。
7 7  
8   -**2026-08-13 §2.1/§2.2 已实施**:`work_graph_run.org_node` 列彻底删除,`ux_work_graph_run_active` 收紧为 `UNIQUE(task_id) WHERE status NOT IN ('done', 'failed')`('blocked' 故意算作活跃——恢复一个 blocked 的 run 现在总是走 `append_to_run_id` 追加进同一行,不会再 INSERT 新行)。新迁移函数 `schema.migrate_drop_work_graph_run_org_node()`(内部先幂等调用 `migrate_relax_org_node_constraints()`,再做 create-copy-drop-rename,`PRAGMA foreign_keys=OFF` 全程包裹,理由同 `migrate_add_reviewing_planner_output_status` 的真实重启事故记录——`work_graph_run` 是被多张表 REFERENCES 的父表)已接入 `backend_server.py::_ensure_schema()`。下游改动:`work_graph_plan.py`/`work_graph_fork.py`(含 `create_fork()`/`_reconstruct_build_prompt()`/`execute_fork()`,`fork_info["org_node"]` 改名 `fork_info["subagent_type"]`,源节点自己的执行身份)/`root_cause_trace.py` 的三处 INSERT 全部去掉 `org_node` 列;`work_graph_run_query.py`(`list_work_graph_runs`/`get_work_graph_run`)与 `task_query.py::get_task` 的返回形状从单值 `"org_node"` 改成 `"org_nodes_involved"`(从 `work_node_execution.subagent_type` 去重派生的列表,`org_node=` 查询参数语义从等值匹配改成 EXISTS 子查询),前端 `frontend/app.js` 对应改造(`getLatestRunForOrgNode`/`orgNodesLabel`/`renderOrgGraphTopology` 等消费点全部改用列表语义)。发现一处设计文档本节 §2.1.3 没预料到的遗漏点:`task_query.py::get_task` 也有同样的单值 `org_node` 读取,一并修完。`root_cause_trace.py::judge_root_cause_via_work_graph` 的 INSERT 有一个已知、记录在代码注释里的边界情况:索引收紧后,若这个函数被调到时对应 task 已有一个仍处于活跃/blocked 状态的共享 run,会撞索引——目前不是真实风险(这个函数唯一的调用方 `org_level_loop.run_fix_loop` 只在一个已经不可达的分支里被调用,且它自己的测试夹具里对应 task 从未预先插入过 work_graph_run 行),但如果未来重新接上这条路径需要重新评估。`python3 -m unittest discover -s src`(768 tests)+ `discover -s supervisor`(126 tests)+ 4 个 frontend smoke test 全绿。**仍未实施**:§7.1(新 skill `work-graph-node-review` + 三个 role.json 追加)。**2026-08-08 修订**:写作过程中,需求文档 §7 的四个开放问题已被用户直接拍板(详见该文档 §7 对应条目),其中两条推翻了本文档最初的设计判断——Coder/Validator 自规划改为"建议节点清单"而非完全自由(§3),`work_graph_run.org_node` 改为彻底移除而非保留(§2.1)——本版本已按拍板结果改写,不是原始草稿。**2026-08-08 追加**(同日,见需求文档新增 §3.1):FR-4 的目标被明确为"失败即进化"完整方法论(判断失败 → Reflection-review 动态诊断 → 系统性缺陷则先 Fix-Skill → 跳回执行),当时 §5 设计的 `dynamic_loop_back` 只覆盖第 1/2/4 步,第 3 步(Fix-Skill)是已知缺口。**2026-08-12 补齐**:§5 已扩写为完整四步设计(§5.4 Reflection-review 现在同时判断目标节点和缺陷性质,§5.5 新增 Fix-Skill,真实调用既有但从未真正投入使用的 `skill_update.py::update_skill()`),过程中发现两个既有事实(`skills:` frontmatter 只按名字动态加载内容、不需要重新部署;`org_level_loop.py` 的 `_ROUTE_FOR_CATEGORY` 从来没有 `skill_defect` 这个 key,`update_skill()` 在这个代码库里投产以来从未被真正调用过)——详见 §5 开头。由哪个身份执笔改 Skill 已通过 `AskUserQuestion` 确认(2026-08-12):统一用 Validator,见 §5.5。
  8 +**2026-08-13 §2.1/§2.2 已实施**:`work_graph_run.org_node` 列彻底删除,`ux_work_graph_run_active` 收紧为 `UNIQUE(task_id) WHERE status NOT IN ('done', 'failed')`('blocked' 故意算作活跃——恢复一个 blocked 的 run 现在总是走 `append_to_run_id` 追加进同一行,不会再 INSERT 新行)。新迁移函数 `schema.migrate_drop_work_graph_run_org_node()`(内部先幂等调用 `migrate_relax_org_node_constraints()`,再做 create-copy-drop-rename,`PRAGMA foreign_keys=OFF` 全程包裹,理由同 `migrate_add_reviewing_planner_output_status` 的真实重启事故记录——`work_graph_run` 是被多张表 REFERENCES 的父表)已接入 `backend_server.py::_ensure_schema()`。下游改动:`work_graph_plan.py`/`work_graph_fork.py`(含 `create_fork()`/`_reconstruct_build_prompt()`/`execute_fork()`,`fork_info["org_node"]` 改名 `fork_info["subagent_type"]`,源节点自己的执行身份)/`root_cause_trace.py` 的三处 INSERT 全部去掉 `org_node` 列;`work_graph_run_query.py`(`list_work_graph_runs`/`get_work_graph_run`)与 `task_query.py::get_task` 的返回形状从单值 `"org_node"` 改成 `"org_nodes_involved"`(从 `work_node_execution.subagent_type` 去重派生的列表,`org_node=` 查询参数语义从等值匹配改成 EXISTS 子查询),前端 `frontend/app.js` 对应改造(`getLatestRunForOrgNode`/`orgNodesLabel`/`renderOrgGraphTopology` 等消费点全部改用列表语义)。发现一处设计文档本节 §2.1.3 没预料到的遗漏点:`task_query.py::get_task` 也有同样的单值 `org_node` 读取,一并修完。`root_cause_trace.py::judge_root_cause_via_work_graph` 的 INSERT 有一个已知、记录在代码注释里的边界情况:索引收紧后,若这个函数被调到时对应 task 已有一个仍处于活跃/blocked 状态的共享 run,会撞索引——目前不是真实风险(这个函数唯一的调用方 `org_level_loop.run_fix_loop` 只在一个已经不可达的分支里被调用,且它自己的测试夹具里对应 task 从未预先插入过 work_graph_run 行),但如果未来重新接上这条路径需要重新评估。`python3 -m unittest discover -s src`(768 tests)+ `discover -s supervisor`(126 tests)+ 4 个 frontend smoke test 全绿。
  9 +
  10 +**2026-08-13 §7.1 已实施**:新增 `skills/work-graph-node-review/SKILL.md`——判断"自规划 Work Graph 节点(Coder/Validator/planner_reviewer 自己规划出的、声明 `judgment.kind="llm"` 的节点)的产出是不是真的、结构化的",不是叙述文字冒充产出;吸收了 `docs/todo/validator-test-script-narrative-output-issue.md` §6 的根因分析和 `_NARRATION_RETRY_HINT`(条件句而非断言句)的措辞教训,并明确跟 `planner-output-review`(Planner 固定文档链、任务级、Coder 开始前把关)、`qa-task-validation`(任务级整体验收)、`skill-defect-remediation`(改 Skill 文件本身)三者的边界——判断粒度是"这一个自规划节点",不是文档链/整任务/Skill 文件。已挂到 `roles/planner_reviewer`、`roles/coder`、`roles/validator` 三个 `role.json` 的 `skills` 列表(各追加一行,如 §9 核对清单当时预判)。未修改 `.claude/agents/*.md`——那是 `agent_profile.deploy_agent_definitions()` 从 `role.json` 编译出的部署产物,由 Deploy Supervisor 在真实 cutover 时重新生成,不是手动跟 `role.json` 同步维护的(观察到现有 `.claude/agents/coder.md` 已经缺了更早前 Phase 2a 加的 `design-dynamic-work-graph`,印证这个文件本来就不是手动保持同步的)。`python3 -m unittest discover -s src`(768 tests,含 `test_agent_profile.py`)全绿。
  11 +
  12 +**Phase 2c 至此全部完成**(§2.1/§2.2/§6 Validator 自规划改造/§7.1 均已实施;§7.2 收口分析已完成,决定不改代码,见上文)。**2026-08-08 修订**:写作过程中,需求文档 §7 的四个开放问题已被用户直接拍板(详见该文档 §7 对应条目),其中两条推翻了本文档最初的设计判断——Coder/Validator 自规划改为"建议节点清单"而非完全自由(§3),`work_graph_run.org_node` 改为彻底移除而非保留(§2.1)——本版本已按拍板结果改写,不是原始草稿。**2026-08-08 追加**(同日,见需求文档新增 §3.1):FR-4 的目标被明确为"失败即进化"完整方法论(判断失败 → Reflection-review 动态诊断 → 系统性缺陷则先 Fix-Skill → 跳回执行),当时 §5 设计的 `dynamic_loop_back` 只覆盖第 1/2/4 步,第 3 步(Fix-Skill)是已知缺口。**2026-08-12 补齐**:§5 已扩写为完整四步设计(§5.4 Reflection-review 现在同时判断目标节点和缺陷性质,§5.5 新增 Fix-Skill,真实调用既有但从未真正投入使用的 `skill_update.py::update_skill()`),过程中发现两个既有事实(`skills:` frontmatter 只按名字动态加载内容、不需要重新部署;`org_level_loop.py` 的 `_ROUTE_FOR_CATEGORY` 从来没有 `skill_defect` 这个 key,`update_skill()` 在这个代码库里投产以来从未被真正调用过)——详见 §5 开头。由哪个身份执笔改 Skill 已通过 `AskUserQuestion` 确认(2026-08-12):统一用 Validator,见 §5.5。
9 13  
10 14 ---
11 15  
... ...
roles/coder/role.json
... ... @@ -15,7 +15,8 @@
15 15 "code-agent-engineering",
16 16 "debugging-toolkit",
17 17 "software-planning-execution",
18   - "design-dynamic-work-graph"
  18 + "design-dynamic-work-graph",
  19 + "work-graph-node-review"
19 20 ],
20 21 "applications": {
21 22 "mcp_servers": []
... ...
roles/planner_reviewer/role.json
... ... @@ -8,7 +8,7 @@
8 8 },
9 9 "context": { "memory": "project" },
10 10 "model": "claude-sonnet-5",
11   - "skills": ["planner-output-review", "unattended-agent-operations"],
  11 + "skills": ["planner-output-review", "work-graph-node-review", "unattended-agent-operations"],
12 12 "applications": { "mcp_servers": [] },
13 13 "workflows": { "dynamic_planning": "none" },
14 14 "permissions": {
... ...
roles/validator/role.json
... ... @@ -15,7 +15,8 @@
15 15 "unattended-agent-operations",
16 16 "design-dynamic-work-graph",
17 17 "unknown-scope-defect-sweep",
18   - "skill-defect-remediation"
  18 + "skill-defect-remediation",
  19 + "work-graph-node-review"
19 20 ],
20 21 "applications": {
21 22 "mcp_servers": []
... ...
skills/work-graph-node-review/SKILL.md 0 → 100644
  1 +---
  2 +name: work-graph-node-review
  3 +description: |
  4 + 给任何自规划 Work Graph 节点(Coder/Validator/planner_reviewer 自己规划出的、声明
  5 + judgment.kind="llm" 的节点)判断"这一个节点的产出是不是真的、结构化的、完成了它被要求做的
  6 + 事",而不是叙述文字/进度汇报冒充产出。这是节点级判断,不是任务级验收——判断粒度是"这一个
  7 + 节点",不是"整个任务"。
  8 +version: 1.0.0
  9 +tags:
  10 + - review
  11 + - judgment
  12 + - work-graph
  13 + - self-planning
  14 +---
  15 +
  16 +# 自规划节点产出复核
  17 +
  18 +## 我在做什么、不做什么
  19 +
  20 +我判断的是 Work Graph 里**一个具体节点**的产出是否合格——这个节点是 Coder/Validator/
  21 +planner_reviewer 自己规划出来的(`task_prompt` 字段描述它该做什么),不是 Planner 固定文档链
  22 +上的 brd/prd/sdd/benchmark(那是 `planner-output-review` 的职责,判断粒度是整批文档、发生在
  23 +Coder 开始之前一次性把关);我也不判断"整个任务是否验收通过"(那是 `qa-task-validation` 的
  24 +职责,任务级)。我的判断只回答一个问题:**这一个节点,针对它自己的 `task_prompt`,给出的
  25 +`content` 是不是真的完成了要求的事**。
  26 +
  27 +我是只读判断,不执行代码、不修改文件——如果节点本身需要机械验证(比如脚本是否真的能编译/运
  28 +行),那用 `judgment.kind="mechanical"`,不是我;我处理的是 `judgment.kind="llm"` 的场景,
  29 +即"这份内容好不好"只能靠语义判断,没有客观对错的机械检查方式。
  30 +
  31 +## 判断标准:是不是叙述文字冒充产出
  32 +
  33 +**真实反复复现过的一类系统性失败**(`docs/todo/validator-test-script-narrative-output-issue.md`
  34 +§6 的完整根因记录):要求"先用工具探索、再产出最终内容"的节点,模型探索完之后的收尾话术天然
  35 +倾向"汇报我刚才做了/启动了什么",而不是切换成"直接吐出最终内容"模式——即使节点自己的提示词
  36 +已经写了"不要写叙述文字",这句话作为附加在提示词末尾的格式规则,不足以覆盖"多轮工具调用之后
  37 +的收尾习惯"这个更底层的行为模式。真实复现过的原文片段:
  38 +
  39 +- "Launched the workflow in the background, run ID wf_.... I'll report results once it
  40 + completes; no further action is needed right now."
  41 +- "The workflow completed. It authored `test_....js` — an independent mechanical test for
  42 + ..."(描述"我做了什么",不是测试脚本本身的内容)
  43 +
  44 +这些文本的共同特征:**在描述"我刚才启动/完成了什么动作",而不是产出该动作本来应该产生的
  45 +内容本身**。判断时把这个节点的 `task_prompt` 当作唯一的标尺——它要求的是代码、是测试输出、
  46 +是结构化分析,产出就必须直接"是"那个东西,而不是关于"我如何得到那个东西"的说明。单独出现的
  47 +词("workflow"、"launched")不构成判断依据本身——判断的是**整段内容的性质**,不要因为内容里
  48 +合理提到了"运行"、"执行"这类词就误判。
  49 +
  50 +## 定位到具体缺口,不要给笼统判断
  51 +
  52 +`ok=false` 时,`issues` 里每一条都要能回答"内容里具体哪一部分不对/缺什么",不是"整体上不够
  53 +好"这种不可操作的结论。好的判断形状是:"task_prompt 要求给出可运行的测试脚本,但 content 的
  54 +开头三段全是进度汇报式散文,直到第四段才出现看起来像代码的内容,且缺少必要的 import"——具体
  55 +到能让重新生成时明确知道该改什么。
  56 +
  57 +## 措辞上的教训:用条件句,不用断言句
  58 +
  59 +`_NARRATION_RETRY_HINT`(`src/e2e_pipeline.py`)在给重试反馈时刻意用"If your previous output
  60 +was a narrative description..."这种条件句,不是"Your output was a narrative description"这种
  61 +断言句——因为这个判断本身有误判空间:一段内容也可能只是普通的语法笔误导致机械检查失败,不是
  62 +真的叙述文字。判断时同样适用这个原则:如果不能确定内容性质就是叙述文字而不是别的问题(比如
  63 +格式错误、遗漏字段),`issues` 里的措辞也应该描述"观察到的具体现象",而不是给出一个可能张冠
  64 +李戴的定性指控。
  65 +
  66 +## 跟 `_NODE_REVIEW_JUDGE_PROMPT` 的关系
  67 +
  68 +`work_graph_plan.py::_NODE_REVIEW_JUDGE_PROMPT` 是这份方法论压缩成的、可以直接作为
  69 +`judgment.judge_prompt` 使用的机械提示词常量,被 `_CODER_NODE_CATALOG` 的 `unit_test`/
  70 +`integration_check` 条目引用为 `suggested_judgment` 默认值。这份 skill 不是重复那个常量,而是
  71 +它背后"为什么这么判断、真实失败案例长什么样"的方法论沉淀——规划节点时如果需要自己写一个
  72 +`judge_prompt`(而不是直接复用目录里的建议),应该参考这里的判断标准,而不是从零发明一套。
  73 +
  74 +## 输出格式
  75 +
  76 +`ok`(布尔)+ `issues`(字符串列表,`ok=false` 时每条都指向具体缺口)。不改写节点自己的
  77 +`content`,不建议节点该怎么重新生成——那是 `on_fail.action` 触发的重试/回退机制的事,我只
  78 +负责判断这一次的产出合不合格。
... ...