Skip to content

Releases: 11com7/code-review-graph

2.3.6+11com7.7

Choose a tag to compare

@dpesch dpesch released this 15 Jun 07:05

Added

  • prune CLI command (11com7): removes registry entries whose paths no longer exist on disk (code-review-graph prune). Supports --dry-run to preview stale entries without touching the registry file. Useful after deleting or moving repositories that were registered via code-review-graph register.
  • CRG_REGISTRY environment variable (11com7): overrides the default registry path (~/.code-review-graph/registry.json). Intended for cross-project setups where a secondary project (e.g. a documentation repo) needs to point at the registry of a different project's graph data — without touching the global default. Also used for test isolation as a side effect.

Fixed

  • Test isolation: TestDataDirRegistry wrote into real user registry (11com7): three tests in tests/test_incremental.py called Registry() without a path, causing every pytest run to append temp-directory entries to ~/.code-review-graph/registry.json. After 31 runs this accumulated 93 stale entries, making code-review-graph repos and list_repos_tool output unreliable. Fixed by setting CRG_REGISTRY via monkeypatch in the affected tests.

2.3.6+11com7.6

Choose a tag to compare

@dpesch dpesch released this 12 Jun 19:09

Added

  • --require-repo-root flag for serve (11com7): prevents silent empty-graph startup when the server is meant to index an external project (e.g. a documentation repo pointing at a source repo via CRG_REPO_ROOT). Without this flag, a missing env var causes SQLite to silently create a blank database in the current directory — all graph tools return empty results with no indication of misconfiguration. With the flag set, the server exits immediately with a clear message showing how to set CRG_REPO_ROOT on PowerShell and bash/zsh.

2.3.6+11com7.5

Choose a tag to compare

@dpesch dpesch released this 12 Jun 13:47

Changed

  • Multilingual default embedding model (11com7): the local provider now defaults to paraphrase-multilingual-MiniLM-L12-v2 instead of upstream's English-only all-MiniLM-L6-v2. Our codebases carry German docstrings, comments, and identifiers, so semantic search needs a multilingual embedding space out of the box. Databases embedded with the old default will report an embedding_mismatch and need a one-time code-review-graph embed re-run.

Added

  • .mcp.json env resolution for CLI runs (11com7): code-review-graph embed (and a manual serve start) now adopt CRG_* entries from the env block of the project's .mcp.json (mcpServers["code-review-graph"].env) via os.environ.setdefault. Previously only MCP clients applied that block, so a manual embed run silently fell back to the default model and produced vectors that mismatched what the server queries against. Precedence: --model flag > process environment > .mcp.json env > built-in default.

2.3.6+11com7.4

Choose a tag to compare

@dpesch dpesch released this 12 Jun 10:05

Added

  • /release skill (11com7): project-local Claude Code skill that automates the full fork release workflow — preflight checks (uncommitted changes, ruff lint, mypy, targeted tests, GitHub workflow audit), CHANGELOG completeness check with commit-diff, automatic fork counter increment (+11com7.N+1), three-file version sync (pyproject.toml, __init__.py, uv.lock), annotated tag, push, and GitHub release via make_releases.py. Supports --dry-run to preview all planned changes read-only without touching any file.
  • mypy invocation fix (11com7): mypy is not part of the dev extras and not installed in the local venv — uv run mypy failed with "program not found". Fixed by using uv run --with mypy --with types-networkx mypy, which matches how CI installs it (pip install mypy types-networkx) without requiring a permanent dev-dependency entry.

2.3.6+11com7.3

Choose a tag to compare

@dpesch dpesch released this 12 Jun 09:17

Added

  • Enriched embedding text per node (11com7): natural-language semantic search ranked nodes poorly because only identifier metadata was embedded — "Vortrag Stornierung Referent" ranked VortragsberichtFragebogen far above the semantically correct StorniertAbgesagtHandler. New _node_to_embedding_text() wraps the upstream _node_to_text() (kept byte-identical for clean future merges) and appends discriminating context: decorators/attributes from node.extra, identifier-split trailing path segments, and a compact source excerpt of the definition (declaration line incl. extends, salient body strings; 12 lines / 400 chars cap — the MiniLM models truncate at ~128 tokens anyway). Measured with the production multilingual model on the real classes: correct handler 0.263 → 0.413, related guard 0.136 → 0.262, false positive 0.615 → 0.454. The text_hash comparison re-embeds all nodes automatically on the next code-review-graph embed run.

Fixed

  • _split_identifier now splits acronym boundaries (11com7): HTTPServerHTTP Server, XMLHttpRequestXML Http Request. Previously only lowercase→uppercase transitions were split, so acronym-prefixed names stayed single tokens.
  • semantic_search_nodes labels its scores (11com7): the response now carries score_type: "rrf" (or "keyword" for the fallback path). RRF scores are rank-based — 1/(60+rank+1), so even a perfect top hit caps at ~0.033 — and were repeatedly misread as poor cosine similarities.

2.3.6+11com7.2

Choose a tag to compare

@dpesch dpesch released this 12 Jun 09:17

Fixed

  • Windows: embed_graph tool hang reintroduced by upstream's provider availability check (11com7): upstream 2.3.4 (42dd2c9) added _check_available() to get_provider(), which does a real import sentence_transformers to report unavailable local embeddings. On the MCP tool-call path this import runs on an executor thread and pulls in the torch/numpy C extensions — the exact Windows DLL Loader Lock deadlock the fork's embed_worker architecture exists to avoid. Reproduced 2/2 HANG with the MCP harness after the v2.3.6 merge; py-spy showed the asyncio_0 thread frozen in numpy._core.multiarray under _check_available. Fixed by switching the check to importlib.util.find_spec() (pure filesystem lookup, no import side effects). Verified 3/3 OK: handshake ~1 s, embed 5–20 s, vector search immediately afterwards.

2.3.6+11com7.1

Choose a tag to compare

@dpesch dpesch released this 12 Jun 09:17

Changed

  • Merged upstream v2.3.6 (11com7): rebases the fork onto upstream releases 2.3.4–2.3.6 (51 commits). Notable conflict resolutions:
    • prewarm_local_embeddings() (upstream's Windows startup fix) is now worker-aware: on Windows it delegates to the fork's embed_worker subprocess (loader-lock-free, returns immediately) instead of loading the model in-process; on other platforms it pre-loads into the shared thread-safe model cache. Upstream's lock-free _MODEL_CACHE was dropped in favour of the fork's _local_model_cache + lock.
    • The fork's per-process GraphStore cache in tools/_common.py was removed in favour of upstream's caller-owns-and-closes lifecycle (upstream tools now close stores in try/finally; a shared cached store would be closed under concurrent callers).
    • The duplicate embed CLI subcommand (added independently by both sides) keeps the fork's implementation (--batch-size, chunked commits, progress output).
    • Upstream's Rust attribute detection was folded into the fork's _extract_annotation_list() helper.
    • query_graph target resolution combines upstream's file_summary path handling and cross-file caller fixes with the fork's non-test tie-break for ambiguous targets.

2.3.3+11com7.6

Choose a tag to compare

@dpesch dpesch released this 12 Jun 09:17

Added

  • code-review-graph embed CLI command (11com7): computes (or re-computes) vector embeddings outside the MCP request path. Re-embedding a large repo — e.g. after switching CRG_EMBEDDING_MODEL — can take many minutes and previously had to go through the embed_graph MCP tool, where client timeouts cut it off. The command embeds in chunks (default 500 nodes, --batch-size) with progress output and per-chunk commits, so an interrupted run resumes where it left off. Supports --model, --provider, --repo and --data-dir.

Fixed

  • semantic_search_nodes no longer mislabels results as "hybrid" (11com7): the search_mode field always claimed "hybrid" for non-empty results, even when the vector path never ran (no embeddings, model still loading, provider mismatch) and results were FTS5/keyword-only. hybrid_search() now reports which paths actually contributed via a new diagnostics out-parameter, and the tool surfaces an honest mode: hybrid, fts, vector, or keyword.

  • Provider/model mismatch is reported instead of silently degrading (11com7): when stored embeddings belong to a different provider or model than the active one (e.g. embeddings built with all-MiniLM-L6-v2, then CRG_EMBEDDING_MODEL switched to a multilingual model), the provider filter matched 0 vectors and semantic search silently fell back to keyword search — while still labeled "hybrid". The search response now includes a warning explaining the mismatch and how to re-embed (embed_graph tool or code-review-graph embed). As a bonus the mismatch check runs before the query is embedded, so it no longer triggers a pointless model load just to find 0 matching vectors.

  • Windows: MCP server marked as failed on cold start (11com7): main() called import sentence_transformers on the main thread just to check whether the package is installed. On Windows this pulls in PyTorch and all its DLLs synchronously — typically 10–30 s — which blocks the stdio handshake long enough for MCP clients (e.g. Claude Code) to time out and mark the server as failed before any tools are registered. Fixed by replacing the bare import with importlib.util.find_spec('sentence_transformers'), a pure file-system lookup that takes < 1 ms and has no import side effects. The daemon thread that actually loads the model is unchanged. See: tirth8205#46, tirth8205#136

  • Windows: local embeddings now run in a dedicated worker process — fixes all DLL Loader Lock deadlocks (11com7): importing the numpy/torch C extensions from any non-main thread of the MCP server process deadlocks on the Windows DLL Loader Lock: the importing thread holds the lock during LoadLibrary, while torch's import machinery spawns helper threads whose DllMain(DLL_THREAD_ATTACH) callbacks block on that same lock. Stack dumps show the pre-warm thread stuck in numpy._core.multiarraycreate_module and the event loop stuck in Thread.start(). This proved 100% reproducible (10/10 runs on Python 3.12 and 3.13) regardless of pre-warm strategy — every previous in-process mitigation (_prewarm_done event polling, model cache guards, thread-count env vars) only moved the deadlock around. Loading the same model on the main thread of a dedicated process works reliably, so that is now the architecture on Windows:

    • New embed_worker.py: a small worker process that loads the sentence-transformers model on its main thread and serves embedding requests over a JSON-lines stdio protocol. It inherits the server's stderr for logging and exits when the server's pipe closes.
    • LocalEmbeddingProvider transparently routes embed / embed_query / dimension through the worker on Windows (override with CRG_EMBED_WORKER=0/1). One worker per model name, requests serialized, automatic one-shot restart if the worker dies mid-session.
    • EmbeddingStore.search() scores vectors in pure Python on Windows instead of importing numpy into the server process (a few thousand 384-dim vectors rank in well under a second).
    • main() no longer starts a pre-warm thread on Windows; it just spawns the worker (process creation is loader-lock-free and instant), and embed_graph_tool is a plain asyncio.to_thread call again. The _prewarm_done event and the _local_model_cache guard in _embedding_search() are gone — the first semantic search after startup now blocks a worker thread (not the event loop) until the model is ready instead of silently degrading to FTS5-only results.

    Verified end-to-end with an MCP repro harness: 5/5 cold-start runs with handshake ~1 s, embed_graph 5–33 s, and genuine vector search results immediately afterwards — previously 10/10 runs hung indefinitely. See: tirth8205#46, tirth8205#136

  • .mcp.json: pin the MCP server to the installed code-review-graph executable (11com7): the previous uvx code-review-graph serve resolves "latest on PyPI" on every launch. The moment upstream published 2.3.4, uvx silently stopped using the locally installed fork (2.3.3+11com7.x) and ran the upstream package without any of the Windows fixes — which is why the hang appeared to "come back" despite verified fixes. The command is now code-review-graph serve, which always runs whatever uv tool install put on PATH.

2.3.3+11com7.3

Choose a tag to compare

@dpesch dpesch released this 12 Jun 09:17

Fixed

  • Windows: semantic_search_nodes_tool deadlock on first call after server start (11com7):
    even with the process-level _local_model_cache in place, the very first call to
    semantic_search_nodes_tool (or embed_graph_tool) after a fresh server start still
    triggered a Windows DLL loader deadlock. Loading PyTorch / sentence-transformers native
    extensions from a ThreadPoolExecutor thread while the main thread is blocked inside the
    asyncio event loop (WindowsSelectorEventLoopPolicy) causes a circular wait on the OS
    loader lock. Fixed by pre-warming the embedding model on the main thread in main(),
    immediately after asyncio.set_event_loop_policy() and before mcp.run(). When
    CRG_EMBEDDING_MODEL is set and the platform is Windows, LocalEmbeddingProvider._get_model()
    is called once to populate _local_model_cache; subsequent asyncio.to_thread calls then
    only do a dict lookup with no native DLL loading. No-op on Linux/macOS. See: tirth8205#46, tirth8205#136

2.3.3+11com7.5

Choose a tag to compare

@dpesch dpesch released this 12 Jun 09:17

Fixed

  • Windows: semantic_search_nodes_tool still freezes MCP server on default config (11com7): the pre-warm daemon thread introduced in 2.3.3+11com7.4 was gated behind if _prewarm_model and sys.platform == "win32", meaning it only fired when CRG_EMBEDDING_MODEL was explicitly set. Users relying on the default model (all-MiniLM-L6-v2) received no pre-warm and still hit the DLL Loader Lock deadlock on the first semantic_search_nodes_tool call. Fixed in two places:

    1. main.py: the pre-warm now runs on Windows whenever sentence-transformers is importable, regardless of whether CRG_EMBEDDING_MODEL is set. When the env var is absent, LOCAL_DEFAULT_MODEL (all-MiniLM-L6-v2) is used as the fallback.

    2. search.py: _embedding_search() now checks _local_model_cache before touching the embedding provider on Windows. If the model is not yet cached (pre-warm still in progress), the function returns [] immediately and the caller falls back to FTS5. This closes the remaining race window: even if the first MCP tool call arrives before the daemon thread finishes, the event loop does not deadlock — it simply returns FTS5-only results for that one call, and full embedding search resumes once the cache is populated. See: tirth8205#46, tirth8205#136