codeboarding 0.12.2__tar.gz → 0.12.4__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.12.2/codeboarding.egg-info → codeboarding-0.12.4}/PKG-INFO +1 -1
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/llm_config.py +20 -4
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/read_file_structure.py +1 -1
- {codeboarding-0.12.2 → codeboarding-0.12.4/codeboarding.egg-info}/PKG-INFO +1 -1
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding.egg-info/SOURCES.txt +0 -1
- {codeboarding-0.12.2 → codeboarding-0.12.4}/pyproject.toml +1 -1
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/analysis_cache.py +77 -83
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/analysis_result.py +44 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/graph.py +8 -2
- codeboarding-0.12.4/static_analyzer/incremental_orchestrator.py +338 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/scanner.py +1 -1
- codeboarding-0.12.2/health_main.py +0 -152
- codeboarding-0.12.2/static_analyzer/incremental_orchestrator.py +0 -125
- {codeboarding-0.12.2 → codeboarding-0.12.4}/LICENSE +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/PYPI.md +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/README.md +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/abstraction_agent.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/agent.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/agent_responses.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/change_status.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/cluster_budget.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/cluster_methods_mixin.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/constants.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/dependency_discovery.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/details_agent.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/incremental_agent.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/meta_agent.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/model_capabilities.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/planner_agent.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/prompts/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/prompts/abstract_prompt_factory.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/prompts/claude_prompts.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/prompts/deepseek_prompts.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/prompts/gemini_flash_prompts.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/prompts/glm_prompts.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/prompts/gpt_prompts.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/prompts/kimi_prompts.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/prompts/prompt_factory.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/retry.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/base.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/get_external_deps.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/get_method_invocations.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/read_cfg.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/read_docs.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/read_file.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/read_packages.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/read_source.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/read_structure.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/tools/toolkit.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/agents/validation.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/caching/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/caching/cache.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/caching/details_cache.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/caching/meta_cache.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding.egg-info/dependency_links.txt +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding.egg-info/entry_points.txt +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding.egg-info/requires.txt +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding.egg-info/top_level.txt +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_cli/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_cli/bootstrap.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_cli/commands/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_cli/commands/full_analysis.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_cli/commands/incremental_analysis.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_cli/commands/partial_analysis.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_workflows/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_workflows/analysis.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_workflows/orchestration.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_workflows/rendering.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_workflows/sources/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_workflows/sources/local.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/codeboarding_workflows/sources/remote.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/constants.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/core/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/core/plugin_loader.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/core/protocols.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/core/registry.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/analysis_json.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/cluster_delta.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/cluster_snapshot.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/diagram_generator.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/exceptions.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/file_coverage.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/io_utils.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/run_context.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/run_mode.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/diagram_analysis/version.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/github_action.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/checks/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/checks/circular_deps.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/checks/cohesion.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/checks/coupling.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/checks/function_size.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/checks/god_class.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/checks/inheritance.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/checks/instability.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/checks/unused_code_diagnostics.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/config.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/models.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/health/runner.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/install.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/logging_config.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/main.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/monitoring/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/monitoring/callbacks.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/monitoring/context.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/monitoring/mixin.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/monitoring/paths.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/monitoring/stats.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/monitoring/writers.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/output_generators/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/output_generators/html.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/output_generators/html_template.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/output_generators/markdown.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/output_generators/mdx.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/output_generators/sphinx.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/repo_utils/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/repo_utils/change_detector.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/repo_utils/diff_parser.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/repo_utils/errors.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/repo_utils/git_ops.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/repo_utils/ignore.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/setup.cfg +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/cfg_skip_planner.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/cluster_helpers.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/cluster_relations.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/constants.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/csharp_config_scanner.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/dotnet_sdk.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/adapters/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/adapters/csharp_adapter.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/adapters/go_adapter.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/adapters/java_adapter.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/adapters/php_adapter.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/adapters/python_adapter.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/adapters/rust_adapter.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/adapters/typescript_adapter.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/call_graph_builder.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/edge_build_context.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/edge_builder.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/hierarchy_builder.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/language_adapter.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/lsp_client.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/lsp_constants.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/models.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/progress.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/protocols.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/result_converter.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/source_inspector.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/symbol_table.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/engine/utils.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/java_config_scanner.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/java_utils.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/language_results.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/leiden_utils.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/lsp_client/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/lsp_client/diagnostics.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/node.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/programming_language.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/reference_resolve_mixin.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/static_analyzer/typescript_config_scanner.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/telemetry/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/telemetry/device_id.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/telemetry/events.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/telemetry/schemas.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/telemetry/service.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_cli_parser.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_github_action.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_install.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_logging_config.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_main.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_pyproject_packages.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_registry_coverage.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_telemetry_events.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_tool_registry.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_user_config.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_vscode_constants.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_windows_compatibility.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tests/test_windows_encoding.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tool_registry/__init__.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tool_registry/installers.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tool_registry/manifest.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tool_registry/paths.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/tool_registry/registry.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/user_config.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/utils.py +0 -0
- {codeboarding-0.12.2 → codeboarding-0.12.4}/vscode_constants.py +0 -0
|
@@ -25,6 +25,23 @@ logger = logging.getLogger(__name__)
|
|
|
25
25
|
|
|
26
26
|
_OPENROUTER_FALLBACK_CONTEXT_WINDOW = ContextWindow(1_048_576, 65_536, is_fallback=True)
|
|
27
27
|
|
|
28
|
+
# Model families that reject sampling params (temperature/top_p/top_k) with HTTP 400.
|
|
29
|
+
# Why: Anthropic removed them starting with Opus 4.7; sending temperature (even 0) 400s.
|
|
30
|
+
# Substrings match bare IDs and Bedrock-prefixed forms (e.g. us.anthropic.claude-opus-4-8-v1:0).
|
|
31
|
+
_SAMPLING_PARAM_FREE_MODEL_SUBSTRINGS = (
|
|
32
|
+
"claude-opus-4-7",
|
|
33
|
+
"claude-opus-4-8",
|
|
34
|
+
"claude-fable-5",
|
|
35
|
+
"claude-mythos-5",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _model_accepts_temperature(model_name: str) -> bool:
|
|
40
|
+
"""False for models that reject sampling params; temperature must be omitted for those."""
|
|
41
|
+
lowered = model_name.lower()
|
|
42
|
+
return not any(s in lowered for s in _SAMPLING_PARAM_FREE_MODEL_SUBSTRINGS)
|
|
43
|
+
|
|
44
|
+
|
|
28
45
|
# ---------------------------------------------------------------------------
|
|
29
46
|
# Module-level model overrides – set once by the orchestrator (main.py) and
|
|
30
47
|
# consumed by initialize_llms() without needing to thread the values through
|
|
@@ -341,10 +358,9 @@ def _initialize_llm(
|
|
|
341
358
|
|
|
342
359
|
logger.info(f"Using {name.title()} {log_prefix}LLM with model: {model_name}")
|
|
343
360
|
|
|
344
|
-
kwargs = {
|
|
345
|
-
|
|
346
|
-
"temperature"
|
|
347
|
-
}
|
|
361
|
+
kwargs: dict[str, Any] = {"model": model_name}
|
|
362
|
+
if _model_accepts_temperature(model_name):
|
|
363
|
+
kwargs["temperature"] = getattr(config, temperature_attr)
|
|
348
364
|
kwargs.update(config.get_resolved_extra_args())
|
|
349
365
|
|
|
350
366
|
# ChatBedrockConverse and ChatOllama take no api_key kwarg; their SDKs read
|
|
@@ -36,7 +36,7 @@ class FileStructureTool(BaseRepoTool):
|
|
|
36
36
|
# Ensure they are sorted by depth for is_subsequence logic
|
|
37
37
|
return sorted(dirs, key=lambda x: len(x.parts))
|
|
38
38
|
|
|
39
|
-
def _run(self, dir: str) -> str:
|
|
39
|
+
def _run(self, dir: str = ".") -> str:
|
|
40
40
|
"""
|
|
41
41
|
Run the tool with the given input.
|
|
42
42
|
"""
|
|
@@ -29,7 +29,8 @@ from typing import TYPE_CHECKING, Any
|
|
|
29
29
|
|
|
30
30
|
from filelock import FileLock
|
|
31
31
|
|
|
32
|
-
from static_analyzer.
|
|
32
|
+
from static_analyzer.analysis_result import AnalysisData, InvalidatedAnalysis, InvalidatedEdge
|
|
33
|
+
from static_analyzer.graph import Edge
|
|
33
34
|
from static_analyzer.lsp_client.diagnostics import FileDiagnosticsMap
|
|
34
35
|
from static_analyzer.node import Node
|
|
35
36
|
from utils import to_absolute_path, to_relative_path
|
|
@@ -332,67 +333,67 @@ def _atomic_copy(src: Path, dest: Path) -> None:
|
|
|
332
333
|
raise
|
|
333
334
|
|
|
334
335
|
|
|
335
|
-
def invalidate_files(analysis_result: dict[str, Any], changed_files: set[Path]) ->
|
|
336
|
+
def invalidate_files(analysis_result: dict[str, Any], changed_files: set[Path]) -> InvalidatedAnalysis:
|
|
336
337
|
"""Return a copy of *analysis_result* with every entry from *changed_files* removed.
|
|
337
338
|
|
|
338
339
|
Drops nodes whose ``file_path`` is in the change set, cascades edges that
|
|
339
|
-
reference dropped nodes,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
reference dropped nodes, remembers cross-boundary edges for later LSP
|
|
341
|
+
validation, drops class hierarchies and references from the same files,
|
|
342
|
+
prunes package relations to surviving files, and filters ``source_files`` /
|
|
343
|
+
``diagnostics`` accordingly. Raises ``ValueError`` if the result has
|
|
344
|
+
dangling edges or references after filtering.
|
|
343
345
|
"""
|
|
344
346
|
changed_file_strs = {str(path) for path in changed_files}
|
|
345
347
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
+
cached = AnalysisData.from_dict(analysis_result)
|
|
349
|
+
call_graph = cached.call_graph
|
|
350
|
+
invalidated_edges: list[InvalidatedEdge] = []
|
|
351
|
+
filtered_cg = call_graph.filter(
|
|
352
|
+
lambda node: node.file_path not in changed_file_strs,
|
|
353
|
+
on_dropped_edge=lambda edge: _collect_invalidated_edge(edge, changed_file_strs, invalidated_edges),
|
|
354
|
+
)
|
|
348
355
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
"package_relations": {},
|
|
353
|
-
"references": [],
|
|
354
|
-
"source_files": [],
|
|
355
|
-
}
|
|
356
|
+
diagnostics = None
|
|
357
|
+
if cached.diagnostics is not None:
|
|
358
|
+
diagnostics = {fp: diags for fp, diags in cached.diagnostics.items() if fp not in changed_file_strs}
|
|
356
359
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
360
|
+
class_hierarchies = {
|
|
361
|
+
class_name: class_info.copy()
|
|
362
|
+
for class_name, class_info in cached.class_hierarchies.items()
|
|
363
|
+
if class_info.get("file_path", "") not in changed_file_strs
|
|
364
|
+
}
|
|
361
365
|
|
|
362
|
-
|
|
363
|
-
for
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
for
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
updated_result["references"].append(ref)
|
|
380
|
-
|
|
381
|
-
source_files: list[Path] = analysis_result["source_files"]
|
|
382
|
-
for file_path in source_files:
|
|
383
|
-
if str(file_path) not in changed_file_strs:
|
|
384
|
-
updated_result["source_files"].append(file_path)
|
|
366
|
+
package_relations: dict[str, Any] = {}
|
|
367
|
+
for package_name, package_info in cached.package_relations.items():
|
|
368
|
+
remaining_files = [f for f in package_info.get("files", []) if f not in changed_file_strs]
|
|
369
|
+
if remaining_files:
|
|
370
|
+
package_relations[package_name] = {**package_info, "files": remaining_files}
|
|
371
|
+
|
|
372
|
+
references = [ref for ref in cached.references if ref.file_path not in changed_file_strs]
|
|
373
|
+
source_files = [file_path for file_path in cached.source_files if str(file_path) not in changed_file_strs]
|
|
374
|
+
|
|
375
|
+
updated_result = AnalysisData(
|
|
376
|
+
call_graph=filtered_cg,
|
|
377
|
+
class_hierarchies=class_hierarchies,
|
|
378
|
+
package_relations=package_relations,
|
|
379
|
+
references=references,
|
|
380
|
+
source_files=source_files,
|
|
381
|
+
diagnostics=diagnostics,
|
|
382
|
+
)
|
|
385
383
|
|
|
386
384
|
_validate_no_dangling_references(updated_result)
|
|
387
385
|
|
|
388
386
|
logger.info(
|
|
389
387
|
f"Invalidated {len(changed_files)} files: kept {len(filtered_cg.nodes)} nodes, "
|
|
390
|
-
f"{len(filtered_cg.edges)} edges, {len(updated_result
|
|
388
|
+
f"{len(filtered_cg.edges)} edges, {len(updated_result.references)} references"
|
|
391
389
|
)
|
|
392
|
-
return updated_result
|
|
390
|
+
return InvalidatedAnalysis(updated_result, invalidated_edges, changed_file_strs)
|
|
393
391
|
|
|
394
392
|
|
|
395
|
-
def merge_results(
|
|
393
|
+
def merge_results(
|
|
394
|
+
cached_result: AnalysisData,
|
|
395
|
+
new_result: dict[str, Any],
|
|
396
|
+
) -> AnalysisData:
|
|
396
397
|
"""Union ``cached_result`` (post-invalidation) with ``new_result`` (fresh re-LSP).
|
|
397
398
|
|
|
398
399
|
For overlapping keys (same file appearing in both), the new result wins
|
|
@@ -400,51 +401,44 @@ def merge_results(cached_result: dict[str, Any], new_result: dict[str, Any]) ->
|
|
|
400
401
|
nodes from both sides merge; edges from either side that reference
|
|
401
402
|
nodes present in the merged graph are kept.
|
|
402
403
|
"""
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
"references": [],
|
|
408
|
-
"source_files": [],
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
merged_result["class_hierarchies"].update(cached_result["class_hierarchies"])
|
|
412
|
-
merged_result["class_hierarchies"].update(new_result["class_hierarchies"])
|
|
413
|
-
|
|
414
|
-
merged_result["package_relations"].update(cached_result["package_relations"])
|
|
415
|
-
merged_result["package_relations"].update(new_result["package_relations"])
|
|
416
|
-
|
|
417
|
-
new_source_files: list[Path] = new_result.get("source_files", [])
|
|
418
|
-
new_file_paths = {str(path) for path in new_source_files}
|
|
419
|
-
|
|
420
|
-
for ref in cached_result["references"]:
|
|
421
|
-
if ref.file_path not in new_file_paths:
|
|
422
|
-
merged_result["references"].append(ref)
|
|
423
|
-
merged_result["references"].extend(new_result["references"])
|
|
424
|
-
|
|
425
|
-
for file_path in cached_result["source_files"]:
|
|
426
|
-
if str(file_path) not in new_file_paths:
|
|
427
|
-
merged_result["source_files"].append(file_path)
|
|
428
|
-
merged_result["source_files"].extend(new_source_files)
|
|
429
|
-
|
|
430
|
-
cached_diagnostics: FileDiagnosticsMap = cached_result.get("diagnostics", {})
|
|
431
|
-
new_diagnostics: FileDiagnosticsMap = new_result.get("diagnostics", {})
|
|
404
|
+
new = AnalysisData.from_dict(new_result)
|
|
405
|
+
new_file_paths = {str(path) for path in new.source_files}
|
|
406
|
+
cached_diagnostics = cached_result.diagnostics or {}
|
|
407
|
+
new_diagnostics = new.diagnostics or {}
|
|
432
408
|
merged_diagnostics: FileDiagnosticsMap = {
|
|
433
409
|
fp: diags for fp, diags in cached_diagnostics.items() if fp not in new_file_paths
|
|
434
410
|
}
|
|
435
411
|
merged_diagnostics.update(new_diagnostics)
|
|
436
|
-
if merged_diagnostics:
|
|
437
|
-
merged_result["diagnostics"] = merged_diagnostics
|
|
438
412
|
|
|
439
|
-
|
|
413
|
+
merged = AnalysisData(
|
|
414
|
+
call_graph=cached_result.call_graph.union(new.call_graph),
|
|
415
|
+
class_hierarchies={**cached_result.class_hierarchies, **new.class_hierarchies},
|
|
416
|
+
package_relations={**cached_result.package_relations, **new.package_relations},
|
|
417
|
+
references=[ref for ref in cached_result.references if ref.file_path not in new_file_paths] + new.references,
|
|
418
|
+
source_files=[path for path in cached_result.source_files if str(path) not in new_file_paths]
|
|
419
|
+
+ new.source_files,
|
|
420
|
+
diagnostics=merged_diagnostics or None,
|
|
421
|
+
)
|
|
422
|
+
return merged
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _collect_invalidated_edge(
|
|
426
|
+
edge: Edge, changed_file_strs: set[str], invalidated_edges: list[InvalidatedEdge]
|
|
427
|
+
) -> None:
|
|
428
|
+
src_node = edge.src_node
|
|
429
|
+
dst_node = edge.dst_node
|
|
430
|
+
src_changed = src_node.file_path in changed_file_strs
|
|
431
|
+
dst_changed = dst_node.file_path in changed_file_strs
|
|
432
|
+
if src_changed != dst_changed:
|
|
433
|
+
invalidated_edges.append((edge.get_source(), edge.get_destination(), src_node, dst_node))
|
|
440
434
|
|
|
441
435
|
|
|
442
|
-
def _validate_no_dangling_references(analysis_result:
|
|
436
|
+
def _validate_no_dangling_references(analysis_result: AnalysisData) -> None:
|
|
443
437
|
"""Sanity-check: every edge reaches existing nodes, every reference / class /
|
|
444
438
|
package points at a file in ``source_files``. Raises on violations."""
|
|
445
|
-
call_graph
|
|
439
|
+
call_graph = analysis_result.call_graph
|
|
446
440
|
existing_nodes = set(call_graph.nodes.keys())
|
|
447
|
-
source_file_strs = {str(path) for path in analysis_result
|
|
441
|
+
source_file_strs = {str(path) for path in analysis_result.source_files}
|
|
448
442
|
errors: list[str] = []
|
|
449
443
|
|
|
450
444
|
for edge in call_graph.edges:
|
|
@@ -455,16 +449,16 @@ def _validate_no_dangling_references(analysis_result: dict[str, Any]) -> None:
|
|
|
455
449
|
if dst_name not in existing_nodes:
|
|
456
450
|
errors.append(f"Edge destination '{dst_name}' references non-existent node")
|
|
457
451
|
|
|
458
|
-
for ref in analysis_result
|
|
452
|
+
for ref in analysis_result.references:
|
|
459
453
|
if ref.file_path not in source_file_strs:
|
|
460
454
|
errors.append(f"Reference '{ref.fully_qualified_name}' from '{ref.file_path}' references non-existent file")
|
|
461
455
|
|
|
462
|
-
for class_name, class_info in analysis_result
|
|
456
|
+
for class_name, class_info in analysis_result.class_hierarchies.items():
|
|
463
457
|
class_file_path = class_info.get("file_path", "")
|
|
464
458
|
if class_file_path and class_file_path not in source_file_strs:
|
|
465
459
|
errors.append(f"Class hierarchy '{class_name}' references non-existent file '{class_file_path}'")
|
|
466
460
|
|
|
467
|
-
for package_name, package_info in analysis_result
|
|
461
|
+
for package_name, package_info in analysis_result.package_relations.items():
|
|
468
462
|
for package_file in package_info.get("files", []):
|
|
469
463
|
if package_file not in source_file_strs:
|
|
470
464
|
errors.append(f"Package '{package_name}' references non-existent file '{package_file}'")
|
|
@@ -2,6 +2,8 @@ import logging
|
|
|
2
2
|
import re
|
|
3
3
|
from collections.abc import Iterator
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
5
7
|
|
|
6
8
|
from static_analyzer.constants import Language
|
|
7
9
|
from static_analyzer.graph import CallGraph
|
|
@@ -32,6 +34,48 @@ _WORD_RE = re.compile(r"\b([a-z]+)\b")
|
|
|
32
34
|
# Used to detect generic type params like T or E in lowercased method signatures.
|
|
33
35
|
_STANDALONE_SINGLE_LETTER_RE = re.compile(r"(?<![a-z])([a-z])(?!\w)")
|
|
34
36
|
|
|
37
|
+
InvalidatedEdge = tuple[str, str, Node, Node]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class AnalysisData:
|
|
42
|
+
call_graph: CallGraph
|
|
43
|
+
class_hierarchies: dict[str, Any]
|
|
44
|
+
package_relations: dict[str, Any]
|
|
45
|
+
references: list[Node]
|
|
46
|
+
source_files: list[Path]
|
|
47
|
+
diagnostics: FileDiagnosticsMap | None = None
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_dict(cls, analysis: dict[str, Any]) -> "AnalysisData":
|
|
51
|
+
return cls(
|
|
52
|
+
call_graph=analysis["call_graph"],
|
|
53
|
+
class_hierarchies=analysis["class_hierarchies"],
|
|
54
|
+
package_relations=analysis["package_relations"],
|
|
55
|
+
references=analysis["references"],
|
|
56
|
+
source_files=analysis["source_files"],
|
|
57
|
+
diagnostics=analysis.get("diagnostics"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def to_dict(self) -> dict[str, Any]:
|
|
61
|
+
analysis: dict[str, Any] = {
|
|
62
|
+
"call_graph": self.call_graph,
|
|
63
|
+
"class_hierarchies": self.class_hierarchies,
|
|
64
|
+
"package_relations": self.package_relations,
|
|
65
|
+
"references": self.references,
|
|
66
|
+
"source_files": self.source_files,
|
|
67
|
+
}
|
|
68
|
+
if self.diagnostics is not None:
|
|
69
|
+
analysis["diagnostics"] = self.diagnostics
|
|
70
|
+
return analysis
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class InvalidatedAnalysis:
|
|
75
|
+
analysis: AnalysisData
|
|
76
|
+
invalidated_edges: list[InvalidatedEdge]
|
|
77
|
+
invalidated_files: set[str]
|
|
78
|
+
|
|
35
79
|
|
|
36
80
|
def _strip_java_generics(name: str) -> str:
|
|
37
81
|
"""Remove Java generic type params from a (already lowercased) qualified name.
|
|
@@ -171,13 +171,17 @@ class CallGraph:
|
|
|
171
171
|
|
|
172
172
|
self.nodes[src_name].added_method_called_by_me(self.nodes[dst_name])
|
|
173
173
|
|
|
174
|
-
def filter(
|
|
174
|
+
def filter(
|
|
175
|
+
self,
|
|
176
|
+
keep_node: Callable[[Node], bool],
|
|
177
|
+
on_dropped_edge: Callable[[Edge], None],
|
|
178
|
+
) -> "CallGraph":
|
|
175
179
|
"""Return a new CallGraph keeping only nodes matching ``keep_node`` and connecting edges.
|
|
176
180
|
|
|
177
181
|
``_cluster_cache`` is preserved and pruned to the surviving qnames so
|
|
178
182
|
a warm-start invalidation/filter step doesn't silently drop the prior
|
|
179
183
|
clustering. Edges whose endpoints both survive are re-added; edges
|
|
180
|
-
with a dropped endpoint are cascaded out.
|
|
184
|
+
with a dropped endpoint are cascaded out and optionally collected.
|
|
181
185
|
"""
|
|
182
186
|
out = CallGraph(language=self.language)
|
|
183
187
|
for node in self.nodes.values():
|
|
@@ -190,6 +194,8 @@ class CallGraph:
|
|
|
190
194
|
out.add_edge(src, dst)
|
|
191
195
|
except ValueError as e:
|
|
192
196
|
logger.warning(f"Failed to add edge {src} -> {dst} during filter: {e}")
|
|
197
|
+
else:
|
|
198
|
+
on_dropped_edge(edge)
|
|
193
199
|
out._cluster_cache = self._prune_cluster_cache(out.nodes)
|
|
194
200
|
return out
|
|
195
201
|
|