codeboarding 0.9.6__tar.gz → 0.10.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codeboarding-0.9.6/codeboarding.egg-info → codeboarding-0.10.1}/PKG-INFO +1 -1
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/abstraction_agent.py +8 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/agent_responses.py +1 -1
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/cluster_methods_mixin.py +12 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/details_agent.py +8 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/prompts/claude_prompts.py +2 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/prompts/deepseek_prompts.py +2 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/prompts/gemini_flash_prompts.py +6 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/prompts/glm_prompts.py +2 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/prompts/gpt_prompts.py +2 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/prompts/kimi_prompts.py +2 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/validation.py +36 -60
- {codeboarding-0.9.6 → codeboarding-0.10.1/codeboarding.egg-info}/PKG-INFO +1 -1
- {codeboarding-0.9.6 → codeboarding-0.10.1}/codeboarding.egg-info/SOURCES.txt +22 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/diagram_analysis/analysis_json.py +5 -1
- {codeboarding-0.9.6 → codeboarding-0.10.1}/diagram_analysis/incremental_updater.py +27 -2
- {codeboarding-0.9.6 → codeboarding-0.10.1}/pyproject.toml +2 -2
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/__init__.py +44 -7
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/analysis_result.py +1 -1
- codeboarding-0.10.1/static_analyzer/cluster_helpers.py +464 -0
- codeboarding-0.10.1/static_analyzer/engine/__init__.py +27 -0
- codeboarding-0.10.1/static_analyzer/engine/adapters/__init__.py +32 -0
- codeboarding-0.10.1/static_analyzer/engine/adapters/go_adapter.py +207 -0
- codeboarding-0.10.1/static_analyzer/engine/adapters/java_adapter.py +295 -0
- codeboarding-0.10.1/static_analyzer/engine/adapters/php_adapter.py +50 -0
- codeboarding-0.10.1/static_analyzer/engine/adapters/python_adapter.py +56 -0
- codeboarding-0.10.1/static_analyzer/engine/adapters/typescript_adapter.py +51 -0
- codeboarding-0.10.1/static_analyzer/engine/call_graph_builder.py +292 -0
- codeboarding-0.10.1/static_analyzer/engine/edge_build_context.py +18 -0
- codeboarding-0.10.1/static_analyzer/engine/edge_builder.py +506 -0
- codeboarding-0.10.1/static_analyzer/engine/hierarchy_builder.py +200 -0
- codeboarding-0.10.1/static_analyzer/engine/language_adapter.py +271 -0
- codeboarding-0.10.1/static_analyzer/engine/lsp_client.py +706 -0
- codeboarding-0.10.1/static_analyzer/engine/lsp_constants.py +33 -0
- codeboarding-0.10.1/static_analyzer/engine/models.py +101 -0
- codeboarding-0.10.1/static_analyzer/engine/progress.py +83 -0
- codeboarding-0.10.1/static_analyzer/engine/protocols.py +48 -0
- codeboarding-0.10.1/static_analyzer/engine/result_converter.py +133 -0
- codeboarding-0.10.1/static_analyzer/engine/source_inspector.py +203 -0
- codeboarding-0.10.1/static_analyzer/engine/symbol_table.py +328 -0
- codeboarding-0.10.1/static_analyzer/engine/utils.py +22 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/graph.py +3 -7
- codeboarding-0.10.1/tests/test_pyproject_packages.py +52 -0
- codeboarding-0.9.6/static_analyzer/cluster_helpers.py +0 -79
- {codeboarding-0.9.6 → codeboarding-0.10.1}/LICENSE +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/PYPI.md +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/README.md +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/agent.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/change_status.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/constants.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/dependency_discovery.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/llm_config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/meta_agent.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/planner_agent.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/prompts/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/prompts/abstract_prompt_factory.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/prompts/prompt_factory.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/base.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/get_external_deps.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/get_method_invocations.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/read_cfg.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/read_docs.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/read_file.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/read_file_structure.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/read_git_diff.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/read_packages.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/read_source.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/read_structure.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/agents/tools/toolkit.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/caching/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/caching/cache.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/caching/details_cache.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/caching/meta_cache.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/codeboarding.egg-info/dependency_links.txt +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/codeboarding.egg-info/entry_points.txt +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/codeboarding.egg-info/requires.txt +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/codeboarding.egg-info/top_level.txt +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/constants.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/core/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/core/plugin_loader.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/core/protocols.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/core/registry.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/diagram_analysis/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/diagram_analysis/diagram_generator.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/diagram_analysis/file_coverage.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/diagram_analysis/incremental_types.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/diagram_analysis/io_utils.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/diagram_analysis/manifest.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/diagram_analysis/run_context.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/diagram_analysis/version.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/duckdb_crud.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/github_action.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/checks/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/checks/circular_deps.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/checks/cohesion.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/checks/coupling.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/checks/function_size.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/checks/god_class.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/checks/inheritance.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/checks/instability.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/checks/unused_code_diagnostics.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/constants.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/models.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health/runner.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/health_main.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/install.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/logging_config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/main.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/monitoring/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/monitoring/callbacks.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/monitoring/context.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/monitoring/mixin.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/monitoring/paths.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/monitoring/stats.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/monitoring/writers.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/output_generators/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/output_generators/html.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/output_generators/html_template.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/output_generators/markdown.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/output_generators/mdx.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/output_generators/sphinx.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/repo_utils/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/repo_utils/change_detector.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/repo_utils/errors.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/repo_utils/git_diff.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/repo_utils/ignore.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/repo_utils/method_diff.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/setup.cfg +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/analysis_cache.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/cluster_change_analyzer.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/cluster_relations.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/constants.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/git_diff_analyzer.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/incremental_orchestrator.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/java_config_scanner.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/java_utils.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/lsp_client/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/lsp_client/diagnostics.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/node.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/programming_language.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/reference_resolve_mixin.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/scanner.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/static_analyzer/typescript_config_scanner.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/tests/test_github_action.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/tests/test_install.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/tests/test_logging_config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/tests/test_main.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/tests/test_tool_registry.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/tests/test_user_config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/tests/test_vscode_constants.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/tests/test_windows_compatibility.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/tests/test_windows_encoding.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/tool_registry.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/user_config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/utils.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.1}/vscode_constants.py +0 -0
|
@@ -103,6 +103,8 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
|
|
|
103
103
|
|
|
104
104
|
cluster_str = llm_cluster_analysis.llm_str() if llm_cluster_analysis else "No cluster analysis available."
|
|
105
105
|
|
|
106
|
+
group_names = [cc.name for cc in llm_cluster_analysis.cluster_components] if llm_cluster_analysis else []
|
|
107
|
+
|
|
106
108
|
prompt = self.prompts["final_analysis"].format(
|
|
107
109
|
project_name=self.project_name,
|
|
108
110
|
cluster_analysis=cluster_str,
|
|
@@ -110,6 +112,12 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
|
|
|
110
112
|
project_type=project_type,
|
|
111
113
|
)
|
|
112
114
|
|
|
115
|
+
if group_names:
|
|
116
|
+
prompt += (
|
|
117
|
+
f"\n\n## All Group Names ({len(group_names)} total)\n"
|
|
118
|
+
f"Every one of these names must appear in exactly one component's source_group_names: {group_names}\n"
|
|
119
|
+
)
|
|
120
|
+
|
|
113
121
|
# Build validation context with CFG graphs for edge checking
|
|
114
122
|
context = ValidationContext(
|
|
115
123
|
cluster_results=cluster_results,
|
|
@@ -111,7 +111,7 @@ class ClustersComponent(LLMBaseModel):
|
|
|
111
111
|
description="List of cluster IDs from the CFG analysis that are grouped together (e.g., [1, 3, 5])"
|
|
112
112
|
)
|
|
113
113
|
description: str = Field(
|
|
114
|
-
description="Explanation of what this component does, its main flow, WHY these clusters are grouped together,
|
|
114
|
+
description="Explanation of what this component does, its main flow, WHY these clusters are grouped together, how it interacts with other cluster groups, and the most important classes/methods (by their exact qualified names from the clusters)"
|
|
115
115
|
)
|
|
116
116
|
|
|
117
117
|
def llm_str(self):
|
|
@@ -71,6 +71,7 @@ class ClusterMethodsMixin:
|
|
|
71
71
|
Formatted cluster string with headers per language
|
|
72
72
|
"""
|
|
73
73
|
cluster_lines = []
|
|
74
|
+
all_cluster_ids: set[int] = set()
|
|
74
75
|
|
|
75
76
|
for lang in programming_langs:
|
|
76
77
|
cfg = self.static_analysis.get_cfg(lang)
|
|
@@ -83,6 +84,17 @@ class ClusterMethodsMixin:
|
|
|
83
84
|
cluster_lines.append(f"\n## {lang.capitalize()} - {header}\n")
|
|
84
85
|
cluster_lines.append(cluster_str)
|
|
85
86
|
cluster_lines.append("\n")
|
|
87
|
+
if cluster_result:
|
|
88
|
+
lang_ids = cluster_ids if cluster_ids else cluster_result.get_cluster_ids()
|
|
89
|
+
all_cluster_ids.update(lang_ids)
|
|
90
|
+
|
|
91
|
+
# Add explicit ID checklist so the LLM knows exactly which IDs to assign
|
|
92
|
+
if all_cluster_ids and not cluster_ids:
|
|
93
|
+
sorted_cluster_ids = sorted(all_cluster_ids)
|
|
94
|
+
cluster_lines.append(
|
|
95
|
+
f"\n## All Cluster IDs ({len(sorted_cluster_ids)} total)\n"
|
|
96
|
+
f"Every one of these IDs: {sorted_cluster_ids} must appear in exactly one group."
|
|
97
|
+
)
|
|
86
98
|
|
|
87
99
|
return "".join(cluster_lines)
|
|
88
100
|
|
|
@@ -138,6 +138,8 @@ class DetailsAgent(ClusterMethodsMixin, CodeBoardingAgent):
|
|
|
138
138
|
|
|
139
139
|
cluster_str = cluster_analysis.llm_str() if cluster_analysis else "No cluster analysis available."
|
|
140
140
|
|
|
141
|
+
group_names = [cc.name for cc in cluster_analysis.cluster_components] if cluster_analysis else []
|
|
142
|
+
|
|
141
143
|
prompt = self.prompts["final_analysis"].format(
|
|
142
144
|
project_name=self.project_name,
|
|
143
145
|
cluster_analysis=cluster_str,
|
|
@@ -146,6 +148,12 @@ class DetailsAgent(ClusterMethodsMixin, CodeBoardingAgent):
|
|
|
146
148
|
project_type=project_type,
|
|
147
149
|
)
|
|
148
150
|
|
|
151
|
+
if group_names:
|
|
152
|
+
prompt += (
|
|
153
|
+
f"\n\n## All Group Names ({len(group_names)} total)\n"
|
|
154
|
+
f"Every one of these names: {group_names} must appear in exactly one component's source_group_names\n"
|
|
155
|
+
)
|
|
156
|
+
|
|
149
157
|
# Build validation context with subgraph CFG graphs for edge checking
|
|
150
158
|
context = ValidationContext(
|
|
151
159
|
cluster_results=subgraph_cluster_results,
|
|
@@ -67,6 +67,7 @@ Instructions:
|
|
|
67
67
|
* What is its main flow/purpose
|
|
68
68
|
* WHY these specific clusters are grouped together (provide clear rationale for the grouping decision)
|
|
69
69
|
* How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
|
|
70
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
70
71
|
|
|
71
72
|
Focus on:
|
|
72
73
|
- Creating cohesive, logical groupings that reflect the actual {project_type} architecture
|
|
@@ -294,6 +295,7 @@ Instructions:
|
|
|
294
295
|
* What is its main flow/purpose
|
|
295
296
|
* WHY these specific clusters are grouped together (provide clear rationale)
|
|
296
297
|
* How this group interacts with other cluster groups
|
|
298
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
297
299
|
|
|
298
300
|
Focus on core subsystem functionality only. Avoid cross-cutting concerns like logging or error handling.
|
|
299
301
|
|
|
@@ -63,6 +63,7 @@ The CFG has been pre-clustered into groups of related methods/functions. Each cl
|
|
|
63
63
|
* What is its main flow/purpose
|
|
64
64
|
* WHY these specific clusters are grouped together (provide clear rationale for the grouping decision)
|
|
65
65
|
* How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
|
|
66
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
66
67
|
|
|
67
68
|
# Focus areas
|
|
68
69
|
- Create cohesive, logical groupings that reflect the actual {project_type} architecture
|
|
@@ -296,6 +297,7 @@ The CFG has been pre-clustered into groups of related methods/functions. Each cl
|
|
|
296
297
|
* What is its main flow/purpose
|
|
297
298
|
* WHY these specific clusters are grouped together (provide clear rationale)
|
|
298
299
|
* How this group interacts with other cluster groups
|
|
300
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
299
301
|
|
|
300
302
|
# Focus
|
|
301
303
|
Analyze core subsystem functionality only. Avoid cross-cutting concerns like logging or error handling.
|
|
@@ -49,6 +49,10 @@ CFG Clusters:
|
|
|
49
49
|
Your Task:
|
|
50
50
|
GROUP similar clusters together into logical components based on their relationships and purpose.
|
|
51
51
|
|
|
52
|
+
CRITICAL requirements:
|
|
53
|
+
- Every cluster ID must be included in exactly one group — no cluster left out, no duplicates
|
|
54
|
+
- Each group must represent a coherent architectural concept justified by method names, call patterns, and inter-cluster connections
|
|
55
|
+
|
|
52
56
|
Instructions:
|
|
53
57
|
1. Analyze the clusters shown above and identify which ones work together or are functionally related
|
|
54
58
|
2. Group related clusters into meaningful components
|
|
@@ -61,6 +65,7 @@ Instructions:
|
|
|
61
65
|
* What is its main flow/purpose
|
|
62
66
|
* WHY these specific clusters are grouped together (provide clear rationale for the grouping decision)
|
|
63
67
|
* How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
|
|
68
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
64
69
|
|
|
65
70
|
Focus on:
|
|
66
71
|
- Creating cohesive, logical groupings that reflect the actual {project_type} architecture
|
|
@@ -275,6 +280,7 @@ Instructions:
|
|
|
275
280
|
* What is its main flow/purpose
|
|
276
281
|
* WHY these specific clusters are grouped together (provide clear rationale)
|
|
277
282
|
* How this group interacts with other cluster groups
|
|
283
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
278
284
|
|
|
279
285
|
Focus on core subsystem functionality only. Avoid cross-cutting concerns like logging or error handling.
|
|
280
286
|
|
|
@@ -67,6 +67,7 @@ REQUIRED STEPS (execute in order):
|
|
|
67
67
|
* What is its main flow/purpose
|
|
68
68
|
* WHY these specific clusters are grouped together (MUST provide clear rationale)
|
|
69
69
|
* How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
|
|
70
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
70
71
|
|
|
71
72
|
FOCUS AREAS (prioritize):
|
|
72
73
|
- Create cohesive, logical groupings that reflect the actual {project_type} architecture
|
|
@@ -313,6 +314,7 @@ REQUIRED STEPS (execute in order):
|
|
|
313
314
|
* What is its main flow/purpose
|
|
314
315
|
* WHY these specific clusters are grouped together (MUST provide clear rationale)
|
|
315
316
|
* How this group interacts with other cluster groups
|
|
317
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
316
318
|
|
|
317
319
|
FOCUS:
|
|
318
320
|
MUST analyze core subsystem functionality only. STRICTLY avoid cross-cutting concerns like logging or error handling.
|
|
@@ -69,6 +69,7 @@ Instructions:
|
|
|
69
69
|
* What is its main flow/purpose
|
|
70
70
|
* WHY these specific clusters are grouped together (provide clear rationale for the grouping decision)
|
|
71
71
|
* How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
|
|
72
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
72
73
|
|
|
73
74
|
Focus on:
|
|
74
75
|
- Creating cohesive, logical groupings that reflect the actual {project_type} architecture
|
|
@@ -383,6 +384,7 @@ Instructions:
|
|
|
383
384
|
* What is its main flow/purpose
|
|
384
385
|
* WHY these specific clusters are grouped together (provide clear rationale)
|
|
385
386
|
* How this group interacts with other cluster groups
|
|
387
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
386
388
|
|
|
387
389
|
Focus on core subsystem functionality only. Avoid cross-cutting concerns like logging or error handling.
|
|
388
390
|
|
|
@@ -62,6 +62,7 @@ Reason carefully, then execute:
|
|
|
62
62
|
* What is its main flow/purpose
|
|
63
63
|
* WHY these specific clusters are grouped together (provide clear rationale for the grouping decision)
|
|
64
64
|
* How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
|
|
65
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
65
66
|
|
|
66
67
|
Focus on creating cohesive, logical groupings that reflect the actual {project_type} architecture based on semantic meaning from method names, call patterns, and architectural context. Describe inter-group interactions based on the inter-cluster connections.
|
|
67
68
|
|
|
@@ -303,6 +304,7 @@ Reason carefully, then execute:
|
|
|
303
304
|
* What is its main flow/purpose
|
|
304
305
|
* WHY these specific clusters are grouped together (provide clear rationale)
|
|
305
306
|
* How this group interacts with other cluster groups
|
|
307
|
+
* The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
|
|
306
308
|
|
|
307
309
|
Focus on core subsystem functionality only. Avoid cross-cutting concerns like logging or error handling.
|
|
308
310
|
|
|
@@ -322,11 +322,9 @@ def validate_group_name_coverage(result: AnalysisInsights, context: ValidationCo
|
|
|
322
322
|
def validate_key_entities(result: AnalysisInsights, context: ValidationContext) -> ValidationResult:
|
|
323
323
|
"""
|
|
324
324
|
Validate key_entities on every component:
|
|
325
|
-
1.
|
|
326
|
-
2.
|
|
327
|
-
|
|
328
|
-
Otherwise, validates that qualified names exist in the static analysis.
|
|
329
|
-
Uses loose matching to auto-correct shortened paths in-place.
|
|
325
|
+
1. Auto-correct qualified names via loose matching.
|
|
326
|
+
2. Silently drop invalid key entities (out of scope or not found).
|
|
327
|
+
3. Only fail if a component ends up with zero key_entities after dropping.
|
|
330
328
|
|
|
331
329
|
Args:
|
|
332
330
|
result: AnalysisInsights containing components with key_entities
|
|
@@ -334,26 +332,8 @@ def validate_key_entities(result: AnalysisInsights, context: ValidationContext)
|
|
|
334
332
|
and cluster_results for scope validation
|
|
335
333
|
|
|
336
334
|
Returns:
|
|
337
|
-
ValidationResult with feedback for
|
|
335
|
+
ValidationResult with feedback only for components left with zero key entities
|
|
338
336
|
"""
|
|
339
|
-
feedback_messages: list[str] = []
|
|
340
|
-
|
|
341
|
-
# Check 1: components without any key_entities
|
|
342
|
-
components_without_key_entities = [c.name for c in result.components if not c.key_entities]
|
|
343
|
-
if components_without_key_entities:
|
|
344
|
-
missing_str = ", ".join(components_without_key_entities)
|
|
345
|
-
feedback_messages.append(
|
|
346
|
-
f"The following components are missing key entities: {missing_str}. "
|
|
347
|
-
f"Every component must have at least one key entity (critical class or method) "
|
|
348
|
-
f"that represents its core functionality. Please identify and add 2-5 key entities "
|
|
349
|
-
f"for each component."
|
|
350
|
-
)
|
|
351
|
-
logger.warning(f"[Validation] Components without key entities: {missing_str}")
|
|
352
|
-
|
|
353
|
-
# Check 2: Unified scope/existence check — key entities must be valid for this component.
|
|
354
|
-
# Auto-correct via loose matching first (fixes typos in qualified names).
|
|
355
|
-
# Then validate against cluster scope (strict) or static analysis existence (fallback).
|
|
356
|
-
invalid_entities: list[str] = []
|
|
357
337
|
auto_corrected = 0
|
|
358
338
|
|
|
359
339
|
# Auto-correct qualified names via loose matching (always, when static_analysis available)
|
|
@@ -362,13 +342,11 @@ def validate_key_entities(result: AnalysisInsights, context: ValidationContext)
|
|
|
362
342
|
for key_entity in component.key_entities:
|
|
363
343
|
qname = key_entity.qualified_name.replace("/", ".")
|
|
364
344
|
for lang in context.static_analysis.get_languages():
|
|
365
|
-
# Exact / case-insensitive match
|
|
366
345
|
try:
|
|
367
346
|
context.static_analysis.get_reference(lang, qname)
|
|
368
347
|
break
|
|
369
348
|
except (ValueError, FileExistsError):
|
|
370
349
|
pass
|
|
371
|
-
# Loose match – auto-correct the qualified name in-place
|
|
372
350
|
_text, loose_node = context.static_analysis.get_loose_reference(lang, qname)
|
|
373
351
|
if loose_node is not None:
|
|
374
352
|
logger.info(
|
|
@@ -381,7 +359,8 @@ def validate_key_entities(result: AnalysisInsights, context: ValidationContext)
|
|
|
381
359
|
if auto_corrected:
|
|
382
360
|
logger.info(f"[Validation] Auto-corrected {auto_corrected} qualified names via loose matching")
|
|
383
361
|
|
|
384
|
-
#
|
|
362
|
+
# Silently drop invalid key entities
|
|
363
|
+
dropped = 0
|
|
385
364
|
if context.cluster_results:
|
|
386
365
|
nodes_in_scope: set[str] = set()
|
|
387
366
|
for cr in context.cluster_results.values():
|
|
@@ -389,35 +368,24 @@ def validate_key_entities(result: AnalysisInsights, context: ValidationContext)
|
|
|
389
368
|
nodes_in_scope.update(members)
|
|
390
369
|
|
|
391
370
|
for component in result.components:
|
|
371
|
+
valid = []
|
|
392
372
|
for key_entity in component.key_entities:
|
|
393
373
|
qname = key_entity.qualified_name
|
|
394
374
|
in_scope = qname in nodes_in_scope
|
|
395
375
|
if not in_scope:
|
|
396
376
|
for scope_node in nodes_in_scope:
|
|
397
|
-
if qname.startswith(scope_node + "."):
|
|
377
|
+
if qname.startswith(scope_node + ".") or scope_node.startswith(qname + "."):
|
|
398
378
|
in_scope = True
|
|
399
379
|
break
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if not in_scope:
|
|
406
|
-
invalid_entities.append(f"{component.name}: '{qname}'")
|
|
407
|
-
|
|
408
|
-
if invalid_entities:
|
|
409
|
-
invalid_str = "; ".join(invalid_entities[:10])
|
|
410
|
-
more_msg = f" and {len(invalid_entities) - 10} more" if len(invalid_entities) > 10 else ""
|
|
411
|
-
feedback_messages.append(
|
|
412
|
-
f"The following key_entities are outside the component's cluster scope: {invalid_str}{more_msg}. "
|
|
413
|
-
f"Key entities must reference code that is within the component's assigned clusters. "
|
|
414
|
-
f"Please choose key entities from the code shown in the cluster analysis."
|
|
415
|
-
)
|
|
416
|
-
logger.warning(f"[Validation] Invalid key entities (out of scope): {len(invalid_entities)} found")
|
|
380
|
+
if in_scope:
|
|
381
|
+
valid.append(key_entity)
|
|
382
|
+
else:
|
|
383
|
+
dropped += 1
|
|
384
|
+
component.key_entities = valid
|
|
417
385
|
|
|
418
386
|
elif context.static_analysis:
|
|
419
|
-
# No cluster_results — fall back to checking if qualified names exist in static analysis
|
|
420
387
|
for component in result.components:
|
|
388
|
+
valid = []
|
|
421
389
|
for key_entity in component.key_entities:
|
|
422
390
|
qname = key_entity.qualified_name.replace("/", ".")
|
|
423
391
|
found = False
|
|
@@ -433,23 +401,31 @@ def validate_key_entities(result: AnalysisInsights, context: ValidationContext)
|
|
|
433
401
|
key_entity.qualified_name = loose_node.fully_qualified_name
|
|
434
402
|
found = True
|
|
435
403
|
break
|
|
436
|
-
if
|
|
437
|
-
|
|
404
|
+
if found:
|
|
405
|
+
valid.append(key_entity)
|
|
406
|
+
else:
|
|
407
|
+
dropped += 1
|
|
408
|
+
component.key_entities = valid
|
|
438
409
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
more_msg = f" and {len(invalid_entities) - 10} more" if len(invalid_entities) > 10 else ""
|
|
442
|
-
feedback_messages.append(
|
|
443
|
-
f"The following qualified names do not exist in the static analysis: {invalid_str}{more_msg}. "
|
|
444
|
-
f"Please ensure all key_entities use qualified names that were found during static analysis."
|
|
445
|
-
)
|
|
446
|
-
logger.warning(f"[Validation] Invalid key entities (not found): {len(invalid_entities)} found")
|
|
410
|
+
if dropped:
|
|
411
|
+
logger.info(f"[Validation] Silently dropped {dropped} invalid key entities")
|
|
447
412
|
|
|
448
|
-
if
|
|
449
|
-
|
|
450
|
-
|
|
413
|
+
# Only fail if any component ended up with zero key_entities
|
|
414
|
+
empty_components = [c.name for c in result.components if not c.key_entities]
|
|
415
|
+
if empty_components:
|
|
416
|
+
missing_str = ", ".join(empty_components)
|
|
417
|
+
logger.warning(f"[Validation] Components with no valid key entities after cleanup: {missing_str}")
|
|
418
|
+
return ValidationResult(
|
|
419
|
+
is_valid=False,
|
|
420
|
+
feedback_messages=[
|
|
421
|
+
f"The following components have no valid key entities: {missing_str}. "
|
|
422
|
+
f"Every component must have at least one key entity (critical class or method) "
|
|
423
|
+
f"that represents its core functionality. Use exact qualified names from the cluster analysis."
|
|
424
|
+
],
|
|
425
|
+
)
|
|
451
426
|
|
|
452
|
-
|
|
427
|
+
logger.info("[Validation] All key entities are valid")
|
|
428
|
+
return ValidationResult(is_valid=True)
|
|
453
429
|
|
|
454
430
|
|
|
455
431
|
def validate_file_classifications(result: ComponentFiles, context: ValidationContext) -> ValidationResult:
|
|
@@ -122,12 +122,34 @@ static_analyzer/programming_language.py
|
|
|
122
122
|
static_analyzer/reference_resolve_mixin.py
|
|
123
123
|
static_analyzer/scanner.py
|
|
124
124
|
static_analyzer/typescript_config_scanner.py
|
|
125
|
+
static_analyzer/engine/__init__.py
|
|
126
|
+
static_analyzer/engine/call_graph_builder.py
|
|
127
|
+
static_analyzer/engine/edge_build_context.py
|
|
128
|
+
static_analyzer/engine/edge_builder.py
|
|
129
|
+
static_analyzer/engine/hierarchy_builder.py
|
|
130
|
+
static_analyzer/engine/language_adapter.py
|
|
131
|
+
static_analyzer/engine/lsp_client.py
|
|
132
|
+
static_analyzer/engine/lsp_constants.py
|
|
133
|
+
static_analyzer/engine/models.py
|
|
134
|
+
static_analyzer/engine/progress.py
|
|
135
|
+
static_analyzer/engine/protocols.py
|
|
136
|
+
static_analyzer/engine/result_converter.py
|
|
137
|
+
static_analyzer/engine/source_inspector.py
|
|
138
|
+
static_analyzer/engine/symbol_table.py
|
|
139
|
+
static_analyzer/engine/utils.py
|
|
140
|
+
static_analyzer/engine/adapters/__init__.py
|
|
141
|
+
static_analyzer/engine/adapters/go_adapter.py
|
|
142
|
+
static_analyzer/engine/adapters/java_adapter.py
|
|
143
|
+
static_analyzer/engine/adapters/php_adapter.py
|
|
144
|
+
static_analyzer/engine/adapters/python_adapter.py
|
|
145
|
+
static_analyzer/engine/adapters/typescript_adapter.py
|
|
125
146
|
static_analyzer/lsp_client/__init__.py
|
|
126
147
|
static_analyzer/lsp_client/diagnostics.py
|
|
127
148
|
tests/test_github_action.py
|
|
128
149
|
tests/test_install.py
|
|
129
150
|
tests/test_logging_config.py
|
|
130
151
|
tests/test_main.py
|
|
152
|
+
tests/test_pyproject_packages.py
|
|
131
153
|
tests/test_tool_registry.py
|
|
132
154
|
tests/test_user_config.py
|
|
133
155
|
tests/test_vscode_constants.py
|
|
@@ -80,6 +80,7 @@ def _hydrate_component_methods_from_refs(
|
|
|
80
80
|
files_index: dict[str, FileEntry],
|
|
81
81
|
methods_index: dict[str, "MethodIndexEntry"],
|
|
82
82
|
) -> None:
|
|
83
|
+
missing: list[str] = []
|
|
83
84
|
for component in analysis.components:
|
|
84
85
|
rebuilt: list[FileMethodGroup] = []
|
|
85
86
|
for group in component.file_methods:
|
|
@@ -91,7 +92,7 @@ def _hydrate_component_methods_from_refs(
|
|
|
91
92
|
qname = _to_method_qualified_name(method)
|
|
92
93
|
indexed = methods_index.get(_method_key(file_path, qname))
|
|
93
94
|
if indexed is None:
|
|
94
|
-
|
|
95
|
+
missing.append(f"{file_path}|{qname}")
|
|
95
96
|
continue
|
|
96
97
|
methods.append(
|
|
97
98
|
MethodEntry(
|
|
@@ -108,6 +109,9 @@ def _hydrate_component_methods_from_refs(
|
|
|
108
109
|
|
|
109
110
|
component.file_methods = rebuilt
|
|
110
111
|
|
|
112
|
+
if missing:
|
|
113
|
+
logger.warning("Missing method index entry for %d ref(s): %s", len(missing), missing)
|
|
114
|
+
|
|
111
115
|
|
|
112
116
|
class RelationJson(Relation):
|
|
113
117
|
"""Relation subclass that includes src_id/dst_id and static analysis evidence in JSON serialization."""
|
|
@@ -10,6 +10,7 @@ NOTE: Method status assignment is delegated to repo_utils.method_diff to ensure
|
|
|
10
10
|
consistent behavior between the main analysis pipeline and incremental updates.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
import logging
|
|
13
14
|
import time
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import Callable
|
|
@@ -21,6 +22,8 @@ from diagram_analysis.manifest import AnalysisManifest
|
|
|
21
22
|
from repo_utils.change_detector import ChangeSet
|
|
22
23
|
from repo_utils.method_diff import get_method_statuses_for_file
|
|
23
24
|
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
24
27
|
# Callable that resolves a repo-relative file path to its current MethodEntry list.
|
|
25
28
|
SymbolResolver = Callable[[str], list[MethodEntry]]
|
|
26
29
|
|
|
@@ -124,7 +127,29 @@ class IncrementalUpdater:
|
|
|
124
127
|
|
|
125
128
|
# Modified
|
|
126
129
|
prev_active = self._get_previous_active_methods(file_path)
|
|
127
|
-
|
|
130
|
+
try:
|
|
131
|
+
current = self._get_current_methods(file_path)
|
|
132
|
+
except Exception as exc:
|
|
133
|
+
logger.warning("Symbol resolution failed for %s: %s", file_path, exc)
|
|
134
|
+
current = []
|
|
135
|
+
|
|
136
|
+
if not current and prev_active:
|
|
137
|
+
logger.warning(
|
|
138
|
+
"Symbol resolution returned no methods for %s; marking all existing methods as modified",
|
|
139
|
+
file_path,
|
|
140
|
+
)
|
|
141
|
+
return (
|
|
142
|
+
FileDelta(
|
|
143
|
+
file_path=file_path,
|
|
144
|
+
file_status=ChangeStatus.MODIFIED,
|
|
145
|
+
component_id=component_id,
|
|
146
|
+
modified_methods=[
|
|
147
|
+
self._to_method_change(file_path, m, change_type=ChangeStatus.MODIFIED)
|
|
148
|
+
for _, m in sorted(prev_active.items())
|
|
149
|
+
],
|
|
150
|
+
),
|
|
151
|
+
missing,
|
|
152
|
+
)
|
|
128
153
|
current_by_name = {m.qualified_name: m for m in current}
|
|
129
154
|
|
|
130
155
|
prev_keys = set(prev_active.keys())
|
|
@@ -138,7 +163,7 @@ class IncrementalUpdater:
|
|
|
138
163
|
file_status=ChangeStatus.MODIFIED,
|
|
139
164
|
component_id=component_id,
|
|
140
165
|
added_methods=[
|
|
141
|
-
self._to_method_change(file_path, m)
|
|
166
|
+
self._to_method_change(file_path, m, change_type=ChangeStatus.ADDED)
|
|
142
167
|
for m in current
|
|
143
168
|
if m.qualified_name in current_keys - prev_keys
|
|
144
169
|
],
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codeboarding"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.10.1"
|
|
8
8
|
description = "Interactive Diagrams for Code"
|
|
9
9
|
readme = "PYPI.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -62,7 +62,7 @@ all = [
|
|
|
62
62
|
]
|
|
63
63
|
|
|
64
64
|
[tool.setuptools]
|
|
65
|
-
packages = ["agents", "agents.prompts", "agents.tools", "core", "diagram_analysis", "health", "health.checks", "monitoring", "output_generators", "repo_utils", "static_analyzer", "static_analyzer.lsp_client", "caching"]
|
|
65
|
+
packages = ["agents", "agents.prompts", "agents.tools", "core", "diagram_analysis", "health", "health.checks", "monitoring", "output_generators", "repo_utils", "static_analyzer", "static_analyzer.engine", "static_analyzer.engine.adapters", "static_analyzer.lsp_client", "caching"]
|
|
66
66
|
py-modules = ["constants", "utils", "vscode_constants", "logging_config", "duckdb_crud", "github_action", "tool_registry", "user_config", "main", "install"]
|
|
67
67
|
include-package-data = true
|
|
68
68
|
|
|
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from repo_utils import get_git_commit_hash
|
|
6
6
|
from repo_utils.ignore import RepoIgnoreManager
|
|
7
7
|
from static_analyzer.analysis_cache import AnalysisCacheManager
|
|
8
|
-
from static_analyzer.analysis_result import
|
|
8
|
+
from static_analyzer.analysis_result import StaticAnalysisCache, StaticAnalysisResults
|
|
9
9
|
from static_analyzer.cluster_change_analyzer import ChangeClassification
|
|
10
10
|
from static_analyzer.constants import Language
|
|
11
11
|
from static_analyzer.engine.adapters import get_adapter
|
|
@@ -194,6 +194,46 @@ class StaticAnalyzer:
|
|
|
194
194
|
self._clients_started = False
|
|
195
195
|
self._cached_results = None
|
|
196
196
|
|
|
197
|
+
def collect_fresh_diagnostics(self) -> dict[str, FileDiagnosticsMap]:
|
|
198
|
+
"""Read current diagnostics from all running LSP clients without re-analyzing.
|
|
199
|
+
|
|
200
|
+
The LSP servers accumulate ``textDocument/publishDiagnostics`` notifications
|
|
201
|
+
automatically after ``didChange``. This method reads the collected
|
|
202
|
+
diagnostics without triggering any new analysis work.
|
|
203
|
+
"""
|
|
204
|
+
result: dict[str, FileDiagnosticsMap] = {}
|
|
205
|
+
for adapter, _, client in self._engine_clients:
|
|
206
|
+
diags = client.get_collected_diagnostics()
|
|
207
|
+
if diags:
|
|
208
|
+
result[adapter.language] = diags
|
|
209
|
+
return result
|
|
210
|
+
|
|
211
|
+
def get_diagnostics_generation(self) -> int:
|
|
212
|
+
"""Return the sum of diagnostics generation counters across all LSP clients."""
|
|
213
|
+
return sum(client.get_diagnostics_generation() for _, _, client in self._engine_clients)
|
|
214
|
+
|
|
215
|
+
def load_from_disk_cache(self, cache_dir: Path | None = None) -> StaticAnalysisResults | None:
|
|
216
|
+
"""Load structural analysis results from the on-disk cache.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
cache_dir: Optional cache directory to load from. If None, uses the default
|
|
220
|
+
cache directory for this repository.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Cached StaticAnalysisResults if found, None otherwise.
|
|
224
|
+
Sets ``_cached_results`` so subsequent calls are free.
|
|
225
|
+
"""
|
|
226
|
+
if self._cached_results is not None:
|
|
227
|
+
return self._cached_results
|
|
228
|
+
|
|
229
|
+
load_dir = Path(cache_dir) if cache_dir is not None else get_cache_dir(self.repository_path)
|
|
230
|
+
static_analysis_cache = StaticAnalysisCache(load_dir, self.repository_path)
|
|
231
|
+
cached_results = static_analysis_cache.get("static_analysis_results")
|
|
232
|
+
if cached_results is not None:
|
|
233
|
+
self._cached_results = cached_results
|
|
234
|
+
self.collected_diagnostics = cached_results.diagnostics
|
|
235
|
+
return cached_results
|
|
236
|
+
|
|
197
237
|
def notify_file_changed(self, file_path: Path, content: str) -> None:
|
|
198
238
|
"""Notify the LSP server that the editor has saved new content for a file.
|
|
199
239
|
|
|
@@ -276,8 +316,7 @@ class StaticAnalyzer:
|
|
|
276
316
|
# Try loading from disk cache (survives across processes)
|
|
277
317
|
if not skip_cache:
|
|
278
318
|
load_dir = Path(cache_dir) if cache_dir is not None else get_cache_dir(self.repository_path)
|
|
279
|
-
|
|
280
|
-
cached = disk_cache.get("static_analysis_results")
|
|
319
|
+
cached = self.load_from_disk_cache(load_dir)
|
|
281
320
|
if cached is not None:
|
|
282
321
|
langs = cached.get_languages()
|
|
283
322
|
file_count = len(cached.get_all_source_files())
|
|
@@ -285,8 +324,6 @@ class StaticAnalyzer:
|
|
|
285
324
|
f"Returning static analysis results from disk cache "
|
|
286
325
|
f"({file_count} files, languages: {', '.join(langs)})"
|
|
287
326
|
)
|
|
288
|
-
self._cached_results = cached
|
|
289
|
-
self.collected_diagnostics = cached.diagnostics
|
|
290
327
|
return cached
|
|
291
328
|
|
|
292
329
|
results = StaticAnalysisResults()
|
|
@@ -362,8 +399,8 @@ class StaticAnalyzer:
|
|
|
362
399
|
# Persist to disk so subprocess callers can reuse without LSP
|
|
363
400
|
save_dir = Path(cache_dir) if cache_dir is not None else get_cache_dir(self.repository_path)
|
|
364
401
|
logger.info(f"Saving static analysis results to disk cache at {save_dir}")
|
|
365
|
-
|
|
366
|
-
|
|
402
|
+
static_analysis_cache = StaticAnalysisCache(save_dir, self.repository_path)
|
|
403
|
+
static_analysis_cache.save("static_analysis_results", results)
|
|
367
404
|
|
|
368
405
|
return results
|
|
369
406
|
|
|
@@ -120,7 +120,7 @@ def _reference_key(fully_qualified_name: str) -> str:
|
|
|
120
120
|
return _strip_java_generics(result)
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
class
|
|
123
|
+
class StaticAnalysisCache:
|
|
124
124
|
def __init__(self, cache_dir: Path, repo_root: Path):
|
|
125
125
|
self.cache_dir = cache_dir
|
|
126
126
|
self.repo_root = repo_root.resolve()
|