pviz-parser 0.1.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.
- pviz_parser-0.1.0/LICENSE +21 -0
- pviz_parser-0.1.0/PKG-INFO +123 -0
- pviz_parser-0.1.0/README.md +94 -0
- pviz_parser-0.1.0/adapters/__init__.py +0 -0
- pviz_parser-0.1.0/adapters/analyzer_bridge/__init__.py +26 -0
- pviz_parser-0.1.0/adapters/analyzer_bridge/bridge_payloads.py +259 -0
- pviz_parser-0.1.0/adapters/analyzer_bridge/core.py +668 -0
- pviz_parser-0.1.0/adapters/analyzer_bridge/internals.py +260 -0
- pviz_parser-0.1.0/adapters/analyzer_bridge/provenance.py +135 -0
- pviz_parser-0.1.0/adapters/canonical.py +340 -0
- pviz_parser-0.1.0/adapters/payloads.py +253 -0
- pviz_parser-0.1.0/analyzer/__init__.py +172 -0
- pviz_parser-0.1.0/analyzer/analyzer_types.py +216 -0
- pviz_parser-0.1.0/analyzer/ast_common.py +1098 -0
- pviz_parser-0.1.0/analyzer/build_classic.py +373 -0
- pviz_parser-0.1.0/analyzer/config.py +288 -0
- pviz_parser-0.1.0/analyzer/dead_code.py +635 -0
- pviz_parser-0.1.0/analyzer/discovery_manifest.py +936 -0
- pviz_parser-0.1.0/analyzer/duplicate_code.py +400 -0
- pviz_parser-0.1.0/analyzer/extract_crosstalk.py +414 -0
- pviz_parser-0.1.0/analyzer/followup_python.py +0 -0
- pviz_parser-0.1.0/analyzer/fs.py +259 -0
- pviz_parser-0.1.0/analyzer/go/__init__.py +26 -0
- pviz_parser-0.1.0/analyzer/go/go_build_artifacts.py +614 -0
- pviz_parser-0.1.0/analyzer/go/go_canonical.py +315 -0
- pviz_parser-0.1.0/analyzer/go/go_config.py +155 -0
- pviz_parser-0.1.0/analyzer/go/go_folder_index.py +976 -0
- pviz_parser-0.1.0/analyzer/go/go_nodefacts_symbols.py +172 -0
- pviz_parser-0.1.0/analyzer/go/go_parse.py +319 -0
- pviz_parser-0.1.0/analyzer/go/go_parse_dispatch.py +284 -0
- pviz_parser-0.1.0/analyzer/go/go_run.py +535 -0
- pviz_parser-0.1.0/analyzer/imports_lex.py +167 -0
- pviz_parser-0.1.0/analyzer/index.py +428 -0
- pviz_parser-0.1.0/analyzer/java/__init__.py +0 -0
- pviz_parser-0.1.0/analyzer/java/java_build_artifacts.py +1164 -0
- pviz_parser-0.1.0/analyzer/java/java_canonical.py +329 -0
- pviz_parser-0.1.0/analyzer/java/java_config.py +75 -0
- pviz_parser-0.1.0/analyzer/java/java_folder_index.py +1142 -0
- pviz_parser-0.1.0/analyzer/java/java_nodefacts_symbols.py +445 -0
- pviz_parser-0.1.0/analyzer/java/java_parse/__init__.py +19 -0
- pviz_parser-0.1.0/analyzer/java/java_parse/engine.py +48 -0
- pviz_parser-0.1.0/analyzer/java/java_parse/javaparser_engine.py +336 -0
- pviz_parser-0.1.0/analyzer/java/java_parse/models.py +58 -0
- pviz_parser-0.1.0/analyzer/java/java_parse/regex_engine.py +372 -0
- pviz_parser-0.1.0/analyzer/java/java_parse_dispatch.py +238 -0
- pviz_parser-0.1.0/analyzer/java/java_run.py +488 -0
- pviz_parser-0.1.0/analyzer/kotlin/__init__.py +1 -0
- pviz_parser-0.1.0/analyzer/kotlin/kotlin_build_artifacts.py +508 -0
- pviz_parser-0.1.0/analyzer/kotlin/kotlin_canonical.py +147 -0
- pviz_parser-0.1.0/analyzer/kotlin/kotlin_config.py +37 -0
- pviz_parser-0.1.0/analyzer/kotlin/kotlin_folder_index.py +1144 -0
- pviz_parser-0.1.0/analyzer/kotlin/kotlin_nodefacts_symbols.py +123 -0
- pviz_parser-0.1.0/analyzer/kotlin/kotlin_parse_dispatch.py +20 -0
- pviz_parser-0.1.0/analyzer/kotlin/kotlin_run.py +299 -0
- pviz_parser-0.1.0/analyzer/kotlin/parse_kotlin/__init__.py +15 -0
- pviz_parser-0.1.0/analyzer/kotlin/parse_kotlin/engine.py +104 -0
- pviz_parser-0.1.0/analyzer/kotlin/parse_kotlin/models.py +143 -0
- pviz_parser-0.1.0/analyzer/l2_method_usage.py +196 -0
- pviz_parser-0.1.0/analyzer/module_map.py +170 -0
- pviz_parser-0.1.0/analyzer/module_resolve.py +420 -0
- pviz_parser-0.1.0/analyzer/parse.py +306 -0
- pviz_parser-0.1.0/analyzer/py/__init__.py +0 -0
- pviz_parser-0.1.0/analyzer/py/py_run.py +120 -0
- pviz_parser-0.1.0/analyzer/rust/__init__.py +0 -0
- pviz_parser-0.1.0/analyzer/rust/rust_build_artifacts.py +1347 -0
- pviz_parser-0.1.0/analyzer/rust/rust_canonical.py +392 -0
- pviz_parser-0.1.0/analyzer/rust/rust_config.py +133 -0
- pviz_parser-0.1.0/analyzer/rust/rust_folder_index.py +1234 -0
- pviz_parser-0.1.0/analyzer/rust/rust_nodefacts_symbols.py +483 -0
- pviz_parser-0.1.0/analyzer/rust/rust_parse/__init__.py +44 -0
- pviz_parser-0.1.0/analyzer/rust/rust_parse/engine.py +282 -0
- pviz_parser-0.1.0/analyzer/rust/rust_parse/models.py +438 -0
- pviz_parser-0.1.0/analyzer/rust/rust_parse_dispatch.py +218 -0
- pviz_parser-0.1.0/analyzer/rust/rust_run.py +489 -0
- pviz_parser-0.1.0/analyzer/ts/__init__.py +7 -0
- pviz_parser-0.1.0/analyzer/ts/build_artifacts.py +690 -0
- pviz_parser-0.1.0/analyzer/ts/canonical_web.py +422 -0
- pviz_parser-0.1.0/analyzer/ts/config.py +53 -0
- pviz_parser-0.1.0/analyzer/ts/discover.py +54 -0
- pviz_parser-0.1.0/analyzer/ts/extract_crosstalk.py +344 -0
- pviz_parser-0.1.0/analyzer/ts/extract_imports.py +188 -0
- pviz_parser-0.1.0/analyzer/ts/model.py +24 -0
- pviz_parser-0.1.0/analyzer/ts/parser_runtime.py +91 -0
- pviz_parser-0.1.0/analyzer/ts/resolve_imports.py +266 -0
- pviz_parser-0.1.0/analyzer/ts/run.py +979 -0
- pviz_parser-0.1.0/analyzer/ts/symbols_js.py +319 -0
- pviz_parser-0.1.0/analyzer/ts/tsconfig_paths.py +548 -0
- pviz_parser-0.1.0/analyzer/ts/write_artifacts.py +12 -0
- pviz_parser-0.1.0/analyzer_store/__init__.py +1 -0
- pviz_parser-0.1.0/analyzer_store/coverage.py +73 -0
- pviz_parser-0.1.0/analyzer_store/edge_pass.py +536 -0
- pviz_parser-0.1.0/analyzer_store/folder_index.py +931 -0
- pviz_parser-0.1.0/analyzer_store/followup_assess.py +50 -0
- pviz_parser-0.1.0/analyzer_store/integration.py +727 -0
- pviz_parser-0.1.0/analyzer_store/io_utils.py +128 -0
- pviz_parser-0.1.0/analyzer_store/nodefacts.py +1256 -0
- pviz_parser-0.1.0/analyzer_store/types.py +138 -0
- pviz_parser-0.1.0/analyzer_store/validators.py +72 -0
- pviz_parser-0.1.0/contracts/__init__.py +0 -0
- pviz_parser-0.1.0/contracts/types.py +184 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/__init__.py +5 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/api.py +176 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/config.py +86 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/crosstalk.py +225 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/discovery.py +44 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/edges.py +53 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/folder_index.py +125 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/io.py +52 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/meta.py +112 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/metrics.py +115 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/nodes.py +205 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/normalize.py +33 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/publish.py +156 -0
- pviz_parser-0.1.0/core/artifact_sets_merge/scc.py +245 -0
- pviz_parser-0.1.0/core/artifacts.py +204 -0
- pviz_parser-0.1.0/core/artifacts_headless.py +50 -0
- pviz_parser-0.1.0/core/build_context.py +91 -0
- pviz_parser-0.1.0/core/build_pipeline/JS_P_Crosstalk.py +845 -0
- pviz_parser-0.1.0/core/build_pipeline/P_JS_Crosstalk.py +1023 -0
- pviz_parser-0.1.0/core/build_pipeline/__init__.py +32 -0
- pviz_parser-0.1.0/core/build_pipeline/buckets.py +955 -0
- pviz_parser-0.1.0/core/build_pipeline/followups.py +108 -0
- pviz_parser-0.1.0/core/build_pipeline/json_io.py +18 -0
- pviz_parser-0.1.0/core/build_pipeline/normalize.py +82 -0
- pviz_parser-0.1.0/core/build_pipeline/pipeline.py +556 -0
- pviz_parser-0.1.0/core/build_pipeline/types.py +87 -0
- pviz_parser-0.1.0/core/json_export.py +541 -0
- pviz_parser-0.1.0/core/pkgzones/builder.py +437 -0
- pviz_parser-0.1.0/core/pkgzones/contracts.py +91 -0
- pviz_parser-0.1.0/core/pkgzones/placement.py +114 -0
- pviz_parser-0.1.0/core/pkgzones/state.py +32 -0
- pviz_parser-0.1.0/core/store_root.py +79 -0
- pviz_parser-0.1.0/core/workspace_core.py +150 -0
- pviz_parser-0.1.0/core/zones_ctx.py +313 -0
- pviz_parser-0.1.0/core/zones_headless.py +71 -0
- pviz_parser-0.1.0/diagnostics/__init__.py +0 -0
- pviz_parser-0.1.0/diagnostics/events.py +52 -0
- pviz_parser-0.1.0/diagnostics/logging.py +360 -0
- pviz_parser-0.1.0/i_o/__init__.py +55 -0
- pviz_parser-0.1.0/i_o/diagnostics_export.py +51 -0
- pviz_parser-0.1.0/i_o/export_json.py +361 -0
- pviz_parser-0.1.0/i_o/export_llm_json/__init__.py +4 -0
- pviz_parser-0.1.0/i_o/export_llm_json/artifacts.py +65 -0
- pviz_parser-0.1.0/i_o/export_llm_json/builders_discovery.py +119 -0
- pviz_parser-0.1.0/i_o/export_llm_json/builders_folders.py +356 -0
- pviz_parser-0.1.0/i_o/export_llm_json/builders_node_order.py +15 -0
- pviz_parser-0.1.0/i_o/export_llm_json/builders_nodes_edges.py +251 -0
- pviz_parser-0.1.0/i_o/export_llm_json/builders_zones.py +41 -0
- pviz_parser-0.1.0/i_o/export_llm_json/errors.py +5 -0
- pviz_parser-0.1.0/i_o/export_llm_json/export.py +573 -0
- pviz_parser-0.1.0/i_o/export_llm_json/json_compression/__init__.py +16 -0
- pviz_parser-0.1.0/i_o/export_llm_json/json_compression/edge_codecs.py +157 -0
- pviz_parser-0.1.0/i_o/export_llm_json/json_compression/format_guide.py +215 -0
- pviz_parser-0.1.0/i_o/export_llm_json/json_compression/node_codecs.py +328 -0
- pviz_parser-0.1.0/i_o/export_llm_json/json_compression/nodes.py +165 -0
- pviz_parser-0.1.0/i_o/export_llm_json/json_compression/path_legend.py +230 -0
- pviz_parser-0.1.0/i_o/export_llm_json/json_compression/run.py +136 -0
- pviz_parser-0.1.0/i_o/export_llm_json/json_compression/types.py +130 -0
- pviz_parser-0.1.0/i_o/export_llm_json/json_compression/util.py +60 -0
- pviz_parser-0.1.0/i_o/export_llm_json/summary.py +649 -0
- pviz_parser-0.1.0/i_o/export_llm_json/utils.py +62 -0
- pviz_parser-0.1.0/i_o/export_pdf.py +97 -0
- pviz_parser-0.1.0/i_o/layout_store.py +455 -0
- pviz_parser-0.1.0/i_o/path_utils.py +23 -0
- pviz_parser-0.1.0/i_o/workspace_io.py +295 -0
- pviz_parser-0.1.0/i_o/workspace_manager.py +171 -0
- pviz_parser-0.1.0/pviz_parser.egg-info/PKG-INFO +123 -0
- pviz_parser-0.1.0/pviz_parser.egg-info/SOURCES.txt +172 -0
- pviz_parser-0.1.0/pviz_parser.egg-info/dependency_links.txt +1 -0
- pviz_parser-0.1.0/pviz_parser.egg-info/entry_points.txt +2 -0
- pviz_parser-0.1.0/pviz_parser.egg-info/requires.txt +5 -0
- pviz_parser-0.1.0/pviz_parser.egg-info/top_level.txt +7 -0
- pviz_parser-0.1.0/pyproject.toml +65 -0
- pviz_parser-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael McClellan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pviz-parser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate LLM-optimized dependency analysis bundles from your codebase
|
|
5
|
+
Author-email: Michael McClellan <mikemc@pvizgenerator.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://pvizgenerator.com
|
|
8
|
+
Project-URL: Repository, https://github.com/mikebmac86/pviz-parser
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/mikebmac86/pviz-parser/issues
|
|
10
|
+
Keywords: dependency analysis,codebase analysis,llm context,static analysis,dependency graph
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: tree-sitter-language-pack
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build; extra == "dev"
|
|
27
|
+
Requires-Dist: twine; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# pviz-parser
|
|
31
|
+
|
|
32
|
+
Generate LLM-optimized dependency analysis bundles from your codebase.
|
|
33
|
+
|
|
34
|
+
`pviz-parser` analyzes your project's dependency graph and produces a structured JSON bundle designed to fit inside an LLM context window. Instead of pasting files one by one, you give your LLM a complete picture of how your codebase is wired — nodes, edges, import relationships, cycle detection, and per-file metrics — in a single compressed artifact.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install pviz-parser
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pviz . -o bundle.json --clean
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
That's it. Point `pviz` at your project root, specify an output path, and get a bundle ready to drop into Claude, ChatGPT, or any LLM that accepts file uploads or large context.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
pviz <scan_root> -o <output.json> [options]
|
|
54
|
+
|
|
55
|
+
Arguments:
|
|
56
|
+
scan_root Root directory of the project to analyze
|
|
57
|
+
|
|
58
|
+
Options:
|
|
59
|
+
-o, --output Output path for the bundle JSON (required)
|
|
60
|
+
--clean Nuke the artifact cache before running (recommended when switching repos)
|
|
61
|
+
--store-root PATH Override the sandbox directory (default: per-user .pviz_store)
|
|
62
|
+
--mode classic|zones Build mode (default: zones)
|
|
63
|
+
--max-bytes N Per-file size limit in bytes (default: 100MB)
|
|
64
|
+
--allow-output-in-repo Allow writing the bundle inside scan_root
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Examples
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Analyze current directory
|
|
71
|
+
pviz . -o bundle.json --clean
|
|
72
|
+
|
|
73
|
+
# Analyze a specific project
|
|
74
|
+
pviz ~/projects/myapp -o ~/Desktop/myapp_bundle.json --clean
|
|
75
|
+
|
|
76
|
+
# Use a custom store root to keep artifacts organized
|
|
77
|
+
pviz . -o bundle.json --store-root /tmp/pviz --clean
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## What's in the bundle
|
|
81
|
+
|
|
82
|
+
The output is a structured JSON artifact with:
|
|
83
|
+
|
|
84
|
+
- **Nodes** — one per source file, with LOC, SLOC, import/exporter counts, SCC membership, symbols, and language
|
|
85
|
+
- **Edges** — directed import relationships between files
|
|
86
|
+
- **Dependency metrics** — which files are most imported, which import the most, hotspots
|
|
87
|
+
- **Cycle detection** — strongly connected components (SCCs) flagged at the node level
|
|
88
|
+
- **Discovery manifest** — full file inventory with language breakdown
|
|
89
|
+
- **Folder index** — per-file import surface and resolution data
|
|
90
|
+
- **Summary** — counts, parse status, edge stats, crosstalk candidates
|
|
91
|
+
|
|
92
|
+
A compressed format is also generated alongside the standard bundle (`.compressed.json`), typically 55–65% smaller, optimized for tight context windows.
|
|
93
|
+
|
|
94
|
+
## Language support
|
|
95
|
+
|
|
96
|
+
| Language | CLI (this package) | SaaS ([pvizgenerator.com](https://pvizgenerator.com)) |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| Python | ✅ | ✅ |
|
|
99
|
+
| TypeScript | ✅ | ✅ |
|
|
100
|
+
| JavaScript | ✅ | ✅ |
|
|
101
|
+
| Java | ✅ (partial — pure Python parser) | ✅ (full resolution) |
|
|
102
|
+
| Kotlin | ❌ | ✅ |
|
|
103
|
+
| Go | ❌ | ✅ |
|
|
104
|
+
| Rust | ❌ | ✅ |
|
|
105
|
+
|
|
106
|
+
Kotlin, Go, and Rust analysis requires compiled binary dependencies that are part of the hosted SaaS only. Polyglot repos with multiple supported languages are handled automatically — the bundle merges all detected languages into a single artifact.
|
|
107
|
+
|
|
108
|
+
## CLI vs SaaS
|
|
109
|
+
|
|
110
|
+
`pviz-parser` is the open source CLI. It runs locally, produces bundles you own, and supports Python, TypeScript, JavaScript, and Java out of the box.
|
|
111
|
+
|
|
112
|
+
[pvizgenerator.com](https://pvizgenerator.com) is the hosted SaaS layer. It adds full Kotlin, Go, and Rust support, hosted bundle storage, bundle diffing across commits, and MCP delivery for direct LLM tool integration — without running anything locally.
|
|
113
|
+
|
|
114
|
+
## Requirements
|
|
115
|
+
|
|
116
|
+
- Python 3.10+
|
|
117
|
+
- No other system dependencies for Python/TS/JS/Java analysis
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT — see [LICENSE](LICENSE)
|
|
122
|
+
|
|
123
|
+
Built by [Michael McClellan](https://pvizgenerator.com)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# pviz-parser
|
|
2
|
+
|
|
3
|
+
Generate LLM-optimized dependency analysis bundles from your codebase.
|
|
4
|
+
|
|
5
|
+
`pviz-parser` analyzes your project's dependency graph and produces a structured JSON bundle designed to fit inside an LLM context window. Instead of pasting files one by one, you give your LLM a complete picture of how your codebase is wired — nodes, edges, import relationships, cycle detection, and per-file metrics — in a single compressed artifact.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install pviz-parser
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pviz . -o bundle.json --clean
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
That's it. Point `pviz` at your project root, specify an output path, and get a bundle ready to drop into Claude, ChatGPT, or any LLM that accepts file uploads or large context.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
pviz <scan_root> -o <output.json> [options]
|
|
25
|
+
|
|
26
|
+
Arguments:
|
|
27
|
+
scan_root Root directory of the project to analyze
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
-o, --output Output path for the bundle JSON (required)
|
|
31
|
+
--clean Nuke the artifact cache before running (recommended when switching repos)
|
|
32
|
+
--store-root PATH Override the sandbox directory (default: per-user .pviz_store)
|
|
33
|
+
--mode classic|zones Build mode (default: zones)
|
|
34
|
+
--max-bytes N Per-file size limit in bytes (default: 100MB)
|
|
35
|
+
--allow-output-in-repo Allow writing the bundle inside scan_root
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Examples
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Analyze current directory
|
|
42
|
+
pviz . -o bundle.json --clean
|
|
43
|
+
|
|
44
|
+
# Analyze a specific project
|
|
45
|
+
pviz ~/projects/myapp -o ~/Desktop/myapp_bundle.json --clean
|
|
46
|
+
|
|
47
|
+
# Use a custom store root to keep artifacts organized
|
|
48
|
+
pviz . -o bundle.json --store-root /tmp/pviz --clean
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## What's in the bundle
|
|
52
|
+
|
|
53
|
+
The output is a structured JSON artifact with:
|
|
54
|
+
|
|
55
|
+
- **Nodes** — one per source file, with LOC, SLOC, import/exporter counts, SCC membership, symbols, and language
|
|
56
|
+
- **Edges** — directed import relationships between files
|
|
57
|
+
- **Dependency metrics** — which files are most imported, which import the most, hotspots
|
|
58
|
+
- **Cycle detection** — strongly connected components (SCCs) flagged at the node level
|
|
59
|
+
- **Discovery manifest** — full file inventory with language breakdown
|
|
60
|
+
- **Folder index** — per-file import surface and resolution data
|
|
61
|
+
- **Summary** — counts, parse status, edge stats, crosstalk candidates
|
|
62
|
+
|
|
63
|
+
A compressed format is also generated alongside the standard bundle (`.compressed.json`), typically 55–65% smaller, optimized for tight context windows.
|
|
64
|
+
|
|
65
|
+
## Language support
|
|
66
|
+
|
|
67
|
+
| Language | CLI (this package) | SaaS ([pvizgenerator.com](https://pvizgenerator.com)) |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Python | ✅ | ✅ |
|
|
70
|
+
| TypeScript | ✅ | ✅ |
|
|
71
|
+
| JavaScript | ✅ | ✅ |
|
|
72
|
+
| Java | ✅ (partial — pure Python parser) | ✅ (full resolution) |
|
|
73
|
+
| Kotlin | ❌ | ✅ |
|
|
74
|
+
| Go | ❌ | ✅ |
|
|
75
|
+
| Rust | ❌ | ✅ |
|
|
76
|
+
|
|
77
|
+
Kotlin, Go, and Rust analysis requires compiled binary dependencies that are part of the hosted SaaS only. Polyglot repos with multiple supported languages are handled automatically — the bundle merges all detected languages into a single artifact.
|
|
78
|
+
|
|
79
|
+
## CLI vs SaaS
|
|
80
|
+
|
|
81
|
+
`pviz-parser` is the open source CLI. It runs locally, produces bundles you own, and supports Python, TypeScript, JavaScript, and Java out of the box.
|
|
82
|
+
|
|
83
|
+
[pvizgenerator.com](https://pvizgenerator.com) is the hosted SaaS layer. It adds full Kotlin, Go, and Rust support, hosted bundle storage, bundle diffing across commits, and MCP delivery for direct LLM tool integration — without running anything locally.
|
|
84
|
+
|
|
85
|
+
## Requirements
|
|
86
|
+
|
|
87
|
+
- Python 3.10+
|
|
88
|
+
- No other system dependencies for Python/TS/JS/Java analysis
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT — see [LICENSE](LICENSE)
|
|
93
|
+
|
|
94
|
+
Built by [Michael McClellan](https://pvizgenerator.com)
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# adapters/analyzer_bridge/__init__.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .core import normalize_graph_for_contracts as normalize_graph_for_contracts
|
|
8
|
+
__all__ = [
|
|
9
|
+
"normalize_graph_for_contracts",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
def enrich_graph(graph: Any, *, repo_root: Optional[Path | str] = None) -> Dict[str, Any]:
|
|
13
|
+
g = graph if isinstance(graph, dict) else {"nodes": {}, "edges": []}
|
|
14
|
+
nodes = g.get("nodes") or {}
|
|
15
|
+
for _id, payload in nodes.items():
|
|
16
|
+
if not isinstance(payload, dict):
|
|
17
|
+
continue
|
|
18
|
+
defs_rows = payload.get("defs_rows") or []
|
|
19
|
+
extra = payload.setdefault("extra", {})
|
|
20
|
+
if "defs_rows" not in extra:
|
|
21
|
+
extra["defs_rows"] = list(defs_rows)
|
|
22
|
+
return g
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Backward-compatible alias
|
|
26
|
+
enrich_for_ui = enrich_graph
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .internals import build_import_row_annotations
|
|
7
|
+
from adapters.canonical import (
|
|
8
|
+
repo_rel,
|
|
9
|
+
to_posix,
|
|
10
|
+
canon_file_id,
|
|
11
|
+
canon_module_id,
|
|
12
|
+
)
|
|
13
|
+
from adapters.analyzer_bridge.internals import (
|
|
14
|
+
as_list,
|
|
15
|
+
attr_or_key,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _dedupe_preserve_order(seq: Sequence[str]) -> List[str]:
|
|
20
|
+
seen: set[str] = set()
|
|
21
|
+
out: List[str] = []
|
|
22
|
+
for s in seq:
|
|
23
|
+
if s not in seen:
|
|
24
|
+
seen.add(s)
|
|
25
|
+
out.append(s)
|
|
26
|
+
return out
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _to_name(v) -> Optional[str]:
|
|
30
|
+
if v is None:
|
|
31
|
+
return None
|
|
32
|
+
if isinstance(v, str):
|
|
33
|
+
s = v.strip()
|
|
34
|
+
if s.startswith(("def ", "class ")):
|
|
35
|
+
s = s.split(" ", 1)[1].strip() or s
|
|
36
|
+
return s or None
|
|
37
|
+
if isinstance(v, dict):
|
|
38
|
+
name = v.get("name") or v.get("label") or v.get("id")
|
|
39
|
+
return str(name) if name else None
|
|
40
|
+
for attr in ("name", "label", "id"):
|
|
41
|
+
try:
|
|
42
|
+
name = getattr(v, attr, None)
|
|
43
|
+
if isinstance(name, str) and name:
|
|
44
|
+
return name
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
try:
|
|
48
|
+
s = str(v)
|
|
49
|
+
if s and s != "(none)":
|
|
50
|
+
return s
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _clean_and_normalize_import_rows(rows_like) -> List[str]:
|
|
57
|
+
out: List[str] = []
|
|
58
|
+
for r in as_list(rows_like):
|
|
59
|
+
s = r if isinstance(r, str) else _to_name(r) or ""
|
|
60
|
+
s = (s or "").strip()
|
|
61
|
+
if not s:
|
|
62
|
+
continue
|
|
63
|
+
# strip trailing comments
|
|
64
|
+
i = s.find("#")
|
|
65
|
+
if i == 0:
|
|
66
|
+
continue
|
|
67
|
+
if i > 0:
|
|
68
|
+
s = s[:i].rstrip()
|
|
69
|
+
if not s:
|
|
70
|
+
continue
|
|
71
|
+
if s.startswith("from ") or s.startswith("import "):
|
|
72
|
+
out.append(s)
|
|
73
|
+
else:
|
|
74
|
+
out.append(f"import {s}")
|
|
75
|
+
return _dedupe_preserve_order(out)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def node_payload_from(node_id: str, n: Any, *, repo_root: Optional[Path | str]) -> Dict[str, Any]:
|
|
79
|
+
"""
|
|
80
|
+
Authoritative payload builder used by analyzer.
|
|
81
|
+
|
|
82
|
+
Assumptions under the current contracts:
|
|
83
|
+
- `node_id` is a stable node identifier; for analyzer-backed graphs this
|
|
84
|
+
is a **module id** relative to the scan root (e.g. 'downloader',
|
|
85
|
+
'scrapy.core.engine', 'ui.app.run_gui').
|
|
86
|
+
- The node record `n` may carry:
|
|
87
|
+
• path/file: repo-relative or absolute path
|
|
88
|
+
• module: dotted module id
|
|
89
|
+
"""
|
|
90
|
+
root = to_posix(str(repo_root)) if repo_root else None
|
|
91
|
+
|
|
92
|
+
# ---------- Canonical file-id (repo-rel POSIX) ----------
|
|
93
|
+
# Prefer an explicit path/file on the node; fall back to id/module when needed.
|
|
94
|
+
raw_path_like = (
|
|
95
|
+
attr_or_key(n, "path")
|
|
96
|
+
or attr_or_key(n, "file")
|
|
97
|
+
or (str(node_id) if isinstance(node_id, str) else "")
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
canon_path = ""
|
|
101
|
+
try:
|
|
102
|
+
# canon_file_id knows how to handle both path-ish and dotted inputs;
|
|
103
|
+
# we pass scan_root so it will keep things repo-relative when possible.
|
|
104
|
+
canon_path = canon_file_id(raw_path_like, root, strict_suffix=False) or ""
|
|
105
|
+
except Exception:
|
|
106
|
+
canon_path = ""
|
|
107
|
+
|
|
108
|
+
if not canon_path:
|
|
109
|
+
# Retry from explicit module field if present
|
|
110
|
+
raw_mod = attr_or_key(n, "module") or ""
|
|
111
|
+
try:
|
|
112
|
+
if raw_mod:
|
|
113
|
+
canon_path = canon_file_id(raw_mod, root, strict_suffix=False) or ""
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
canon_path = to_posix(canon_path) if canon_path else ""
|
|
118
|
+
|
|
119
|
+
# ---------- Canonical module (dotted) ----------
|
|
120
|
+
# Prefer node_id itself when it behaves like a module id (no slashes, no .py).
|
|
121
|
+
module = ""
|
|
122
|
+
if isinstance(node_id, str):
|
|
123
|
+
nid = node_id.strip()
|
|
124
|
+
if nid and "/" not in nid and "\\" not in nid and not nid.endswith(".py"):
|
|
125
|
+
module = nid
|
|
126
|
+
|
|
127
|
+
if not module and canon_path:
|
|
128
|
+
try:
|
|
129
|
+
module = canon_module_id(canon_path, root) or ""
|
|
130
|
+
except Exception:
|
|
131
|
+
module = ""
|
|
132
|
+
|
|
133
|
+
if not module:
|
|
134
|
+
raw_mod = attr_or_key(n, "module") or ""
|
|
135
|
+
try:
|
|
136
|
+
if raw_mod:
|
|
137
|
+
module = canon_module_id(raw_mod, root) or ""
|
|
138
|
+
except Exception:
|
|
139
|
+
# last resort: try making a module from the raw path-ish
|
|
140
|
+
try:
|
|
141
|
+
module = canon_module_id(raw_path_like, root) if raw_path_like else ""
|
|
142
|
+
except Exception:
|
|
143
|
+
module = ""
|
|
144
|
+
|
|
145
|
+
# ---------- Header path for UI (repo-rel POSIX) ----------
|
|
146
|
+
header_path = canon_path or repo_rel(
|
|
147
|
+
attr_or_key(n, "path") or attr_or_key(n, "file"),
|
|
148
|
+
repo_root,
|
|
149
|
+
)
|
|
150
|
+
if isinstance(header_path, str):
|
|
151
|
+
header_path = to_posix(header_path)
|
|
152
|
+
|
|
153
|
+
# ---------- Label: prefer module leaf; fall back to basename ----------
|
|
154
|
+
if module:
|
|
155
|
+
label = module.split(".")[-1]
|
|
156
|
+
else:
|
|
157
|
+
leaf = to_posix(str(header_path or node_id))
|
|
158
|
+
label = os.path.basename(leaf.rstrip("/")) if leaf else str(node_id)
|
|
159
|
+
|
|
160
|
+
# ---------- Imports ----------
|
|
161
|
+
pre_rows = attr_or_key(n, "imports_rows") or (
|
|
162
|
+
(attr_or_key(n, "extra") or {}).get("imports_rows")
|
|
163
|
+
if isinstance(n, dict)
|
|
164
|
+
else None
|
|
165
|
+
)
|
|
166
|
+
raw_imports = attr_or_key(n, "imports")
|
|
167
|
+
imp_rows = (
|
|
168
|
+
_clean_and_normalize_import_rows(pre_rows)
|
|
169
|
+
if pre_rows
|
|
170
|
+
else _clean_and_normalize_import_rows(raw_imports)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Fallback: mine file only if still empty
|
|
174
|
+
if not imp_rows:
|
|
175
|
+
try:
|
|
176
|
+
abs_hint = attr_or_key(n, "file") or attr_or_key(n, "path") or ""
|
|
177
|
+
p: Optional[str] = None
|
|
178
|
+
if isinstance(abs_hint, str) and os.path.isabs(abs_hint):
|
|
179
|
+
p = abs_hint
|
|
180
|
+
elif isinstance(canon_path, str) and canon_path:
|
|
181
|
+
p = str(Path(root) / canon_path) if root else None
|
|
182
|
+
if p and p.endswith(".py") and os.path.exists(p):
|
|
183
|
+
with open(p, "r", encoding="utf-8", errors="ignore") as f:
|
|
184
|
+
text = f.read(64_000)
|
|
185
|
+
lines = (ln.strip() for ln in text.splitlines())
|
|
186
|
+
imp_rows = [
|
|
187
|
+
s
|
|
188
|
+
for s in lines
|
|
189
|
+
if s
|
|
190
|
+
and not s.startswith("#")
|
|
191
|
+
and (s.startswith("import ") or (s.startswith("from ") and " import " in s))
|
|
192
|
+
]
|
|
193
|
+
imp_rows = _dedupe_preserve_order(imp_rows)
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
# ---------- Defs / exports ----------
|
|
198
|
+
pre_defs = attr_or_key(n, "defs_rows") or (
|
|
199
|
+
(attr_or_key(n, "extra") or {}).get("defs_rows")
|
|
200
|
+
if isinstance(n, dict)
|
|
201
|
+
else None
|
|
202
|
+
)
|
|
203
|
+
classes = _dedupe_preserve_order(as_list(attr_or_key(n, "classes")))
|
|
204
|
+
functions = _dedupe_preserve_order(as_list(attr_or_key(n, "functions")))
|
|
205
|
+
globals_ = _dedupe_preserve_order(as_list(attr_or_key(n, "globals")))
|
|
206
|
+
raw_exports = attr_or_key(n, "exports")
|
|
207
|
+
|
|
208
|
+
if pre_defs:
|
|
209
|
+
def_rows = [
|
|
210
|
+
s
|
|
211
|
+
for s in _dedupe_preserve_order(as_list(pre_defs))
|
|
212
|
+
if s and s != "(none)"
|
|
213
|
+
]
|
|
214
|
+
else:
|
|
215
|
+
seen: set[str] = set()
|
|
216
|
+
acc: List[str] = []
|
|
217
|
+
for s in [*classes, *functions, *globals_]:
|
|
218
|
+
if s not in seen:
|
|
219
|
+
seen.add(s)
|
|
220
|
+
acc.append(s)
|
|
221
|
+
def_rows = acc or ["(none)"]
|
|
222
|
+
|
|
223
|
+
parsed_for_tags = attr_or_key(n, "parsed")
|
|
224
|
+
import_rows_annotated, import_tag_counts = build_import_row_annotations(
|
|
225
|
+
imp_rows,
|
|
226
|
+
parsed_for_tags,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
extra: Dict[str, Any] = {
|
|
230
|
+
"imports": imp_rows,
|
|
231
|
+
"imports_rows": imp_rows,
|
|
232
|
+
"imports_display": [a.get("row") for a in import_rows_annotated]
|
|
233
|
+
if import_rows_annotated
|
|
234
|
+
else imp_rows,
|
|
235
|
+
"defs_rows": def_rows,
|
|
236
|
+
"import_rows_annotated": import_rows_annotated,
|
|
237
|
+
"import_tag_counts": import_tag_counts,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# ---------- Final payload ----------
|
|
241
|
+
payload: Dict[str, Any] = {
|
|
242
|
+
"node_id": node_id,
|
|
243
|
+
"label": label,
|
|
244
|
+
"kind": "file",
|
|
245
|
+
"path": header_path or "",
|
|
246
|
+
"module": module or None,
|
|
247
|
+
"package": None,
|
|
248
|
+
"file": header_path or "",
|
|
249
|
+
"extra": extra,
|
|
250
|
+
"imports": imp_rows,
|
|
251
|
+
"imports_rows": imp_rows,
|
|
252
|
+
"imports_display": extra["imports_display"],
|
|
253
|
+
"defs_rows": def_rows,
|
|
254
|
+
"classes": classes,
|
|
255
|
+
"functions": functions,
|
|
256
|
+
"globals": globals_,
|
|
257
|
+
"exports": [e for e in as_list(raw_exports) if isinstance(e, str)],
|
|
258
|
+
}
|
|
259
|
+
return {k: v for k, v in payload.items() if v is not None}
|