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.
Files changed (174) hide show
  1. pviz_parser-0.1.0/LICENSE +21 -0
  2. pviz_parser-0.1.0/PKG-INFO +123 -0
  3. pviz_parser-0.1.0/README.md +94 -0
  4. pviz_parser-0.1.0/adapters/__init__.py +0 -0
  5. pviz_parser-0.1.0/adapters/analyzer_bridge/__init__.py +26 -0
  6. pviz_parser-0.1.0/adapters/analyzer_bridge/bridge_payloads.py +259 -0
  7. pviz_parser-0.1.0/adapters/analyzer_bridge/core.py +668 -0
  8. pviz_parser-0.1.0/adapters/analyzer_bridge/internals.py +260 -0
  9. pviz_parser-0.1.0/adapters/analyzer_bridge/provenance.py +135 -0
  10. pviz_parser-0.1.0/adapters/canonical.py +340 -0
  11. pviz_parser-0.1.0/adapters/payloads.py +253 -0
  12. pviz_parser-0.1.0/analyzer/__init__.py +172 -0
  13. pviz_parser-0.1.0/analyzer/analyzer_types.py +216 -0
  14. pviz_parser-0.1.0/analyzer/ast_common.py +1098 -0
  15. pviz_parser-0.1.0/analyzer/build_classic.py +373 -0
  16. pviz_parser-0.1.0/analyzer/config.py +288 -0
  17. pviz_parser-0.1.0/analyzer/dead_code.py +635 -0
  18. pviz_parser-0.1.0/analyzer/discovery_manifest.py +936 -0
  19. pviz_parser-0.1.0/analyzer/duplicate_code.py +400 -0
  20. pviz_parser-0.1.0/analyzer/extract_crosstalk.py +414 -0
  21. pviz_parser-0.1.0/analyzer/followup_python.py +0 -0
  22. pviz_parser-0.1.0/analyzer/fs.py +259 -0
  23. pviz_parser-0.1.0/analyzer/go/__init__.py +26 -0
  24. pviz_parser-0.1.0/analyzer/go/go_build_artifacts.py +614 -0
  25. pviz_parser-0.1.0/analyzer/go/go_canonical.py +315 -0
  26. pviz_parser-0.1.0/analyzer/go/go_config.py +155 -0
  27. pviz_parser-0.1.0/analyzer/go/go_folder_index.py +976 -0
  28. pviz_parser-0.1.0/analyzer/go/go_nodefacts_symbols.py +172 -0
  29. pviz_parser-0.1.0/analyzer/go/go_parse.py +319 -0
  30. pviz_parser-0.1.0/analyzer/go/go_parse_dispatch.py +284 -0
  31. pviz_parser-0.1.0/analyzer/go/go_run.py +535 -0
  32. pviz_parser-0.1.0/analyzer/imports_lex.py +167 -0
  33. pviz_parser-0.1.0/analyzer/index.py +428 -0
  34. pviz_parser-0.1.0/analyzer/java/__init__.py +0 -0
  35. pviz_parser-0.1.0/analyzer/java/java_build_artifacts.py +1164 -0
  36. pviz_parser-0.1.0/analyzer/java/java_canonical.py +329 -0
  37. pviz_parser-0.1.0/analyzer/java/java_config.py +75 -0
  38. pviz_parser-0.1.0/analyzer/java/java_folder_index.py +1142 -0
  39. pviz_parser-0.1.0/analyzer/java/java_nodefacts_symbols.py +445 -0
  40. pviz_parser-0.1.0/analyzer/java/java_parse/__init__.py +19 -0
  41. pviz_parser-0.1.0/analyzer/java/java_parse/engine.py +48 -0
  42. pviz_parser-0.1.0/analyzer/java/java_parse/javaparser_engine.py +336 -0
  43. pviz_parser-0.1.0/analyzer/java/java_parse/models.py +58 -0
  44. pviz_parser-0.1.0/analyzer/java/java_parse/regex_engine.py +372 -0
  45. pviz_parser-0.1.0/analyzer/java/java_parse_dispatch.py +238 -0
  46. pviz_parser-0.1.0/analyzer/java/java_run.py +488 -0
  47. pviz_parser-0.1.0/analyzer/kotlin/__init__.py +1 -0
  48. pviz_parser-0.1.0/analyzer/kotlin/kotlin_build_artifacts.py +508 -0
  49. pviz_parser-0.1.0/analyzer/kotlin/kotlin_canonical.py +147 -0
  50. pviz_parser-0.1.0/analyzer/kotlin/kotlin_config.py +37 -0
  51. pviz_parser-0.1.0/analyzer/kotlin/kotlin_folder_index.py +1144 -0
  52. pviz_parser-0.1.0/analyzer/kotlin/kotlin_nodefacts_symbols.py +123 -0
  53. pviz_parser-0.1.0/analyzer/kotlin/kotlin_parse_dispatch.py +20 -0
  54. pviz_parser-0.1.0/analyzer/kotlin/kotlin_run.py +299 -0
  55. pviz_parser-0.1.0/analyzer/kotlin/parse_kotlin/__init__.py +15 -0
  56. pviz_parser-0.1.0/analyzer/kotlin/parse_kotlin/engine.py +104 -0
  57. pviz_parser-0.1.0/analyzer/kotlin/parse_kotlin/models.py +143 -0
  58. pviz_parser-0.1.0/analyzer/l2_method_usage.py +196 -0
  59. pviz_parser-0.1.0/analyzer/module_map.py +170 -0
  60. pviz_parser-0.1.0/analyzer/module_resolve.py +420 -0
  61. pviz_parser-0.1.0/analyzer/parse.py +306 -0
  62. pviz_parser-0.1.0/analyzer/py/__init__.py +0 -0
  63. pviz_parser-0.1.0/analyzer/py/py_run.py +120 -0
  64. pviz_parser-0.1.0/analyzer/rust/__init__.py +0 -0
  65. pviz_parser-0.1.0/analyzer/rust/rust_build_artifacts.py +1347 -0
  66. pviz_parser-0.1.0/analyzer/rust/rust_canonical.py +392 -0
  67. pviz_parser-0.1.0/analyzer/rust/rust_config.py +133 -0
  68. pviz_parser-0.1.0/analyzer/rust/rust_folder_index.py +1234 -0
  69. pviz_parser-0.1.0/analyzer/rust/rust_nodefacts_symbols.py +483 -0
  70. pviz_parser-0.1.0/analyzer/rust/rust_parse/__init__.py +44 -0
  71. pviz_parser-0.1.0/analyzer/rust/rust_parse/engine.py +282 -0
  72. pviz_parser-0.1.0/analyzer/rust/rust_parse/models.py +438 -0
  73. pviz_parser-0.1.0/analyzer/rust/rust_parse_dispatch.py +218 -0
  74. pviz_parser-0.1.0/analyzer/rust/rust_run.py +489 -0
  75. pviz_parser-0.1.0/analyzer/ts/__init__.py +7 -0
  76. pviz_parser-0.1.0/analyzer/ts/build_artifacts.py +690 -0
  77. pviz_parser-0.1.0/analyzer/ts/canonical_web.py +422 -0
  78. pviz_parser-0.1.0/analyzer/ts/config.py +53 -0
  79. pviz_parser-0.1.0/analyzer/ts/discover.py +54 -0
  80. pviz_parser-0.1.0/analyzer/ts/extract_crosstalk.py +344 -0
  81. pviz_parser-0.1.0/analyzer/ts/extract_imports.py +188 -0
  82. pviz_parser-0.1.0/analyzer/ts/model.py +24 -0
  83. pviz_parser-0.1.0/analyzer/ts/parser_runtime.py +91 -0
  84. pviz_parser-0.1.0/analyzer/ts/resolve_imports.py +266 -0
  85. pviz_parser-0.1.0/analyzer/ts/run.py +979 -0
  86. pviz_parser-0.1.0/analyzer/ts/symbols_js.py +319 -0
  87. pviz_parser-0.1.0/analyzer/ts/tsconfig_paths.py +548 -0
  88. pviz_parser-0.1.0/analyzer/ts/write_artifacts.py +12 -0
  89. pviz_parser-0.1.0/analyzer_store/__init__.py +1 -0
  90. pviz_parser-0.1.0/analyzer_store/coverage.py +73 -0
  91. pviz_parser-0.1.0/analyzer_store/edge_pass.py +536 -0
  92. pviz_parser-0.1.0/analyzer_store/folder_index.py +931 -0
  93. pviz_parser-0.1.0/analyzer_store/followup_assess.py +50 -0
  94. pviz_parser-0.1.0/analyzer_store/integration.py +727 -0
  95. pviz_parser-0.1.0/analyzer_store/io_utils.py +128 -0
  96. pviz_parser-0.1.0/analyzer_store/nodefacts.py +1256 -0
  97. pviz_parser-0.1.0/analyzer_store/types.py +138 -0
  98. pviz_parser-0.1.0/analyzer_store/validators.py +72 -0
  99. pviz_parser-0.1.0/contracts/__init__.py +0 -0
  100. pviz_parser-0.1.0/contracts/types.py +184 -0
  101. pviz_parser-0.1.0/core/artifact_sets_merge/__init__.py +5 -0
  102. pviz_parser-0.1.0/core/artifact_sets_merge/api.py +176 -0
  103. pviz_parser-0.1.0/core/artifact_sets_merge/config.py +86 -0
  104. pviz_parser-0.1.0/core/artifact_sets_merge/crosstalk.py +225 -0
  105. pviz_parser-0.1.0/core/artifact_sets_merge/discovery.py +44 -0
  106. pviz_parser-0.1.0/core/artifact_sets_merge/edges.py +53 -0
  107. pviz_parser-0.1.0/core/artifact_sets_merge/folder_index.py +125 -0
  108. pviz_parser-0.1.0/core/artifact_sets_merge/io.py +52 -0
  109. pviz_parser-0.1.0/core/artifact_sets_merge/meta.py +112 -0
  110. pviz_parser-0.1.0/core/artifact_sets_merge/metrics.py +115 -0
  111. pviz_parser-0.1.0/core/artifact_sets_merge/nodes.py +205 -0
  112. pviz_parser-0.1.0/core/artifact_sets_merge/normalize.py +33 -0
  113. pviz_parser-0.1.0/core/artifact_sets_merge/publish.py +156 -0
  114. pviz_parser-0.1.0/core/artifact_sets_merge/scc.py +245 -0
  115. pviz_parser-0.1.0/core/artifacts.py +204 -0
  116. pviz_parser-0.1.0/core/artifacts_headless.py +50 -0
  117. pviz_parser-0.1.0/core/build_context.py +91 -0
  118. pviz_parser-0.1.0/core/build_pipeline/JS_P_Crosstalk.py +845 -0
  119. pviz_parser-0.1.0/core/build_pipeline/P_JS_Crosstalk.py +1023 -0
  120. pviz_parser-0.1.0/core/build_pipeline/__init__.py +32 -0
  121. pviz_parser-0.1.0/core/build_pipeline/buckets.py +955 -0
  122. pviz_parser-0.1.0/core/build_pipeline/followups.py +108 -0
  123. pviz_parser-0.1.0/core/build_pipeline/json_io.py +18 -0
  124. pviz_parser-0.1.0/core/build_pipeline/normalize.py +82 -0
  125. pviz_parser-0.1.0/core/build_pipeline/pipeline.py +556 -0
  126. pviz_parser-0.1.0/core/build_pipeline/types.py +87 -0
  127. pviz_parser-0.1.0/core/json_export.py +541 -0
  128. pviz_parser-0.1.0/core/pkgzones/builder.py +437 -0
  129. pviz_parser-0.1.0/core/pkgzones/contracts.py +91 -0
  130. pviz_parser-0.1.0/core/pkgzones/placement.py +114 -0
  131. pviz_parser-0.1.0/core/pkgzones/state.py +32 -0
  132. pviz_parser-0.1.0/core/store_root.py +79 -0
  133. pviz_parser-0.1.0/core/workspace_core.py +150 -0
  134. pviz_parser-0.1.0/core/zones_ctx.py +313 -0
  135. pviz_parser-0.1.0/core/zones_headless.py +71 -0
  136. pviz_parser-0.1.0/diagnostics/__init__.py +0 -0
  137. pviz_parser-0.1.0/diagnostics/events.py +52 -0
  138. pviz_parser-0.1.0/diagnostics/logging.py +360 -0
  139. pviz_parser-0.1.0/i_o/__init__.py +55 -0
  140. pviz_parser-0.1.0/i_o/diagnostics_export.py +51 -0
  141. pviz_parser-0.1.0/i_o/export_json.py +361 -0
  142. pviz_parser-0.1.0/i_o/export_llm_json/__init__.py +4 -0
  143. pviz_parser-0.1.0/i_o/export_llm_json/artifacts.py +65 -0
  144. pviz_parser-0.1.0/i_o/export_llm_json/builders_discovery.py +119 -0
  145. pviz_parser-0.1.0/i_o/export_llm_json/builders_folders.py +356 -0
  146. pviz_parser-0.1.0/i_o/export_llm_json/builders_node_order.py +15 -0
  147. pviz_parser-0.1.0/i_o/export_llm_json/builders_nodes_edges.py +251 -0
  148. pviz_parser-0.1.0/i_o/export_llm_json/builders_zones.py +41 -0
  149. pviz_parser-0.1.0/i_o/export_llm_json/errors.py +5 -0
  150. pviz_parser-0.1.0/i_o/export_llm_json/export.py +573 -0
  151. pviz_parser-0.1.0/i_o/export_llm_json/json_compression/__init__.py +16 -0
  152. pviz_parser-0.1.0/i_o/export_llm_json/json_compression/edge_codecs.py +157 -0
  153. pviz_parser-0.1.0/i_o/export_llm_json/json_compression/format_guide.py +215 -0
  154. pviz_parser-0.1.0/i_o/export_llm_json/json_compression/node_codecs.py +328 -0
  155. pviz_parser-0.1.0/i_o/export_llm_json/json_compression/nodes.py +165 -0
  156. pviz_parser-0.1.0/i_o/export_llm_json/json_compression/path_legend.py +230 -0
  157. pviz_parser-0.1.0/i_o/export_llm_json/json_compression/run.py +136 -0
  158. pviz_parser-0.1.0/i_o/export_llm_json/json_compression/types.py +130 -0
  159. pviz_parser-0.1.0/i_o/export_llm_json/json_compression/util.py +60 -0
  160. pviz_parser-0.1.0/i_o/export_llm_json/summary.py +649 -0
  161. pviz_parser-0.1.0/i_o/export_llm_json/utils.py +62 -0
  162. pviz_parser-0.1.0/i_o/export_pdf.py +97 -0
  163. pviz_parser-0.1.0/i_o/layout_store.py +455 -0
  164. pviz_parser-0.1.0/i_o/path_utils.py +23 -0
  165. pviz_parser-0.1.0/i_o/workspace_io.py +295 -0
  166. pviz_parser-0.1.0/i_o/workspace_manager.py +171 -0
  167. pviz_parser-0.1.0/pviz_parser.egg-info/PKG-INFO +123 -0
  168. pviz_parser-0.1.0/pviz_parser.egg-info/SOURCES.txt +172 -0
  169. pviz_parser-0.1.0/pviz_parser.egg-info/dependency_links.txt +1 -0
  170. pviz_parser-0.1.0/pviz_parser.egg-info/entry_points.txt +2 -0
  171. pviz_parser-0.1.0/pviz_parser.egg-info/requires.txt +5 -0
  172. pviz_parser-0.1.0/pviz_parser.egg-info/top_level.txt +7 -0
  173. pviz_parser-0.1.0/pyproject.toml +65 -0
  174. 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}