xiaowei-system/scripts/skill-find.py

106 lines
4.1 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""skill-find —— 任务→技能 检索器2026-09-11 建,牧尘"技能本来都有你却不用"的机械解法)
为什么存在:规则写了 N 遍我没执行,因为"扫技能库"这件事在脑子里是**可选动作**。
把它变成一条命令:**任何任务动手前先跑它**,命中即 skill_view 加载。
用法:
skill-find.py "obsidian 同步 配置" # 关键词
skill-find.py "密钥泄露了怎么清历史" # 整句任务描述也行
skill-find.py --top 15 "整理笔记库"
skill-find.py --list-cats # 看技能分类
"""
import os, re, sys, glob
SKILLS = os.path.expanduser("~/.hermes/skills")
QSTOP = set("的了和与及怎么如何要要不要是否有我你他她它把被给对为在从到就都也很呢吗个这那注意事项问题办法方法请问帮我一下做个")
def load_skills():
out = []
for p in glob.glob(os.path.join(SKILLS, "**", "SKILL.md"), recursive=True):
try:
t = open(p, encoding="utf-8", errors="ignore").read()
except Exception:
continue
fm = t.split("---", 2)
fm = fm[1] if len(fm) > 2 else t[:2000]
def get(key):
m = re.search(rf'^{key}:\s*(.+?)(?=\n[a-z_]+:|\Z)', fm, re.M | re.S)
return m.group(1).strip().strip('"').replace("\n", " ") if m else ""
name = get("name") or os.path.basename(os.path.dirname(p))
desc = re.sub(r"\s+", " ", get("description"))
trig = get("triggers") + " " + get("trigger")
cat = p.replace(SKILLS + "/", "").rsplit("/", 2)[0] if "/" in p.replace(SKILLS + "/", "") else ""
out.append({"name": name, "desc": desc, "trig": trig, "path": p, "cat": cat})
return out
def tokens(q):
q = q.strip()
parts = [p for p in re.split(r"[\s,,、/|]+", q) if p]
toks = set(parts)
# 中文长词再切 2-3 字滑窗,提高召回("密钥泄露了怎么清历史" → 密钥/泄露/清历史…)
for p in parts:
cjk = re.findall(r"[\u4e00-\u9fff]+", p)
for c in cjk:
for n in (2, 3, 4):
for i in range(0, max(1, len(c) - n + 1)):
toks.add(c[i:i + n])
return {t for t in toks if t and t not in QSTOP and len(t) >= 2}
def main():
argv = sys.argv[1:]
top = 8
if "--top" in argv:
i = argv.index("--top")
top = int(argv[i + 1])
del argv[i:i + 2]
args = [a for a in argv if not a.startswith("--")]
sk = load_skills()
if "--list-cats" in sys.argv:
cats = {}
for s in sk:
cats[s["cat"]] = cats.get(s["cat"], 0) + 1
for c, n in sorted(cats.items(), key=lambda x: -x[1]):
print(f" {c:44s} {n}")
return
if not args:
print(__doc__); return
q = " ".join(args)
toks = tokens(q)
scored = []
for s in sk:
sc, why = 0, []
blob = (s["name"] + " " + s["desc"] + " " + s["trig"]).lower()
for t in toks:
tl = t.lower()
if tl in s["name"].lower():
sc += 10; why.append(f"name:{t}")
if tl in s["trig"].lower():
sc += 6; why.append(f"trigger:{t}")
if tl in s["desc"].lower():
sc += 4
if q.lower() in blob:
sc += 8; why.append("整句命中")
if sc:
scored.append((sc, s, why))
scored.sort(key=lambda x: -x[0])
if not scored:
print(f"❌ 技能库无匹配:「{q}")
print(" → 下一步web_search 找现成技能库 → 装skill-library-porting都没有 → 按方法论自建hermes-self-improvement")
return
print(f"🔍 「{q}」→ {len(scored)} 个候选,取前 {min(top,len(scored))}\n")
for sc, s, why in scored[:top]:
flag = "" if sc >= 12 else " "
print(f"{flag} [{sc:>3}] {s['name']} ({s['cat']})")
print(f" {s['desc'][:110]}")
if why:
print(f" 命中: {', '.join(sorted(set(why))[:5])}")
print(f"\n▶ 加载skill_view(name='{scored[0][1]['name']}') (可组合多个)")
if __name__ == "__main__":
main()