diff --git a/pipeline_core/skill_loader.py b/pipeline_core/skill_loader.py index 5ee78fb..01cf4d8 100644 --- a/pipeline_core/skill_loader.py +++ b/pipeline_core/skill_loader.py @@ -25,6 +25,7 @@ pipeline-core: Skill Loader — 六级可插拔技能隔离 import logging import os +import re from dataclasses import dataclass, field from typing import Dict, List, Optional @@ -280,18 +281,29 @@ class SkillLoader: role: str = "", project_id: str = "", org_id: str = "", user_id: str = "", max_skills: int = 5) -> List[Skill]: - """根据用户输入匹配相关技能(trigger_keywords + scope 加权)""" + """根据用户输入匹配相关技能(trigger_keywords + 技能名拆分 + scope 加权)。 + + 兼容 Hermes 技能格式:多数 Hermes 技能只有 trigger_conditions(自然语言)而无 + trigger_keywords,因此额外用技能名拆分匹配(module-development-spec → module/development/spec) + 兜底,保证英文技能名也能被中文输入触达。 + """ all_skills = list(self.get_merged(pipeline_id, role, project_id, org_id, user_id).values()) scored = [] user_lower = user_input.lower() for skill in all_skills: score = 0 + # 1. 显式 trigger_keywords(中文/英文关键词) for kw in skill.trigger_keywords: if kw.lower() in user_lower: - score += 1 + score += 3 + # 2. 技能名整体匹配 if skill.name.lower() in user_lower: score += 2 - # scope 优先级加权 + # 3. 技能名拆分匹配(module-development-spec → module/development/spec) + for part in re.split(r'[-_]+', skill.name.lower()): + if len(part) >= 3 and part in user_lower: + score += 1 + # 4. scope 优先级加权 score += SCOPE_PRIORITY.get(skill.scope, 0) if score > 0: scored.append((score, skill)) @@ -322,10 +334,16 @@ class SkillLoader: if user_input: skills = self.get_by_trigger(user_input, pipeline_id, role, project_id, org_id, user_id, max_skills) + if not skills: + # 匹配不到时兜底:按 scope 优先级 + 名字稳定排序,避免目录层为空 + merged = self.get_merged(pipeline_id, role, project_id, org_id, user_id) + skills = sorted(merged.values(), + key=lambda s: (SCOPE_PRIORITY.get(s.scope, 9), s.name)) + skills = skills[:max_skills] else: merged = self.get_merged(pipeline_id, role, project_id, org_id, user_id) skills = sorted(merged.values(), - key=lambda s: SCOPE_PRIORITY.get(s.scope, 9)) + key=lambda s: (SCOPE_PRIORITY.get(s.scope, 9), s.name)) skills = skills[:max_skills] if not skills: