victor-codegraph 0.9.0__tar.gz → 0.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/PKG-INFO +32 -4
  2. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/README.md +29 -3
  3. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/pyproject.toml +5 -1
  4. victor_codegraph-0.10.0/tests/test_embedding_contract.py +145 -0
  5. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/__init__.py +17 -1
  6. victor_codegraph-0.10.0/victor_codegraph/embeddings.py +196 -0
  7. victor_codegraph-0.10.0/victor_codegraph/py.typed +0 -0
  8. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph.egg-info/PKG-INFO +32 -4
  9. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph.egg-info/SOURCES.txt +3 -0
  10. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph.egg-info/requires.txt +3 -0
  11. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/setup.cfg +0 -0
  12. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_adapter.py +0 -0
  13. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_adapter_validation_v2.py +0 -0
  14. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_chunk_contract_v2.py +0 -0
  15. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_diagnostics_v2.py +0 -0
  16. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_identity_v2.py +0 -0
  17. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_line_base_contract.py +0 -0
  18. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_manifest.py +0 -0
  19. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_python_parser.py +0 -0
  20. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_relations_v2.py +0 -0
  21. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_repo.py +0 -0
  22. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_repository_delta_v2.py +0 -0
  23. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_sizing_and_chunk.py +0 -0
  24. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_sizing_overlap.py +0 -0
  25. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_symbol_oid_contract.py +0 -0
  26. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_symbol_oid_parity.py +0 -0
  27. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_treesitter_jsts.py +0 -0
  28. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_treesitter_languages.py +0 -0
  29. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/tests/test_treesitter_relations.py +0 -0
  30. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/adapter.py +0 -0
  31. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/config.py +0 -0
  32. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/javascript_repository.py +0 -0
  33. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/languages.py +0 -0
  34. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/manifest.py +0 -0
  35. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/model.py +0 -0
  36. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/parser.py +0 -0
  37. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/python_parser.py +0 -0
  38. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/repo.py +0 -0
  39. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/repository_index.py +0 -0
  40. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/resolution.py +0 -0
  41. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/rust_repository.py +0 -0
  42. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/sizing.py +0 -0
  43. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/treesitter_parser.py +0 -0
  44. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph/treesitter_tables.py +0 -0
  45. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph.egg-info/dependency_links.txt +0 -0
  46. {victor_codegraph-0.9.0 → victor_codegraph-0.10.0}/victor_codegraph.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: victor-codegraph
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: Repository-to-Code-Semantic-Graph compiler with stable identity, bounded chunks, incremental deltas, and ProximaRecord projection.
5
5
  Author-email: Vijaykumar Singh <vijay@anvaiops.com>
6
6
  License: Apache-2.0
@@ -16,6 +16,8 @@ Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Topic :: Software Development :: Libraries
17
17
  Requires-Python: >=3.12
18
18
  Description-Content-Type: text/markdown
19
+ Provides-Extra: embeddings
20
+ Requires-Dist: sentence-transformers<6,>=3; extra == "embeddings"
19
21
  Provides-Extra: treesitter
20
22
  Requires-Dist: tree-sitter>=0.26.0; extra == "treesitter"
21
23
  Requires-Dist: tree-sitter-language-pack>=1.21.3; extra == "treesitter"
@@ -54,7 +56,7 @@ gaps:
54
56
  ## Install
55
57
 
