add skill_search SOP to tracked memory files

This commit is contained in:
Liang Jiaqing
2026-03-14 20:33:54 +08:00
parent afa288f240
commit bbde5399cf
5 changed files with 401 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
"""CLI 入口: python -m skill_search"""
from __future__ import annotations
import argparse, json, sys
from .engine import SearchResult, SkillSearchError, detect_environment, search, get_stats
# ── 格式化 ───────────────────────────────────────────────
def format_results(results: list[SearchResult], env: dict, query: str) -> str:
lines = [f'🔍 搜索: "{query}"',
f"🖥️ 环境: {env.get('os','?')} / {env.get('shell','?')} / {', '.join(env.get('runtimes',[]))}",
f"📊 找到 {len(results)} 个匹配结果\n"]
if not results:
lines.append("未找到匹配的 skill。试试其他关键词")
return "\n".join(lines)
for i, r in enumerate(results, 1):
s = r.skill
safe_icon = "🟢" if s.autonomous_safe else "🔴"
score_bar = "" * int(r.final_score * 10) + "" * (10 - int(r.final_score * 10))
lines += [
f"{''*60}",
f"#{i} {safe_icon} {s.name}",
f" 路径: {s.key}",
f" 类别: {s.category} | 标签: {', '.join(s.tags[:5])}",
f" 摘要: {s.one_line_summary}",
f" 评分: [{score_bar}] {r.final_score:.2f} (相关={r.relevance:.2f} 质量={r.quality:.1f})",
f" 清晰={s.clarity} 完整={s.completeness} 可操作={s.actionability} | 形式={s.form}",
]
if r.match_reasons:
lines.append(f" 匹配: {' | '.join(r.match_reasons[:3])}")
if r.warnings:
lines.extend(f" {w}" for w in r.warnings)
lines.append("")
lines.append(f"{''*60}")
return "\n".join(lines)
def format_results_json(results: list[SearchResult]) -> list[dict]:
out = []
for r in results:
s = r.skill
out.append({
"rank": len(out) + 1, "key": s.key, "name": s.name,
"category": s.category, "tags": s.tags,
"description": s.description, "one_line_summary": s.one_line_summary,
"scores": {"final": round(r.final_score, 3), "relevance": round(r.relevance, 3),
"quality": round(r.quality, 1), "clarity": s.clarity,
"completeness": s.completeness, "actionability": s.actionability},
"safety": {"autonomous_safe": s.autonomous_safe, "blast_radius": s.blast_radius,
"requires_credentials": s.requires_credentials,
"data_exposure": s.data_exposure, "effect_scope": s.effect_scope},
"platform": {"os": s.os, "runtimes": s.runtimes, "tools": s.tools, "services": s.services},
"warnings": r.warnings, "match_reasons": r.match_reasons,
})
return out
# ── CLI ──────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(prog="skill_search",
description="Skill 检索系统 — 根据环境和需求智能推荐 skillAPI 客户端)")
parser.add_argument("query", nargs="?", help="搜索关键词(如: 'python testing'")
parser.add_argument("--category", "-cat", help="限定类别")
parser.add_argument("--top", "-k", type=int, default=10, help="返回结果数(默认 10")
parser.add_argument("--json", action="store_true", help="JSON 格式输出")
parser.add_argument("--env", action="store_true", help="仅显示检测到的环境信息")
parser.add_argument("--stats", action="store_true", help="显示索引统计信息")
parser.add_argument("--api-url", help="指定 API 地址(也可用 SKILL_SEARCH_API 环境变量)")
args = parser.parse_args()
if args.api_url:
import os; os.environ["SKILL_SEARCH_API"] = args.api_url
env = detect_environment()
if args.env:
print("🖥️ 当前环境:")
print(f" OS: {env['os']}")
print(f" Shell: {env['shell']}")
print(f" 运行时: {', '.join(env['runtimes'])}")
print(f" 工具: {', '.join(env['tools'])}")
print(f" 模型能力: tool_calling={env['model']['tool_calling']}, "
f"reasoning={env['model']['reasoning']}, context={env['model']['context_window']}")
return
if args.stats:
try:
stats = get_stats(env)
print(f"📊 索引统计:")
print(f" 总计: {stats.get('total', '?')} 个 skills")
print(f" 自动安全: {stats.get('safe_count', '?')}")
if 'categories' in stats:
print(f" 类别分布:")
for cat, cnt in sorted(stats['categories'].items(), key=lambda x: -x[1]):
print(f" {cat:15s} {cnt:4d}")
except SkillSearchError as e:
print(f"{e}", file=sys.stderr); sys.exit(1)
return
if not args.query:
parser.print_help(); return
try:
results = search(query=args.query, env=env, category=args.category, top_k=args.top)
except SkillSearchError as e:
print(f"{e}", file=sys.stderr); sys.exit(1)
if args.json:
print(json.dumps(format_results_json(results), indent=2, ensure_ascii=False))
else:
print(format_results(results, env, args.query))
if __name__ == "__main__":
main()