codeboarding 0.9.5__tar.gz → 0.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. {codeboarding-0.9.5/codeboarding.egg-info → codeboarding-0.10.0}/PKG-INFO +16 -20
  2. {codeboarding-0.9.5 → codeboarding-0.10.0}/PYPI.md +14 -0
  3. {codeboarding-0.9.5 → codeboarding-0.10.0}/README.md +12 -2
  4. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/abstraction_agent.py +27 -20
  5. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/agent.py +92 -156
  6. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/agent_responses.py +98 -30
  7. codeboarding-0.10.0/agents/change_status.py +9 -0
  8. codeboarding-0.10.0/agents/cluster_methods_mixin.py +601 -0
  9. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/constants.py +1 -1
  10. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/details_agent.py +82 -25
  11. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/llm_config.py +3 -3
  12. codeboarding-0.10.0/agents/meta_agent.py +67 -0
  13. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/planner_agent.py +19 -7
  14. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/prompts/__init__.py +0 -3
  15. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/prompts/abstract_prompt_factory.py +0 -4
  16. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/prompts/claude_prompts.py +66 -59
  17. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/prompts/deepseek_prompts.py +66 -62
  18. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/prompts/gemini_flash_prompts.py +66 -59
  19. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/prompts/glm_prompts.py +70 -65
  20. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/prompts/gpt_prompts.py +66 -59
  21. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/prompts/kimi_prompts.py +65 -65
  22. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/prompts/prompt_factory.py +0 -4
  23. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/read_cfg.py +6 -5
  24. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/read_file_structure.py +1 -2
  25. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/read_source.py +10 -30
  26. codeboarding-0.10.0/agents/validation.py +630 -0
  27. codeboarding-0.10.0/caching/__init__.py +12 -0
  28. codeboarding-0.10.0/caching/cache.py +310 -0
  29. codeboarding-0.10.0/caching/details_cache.py +58 -0
  30. codeboarding-0.10.0/caching/meta_cache.py +111 -0
  31. {codeboarding-0.9.5 → codeboarding-0.10.0/codeboarding.egg-info}/PKG-INFO +16 -20
  32. {codeboarding-0.9.5 → codeboarding-0.10.0}/codeboarding.egg-info/SOURCES.txt +32 -16
  33. {codeboarding-0.9.5 → codeboarding-0.10.0}/codeboarding.egg-info/requires.txt +0 -17
  34. {codeboarding-0.9.5 → codeboarding-0.10.0}/codeboarding.egg-info/top_level.txt +1 -0
  35. codeboarding-0.10.0/constants.py +14 -0
  36. {codeboarding-0.9.5 → codeboarding-0.10.0}/diagram_analysis/__init__.py +1 -1
  37. {codeboarding-0.9.5 → codeboarding-0.10.0}/diagram_analysis/analysis_json.py +237 -82
  38. {codeboarding-0.9.5 → codeboarding-0.10.0}/diagram_analysis/diagram_generator.py +79 -110
  39. codeboarding-0.10.0/diagram_analysis/incremental_types.py +70 -0
  40. codeboarding-0.10.0/diagram_analysis/incremental_updater.py +325 -0
  41. {codeboarding-0.9.5/diagram_analysis/incremental → codeboarding-0.10.0/diagram_analysis}/io_utils.py +26 -7
  42. {codeboarding-0.9.5 → codeboarding-0.10.0}/diagram_analysis/manifest.py +3 -2
  43. codeboarding-0.10.0/diagram_analysis/run_context.py +57 -0
  44. {codeboarding-0.9.5 → codeboarding-0.10.0}/github_action.py +5 -4
  45. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/checks/function_size.py +2 -2
  46. codeboarding-0.10.0/health_main.py +151 -0
  47. {codeboarding-0.9.5 → codeboarding-0.10.0}/install.py +71 -50
  48. codeboarding-0.10.0/logging_config.py +136 -0
  49. {codeboarding-0.9.5 → codeboarding-0.10.0}/main.py +68 -30
  50. {codeboarding-0.9.5 → codeboarding-0.10.0}/monitoring/paths.py +3 -4
  51. {codeboarding-0.9.5 → codeboarding-0.10.0}/output_generators/markdown.py +17 -0
  52. {codeboarding-0.9.5 → codeboarding-0.10.0}/output_generators/mdx.py +24 -1
  53. {codeboarding-0.9.5 → codeboarding-0.10.0}/output_generators/sphinx.py +22 -0
  54. {codeboarding-0.9.5 → codeboarding-0.10.0}/pyproject.toml +4 -22
  55. {codeboarding-0.9.5 → codeboarding-0.10.0}/repo_utils/__init__.py +1 -1
  56. {codeboarding-0.9.5 → codeboarding-0.10.0}/repo_utils/change_detector.py +12 -7
  57. codeboarding-0.10.0/repo_utils/ignore.py +305 -0
  58. codeboarding-0.10.0/repo_utils/method_diff.py +177 -0
  59. codeboarding-0.10.0/static_analyzer/__init__.py +524 -0
  60. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/analysis_cache.py +80 -20
  61. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/analysis_result.py +200 -19
  62. codeboarding-0.10.0/static_analyzer/cluster_relations.py +175 -0
  63. codeboarding-0.10.0/static_analyzer/constants.py +164 -0
  64. codeboarding-0.10.0/static_analyzer/engine/__init__.py +27 -0
  65. codeboarding-0.10.0/static_analyzer/engine/adapters/__init__.py +32 -0
  66. codeboarding-0.10.0/static_analyzer/engine/adapters/go_adapter.py +207 -0
  67. codeboarding-0.10.0/static_analyzer/engine/adapters/java_adapter.py +295 -0
  68. codeboarding-0.10.0/static_analyzer/engine/adapters/php_adapter.py +50 -0
  69. codeboarding-0.10.0/static_analyzer/engine/adapters/python_adapter.py +56 -0
  70. codeboarding-0.10.0/static_analyzer/engine/adapters/typescript_adapter.py +51 -0
  71. codeboarding-0.10.0/static_analyzer/engine/call_graph_builder.py +292 -0
  72. codeboarding-0.10.0/static_analyzer/engine/edge_build_context.py +18 -0
  73. codeboarding-0.10.0/static_analyzer/engine/edge_builder.py +506 -0
  74. codeboarding-0.10.0/static_analyzer/engine/hierarchy_builder.py +200 -0
  75. codeboarding-0.10.0/static_analyzer/engine/language_adapter.py +271 -0
  76. codeboarding-0.10.0/static_analyzer/engine/lsp_client.py +679 -0
  77. codeboarding-0.10.0/static_analyzer/engine/lsp_constants.py +33 -0
  78. codeboarding-0.10.0/static_analyzer/engine/models.py +101 -0
  79. codeboarding-0.10.0/static_analyzer/engine/progress.py +83 -0
  80. codeboarding-0.10.0/static_analyzer/engine/protocols.py +48 -0
  81. codeboarding-0.10.0/static_analyzer/engine/result_converter.py +133 -0
  82. codeboarding-0.10.0/static_analyzer/engine/source_inspector.py +203 -0
  83. codeboarding-0.10.0/static_analyzer/engine/symbol_table.py +328 -0
  84. codeboarding-0.10.0/static_analyzer/engine/utils.py +22 -0
  85. codeboarding-0.10.0/static_analyzer/graph.py +619 -0
  86. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/incremental_orchestrator.py +194 -221
  87. codeboarding-0.10.0/static_analyzer/node.py +67 -0
  88. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/reference_resolve_mixin.py +46 -25
  89. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/scanner.py +9 -0
  90. {codeboarding-0.9.5 → codeboarding-0.10.0}/tests/test_github_action.py +6 -9
  91. {codeboarding-0.9.5 → codeboarding-0.10.0}/tests/test_install.py +83 -0
  92. {codeboarding-0.9.5 → codeboarding-0.10.0}/tests/test_logging_config.py +50 -33
  93. {codeboarding-0.9.5 → codeboarding-0.10.0}/tests/test_main.py +48 -7
  94. codeboarding-0.10.0/tests/test_pyproject_packages.py +52 -0
  95. {codeboarding-0.9.5 → codeboarding-0.10.0}/tests/test_windows_compatibility.py +1 -1
  96. {codeboarding-0.9.5 → codeboarding-0.10.0}/tests/test_windows_encoding.py +1 -1
  97. {codeboarding-0.9.5 → codeboarding-0.10.0}/tool_registry.py +24 -4
  98. {codeboarding-0.9.5 → codeboarding-0.10.0}/utils.py +36 -0
  99. codeboarding-0.9.5/agents/cluster_methods_mixin.py +0 -284
  100. codeboarding-0.9.5/agents/meta_agent.py +0 -105
  101. codeboarding-0.9.5/agents/validation.py +0 -383
  102. codeboarding-0.9.5/caching/__init__.py +0 -4
  103. codeboarding-0.9.5/caching/cache.py +0 -29
  104. codeboarding-0.9.5/caching/meta_cache.py +0 -227
  105. codeboarding-0.9.5/diagram_analysis/incremental/__init__.py +0 -63
  106. codeboarding-0.9.5/diagram_analysis/incremental/component_checker.py +0 -236
  107. codeboarding-0.9.5/diagram_analysis/incremental/file_manager.py +0 -217
  108. codeboarding-0.9.5/diagram_analysis/incremental/impact_analyzer.py +0 -238
  109. codeboarding-0.9.5/diagram_analysis/incremental/models.py +0 -72
  110. codeboarding-0.9.5/diagram_analysis/incremental/path_patching.py +0 -164
  111. codeboarding-0.9.5/diagram_analysis/incremental/reexpansion.py +0 -166
  112. codeboarding-0.9.5/diagram_analysis/incremental/scoped_analysis.py +0 -227
  113. codeboarding-0.9.5/diagram_analysis/incremental/updater.py +0 -464
  114. codeboarding-0.9.5/diagram_analysis/incremental/validation.py +0 -48
  115. codeboarding-0.9.5/health_main.py +0 -122
  116. codeboarding-0.9.5/logging_config.py +0 -119
  117. codeboarding-0.9.5/repo_utils/ignore.py +0 -341
  118. codeboarding-0.9.5/static_analyzer/__init__.py +0 -475
  119. codeboarding-0.9.5/static_analyzer/constants.py +0 -166
  120. codeboarding-0.9.5/static_analyzer/graph.py +0 -746
  121. codeboarding-0.9.5/static_analyzer/lsp_client/client.py +0 -1750
  122. codeboarding-0.9.5/static_analyzer/lsp_client/java_client.py +0 -517
  123. codeboarding-0.9.5/static_analyzer/lsp_client/language_settings.py +0 -97
  124. codeboarding-0.9.5/static_analyzer/lsp_client/typescript_client.py +0 -235
  125. codeboarding-0.9.5/tests/test_incremental_analyzer.py +0 -204
  126. {codeboarding-0.9.5 → codeboarding-0.10.0}/LICENSE +0 -0
  127. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/__init__.py +0 -0
  128. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/dependency_discovery.py +0 -0
  129. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/__init__.py +0 -0
  130. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/base.py +0 -0
  131. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/get_external_deps.py +0 -0
  132. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/get_method_invocations.py +0 -0
  133. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/read_docs.py +0 -0
  134. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/read_file.py +0 -0
  135. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/read_git_diff.py +0 -0
  136. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/read_packages.py +0 -0
  137. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/read_structure.py +0 -0
  138. {codeboarding-0.9.5 → codeboarding-0.10.0}/agents/tools/toolkit.py +0 -0
  139. {codeboarding-0.9.5 → codeboarding-0.10.0}/codeboarding.egg-info/dependency_links.txt +0 -0
  140. {codeboarding-0.9.5 → codeboarding-0.10.0}/codeboarding.egg-info/entry_points.txt +0 -0
  141. {codeboarding-0.9.5 → codeboarding-0.10.0}/core/__init__.py +0 -0
  142. {codeboarding-0.9.5 → codeboarding-0.10.0}/core/plugin_loader.py +0 -0
  143. {codeboarding-0.9.5 → codeboarding-0.10.0}/core/protocols.py +0 -0
  144. {codeboarding-0.9.5 → codeboarding-0.10.0}/core/registry.py +0 -0
  145. {codeboarding-0.9.5 → codeboarding-0.10.0}/diagram_analysis/file_coverage.py +0 -0
  146. {codeboarding-0.9.5 → codeboarding-0.10.0}/diagram_analysis/version.py +0 -0
  147. {codeboarding-0.9.5 → codeboarding-0.10.0}/duckdb_crud.py +0 -0
  148. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/__init__.py +0 -0
  149. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/checks/__init__.py +0 -0
  150. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/checks/circular_deps.py +0 -0
  151. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/checks/cohesion.py +0 -0
  152. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/checks/coupling.py +0 -0
  153. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/checks/god_class.py +0 -0
  154. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/checks/inheritance.py +0 -0
  155. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/checks/instability.py +0 -0
  156. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/checks/unused_code_diagnostics.py +0 -0
  157. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/config.py +0 -0
  158. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/constants.py +0 -0
  159. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/models.py +0 -0
  160. {codeboarding-0.9.5 → codeboarding-0.10.0}/health/runner.py +0 -0
  161. {codeboarding-0.9.5 → codeboarding-0.10.0}/monitoring/__init__.py +0 -0
  162. {codeboarding-0.9.5 → codeboarding-0.10.0}/monitoring/callbacks.py +0 -0
  163. {codeboarding-0.9.5 → codeboarding-0.10.0}/monitoring/context.py +0 -0
  164. {codeboarding-0.9.5 → codeboarding-0.10.0}/monitoring/mixin.py +0 -0
  165. {codeboarding-0.9.5 → codeboarding-0.10.0}/monitoring/stats.py +0 -0
  166. {codeboarding-0.9.5 → codeboarding-0.10.0}/monitoring/writers.py +0 -0
  167. {codeboarding-0.9.5 → codeboarding-0.10.0}/output_generators/__init__.py +0 -0
  168. {codeboarding-0.9.5 → codeboarding-0.10.0}/output_generators/html.py +0 -0
  169. {codeboarding-0.9.5 → codeboarding-0.10.0}/output_generators/html_template.py +0 -0
  170. {codeboarding-0.9.5 → codeboarding-0.10.0}/repo_utils/errors.py +0 -0
  171. {codeboarding-0.9.5 → codeboarding-0.10.0}/repo_utils/git_diff.py +0 -0
  172. {codeboarding-0.9.5 → codeboarding-0.10.0}/setup.cfg +0 -0
  173. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/cluster_change_analyzer.py +0 -0
  174. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/cluster_helpers.py +0 -0
  175. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/git_diff_analyzer.py +0 -0
  176. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/java_config_scanner.py +0 -0
  177. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/java_utils.py +0 -0
  178. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/lsp_client/__init__.py +0 -0
  179. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/lsp_client/diagnostics.py +0 -0
  180. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/programming_language.py +0 -0
  181. {codeboarding-0.9.5 → codeboarding-0.10.0}/static_analyzer/typescript_config_scanner.py +0 -0
  182. {codeboarding-0.9.5 → codeboarding-0.10.0}/tests/test_tool_registry.py +0 -0
  183. {codeboarding-0.9.5 → codeboarding-0.10.0}/tests/test_user_config.py +0 -0
  184. {codeboarding-0.9.5 → codeboarding-0.10.0}/tests/test_vscode_constants.py +0 -0
  185. {codeboarding-0.9.5 → codeboarding-0.10.0}/user_config.py +0 -0
  186. {codeboarding-0.9.5 → codeboarding-0.10.0}/vscode_constants.py +0 -0
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.9.5
3
+ Version: 0.10.0
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Repository, https://github.com/CodeBoarding/CodeBoarding
8
8
  Project-URL: Documentation, https://github.com/CodeBoarding/CodeBoarding/blob/main/.codeboarding/overview.md
