diff --git a/.gitignore b/.gitignore index 85ceced4..e919e0cc 100644 --- a/.gitignore +++ b/.gitignore @@ -87,10 +87,6 @@ memory/L4_raw_sessions/* # Morphling SOP !memory/morphling_sop.md -# Skill Search SOP -!memory/skill_search/ -!memory/skill_search/** - # ADB UI tool !memory/adb_ui.py diff --git a/assets/global_mem_insight_template.txt b/assets/global_mem_insight_template.txt index 3385393c..4bf24e87 100644 --- a/assets/global_mem_insight_template.txt +++ b/assets/global_mem_insight_template.txt @@ -2,7 +2,7 @@ 需要时read L2 或 ls ../memory/ 查L3 L0(META-SOP): memory_management_sop L2: 现空 -L3: memory_cleanup_sop(记忆整理) | skill_search | ui_detect.py | ocr_utils.py | subagent | web_setup_sop | plan_sop +L3: memory_cleanup_sop(记忆整理) | ui_detect.py | ocr_utils.py | subagent | web_setup_sop | plan_sop | procmem_scanner | keychain | ljqCtrl_sop+.py | tmwebdriver_sop | autonomous_operation_sop | scheduled_task_sop | vision_sop | adb_ui.py L4: L4_raw_sessions/ 历史会话 diff --git a/assets/global_mem_insight_template_en.txt b/assets/global_mem_insight_template_en.txt index 0ab46ea0..2e094235 100644 --- a/assets/global_mem_insight_template_en.txt +++ b/assets/global_mem_insight_template_en.txt @@ -2,7 +2,7 @@ Read L2 or ls ../memory/ for L3 when needed L0(META-SOP): memory_management_sop L2: currently empty -L3: memory_cleanup_sop(memory cleanup) | skill_search | ui_detect.py | ocr_utils.py | subagent | web_setup_sop | plan_sop +L3: memory_cleanup_sop(memory cleanup) | ui_detect.py | ocr_utils.py | subagent | web_setup_sop | plan_sop | procmem_scanner | keychain | ljqCtrl_sop+.py | tmwebdriver_sop | autonomous_operation_sop | scheduled_task_sop | vision_sop | adb_ui.py L4: L4_raw_sessions/ historical sessions diff --git a/llmcore.py b/llmcore.py index 6e6b7f19..0be84f8f 100644 --- a/llmcore.py +++ b/llmcore.py @@ -188,7 +188,8 @@ def _parse_claude_sse(resp_lines): content_blocks.append(current_block); current_block = None if warn: print(f"[WARN] {warn.strip()}") - content_blocks.append({"type": "text", "text": warn}); yield warn + insert_at = next((i for i,b in enumerate(content_blocks) if b.get("type") == "tool_use"), len(content_blocks)) + content_blocks.insert(insert_at, {"type": "text", "text": warn}); yield warn return content_blocks def _try_parse_tool_args(raw): diff --git a/memory/skill_search/SKILL.md b/memory/skill_search/SKILL.md deleted file mode 100644 index e89b34b1..00000000 --- a/memory/skill_search/SKILL.md +++ /dev/null @@ -1,64 +0,0 @@ -# Skill Search — 105K 技能卡检索 - -> 从 105K+ 技能卡中语义搜索最匹配的 skill。零依赖,内置默认 API 地址,开箱即用。 - -## 最简调用 - -```python -import sys; sys.path.append('../memory/skill_search') -from skill_search import search - -results = search("python send email") # ⚠️ 必须用英文查询,中文匹配效果极差 -for r in results: - s = r.skill - print(f"[{r.final_score:.2f}] {s.name} — {s.one_line_summary}") - print(f" key: {s.key} category: {s.category} tags: {s.tags[:3]}") -``` - -## API 签名 - -```python -search(query, env=None, category=None, top_k=10) -> list[SearchResult] -# env: 自动检测,一般不传 -# category: 可选过滤,如 "devops" -# top_k: 返回数量,默认10 -``` - -## 返回结构 - -``` -SearchResult - .final_score float 综合评分 (0~1) - .relevance float 语义相关度 - .quality float 质量分 - .match_reasons list[str] 匹配原因 - .warnings list[str] 警告 - .skill SkillIndex ↓ - -SkillIndex (常用字段) - .key str 唯一标识/路径 - .name str 名称 - .one_line_summary str 一句话摘要 - .description str 详细描述 - .category str 类别 - .tags list[str] 标签 - .form str 形式(sop/script/...) - .autonomous_safe bool 是否自主安全 -``` - -## CLI - -```bash -python -m skill_search "python testing" -python -m skill_search "docker deployment" --category devops --top 5 -python -m skill_search "git" --json -python -m skill_search --stats -python -m skill_search --env -``` - -## 配置 - -| 项 | 默认值 | 说明 | -|---|---|---| -| API地址 | `http://www.fudankw.cn:58787` | 环境变量 `SKILL_SEARCH_API` 可覆盖 | -| API密钥 | 无(可选) | 环境变量 `SKILL_SEARCH_KEY` | \ No newline at end of file diff --git a/memory/skill_search/skill_search/__init__.py b/memory/skill_search/skill_search/__init__.py deleted file mode 100644 index 70b246f8..00000000 --- a/memory/skill_search/skill_search/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -"""skill_search — Skill 检索 API 客户端""" -from .engine import ( - SkillIndex, SearchResult, SkillSearchError, - search, get_stats, detect_environment, -) - -__all__ = ["SkillIndex", "SearchResult", "SkillSearchError", - "search", "get_stats", "detect_environment"] \ No newline at end of file diff --git a/memory/skill_search/skill_search/__main__.py b/memory/skill_search/skill_search/__main__.py deleted file mode 100644 index 6691a27b..00000000 --- a/memory/skill_search/skill_search/__main__.py +++ /dev/null @@ -1,116 +0,0 @@ -"""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 检索系统 — 根据环境和需求智能推荐 skill(API 客户端)") - 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() \ No newline at end of file diff --git a/memory/skill_search/skill_search/engine.py b/memory/skill_search/skill_search/engine.py deleted file mode 100644 index 5625a4fa..00000000 --- a/memory/skill_search/skill_search/engine.py +++ /dev/null @@ -1,156 +0,0 @@ -"""Skill 检索引擎 — API 客户端(含数据模型与环境检测)""" -from __future__ import annotations -import json, os, platform, shutil, subprocess, urllib.request, urllib.error -from dataclasses import dataclass, field - -# ── 数据模型 ───────────────────────────────────────────── - -@dataclass -class SkillIndex: - """Skill 索引条目(与服务端结构对齐)""" - key: str - name: str = "" - description: str = "" - one_line_summary: str = "" - category: str = "" - tags: list[str] = field(default_factory=list) - language: str = "en" - os: list[str] = field(default_factory=list) - shell: list[str] = field(default_factory=list) - runtimes: list[str] = field(default_factory=list) - tools: list[str] = field(default_factory=list) - services: list[str] = field(default_factory=list) - needs_tool_calling: bool = False - needs_reasoning: bool = False - min_context_window: str = "standard" - decay_risk: str = "low" - clarity: int = 0 - completeness: int = 0 - actionability: int = 0 - autonomous_safe: bool = True - blast_radius: str = "low" - requires_credentials: bool = False - data_exposure: str = "none" - effect_scope: str = "local" - form: str = "" - estimated_tokens: str = "medium" - capabilities: list[str] = field(default_factory=list) - github_stars: int = 0 - github_url: str = "" - - @property - def quality_score(self): - return self.clarity * 0.3 + self.completeness * 0.3 + self.actionability * 0.4 - - @classmethod - def from_dict(cls, d): - known = {f.name for f in cls.__dataclass_fields__.values()} - return cls(**{k: v for k, v in d.items() if k in known}) - - -@dataclass -class SearchResult: - """单条检索结果""" - skill: SkillIndex - relevance: float = 0.0 - quality: float = 0.0 - final_score: float = 0.0 - match_reasons: list[str] = field(default_factory=list) - warnings: list[str] = field(default_factory=list) - - @classmethod - def from_dict(cls, d): - skill = SkillIndex.from_dict(d.get("skill", d)) - return cls(skill=skill, relevance=d.get("relevance", 0.0), - quality=d.get("quality", 0.0), final_score=d.get("final_score", 0.0), - match_reasons=d.get("match_reasons", []), warnings=d.get("warnings", [])) - - -# ── 环境检测 ───────────────────────────────────────────── - -def _run(cmd): - try: - r = subprocess.run(cmd.split(), capture_output=True, text=True, timeout=5) - return r.stdout.strip() if r.returncode == 0 else "" - except Exception: - return "" - -def _detect_os(): - s = platform.system().lower() - return {"darwin": "macos", "linux": "linux", "windows": "windows"}.get(s, s) - -def _detect_shell(): - shell = os.environ.get("SHELL", "") - if "zsh" in shell: return "zsh" - if "bash" in shell: return "bash" - if platform.system() == "Windows": return "powershell" - return os.path.basename(shell) if shell else "unknown" - -def _detect_runtimes(): - checks = {"python": ["python3", "python"], "node": ["node"], "go": ["go"], - "rust": ["rustc"], "java": ["java"], "ruby": ["ruby"], - "php": ["php"], "dotnet": ["dotnet"]} - found = [] - for name, cmds in checks.items(): - for cmd in cmds: - if shutil.which(cmd): - found.append(name); break - return found - -def _detect_tools(): - tools = ["git", "docker", "npm", "pip", "curl", "wget", "kubectl", - "terraform", "aws", "gcloud", "az", "brew", "cargo", "make", "cmake"] - return [t for t in tools if shutil.which(t)] - -def detect_environment(): - """采集完整环境信息""" - return {"os": _detect_os(), "shell": _detect_shell(), - "runtimes": _detect_runtimes(), "tools": _detect_tools(), - "model": {"tool_calling": True, "reasoning": True, "context_window": "large"}} - - -# ── API 配置与调用 ──────────────────────────────────────── - -DEFAULT_API_URL = "http://www.fudankw.cn:58787" - -def _get_api_url(): - return os.environ.get("SKILL_SEARCH_API", DEFAULT_API_URL) - -def _get_api_key(): - return os.environ.get("SKILL_SEARCH_KEY") - -class SkillSearchError(Exception): - pass - -def _api_request(endpoint, payload): - url = f"{_get_api_url()}/{endpoint}" - data = json.dumps(payload).encode("utf-8") - headers = {"Content-Type": "application/json"} - api_key = _get_api_key() - if api_key: - headers["Authorization"] = f"Bearer {api_key}" - req = urllib.request.Request(url, data=data, headers=headers, method="POST") - try: - with urllib.request.urlopen(req, timeout=30) as resp: - return json.loads(resp.read().decode("utf-8")) - except urllib.error.HTTPError as e: - body = e.read().decode("utf-8", errors="replace") - raise SkillSearchError(f"API 错误 {e.code}: {body}") from e - except urllib.error.URLError as e: - raise SkillSearchError(f"无法连接服务: {e.reason}") from e - except Exception as e: - raise SkillSearchError(f"请求失败: {e}") from e - - -# ── 公开接口 ───────────────────────────────────────────── - -def search(query, env=None, category=None, top_k=10): - if env is None: env = detect_environment() - payload = {"query": query, "env": env, "top_k": top_k} - if category: payload["category"] = category - resp = _api_request("search", payload) - return [SearchResult.from_dict(r) for r in resp.get("results", [])] - -def get_stats(env=None): - if env is None: env = detect_environment() - return _api_request("stats", {"env": env}) \ No newline at end of file