refactor: rename package fabledassistant -> scribe (code-only)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 1m14s

Renames src/fabledassistant -> src/scribe and all imports, plus the
default DB name and DB user/password (fabled -> scribe) in config +
compose. 952 refs / 154 files. Reverses the old 'internal name stays
fabledassistant' convention.

Code-only: live databases are still physically named 'fabledassistant'.
Deployed environments must set POSTGRES_DB / POSTGRES_USER (or rename the
DB) since the defaults now resolve to 'scribe'. Repo (FabledScribe), git
host (fabledsword), MCP (fabled-git) and the image name (fabledscribe)
are intentionally unchanged.

ruff check src/ clean locally; CI (typecheck + pytest) is the gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 15:48:35 -04:00
parent 1d4c206563
commit b255a0f90e
167 changed files with 1183 additions and 2368 deletions
+49
View File
@@ -0,0 +1,49 @@
"""search — semantic search across the user's notes and tasks.
Mirrors the existing fable-mcp contract so Claude's prior usage pattern keeps
working. Differences from fable-mcp:
- calls services.embeddings.semantic_search_notes directly instead of HTTP
- user_id comes from mcp.current_user_id() rather than a global API key
"""
from __future__ import annotations
from scribe.mcp._context import current_user_id
from scribe.services.embeddings import semantic_search_notes
async def search(q: str, content_type: str = "all", limit: int = 10) -> dict:
"""Semantic search over the user's notes and tasks.
Args:
q: search query string.
content_type: 'all' (default), 'note' (notes only), or 'task' (tasks only).
limit: maximum number of results (1-50).
Returns:
{"results": [{"id", "title", "body", "is_task", "tags", "similarity"}],
"total": int}
"""
uid = current_user_id()
limit = max(1, min(limit, 50))
is_task = {"note": False, "task": True}.get(content_type) # None => any
raw = await semantic_search_notes(
uid, q, limit=limit, is_task=is_task,
)
return {
"results": [
{
"id": note.id,
"title": note.title,
"body": (note.body or "")[:240],
"is_task": bool(note.is_task),
"tags": list(note.tags or []),
"similarity": float(score),
}
for score, note in raw
],
"total": len(raw),
}
def register(mcp) -> None:
mcp.tool(name="search")(search)