9
9
  Keywords: code-understanding,code-visualization,code-analysis,diagrams,documentation,llm,static-analysis,mermaid,onboarding
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: MIT License
13
12
  Classifier: Programming Language :: Python :: 3.12
14
13
  Classifier: Programming Language :: Python :: 3.13
15
14
  Classifier: Topic :: Software Development :: Documentation
@@ -17,23 +16,14 @@ Classifier: Topic :: Software Development :: Code Generators
17
16
  Requires-Python: <3.14,>=3.12
18
17
  Description-Content-Type: text/markdown
19
18
  License-File: LICENSE
20
- Requires-Dist: asgiref>=3.9
21
- Requires-Dist: async-timeout>=4.0
22
19
  Requires-Dist: docker>=7.1
23
20
  Requires-Dist: dotenv>=0.9
24
21
  Requires-Dist: duckdb>=1.3
25
22
  Requires-Dist: dulwich>=0.22
26
- Requires-Dist: email-validator>=2.2
27
- Requires-Dist: exceptiongroup>=1.2
28
23
  Requires-Dist: fastapi>=0.115
29
24
  Requires-Dist: filelock>=3.12
30
25
  Requires-Dist: gitpython>=3.1
31
26
  Requires-Dist: google-api-core>=2.10
32
- Requires-Dist: google-genai>=1.10
33
- Requires-Dist: gql>=3.5
34
- Requires-Dist: injector>=0.21
35
- Requires-Dist: ipykernel>=6.29
36
- Requires-Dist: isort>=6.0
37
27
  Requires-Dist: jsonschema>=4.25
