Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/xskill/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from fastapi import APIRouter, FastAPI, HTTPException
from fastapi.responses import FileResponse
from pydantic import BaseModel, Field
from dulwich.errors import NotGitRepository

from xskill import __version__
from xskill.config import load_config, get_skill_dir
Expand Down Expand Up @@ -183,7 +184,7 @@ class HealthResponse(BaseModel):
class StatusResponse(BaseModel):
skill_dir: str
skill_count: int
git_branch: str
git_branch: Optional[str] = None


class InitRequest(BaseModel):
Expand Down Expand Up @@ -453,6 +454,8 @@ async def api_import_skill(req: ImportSkillRequest):
async def api_search_skills(req: SkillSearchRequest):
"""Search existing skills by semantic similarity."""
try:
if not (_skill_dir / ".skill_index.pkl").exists():
return []
embedding_client = create_embed_client(_config)
return search_skill_index(
skill_dir=_skill_dir,
Expand Down Expand Up @@ -734,7 +737,10 @@ async def api_status():
"""Return system status: skill dir, skill count, git branch."""
try:
skills = list_skills(_skill_dir)
branch = current_branch(str(_skill_dir))
try:
branch = current_branch(str(_skill_dir))
except NotGitRepository:
branch = None
return StatusResponse(
skill_dir=str(_skill_dir),
skill_count=len(skills),
Expand Down
2 changes: 2 additions & 0 deletions src/xskill/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ def search_trajectories(self, query: str, top_k: int = 5,
def search_skills(self, query: str, top_k: int = 5) -> list[SkillHit]:
"""跨 skill_repo 搜索 skill。"""
from xskill.skill.repo import search_skill_index
if not (self.skill_repo.root / ".skill_index.pkl").exists():
return []
items = search_skill_index(
skill_dir=self.skill_repo.root,
query=query,
Expand Down
69 changes: 69 additions & 0 deletions tests/test_first_use_status_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from __future__ import annotations

from fastapi.testclient import TestClient


def _configure_server(monkeypatch, tmp_path):
from xskill.api import app as srv

skill_dir = tmp_path / "skill"
skill_dir.mkdir()
monkeypatch.setattr(srv, "_config", {
"llm": {},
"embedding": {},
"watcher": {"poll_interval": 30},
})
monkeypatch.setattr(srv, "_skill_dir", skill_dir)
return srv, skill_dir


def test_status_handles_uninitialized_skill_dir(monkeypatch, tmp_path):
srv, skill_dir = _configure_server(monkeypatch, tmp_path)

app = srv.create_app(home_root=tmp_path)
client = TestClient(app, raise_server_exceptions=False)

resp = client.get("/api/v1/status")

assert resp.status_code == 200
assert resp.json() == {
"skill_dir": str(skill_dir),
"skill_count": 0,
"git_branch": None,
}


def test_api_skill_search_missing_index_skips_embedding_client(monkeypatch, tmp_path):
srv, _skill_dir = _configure_server(monkeypatch, tmp_path)
monkeypatch.setattr(
srv,
"create_embed_client",
lambda _config: (_ for _ in ()).throw(AssertionError("unexpected embed client")),
)

app = srv.create_app(home_root=tmp_path)
client = TestClient(app, raise_server_exceptions=False)

resp = client.post("/api/v1/skills/search", json={"query": "heartbeat", "top_k": 2})

assert resp.status_code == 200
assert resp.json() == []


def test_sdk_skill_search_missing_index_skips_embedding_client(monkeypatch, tmp_path):
from xskill import core
from xskill.utils import llm

skill_dir = tmp_path / "skill"
skill_dir.mkdir()
monkeypatch.setattr(core, "load_config", lambda config_path=None: {"embedding": {}})
monkeypatch.setattr(core, "get_skill_dir", lambda: skill_dir)
monkeypatch.setattr(
llm,
"create_embed_client",
lambda _config: (_ for _ in ()).throw(AssertionError("unexpected embed client")),
)

xskill = core.XSkill()

assert xskill.search_skills("heartbeat", top_k=2) == []
Loading