56
58
  Published on PyPI as [`victor-codegraph`](https://pypi.org/project/victor-codegraph/).
57
- This source tree is version **0.9.0**; consumers should use a compatible `<1.0` pin.
59
+ This source tree is version **0.10.0**; consumers should use a compatible `<1.0` pin.
58
60
 
59
61
  ```bash
60
62
  # from PyPI
@@ -126,7 +128,7 @@ records = to_proxima_records(repository, repo_graph_id="myrepo")
126
128
 
127
129
  ## Status
128
130
 
129
- `0.9.0` source version. Python (`ast`) is the full, offline frontend. JS/TS, Go, Rust,
131
+ `0.10.0` source version. Python (`ast`) is the full, offline frontend. JS/TS, Go, Rust,
130
132
  Java, C, and C++ have conformance fixtures; other mapped tree-sitter languages report
131
133
  symbols-tier or fallback status rather than implying full fidelity.
132
134
 
@@ -143,4 +145,30 @@ workspace-inherited dependencies, re-export chains, macros and unknown receiver
143
145
  types retain unresolved references. Glob imports retain a module dependency but
144
146
  do not guess call targets. This is a conservative static graph, not rustc name
145
147
  resolution or evidence that a call executes. Publish this package before a Victor
146
- release that requires its `0.9.0` repository graph contract.
148
+ release that requires its exact repository graph contract.
149
+
150
+
151
+ ### Embedding conformance (0.10.0)
152
+
153
+ The accepted FEP-0040 supplies store-neutral
154
+ `EmbeddingProfile`, `SymbolEmbedding`, `embed_symbols` and an optional
155
+ `SentenceTransformerEncoder`. The base compiler stays dependency-free. The
156
+ `embeddings` extra loads sentence-transformers only when explicitly selected;
157
+ private source is not sent to a remote model by default.
158
+
159
+ The contract uses canonical compiler IDs and `symbol-summary-v1` (name,
160
+ signature and documentation), with an immutable model revision, dimensions,
161
+ token limit, normalization, query prefix and separate input/source hashes.
162
+ Inference errors propagate; hash placeholders, zero vectors and malformed batches
163
+ are not semantic evidence. File/container symbols marked `embedding_excluded`
164
+ remain graph evidence only.
165
+
166
+ These APIs are introduced in 0.10.0 and are unavailable in 0.9.0. Install the
167
+ exact published `victor-codegraph[treesitter,embeddings]==0.10.0` when selecting
168
+ local model execution; the base package has no mandatory model dependency.
169
+ Current evidence covers
170
+ synthetic document-vector persistence parity and local LanceDB collection/query
171
+ profile enforcement. Labeled relevance and hosted lifecycle remain separate
172
+ acceptance gates. Victor binds a new LanceDB table to the complete profile in its
173
+ Arrow schema and rejects incompatible query encoders and mixed/orphan row markers.
174
+ Legacy tables require a fresh destination; they are never silently relabeled.
@@ -25,7 +25,7 @@ gaps:
25
25
  ## Install
26
26
 
27
27
  Published on PyPI as [`victor-codegraph`](https://pypi.org/project/victor-codegraph/).
28
- This source tree is version **0.9.0**; consumers should use a compatible `<1.0` pin.
28
+ This source tree is version **0.10.0**; consumers should use a compatible `<1.0` pin.
29
29
 
30
30
  ```bash
31
31
  # from PyPI
@@ -97,7 +97,7 @@ records = to_proxima_records(repository, repo_graph_id="myrepo")
97
97
 
98
98
  ## Status
99
99
 
100
- `0.9.0` source version. Python (`ast`) is the full, offline frontend. JS/TS, Go, Rust,
100
+ `0.10.0` source version. Python (`ast`) is the full, offline frontend. JS/TS, Go, Rust,
101
101
  Java, C, and C++ have conformance fixtures; other mapped tree-sitter languages report
102
102
  symbols-tier or fallback status rather than implying full fidelity.
103
103
 
@@ -114,4 +114,30 @@ workspace-inherited dependencies, re-export chains, macros and unknown receiver
114
114
  types retain unresolved references. Glob imports retain a module dependency but
115
115
  do not guess call targets. This is a conservative static graph, not rustc name
116
116
  resolution or evidence that a call executes. Publish this package before a Victor
117
- release that requires its `0.9.0` repository graph contract.
117
+ release that requires its exact repository graph contract.
118
+
119
+
120
+ ### Embedding conformance (0.10.0)
121
+
122
+ The accepted FEP-0040 supplies store-neutral
123
+ `EmbeddingProfile`, `SymbolEmbedding`, `embed_symbols` and an optional
124
+ `SentenceTransformerEncoder`. The base compiler stays dependency-free. The
125
+ `embeddings` extra loads sentence-transformers only when explicitly selected;
126
+ private source is not sent to a remote model by default.
127
+
128
+ The contract uses canonical compiler IDs and `symbol-summary-v1` (name,
129
+ signature and documentation), with an immutable model revision, dimensions,
130
+ token limit, normalization, query prefix and separate input/source hashes.
131
+ Inference errors propagate; hash placeholders, zero vectors and malformed batches
132
+ are not semantic evidence. File/container symbols marked `embedding_excluded`
133
+ remain graph evidence only.
134
+
135
+ These APIs are introduced in 0.10.0 and are unavailable in 0.9.0. Install the
136
+ exact published `victor-codegraph[treesitter,embeddings]==0.10.0` when selecting
137
+ local model execution; the base package has no mandatory model dependency.
138
+ Current evidence covers
139
+ synthetic document-vector persistence parity and local LanceDB collection/query
140
+ profile enforcement. Labeled relevance and hosted lifecycle remain separate
141
+ acceptance gates. Victor binds a new LanceDB table to the complete profile in its
142
+ Arrow schema and rejects incompatible query encoders and mixed/orphan row markers.
143
+ Legacy tables require a fresh destination; they are never silently relabeled.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "victor-codegraph"
7
- version = "0.9.0"
7
+ version = "0.10.0"
8
8
  description = "Repository-to-Code-Semantic-Graph compiler with stable identity, bounded chunks, incremental deltas, and ProximaRecord projection."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -40,6 +40,7 @@ classifiers = [
40
40
  dependencies = []
41
41
 
42
42
  [project.optional-dependencies]
43
+ embeddings = ["sentence-transformers>=3,<6"]
43
44
  treesitter = [
44
45
  "tree-sitter>=0.26.0",
45
46
  "tree-sitter-language-pack>=1.21.3",
@@ -77,3 +78,6 @@ select = ["E4", "E7", "E9", "F"]
77
78
  [tool.pytest.ini_options]
78
79
  testpaths = ["tests"]
79
80
  addopts = ""
81
+
82
+ [tool.setuptools.package-data]
83
+ victor_codegraph = ["py.typed"]
@@ -0,0 +1,145 @@
1
+ """One semantic input/provenance contract for local and hosted consumers."""
2
+
3
+ from dataclasses import replace
4
+ import pytest
5
+ from victor_codegraph import parse
6
+ from victor_codegraph.embeddings import (
7
+ BGE_SMALL_PROFILE,
8
+ embedding_text,
9
+ embed_symbols,
10
+ validate_vectors,
11
+ )
12
+
13
+
14
+ def test_same_symbols_produce_store_independent_embedding_evidence():
15
+ symbols = parse(
16
+ 'def pay(x):\n """Charge a customer."""\n return x\n', file_path="billing.py"
17
+ ).symbols
18
+ seen = []
19
+
20
+ def encode(texts):
21
+ seen.extend(texts)
22
+ return [[1.0] + [0.0] * 383 for _ in texts]
23
+
24
+ documents = embed_symbols(symbols, encode, BGE_SMALL_PROFILE)
25
+ assert seen == [embedding_text(s.simple_name, s.signature, s.documentation) for s in symbols]
26
+ assert [d.symbol_id for d in documents] == [s.id for s in symbols]
27
+ assert all(d.profile_id == BGE_SMALL_PROFILE.identity for d in documents)
28
+ assert all(len(d.input_hash) == 64 and len(d.source_hash) == 64 for d in documents)
29
+ assert all(len(d.values) == 384 for d in documents)
30
+
31
+
32
+ @pytest.mark.parametrize(
33
+ "vectors",
34
+ [[], [[0.0, 0.0]], [[True, 1.0]], [[float("nan"), 1.0]], [[1.0]], [[float("inf"), 1.0]]],
35
+ )
36
+ def test_invalid_or_missing_vectors_fail_without_substitution(vectors):
37
+ with pytest.raises(ValueError):
38
+ validate_vectors(vectors, count=1, dimension=2)
39
+
40
+
41
+ def test_model_preprocessing_changes_invalidate_profile_identity():
42
+ p = BGE_SMALL_PROFILE
43
+ assert (
44
+ len(
45
+ {
46
+ p.identity,
47
+ replace(p, revision="a" * 40).identity,
48
+ replace(p, normalize=True).identity,
49
+ replace(p, query_prefix="query: ").identity,
50
+ replace(p, max_tokens=256).identity,
51
+ }
52
+ )
53
+ == 5
54
+ )
55
+ with pytest.raises(ValueError, match="revision"):
56
+ replace(p, revision="main")
57
+
58
+
59
+ def test_failed_encoder_propagates_and_duplicate_symbol_ids_are_rejected():
60
+ symbols = parse("def f(): pass", file_path="m.py").symbols
61
+
62
+ def fail(texts):
63
+ raise RuntimeError("model unavailable")
64
+
65
+ with pytest.raises(RuntimeError, match="model unavailable"):
66
+ embed_symbols(symbols, fail, BGE_SMALL_PROFILE)
67
+ with pytest.raises(ValueError, match="duplicate"):
68
+ embed_symbols(symbols * 2, fail, BGE_SMALL_PROFILE)
69
+
70
+
71
+ def test_unbounded_encoder_outputs_are_rejected_after_one_overflow_item():
72
+ from itertools import repeat
73
+
74
+ with pytest.raises(ValueError, match="dimension"):
75
+ validate_vectors([repeat(1.0)], count=1, dimension=2)
76
+ with pytest.raises(ValueError, match="exactly one"):
77
+ validate_vectors(repeat([1.0, 2.0]), count=1, dimension=2)
78
+
79
+
80
+ def test_optional_encoder_owns_one_pinned_model_and_query_preprocessing(monkeypatch):
81
+ import asyncio
82
+ import sys
83
+ from types import SimpleNamespace
84
+ from victor_codegraph.embeddings import SentenceTransformerEncoder
85
+
86
+ calls = []
87
+
88
+ class Model:
89
+ max_seq_length = 0
90
+
91
+ def __init__(self, model_id, **kwargs):
92
+ calls.append((model_id, kwargs))
93
+
94
+ def get_sentence_embedding_dimension(self):
95
+ return 3
96
+
97
+ def encode(self, texts, **kwargs):
98
+ calls.append((texts, kwargs, self.max_seq_length))
99
+ return [[1.0, 2.0, 3.0] for _ in texts]
100
+
101
+ monkeypatch.setitem(
102
+ sys.modules, "sentence_transformers", SimpleNamespace(SentenceTransformer=Model)
103
+ )
104
+ profile = replace(BGE_SMALL_PROFILE, dimension=3, query_prefix="query: ", max_tokens=256)
105
+ encoder = SentenceTransformerEncoder(profile)
106
+ assert encoder.encode([]) == [] and calls == []
107
+ assert encoder.encode_query("permissions") == [1.0, 2.0, 3.0]
108
+ assert asyncio.run(encoder.embed_text("symbol")) == [1.0, 2.0, 3.0]
109
+ assert len(calls) == 3 # one model load, two bounded inference calls
110
+ assert calls[0] == (
111
+ profile.model_id,
112
+ {"revision": profile.revision, "device": "cpu", "trust_remote_code": False},
113
+ )
114
+ assert calls[1][0] == ["query: permissions"]
115
+ assert calls[1][1]["normalize_embeddings"] is False
116
+ assert calls[1][2] == 256
117
+ with pytest.raises(ValueError, match="512"):
118
+ encoder.encode(["x"] * 513)
119
+ with pytest.raises(ValueError, match="dimension"):
120
+ SentenceTransformerEncoder(BGE_SMALL_PROFILE).encode(["x"])
121
+
122
+
123
+ def test_optional_encoder_missing_dependency_is_explicit(monkeypatch):
124
+ import sys
125
+ from victor_codegraph.embeddings import SentenceTransformerEncoder
126
+
127
+ monkeypatch.setitem(sys.modules, "sentence_transformers", None)
128
+ with pytest.raises(ImportError):
129
+ SentenceTransformerEncoder().encode(["symbol"])
130
+
131
+
132
+ @pytest.mark.parametrize(
133
+ "changes",
134
+ [
135
+ {"model_id": ""},
136
+ {"revision": "main"},
137
+ {"dimension": True},
138
+ {"max_tokens": 0},
139
+ {"text_contract": "body-v1"},
140
+ {"normalize": 1},
141
+ ],
142
+ )
143
+ def test_invalid_profile_cannot_claim_semantic_compatibility(changes):
144
+ with pytest.raises(ValueError):
145
+ replace(BGE_SMALL_PROFILE, **changes)
@@ -14,6 +14,15 @@ from __future__ import annotations
14
14
 
15
15
  from .adapter import relation_to_record, symbol_to_record, to_proxima_records
16
16
  from .config import ChunkConfig
17
+ from .embeddings import (
18
+ BGE_SMALL_PROFILE,
19
+ EmbeddingProfile,
20
+ SymbolEmbedding,
21
+ SentenceTransformerEncoder,
22
+ embedding_text,
23
+ embed_symbols,
24
+ validate_vectors,
25
+ )
17
26
  from .languages import detect_language
18
27
  from .model import (
19
28
  CapabilityTier,
@@ -45,10 +54,17 @@ from .repository_index import (
45
54
  relation_key,
46
55
  )
47
56
 
48
- __version__ = "0.9.0"
57
+ __version__ = "0.10.0"
49
58
 
50
59
  __all__ = [
51
60
  "__version__",
61
+ "BGE_SMALL_PROFILE",
62
+ "EmbeddingProfile",
63
+ "SymbolEmbedding",
64
+ "SentenceTransformerEncoder",
65
+ "embedding_text",
66
+ "embed_symbols",
67
+ "validate_vectors",
52
68
  "chunk",
53
69
  "parse",
54
70
  "chunk_repo",
@@ -0,0 +1,196 @@
1
+ """Store-neutral semantic embedding evidence; model dependencies are optional.
2
+
3
+ Inference and publication failures propagate. Graph identity is supplied by the
4
+ compiler; this module neither derives replacement IDs nor writes to a database.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import asdict, dataclass
10
+ import hashlib
11
+ import json
12
+ from itertools import islice
13
+ import math
14
+ from numbers import Real
15
+ import re
16
+ from typing import Any, Callable, Iterable, Sequence
17
+
18
+ from .model import CodeSymbol
19
+
20
+
21
+ def embedding_text(name: str, signature: str | None, documentation: str | None) -> str:
22
+ """The symbol-summary-v1 input, identical to Victor's existing node text."""
23
+ return " ".join(filter(None, [name, signature or "", documentation or ""]))
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class EmbeddingProfile:
28
+ """Complete compatibility key for document and query vectors."""
29
+
30
+ model_id: str
31
+ revision: str
32
+ dimension: int
33
+ text_contract: str = "symbol-summary-v1"
34
+ max_tokens: int = 512
35
+ normalize: bool = False
36
+ query_prefix: str = ""
37
+
38
+ def __post_init__(self) -> None:
39
+ if not isinstance(self.model_id, str) or not self.model_id.strip():
40
+ raise ValueError("model_id is required")
41
+ if not isinstance(self.revision, str) or not re.fullmatch(r"[0-9a-f]{40}", self.revision):
42
+ raise ValueError("model revision must be an immutable 40-hex commit")
43
+ if type(self.dimension) is not int or not 1 <= self.dimension <= 65536:
44
+ raise ValueError("invalid embedding dimension")
45
+ if type(self.max_tokens) is not int or not 1 <= self.max_tokens <= 32768:
46
+ raise ValueError("invalid embedding token limit")
47
+ if self.text_contract != "symbol-summary-v1":
48
+ raise ValueError("unsupported embedding text contract")
49
+ if type(self.normalize) is not bool or not isinstance(self.query_prefix, str):
50
+ raise ValueError("invalid embedding preprocessing")
51
+
52
+ @property
53
+ def identity(self) -> str:
54
+ return _digest(json.dumps(asdict(self), sort_keys=True, separators=(",", ":")))
55
+
56
+
57
+ BGE_SMALL_PROFILE = EmbeddingProfile(
58
+ model_id="BAAI/bge-small-en-v1.5",
59
+ revision="5c38ec7c405ec4b44b94cc5a9bb96e735b38267a",
60
+ dimension=384,
61
+ )
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class SymbolEmbedding:
66
+ symbol_id: str
67
+ profile_id: str
68
+ input_hash: str
69
+ source_hash: str
70
+ values: tuple[float, ...]
71
+
72
+
73
+ def _digest(text: str) -> str:
74
+ return hashlib.sha256(text.encode("utf-8")).hexdigest()
75
+
76
+
77
+ def validate_vectors(
78
+ vectors: Iterable[Iterable[float]], *, count: int, dimension: int
79
+ ) -> list[list[float]]:
80
+ """Reject failed, truncated or malformed inference before any publication."""
81
+ if type(count) is not int or not 0 <= count <= 512:
82
+ raise ValueError("embedding batch must contain at most 512 inputs")
83
+ if type(dimension) is not int or not 1 <= dimension <= 65536:
84
+ raise ValueError("invalid embedding dimension")
85
+ rows = [list(islice(row, dimension + 1)) for row in islice(vectors, count + 1)]
86
+ if len(rows) != count:
87
+ raise ValueError("embedding batch must return exactly one vector per input")
88
+ for row in rows:
89
+ if len(row) != dimension:
90
+ raise ValueError("embedding dimension mismatch")
91
+ if any(
92
+ isinstance(v, bool) or not isinstance(v, Real) or not math.isfinite(float(v))
93
+ for v in row
94
+ ):
95
+ raise ValueError("embedding values must be finite numeric values")
96
+ if not any(float(v) != 0.0 for v in row):
97
+ raise ValueError("zero vector is not a semantic embedding")
98
+ return [[float(v) for v in row] for row in rows]
99
+
100
+
101
+ def embed_symbols(
102
+ symbols: Sequence[CodeSymbol],
103
+ encode: Callable[[list[str]], Iterable[Iterable[float]]],
104
+ profile: EmbeddingProfile,
105
+ *,
106
+ batch_size: int = 32,
107
+ ) -> list[SymbolEmbedding]:
108
+ """Prepare, batch and validate compiler symbols without a store dependency.
109
+
110
+ ``encode`` is trusted configuration: it must implement the supplied profile.
111
+ Backend identity and real-model quality require separate deployment acceptance.
112
+ """
113
+ if type(batch_size) is not int or not 1 <= batch_size <= 512:
114
+ raise ValueError("embedding batch size must be between 1 and 512")
115
+ ids = [s.id for s in symbols]
116
+ if len(set(ids)) != len(ids) or any(not sid for sid in ids):
117
+ raise ValueError("empty or duplicate canonical symbol identity")
118
+ eligible = [s for s in symbols if not s.metadata.get("embedding_excluded")]
119
+ result = []
120
+ for start in range(0, len(eligible), batch_size):
121
+ batch = eligible[start : start + batch_size]
122
+ texts = [embedding_text(s.simple_name, s.signature, s.documentation) for s in batch]
123
+ vectors = validate_vectors(encode(texts), count=len(texts), dimension=profile.dimension)
124
+ for symbol, text, vector in zip(batch, texts, vectors, strict=True):
125
+ result.append(
126
+ SymbolEmbedding(
127
+ symbol.id,
128
+ profile.identity,
129
+ _digest(text),
130
+ _digest(symbol.source_code),
131
+ tuple(vector),
132
+ )
133
+ )
134
+ return result
135
+
136
+
137
+ class SentenceTransformerEncoder:
138
+ """Optional local inference for an explicit immutable profile.
139
+
140
+ Uses sentence-transformers itself; no alternative model implementation or
141
+ remote-code execution. Own one encoder per configured worker/model lifetime.
142
+ """
143
+
144
+ semantic = True
145
+
146
+ def __init__(
147
+ self, profile: EmbeddingProfile = BGE_SMALL_PROFILE, *, device: str = "cpu"
148
+ ) -> None:
149
+ import threading
150
+
151
+ self.profile = profile
152
+ self.device = device
153
+ self._model: Any = None
154
+ self._lock = threading.Lock()
155
+
156
+ def encode(self, texts: list[str]) -> list[list[float]]:
157
+ """Encode one bounded document batch; propagate model/installation errors."""
158
+ if len(texts) > 512:
159
+ raise ValueError("embedding batch exceeds 512 inputs")
160
+ if not texts:
161
+ return []
162
+ with self._lock:
163
+ if self._model is None:
164
+ from sentence_transformers import SentenceTransformer
165
+
166
+ model = SentenceTransformer(
167
+ self.profile.model_id,
168
+ revision=self.profile.revision,
169
+ device=self.device,
170
+ trust_remote_code=False,
171
+ )
172
+ if model.get_sentence_embedding_dimension() != self.profile.dimension:
173
+ raise ValueError("model/profile embedding dimension mismatch")
174
+ model.max_seq_length = self.profile.max_tokens
175
+ self._model = model
176
+ values = self._model.encode(
177
+ texts,
178
+ batch_size=32,
179
+ normalize_embeddings=self.profile.normalize,
180
+ convert_to_numpy=True,
181
+ show_progress_bar=False,
182
+ )
183
+ return validate_vectors(values, count=len(texts), dimension=self.profile.dimension)
184
+
185
+ def encode_query(self, query: str) -> list[float]:
186
+ return self.encode([self.profile.query_prefix + query])[0]
187
+
188
+ async def embed_batch(self, texts: list[str]) -> list[list[float]]:
189
+ import asyncio
190
+
191
+ return await asyncio.to_thread(self.encode, texts)
192
+
193
+ async def embed_text(self, text: str, use_cache: bool = True) -> list[float]:
194
+ # Compatibility with Victor's injected embedding-service seam. Cache
195
+ # ownership remains outside this encoder; the flag never changes semantics.
196
+ return (await self.embed_batch([text]))[0]
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: victor-codegraph
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: Repository-to-Code-Semantic-Graph compiler with stable identity, bounded chunks, incremental deltas, and ProximaRecord projection.
5
5
  Author-email: Vijaykumar Singh <vijay@anvaiops.com>
6
6
  License: Apache-2.0
@@ -16,6 +16,8 @@ Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Topic :: Software Development :: Libraries
17
17
  Requires-Python: >=3.12
18
18
  Description-Content-Type: text/markdown
19
+ Provides-Extra: embeddings
20
+ Requires-Dist: sentence-transformers<6,>=3; extra == "embeddings"
19
21
  Provides-Extra: treesitter
20
22
  Requires-Dist: tree-sitter>=0.26.0; extra == "treesitter"
21
23
  Requires-Dist: tree-sitter-language-pack>=1.21.3; extra == "treesitter"
@@ -54,7 +56,7 @@ gaps:
54
56
  ## Install
55
57
 
56
58
  Published on PyPI as [`victor-codegraph`](https://pypi.org/project/victor-codegraph/).
57
- This source tree is version **0.9.0**; consumers should use a compatible `<1.0` pin.
59
+ This source tree is version **0.10.0**; consumers should use a compatible `<1.0` pin.
58
60
 
59
61
  ```bash
60
62
  # from PyPI
@@ -126,7 +128,7 @@ records = to_proxima_records(repository, repo_graph_id="myrepo")
126
128
 
127
129
  ## Status
128
130
 
129
- `0.9.0` source version. Python (`ast`) is the full, offline frontend. JS/TS, Go, Rust,
131
+ `0.10.0` source version. Python (`ast`) is the full, offline frontend. JS/TS, Go, Rust,
130
132
  Java, C, and C++ have conformance fixtures; other mapped tree-sitter languages report
131
133
  symbols-tier or fallback status rather than implying full fidelity.
132
134
 
@@ -143,4 +145,30 @@ workspace-inherited dependencies, re-export chains, macros and unknown receiver
143
145
  types retain unresolved references. Glob imports retain a module dependency but
144
146
  do not guess call targets. This is a conservative static graph, not rustc name
145
147
  resolution or evidence that a call executes. Publish this package before a Victor
146
- release that requires its `0.9.0` repository graph contract.
148
+ release that requires its exact repository graph contract.
149
+
150
+
151
+ ### Embedding conformance (0.10.0)
152
+
153
+ The accepted FEP-0040 supplies store-neutral
154
+ `EmbeddingProfile`, `SymbolEmbedding`, `embed_symbols` and an optional
155
+ `SentenceTransformerEncoder`. The base compiler stays dependency-free. The
156
+ `embeddings` extra loads sentence-transformers only when explicitly selected;
157
+ private source is not sent to a remote model by default.
158
+
159
+ The contract uses canonical compiler IDs and `symbol-summary-v1` (name,
160
+ signature and documentation), with an immutable model revision, dimensions,
161
+ token limit, normalization, query prefix and separate input/source hashes.
162
+ Inference errors propagate; hash placeholders, zero vectors and malformed batches
163
+ are not semantic evidence. File/container symbols marked `embedding_excluded`
164
+ remain graph evidence only.
165
+
166
+ These APIs are introduced in 0.10.0 and are unavailable in 0.9.0. Install the
167
+ exact published `victor-codegraph[treesitter,embeddings]==0.10.0` when selecting
168
+ local model execution; the base package has no mandatory model dependency.
169
+ Current evidence covers
170
+ synthetic document-vector persistence parity and local LanceDB collection/query
171
+ profile enforcement. Labeled relevance and hosted lifecycle remain separate
172
+ acceptance gates. Victor binds a new LanceDB table to the complete profile in its
173
+ Arrow schema and rejects incompatible query encoders and mixed/orphan row markers.
174
+ Legacy tables require a fresh destination; they are never silently relabeled.
@@ -4,6 +4,7 @@ tests/test_adapter.py
4
4
  tests/test_adapter_validation_v2.py
5
5
  tests/test_chunk_contract_v2.py
6
6
  tests/test_diagnostics_v2.py
7
+ tests/test_embedding_contract.py
7
8
  tests/test_identity_v2.py
8
9
  tests/test_line_base_contract.py
9
10
  tests/test_manifest.py
@@ -21,11 +22,13 @@ tests/test_treesitter_relations.py
21
22
  victor_codegraph/__init__.py
22
23
  victor_codegraph/adapter.py
23
24
  victor_codegraph/config.py
25
+ victor_codegraph/embeddings.py
24
26
  victor_codegraph/javascript_repository.py
25
27
  victor_codegraph/languages.py
26
28
  victor_codegraph/manifest.py
27
29
  victor_codegraph/model.py
28
30
  victor_codegraph/parser.py
31
+ victor_codegraph/py.typed
29
32
  victor_codegraph/python_parser.py
30
33
  victor_codegraph/repo.py
31
34
  victor_codegraph/repository_index.py
@@ -8,6 +8,9 @@ ruff>=0.16.7
8
8
  tree-sitter>=0.26.0
9
9
  tree-sitter-language-pack>=1.21.3
10
10
 
11
+ [embeddings]
12
+ sentence-transformers<6,>=3
13
+
11
14
  [treesitter]
12
15
  tree-sitter>=0.26.0
13
16
  tree-sitter-language-pack>=1.21.3