feat(sdlc): approve_task/complete_task 对齐 PM 行为推进任务链

- approve_task: 解决 pending review_reject + git 提交 + _get_next_role→迭代边界→_create_next_task 推进链
- complete_task: 同步交付件 review_status 标记 approved,避免任务 completed 但交付件仍 pending 不一致
- 会话 agent 手动 approve 不再断链(下一角色任务照常自动创建)
This commit is contained in:
ymq 2026-08-21 23:39:15 +08:00
parent 3c34eedb04
commit 8eedff7af9

View File

@ -550,7 +550,12 @@ async def _h_reset_task_retry(sor, p, ctx):
async def _h_approve_task(sor, p, ctx):
"""审核通过任务PM 验收review → approved。"""
"""审核通过任务PM 验收review → approved + 对齐 PM 行为推进任务链。
对齐 pm_review_run approved 分支解决该任务 pending review_reject 问题
git 提交产出查下一角色并创建下一阶段任务含迭代边界检查这样会话 agent
手动 approve 不会让任务链断下一角色任务照常自动创建
"""
task_id = (p.get("task_id") or "").strip()
if not task_id:
return "需要任务ID"
@ -560,15 +565,61 @@ async def _h_approve_task(sor, p, ctx):
full = await _resolve_task_id(sor, task_id, pid)
if not full:
return f"任务不存在: {task_id}"
recs = await sor.R('pipeline_tasks', {'id': full})
if not recs:
return f"任务不存在: {task_id}"
task = recs[0]
task_role = getattr(task, 'role', '') or ''
title = getattr(task, 'title', '') or ''
comment = (p.get("comment") or "").strip()
from .task_capability import approve_task
ok, msg = await approve_task(full, pid, who="agent.pm",
agent_id=ctx.get("user_id", ""),
comment=(p.get("comment") or "").strip())
return f"OK: {msg}" if ok else f"ERROR: {msg}"
agent_id=ctx.get("user_id", ""), comment=comment)
if not ok:
return f"ERROR: {msg}"
# 解决该任务 pending 的 review_reject 问题(对齐 PM防永久堆积
await sor.sqlExe(
"UPDATE pipeline_agent_questions SET status='answered', answer=${a}$, "
"answer_source='role_agent', answered_by=${role}$ "
"WHERE task_id=${tid}$ AND status='pending' "
"AND (problem_type='review_reject' "
" OR ((problem_type IS NULL OR problem_type='') AND from_role IN ('pm','cockpit')))",
{"a": "角色已响应,审核通过", "role": task_role or "role_agent", "tid": full})
# git 提交产出(对齐 PM审核通过后统一提交workspace_dir 空或 git 失败不阻断 approve
workspace_dir = ctx.get("workspace_dir", "") or ""
if workspace_dir:
try:
from .agent_loop import _commit_repos_after_approve
await _commit_repos_after_approve(workspace_dir, title)
except Exception:
pass
# 推进任务链(对齐 PM查下一角色 + 迭代边界 + 创建下一阶段任务
from .agent_loop import _get_next_role, _create_next_task, _task_iteration_name
next_role = await _get_next_role(task_role, pid)
if next_role:
task_iter = _task_iteration_name(task)
if task_iter:
_it = await sor.sqlExe(
"SELECT status FROM sd_iterations WHERE project_id=${pid}$ AND iteration_name=${name}$",
{"pid": pid, "name": task_iter})
_it_status = getattr(_it[0], 'status', '') if _it else ''
if _it_status in ('completed', 'cancelled'):
return f"OK: {msg}(任务归属迭代「{task_iter}」已 {_it_status},链终止)"
next_tid, next_title = await _create_next_task(sor, pid, task, next_role, comment)
return f"OK: {msg};已创建下一阶段任务「{next_title}」({next_role}id={next_tid}"
return f"OK: {msg}(无下一角色,任务链终结)"
async def _h_complete_task(sor, p, ctx):
"""标记任务完成PMapproved/review → completed。"""
"""标记任务完成PMapproved/review → completed + 同步交付件标记 approved对齐 PM
对齐 pm_review_run review_complete 分支先把交付件 review_status pending approved
避免任务 state=completed 但交付件 review_status pending 的状态不一致
"""
task_id = (p.get("task_id") or "").strip()
if not task_id:
return "需要任务ID"
@ -578,6 +629,10 @@ async def _h_complete_task(sor, p, ctx):
full = await _resolve_task_id(sor, task_id, pid)
if not full:
return f"任务不存在: {task_id}"
await sor.sqlExe(
"UPDATE pipeline_deliverables SET review_status='approved', review_comment=${cm}$ "
"WHERE task_id=${tid}$ AND review_status='pending'",
{"cm": (p.get("comment") or "").strip(), "tid": full})
from .task_capability import complete_task
ok, msg = await complete_task(full, pid, who="agent.pm",
agent_id=ctx.get("user_id", ""))