Merge branch 'lsdefine:main' into main

This commit is contained in:
Jahan authored and GitHub committed 2026-06-12 19:39:52 +08:00
commit 34b463c558
10 files changed
+13 -357

No files matched your search

-4
View File
@@ -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
+1 -1
View File
@@ -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/ 历史会话
+1 -1
View File
@@ -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
+7 -4
View File
@@ -1137,8 +1137,13 @@ def _md_line_has_box_drawing(line: str) -> bool:
left after rstrip trims the trailing space of an empty command/output line).
Exempt all of them so the cards keep their exact narrow\u2194wide line mapping
instead of the visible-text passthrough (which would copy the card's
visual-only margin). A pure horizontal run (only `\u2500` + spaces) is a
separator / markdown hr, not a table \u2014 also exempt.
visual-only margin).
A pure horizontal run (only `─` + spaces) is NOT exempt: a SIMPLE-box
Markdown table's only box glyph is its header rule, so exempting bare
`─` rows drops the whole table out of passthrough and misaligns CJK
copy. The cosmetic cost is that a real markdown hr copies as a dash run
— the pre-card behavior, never reported as a problem.
"""
s = line.lstrip()
for pfx in ("\u2514\u2500 ", "\u2502 ", "\u2514 "):
@@ -1148,8 +1153,6 @@ def _md_line_has_box_drawing(line: str) -> bool:
else:
if s in ("\u2502", "\u2514"): # gutter-only row (empty command/output line)
s = ""
if not s.replace("\u2500", "").replace(" ", ""):
return False
return any("\u2500" <= ch <= "\u257f" for ch in s)
+2 -2
View File
@@ -1,4 +1,4 @@
import sys, os, re, json, time, threading, importlib
import sys, os, re, json, time, threading, importlib, webbrowser
from datetime import datetime
from pathlib import Path
import tempfile, traceback, subprocess, itertools, collections, difflib, shutil
@@ -112,7 +112,7 @@ def first_init_driver():
time.sleep(2)
sess = driver.get_all_sessions()
if len(sess) > 0: break
if i == 4: os.startfile("https://example.com")
if i == 4: webbrowser.open("https://example.com")
def web_scan(tabs_only=False, switch_tab_id=None, text_only=False, maxlen=35000):
"""获取当前页面的简化HTML内容和标签页列表。注意:简化过程会过滤边栏、浮动元素等非主体内容。
+2 -1
View File
@@ -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):
-64
View File
@@ -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` |
@@ -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"]
@@ -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()
-156
View File
@@ -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})