victor-codegraph 0.9.0__tar.gz → 0.10.1__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.
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/PKG-INFO +58 -4
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/README.md +55 -3
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/pyproject.toml +5 -1
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_adapter.py +9 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_adapter_validation_v2.py +27 -0
- victor_codegraph-0.10.1/tests/test_diagnostics_v2.py +46 -0
- victor_codegraph-0.10.1/tests/test_embedding_contract.py +145 -0
- victor_codegraph-0.10.1/tests/test_repository_delta_v2.py +128 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_treesitter_jsts.py +35 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/__init__.py +17 -1
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/adapter.py +19 -4
- victor_codegraph-0.10.1/victor_codegraph/embeddings.py +196 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/parser.py +18 -10
- victor_codegraph-0.10.1/victor_codegraph/py.typed +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/repository_index.py +59 -8
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/treesitter_parser.py +3 -1
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph.egg-info/PKG-INFO +58 -4
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph.egg-info/SOURCES.txt +3 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph.egg-info/requires.txt +3 -0
- victor_codegraph-0.9.0/tests/test_diagnostics_v2.py +0 -26
- victor_codegraph-0.9.0/tests/test_repository_delta_v2.py +0 -46
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/setup.cfg +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_chunk_contract_v2.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_identity_v2.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_line_base_contract.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_manifest.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_python_parser.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_relations_v2.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_repo.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_sizing_and_chunk.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_sizing_overlap.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_symbol_oid_contract.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_symbol_oid_parity.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_treesitter_languages.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/tests/test_treesitter_relations.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/config.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/javascript_repository.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/languages.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/manifest.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/model.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/python_parser.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/repo.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/resolution.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/rust_repository.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/sizing.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph/treesitter_tables.py +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/victor_codegraph.egg-info/dependency_links.txt +0 -0
- {victor_codegraph-0.9.0 → victor_codegraph-0.10.1}/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.
|
|
3
|
+
Version: 0.10.1
|
|
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.
|
|
59
|
+
This source tree is version **0.10.1**; consumers should use a compatible `<1.0` pin.
|
|
58
60
|
|
|
59
61
|
```bash
|
|
60
62
|
# from PyPI
|
|
@@ -126,10 +128,36 @@ records = to_proxima_records(repository, repo_graph_id="myrepo")
|
|
|
126
128
|
|
|
127
129
|
## Status
|
|
128
130
|
|
|
129
|
-
`0.
|
|
131
|
+
`0.10.1` 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
|
|
|
135
|
+
### Ambiguous declarations
|
|
136
|
+
|
|
137
|
+
Duplicate structural identities retain raw declarations in `ParsedRepository.files`
|
|
138
|
+
and report each conflicting source location. Repository projection emits only the
|
|
139
|
+
canonical FILE marker for that file, with typed diagnostics, declaration spans and
|
|
140
|
+
omitted symbol/relation counts in metadata. Calls into its declarations remain
|
|
141
|
+
unresolved; healthy imports can still point to its diagnostic marker. Repairing the
|
|
142
|
+
source restores normal projection through deletion-complete repository deltas.
|
|
143
|
+
Direct record projection rejects duplicate identities before invoking an embedder,
|
|
144
|
+
including when legacy OIDs are selected.
|
|
145
|
+
|
|
146
|
+
`identity_collisions` groups use the existing `symbol_id` with `resolution: ambiguous`.
|
|
147
|
+
Each declaration retains `name`, `qualified_name`, `symbol_type` and its full
|
|
148
|
+
`source_span`; these are inspectable evidence, not separately resolved graph nodes.
|
|
149
|
+
The record adapter forwards these groups, projection status and omitted counts only
|
|
150
|
+
when present, without copying arbitrary symbol metadata into exported properties.
|
|
151
|
+
|
|
152
|
+
### TypeScript grammar limitation
|
|
153
|
+
|
|
154
|
+
The current tree-sitter grammar reports a partial parse for inline import types
|
|
155
|
+
in generic calls, for example `load<typeof import('child_process')>()`, even though
|
|
156
|
+
TypeScript accepts that syntax. A named type alias (`type ChildProcessModule =
|
|
157
|
+
typeof import('child_process')`, then `load<ChildProcessModule>()`) is supported.
|
|
158
|
+
Partial status and syntax diagnostics remain visible; the parser does not rewrite
|
|
159
|
+
source or infer missing relationships.
|
|
160
|
+
|
|
133
161
|
### Rust graph fidelity
|
|
134
162
|
|
|
135
163
|
Rust indexing retains inline module scopes and discriminates same-named trait
|
|
@@ -143,4 +171,30 @@ workspace-inherited dependencies, re-export chains, macros and unknown receiver
|
|
|
143
171
|
types retain unresolved references. Glob imports retain a module dependency but
|
|
144
172
|
do not guess call targets. This is a conservative static graph, not rustc name
|
|
145
173
|
resolution or evidence that a call executes. Publish this package before a Victor
|
|
146
|
-
release that requires its
|
|
174
|
+
release that requires its exact repository graph contract.
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
### Embedding conformance (0.10.0)
|
|
178
|
+
|
|
179
|
+
The accepted FEP-0040 supplies store-neutral
|
|
180
|
+
`EmbeddingProfile`, `SymbolEmbedding`, `embed_symbols` and an optional
|
|
181
|
+
`SentenceTransformerEncoder`. The base compiler stays dependency-free. The
|
|
182
|
+
`embeddings` extra loads sentence-transformers only when explicitly selected;
|
|
183
|
+
private source is not sent to a remote model by default.
|
|
184
|
+
|
|
185
|
+
The contract uses canonical compiler IDs and `symbol-summary-v1` (name,
|
|
186
|
+
signature and documentation), with an immutable model revision, dimensions,
|
|
187
|
+
token limit, normalization, query prefix and separate input/source hashes.
|
|
188
|
+
Inference errors propagate; hash placeholders, zero vectors and malformed batches
|
|
189
|
+
are not semantic evidence. File/container symbols marked `embedding_excluded`
|
|
190
|
+
remain graph evidence only.
|
|
191
|
+
|
|
192
|
+
These APIs are introduced in 0.10.0 and are unavailable in 0.9.0. Install the
|
|
193
|
+
exact published `victor-codegraph[treesitter,embeddings]==0.10.0` when selecting
|
|
194
|
+
local model execution; the base package has no mandatory model dependency.
|
|
195
|
+
Current evidence covers
|
|
196
|
+
synthetic document-vector persistence parity and local LanceDB collection/query
|
|
197
|
+
profile enforcement. Labeled relevance and hosted lifecycle remain separate
|
|
198
|
+
acceptance gates. Victor binds a new LanceDB table to the complete profile in its
|
|
199
|
+
Arrow schema and rejects incompatible query encoders and mixed/orphan row markers.
|
|
200
|
+
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.
|
|
28
|
+
This source tree is version **0.10.1**; consumers should use a compatible `<1.0` pin.
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
# from PyPI
|
|
@@ -97,10 +97,36 @@ records = to_proxima_records(repository, repo_graph_id="myrepo")
|
|
|
97
97
|
|
|
98
98
|
## Status
|
|
99
99
|
|
|
100
|
-
`0.
|
|
100
|
+
`0.10.1` 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
|
|
|
104
|
+
### Ambiguous declarations
|
|
105
|
+
|
|
106
|
+
Duplicate structural identities retain raw declarations in `ParsedRepository.files`
|
|
107
|
+
and report each conflicting source location. Repository projection emits only the
|
|
108
|
+
canonical FILE marker for that file, with typed diagnostics, declaration spans and
|
|
109
|
+
omitted symbol/relation counts in metadata. Calls into its declarations remain
|
|
110
|
+
unresolved; healthy imports can still point to its diagnostic marker. Repairing the
|
|
111
|
+
source restores normal projection through deletion-complete repository deltas.
|
|
112
|
+
Direct record projection rejects duplicate identities before invoking an embedder,
|
|
113
|
+
including when legacy OIDs are selected.
|
|
114
|
+
|
|
115
|
+
`identity_collisions` groups use the existing `symbol_id` with `resolution: ambiguous`.
|
|
116
|
+
Each declaration retains `name`, `qualified_name`, `symbol_type` and its full
|
|
117
|
+
`source_span`; these are inspectable evidence, not separately resolved graph nodes.
|
|
118
|
+
The record adapter forwards these groups, projection status and omitted counts only
|
|
119
|
+
when present, without copying arbitrary symbol metadata into exported properties.
|
|
120
|
+
|
|
121
|
+
### TypeScript grammar limitation
|
|
122
|
+
|
|
123
|
+
The current tree-sitter grammar reports a partial parse for inline import types
|
|
124
|
+
in generic calls, for example `load<typeof import('child_process')>()`, even though
|
|
125
|
+
TypeScript accepts that syntax. A named type alias (`type ChildProcessModule =
|
|
126
|
+
typeof import('child_process')`, then `load<ChildProcessModule>()`) is supported.
|
|
127
|
+
Partial status and syntax diagnostics remain visible; the parser does not rewrite
|
|
128
|
+
source or infer missing relationships.
|
|
129
|
+
|
|
104
130
|
### Rust graph fidelity
|
|
105
131
|
|
|
106
132
|
Rust indexing retains inline module scopes and discriminates same-named trait
|
|
@@ -114,4 +140,30 @@ workspace-inherited dependencies, re-export chains, macros and unknown receiver
|
|
|
114
140
|
types retain unresolved references. Glob imports retain a module dependency but
|
|
115
141
|
do not guess call targets. This is a conservative static graph, not rustc name
|
|
116
142
|
resolution or evidence that a call executes. Publish this package before a Victor
|
|
117
|
-
release that requires its
|
|
143
|
+
release that requires its exact repository graph contract.
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
### Embedding conformance (0.10.0)
|
|
147
|
+
|
|
148
|
+
The accepted FEP-0040 supplies store-neutral
|
|
149
|
+
`EmbeddingProfile`, `SymbolEmbedding`, `embed_symbols` and an optional
|
|
150
|
+
`SentenceTransformerEncoder`. The base compiler stays dependency-free. The
|
|
151
|
+
`embeddings` extra loads sentence-transformers only when explicitly selected;
|
|
152
|
+
private source is not sent to a remote model by default.
|
|
153
|
+
|
|
154
|
+
The contract uses canonical compiler IDs and `symbol-summary-v1` (name,
|
|
155
|
+
signature and documentation), with an immutable model revision, dimensions,
|
|
156
|
+
token limit, normalization, query prefix and separate input/source hashes.
|
|
157
|
+
Inference errors propagate; hash placeholders, zero vectors and malformed batches
|
|
158
|
+
are not semantic evidence. File/container symbols marked `embedding_excluded`
|
|
159
|
+
remain graph evidence only.
|
|
160
|
+
|
|
161
|
+
These APIs are introduced in 0.10.0 and are unavailable in 0.9.0. Install the
|
|
162
|
+
exact published `victor-codegraph[treesitter,embeddings]==0.10.0` when selecting
|
|
163
|
+
local model execution; the base package has no mandatory model dependency.
|
|
164
|
+
Current evidence covers
|
|
165
|
+
synthetic document-vector persistence parity and local LanceDB collection/query
|
|
166
|
+
profile enforcement. Labeled relevance and hosted lifecycle remain separate
|
|
167
|
+
acceptance gates. Victor binds a new LanceDB table to the complete profile in its
|
|
168
|
+
Arrow schema and rejects incompatible query encoders and mixed/orphan row markers.
|
|
169
|
+
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.
|
|
7
|
+
version = "0.10.1"
|
|
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"]
|
|
@@ -26,6 +26,15 @@ def test_symbol_records_shape():
|
|
|
26
26
|
assert n["props"]["lang"] == "python"
|
|
27
27
|
assert n["props"]["ast_kind"] in ("FUNCTION", "METHOD", "CONSTRUCTOR")
|
|
28
28
|
assert n["embeddings"] == [] # no embedder supplied
|
|
29
|
+
assert (
|
|
30
|
+
not {
|
|
31
|
+
"identity_collisions",
|
|
32
|
+
"projection_status",
|
|
33
|
+
"omitted_symbol_count",
|
|
34
|
+
"omitted_relation_count",
|
|
35
|
+
}
|
|
36
|
+
& n["props"].keys()
|
|
37
|
+
)
|
|
29
38
|
|
|
30
39
|
|
|
31
40
|
def test_edge_records_reference_node_oids():
|
|
@@ -40,3 +40,30 @@ def test_legacy_oid_mode_keeps_edge_endpoints_attached():
|
|
|
40
40
|
edge = next(r for r in records if "graph_edge" in r["labels"])
|
|
41
41
|
assert edge["edge"]["from_oid"] in nodes
|
|
42
42
|
assert edge["edge"]["to_oid"] in nodes
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.parametrize("stable_oid", [True, False])
|
|
46
|
+
@pytest.mark.parametrize("embedding_mode", ["embedder", "batch_embedder"])
|
|
47
|
+
def test_duplicate_symbol_ids_fail_before_embedding(stable_oid, embedding_mode):
|
|
48
|
+
from unittest.mock import Mock
|
|
49
|
+
|
|
50
|
+
parsed = parse("def f(): return 1\ndef f(): return 2\n", file_path="duplicate.py")
|
|
51
|
+
embedder = Mock(
|
|
52
|
+
return_value=[0.0, 0.0] if embedding_mode == "embedder" else [[0.0, 0.0], [0.0, 0.0]]
|
|
53
|
+
)
|
|
54
|
+
with pytest.raises(ValueError, match="identity collision"):
|
|
55
|
+
to_proxima_records(
|
|
56
|
+
parsed, "repo", stable_oid=stable_oid, dim=2, **{embedding_mode: embedder}
|
|
57
|
+
)
|
|
58
|
+
embedder.assert_not_called()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_symbol_alias_cannot_rebind_another_canonical_identity():
|
|
62
|
+
from unittest.mock import Mock
|
|
63
|
+
|
|
64
|
+
parsed = parse("def a(): pass\ndef b(): pass\n", file_path="aliases.py")
|
|
65
|
+
parsed.symbols[1].legacy_id = parsed.symbols[0].id
|
|
66
|
+
embedder = Mock(return_value=[0.0, 0.0])
|
|
67
|
+
with pytest.raises(ValueError, match="alias identity collision"):
|
|
68
|
+
to_proxima_records(parsed, "repo", embedder=embedder, dim=2)
|
|
69
|
+
embedder.assert_not_called()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from victor_codegraph import CapabilityTier, ChunkConfig, ParseStatus, parse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_syntax_error_reports_fallback_diagnostic():
|
|
7
|
+
parsed = parse("def broken(:\n", file_path="bad.py")
|
|
8
|
+
assert parsed.status == ParseStatus.FALLBACK
|
|
9
|
+
assert parsed.diagnostics
|
|
10
|
+
assert parsed.diagnostics[0].code == "syntax_error"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_unknown_language_reports_fallback_instead_of_silent_success():
|
|
14
|
+
parsed = parse("plain text", language="unknown", file_path="x.unknown")
|
|
15
|
+
assert parsed.status == ParseStatus.FALLBACK
|
|
16
|
+
assert parsed.capability_tier == CapabilityTier.FALLBACK
|
|
17
|
+
assert parsed.diagnostics
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_extract_relations_config_is_effective():
|
|
21
|
+
parsed = parse(
|
|
22
|
+
"def f():\n return g()\n",
|
|
23
|
+
file_path="m.py",
|
|
24
|
+
config=ChunkConfig(extract_relations=False),
|
|
25
|
+
)
|
|
26
|
+
assert parsed.relations == []
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_shadowed_python_declarations_remain_explicit_identity_collisions():
|
|
30
|
+
parsed = parse(
|
|
31
|
+
"class C:\n @property\n def value(self): return 1\n"
|
|
32
|
+
" @property\n def value(self): return 2\n",
|
|
33
|
+
file_path="duplicate.py",
|
|
34
|
+
)
|
|
35
|
+
assert parsed.status == ParseStatus.PARTIAL
|
|
36
|
+
assert [(item.code, item.severity) for item in parsed.diagnostics] == [
|
|
37
|
+
("identity_collision", "error"),
|
|
38
|
+
("identity_collision", "error"),
|
|
39
|
+
]
|
|
40
|
+
assert [(item.line, item.column) for item in parsed.diagnostics] == [(2, 4), (4, 4)]
|
|
41
|
+
definitions = [symbol for symbol in parsed.symbols if symbol.simple_name == "value"]
|
|
42
|
+
assert len(definitions) == 2
|
|
43
|
+
assert definitions[0].id == definitions[1].id
|
|
44
|
+
assert definitions[0].legacy_id != definitions[1].legacy_id
|
|
45
|
+
assert all("duplicate.py::C::value" in item.message for item in parsed.diagnostics)
|
|
46
|
+
assert all(definitions[0].id in item.message for item in parsed.diagnostics)
|
|
@@ -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)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import replace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from victor_codegraph import apply_index_delta, diff_repository, parse_repo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_repository_delta_deletes_removed_and_replaced_symbols(tmp_path):
|
|
11
|
+
a = tmp_path / "a.py"
|
|
12
|
+
b = tmp_path / "b.py"
|
|
13
|
+
a.write_text("def old_name():\n return 1\n")
|
|
14
|
+
b.write_text("def removed():\n return 2\n")
|
|
15
|
+
previous = parse_repo(tmp_path, repo_id="repo")
|
|
16
|
+
|
|
17
|
+
a.write_text("def new_name():\n return 1\n")
|
|
18
|
+
b.unlink()
|
|
19
|
+
current = parse_repo(tmp_path, repo_id="repo")
|
|
20
|
+
delta = diff_repository(current, previous)
|
|
21
|
+
|
|
22
|
+
old_function_ids = {s.id for s in previous.symbols if s.symbol_type.name == "FUNCTION"}
|
|
23
|
+
assert old_function_ids <= set(delta.delete_symbol_ids)
|
|
24
|
+
assert "new_name" in {s.simple_name for s in delta.upsert_symbols}
|
|
25
|
+
|
|
26
|
+
rebuilt = apply_index_delta(previous, delta)
|
|
27
|
+
assert {s.id for s in rebuilt.symbols} == {s.id for s in current.symbols}
|
|
28
|
+
assert rebuilt.manifest == current.manifest
|
|
29
|
+
assert delta.base_generation_id == previous.generation_id
|
|
30
|
+
assert delta.target_generation_id == current.generation_id
|
|
31
|
+
|
|
32
|
+
with pytest.raises(ValueError, match="base generation"):
|
|
33
|
+
apply_index_delta(replace(previous, generation_id="stale"), delta)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_repository_delta_refreshes_relation_evidence_on_line_shift(tmp_path):
|
|
37
|
+
path = tmp_path / "m.py"
|
|
38
|
+
path.write_text("def a():\n return b()\n\ndef b(): return 1\n")
|
|
39
|
+
previous = parse_repo(tmp_path, repo_id="repo")
|
|
40
|
+
path.write_text("\ndef a():\n return b()\n\ndef b(): return 1\n")
|
|
41
|
+
current = parse_repo(tmp_path, repo_id="repo")
|
|
42
|
+
|
|
43
|
+
delta = diff_repository(current, previous)
|
|
44
|
+
assert delta.upsert_relations
|
|
45
|
+
rebuilt = apply_index_delta(previous, delta)
|
|
46
|
+
assert rebuilt.relations[0].call_site.start_line == current.relations[0].call_site.start_line
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_repository_quarantines_identity_collisions_and_delta_recovers(tmp_path):
|
|
50
|
+
from victor_codegraph import CodeRelationType, CodeSymbolType, ParseStatus
|
|
51
|
+
|
|
52
|
+
ambiguous = tmp_path / "duplicate.py"
|
|
53
|
+
ambiguous.write_text("def helper(): return 1\n")
|
|
54
|
+
(tmp_path / "caller.py").write_text(
|
|
55
|
+
"from duplicate import helper\ndef caller(): return helper()\n"
|
|
56
|
+
)
|
|
57
|
+
previous = parse_repo(tmp_path, repo_id="repo")
|
|
58
|
+
ambiguous.write_text(
|
|
59
|
+
"def f(): return helper()\ndef f(): return helper()\ndef helper(): return 1\n"
|
|
60
|
+
)
|
|
61
|
+
current = parse_repo(tmp_path, repo_id="repo")
|
|
62
|
+
raw = current.files["duplicate.py"]
|
|
63
|
+
assert raw.status == ParseStatus.PARTIAL
|
|
64
|
+
assert len(raw.symbols) == 3 # Keep original evidence, including both definitions.
|
|
65
|
+
projected = [s for s in current.symbols if s.location.file_path == "duplicate.py"]
|
|
66
|
+
assert len(projected) == 1
|
|
67
|
+
marker = projected[0]
|
|
68
|
+
assert marker.symbol_type == CodeSymbolType.FILE
|
|
69
|
+
assert marker.metadata["parse_status"] == "partial"
|
|
70
|
+
assert marker.metadata["projection_status"] == "file_marker_only"
|
|
71
|
+
assert marker.metadata["omitted_symbol_count"] == 3
|
|
72
|
+
assert marker.metadata["omitted_relation_count"] == len(raw.relations)
|
|
73
|
+
assert [d["line"] for d in marker.metadata["diagnostics"]] == [1, 2]
|
|
74
|
+
collisions = marker.metadata["identity_collisions"]
|
|
75
|
+
assert len(collisions) == 1 and collisions[0]["symbol_id"] == raw.symbols[0].id
|
|
76
|
+
assert collisions[0]["resolution"] == "ambiguous"
|
|
77
|
+
assert [d["qualified_name"] for d in collisions[0]["declarations"]] == [
|
|
78
|
+
"duplicate.py::f",
|
|
79
|
+
"duplicate.py::f",
|
|
80
|
+
]
|
|
81
|
+
assert [d["symbol_type"] for d in collisions[0]["declarations"]] == ["function", "function"]
|
|
82
|
+
assert [d["source_span"]["start_line"] for d in collisions[0]["declarations"]] == [1, 2]
|
|
83
|
+
from victor_codegraph import to_proxima_records
|
|
84
|
+
|
|
85
|
+
marker.metadata["private_source_metadata"] = "must not be exported"
|
|
86
|
+
records = to_proxima_records(current, repo_graph_id="repo")
|
|
87
|
+
record = next(
|
|
88
|
+
r
|
|
89
|
+
for r in records
|
|
90
|
+
if r.get("props", {}).get("file") == "duplicate.py"
|
|
91
|
+
and r.get("props", {}).get("ast_kind") == "FILE"
|
|
92
|
+
)
|
|
93
|
+
for key in (
|
|
94
|
+
"identity_collisions",
|
|
95
|
+
"projection_status",
|
|
96
|
+
"omitted_symbol_count",
|
|
97
|
+
"omitted_relation_count",
|
|
98
|
+
):
|
|
99
|
+
assert record["props"][key] == marker.metadata[key]
|
|
100
|
+
assert "private_source_metadata" not in record["props"]
|
|
101
|
+
assert not any(
|
|
102
|
+
"/external/" not in r["edge"]["to_oid"]
|
|
103
|
+
for r in records
|
|
104
|
+
if "graph_edge" in r["labels"] and r["edge"]["edge_type"] == "CALLS"
|
|
105
|
+
)
|
|
106
|
+
assert len({s.id for s in current.symbols}) == len(current.symbols)
|
|
107
|
+
assert not any(r.from_symbol_id == marker.id for r in current.relations)
|
|
108
|
+
calls = [r for r in current.relations if r.relation_type == CodeRelationType.CALLS]
|
|
109
|
+
assert len(calls) == 1 and calls[0].target_ref.name == "helper"
|
|
110
|
+
assert any(
|
|
111
|
+
r.relation_type == CodeRelationType.IMPORTS and r.to_symbol_id == marker.id
|
|
112
|
+
for r in current.relations
|
|
113
|
+
)
|
|
114
|
+
delta = diff_repository(current, previous)
|
|
115
|
+
assert delta.delete_symbol_ids and delta.delete_relation_keys
|
|
116
|
+
rebuilt = apply_index_delta(previous, delta)
|
|
117
|
+
assert {s.id for s in rebuilt.symbols} == {s.id for s in current.symbols}
|
|
118
|
+
|
|
119
|
+
ambiguous.write_text("def helper(): return 2\n")
|
|
120
|
+
repaired = parse_repo(tmp_path, repo_id="repo")
|
|
121
|
+
rebuilt = apply_index_delta(current, diff_repository(repaired, current))
|
|
122
|
+
assert {s.id for s in rebuilt.symbols} == {s.id for s in repaired.symbols}
|
|
123
|
+
calls = [r for r in rebuilt.relations if r.relation_type == CodeRelationType.CALLS]
|
|
124
|
+
assert len(calls) == 1 and calls[0].target_ref is None
|
|
125
|
+
assert (
|
|
126
|
+
next(s for s in repaired.symbols if s.id == marker.id).metadata["projection_status"]
|
|
127
|
+
== "symbols"
|
|
128
|
+
)
|
|
@@ -75,3 +75,38 @@ def test_chunk_jsts_is_size_capped():
|
|
|
75
75
|
if not parsed.symbols:
|
|
76
76
|
pytest.skip("javascript grammar not installed")
|
|
77
77
|
assert parsed.symbols # routed through tree-sitter, not the empty stub
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@pytest.mark.parametrize(
|
|
81
|
+
"annotation",
|
|
82
|
+
[
|
|
83
|
+
"export type X<T> = T extends object ? T : never;",
|
|
84
|
+
"type Options = Record<string, object>;",
|
|
85
|
+
"function accept(value: object): object { return value; }",
|
|
86
|
+
],
|
|
87
|
+
)
|
|
88
|
+
def test_typescript_object_keyword_is_not_an_object_literal_scope(annotation):
|
|
89
|
+
source = annotation + "\nconst handlers = { run() { return 1; } };"
|
|
90
|
+
parsed = parse(source, file_path="case.ts")
|
|
91
|
+
shifted = parse("// heading\n\n" + source, file_path="case.ts")
|
|
92
|
+
assert parsed.status.value == "success"
|
|
93
|
+
assert not parsed.diagnostics
|
|
94
|
+
methods = [symbol for symbol in parsed.symbols if symbol.simple_name == "run"]
|
|
95
|
+
assert len(methods) == 1
|
|
96
|
+
assert methods[0].scope_chain
|
|
97
|
+
assert {symbol.id for symbol in parsed.symbols} == {symbol.id for symbol in shifted.symbols}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_inline_import_generic_grammar_limitation_stays_explicit():
|
|
101
|
+
# The pinned grammar rejects this valid TypeScript import type in a generic
|
|
102
|
+
# call. Do not hide the partial parse; an equivalent named alias is supported.
|
|
103
|
+
inline = parse("const value = load<typeof import('child_process')>();", file_path="case.ts")
|
|
104
|
+
assert inline.status.value == "partial"
|
|
105
|
+
assert "syntax_error" in {diagnostic.code for diagnostic in inline.diagnostics}
|
|
106
|
+
aliased = parse(
|
|
107
|
+
"type ChildProcessModule = typeof import('child_process');"
|
|
108
|
+
"const value = load<ChildProcessModule>();",
|
|
109
|
+
file_path="case.ts",
|
|
110
|
+
)
|
|
111
|
+
assert aliased.status.value == "success"
|
|
112
|
+
assert not aliased.diagnostics
|
|
@@ -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.
|
|
57
|
+
__version__ = "0.10.1"
|
|
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",
|