327 lines
15 KiB
Python
327 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""能力工具注入:capability → 工具列表 从产线状态机规范 frontmatter 读取(可配置)。
|
||
|
||
三层:
|
||
1. 产线状态机规范(common/feature 等)frontmatter 声明 `capability: feature_capability` + `tools: [propose_feature, ...]`
|
||
—— 这是「配置」:哪个 capability 含哪些工具,改 frontmatter 即可增删,不用改代码。
|
||
2. 角色技能(roles/{role}/role)frontmatter 声明 `capability: feature_capability`
|
||
—— 角色声明「我需要这个能力」,系统据此注入该能力下的全部工具。
|
||
3. 本模块:工具 schema(分发模块/描述/参数)硬编码在此(代码不可避免),
|
||
但 capability→工具列表 由第 1 层 frontmatter 决定(可配置)。
|
||
|
||
系统上下文(project_id/iteration_id/who/agent_id)由分发函数自动注入,不暴露给 LLM。
|
||
"""
|
||
import inspect
|
||
import importlib
|
||
import logging
|
||
|
||
logger = logging.getLogger("pipeline")
|
||
|
||
|
||
# 工具 schema(分发模块 + 描述 + 参数)——代码层,不可避免。
|
||
# 每个工具名对应某个 capability 模块里的同名函数。参数只暴露 LLM 可见的业务参数,
|
||
# project_id/iteration_id/who/agent_id 由 exec 自动注入。
|
||
TOOL_SCHEMAS = {
|
||
# ── feature_capability ──
|
||
"propose_feature": {
|
||
"module": "feature_capability",
|
||
"description": "提出功能点(拆解需求提功能),落库 sd_features,status=proposed。每个业务域的功能都要提",
|
||
"params": {"feature_name": "功能名称", "description": "功能描述(输入/处理/输出/验收标准)",
|
||
"priority": "优先级 P0/P1/P2(默认 P2)", "feature_type": "功能类型(默认 new_feature)"},
|
||
"required": ["feature_name"],
|
||
},
|
||
"list_features": {
|
||
"module": "feature_capability",
|
||
"description": "列出功能清单(sd_features),可按状态/迭代过滤",
|
||
"params": {"status": "状态(可选 proposed/approved/...)", "iteration_id": "迭代ID(可选)"},
|
||
"required": [],
|
||
},
|
||
"approve_feature": {
|
||
"module": "feature_capability",
|
||
"description": "审批功能:proposed → approved",
|
||
"params": {"feature_id": "功能ID", "comment": "审批意见(可选)"},
|
||
"required": ["feature_id"],
|
||
},
|
||
"reject_feature": {
|
||
"module": "feature_capability",
|
||
"description": "驳回功能:proposed/approved → rejected",
|
||
"params": {"feature_id": "功能ID", "comment": "驳回原因"},
|
||
"required": ["feature_id"],
|
||
},
|
||
"start_feature": {
|
||
"module": "feature_capability",
|
||
"description": "开始开发功能:approved → in_progress",
|
||
"params": {"feature_id": "功能ID"},
|
||
"required": ["feature_id"],
|
||
},
|
||
"deliver_feature": {
|
||
"module": "feature_capability",
|
||
"description": "交付功能:in_progress → delivered",
|
||
"params": {"feature_id": "功能ID"},
|
||
"required": ["feature_id"],
|
||
},
|
||
"verify_feature": {
|
||
"module": "feature_capability",
|
||
"description": "验收功能:delivered → verified",
|
||
"params": {"feature_id": "功能ID"},
|
||
"required": ["feature_id"],
|
||
},
|
||
"reopen_feature": {
|
||
"module": "feature_capability",
|
||
"description": "重新打开功能:rejected/verified → proposed(回炉)",
|
||
"params": {"feature_id": "功能ID", "comment": "重开原因(可选)"},
|
||
"required": ["feature_id"],
|
||
},
|
||
# ── test_plan_capability ──
|
||
"create_test_plan": {
|
||
"module": "test_plan_capability",
|
||
"description": "创建测试计划,落库 sd_test_plans,status=draft",
|
||
"params": {"plan_name": "计划名称", "plan_type": "类型(默认 functional)", "scope": "测试范围",
|
||
"environment": "测试环境", "entry_criteria": "准入标准", "exit_criteria": "准出标准"},
|
||
"required": ["plan_name"],
|
||
},
|
||
"list_plans": {
|
||
"module": "test_plan_capability",
|
||
"description": "列出测试计划(sd_test_plans)",
|
||
"params": {"status": "状态(可选)"},
|
||
"required": [],
|
||
},
|
||
# ── test_case_capability ──
|
||
"create_case": {
|
||
"module": "test_case_capability",
|
||
"description": "创建测试用例,落库 sd_test_cases,status=pending",
|
||
"params": {"plan_id": "测试计划ID", "case_name": "用例名称", "case_type": "类型(默认 functional)",
|
||
"priority": "优先级(默认 P2)", "precondition": "前置条件", "steps": "执行步骤",
|
||
"expected_result": "预期结果"},
|
||
"required": ["plan_id", "case_name"],
|
||
},
|
||
"pass_case": {
|
||
"module": "test_case_capability",
|
||
"description": "用例通过:pending/fail → pass",
|
||
"params": {"case_id": "用例ID", "plan_id": "计划ID", "actual_result": "实际结果"},
|
||
"required": ["case_id", "plan_id"],
|
||
},
|
||
"fail_case": {
|
||
"module": "test_case_capability",
|
||
"description": "用例失败:pending/pass → fail(失败后应 report_bug 建 Bug)",
|
||
"params": {"case_id": "用例ID", "plan_id": "计划ID", "actual_result": "实际结果(与预期不符处)"},
|
||
"required": ["case_id", "plan_id"],
|
||
},
|
||
"skip_case": {
|
||
"module": "test_case_capability",
|
||
"description": "用例跳过:pending → skipped(注明原因)",
|
||
"params": {"case_id": "用例ID", "plan_id": "计划ID", "actual_result": "跳过原因"},
|
||
"required": ["case_id", "plan_id"],
|
||
},
|
||
"block_case": {
|
||
"module": "test_case_capability",
|
||
"description": "用例阻塞:pending → blocked(环境/依赖无法执行)",
|
||
"params": {"case_id": "用例ID", "plan_id": "计划ID", "actual_result": "阻塞原因"},
|
||
"required": ["case_id", "plan_id"],
|
||
},
|
||
"list_cases": {
|
||
"module": "test_case_capability",
|
||
"description": "列出测试用例(sd_test_cases)",
|
||
"params": {"plan_id": "计划ID", "status": "状态(可选)"},
|
||
"required": ["plan_id"],
|
||
},
|
||
# ── bug_capability ──
|
||
"report_bug": {
|
||
"module": "bug_capability",
|
||
"description": "上报 Bug,落库 sd_bugs,status=open(用例 fail 后应调用)",
|
||
"params": {"title": "Bug 标题", "description": "Bug 描述", "severity": "严重度 critical/major/minor(默认 major)",
|
||
"priority": "优先级(默认 P1)", "case_id": "关联用例ID(可选)", "step_name": "发现步骤(可选)"},
|
||
"required": ["title"],
|
||
},
|
||
"list_bugs": {
|
||
"module": "bug_capability",
|
||
"description": "列出 Bug(sd_bugs)",
|
||
"params": {"status": "状态(可选)", "severity": "严重度(可选)"},
|
||
"required": [],
|
||
},
|
||
"classify_task": {
|
||
"module": "bug_capability",
|
||
"description": "判断任务来源:new_dev(新开发)/bug_fix(修复Bug)/rework(质检重做)。develop 接任务后先调用,只有 bug_fix 才走 start_fix→fix_bug 状态机;rework/new_dev 不调 bug 工具",
|
||
"params": {"task_id": "任务ID(可选,默认当前任务)"},
|
||
"required": [],
|
||
},
|
||
"start_fix": {
|
||
"module": "bug_capability",
|
||
"description": "开始修复 Bug:open/confirmed → fixing(记录处理人)。回退重做/修复 Bug 任务里修完代码必须调用,open 状态也可直接修复(PM review_rollback 回退时不走 confirm_bug)",
|
||
"params": {"bug_id": "Bug ID", "assignee_id": "处理人(可选)"},
|
||
"required": ["bug_id"],
|
||
},
|
||
"fix_bug": {
|
||
"module": "bug_capability",
|
||
"description": "修复完成 Bug:fixing → fixed(附修复说明+提交)。修完代码必须调用,把对应 bug 标记 fixed,否则 PM 查不到 fixed bug 无法派重新部署、任务链断",
|
||
"params": {"bug_id": "Bug ID", "fix_description": "修复说明", "fix_commit": "修复提交(可选)"},
|
||
"required": ["bug_id"],
|
||
},
|
||
"verify_bug": {
|
||
"module": "bug_capability",
|
||
"description": "验证 Bug 修复:fixed → verified(记录验证人)",
|
||
"params": {"bug_id": "Bug ID", "verified_by": "验证人(可选)"},
|
||
"required": ["bug_id"],
|
||
},
|
||
"close_bug": {
|
||
"module": "bug_capability",
|
||
"description": "关闭 Bug:verified → closed",
|
||
"params": {"bug_id": "Bug ID"},
|
||
"required": ["bug_id"],
|
||
},
|
||
"confirm_bug": {
|
||
"module": "bug_capability",
|
||
"description": "确认 Bug:open → confirmed(确认缺陷有效,准备派发修复)",
|
||
"params": {"bug_id": "Bug ID"},
|
||
"required": ["bug_id"],
|
||
},
|
||
"reject_bug": {
|
||
"module": "bug_capability",
|
||
"description": "驳回 Bug:open/confirmed → rejected(附驳回原因,如误报/重复/非缺陷)",
|
||
"params": {"bug_id": "Bug ID", "comment": "驳回原因"},
|
||
"required": ["bug_id"],
|
||
},
|
||
"reopen_bug": {
|
||
"module": "bug_capability",
|
||
"description": "重开 Bug:closed/rejected → open(复测未通过或驳回后重新处理)",
|
||
"params": {"bug_id": "Bug ID", "comment": "重开原因(可选)"},
|
||
"required": ["bug_id"],
|
||
},
|
||
# ── deploy_capability ──
|
||
"configure_env": {
|
||
"module": "deploy_capability",
|
||
"description": "配置部署环境,落库 sd_deploy_envs,status=configured",
|
||
"params": {"env_type": "环境类型 test/prod", "host": "目标机主机", "user": "SSH 用户",
|
||
"deploy_path": "部署路径", "port": "SSH 端口(默认 22)", "ssh_key_path": "SSH 密钥路径(可选)",
|
||
"sudo_enabled": "是否 sudo(可选)", "python_path": "Python 路径(可选)", "db_host": "DB 主机(可选)",
|
||
"db_port": "DB 端口(可选)", "db_name": "DB 名(可选)", "db_user": "DB 用户(可选)",
|
||
"db_password": "DB 密码(可选)"},
|
||
"required": ["env_type", "host", "user", "deploy_path"],
|
||
},
|
||
"verify_env": {
|
||
"module": "deploy_capability",
|
||
"description": "验证部署环境可用:configured → verified",
|
||
"params": {"env_id": "环境ID"},
|
||
"required": ["env_id"],
|
||
},
|
||
"fail_env": {
|
||
"module": "deploy_capability",
|
||
"description": "标记部署环境不可用(部署前检查失败时调用)",
|
||
"params": {"env_id": "环境ID", "reason": "不可用原因"},
|
||
"required": ["env_id"],
|
||
},
|
||
"list_envs": {
|
||
"module": "deploy_capability",
|
||
"description": "列出部署环境(sd_deploy_envs)",
|
||
"params": {"env_type": "类型(可选 test/prod)", "status": "状态(可选)"},
|
||
"required": [],
|
||
},
|
||
# ── retrospective_capability(项目复盘:问题收集 + 技能提议)──
|
||
"project_retrospective_data": {
|
||
"module": "retrospective_capability",
|
||
"description": "取本项目全部问题素材(冒泡/退回重做/编排缺口/Bug 四类,含解决方法),复盘分析用",
|
||
"params": {},
|
||
"required": [],
|
||
},
|
||
"propose_skill": {
|
||
"module": "retrospective_capability",
|
||
"description": "提交技能提议(复盘产出,进技能管理建议列表待人工审核,不直接生效)",
|
||
"params": {"name": "技能名", "description": "一句话描述",
|
||
"content": "SKILL.md 草稿(头部 <!-- target: ... --> 定位,四段:触发条件/问题现象/根因/处理方法)"},
|
||
"required": ["name", "content"],
|
||
},
|
||
}
|
||
|
||
|
||
def _capability_to_tools_map(merged_skills):
|
||
"""从技能 frontmatter 的 capability + tools 声明,建立 capability → 工具名列表。
|
||
|
||
「可配置」的核心:产线状态机规范(common/feature 等)frontmatter 写
|
||
`capability: feature_capability` + `tools: [propose_feature, ...]`,
|
||
这里读取该声明。改 frontmatter 即可增删 capability 下的工具,不用改代码。
|
||
"""
|
||
m = {}
|
||
for skill in merged_skills.values():
|
||
cap = getattr(skill, "capability", "") or ""
|
||
tools = getattr(skill, "tools", []) or []
|
||
if not cap:
|
||
continue
|
||
m.setdefault(cap, [])
|
||
for t in tools:
|
||
if t not in m[cap]:
|
||
m[cap].append(t)
|
||
return m
|
||
|
||
|
||
def resolve_capability_tools(capabilities, merged_skills):
|
||
"""根据角色声明的 capability 集合 + 技能 frontmatter 映射,返回工具定义列表。
|
||
|
||
capabilities: 角色技能 frontmatter 声明的 capability 集合
|
||
merged_skills: 角色可见技能(get_merged 结果)
|
||
返回 [{name, description, params, required}, ...](去重)
|
||
"""
|
||
cap_map = _capability_to_tools_map(merged_skills)
|
||
tools = []
|
||
seen = set()
|
||
for cap in (capabilities or []):
|
||
for tool_name in cap_map.get(cap, []):
|
||
schema = TOOL_SCHEMAS.get(tool_name)
|
||
if not schema or tool_name in seen:
|
||
continue
|
||
seen.add(tool_name)
|
||
tools.append({
|
||
"name": tool_name,
|
||
"description": schema["description"],
|
||
"params": schema["params"],
|
||
"required": schema["required"],
|
||
})
|
||
return tools
|
||
|
||
|
||
async def exec_capability_tool(tool_name, params, ctx):
|
||
"""分发能力工具。ctx: {project_id, iteration_id, who, agent_id}。"""
|
||
schema = TOOL_SCHEMAS.get(tool_name)
|
||
if not schema:
|
||
return f"FAIL: 未注册的能力工具 {tool_name}"
|
||
|
||
p = dict(params or {})
|
||
p.setdefault("project_id", ctx.get("project_id", ""))
|
||
p.setdefault("iteration_id", ctx.get("iteration_id", ""))
|
||
p.setdefault("who", ctx.get("who", ""))
|
||
p.setdefault("agent_id", ctx.get("agent_id", ""))
|
||
p.setdefault("task_id", ctx.get("task_id", ""))
|
||
|
||
try:
|
||
# module 支持两种写法:裸名(如 'feature_capability',pipeline-service 包内)
|
||
# 或全路径(如 'pipeline_bidding.bid_tender_capability',产线自有能力包)。
|
||
# 产线能力模块放自己仓库:宿主不装该产线就零接触,不污染引擎包。
|
||
modname = schema['module']
|
||
if '.' not in modname:
|
||
modname = "pipeline_service." + modname
|
||
mod = importlib.import_module(modname)
|
||
fn = getattr(mod, tool_name)
|
||
sig = inspect.signature(fn)
|
||
kwargs = {k: v for k, v in p.items() if k in sig.parameters}
|
||
result = await fn(**kwargs)
|
||
if isinstance(result, tuple) and len(result) == 2:
|
||
ok, msg = result
|
||
return f"{'OK' if ok else 'FAIL'}: {msg}"
|
||
if isinstance(result, list):
|
||
if not result:
|
||
return "(空)"
|
||
lines = []
|
||
for item in result[:50]:
|
||
# dict 的键必须 item[k] 取(getattr(dict, k) 永远落默认值 →
|
||
# 2026-08-31 测试机实测:list_features 返回全空值,QC 功能落库核对失效)。
|
||
if isinstance(item, dict):
|
||
lines.append(str(item))
|
||
elif hasattr(item, "keys"):
|
||
lines.append(str({k: item[k] for k in item.keys()}))
|
||
else:
|
||
lines.append(str(item))
|
||
return "\n".join(lines)
|
||
return str(result)
|
||
except Exception as e:
|
||
logger.exception(f"exec_capability_tool {tool_name} failed")
|
||
return f"ERROR: {str(e)[:300]}"
|