feat: PM 审核新增 review_rollback 决策——测试 Bug 后按问题内容回退到任一阶段,回退点之后的关联任务全部作废并重做
This commit is contained in:
parent
ab776d0c37
commit
a00ba32430
@ -462,6 +462,7 @@ __REPO_STATE__
|
|||||||
批准:{"action":"review_approve","comment":"审核意见","next_task_title":"下阶段标题","next_task_description":"描述"}
|
批准:{"action":"review_approve","comment":"审核意见","next_task_title":"下阶段标题","next_task_description":"描述"}
|
||||||
驳回:{"action":"review_reject","comment":"原因","questions":"修改要求"}
|
驳回:{"action":"review_reject","comment":"原因","questions":"修改要求"}
|
||||||
完成:{"action":"review_complete","comment":"总结"}
|
完成:{"action":"review_complete","comment":"总结"}
|
||||||
|
回退:{"action":"review_rollback","rollback_role":"develop","comment":"回退原因"}
|
||||||
|
|
||||||
审核标准:
|
审核标准:
|
||||||
- requirement:需求是否清晰完整可量化(含部署环境需求是否明确)
|
- requirement:需求是否清晰完整可量化(含部署环境需求是否明确)
|
||||||
@ -469,7 +470,11 @@ __REPO_STATE__
|
|||||||
- develop:必须用 git_status/read_file 检查代码是否实际写入仓库
|
- develop:必须用 git_status/read_file 检查代码是否实际写入仓库
|
||||||
- deploy_test:测试环境部署配置完整、服务可访问
|
- deploy_test:测试环境部署配置完整、服务可访问
|
||||||
- test:测试覆盖充分、发现问题记录完整
|
- test:测试覆盖充分、发现问题记录完整
|
||||||
- deploy_prod:生产部署配置完整、可一键部署、有回滚方案"""
|
- deploy_prod:生产部署配置完整、可一键部署、有回滚方案
|
||||||
|
|
||||||
|
## 回退规则(test 阶段)
|
||||||
|
- 测试提交 Bug 后,零散 Bug → 走 Bug 闭环(report_bug→fix_bug→verify_bug),不打断任务链。
|
||||||
|
- 系统性缺陷(Bug 多/严重、设计有缺陷、需求理解错、部署有问题)→ review_rollback,rollback_role 指定回退目标阶段(develop/design/requirement/deploy_test),回退点之后的关联任务将全部作废。"""
|
||||||
|
|
||||||
|
|
||||||
QC_SYSTEM_PROMPT = """你是质量控制工程师(QC)。对交付件做合规检查和质量检查,不合规直接退回重做。
|
QC_SYSTEM_PROMPT = """你是质量控制工程师(QC)。对交付件做合规检查和质量检查,不合规直接退回重做。
|
||||||
@ -668,6 +673,81 @@ def _task_iteration_name(task):
|
|||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
async def _rollback_task_chain(sor, project_id, task_id, rollback_role, comment):
|
||||||
|
"""回退:追溯任务链(previous_task_id),作废回退目标及之后的任务,创建回退目标的新任务。
|
||||||
|
|
||||||
|
回退点之后的关联任务全部作废(state=cancelled),回退目标阶段重新做。
|
||||||
|
"""
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
# 追溯任务链(previous_task_id 往前),得到 [最早 ... 当前]
|
||||||
|
chain = []
|
||||||
|
cur_id = task_id
|
||||||
|
visited = set()
|
||||||
|
while cur_id and cur_id not in visited:
|
||||||
|
visited.add(cur_id)
|
||||||
|
recs = await sor.R('pipeline_tasks', {'id': cur_id})
|
||||||
|
if not recs:
|
||||||
|
break
|
||||||
|
t = recs[0]
|
||||||
|
chain.append(t)
|
||||||
|
try:
|
||||||
|
p = json.loads(getattr(t, 'params', '{}') or '{}')
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
p = {}
|
||||||
|
cur_id = p.get('previous_task_id') or ''
|
||||||
|
chain.reverse()
|
||||||
|
|
||||||
|
# 定位回退目标在链上的位置
|
||||||
|
target_idx = -1
|
||||||
|
for i, t in enumerate(chain):
|
||||||
|
if _normalize_role(getattr(t, 'role', '')) == rollback_role:
|
||||||
|
target_idx = i
|
||||||
|
break
|
||||||
|
if target_idx < 0:
|
||||||
|
return {"status": "error", "task_id": task_id,
|
||||||
|
"error": f"任务链上找不到回退目标角色 {rollback_role},可用:"
|
||||||
|
+ "、".join(_normalize_role(getattr(t, 'role', '')) for t in chain)}
|
||||||
|
|
||||||
|
target_task = chain[target_idx]
|
||||||
|
prev_task = chain[target_idx - 1] if target_idx > 0 else None
|
||||||
|
|
||||||
|
# 作废回退目标及之后的任务(含回退目标本身)
|
||||||
|
cancelled = []
|
||||||
|
for t in chain[target_idx:]:
|
||||||
|
tid = getattr(t, 'id', '')
|
||||||
|
await sor.sqlExe(
|
||||||
|
"UPDATE pipeline_tasks SET state='cancelled', claimed_by=NULL, updated_at=NOW() WHERE id=${tid}$",
|
||||||
|
{"tid": tid})
|
||||||
|
cancelled.append(tid)
|
||||||
|
|
||||||
|
# 创建回退目标的新任务(继承回退目标任务 params,记录回退信息,previous 指向前一任务)
|
||||||
|
try:
|
||||||
|
tp = json.loads(getattr(target_task, 'params', '{}') or '{}')
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
tp = {}
|
||||||
|
new_params = {**tp, 'rollback_from': task_id, 'rollback_comment': comment}
|
||||||
|
if prev_task:
|
||||||
|
new_params['previous_task_id'] = getattr(prev_task, 'id', '')
|
||||||
|
new_params['previous_role'] = _normalize_role(getattr(prev_task, 'role', ''))
|
||||||
|
else:
|
||||||
|
new_params.pop('previous_task_id', None)
|
||||||
|
new_params.pop('previous_role', None)
|
||||||
|
|
||||||
|
new_tid = getID()
|
||||||
|
new_title = f"{getattr(target_task, 'title', '') or '任务'}(回退重做)"
|
||||||
|
await sor.C('pipeline_tasks', {
|
||||||
|
'id': new_tid, 'tenant_id': project_id, 'pipeline_id': 'role_task',
|
||||||
|
'owner_id': 'pm', 'title': new_title,
|
||||||
|
'params': json.dumps(new_params, ensure_ascii=False),
|
||||||
|
'role': rollback_role, 'state': 'submitted', 'claimed_by': None,
|
||||||
|
})
|
||||||
|
await sor.sqlExe("COMMIT", {})
|
||||||
|
logger.info(f"_rollback_task_chain: task={task_id} -> rollback {rollback_role}, "
|
||||||
|
f"cancelled={len(cancelled)} 任务, new_task={new_tid}")
|
||||||
|
return {"status": "rollback", "task_id": task_id, "rollback_role": rollback_role,
|
||||||
|
"cancelled": cancelled, "new_task_id": new_tid, "comment": comment}
|
||||||
|
|
||||||
|
|
||||||
# ── Agent 工具执行 ──
|
# ── Agent 工具执行 ──
|
||||||
|
|
||||||
def _parse_agent_action(raw):
|
def _parse_agent_action(raw):
|
||||||
@ -1144,7 +1224,7 @@ async def pm_review_run(project_id, agent_id=None, model_name=None):
|
|||||||
msgs.append({"role": "user", "content":
|
msgs.append({"role": "user", "content":
|
||||||
"已检查足够信息。现在必须立即收尾:"
|
"已检查足够信息。现在必须立即收尾:"
|
||||||
"若本任务审批通过后需拆分为多个后续任务,现在用 create_tasks 一次性派发;"
|
"若本任务审批通过后需拆分为多个后续任务,现在用 create_tasks 一次性派发;"
|
||||||
"随后必须立即输出 review_approve / review_reject / review_complete 三者之一,"
|
"随后必须立即输出 review_approve / review_reject / review_complete / review_rollback 之一,"
|
||||||
"禁止再调用其它工具。"})
|
"禁止再调用其它工具。"})
|
||||||
try:
|
try:
|
||||||
raw = await llm_call_msgs(msgs, model=model_name, temperature=0.3, org_id=org_id)
|
raw = await llm_call_msgs(msgs, model=model_name, temperature=0.3, org_id=org_id)
|
||||||
@ -1165,6 +1245,9 @@ async def pm_review_run(project_id, agent_id=None, model_name=None):
|
|||||||
elif act.get('action') == 'review_complete':
|
elif act.get('action') == 'review_complete':
|
||||||
decision = {'status': 'completed', 'comment': act.get('comment', '')}
|
decision = {'status': 'completed', 'comment': act.get('comment', '')}
|
||||||
break
|
break
|
||||||
|
elif act.get('action') == 'review_rollback':
|
||||||
|
decision = {'status': 'rollback', 'rollback_role': act.get('rollback_role', ''), 'comment': act.get('comment', '')}
|
||||||
|
break
|
||||||
elif act.get('action') == 'tool_call':
|
elif act.get('action') == 'tool_call':
|
||||||
tool = act.get('tool', '')
|
tool = act.get('tool', '')
|
||||||
params = act.get('params', {})
|
params = act.get('params', {})
|
||||||
@ -1240,6 +1323,28 @@ async def pm_review_run(project_id, agent_id=None, model_name=None):
|
|||||||
await reject_task(task_id, project_id, who="agent.pm", agent_id=agent_id, comment=comment)
|
await reject_task(task_id, project_id, who="agent.pm", agent_id=agent_id, comment=comment)
|
||||||
return {"status": "rejected", "task_id": task_id, "comment": comment, "question": rejection_q}
|
return {"status": "rejected", "task_id": task_id, "comment": comment, "question": rejection_q}
|
||||||
|
|
||||||
|
elif status == 'rollback':
|
||||||
|
rollback_role = _normalize_role((decision.get('rollback_role') or '').strip())
|
||||||
|
if not rollback_role:
|
||||||
|
# 未指定回退目标 → 视为驳回重审
|
||||||
|
from .task_capability import reject_task
|
||||||
|
await reject_task(task_id, project_id, who="agent.pm", agent_id=agent_id,
|
||||||
|
comment=(comment or '回退目标未指定'))
|
||||||
|
return {"status": "rejected", "task_id": task_id,
|
||||||
|
"comment": "回退目标未指定,已驳回重审"}
|
||||||
|
# 记 PM 审核交付件(回退决策)
|
||||||
|
from appPublic.uniqueID import getID as _getID
|
||||||
|
pm_did = _getID()
|
||||||
|
await sor.C("pipeline_deliverables", {
|
||||||
|
"id": pm_did, "project_id": project_id, "task_id": task_id,
|
||||||
|
"deliverable_type": "pm_review", "title": f"PM审核(回退):{title}",
|
||||||
|
"content": json.dumps(decision, ensure_ascii=False),
|
||||||
|
"file_path": os.path.join(workspace_dir, 'deliverables', 'pm', f"{task_id}_rollback.md"),
|
||||||
|
"quality_score": 0, "review_status": "rejected", "created_by": agent_id or "pm",
|
||||||
|
})
|
||||||
|
# 回退:作废回退点及之后的任务,创建回退目标的新任务
|
||||||
|
return await _rollback_task_chain(sor, project_id, task_id, rollback_role, comment)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# review_complete(项目完成/无下一阶段):同步把交付件标记 approved,
|
# review_complete(项目完成/无下一阶段):同步把交付件标记 approved,
|
||||||
# 避免任务 state=completed 但交付件 review_status 仍 pending 的状态不一致。
|
# 避免任务 state=completed 但交付件 review_status 仍 pending 的状态不一致。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user