38
28
  Requires-Dist: langchain>=1.2
39
29
  Requires-Dist: langchain-anthropic>=1.3
@@ -46,21 +36,13 @@ Requires-Dist: langchain-openai>=1.1
46
36
  Requires-Dist: markdown>=3.8
47
37
  Requires-Dist: markdown-it-py>=3.0
48
38
  Requires-Dist: markitdown>=0.1
49
- Requires-Dist: matplotlib>=3.10
50
39
  Requires-Dist: networkx>=3.4
51
- Requires-Dist: openpyxl>=3.1
52
- Requires-Dist: pandas>=2.2
53
40
  Requires-Dist: pathspec>=0.12
54
- Requires-Dist: pydot>=3.0
55
41
  Requires-Dist: pyyaml>=6.0
56
42
  Requires-Dist: regex>=2024.11
57
43
  Requires-Dist: rich>=12.6
58
- Requires-Dist: seaborn>=0.13
59
- Requires-Dist: tomli>=2.2
60
44
  Requires-Dist: trustcall>=0.0.39
61
- Requires-Dist: typer>=0.9
62
45
  Requires-Dist: uvicorn>=0.23
63
- Requires-Dist: wcwidth>=0.2.13
64
46
  Provides-Extra: dev
65
47
  Requires-Dist: pytest>=8.3; extra == "dev"
