codeboarding 0.9.0__tar.gz → 0.9.3__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.0/codeboarding.egg-info → codeboarding-0.9.3}/PKG-INFO +2 -1
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/agent.py +9 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/llm_config.py +13 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/planner_agent.py +1 -1
- {codeboarding-0.9.0 → codeboarding-0.9.3/codeboarding.egg-info}/PKG-INFO +2 -1
- {codeboarding-0.9.0 → codeboarding-0.9.3}/codeboarding.egg-info/requires.txt +1 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/diagram_generator.py +53 -26
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/reexpansion.py +1 -1
- {codeboarding-0.9.0 → codeboarding-0.9.3}/github_action.py +1 -1
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/checks/unused_code_diagnostics.py +9 -5
- {codeboarding-0.9.0 → codeboarding-0.9.3}/main.py +1 -1
- {codeboarding-0.9.0 → codeboarding-0.9.3}/monitoring/context.py +20 -5
- {codeboarding-0.9.0 → codeboarding-0.9.3}/pyproject.toml +6 -3
- {codeboarding-0.9.0 → codeboarding-0.9.3}/repo_utils/change_detector.py +1 -1
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/__init__.py +192 -52
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/lsp_client/client.py +117 -27
- {codeboarding-0.9.0 → codeboarding-0.9.3}/tests/test_github_action.py +1 -1
- {codeboarding-0.9.0 → codeboarding-0.9.3}/user_config.py +4 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/utils.py +1 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/LICENSE +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/PYPI.md +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/README.md +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/abstraction_agent.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/agent_responses.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/cluster_methods_mixin.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/constants.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/dependency_discovery.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/details_agent.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/meta_agent.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/prompts/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/prompts/abstract_prompt_factory.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/prompts/claude_prompts.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/prompts/deepseek_prompts.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/prompts/gemini_flash_prompts.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/prompts/glm_prompts.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/prompts/gpt_prompts.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/prompts/kimi_prompts.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/prompts/prompt_factory.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/base.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/get_external_deps.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/get_method_invocations.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/read_cfg.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/read_docs.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/read_file.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/read_file_structure.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/read_git_diff.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/read_packages.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/read_source.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/read_structure.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/tools/toolkit.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/agents/validation.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/caching/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/caching/cache.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/caching/meta_cache.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/codeboarding.egg-info/SOURCES.txt +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/codeboarding.egg-info/dependency_links.txt +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/codeboarding.egg-info/entry_points.txt +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/codeboarding.egg-info/top_level.txt +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/core/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/core/plugin_loader.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/core/protocols.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/core/registry.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/analysis_json.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/file_coverage.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/component_checker.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/file_manager.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/impact_analyzer.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/io_utils.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/models.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/path_patching.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/scoped_analysis.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/updater.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/incremental/validation.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/manifest.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/diagram_analysis/version.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/duckdb_crud.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/checks/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/checks/circular_deps.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/checks/cohesion.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/checks/coupling.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/checks/function_size.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/checks/god_class.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/checks/inheritance.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/checks/instability.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/config.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/constants.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/models.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health/runner.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/health_main.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/install.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/logging_config.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/monitoring/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/monitoring/callbacks.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/monitoring/mixin.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/monitoring/paths.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/monitoring/stats.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/monitoring/writers.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/output_generators/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/output_generators/html.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/output_generators/html_template.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/output_generators/markdown.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/output_generators/mdx.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/output_generators/sphinx.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/repo_utils/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/repo_utils/errors.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/repo_utils/git_diff.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/repo_utils/ignore.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/setup.cfg +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/analysis_cache.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/analysis_result.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/cluster_change_analyzer.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/cluster_helpers.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/constants.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/git_diff_analyzer.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/graph.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/incremental_orchestrator.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/java_config_scanner.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/java_utils.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/lsp_client/__init__.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/lsp_client/diagnostics.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/lsp_client/java_client.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/lsp_client/language_settings.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/lsp_client/typescript_client.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/programming_language.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/reference_resolve_mixin.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/scanner.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/static_analyzer/typescript_config_scanner.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/tests/test_incremental_analyzer.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/tests/test_install.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/tests/test_logging_config.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/tests/test_main.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/tests/test_vscode_constants.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/tests/test_windows_compatibility.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/tool_registry.py +0 -0
- {codeboarding-0.9.0 → codeboarding-0.9.3}/vscode_constants.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeboarding
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: Interactive Diagrams for Code
|
|
5
5
|
Author: CodeBoarding Team
|
|
6
6
|
License: MIT
|
|
@@ -28,6 +28,7 @@ Requires-Dist: exceptiongroup>=1.2
|
|
|
28
28
|
Requires-Dist: fastapi>=0.115
|
|
29
29
|
Requires-Dist: filelock>=3.12
|
|
30
30
|
Requires-Dist: gitpython>=3.1
|
|
31
|
+
Requires-Dist: google-api-core>=2.10
|
|
31
32
|
Requires-Dist: google-genai>=1.10
|
|
32
33
|
Requires-Dist: gql>=3.5
|
|
33
34
|
Requires-Dist: injector>=0.21
|
|
@@ -32,6 +32,10 @@ from static_analyzer.reference_resolve_mixin import ReferenceResolverMixin
|
|
|
32
32
|
logger = logging.getLogger(__name__)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
class EmptyExtractorMessageError(ValueError):
|
|
36
|
+
"""Raised when extractor returns an empty message payload."""
|
|
37
|
+
|
|
38
|
+
|
|
35
39
|
class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
|
|
36
40
|
def __init__(
|
|
37
41
|
self,
|
|
@@ -281,9 +285,14 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
|
|
|
281
285
|
if "messages" in result and len(result["messages"]) != 0:
|
|
282
286
|
message = result["messages"][0].content
|
|
283
287
|
parser = PydanticOutputParser(pydantic_object=return_type)
|
|
288
|
+
if not message:
|
|
289
|
+
raise EmptyExtractorMessageError("Extractor returned empty message content")
|
|
284
290
|
return self._try_parse(message, parser)
|
|
285
291
|
parser = PydanticOutputParser(pydantic_object=return_type)
|
|
286
292
|
return self._try_parse(response, parser)
|
|
293
|
+
except EmptyExtractorMessageError as e:
|
|
294
|
+
logger.warning(f"{e} (attempt {attempt + 1}/{max_retries})")
|
|
295
|
+
return self._parse_response(prompt, response, return_type, max_retries, attempt + 1)
|
|
287
296
|
except AttributeError as e:
|
|
288
297
|
# Workaround for trustcall bug: https://github.com/hinthornw/trustcall/issues/47
|
|
289
298
|
# 'ExtractionState' object has no attribute 'tool_call_id' occurs during validation retry
|
|
@@ -235,6 +235,19 @@ LLM_PROVIDERS = {
|
|
|
235
235
|
"max_retries": 0,
|
|
236
236
|
},
|
|
237
237
|
),
|
|
238
|
+
"openrouter": LLMConfig(
|
|
239
|
+
chat_class=ChatOpenAI,
|
|
240
|
+
api_key_env="OPENROUTER_API_KEY",
|
|
241
|
+
agent_model="google/gemini-2.5-flash",
|
|
242
|
+
parsing_model="google/gemini-2.5-flash",
|
|
243
|
+
llm_type=LLMType.GEMINI_FLASH,
|
|
244
|
+
extra_args={
|
|
245
|
+
"base_url": lambda: os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1"),
|
|
246
|
+
"max_tokens": None,
|
|
247
|
+
"timeout": None,
|
|
248
|
+
"max_retries": 0,
|
|
249
|
+
},
|
|
250
|
+
),
|
|
238
251
|
}
|
|
239
252
|
|
|
240
253
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeboarding
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: Interactive Diagrams for Code
|
|
5
5
|
Author: CodeBoarding Team
|
|
6
6
|
License: MIT
|
|
@@ -28,6 +28,7 @@ Requires-Dist: exceptiongroup>=1.2
|
|
|
28
28
|
Requires-Dist: fastapi>=0.115
|
|
29
29
|
Requires-Dist: filelock>=3.12
|
|
30
30
|
Requires-Dist: gitpython>=3.1
|
|
31
|
+
Requires-Dist: google-api-core>=2.10
|
|
31
32
|
Requires-Dist: google-genai>=1.10
|
|
32
33
|
Requires-Dist: gql>=3.5
|
|
33
34
|
Requires-Dist: injector>=0.21
|
|
@@ -13,7 +13,7 @@ from agents.agent_responses import AnalysisInsights, Component
|
|
|
13
13
|
from agents.details_agent import DetailsAgent
|
|
14
14
|
from agents.llm_config import initialize_llms
|
|
15
15
|
from agents.meta_agent import MetaAgent
|
|
16
|
-
from agents.planner_agent import
|
|
16
|
+
from agents.planner_agent import get_expandable_components
|
|
17
17
|
from diagram_analysis.analysis_json import (
|
|
18
18
|
FileCoverageReport,
|
|
19
19
|
FileCoverageSummary,
|
|
@@ -35,9 +35,10 @@ from monitoring.mixin import MonitoringMixin
|
|
|
35
35
|
from monitoring.paths import generate_run_id, get_monitoring_run_dir
|
|
36
36
|
from repo_utils import get_git_commit_hash, get_repo_state_hash
|
|
37
37
|
from repo_utils.ignore import RepoIgnoreManager
|
|
38
|
-
from static_analyzer import get_static_analysis
|
|
38
|
+
from static_analyzer import StaticAnalyzer, get_static_analysis
|
|
39
39
|
from static_analyzer.analysis_result import StaticAnalysisResults
|
|
40
40
|
from static_analyzer.scanner import ProjectScanner
|
|
41
|
+
from utils import get_cache_dir
|
|
41
42
|
|
|
42
43
|
logger = logging.getLogger(__name__)
|
|
43
44
|
|
|
@@ -53,6 +54,7 @@ class DiagramGenerator:
|
|
|
53
54
|
project_name: str | None = None,
|
|
54
55
|
run_id: str | None = None,
|
|
55
56
|
monitoring_enabled: bool = False,
|
|
57
|
+
static_analyzer: StaticAnalyzer | None = None,
|
|
56
58
|
):
|
|
57
59
|
self.repo_location = repo_location
|
|
58
60
|
self.temp_folder = temp_folder
|
|
@@ -63,6 +65,10 @@ class DiagramGenerator:
|
|
|
63
65
|
self.run_id = run_id
|
|
64
66
|
self.monitoring_enabled = monitoring_enabled
|
|
65
67
|
self.force_full_analysis = False # Set to True to skip incremental updates
|
|
68
|
+
# Optional pre-started StaticAnalyzer injected by long-lived callers (e.g. the
|
|
69
|
+
# wrapper). When set, pre_analysis() uses it directly instead of creating a new
|
|
70
|
+
# one-shot analyzer via get_static_analysis().
|
|
71
|
+
self._static_analyzer = static_analyzer
|
|
66
72
|
|
|
67
73
|
self.details_agent: DetailsAgent | None = None
|
|
68
74
|
self.static_analysis: StaticAnalysisResults | None = None # Cache static analysis for reuse
|
|
@@ -87,7 +93,7 @@ class DiagramGenerator:
|
|
|
87
93
|
parent_had_clusters = bool(component.source_cluster_ids)
|
|
88
94
|
|
|
89
95
|
# Get new components to analyze (deterministic, no LLM)
|
|
90
|
-
new_components =
|
|
96
|
+
new_components = get_expandable_components(analysis, parent_had_clusters=parent_had_clusters)
|
|
91
97
|
|
|
92
98
|
return component.component_id, analysis, new_components
|
|
93
99
|
except Exception as e:
|
|
@@ -107,7 +113,7 @@ class DiagramGenerator:
|
|
|
107
113
|
repo_path=self.repo_location,
|
|
108
114
|
)
|
|
109
115
|
if health_report is not None:
|
|
110
|
-
health_path =
|
|
116
|
+
health_path = Path(self.output_dir) / "health" / "health_report.json"
|
|
111
117
|
with open(health_path, "w") as f:
|
|
112
118
|
f.write(health_report.model_dump_json(indent=2, exclude_none=True))
|
|
113
119
|
logger.info(f"Health report written to {health_path} (score: {health_report.overall_score:.3f})")
|
|
@@ -138,11 +144,18 @@ class DiagramGenerator:
|
|
|
138
144
|
summary=FileCoverageSummary(**self.file_coverage_data["summary"]),
|
|
139
145
|
)
|
|
140
146
|
|
|
141
|
-
coverage_path =
|
|
147
|
+
coverage_path = Path(self.output_dir) / "file_coverage.json"
|
|
142
148
|
with open(coverage_path, "w") as f:
|
|
143
149
|
f.write(report.model_dump_json(indent=2, exclude_none=True))
|
|
144
150
|
logger.info(f"File coverage report written to {coverage_path}")
|
|
145
151
|
|
|
152
|
+
def _get_static_from_injected_analyzer(self, cache_dir: Path | None) -> StaticAnalysisResults:
|
|
153
|
+
result = self._static_analyzer.analyze( # type: ignore[union-attr]
|
|
154
|
+
cache_dir=cache_dir,
|
|
155
|
+
)
|
|
156
|
+
result.diagnostics = self._static_analyzer.collected_diagnostics # type: ignore[union-attr]
|
|
157
|
+
return result
|
|
158
|
+
|
|
146
159
|
def pre_analysis(self):
|
|
147
160
|
analysis_start_time = time.time()
|
|
148
161
|
|
|
@@ -157,14 +170,27 @@ class DiagramGenerator:
|
|
|
157
170
|
)
|
|
158
171
|
self._monitoring_agents["MetaAgent"] = self.meta_agent
|
|
159
172
|
|
|
160
|
-
|
|
161
|
-
|
|
173
|
+
def get_static_with_injected_analyzer() -> StaticAnalysisResults:
|
|
174
|
+
cache_dir = None if self.force_full_analysis else get_cache_dir(self.repo_location)
|
|
175
|
+
return self._get_static_from_injected_analyzer(cache_dir)
|
|
176
|
+
|
|
177
|
+
def get_static_with_new_analyzer() -> StaticAnalysisResults:
|
|
162
178
|
skip_cache = self.force_full_analysis
|
|
163
179
|
if skip_cache:
|
|
164
180
|
logger.info("Force full analysis: skipping static analysis cache")
|
|
165
|
-
|
|
166
|
-
|
|
181
|
+
return get_static_analysis(self.repo_location, skip_cache=skip_cache)
|
|
182
|
+
|
|
183
|
+
# Decide how to obtain static analysis results, then run it in parallel
|
|
184
|
+
# with the meta-context computation so neither blocks the other.
|
|
185
|
+
if self._static_analyzer is not None:
|
|
186
|
+
logger.info("Using injected StaticAnalyzer (clients already running)")
|
|
187
|
+
static_callable = get_static_with_injected_analyzer
|
|
188
|
+
else:
|
|
189
|
+
static_callable = get_static_with_new_analyzer
|
|
167
190
|
|
|
191
|
+
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
192
|
+
static_future = executor.submit(static_callable)
|
|
193
|
+
meta_future = executor.submit(self.meta_agent.get_meta_context, refresh=self.force_full_analysis)
|
|
168
194
|
static_analysis = static_future.result()
|
|
169
195
|
meta_context = meta_future.result()
|
|
170
196
|
|
|
@@ -205,7 +231,7 @@ class DiagramGenerator:
|
|
|
205
231
|
)
|
|
206
232
|
self._monitoring_agents["AbstractionAgent"] = self.abstraction_agent
|
|
207
233
|
|
|
208
|
-
version_file =
|
|
234
|
+
version_file = Path(self.output_dir) / "codeboarding_version.json"
|
|
209
235
|
with open(version_file, "w") as f:
|
|
210
236
|
f.write(
|
|
211
237
|
Version(
|
|
@@ -317,7 +343,7 @@ class DiagramGenerator:
|
|
|
317
343
|
|
|
318
344
|
return expanded_components, sub_analyses
|
|
319
345
|
|
|
320
|
-
def generate_analysis(self):
|
|
346
|
+
def generate_analysis(self) -> list[Path]:
|
|
321
347
|
"""
|
|
322
348
|
Generate the graph analysis for the given repository.
|
|
323
349
|
The output is stored in a single analysis.json file in output_dir.
|
|
@@ -337,7 +363,7 @@ class DiagramGenerator:
|
|
|
337
363
|
analysis, cluster_results = self.abstraction_agent.run()
|
|
338
364
|
|
|
339
365
|
# Get the initial components to analyze (deterministic, no LLM)
|
|
340
|
-
root_components =
|
|
366
|
+
root_components = get_expandable_components(analysis)
|
|
341
367
|
logger.info(f"Found {len(root_components)} components to analyze at level 1")
|
|
342
368
|
|
|
343
369
|
# Process components using a frontier queue: submit children as soon as parent finishes.
|
|
@@ -355,18 +381,15 @@ class DiagramGenerator:
|
|
|
355
381
|
)
|
|
356
382
|
|
|
357
383
|
# Final write of unified analysis.json
|
|
358
|
-
analysis_path =
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
file_coverage_summary=file_coverage_summary,
|
|
365
|
-
)
|
|
384
|
+
analysis_path = save_analysis(
|
|
385
|
+
analysis=analysis,
|
|
386
|
+
output_dir=Path(self.output_dir),
|
|
387
|
+
sub_analyses=sub_analyses,
|
|
388
|
+
repo_name=self.repo_name,
|
|
389
|
+
file_coverage_summary=file_coverage_summary,
|
|
366
390
|
)
|
|
367
391
|
|
|
368
392
|
logger.info(f"Analysis complete. Written unified analysis to {analysis_path}")
|
|
369
|
-
print("Generated analysis file: %s", os.path.abspath(analysis_path))
|
|
370
393
|
|
|
371
394
|
# Write file_coverage.json
|
|
372
395
|
self._write_file_coverage()
|
|
@@ -396,7 +419,7 @@ class DiagramGenerator:
|
|
|
396
419
|
except Exception as e:
|
|
397
420
|
logger.warning(f"Failed to save manifest: {e}")
|
|
398
421
|
|
|
399
|
-
def try_incremental_update(self) -> list[
|
|
422
|
+
def try_incremental_update(self) -> list[Path] | None:
|
|
400
423
|
"""
|
|
401
424
|
Attempt an incremental update if possible.
|
|
402
425
|
|
|
@@ -416,7 +439,11 @@ class DiagramGenerator:
|
|
|
416
439
|
# recompute file assignments with cluster matching. Load it first.
|
|
417
440
|
static_analysis = None
|
|
418
441
|
try:
|
|
419
|
-
|
|
442
|
+
if self._static_analyzer is not None:
|
|
443
|
+
static_analysis = self._static_analyzer.analyze(cache_dir=get_cache_dir(self.repo_location))
|
|
444
|
+
static_analysis.diagnostics = self._static_analyzer.collected_diagnostics
|
|
445
|
+
else:
|
|
446
|
+
static_analysis = get_static_analysis(self.repo_location)
|
|
420
447
|
logger.info("Loaded static analysis for incremental update")
|
|
421
448
|
except Exception as e:
|
|
422
449
|
logger.warning(f"Could not load static analysis: {e}")
|
|
@@ -442,7 +469,7 @@ class DiagramGenerator:
|
|
|
442
469
|
|
|
443
470
|
if impact.action == UpdateAction.NONE:
|
|
444
471
|
logger.info("No changes detected, analysis is up to date")
|
|
445
|
-
return [
|
|
472
|
+
return [self.output_dir / "analysis.json"]
|
|
446
473
|
|
|
447
474
|
# For structural changes, recompute which components are actually affected
|
|
448
475
|
# after static analysis has been updated with cluster matching
|
|
@@ -465,13 +492,13 @@ class DiagramGenerator:
|
|
|
465
492
|
)
|
|
466
493
|
self._write_file_coverage()
|
|
467
494
|
|
|
468
|
-
return [
|
|
495
|
+
return [self.output_dir / "analysis.json"]
|
|
469
496
|
|
|
470
497
|
# Incremental update failed or not possible
|
|
471
498
|
logger.info("Incremental update not possible, falling back to full analysis")
|
|
472
499
|
return None
|
|
473
500
|
|
|
474
|
-
def generate_analysis_smart(self) -> list[
|
|
501
|
+
def generate_analysis_smart(self) -> list[Path]:
|
|
475
502
|
"""
|
|
476
503
|
Smart analysis that tries incremental first, falls back to full.
|
|
477
504
|
|
|
@@ -12,7 +12,7 @@ from agents.llm_config import initialize_llms
|
|
|
12
12
|
from agents.agent_responses import AnalysisInsights
|
|
13
13
|
from agents.details_agent import DetailsAgent
|
|
14
14
|
from agents.meta_agent import MetaAgent
|
|
15
|
-
from agents.planner_agent import
|
|
15
|
+
from agents.planner_agent import get_expandable_components
|
|
16
16
|
from diagram_analysis.incremental.io_utils import load_sub_analysis, save_sub_analysis
|
|
17
17
|
from diagram_analysis.incremental.models import ChangeImpact
|
|
18
18
|
from diagram_analysis.incremental.path_patching import patch_sub_analysis
|
|
@@ -154,7 +154,7 @@ def generate_analysis(
|
|
|
154
154
|
analysis_files = generator.generate_analysis_smart()
|
|
155
155
|
|
|
156
156
|
# The generator now returns a single analysis.json path
|
|
157
|
-
analysis_path =
|
|
157
|
+
analysis_path = analysis_files[0]
|
|
158
158
|
|
|
159
159
|
# Now generate the output docs:
|
|
160
160
|
match extension:
|
|
@@ -136,13 +136,17 @@ class LSPDiagnosticsCollector:
|
|
|
136
136
|
"""Process all collected diagnostics and convert to issues."""
|
|
137
137
|
self.issues = []
|
|
138
138
|
skipped = 0
|
|
139
|
-
seen_issues: set[tuple[str, str, int]] = set() # (file_path,
|
|
139
|
+
seen_issues: set[tuple[str, str, int]] = set() # (file_path, category, line_start)
|
|
140
140
|
|
|
141
141
|
for item in self.diagnostics:
|
|
142
142
|
issue = self._convert_to_issue(item.file_path, item.diagnostic)
|
|
143
143
|
if issue:
|
|
144
|
-
# Deduplicate: same file,
|
|
145
|
-
|
|
144
|
+
# Deduplicate: same file, category, and line.
|
|
145
|
+
# Using category instead of code prevents duplicates from the same
|
|
146
|
+
# LSP server reporting the same issue under different diagnostic codes
|
|
147
|
+
# (e.g. Pyright reports unused variables both via reportUnusedVariable
|
|
148
|
+
# and via the "Unnecessary" tag with different messages).
|
|
149
|
+
key = (issue.file_path, issue.category.value, issue.line_start)
|
|
146
150
|
if key not in seen_issues:
|
|
147
151
|
seen_issues.add(key)
|
|
148
152
|
self.issues.append(issue)
|
|
@@ -187,8 +191,8 @@ class LSPDiagnosticsCollector:
|
|
|
187
191
|
|
|
188
192
|
return DiagnosticIssue(
|
|
189
193
|
file_path=file_path,
|
|
190
|
-
line_start=diagnostic.range.start.line,
|
|
191
|
-
line_end=diagnostic.range.end.line,
|
|
194
|
+
line_start=diagnostic.range.start.line + 1, # LSP lines are 0-based, convert to 1-based
|
|
195
|
+
line_end=diagnostic.range.end.line + 1, # LSP lines are 0-based, convert to 1-based
|
|
192
196
|
message=message,
|
|
193
197
|
code=code if code else None,
|
|
194
198
|
category=category,
|
|
@@ -3,7 +3,7 @@ import logging
|
|
|
3
3
|
import functools
|
|
4
4
|
import time
|
|
5
5
|
import contextlib
|
|
6
|
-
from contextvars import ContextVar
|
|
6
|
+
from contextvars import ContextVar, Token
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Callable, Any
|
|
9
9
|
|
|
@@ -71,25 +71,40 @@ def monitor_execution(
|
|
|
71
71
|
|
|
72
72
|
# Allow the user to manually log steps via the yielded context
|
|
73
73
|
class MonitorContext:
|
|
74
|
+
def __init__(self):
|
|
75
|
+
self._step_tokens: list[Token[str]] = []
|
|
76
|
+
|
|
74
77
|
def step(self, name):
|
|
75
78
|
trace_logger.info(json.dumps({"event": "phase_change", "step": name, "timestamp": time.time()}))
|
|
76
79
|
# Also update the ContextVar for other components
|
|
77
|
-
|
|
80
|
+
token = current_step.set(name)
|
|
81
|
+
self._step_tokens.append(token)
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
def end_step(self):
|
|
84
|
+
if not self._step_tokens:
|
|
85
|
+
return
|
|
86
|
+
token = self._step_tokens.pop()
|
|
87
|
+
current_step.reset(token)
|
|
88
|
+
|
|
89
|
+
def close(self):
|
|
90
|
+
while self._step_tokens:
|
|
91
|
+
self.end_step()
|
|
81
92
|
|
|
82
93
|
# Initialize stats for this run
|
|
83
94
|
run_stats = RunStats()
|
|
84
95
|
stats_token = current_stats.set(run_stats)
|
|
85
96
|
|
|
97
|
+
monitor_context = MonitorContext()
|
|
98
|
+
|
|
86
99
|
try:
|
|
87
100
|
# Log start of run
|
|
88
101
|
trace_logger.info(json.dumps({"event": "run_start", "run_id": run_id, "timestamp": time.time()}))
|
|
89
102
|
|
|
90
|
-
yield
|
|
103
|
+
yield monitor_context
|
|
91
104
|
|
|
92
105
|
finally:
|
|
106
|
+
monitor_context.close()
|
|
107
|
+
|
|
93
108
|
# Log end of run
|
|
94
109
|
trace_logger.info(json.dumps({"event": "run_end", "run_id": run_id, "timestamp": time.time()}))
|
|
95
110
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codeboarding"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.3"
|
|
8
8
|
description = "Interactive Diagrams for Code"
|
|
9
9
|
readme = "PYPI.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -34,6 +34,7 @@ dependencies = [
|
|
|
34
34
|
"fastapi>=0.115",
|
|
35
35
|
"filelock>=3.12",
|
|
36
36
|
"gitpython>=3.1",
|
|
37
|
+
"google-api-core>=2.10",
|
|
37
38
|
"google-genai>=1.10",
|
|
38
39
|
"gql>=3.5",
|
|
39
40
|
"injector>=0.21",
|
|
@@ -106,7 +107,7 @@ warn_return_any = false
|
|
|
106
107
|
warn_unused_configs = true
|
|
107
108
|
disallow_untyped_defs = false
|
|
108
109
|
check_untyped_defs = true
|
|
109
|
-
exclude = "(?x)(^repos/|^build/|^dist/|^temp/|^static_analyzer/servers/)"
|
|
110
|
+
exclude = "(?x)(^repos/|^build/|^dist/|^temp/|^static_analyzer/servers/|^tests/integration/projects/)"
|
|
110
111
|
explicit_package_bases = true
|
|
111
112
|
disable_error_code = ["import-untyped", "no-any-return", "assignment"]
|
|
112
113
|
|
|
@@ -116,6 +117,8 @@ dev-dependencies = [
|
|
|
116
117
|
"pre-commit==3.8.0",
|
|
117
118
|
"mypy==1.19.0",
|
|
118
119
|
"pytest-cov==7.0.0",
|
|
120
|
+
"build>=1.4.0",
|
|
121
|
+
"twine>=6.2.0",
|
|
119
122
|
]
|
|
120
123
|
|
|
121
124
|
[tool.pytest.ini_options]
|
|
@@ -133,4 +136,4 @@ markers = [
|
|
|
133
136
|
"typescript_lang: marks tests for TypeScript language",
|
|
134
137
|
"php_lang: marks tests for PHP language",
|
|
135
138
|
"javascript_lang: marks tests for JavaScript language",
|
|
136
|
-
]
|
|
139
|
+
]
|
|
@@ -125,7 +125,7 @@ def detect_changes(
|
|
|
125
125
|
Example:
|
|
126
126
|
changes = detect_changes(repo_path, "abc1234")
|
|
127
127
|
for rename_old, rename_new in changes.renames.items():
|
|
128
|
-
|
|
128
|
+
logger.info(f"Renamed: {rename_old} -> {rename_new}")
|
|
129
129
|
"""
|
|
130
130
|
changes: list[DetectedChange] = []
|
|
131
131
|
|