victor-codegraph 0.8.1__tar.gz → 0.9.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.
- victor_codegraph-0.9.0/PKG-INFO +146 -0
- victor_codegraph-0.9.0/README.md +117 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/pyproject.toml +15 -15
- victor_codegraph-0.9.0/tests/test_adapter_validation_v2.py +42 -0
- victor_codegraph-0.9.0/tests/test_chunk_contract_v2.py +75 -0
- victor_codegraph-0.9.0/tests/test_diagnostics_v2.py +26 -0
- victor_codegraph-0.9.0/tests/test_identity_v2.py +121 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_python_parser.py +19 -3
- victor_codegraph-0.9.0/tests/test_relations_v2.py +491 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_repo.py +1 -0
- victor_codegraph-0.9.0/tests/test_repository_delta_v2.py +46 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_symbol_oid_parity.py +1 -1
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_treesitter_relations.py +3 -2
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph/__init__.py +26 -2
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph/adapter.py +142 -17
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph/config.py +11 -0
- victor_codegraph-0.9.0/victor_codegraph/javascript_repository.py +164 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph/model.py +101 -1
- victor_codegraph-0.9.0/victor_codegraph/parser.py +329 -0
- victor_codegraph-0.9.0/victor_codegraph/python_parser.py +396 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph/repo.py +15 -6
- victor_codegraph-0.9.0/victor_codegraph/repository_index.py +460 -0
- victor_codegraph-0.9.0/victor_codegraph/resolution.py +209 -0
- victor_codegraph-0.9.0/victor_codegraph/rust_repository.py +236 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph/sizing.py +64 -52
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph/treesitter_parser.py +236 -25
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph/treesitter_tables.py +2 -0
- victor_codegraph-0.9.0/victor_codegraph.egg-info/PKG-INFO +146 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph.egg-info/SOURCES.txt +9 -0
- victor_codegraph-0.9.0/victor_codegraph.egg-info/requires.txt +13 -0
- victor_codegraph-0.8.1/PKG-INFO +0 -107
- victor_codegraph-0.8.1/README.md +0 -77
- victor_codegraph-0.8.1/victor_codegraph/parser.py +0 -122
- victor_codegraph-0.8.1/victor_codegraph/python_parser.py +0 -253
- victor_codegraph-0.8.1/victor_codegraph/resolution.py +0 -68
- victor_codegraph-0.8.1/victor_codegraph.egg-info/PKG-INFO +0 -107
- victor_codegraph-0.8.1/victor_codegraph.egg-info/requires.txt +0 -13
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/setup.cfg +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_adapter.py +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_line_base_contract.py +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_manifest.py +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_sizing_and_chunk.py +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_sizing_overlap.py +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_symbol_oid_contract.py +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_treesitter_jsts.py +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/tests/test_treesitter_languages.py +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph/languages.py +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph/manifest.py +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph.egg-info/dependency_links.txt +0 -0
- {victor_codegraph-0.8.1 → victor_codegraph-0.9.0}/victor_codegraph.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: victor-codegraph
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Repository-to-Code-Semantic-Graph compiler with stable identity, bounded chunks, incremental deltas, and ProximaRecord projection.
|
|
5
|
+
Author-email: Vijaykumar Singh <vijay@anvaiops.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/anvai-labs/victor
|
|
8
|
+
Project-URL: Repository, https://github.com/anvai-labs/victor
|
|
9
|
+
Keywords: code-graph,cpg,semantic-graph,chunking,tree-sitter,ast,embeddings,rag
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Provides-Extra: treesitter
|
|
20
|
+
Requires-Dist: tree-sitter>=0.26.0; extra == "treesitter"
|
|
21
|
+
Requires-Dist: tree-sitter-language-pack>=1.21.3; extra == "treesitter"
|
|
22
|
+
Provides-Extra: contracts
|
|
23
|
+
Requires-Dist: victor-contracts<1.0,>=0.9.1; extra == "contracts"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=9.1.1; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff>=0.16.7; extra == "dev"
|
|
27
|
+
Requires-Dist: tree-sitter>=0.26.0; extra == "dev"
|
|
28
|
+
Requires-Dist: tree-sitter-language-pack>=1.21.3; extra == "dev"
|
|
29
|
+
|
|
30
|
+
# victor-codegraph
|
|
31
|
+
|
|
32
|
+
Shared **repository → Code Semantic Graph compiler**: symbol and relation extraction,
|
|
33
|
+
hard-bounded embeddable chunks, repository-wide resolution/deltas, diagnostics, and a
|
|
34
|
+
`ProximaRecord` projection. One compiler, three
|
|
35
|
+
consumers — Victor (owner), the ProximaDB SDK (`[codegraph]` extra), and AnvaiOps (SaaS
|
|
36
|
+
code-graph vertical).
|
|
37
|
+
|
|
38
|
+
> Design: ProximaDB `ADR-029` (authoritative) · Victor `ADR-014` (owner/donor) ·
|
|
39
|
+
> AnvaiOps `ADR-0018` (consumer). This package is the **TD-CG1** scaffold.
|
|
40
|
+
|
|
41
|
+
## Why
|
|
42
|
+
|
|
43
|
+
The same tree-sitter code→symbol+relation chunker existed twice (ProximaDB SDK `code.py`
|
|
44
|
+
and Victor `victor-coding`) and was about to be written a third time in AnvaiOps. This
|
|
45
|
+
package is the single neutral home. It merges the best of both donors and fixes their two
|
|
46
|
+
gaps:
|
|
47
|
+
|
|
48
|
+
- **Size-capping** — ProximaDB's `code.py` emitted one chunk per symbol with *no* size
|
|
49
|
+
bound (a huge function became a huge chunk). Here, oversized symbols are body-split with
|
|
50
|
+
overlap (LlamaIndex `CodeSplitter` discipline). See `sizing.py`.
|
|
51
|
+
- **Real JS/TS** — the donor JS/TS parser was a stub returning no symbols. Here JS/TS get a
|
|
52
|
+
real tree-sitter extractor (functions, classes, methods, `const … = () =>`, imports).
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
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.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# from PyPI
|
|
61
|
+
pip install victor-codegraph # Python-only (stdlib ast) path, zero native deps
|
|
62
|
+
pip install "victor-codegraph[treesitter]" # + multi-language tree-sitter grammars
|
|
63
|
+
|
|
64
|
+
# dev (monorepo): editable, with tree-sitter grammars + test tooling
|
|
65
|
+
make -C victor-codegraph dev # = pip install -e ../victor-contracts && pip install -e ".[dev]"
|
|
66
|
+
pip install -e ./victor-codegraph # minimal editable install
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Releasing
|
|
70
|
+
|
|
71
|
+
CI: `.github/workflows/ci-codegraph.yml` runs the suite (editable install, grammars on) for every
|
|
72
|
+
PR touching `victor-codegraph/**`. Publishing: push a tag `victor-codegraph-v0.1.0` to trigger
|
|
73
|
+
`.github/workflows/release-codegraph.yml`, which builds and publishes via **PyPI Trusted Publishing**
|
|
74
|
+
(OIDC — no API token). Configure the publisher once on PyPI (owner `vjsingh1984`, repo `victor`,
|
|
75
|
+
workflow `release-codegraph.yml`, environments `pypi` / `testpypi`); see the header of that workflow.
|
|
76
|
+
|
|
77
|
+
## 0.9 consumer qualification and migration
|
|
78
|
+
|
|
79
|
+
| Surface | Contract / limit |
|
|
80
|
+
|---|---|
|
|
81
|
+
| Python references | Exact import aliases and dotted modules; lexical scope precedes arity. Unknown receivers, wildcard ambiguity and shadowed bindings remain explicit unresolved references. No runtime execution/type inference. |
|
|
82
|
+
| ES modules | Relative named/default/namespace imports, explicit named re-exports and exported arrow functions. Type-only imports produce dependencies, never runtime call evidence. Re-export traversal is cycle-safe and bounded to 64 bindings. Package/tsconfig aliases, star exports and dynamic exports remain unresolved. |
|
|
83
|
+
| Object-literal identity | Methods include structural object scopes, including anonymous callback ancestry. Whitespace/comments and checkout root do not affect IDs; inserting/reordering anonymous siblings can change structural ordinals. |
|
|
84
|
+
| Source evidence | Python call spans include UTF-8 byte offsets/lengths and end coordinates. New binding metadata is diagnostic evidence, not runtime authorization or a complete language semantic model. |
|
|
85
|
+
| Victor persistence | The canonical snapshot supplies symbols. CLI distinguishes attempted upserts from persisted totals, exposes partial parses and accepts exact opaque IDs; ambiguous display names are not chosen arbitrarily. Default file exclusions can differ from the package walker. |
|
|
86
|
+
|
|
87
|
+
Rebuild derived indexes after upgrading from older parser versions; an unchanged
|
|
88
|
+
source hash alone does not migrate older IDs/edges. Victor users can run
|
|
89
|
+
`victor graph index --force --no-ccg --no-embeddings`. Consumers must stage a new
|
|
90
|
+
complete generation and switch readers after validation; never mix old and new
|
|
91
|
+
symbol identities. The AnvaiOps adapter/generation migration remains consumer-owned.
|
|
92
|
+
The package adds no commercial runtime, account, entitlement or billing dependency.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Use
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from victor_codegraph import chunk, parse, parse_repo, to_proxima_records, ChunkConfig
|
|
99
|
+
|
|
100
|
+
# Size-capped, embeddable chunks:
|
|
101
|
+
chunks = chunk(source, file_path="app/service.py", config=ChunkConfig(max_chunk_tokens=512))
|
|
102
|
+
|
|
103
|
+
# Symbols + relations:
|
|
104
|
+
parsed = parse(source, file_path="app/service.py")
|
|
105
|
+
|
|
106
|
+
# Project to the ProximaDB substrate-keystone record shape (one symbol = row+node+vector):
|
|
107
|
+
records = to_proxima_records(parsed, repo_graph_id="myrepo", branch_id="main",
|
|
108
|
+
embedder=my_embed_fn) # embedder optional
|
|
109
|
+
|
|
110
|
+
# Repository-wide resolution + file/import/containment graph:
|
|
111
|
+
repository = parse_repo(".", repo_id="myrepo")
|
|
112
|
+
records = to_proxima_records(repository, repo_graph_id="myrepo")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Design principles (the "best posture" this encodes)
|
|
116
|
+
|
|
117
|
+
1. Chunk at **symbol** granularity (not statement, not fixed-size).
|
|
118
|
+
2. **Newline-preferred and hard-bounded** — forced character splits are explicit; an optional
|
|
119
|
+
tokenizer callback enforces the real model limit.
|
|
120
|
+
3. Extract a **Code Semantic Graph** (CALLS/EXTENDS/IMPLEMENTS/CONTAINS/IMPORTS). Full
|
|
121
|
+
intra-procedural CFG/CDG/DDG is a separate optional layer, not implied here.
|
|
122
|
+
4. **One structural v2 ID** across symbols, chunks, relations, and records; line IDs remain aliases.
|
|
123
|
+
5. **Deletion-complete deltas** — incremental output can be checked against a full rebuild.
|
|
124
|
+
6. **Observable fallback** — every parse reports status, capability tier, and diagnostics.
|
|
125
|
+
7. **Explicit uncertainty** — unresolved targets become structured external-reference nodes.
|
|
126
|
+
|
|
127
|
+
## Status
|
|
128
|
+
|
|
129
|
+
`0.9.0` source version. Python (`ast`) is the full, offline frontend. JS/TS, Go, Rust,
|
|
130
|
+
Java, C, and C++ have conformance fixtures; other mapped tree-sitter languages report
|
|
131
|
+
symbols-tier or fallback status rather than implying full fidelity.
|
|
132
|
+
|
|
133
|
+
### Rust graph fidelity
|
|
134
|
+
|
|
135
|
+
Rust indexing retains inline module scopes and discriminates same-named trait
|
|
136
|
+
implementations and conditional definitions in stable identities. Repository
|
|
137
|
+
resolution follows declared library modules and explicit top-level `use` paths,
|
|
138
|
+
including grouped imports, aliases, and local Cargo path dependencies. Hyphenated
|
|
139
|
+
Cargo dependency names are normalized to their Rust underscore names.
|
|
140
|
+
|
|
141
|
+
Binary-owned or ambiguous files, conditional items/imports, `#[path]` overrides,
|
|
142
|
+
workspace-inherited dependencies, re-export chains, macros and unknown receiver
|
|
143
|
+
types retain unresolved references. Glob imports retain a module dependency but
|
|
144
|
+
do not guess call targets. This is a conservative static graph, not rustc name
|
|
145
|
+
resolution or evidence that a call executes. Publish this package before a Victor
|
|
146
|
+
release that requires its `0.9.0` repository graph contract.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# victor-codegraph
|
|
2
|
+
|
|
3
|
+
Shared **repository → Code Semantic Graph compiler**: symbol and relation extraction,
|
|
4
|
+
hard-bounded embeddable chunks, repository-wide resolution/deltas, diagnostics, and a
|
|
5
|
+
`ProximaRecord` projection. One compiler, three
|
|
6
|
+
consumers — Victor (owner), the ProximaDB SDK (`[codegraph]` extra), and AnvaiOps (SaaS
|
|
7
|
+
code-graph vertical).
|
|
8
|
+
|
|
9
|
+
> Design: ProximaDB `ADR-029` (authoritative) · Victor `ADR-014` (owner/donor) ·
|
|
10
|
+
> AnvaiOps `ADR-0018` (consumer). This package is the **TD-CG1** scaffold.
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
The same tree-sitter code→symbol+relation chunker existed twice (ProximaDB SDK `code.py`
|
|
15
|
+
and Victor `victor-coding`) and was about to be written a third time in AnvaiOps. This
|
|
16
|
+
package is the single neutral home. It merges the best of both donors and fixes their two
|
|
17
|
+
gaps:
|
|
18
|
+
|
|
19
|
+
- **Size-capping** — ProximaDB's `code.py` emitted one chunk per symbol with *no* size
|
|
20
|
+
bound (a huge function became a huge chunk). Here, oversized symbols are body-split with
|
|
21
|
+
overlap (LlamaIndex `CodeSplitter` discipline). See `sizing.py`.
|
|
22
|
+
- **Real JS/TS** — the donor JS/TS parser was a stub returning no symbols. Here JS/TS get a
|
|
23
|
+
real tree-sitter extractor (functions, classes, methods, `const … = () =>`, imports).
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
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.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# from PyPI
|
|
32
|
+
pip install victor-codegraph # Python-only (stdlib ast) path, zero native deps
|
|
33
|
+
pip install "victor-codegraph[treesitter]" # + multi-language tree-sitter grammars
|
|
34
|
+
|
|
35
|
+
# dev (monorepo): editable, with tree-sitter grammars + test tooling
|
|
36
|
+
make -C victor-codegraph dev # = pip install -e ../victor-contracts && pip install -e ".[dev]"
|
|
37
|
+
pip install -e ./victor-codegraph # minimal editable install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Releasing
|
|
41
|
+
|
|
42
|
+
CI: `.github/workflows/ci-codegraph.yml` runs the suite (editable install, grammars on) for every
|
|
43
|
+
PR touching `victor-codegraph/**`. Publishing: push a tag `victor-codegraph-v0.1.0` to trigger
|
|
44
|
+
`.github/workflows/release-codegraph.yml`, which builds and publishes via **PyPI Trusted Publishing**
|
|
45
|
+
(OIDC — no API token). Configure the publisher once on PyPI (owner `vjsingh1984`, repo `victor`,
|
|
46
|
+
workflow `release-codegraph.yml`, environments `pypi` / `testpypi`); see the header of that workflow.
|
|
47
|
+
|
|
48
|
+
## 0.9 consumer qualification and migration
|
|
49
|
+
|
|
50
|
+
| Surface | Contract / limit |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Python references | Exact import aliases and dotted modules; lexical scope precedes arity. Unknown receivers, wildcard ambiguity and shadowed bindings remain explicit unresolved references. No runtime execution/type inference. |
|
|
53
|
+
| ES modules | Relative named/default/namespace imports, explicit named re-exports and exported arrow functions. Type-only imports produce dependencies, never runtime call evidence. Re-export traversal is cycle-safe and bounded to 64 bindings. Package/tsconfig aliases, star exports and dynamic exports remain unresolved. |
|
|
54
|
+
| Object-literal identity | Methods include structural object scopes, including anonymous callback ancestry. Whitespace/comments and checkout root do not affect IDs; inserting/reordering anonymous siblings can change structural ordinals. |
|
|
55
|
+
| Source evidence | Python call spans include UTF-8 byte offsets/lengths and end coordinates. New binding metadata is diagnostic evidence, not runtime authorization or a complete language semantic model. |
|
|
56
|
+
| Victor persistence | The canonical snapshot supplies symbols. CLI distinguishes attempted upserts from persisted totals, exposes partial parses and accepts exact opaque IDs; ambiguous display names are not chosen arbitrarily. Default file exclusions can differ from the package walker. |
|
|
57
|
+
|
|
58
|
+
Rebuild derived indexes after upgrading from older parser versions; an unchanged
|
|
59
|
+
source hash alone does not migrate older IDs/edges. Victor users can run
|
|
60
|
+
`victor graph index --force --no-ccg --no-embeddings`. Consumers must stage a new
|
|
61
|
+
complete generation and switch readers after validation; never mix old and new
|
|
62
|
+
symbol identities. The AnvaiOps adapter/generation migration remains consumer-owned.
|
|
63
|
+
The package adds no commercial runtime, account, entitlement or billing dependency.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## Use
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from victor_codegraph import chunk, parse, parse_repo, to_proxima_records, ChunkConfig
|
|
70
|
+
|
|
71
|
+
# Size-capped, embeddable chunks:
|
|
72
|
+
chunks = chunk(source, file_path="app/service.py", config=ChunkConfig(max_chunk_tokens=512))
|
|
73
|
+
|
|
74
|
+
# Symbols + relations:
|
|
75
|
+
parsed = parse(source, file_path="app/service.py")
|
|
76
|
+
|
|
77
|
+
# Project to the ProximaDB substrate-keystone record shape (one symbol = row+node+vector):
|
|
78
|
+
records = to_proxima_records(parsed, repo_graph_id="myrepo", branch_id="main",
|
|
79
|
+
embedder=my_embed_fn) # embedder optional
|
|
80
|
+
|
|
81
|
+
# Repository-wide resolution + file/import/containment graph:
|
|
82
|
+
repository = parse_repo(".", repo_id="myrepo")
|
|
83
|
+
records = to_proxima_records(repository, repo_graph_id="myrepo")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Design principles (the "best posture" this encodes)
|
|
87
|
+
|
|
88
|
+
1. Chunk at **symbol** granularity (not statement, not fixed-size).
|
|
89
|
+
2. **Newline-preferred and hard-bounded** — forced character splits are explicit; an optional
|
|
90
|
+
tokenizer callback enforces the real model limit.
|
|
91
|
+
3. Extract a **Code Semantic Graph** (CALLS/EXTENDS/IMPLEMENTS/CONTAINS/IMPORTS). Full
|
|
92
|
+
intra-procedural CFG/CDG/DDG is a separate optional layer, not implied here.
|
|
93
|
+
4. **One structural v2 ID** across symbols, chunks, relations, and records; line IDs remain aliases.
|
|
94
|
+
5. **Deletion-complete deltas** — incremental output can be checked against a full rebuild.
|
|
95
|
+
6. **Observable fallback** — every parse reports status, capability tier, and diagnostics.
|
|
96
|
+
7. **Explicit uncertainty** — unresolved targets become structured external-reference nodes.
|
|
97
|
+
|
|
98
|
+
## Status
|
|
99
|
+
|
|
100
|
+
`0.9.0` source version. Python (`ast`) is the full, offline frontend. JS/TS, Go, Rust,
|
|
101
|
+
Java, C, and C++ have conformance fixtures; other mapped tree-sitter languages report
|
|
102
|
+
symbols-tier or fallback status rather than implying full fidelity.
|
|
103
|
+
|
|
104
|
+
### Rust graph fidelity
|
|
105
|
+
|
|
106
|
+
Rust indexing retains inline module scopes and discriminates same-named trait
|
|
107
|
+
implementations and conditional definitions in stable identities. Repository
|
|
108
|
+
resolution follows declared library modules and explicit top-level `use` paths,
|
|
109
|
+
including grouped imports, aliases, and local Cargo path dependencies. Hyphenated
|
|
110
|
+
Cargo dependency names are normalized to their Rust underscore names.
|
|
111
|
+
|
|
112
|
+
Binary-owned or ambiguous files, conditional items/imports, `#[path]` overrides,
|
|
113
|
+
workspace-inherited dependencies, re-export chains, macros and unknown receiver
|
|
114
|
+
types retain unresolved references. Glob imports retain a module dependency but
|
|
115
|
+
do not guess call targets. This is a conservative static graph, not rustc name
|
|
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.
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=84.0.0", "wheel"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "victor-codegraph"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Code
|
|
7
|
+
version = "0.9.0"
|
|
8
|
+
description = "Repository-to-Code-Semantic-Graph compiler with stable identity, bounded chunks, incremental deltas, and ProximaRecord projection."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
11
|
license = { text = "Apache-2.0" }
|
|
12
12
|
authors = [
|
|
13
|
-
{ name = "Vijaykumar Singh", email = "
|
|
13
|
+
{ name = "Vijaykumar Singh", email = "vijay@anvaiops.com" },
|
|
14
14
|
]
|
|
15
15
|
keywords = [
|
|
16
16
|
"code-graph",
|
|
17
17
|
"cpg",
|
|
18
|
+
"semantic-graph",
|
|
18
19
|
"chunking",
|
|
19
20
|
"tree-sitter",
|
|
20
21
|
"ast",
|
|
@@ -26,9 +27,8 @@ classifiers = [
|
|
|
26
27
|
"Intended Audience :: Developers",
|
|
27
28
|
"License :: OSI Approved :: Apache Software License",
|
|
28
29
|
"Programming Language :: Python :: 3",
|
|
29
|
-
"Programming Language :: Python :: 3.10",
|
|
30
|
-
"Programming Language :: Python :: 3.11",
|
|
31
30
|
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
32
|
"Topic :: Software Development :: Libraries",
|
|
33
33
|
]
|
|
34
34
|
|
|
@@ -41,17 +41,17 @@ dependencies = []
|
|
|
41
41
|
|
|
42
42
|
[project.optional-dependencies]
|
|
43
43
|
treesitter = [
|
|
44
|
-
"tree-sitter>=0.
|
|
45
|
-
"tree-sitter-language-pack>=1.
|
|
44
|
+
"tree-sitter>=0.26.0",
|
|
45
|
+
"tree-sitter-language-pack>=1.21.3",
|
|
46
46
|
]
|
|
47
47
|
contracts = [
|
|
48
|
-
"victor-contracts>=0.
|
|
48
|
+
"victor-contracts>=0.9.1,<1.0",
|
|
49
49
|
]
|
|
50
50
|
dev = [
|
|
51
|
-
"pytest>=
|
|
52
|
-
"ruff>=0.
|
|
53
|
-
"tree-sitter>=0.
|
|
54
|
-
"tree-sitter-language-pack>=1.
|
|
51
|
+
"pytest>=9.1.1",
|
|
52
|
+
"ruff>=0.16.7",
|
|
53
|
+
"tree-sitter>=0.26.0",
|
|
54
|
+
"tree-sitter-language-pack>=1.21.3",
|
|
55
55
|
]
|
|
56
56
|
|
|
57
57
|
[project.urls]
|
|
@@ -64,7 +64,7 @@ include = ["victor_codegraph*"]
|
|
|
64
64
|
|
|
65
65
|
[tool.ruff]
|
|
66
66
|
line-length = 100
|
|
67
|
-
target-version = "
|
|
67
|
+
target-version = "py312"
|
|
68
68
|
|
|
69
69
|
# Pin the rule set explicitly: the CI workflow installs the latest ruff, whose
|
|
70
70
|
# evolving defaults must not be able to red a path-filtered pipeline overnight.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from victor_codegraph import parse, to_proxima_records
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_batch_embedder_cardinality_mismatch_fails():
|
|
9
|
+
parsed = parse("def a(): pass\ndef b(): pass\n", file_path="m.py")
|
|
10
|
+
with pytest.raises(ValueError, match="one vector per symbol"):
|
|
11
|
+
to_proxima_records(parsed, "repo", batch_embedder=lambda texts: [[0.0] * 3], dim=3)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_embedding_dimension_mismatch_fails():
|
|
15
|
+
parsed = parse("def a(): pass\n", file_path="m.py")
|
|
16
|
+
with pytest.raises(ValueError, match="dimension"):
|
|
17
|
+
to_proxima_records(parsed, "repo", embedder=lambda text: [0.0, 1.0], dim=3)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_non_finite_embedding_fails_before_storage():
|
|
21
|
+
parsed = parse("def a(): pass\n", file_path="m.py")
|
|
22
|
+
with pytest.raises(ValueError, match="finite numeric"):
|
|
23
|
+
to_proxima_records(parsed, "repo", embedder=lambda text: [0.0, float("nan")], dim=2)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_repeated_calls_are_one_edge_with_all_call_sites():
|
|
27
|
+
parsed = parse("def a():\n b()\n b()\n\ndef b(): pass\n", file_path="m.py")
|
|
28
|
+
records = to_proxima_records(parsed, "repo")
|
|
29
|
+
calls = [
|
|
30
|
+
r for r in records if "graph_edge" in r["labels"] and r["edge"]["edge_type"] == "CALLS"
|
|
31
|
+
]
|
|
32
|
+
assert len(calls) == 1
|
|
33
|
+
assert [site["line"] for site in calls[0]["props"]["call_sites"]] == [2, 3]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_legacy_oid_mode_keeps_edge_endpoints_attached():
|
|
37
|
+
parsed = parse("def a(): return b()\n\ndef b(): return 1\n", file_path="m.py")
|
|
38
|
+
records = to_proxima_records(parsed, "repo", stable_oid=False)
|
|
39
|
+
nodes = {r["oid"] for r in records if "graph_node" in r["labels"]}
|
|
40
|
+
edge = next(r for r in records if "graph_edge" in r["labels"])
|
|
41
|
+
assert edge["edge"]["from_oid"] in nodes
|
|
42
|
+
assert edge["edge"]["to_oid"] in nodes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from victor_codegraph import ChunkConfig, chunk
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_fallback_caps_a_single_oversized_line():
|
|
9
|
+
config = ChunkConfig(max_chunk_tokens=10, chunk_overlap_tokens=2, chars_per_token=1)
|
|
10
|
+
chunks = chunk("x" * 100, language="text", file_path="notes.txt", config=config)
|
|
11
|
+
assert len(chunks) > 1
|
|
12
|
+
assert all(len(c.text) <= config.max_chunk_chars for c in chunks)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_python_chunks_report_exact_nonzero_byte_spans():
|
|
16
|
+
source = "prefix = 1\n\ndef f():\n return 1\n"
|
|
17
|
+
function = next(
|
|
18
|
+
c for c in chunk(source, file_path="m.py") if c.metadata.get("simple_name") == "f"
|
|
19
|
+
)
|
|
20
|
+
assert function.start_pos == source.index("def f")
|
|
21
|
+
assert function.end_pos == function.start_pos + len(function.text.encode())
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_chunking_preserves_module_level_semantic_content_without_container_duplication():
|
|
25
|
+
source = """import os
|
|
26
|
+
GLOBAL = configure()
|
|
27
|
+
|
|
28
|
+
class Service:
|
|
29
|
+
def run(self):
|
|
30
|
+
return work()
|
|
31
|
+
|
|
32
|
+
main()
|
|
33
|
+
"""
|
|
34
|
+
chunks = chunk(source, file_path="m.py", config=ChunkConfig(max_chunk_tokens=1000))
|
|
35
|
+
text = "\n".join(c.text for c in chunks)
|
|
36
|
+
assert "import os" in text
|
|
37
|
+
assert "GLOBAL = configure()" in text
|
|
38
|
+
assert "main()" in text
|
|
39
|
+
assert sum(c.text.count("return work()") for c in chunks) == 1
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_include_tests_and_language_filters_are_effective():
|
|
43
|
+
assert (
|
|
44
|
+
chunk(
|
|
45
|
+
"def test_it():\n pass\n",
|
|
46
|
+
file_path="test_demo.py",
|
|
47
|
+
config=ChunkConfig(include_tests=False),
|
|
48
|
+
)
|
|
49
|
+
== []
|
|
50
|
+
)
|
|
51
|
+
assert (
|
|
52
|
+
chunk(
|
|
53
|
+
"def f():\n pass\n",
|
|
54
|
+
file_path="m.py",
|
|
55
|
+
config=ChunkConfig(languages=["rust"]),
|
|
56
|
+
)
|
|
57
|
+
== []
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_custom_token_counter_is_a_hard_limit():
|
|
62
|
+
source = "def f():\n" + "\n".join(f" value_{i} = {i}" for i in range(30))
|
|
63
|
+
config = ChunkConfig(
|
|
64
|
+
max_chunk_tokens=8,
|
|
65
|
+
chunk_overlap_tokens=0,
|
|
66
|
+
token_counter=lambda text: len(text.split()),
|
|
67
|
+
)
|
|
68
|
+
chunks = chunk(source, file_path="m.py", config=config)
|
|
69
|
+
assert all(config.estimate_tokens(c.text) <= config.max_chunk_tokens for c in chunks)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_impossible_token_counter_fails_instead_of_emitting_over_budget_chunk():
|
|
73
|
+
config = ChunkConfig(max_chunk_tokens=1, token_counter=lambda text: 2 if text else 0)
|
|
74
|
+
with pytest.raises(ValueError, match="cannot fit"):
|
|
75
|
+
chunk("x", language="text", file_path="notes.txt", config=config)
|
|
@@ -0,0 +1,26 @@
|
|
|
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 == []
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from victor_codegraph import parse, parse_path, to_proxima_records
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _by_name(parsed, name):
|
|
9
|
+
return next(s for s in parsed.symbols if s.simple_name == name)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_parser_identity_survives_line_shift_and_keeps_legacy_alias():
|
|
13
|
+
base = parse("def f(x):\n return x\n", file_path="pkg/m.py")
|
|
14
|
+
moved = parse("\n\n\ndef f(x):\n return x\n", file_path="pkg/m.py")
|
|
15
|
+
a = _by_name(base, "f")
|
|
16
|
+
b = _by_name(moved, "f")
|
|
17
|
+
|
|
18
|
+
assert a.id == b.id
|
|
19
|
+
assert a.identity_version == b.identity_version == "v2"
|
|
20
|
+
assert a.legacy_id != b.legacy_id
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_parse_path_identity_is_portable_across_checkout_roots(tmp_path):
|
|
24
|
+
roots = [tmp_path / "one", tmp_path / "two"]
|
|
25
|
+
parsed = []
|
|
26
|
+
for root in roots:
|
|
27
|
+
path = root / "pkg" / "m.py"
|
|
28
|
+
path.parent.mkdir(parents=True)
|
|
29
|
+
path.write_text("def f():\n return 1\n")
|
|
30
|
+
parsed.append(parse_path(path, repo_root=root))
|
|
31
|
+
|
|
32
|
+
assert parsed[0] is not None and parsed[1] is not None
|
|
33
|
+
assert parsed[0].file_path == parsed[1].file_path == "pkg/m.py"
|
|
34
|
+
assert parsed[0].symbols[0].id == parsed[1].symbols[0].id
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_path_segments_cannot_alias_dotted_file_names():
|
|
38
|
+
nested = parse("def f(): pass\n", file_path="a/b.py").symbols[0]
|
|
39
|
+
dotted = parse("def f(): pass\n", file_path="a.b.py").symbols[0]
|
|
40
|
+
assert nested.fully_qualified_name == dotted.fully_qualified_name
|
|
41
|
+
assert nested.id != dotted.id
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_cpp_overloads_have_distinct_stable_ids():
|
|
45
|
+
parsed = parse(
|
|
46
|
+
"int f(int x) { return x; }\ndouble f(double x) { return x; }\n",
|
|
47
|
+
language="cpp",
|
|
48
|
+
file_path="f.cpp",
|
|
49
|
+
)
|
|
50
|
+
overloads = [s for s in parsed.symbols if s.simple_name == "f"]
|
|
51
|
+
assert len(overloads) == 2
|
|
52
|
+
assert len({s.id for s in overloads}) == 2
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_anonymous_function_expressions_do_not_collide():
|
|
56
|
+
parsed = parse(
|
|
57
|
+
"const a = function() { return 1 }; const b = function() { return 2 };",
|
|
58
|
+
language="javascript",
|
|
59
|
+
file_path="f.js",
|
|
60
|
+
)
|
|
61
|
+
assert all(s.simple_name != "<anonymous>" for s in parsed.symbols)
|
|
62
|
+
assert len({s.id for s in parsed.symbols}) == len(parsed.symbols)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_python_decorator_change_updates_content_version_and_snippet():
|
|
66
|
+
a = parse("@first\ndef f():\n return 1\n", file_path="m.py")
|
|
67
|
+
b = parse("@second\ndef f():\n return 1\n", file_path="m.py")
|
|
68
|
+
sa, sb = _by_name(a, "f"), _by_name(b, "f")
|
|
69
|
+
ra = to_proxima_records(a, "repo")[0]
|
|
70
|
+
rb = to_proxima_records(b, "repo")[0]
|
|
71
|
+
|
|
72
|
+
assert sa.id == sb.id
|
|
73
|
+
assert sa.source_code.startswith("@first")
|
|
74
|
+
assert sb.source_code.startswith("@second")
|
|
75
|
+
assert ra["props"]["content_version"] != rb["props"]["content_version"]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_rust_inline_modules_preserve_scope_and_identity():
|
|
79
|
+
source = "mod a { pub fn same() {} } mod b { pub fn same() {} }"
|
|
80
|
+
parsed = parse(source, language="rust", file_path="src/lib.rs")
|
|
81
|
+
moved = parse("\n" + source, language="rust", file_path="src/lib.rs")
|
|
82
|
+
functions = [s for s in parsed.symbols if s.simple_name == "same"]
|
|
83
|
+
assert {tuple(s.scope_chain) for s in functions} == {("a",), ("b",)}
|
|
84
|
+
assert len({s.id for s in functions}) == 2
|
|
85
|
+
assert not parsed.diagnostics
|
|
86
|
+
assert {s.id for s in parsed.symbols} == {s.id for s in moved.symbols}
|
|
87
|
+
modules = {s.id for s in parsed.symbols if s.symbol_type.name == "MODULE"}
|
|
88
|
+
assert len(modules) == 2
|
|
89
|
+
assert sum(r.from_symbol_id in modules for r in parsed.relations) == 2
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.parametrize(
|
|
93
|
+
"source,name",
|
|
94
|
+
[
|
|
95
|
+
("struct T; impl A for T { fn fmt() {} } impl B for T { fn fmt() {} }", "fmt"),
|
|
96
|
+
("#[cfg(unix)] fn main() {} #[cfg(not(unix))] fn main() {}", "main"),
|
|
97
|
+
],
|
|
98
|
+
)
|
|
99
|
+
def test_rust_trait_and_cfg_variants_have_distinct_stable_identity(source, name):
|
|
100
|
+
parsed = parse(source, language="rust", file_path="src/lib.rs")
|
|
101
|
+
moved = parse("\n\n" + source, language="rust", file_path="src/lib.rs")
|
|
102
|
+
variants = [s for s in parsed.symbols if s.simple_name == name]
|
|
103
|
+
assert len(variants) == len({s.id for s in variants}) == 2
|
|
104
|
+
assert not parsed.diagnostics
|
|
105
|
+
assert {s.id for s in parsed.symbols} == {s.id for s in moved.symbols}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.mark.parametrize(
|
|
109
|
+
"source",
|
|
110
|
+
[
|
|
111
|
+
"function configure() { use({element(x) { return x; }}); use({element(x) { return x; }}); }",
|
|
112
|
+
"function configure(){ xs.map(() => ({element(x){return x;}})); ys.map(() => ({element(x){return x;}})); }",
|
|
113
|
+
],
|
|
114
|
+
)
|
|
115
|
+
def test_object_literal_methods_have_structural_scopes(source):
|
|
116
|
+
parsed = parse(source, file_path="a.js")
|
|
117
|
+
moved = parse("// header\n\n" + source, file_path="a.js")
|
|
118
|
+
methods = [s for s in parsed.symbols if s.simple_name == "element"]
|
|
119
|
+
assert len(methods) == len({s.id for s in methods}) == 2
|
|
120
|
+
assert not parsed.diagnostics
|
|
121
|
+
assert {s.id for s in parsed.symbols} == {s.id for s in moved.symbols}
|
|
@@ -85,10 +85,11 @@ def test_unresolved_callee_is_retained():
|
|
|
85
85
|
assert r.call_site.start_line == 2 # the verify() call line (1-based)
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
def
|
|
89
|
-
"""
|
|
88
|
+
def test_recursive_self_call_is_retained():
|
|
89
|
+
"""Recursion is graph evidence and remains a self-edge."""
|
|
90
90
|
parsed = parse("def f():\n return f()\n", file_path="m.py")
|
|
91
|
-
assert
|
|
91
|
+
assert len(parsed.relations) == 1
|
|
92
|
+
assert parsed.relations[0].from_symbol_id == parsed.relations[0].to_symbol_id
|
|
92
93
|
|
|
93
94
|
|
|
94
95
|
def test_extends_relation():
|
|
@@ -106,3 +107,18 @@ def test_deterministic_ids():
|
|
|
106
107
|
def test_syntax_error_falls_back_to_no_symbols():
|
|
107
108
|
parsed = parse("def broken(:\n", file_path="bad.py")
|
|
108
109
|
assert parsed.symbols == []
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_call_site_preserves_multiline_utf8_byte_span():
|
|
113
|
+
from victor_codegraph import parse
|
|
114
|
+
|
|
115
|
+
source = 'def target(x): return x\ndef caller():\n label = "é"; return target(\n "你好"\n )\n'
|
|
116
|
+
parsed = parse(source, file_path="a.py")
|
|
117
|
+
call = next(r for r in parsed.relations if r.relation_type.name == "CALLS")
|
|
118
|
+
raw = source.encode("utf-8")
|
|
119
|
+
expected = 'target(\n "你好"\n )'.encode("utf-8")
|
|
120
|
+
span = call.call_site
|
|
121
|
+
assert span.byte_offset == raw.rindex(expected)
|
|
122
|
+
assert span.byte_length == len(expected)
|
|
123
|
+
assert raw[span.byte_offset : span.byte_offset + span.byte_length] == expected
|
|
124
|
+
assert (span.start_line, span.end_line, span.end_column) == (3, 5, 5)
|