# -*- 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": [], }, } 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]: if hasattr(item, "keys"): lines.append(str({k: getattr(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]}"