66
48
  Requires-Dist: pytest-cov>=7.0; extra == "dev"
@@ -91,12 +73,26 @@ Dynamic: license-file
91
73
 
92
74
  ---
93
75
 
76
+ ## Requirements
77
+
78
+ - **Python 3.12 or 3.13** — other versions are currently not supported.
79
+
94
80
  ## Installation
95
81
 
82
+ The recommended way to install the CLI is with [pipx](https://pipx.pypa.io), which automatically creates an isolated environment:
83
+
84
+ ```bash
85
+ pipx install codeboarding --python python3.12
86
+ ```
87
+
88
+ Alternatively, install into an existing virtual environment with pip:
89
+
96
90
  ```bash
97
91
  pip install codeboarding
98
92
  ```
99
93
 
94
+ > Installing into the global Python environment with `pip` is not recommended — it can cause dependency conflicts and will fail if the system Python is not 3.12 or 3.13.
95
+
100
96
  Language server binaries are downloaded automatically on first use. To pre-install them explicitly (useful in CI or restricted environments):
101
97
 
102
98
  ```bash
@@ -14,12 +14,26 @@
14
14
 
15
15
  ---
16
16
 
17
+ ## Requirements
18
+
19
+ - **Python 3.12 or 3.13** — other versions are currently not supported.
20
+
17
21
  ## Installation
18
22
 
23
+ The recommended way to install the CLI is with [pipx](https://pipx.pypa.io), which automatically creates an isolated environment:
24
+
25
+ ```bash
26
+ pipx install codeboarding --python python3.12
27
+ ```
28
+
29
+ Alternatively, install into an existing virtual environment with pip:
30
+
19
31
  ```bash
20
32
  pip install codeboarding
21
33
  ```
22
34
 
35
+ > Installing into the global Python environment with `pip` is not recommended — it can cause dependency conflicts and will fail if the system Python is not 3.12 or 3.13.
36
+
23
37
  Language server binaries are downloaded automatically on first use. To pre-install them explicitly (useful in CI or restricted environments):
24
38
 
25
39
  ```bash
@@ -6,7 +6,7 @@ CodeBoarding gives developers and coding agents a visual map of a codebase. It c
6
6
 
7
7
  [Website](https://codeboarding.org) · [Open VSX extension](https://open-vsx.org/extension/CodeBoarding/codeboarding) · [Explore examples](https://codeboarding.org/diagrams) · [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) · [GitHub Action](https://github.com/marketplace/actions/codeboarding-diagram-first-documentation) ·[Discord](https://discord.gg/T5zHTJYFuy)
8
8
 
9
- [![CodeBoarding demo](docs/assets/codeboarding-demo.gif)](https://open-vsx.org/extension/CodeBoarding/codeboarding)
9
+ [![CodeBoarding demo](https://gist.githubusercontent.com/ivanmilevtues/1c4f921066613516cfd7b938014a6877/raw/611aec7711556807860ff2e1679a5dc4c0c23fed/CodeBoarding_extension_demo.gif)](https://open-vsx.org/extension/CodeBoarding/codeboarding)
10
10
 
11
11
  Install the extension from Open VSX.
12
12
 
@@ -73,6 +73,16 @@ python main.py --local /path/to/repo
73
73
 
74
74
  ### Use the packaged CLI
75
75
 
76
+ Requires **Python 3.12 or 3.13**. The recommended install method is [pipx](https://pipx.pypa.io), which keeps the CLI in its own isolated environment:
77
+
78
+ ```bash
79
+ pipx install codeboarding --python python3.12
80
+ codeboarding-setup
81
+ codeboarding --local /path/to/repo
82
+ ```
83
+
84
+ Or, if you prefer pip, install into a virtual environment (not the global Python):
85
+
76
86
  ```bash
77
87
  pip install codeboarding
78
88
  codeboarding-setup
@@ -117,7 +127,7 @@ python main.py --local ./my-project --depth-level 2
117
127
  python main.py --local ./my-project --incremental
118
128
 
119
129
  # Update a single component by ID
120
- python main.py --local ./my-project --partial-component-id "a3f2b1c4d5e6f789"
130
+ python main.py --local ./my-project --partial-component-id "1.2"
121
131
 
122
132
  # Analyze a remote GitHub repository
123
133
  python main.py https://github.com/pytorch/pytorch
@@ -1,8 +1,8 @@
1
1
  import logging
2
2
  from pathlib import Path
3
3
 
4
- from langchain_core.prompts import PromptTemplate
5
4
  from langchain_core.language_models import BaseChatModel
5
+ from langchain_core.prompts import PromptTemplate
6
6
 
7
7
  from agents.agent import CodeBoardingAgent
8
8
  from agents.agent_responses import (
@@ -11,24 +11,26 @@ from agents.agent_responses import (
11
11
  MetaAnalysisInsights,
12
12
  assign_component_ids,
13
13
  )
14
+ from agents.cluster_methods_mixin import ClusterMethodsMixin
14
15
  from agents.prompts import (
15
- get_system_message,
16
16
  get_cluster_grouping_message,
17
17
  get_final_analysis_message,
18
+ get_system_message,
18
19
  )
19
- from agents.cluster_methods_mixin import ClusterMethodsMixin
20
20
  from agents.validation import (
21
21
  ValidationContext,
22
22
  validate_cluster_coverage,
23
- validate_component_relationships,
23
+ validate_group_name_coverage,
24
24
  validate_key_entities,
25
- validate_cluster_ids_populated,
26
25
  validate_relation_component_names,
27
26
  )
28
27
  from monitoring import trace
29
28
  from static_analyzer.analysis_result import StaticAnalysisResults
29
+ from static_analyzer.cluster_helpers import (
30
+ build_all_cluster_results,
31
+ get_all_cluster_ids,
32
+ )
30
33
  from static_analyzer.graph import ClusterResult
31
- from static_analyzer.cluster_helpers import build_all_cluster_results, get_all_cluster_ids
32
34
 
33
35
  logger = logging.getLogger(__name__)
34
36
 
@@ -86,19 +88,20 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
86
88
  cluster_results=cluster_results,
87
89
  expected_cluster_ids=get_all_cluster_ids(cluster_results),
88
90
  ),
91
+ max_validation_attempts=3,
89
92
  )
90
93
  return cluster_analysis
91
94
 
92
95
  @trace
93
96
  def step_final_analysis(
94
- self, cluster_analysis: ClusterAnalysis, cluster_results: dict[str, ClusterResult]
97
+ self, llm_cluster_analysis: ClusterAnalysis, cluster_results: dict[str, ClusterResult]
95
98
  ) -> AnalysisInsights:
96
99
  logger.info(f"[AbstractionAgent] Generating final analysis for: {self.project_name}")
97
100
 
98
101
  meta_context_str = self.meta_context.llm_str() if self.meta_context else "No project context available."
99
102
  project_type = self.meta_context.project_type if self.meta_context else "unknown"
100
103
 
101
- cluster_str = cluster_analysis.llm_str() if cluster_analysis else "No cluster analysis available."
104
+ cluster_str = llm_cluster_analysis.llm_str() if llm_cluster_analysis else "No cluster analysis available."
102
105
 
103
106
  prompt = self.prompts["final_analysis"].format(
104
107
  project_name=self.project_name,
@@ -111,6 +114,8 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
111
114
  context = ValidationContext(
112
115
  cluster_results=cluster_results,
113
116
  cfg_graphs={lang: self.static_analysis.get_cfg(lang) for lang in self.static_analysis.get_languages()},
117
+ static_analysis=self.static_analysis,
118
+ llm_cluster_analysis=llm_cluster_analysis,
114
119
  )
115
120
 
116
121
  return self._validation_invoke(
@@ -118,11 +123,11 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
118
123
  AnalysisInsights,
119
124
  validators=[
120
125
  validate_relation_component_names,
121
- validate_component_relationships,
126
+ validate_group_name_coverage,
122
127
  validate_key_entities,
123
- validate_cluster_ids_populated,
124
128
  ],
125
129
  context=context,
130
+ max_validation_attempts=3,
126
131
  )
127
132
 
128
133
  def run(self):
@@ -134,17 +139,19 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
134
139
 
135
140
  # Step 2: Generate abstract components from grouped clusters
136
141
  analysis = self.step_final_analysis(cluster_analysis, cluster_results)
137
- # Step 3: Sanitize cluster IDs (remove invalid ones)
138
- self._sanitize_component_cluster_ids(analysis, cluster_results=cluster_results)
139
- # Step 4: Assign files to components (deterministic + LLM-based with validation)
140
- self.classify_files(analysis, cluster_results, self.static_analysis.get_all_source_files())
141
- # Step 5: Fix source code reference lines (resolves reference_file paths for key_entities)
142
+ # Step 3: Assign hierarchical component IDs ("1", "2", "3", ...)
143
+ assign_component_ids(analysis)
144
+ # Step 4: Resolve cluster IDs deterministically from group names
145
+ self._resolve_cluster_ids_from_groups(analysis, cluster_analysis)
146
+ # Step 5: Populate file_methods deterministically from cluster results + orphan assignment
147
+ self.populate_file_methods(analysis, cluster_results)
148
+
149
+ # Step 6: Build static inter-component relations from CFG edges
150
+ self.build_static_relations(analysis)
151
+
152
+ # Step 7: Fix source code reference lines (resolves reference_file paths for key_entities)
142
153
  analysis = self.fix_source_code_reference_lines(analysis)
143
- # Step 6: Ensure unique key entities across components
154
+ # Step 8: Ensure unique key entities across components
144
155
  self._ensure_unique_key_entities(analysis)
145
- # Step 7: Ensure unique file assignments across components
146
- self._ensure_unique_file_assignments(analysis)
147
- # Step 8: Assign deterministic component IDs
148
- assign_component_ids(analysis)
149
156
 
150
157
  return analysis, cluster_results
@@ -1,6 +1,5 @@
1
1
  import json
2
2
  import logging
3
- import os
4
3
  import time
5
4
  from pathlib import Path
6
5
 
@@ -15,14 +14,10 @@ from langgraph.graph.state import CompiledStateGraph
15
14
  from pydantic import ValidationError
16
15
  from trustcall import create_extractor
17
16
 
18
- from agents.agent_responses import AnalysisInsights, ComponentFiles, FileClassification
19
- from agents.prompts import (
20
- get_unassigned_files_classification_message,
21
- get_validation_feedback_message,
22
- )
17
+ from agents.prompts import get_validation_feedback_message
23
18
  from agents.tools.base import RepoContext
24
19
  from agents.tools.toolkit import CodeBoardingToolkit
25
- from agents.validation import ValidationContext, validate_file_classifications
20
+ from agents.validation import ValidationResult, score_validation_results, VALIDATOR_WEIGHTS, DEFAULT_VALIDATOR_WEIGHT
26
21
  from monitoring.mixin import MonitoringMixin
27
22
  from repo_utils.ignore import RepoIgnoreManager
28
23
  from agents.llm_config import MONITORING_CALLBACK
@@ -217,55 +212,123 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
217
212
  except Empty:
218
213
  raise RuntimeError("Agent invocation completed but no result was returned")
219
214
 
220
- def _parse_invoke(self, prompt, type):
215
+ def _parse_invoke(self, prompt: str, type: type):
221
216
  response = self._invoke(prompt)
222
217
  assert isinstance(response, str), f"Expected a string as response type got {response}"
223
218
  return self._parse_response(prompt, response, type)
224
219
 
220
+ def _score_result(self, result, validators: list, context) -> tuple[float, list[tuple[float, str]]]:
221
+ """Run all validators on a result and return (score, prioritized_feedback).
222
+
223
+ The score is computed using weighted validators where coverage-related
224
+ validators (cluster coverage, group name coverage) carry significantly
225
+ more weight than others.
226
+
227
+ Feedback messages are returned as (weight, message) tuples sorted by
228
+ weight descending, so that the LLM focuses on the most critical issues
229
+ (cluster/group coverage) before lower-priority ones (key entities).
230
+ """
231
+ validator_results: list[tuple] = []
232
+ weighted_feedback: list[tuple[float, str]] = []
233
+ for validator in validators:
234
+ validator_result: ValidationResult = validator(result, context)
235
+ validator_results.append((validator, validator_result))
236
+ if not validator_result.is_valid:
237
+ weight = VALIDATOR_WEIGHTS.get(validator.__name__, DEFAULT_VALIDATOR_WEIGHT)
238
+ for msg in validator_result.feedback_messages:
239
+ weighted_feedback.append((weight, msg))
240
+
241
+ # Sort by weight descending so critical feedback comes first
242
+ weighted_feedback.sort(key=lambda x: x[0], reverse=True)
243
+
244
+ score = score_validation_results(validator_results)
245
+ return score, weighted_feedback
246
+
225
247
  def _validation_invoke(
226
- self, prompt: str, return_type: type, validators: list, context, max_validation_retries: int = 1
248
+ self, prompt: str, return_type: type, validators: list, context, max_validation_attempts: int = 1
227
249
  ):
228
250
  """
229
- Invoke LLM with validation and feedback loop.
251
+ Invoke LLM with validation, feedback loop, and best-of-N selection.
252
+
253
+ Each attempt (initial + retries) is scored using weighted validators.
254
+ Coverage validators (validate_cluster_coverage, validate_group_name_coverage)
255
+ are weighted ~2x higher than other validators, so the selection strongly
256
+ favours results with complete coverage.
257
+
258
+ If any attempt scores perfectly (all validators pass), it is returned
259
+ immediately. Otherwise the highest-scoring result across all attempts is
260
+ returned.
230
261
 
231
262
  Args:
232
263
  prompt: The original prompt
233
264
  return_type: Pydantic type to parse into
234
265
  validators: List of validation functions to run
235
266
  context: ValidationContext with data needed for validation
236
- max_validation_retries: Maximum retry attempts with feedback (default: 1)
267
+ max_validation_attempts: Maximum validation attempts (initial attempt included).
268
+ Retries occur only when this value is greater than 1. (default: 1)
237
269
 
238
270
  Returns:
239
- Validated result of return_type
271
+ The highest-scoring result of return_type across all attempts
240
272
  """
273
+ # Compute the maximum possible score so we can detect a perfect result
274
+ max_possible_score = sum(VALIDATOR_WEIGHTS.get(v.__name__, DEFAULT_VALIDATOR_WEIGHT) for v in validators)
275
+
241
276
  result = self._parse_invoke(prompt, return_type)
242
277
 
243
- for attempt in range(max_validation_retries):
244
- # Run all validators
245
- all_feedback = []
246
- for validator in validators:
247
- validation_result = validator(result, context)
248
- if not validation_result.is_valid:
249
- all_feedback.extend(validation_result.feedback_messages)
278
+ # Track the best candidate across all attempts
279
+ best_result = result
280
+ best_score = -1.0
281
+
282
+ # Weight threshold: validators above this are tagged [CRITICAL]
283
+ critical_threshold = 10.0
284
+
285
+ for attempt in range(1, max_validation_attempts + 1):
286
+ score, weighted_feedback = self._score_result(result, validators, context)
287
+
288
+ logger.info(
289
+ f"[Validation] Attempt {attempt}/{max_validation_attempts} "
290
+ f"score: {score}/{max_possible_score} "
291
+ f"({len(weighted_feedback)} issue(s))"
292
+ )
293
+
294
+ if score > best_score:
295
+ best_score = score
296
+ best_result = result
297
+
298
+ # Perfect score — return immediately
299
+ if score >= max_possible_score:
300
+ logger.info(f"[Validation] Perfect score on attempt {attempt}, returning result")
301
+ return result
250
302
 
251
- if not all_feedback:
252
- logger.info(f"[Validation] All validations passed on attempt {attempt + 1}")
253
- return result # All validations passed
303
+ # On the last attempt, don't retry — just fall through to return best
304
+ if attempt == max_validation_attempts:
305
+ logger.warning(
306
+ f"[Validation] Final attempt reached. Best score: {best_score}/{max_possible_score}. "
307
+ f"Returning best result."
308
+ )
309
+ break
310
+
311
+ # Build feedback prompt for the next attempt.
312
+ # Feedback is sorted by weight; high-weight items are tagged [CRITICAL].
313
+ feedback_lines: list[str] = []
314
+ for weight, msg in weighted_feedback:
315
+ tag = "CRITICAL" if weight >= critical_threshold else "Secondary"
316
+ feedback_lines.append(f"- [{tag}] {msg}")
254
317
 
255
- # Build feedback prompt using the prompt factory
256
318
  feedback_template = get_validation_feedback_message()
257
319
  feedback_prompt = feedback_template.format(
258
320
  original_output=result.llm_str(),
259
- feedback_list="\n".join(f"- {msg}" for msg in all_feedback),
321
+ feedback_list="\n".join(feedback_lines),
260
322
  original_prompt=prompt,
261
323
  )
262
324
 
263
325
  logger.info(
264
- f"[Validation] Retry {attempt + 1}/{max_validation_retries} with {len(all_feedback)} feedback items"
326
+ f"[Validation] Preparing attempt {attempt + 1}/{max_validation_attempts} "
327
+ f"with {len(weighted_feedback)} feedback items"
265
328
  )
266
329
  result = self._parse_invoke(feedback_prompt, return_type)
267
330
 
268
- return result
331
+ return best_result
269
332
 
270
333
  def _parse_response(self, prompt, response, return_type, max_retries=5, attempt=0):
271
334
  if attempt >= max_retries:
@@ -305,6 +368,9 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
305
368
  # try to parse with the json parser if possible
306
369
  logger.warning(f"IndexError while parsing response (attempt {attempt + 1}/{max_retries}): {e}")
307
370
  return self._parse_response(prompt, response, return_type, max_retries, attempt + 1)
371
+ except (json.JSONDecodeError, ValueError) as e:
372
+ logger.warning(f"Parse error (attempt {attempt + 1}/{max_retries}): {e}")
373
+ return self._parse_response(prompt, response, return_type, max_retries, attempt + 1)
308
374
  except ResourceExhausted as e:
309
375
  # Parsing uses exponential backoff for rate limits
310
376
  if attempt < max_retries - 1:
@@ -344,133 +410,3 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
344
410
  except:
345
411
  pass
346
412
  raise ValueError(f"Couldn't parse {message_content}")
347
-
348
- def classify_files(self, analysis: AnalysisInsights, cluster_results: dict, scope_files: list[str]) -> None:
349
- """
350
- Two-pass file assignment for AnalysisInsights:
351
- 1. Deterministic: assign files from cluster_ids and key_entities
352
- 2. LLM-based: classify remaining unassigned files
353
-
354
- Args:
355
- analysis: AnalysisInsights object to classify files for
356
- cluster_results: Dict mapping language -> ClusterResult (for the relevant scope)
357
- scope_files: List of file paths to limit classification scope.
358
-
359
- Requires self to be a mixin with ClusterMethodsMixin for helper methods.
360
- """
361
- for comp in analysis.components:
362
- # Deterministic assignment (uses mixin methods)
363
- self._assign_files_to_component(comp, cluster_results) # type: ignore[attr-defined]
364
- self._classify_unassigned_files_llm(analysis, scope_files)
365
- self._log_unclassified_files_count(analysis, scope_files)
366
-
367
- def _classify_unassigned_files_llm(self, analysis: AnalysisInsights, scope_files: list[str]) -> None:
368
- """
369
- Classify files from the scope files that weren't assigned to any component.
370
- Uses a single LLM call to classify all unassigned files.
371
- Args:
372
- analysis: AnalysisInsights object
373
- scope_files: List of file paths to limit classification scope.
374
- """
375
- # Get unassigned files using the helper method
376
- unassigned_files = self._get_unassigned_files(analysis, scope_files)
377
-
378
- if not unassigned_files:
379
- logger.info("[Agent] All files already assigned, skipping LLM classification")
380
- return
381
-
382
- logger.info(f"[Agent] Found {len(unassigned_files)} unassigned files, using LLM classification")
383
-
384
- # 4. Build component summary for LLM using llm_str()
385
- valid_components = [comp for comp in analysis.components if comp.name != "Unclassified"]
386
- components_summary = "\n\n".join(comp.llm_str() for comp in valid_components)
387
- component_map = {comp.name: comp for comp in valid_components}
388
-
389
- # 5. Classify all unassigned files with LLM
390
- classifications: list[FileClassification] = self._classify_unassigned_files_with_llm(
391
- unassigned_files, components_summary, analysis
392
- )
393
-
394
- # 6. Append successfully classified files to components
395
- for fc in classifications:
396
- if fc.component_name in component_map:
397
- comp = component_map[fc.component_name]
398
- if fc.file_path not in comp.assigned_files:
399
- comp.assigned_files.append(fc.file_path)
400
- logger.debug(f"[Agent] Assigned {fc.file_path} to {fc.component_name}")
401
- else:
402
- logger.warning(
403
- f"[Agent] Invalid component name '{fc.component_name}' for file {fc.file_path}, skipping"
404
- )
405
-
406
- logger.info(f"[Agent] File classification complete: {len(classifications)} files classified")
407
-
408
- def _get_unassigned_files(self, analysis: AnalysisInsights, scope_files: list[str]) -> list[str]:
409
- """
410
- Check which files remain unassigned after classification.
411
- Args:
412
- analysis: AnalysisInsights object with classified components
413
- scope_files: List of file paths to limit the scope.
414
- Returns:
415
- List of file paths that are still unassigned
416
- """
417
- # 1. Gather all assigned files
418
- assigned_files = set()
419
- for comp in analysis.components:
420
- for f in comp.assigned_files:
421
- abs_path = os.path.join(self.repo_dir, f) if not os.path.isabs(f) else f
422
- assigned_files.add(os.path.relpath(abs_path, self.repo_dir))
423
-
424
- # 2. Get files to consider for classification
425
- # If scope_files is provided (e.g., DetailsAgent), use those
426
- # Otherwise use all source files from static_analysis (e.g., AbstractionAgent)
427
- all_files = set()
428
- for file_path in scope_files:
429
- file_path_str = str(file_path)
430
- rel_path = os.path.relpath(file_path_str, self.repo_dir) if os.path.isabs(file_path_str) else file_path_str
431
- all_files.add(rel_path)
432
-
433
- # 3. Return unassigned files
434
- return sorted(all_files - assigned_files)
435
-
436
- def _log_unclassified_files_count(self, analysis: AnalysisInsights, scope_files: list[str]) -> None:
437
- """
438
- Log how many files remain unclassified within the analysis.
439
-
440
- Args:
441
- analysis: AnalysisInsights object with classified components
442
- scope_files: List of file paths which are expected to be within the analysis.
443
- """
444
- unassigned = self._get_unassigned_files(analysis, scope_files)
445
- if unassigned:
446
- logger.warning(f"[Agent] {len(unassigned)} files have not been classified successfully: {unassigned}")
447
- else:
448
- logger.info("[Agent] All files have been classified successfully")
449
-
450
- def _classify_unassigned_files_with_llm(
451
- self, unassigned_files: list[str], components_summary: str, analysis: AnalysisInsights
452
- ) -> list[FileClassification]:
453
- """
454
- Classify unassigned files using LLM with validation.
455
- Returns list of FileClassification objects.
456
- """
457
-
458
- prompt = PromptTemplate(
459
- template=get_unassigned_files_classification_message(), input_variables=["unassigned_files", "components"]
460
- ).format(unassigned_files="\n".join(unassigned_files), components=components_summary)
461
-
462
- # Get valid component names from the components_summary
463
- # Parse component names from the summary (components have format "**Component:** `ComponentName`")
464
- valid_component_names = set([comp.name for comp in analysis.components])
465
-
466
- # Build validation context
467
- context = ValidationContext(
468
- expected_files=set(unassigned_files),
469
- valid_component_names=valid_component_names,
470
- repo_dir=str(self.repo_dir),
471
- )
472
-
473
- file_classifications = self._validation_invoke(
474
- prompt, ComponentFiles, validators=[validate_file_classifications], context=context
475
- )
476
- return file_classifications.file_paths