chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
"""Tools 7, 8, 19, 20: embed_graph, get_docs_section, wiki tools."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ..embeddings import EmbeddingStore, embed_all_nodes
|
||||
from ..incremental import find_project_root, get_db_path
|
||||
from ._common import _get_store, _resolve_root, _validate_repo_root
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool 7: embed_graph
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def embed_graph(
|
||||
repo_root: str | None = None,
|
||||
model: str | None = None,
|
||||
provider: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Compute vector embeddings for all graph nodes to enable semantic search.
|
||||
|
||||
Requires: ``pip install code-review-graph[embeddings]`` (local provider only;
|
||||
cloud providers like ``openai`` / ``google`` / ``minimax`` use stdlib ``urllib``).
|
||||
Default model: all-MiniLM-L6-v2. Override via ``model`` param or
|
||||
CRG_EMBEDDING_MODEL env var.
|
||||
Changing the model or provider re-embeds all nodes automatically.
|
||||
|
||||
Only embeds nodes that don't already have up-to-date embeddings.
|
||||
|
||||
Args:
|
||||
repo_root: Repository root path. Auto-detected if omitted.
|
||||
model: Embedding model name. For local: HuggingFace ID or path;
|
||||
for openai: model ID (e.g. ``text-embedding-3-small``);
|
||||
for google: Gemini model ID. Falls back to
|
||||
CRG_EMBEDDING_MODEL / CRG_OPENAI_MODEL env vars as appropriate.
|
||||
provider: Provider name: ``local`` (default), ``openai``, ``google``,
|
||||
or ``minimax``. ``openai`` requires CRG_OPENAI_BASE_URL +
|
||||
CRG_OPENAI_API_KEY + CRG_OPENAI_MODEL env vars and accepts
|
||||
any OpenAI-compatible endpoint (real OpenAI, Azure, new-api,
|
||||
LiteLLM, vLLM, LocalAI, Ollama openai-mode, etc.).
|
||||
|
||||
Returns:
|
||||
Number of nodes embedded and total embedding count.
|
||||
"""
|
||||
store, root = _get_store(repo_root)
|
||||
try:
|
||||
db_path = get_db_path(root)
|
||||
try:
|
||||
emb_store = EmbeddingStore(db_path, provider=provider, model=model)
|
||||
except ValueError as exc:
|
||||
# Unknown provider name or missing provider env vars — surface
|
||||
# as a structured error rather than a traceback.
|
||||
logger.error("embed_graph: %s", exc)
|
||||
return {"status": "error", "error": str(exc)}
|
||||
try:
|
||||
if not emb_store.available:
|
||||
if provider in ("openai", "google", "minimax"):
|
||||
err = (
|
||||
f"The '{provider}' embedding provider is not available. "
|
||||
"Check the required environment variables "
|
||||
"(see README and `get_provider()` docstring) and that "
|
||||
"the endpoint is reachable."
|
||||
)
|
||||
else:
|
||||
err = (
|
||||
"The local embedding provider needs sentence-transformers. "
|
||||
"Install with: pip install code-review-graph[embeddings] — "
|
||||
"or switch provider to 'openai' / 'google' / 'minimax'."
|
||||
)
|
||||
return {"status": "error", "error": err}
|
||||
|
||||
newly_embedded = embed_all_nodes(store, emb_store)
|
||||
total = emb_store.count()
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"summary": (
|
||||
f"Embedded {newly_embedded} new node(s). "
|
||||
f"Total embeddings: {total}. "
|
||||
"Semantic search is now active."
|
||||
),
|
||||
"newly_embedded": newly_embedded,
|
||||
"total_embeddings": total,
|
||||
}
|
||||
finally:
|
||||
emb_store.close()
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool 8: get_docs_section
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def get_docs_section(
|
||||
section_name: str, repo_root: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""Return a specific section from the LLM-optimized reference.
|
||||
|
||||
Used by skills and Claude Code to load only the exact documentation
|
||||
section needed, keeping token usage minimal (90%+ savings).
|
||||
|
||||
Args:
|
||||
section_name: Exact section name. One of: usage, review-delta,
|
||||
review-pr, commands, legal, watch, embeddings,
|
||||
languages, troubleshooting.
|
||||
repo_root: Repository root path. Auto-detected from current
|
||||
directory if omitted.
|
||||
|
||||
Returns:
|
||||
The section content, or an error if not found.
|
||||
"""
|
||||
import re as _re
|
||||
|
||||
search_roots: list[Path] = []
|
||||
|
||||
# Wheel install: docs are packaged inside code_review_graph/docs.
|
||||
in_pkg_docs = (
|
||||
Path(__file__).parent.parent
|
||||
/ "docs"
|
||||
/ "LLM-OPTIMIZED-REFERENCE.md"
|
||||
)
|
||||
if repo_root:
|
||||
try:
|
||||
search_roots.append(_validate_repo_root(Path(repo_root)))
|
||||
except ValueError:
|
||||
pass
|
||||
elif in_pkg_docs.exists():
|
||||
in_pkg_root = in_pkg_docs.parent.parent
|
||||
search_roots.append(in_pkg_root)
|
||||
|
||||
if not repo_root:
|
||||
project_root = find_project_root()
|
||||
if project_root not in search_roots:
|
||||
search_roots.append(project_root)
|
||||
|
||||
# Editable/source-tree fallback: docs live next to code_review_graph/.
|
||||
pkg_docs = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ "docs"
|
||||
/ "LLM-OPTIMIZED-REFERENCE.md"
|
||||
)
|
||||
if pkg_docs.exists():
|
||||
pkg_root = pkg_docs.parent.parent
|
||||
if pkg_root not in search_roots:
|
||||
search_roots.append(pkg_root)
|
||||
|
||||
for search_root in search_roots:
|
||||
candidate = search_root / "docs" / "LLM-OPTIMIZED-REFERENCE.md"
|
||||
if candidate.exists():
|
||||
content = candidate.read_text(encoding="utf-8", errors="replace")
|
||||
match = _re.search(
|
||||
rf'<section name="{_re.escape(section_name)}">'
|
||||
r"(.*?)</section>",
|
||||
content,
|
||||
_re.DOTALL | _re.IGNORECASE,
|
||||
)
|
||||
if match:
|
||||
return {
|
||||
"status": "ok",
|
||||
"section": section_name,
|
||||
"content": match.group(1).strip(),
|
||||
}
|
||||
|
||||
available = [
|
||||
"usage", "review-delta", "review-pr", "commands",
|
||||
"legal", "watch", "embeddings", "languages", "troubleshooting",
|
||||
]
|
||||
return {
|
||||
"status": "not_found",
|
||||
"error": (
|
||||
f"Section '{section_name}' not found. "
|
||||
f"Available: {', '.join(available)}"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool 19: generate_wiki [DOCS]
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def generate_wiki_func(
|
||||
repo_root: str | None = None,
|
||||
force: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Generate a markdown wiki from the community structure.
|
||||
|
||||
[DOCS] Creates a wiki page for each detected community and an index
|
||||
page. Pages are written to ``.code-review-graph/wiki/`` inside the
|
||||
repository. Only regenerates pages whose content has changed unless
|
||||
force=True.
|
||||
|
||||
Args:
|
||||
repo_root: Repository root path. Auto-detected if omitted.
|
||||
force: If True, regenerate all pages even if content is unchanged.
|
||||
|
||||
Returns:
|
||||
Status with pages_generated, pages_updated, pages_unchanged counts.
|
||||
"""
|
||||
from ..incremental import get_data_dir
|
||||
from ..wiki import generate_wiki
|
||||
|
||||
store, root = _get_store(repo_root)
|
||||
try:
|
||||
wiki_dir = get_data_dir(root) / "wiki"
|
||||
result = generate_wiki(store, wiki_dir, force=force)
|
||||
total = (
|
||||
result["pages_generated"]
|
||||
+ result["pages_updated"]
|
||||
+ result["pages_unchanged"]
|
||||
)
|
||||
return {
|
||||
"status": "ok",
|
||||
"summary": (
|
||||
f"Wiki generated: {result['pages_generated']} new, "
|
||||
f"{result['pages_updated']} updated, "
|
||||
f"{result['pages_unchanged']} unchanged "
|
||||
f"({total} total pages)"
|
||||
),
|
||||
"wiki_dir": str(wiki_dir),
|
||||
**result,
|
||||
}
|
||||
except Exception as exc:
|
||||
return {"status": "error", "error": str(exc)}
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool 20: get_wiki_page [DOCS]
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def get_wiki_page_func(
|
||||
community_name: str,
|
||||
repo_root: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Retrieve a specific wiki page by community name.
|
||||
|
||||
[DOCS] Returns the markdown content of the wiki page for the given
|
||||
community. The wiki must have been generated first via generate_wiki.
|
||||
|
||||
Args:
|
||||
community_name: Community name to look up (slugified for filename).
|
||||
repo_root: Repository root path. Auto-detected if omitted.
|
||||
|
||||
Returns:
|
||||
Page content or not_found status.
|
||||
"""
|
||||
from ..incremental import get_data_dir
|
||||
from ..wiki import get_wiki_page
|
||||
|
||||
root = _resolve_root(repo_root)
|
||||
wiki_dir = get_data_dir(root) / "wiki"
|
||||
content = get_wiki_page(wiki_dir, community_name)
|
||||
if content is None:
|
||||
return {
|
||||
"status": "not_found",
|
||||
"summary": f"No wiki page found for '{community_name}'.",
|
||||
}
|
||||
return {
|
||||
"status": "ok",
|
||||
"summary": (
|
||||
f"Wiki page for '{community_name}' ({len(content)} chars)"
|
||||
),
|
||||
"content": content,
|
||||
}
|
||||
Reference in New Issue
Block a user