diff --git a/pipeline_service/sdlc_ability.py b/pipeline_service/sdlc_ability.py index 5027ff9..67f0771 100644 --- a/pipeline_service/sdlc_ability.py +++ b/pipeline_service/sdlc_ability.py @@ -121,6 +121,36 @@ SDL_TOOLS = [ parameters={"title": "Bug标题", "description": "描述", "severity": "严重程度"}, category="task", ), + ToolDefinition( + name="propose_feature", + description="提出新功能/需求(用户提出新需求或需求拆解时用)", + parameters={"feature_name": "功能名称", "description": "功能描述", "iteration_id": "所属迭代(可选)", "priority": "优先级(P0/P1/P2/P3,默认P2)"}, + category="feature", + ), + ToolDefinition( + name="list_features", + description="查看当前项目功能列表", + parameters={"status": "按状态筛选(可选)"}, + category="feature", + ), + ToolDefinition( + name="approve_feature", + description="审批通过功能(需求评审通过)", + parameters={"feature_id": "功能ID"}, + category="feature", + ), + ToolDefinition( + name="reject_feature", + description="驳回功能(附意见)", + parameters={"feature_id": "功能ID", "comment": "驳回意见"}, + category="feature", + ), + ToolDefinition( + name="verify_feature", + description="验证功能(验收通过)", + parameters={"feature_id": "功能ID"}, + category="feature", + ), ] @@ -135,7 +165,8 @@ SDL_PROMPT = """你是「开发产线」的驾驶舱 agent,负责软件项目 - 用户报告异常/故障 → 先用 diagnose_project 定位根因,再用 task_detail 查详情。 - 仓库管理 → add_repo / list_repos / clone_repo。 - 发现卡点(任务卡死、审核超时、失败、僵尸 claimed_by)时,主动定位根因并推动修复。 -- 发现「待回答问题」> 0 或角色提问时,加载 team-communication 技能按冒泡链处理(list_questions 列问题 → 能答则 answer_question → 不能答 escalate_question 沿冒泡路径转下一个处理方)。""" +- 发现「待回答问题」> 0 或角色提问时,加载 team-communication 技能按冒泡链处理(list_questions 列问题 → 能答则 answer_question → 不能答 escalate_question 沿冒泡路径转下一个处理方)。 +- 功能/需求管理 → 用户提新需求时 propose_feature;查功能清单 list_features;需求评审/验收时 approve_feature / reject_feature / verify_feature(状态机规范见 feature 技能)。""" # ── 产线角色集(可插拔,替代硬编码 ROLE_SPECIFICS/ROLE_ALIASES/ROLE_CHAIN) ── @@ -602,6 +633,101 @@ async def _h_add_bug(sor, p, ctx): return f"OK: 已提交Bug: {title}" +async def _resolve_feature_id(sor, fid): + """功能 ID 前缀兜底:精确匹配失败且入参≥6位时,用 LIKE 前缀解析回完整 ID。""" + if not fid: + return "" + recs = await sor.sqlExe( + "SELECT id FROM sd_features WHERE id=${fid}$", {"fid": fid}) + if recs: + return getattr(recs[0], "id", "") + if len(fid) >= 6: + recs = await sor.sqlExe( + "SELECT id FROM sd_features WHERE id LIKE ${prefix}$ LIMIT 1", + {"prefix": fid + "%"}) + if recs: + return getattr(recs[0], "id", "") + return "" + + +async def _h_propose_feature(sor, p, ctx): + pid = ctx.get("project_id", "") + if not pid: + return "请先切换到项目" + name = (p.get("feature_name", "") or "").strip() + if not name: + return "需要功能名称" + from .feature_capability import propose_feature + ok, fid = await propose_feature( + project_id=pid, + feature_name=name, + description=p.get("description", "") or "", + iteration_id=p.get("iteration_id", "") or "", + priority=p.get("priority", "P2") or "P2", + created_by=ctx.get("user_id", "") or "", + who="agent.main_agent", + ) + if not ok: + return f"ERROR: {fid}" + return f"OK: 已提出功能「{name}」(id={fid},状态 proposed)" + + +async def _h_list_features(sor, p, ctx): + pid = ctx.get("project_id", "") + if not pid: + return "请先切换到项目" + status = (p.get("status", "") or "").strip() + sql = "SELECT id, feature_name, status, priority, iteration_id FROM sd_features WHERE project_id=${pid}$" + params = {"pid": pid} + if status: + sql += " AND status=${status}$" + params["status"] = status + sql += " ORDER BY created_at DESC LIMIT 30" + recs = await sor.sqlExe(sql, params) + if not recs: + return "暂无功能" + lines = [] + for r in recs: + lines.append( + f"- [{getattr(r, 'status', '?')}][{getattr(r, 'priority', '') or ''}] " + f"{getattr(r, 'feature_name', '')} (id={getattr(r, 'id', '')})") + return "\n".join(lines) + + +async def _h_approve_feature(sor, p, ctx): + fid = (p.get("feature_id", "") or "").strip() + if not fid: + return "需要功能ID" + pid = ctx.get("project_id", "") + fid = await _resolve_feature_id(sor, fid) or fid + from .feature_capability import approve_feature + ok, msg = await approve_feature(fid, pid, who="agent.main_agent") + return f"OK: {msg}" if ok else f"ERROR: {msg}" + + +async def _h_reject_feature(sor, p, ctx): + fid = (p.get("feature_id", "") or "").strip() + if not fid: + return "需要功能ID" + pid = ctx.get("project_id", "") + fid = await _resolve_feature_id(sor, fid) or fid + from .feature_capability import reject_feature + ok, msg = await reject_feature(fid, pid, who="agent.main_agent", + comment=p.get("comment", "") or "") + return f"OK: {msg}" if ok else f"ERROR: {msg}" + + +async def _h_verify_feature(sor, p, ctx): + fid = (p.get("feature_id", "") or "").strip() + if not fid: + return "需要功能ID" + pid = ctx.get("project_id", "") + fid = await _resolve_feature_id(sor, fid) or fid + from .feature_capability import verify_feature + ok, msg = await verify_feature(fid, pid, who="agent.main_agent") + return f"OK: {msg}" if ok else f"ERROR: {msg}" + + # ── 注册能力包 ── SDL_HANDLERS = { @@ -620,6 +746,11 @@ SDL_HANDLERS = { "clone_repo": _h_clone_repo, "check_progress": _h_check_progress, "add_bug": _h_add_bug, + "propose_feature": _h_propose_feature, + "list_features": _h_list_features, + "approve_feature": _h_approve_feature, + "reject_feature": _h_reject_feature, + "verify_feature": _h_verify_feature, }