codeboarding 0.10.2__tar.gz → 0.10.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.10.2/codeboarding.egg-info → codeboarding-0.10.4}/PKG-INFO +2 -1
- {codeboarding-0.10.2 → codeboarding-0.10.4}/README.md +4 -2
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/abstraction_agent.py +14 -2
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/agent.py +5 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/agent_responses.py +8 -16
- codeboarding-0.10.4/agents/cluster_budget.py +21 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/cluster_methods_mixin.py +170 -17
- codeboarding-0.10.4/agents/constants.py +38 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/details_agent.py +12 -2
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/llm_config.py +67 -40
- codeboarding-0.10.4/agents/model_capabilities.py +217 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4/codeboarding.egg-info}/PKG-INFO +2 -1
- {codeboarding-0.10.2 → codeboarding-0.10.4}/codeboarding.egg-info/SOURCES.txt +13 -3
- {codeboarding-0.10.2 → codeboarding-0.10.4}/codeboarding.egg-info/requires.txt +1 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/diagram_analysis/__init__.py +0 -1
- {codeboarding-0.10.2 → codeboarding-0.10.4}/diagram_analysis/analysis_json.py +146 -115
- {codeboarding-0.10.2 → codeboarding-0.10.4}/diagram_analysis/diagram_generator.py +0 -86
- {codeboarding-0.10.2 → codeboarding-0.10.4}/diagram_analysis/incremental_updater.py +126 -76
- {codeboarding-0.10.2 → codeboarding-0.10.4}/diagram_analysis/run_context.py +3 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/checks/unused_code_diagnostics.py +13 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/install.py +206 -68
- {codeboarding-0.10.2 → codeboarding-0.10.4}/pyproject.toml +6 -3
- {codeboarding-0.10.2 → codeboarding-0.10.4}/repo_utils/ignore.py +9 -2
- {codeboarding-0.10.2 → codeboarding-0.10.4}/repo_utils/method_diff.py +53 -68
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/__init__.py +134 -11
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/analysis_cache.py +2 -0
- codeboarding-0.10.4/static_analyzer/cfg_skip_planner.py +207 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/constants.py +8 -10
- codeboarding-0.10.4/static_analyzer/csharp_config_scanner.py +96 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/adapters/__init__.py +4 -0
- codeboarding-0.10.4/static_analyzer/engine/adapters/csharp_adapter.py +222 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/adapters/go_adapter.py +15 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/adapters/java_adapter.py +14 -9
- codeboarding-0.10.4/static_analyzer/engine/adapters/rust_adapter.py +189 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/call_graph_builder.py +50 -40
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/language_adapter.py +79 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/lsp_client.py +125 -18
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/result_converter.py +2 -1
- codeboarding-0.10.4/static_analyzer/engine/utils.py +68 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/graph.py +153 -35
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/incremental_orchestrator.py +1 -1
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/java_utils.py +18 -1
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/node.py +2 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/tests/test_install.py +67 -0
- codeboarding-0.10.4/tests/test_registry_coverage.py +396 -0
- codeboarding-0.10.4/tests/test_tool_registry.py +1655 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/tests/test_user_config.py +34 -1
- codeboarding-0.10.4/tests/test_windows_compatibility.py +61 -0
- codeboarding-0.10.4/tool_registry/__init__.py +72 -0
- codeboarding-0.10.4/tool_registry/installers.py +628 -0
- codeboarding-0.10.4/tool_registry/manifest.py +347 -0
- codeboarding-0.10.4/tool_registry/paths.py +264 -0
- codeboarding-0.10.4/tool_registry/registry.py +275 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/user_config.py +23 -6
- {codeboarding-0.10.2 → codeboarding-0.10.4}/vscode_constants.py +19 -0
- codeboarding-0.10.2/agents/constants.py +0 -13
- codeboarding-0.10.2/diagram_analysis/manifest.py +0 -153
- codeboarding-0.10.2/static_analyzer/engine/utils.py +0 -22
- codeboarding-0.10.2/tests/test_tool_registry.py +0 -94
- codeboarding-0.10.2/tests/test_windows_compatibility.py +0 -53
- codeboarding-0.10.2/tool_registry.py +0 -635
- {codeboarding-0.10.2 → codeboarding-0.10.4}/LICENSE +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/PYPI.md +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/change_status.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/dependency_discovery.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/meta_agent.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/planner_agent.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/prompts/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/prompts/abstract_prompt_factory.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/prompts/claude_prompts.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/prompts/deepseek_prompts.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/prompts/gemini_flash_prompts.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/prompts/glm_prompts.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/prompts/gpt_prompts.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/prompts/kimi_prompts.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/prompts/prompt_factory.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/base.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/get_external_deps.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/get_method_invocations.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/read_cfg.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/read_docs.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/read_file.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/read_file_structure.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/read_git_diff.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/read_packages.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/read_source.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/read_structure.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/tools/toolkit.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/agents/validation.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/caching/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/caching/cache.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/caching/details_cache.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/caching/meta_cache.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/codeboarding.egg-info/dependency_links.txt +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/codeboarding.egg-info/entry_points.txt +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/codeboarding.egg-info/top_level.txt +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/constants.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/core/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/core/plugin_loader.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/core/protocols.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/core/registry.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/diagram_analysis/file_coverage.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/diagram_analysis/incremental_types.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/diagram_analysis/io_utils.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/diagram_analysis/version.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/duckdb_crud.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/github_action.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/checks/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/checks/circular_deps.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/checks/cohesion.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/checks/coupling.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/checks/function_size.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/checks/god_class.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/checks/inheritance.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/checks/instability.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/config.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/constants.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/models.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health/runner.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/health_main.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/logging_config.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/main.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/monitoring/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/monitoring/callbacks.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/monitoring/context.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/monitoring/mixin.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/monitoring/paths.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/monitoring/stats.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/monitoring/writers.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/output_generators/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/output_generators/html.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/output_generators/html_template.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/output_generators/markdown.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/output_generators/mdx.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/output_generators/sphinx.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/repo_utils/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/repo_utils/change_detector.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/repo_utils/errors.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/repo_utils/git_diff.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/setup.cfg +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/analysis_result.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/cluster_change_analyzer.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/cluster_helpers.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/cluster_relations.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/adapters/php_adapter.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/adapters/python_adapter.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/adapters/typescript_adapter.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/edge_build_context.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/edge_builder.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/hierarchy_builder.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/lsp_constants.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/models.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/progress.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/protocols.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/source_inspector.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/engine/symbol_table.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/git_diff_analyzer.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/java_config_scanner.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/lsp_client/__init__.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/lsp_client/diagnostics.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/programming_language.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/reference_resolve_mixin.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/scanner.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/static_analyzer/typescript_config_scanner.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/tests/test_github_action.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/tests/test_logging_config.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/tests/test_main.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/tests/test_pyproject_packages.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/tests/test_vscode_constants.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/tests/test_windows_encoding.py +0 -0
- {codeboarding-0.10.2 → codeboarding-0.10.4}/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeboarding
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.4
|
|
4
4
|
Summary: Interactive Diagrams for Code
|
|
5
5
|
Author: CodeBoarding Team
|
|
6
6
|
License-Expression: MIT
|
|
@@ -37,6 +37,7 @@ Requires-Dist: markdown>=3.8
|
|
|
37
37
|
Requires-Dist: markdown-it-py>=3.0
|
|
38
38
|
Requires-Dist: markitdown>=0.1
|
|
39
39
|
Requires-Dist: networkx>=3.4
|
|
40
|
+
Requires-Dist: nodeenv>=1.10.0
|
|
40
41
|
Requires-Dist: pathspec>=0.12
|
|
41
42
|
Requires-Dist: pyyaml>=6.0
|
|
42
43
|
Requires-Dist: regex>=2024.11
|
|
@@ -16,6 +16,8 @@ Install the extension from Open VSX.
|
|
|
16
16
|
[](https://www.python.org/)
|
|
17
17
|
[](https://go.dev/)
|
|
18
18
|
[](https://www.php.net/)
|
|
19
|
+
[](https://www.rust-lang.org/)
|
|
20
|
+
[](https://learn.microsoft.com/en-us/dotnet/csharp/)
|
|
19
21
|
|
|
20
22
|
## Few use cases:
|
|
21
23
|
|
|
@@ -91,7 +93,7 @@ codeboarding --local /path/to/repo
|
|
|
91
93
|
|
|
92
94
|
Output is written to `/path/to/repo/.codeboarding/`.
|
|
93
95
|
|
|
94
|
-
`python install.py` and `codeboarding-setup` download language server binaries to `~/.codeboarding/servers/`, shared across projects. `npm` is required for Python, TypeScript, JavaScript, and PHP language servers; if
|
|
96
|
+
`python install.py` and `codeboarding-setup` download language server binaries to `~/.codeboarding/servers/`, shared across projects. Node.js (and its bundled `npm`) is required for the Python, TypeScript, JavaScript, and PHP language servers; if neither `node` nor `CODEBOARDING_NODE_PATH` is set, setup downloads a pinned Node.js runtime into `~/.codeboarding/servers/nodeenv/` automatically.
|
|
95
97
|
|
|
96
98
|
## Configuration
|
|
97
99
|
|
|
@@ -141,7 +143,7 @@ python main.py https://github.com/pytorch/pytorch
|
|
|
141
143
|
|
|
142
144
|
## Supported stack
|
|
143
145
|
|
|
144
|
-
- Languages: Python, TypeScript, JavaScript, Java, Go, PHP.
|
|
146
|
+
- Languages: Python, TypeScript, JavaScript, Java, Go, PHP, Rust.
|
|
145
147
|
- LLM providers: OpenAI, Anthropic, Google, Vercel AI Gateway, AWS Bedrock, Ollama, OpenRouter, and more.
|
|
146
148
|
|
|
147
149
|
## Examples
|
|
@@ -70,8 +70,20 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
|
|
|
70
70
|
|
|
71
71
|
programming_langs = self.static_analysis.get_languages()
|
|
72
72
|
|
|
73
|
-
#
|
|
74
|
-
|
|
73
|
+
# Measure everything that wraps cfg_clusters (system message + rendered
|
|
74
|
+
# template with an empty slot) so the skip planner can back it out of
|
|
75
|
+
# the input window before budgeting the cluster string.
|
|
76
|
+
overhead_chars = len(str(self.system_message.content)) + len(
|
|
77
|
+
self.prompts["group_clusters"].format(
|
|
78
|
+
project_name=self.project_name,
|
|
79
|
+
cfg_clusters="",
|
|
80
|
+
meta_context=meta_context_str,
|
|
81
|
+
project_type=project_type,
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
cluster_str = self._build_cluster_string(
|
|
85
|
+
programming_langs, cluster_results, prompt_overhead_chars=overhead_chars
|
|
86
|
+
)
|
|
75
87
|
|
|
76
88
|
prompt = self.prompts["group_clusters"].format(
|
|
77
89
|
project_name=self.project_name,
|
|
@@ -160,6 +160,11 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
|
|
|
160
160
|
raise
|
|
161
161
|
|
|
162
162
|
except Exception as e:
|
|
163
|
+
# HTTP 404 (e.g. retired model ID) is permanent — retrying won't help.
|
|
164
|
+
if getattr(e, "status_code", None) == 404:
|
|
165
|
+
logger.error(f"Permanent HTTP 404 — not retrying: {type(e).__name__}: {e}")
|
|
166
|
+
raise
|
|
167
|
+
|
|
163
168
|
# Other errors (network, parsing, etc.) get standard exponential backoff
|
|
164
169
|
if attempt < max_retries - 1:
|
|
165
170
|
delay = min(10 * (2**attempt), 120)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import abc
|
|
2
4
|
import logging
|
|
3
5
|
from abc import abstractmethod
|
|
6
|
+
from pathlib import PurePosixPath
|
|
4
7
|
from typing import get_origin, Optional
|
|
5
8
|
|
|
6
9
|
from pydantic import BaseModel, Field
|
|
7
10
|
|
|
8
|
-
from agents.change_status import ChangeStatus
|
|
9
|
-
|
|
10
11
|
logger = logging.getLogger(__name__)
|
|
11
12
|
|
|
12
13
|
|
|
@@ -141,10 +142,6 @@ class MethodEntry(BaseModel):
|
|
|
141
142
|
start_line: int = Field(description="Starting line number in the file.")
|
|
142
143
|
end_line: int = Field(description="Ending line number in the file.")
|
|
143
144
|
node_type: str = Field(description="Node type name matching NodeType enum (e.g. METHOD, FUNCTION, CLASS).")
|
|
144
|
-
status: ChangeStatus = Field(
|
|
145
|
-
default=ChangeStatus.UNCHANGED,
|
|
146
|
-
description="Diff status of this method: added, modified, deleted, or unchanged.",
|
|
147
|
-
)
|
|
148
145
|
|
|
149
146
|
def __hash__(self) -> int:
|
|
150
147
|
return hash(self.qualified_name)
|
|
@@ -155,13 +152,12 @@ class MethodEntry(BaseModel):
|
|
|
155
152
|
return self.qualified_name == other.qualified_name
|
|
156
153
|
|
|
157
154
|
@classmethod
|
|
158
|
-
def from_method_change(cls, method_change
|
|
155
|
+
def from_method_change(cls, method_change) -> MethodEntry:
|
|
159
156
|
return cls(
|
|
160
157
|
qualified_name=method_change.qualified_name,
|
|
161
158
|
start_line=method_change.start_line,
|
|
162
159
|
end_line=method_change.end_line,
|
|
163
160
|
node_type=method_change.node_type,
|
|
164
|
-
status=status_override or method_change.change_type,
|
|
165
161
|
)
|
|
166
162
|
|
|
167
163
|
|
|
@@ -169,10 +165,6 @@ class FileMethodGroup(BaseModel):
|
|
|
169
165
|
"""All methods/functions belonging to a component within a single file."""
|
|
170
166
|
|
|
171
167
|
file_path: str = Field(description="Relative path to the source file.")
|
|
172
|
-
file_status: ChangeStatus = Field(
|
|
173
|
-
default=ChangeStatus.UNCHANGED,
|
|
174
|
-
description="Diff status of this file: added, modified, deleted, renamed, or unchanged.",
|
|
175
|
-
)
|
|
176
168
|
methods: list[MethodEntry] = Field(
|
|
177
169
|
default_factory=list,
|
|
178
170
|
description="Methods and functions in this file that belong to the component, sorted by start_line.",
|
|
@@ -182,10 +174,6 @@ class FileMethodGroup(BaseModel):
|
|
|
182
174
|
class FileEntry(BaseModel):
|
|
183
175
|
"""Single source of truth for methods in one file."""
|
|
184
176
|
|
|
185
|
-
file_status: ChangeStatus = Field(
|
|
186
|
-
default=ChangeStatus.UNCHANGED,
|
|
187
|
-
description="Diff status of this file: added, modified, deleted, renamed, or unchanged.",
|
|
188
|
-
)
|
|
189
177
|
methods: list[MethodEntry] = Field(
|
|
190
178
|
default_factory=list,
|
|
191
179
|
description="Methods and functions in this file, sorted by start line.",
|
|
@@ -261,6 +249,10 @@ class AnalysisInsights(LLMBaseModel):
|
|
|
261
249
|
relations = "\n".join(cr.llm_str() for cr in self.components_relations)
|
|
262
250
|
return title + body + relations
|
|
263
251
|
|
|
252
|
+
def file_to_component(self) -> dict[str, str]:
|
|
253
|
+
"""Build file path -> component_id mapping from root components."""
|
|
254
|
+
return {str(PurePosixPath(fg.file_path)): c.component_id for c in self.components for fg in c.file_methods}
|
|
255
|
+
|
|
264
256
|
|
|
265
257
|
def assign_component_ids(analysis: AnalysisInsights, parent_id: str = "") -> None:
|
|
266
258
|
"""Assign hierarchical component IDs based on sibling index.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from agents.constants import ModelCapabilities
|
|
4
|
+
|
|
5
|
+
OUTPUT_HEADROOM_TOKENS = 8_000
|
|
6
|
+
CONTEXT_MARGIN = 0.9
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class ClusterPromptBudget:
|
|
11
|
+
"""Character budget for the full rendered ``cfg_clusters`` prompt slot."""
|
|
12
|
+
|
|
13
|
+
input_tokens: int
|
|
14
|
+
output_headroom_tokens: int = OUTPUT_HEADROOM_TOKENS
|
|
15
|
+
chars_per_token: float = ModelCapabilities.CHARS_PER_TOKEN
|
|
16
|
+
margin: float = CONTEXT_MARGIN
|
|
17
|
+
|
|
18
|
+
def available_chars(self, prompt_overhead_chars: int) -> int:
|
|
19
|
+
prompt_overhead_tokens = prompt_overhead_chars / self.chars_per_token
|
|
20
|
+
available_tokens = (self.input_tokens - self.output_headroom_tokens - prompt_overhead_tokens) * self.margin
|
|
21
|
+
return int(available_tokens * self.chars_per_token)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
from collections import defaultdict
|
|
4
|
+
from dataclasses import dataclass
|
|
4
5
|
from pathlib import Path
|
|
6
|
+
from typing import NoReturn
|
|
5
7
|
|
|
6
8
|
import networkx as nx
|
|
7
9
|
|
|
@@ -13,8 +15,11 @@ from agents.agent_responses import (
|
|
|
13
15
|
FileMethodGroup,
|
|
14
16
|
MethodEntry,
|
|
15
17
|
)
|
|
18
|
+
from agents.cluster_budget import ClusterPromptBudget
|
|
19
|
+
from agents.llm_config import get_current_agent_context_window
|
|
16
20
|
from constants import MIN_CLUSTERS_THRESHOLD
|
|
17
21
|
from static_analyzer.analysis_result import StaticAnalysisResults
|
|
22
|
+
from static_analyzer.cfg_skip_planner import ContextBudgetExceededError, plan_skip_set
|
|
18
23
|
from static_analyzer.cluster_helpers import (
|
|
19
24
|
MAX_LLM_CLUSTERS,
|
|
20
25
|
enforce_cross_language_budget,
|
|
@@ -34,6 +39,13 @@ from static_analyzer.node import Node
|
|
|
34
39
|
logger = logging.getLogger(__name__)
|
|
35
40
|
|
|
36
41
|
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class _RenderedClusterString:
|
|
44
|
+
text: str
|
|
45
|
+
by_language: dict[str, str]
|
|
46
|
+
cluster_ids: set[int]
|
|
47
|
+
|
|
48
|
+
|
|
37
49
|
class ClusterMethodsMixin:
|
|
38
50
|
"""
|
|
39
51
|
Mixin providing shared cluster-related functionality for agents.
|
|
@@ -61,6 +73,7 @@ class ClusterMethodsMixin:
|
|
|
61
73
|
programming_langs: list[str],
|
|
62
74
|
cluster_results: dict[str, ClusterResult],
|
|
63
75
|
cluster_ids: set[int] | None = None,
|
|
76
|
+
prompt_overhead_chars: int = 0,
|
|
64
77
|
) -> str:
|
|
65
78
|
"""
|
|
66
79
|
Build a cluster string for LLM consumption using pre-computed cluster results.
|
|
@@ -69,29 +82,59 @@ class ClusterMethodsMixin:
|
|
|
69
82
|
programming_langs: List of languages to include
|
|
70
83
|
cluster_results: Pre-computed cluster results mapping language -> ClusterResult
|
|
71
84
|
cluster_ids: Optional set of cluster IDs to filter by
|
|
85
|
+
prompt_overhead_chars: Characters used by everything else in the
|
|
86
|
+
prompt (system message + rendered template with an empty
|
|
87
|
+
``cfg_clusters`` slot). The skip planner subtracts this from
|
|
88
|
+
the model's input window before computing the char budget for
|
|
89
|
+
the cluster string.
|
|
72
90
|
|
|
73
91
|
Returns:
|
|
74
92
|
Formatted cluster string with headers per language
|
|
75
93
|
"""
|
|
76
|
-
|
|
94
|
+
rendered = self._render_cluster_string(programming_langs, cluster_results, cluster_ids, {})
|
|
95
|
+
if cluster_ids:
|
|
96
|
+
return rendered.text
|
|
97
|
+
|
|
98
|
+
char_budget = self._cluster_prompt_budget(prompt_overhead_chars)
|
|
99
|
+
if len(rendered.text) <= char_budget:
|
|
100
|
+
return rendered.text
|
|
101
|
+
|
|
102
|
+
per_lang_skip = self._plan_skip_sets(programming_langs, cluster_results, prompt_overhead_chars)
|
|
103
|
+
rendered_with_skips = self._render_cluster_string(
|
|
104
|
+
programming_langs, cluster_results, cluster_ids, per_lang_skip
|
|
105
|
+
)
|
|
106
|
+
if len(rendered_with_skips.text) > char_budget:
|
|
107
|
+
self._raise_cluster_budget_error(char_budget, rendered_with_skips, per_lang_skip)
|
|
108
|
+
|
|
109
|
+
return rendered_with_skips.text
|
|
110
|
+
|
|
111
|
+
def _render_cluster_string(
|
|
112
|
+
self,
|
|
113
|
+
programming_langs: list[str],
|
|
114
|
+
cluster_results: dict[str, ClusterResult],
|
|
115
|
+
cluster_ids: set[int] | None,
|
|
116
|
+
skip_sets: dict[str, set[str]],
|
|
117
|
+
) -> _RenderedClusterString:
|
|
118
|
+
cluster_lines: list[str] = []
|
|
119
|
+
by_language: dict[str, str] = {}
|
|
77
120
|
all_cluster_ids: set[int] = set()
|
|
78
121
|
|
|
79
122
|
for lang in programming_langs:
|
|
80
123
|
cfg = self.static_analysis.get_cfg(lang)
|
|
81
|
-
# Get cluster result for this language
|
|
82
124
|
cluster_result = cluster_results.get(lang)
|
|
83
|
-
cluster_str = cfg.to_cluster_string(
|
|
125
|
+
cluster_str = cfg.to_cluster_string(
|
|
126
|
+
cluster_ids or set(), cluster_result, skip_nodes=skip_sets.get(lang, set())
|
|
127
|
+
)
|
|
84
128
|
|
|
85
129
|
if cluster_str.strip() and cluster_str not in ("empty", "none", "No clusters found."):
|
|
86
130
|
header = "Component CFG" if cluster_ids else "Clusters"
|
|
87
|
-
|
|
88
|
-
cluster_lines.append(
|
|
89
|
-
|
|
131
|
+
lang_text = f"\n## {lang.capitalize()} - {header}\n{cluster_str}\n"
|
|
132
|
+
cluster_lines.append(lang_text)
|
|
133
|
+
by_language[lang] = lang_text
|
|
90
134
|
if cluster_result:
|
|
91
135
|
lang_ids = cluster_ids if cluster_ids else cluster_result.get_cluster_ids()
|
|
92
136
|
all_cluster_ids.update(lang_ids)
|
|
93
137
|
|
|
94
|
-
# Add explicit ID checklist so the LLM knows exactly which IDs to assign
|
|
95
138
|
if all_cluster_ids and not cluster_ids:
|
|
96
139
|
sorted_cluster_ids = sorted(all_cluster_ids)
|
|
97
140
|
cluster_lines.append(
|
|
@@ -99,7 +142,117 @@ class ClusterMethodsMixin:
|
|
|
99
142
|
f"Every one of these IDs: {sorted_cluster_ids} must appear in exactly one group."
|
|
100
143
|
)
|
|
101
144
|
|
|
102
|
-
return "".join(cluster_lines)
|
|
145
|
+
return _RenderedClusterString(text="".join(cluster_lines), by_language=by_language, cluster_ids=all_cluster_ids)
|
|
146
|
+
|
|
147
|
+
def _plan_skip_sets(
|
|
148
|
+
self,
|
|
149
|
+
programming_langs: list[str],
|
|
150
|
+
cluster_results: dict[str, ClusterResult],
|
|
151
|
+
prompt_overhead_chars: int,
|
|
152
|
+
) -> dict[str, set[str]]:
|
|
153
|
+
"""Compute per-language skip sets so the final combined cluster string fits."""
|
|
154
|
+
char_budget = self._cluster_prompt_budget(prompt_overhead_chars)
|
|
155
|
+
if char_budget <= 0:
|
|
156
|
+
ctx = get_current_agent_context_window()
|
|
157
|
+
msg = (
|
|
158
|
+
f"Prompt overhead ({prompt_overhead_chars} chars) consumes the entire agent input "
|
|
159
|
+
f"window ({ctx.input_tokens} tokens); no room for cluster renderings."
|
|
160
|
+
)
|
|
161
|
+
logger.error("[CFG skip planner] %s", msg)
|
|
162
|
+
raise ContextBudgetExceededError(msg)
|
|
163
|
+
|
|
164
|
+
langs_with_clusters = [l for l in programming_langs if cluster_results.get(l)]
|
|
165
|
+
if not langs_with_clusters:
|
|
166
|
+
return {}
|
|
167
|
+
|
|
168
|
+
skip_sets: dict[str, set[str]] = {}
|
|
169
|
+
rendered = self._render_cluster_string(programming_langs, cluster_results, None, skip_sets)
|
|
170
|
+
if len(rendered.text) <= char_budget:
|
|
171
|
+
return skip_sets
|
|
172
|
+
|
|
173
|
+
max_iterations = max(1, len(langs_with_clusters) * 5)
|
|
174
|
+
for _ in range(max_iterations):
|
|
175
|
+
deficit = len(rendered.text) - char_budget
|
|
176
|
+
ordered_langs = sorted(
|
|
177
|
+
langs_with_clusters,
|
|
178
|
+
key=lambda lang: len(rendered.by_language.get(lang, "")),
|
|
179
|
+
reverse=True,
|
|
180
|
+
)
|
|
181
|
+
progressed = False
|
|
182
|
+
|
|
183
|
+
for lang in ordered_langs:
|
|
184
|
+
lang_text = rendered.by_language.get(lang, "")
|
|
185
|
+
current_len = len(lang_text)
|
|
186
|
+
if current_len == 0:
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
for target in self._language_budget_targets(current_len, deficit):
|
|
190
|
+
try:
|
|
191
|
+
skip = plan_skip_set(self.static_analysis.get_cfg(lang), cluster_results[lang], target)
|
|
192
|
+
except ContextBudgetExceededError:
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
if skip == skip_sets.get(lang, set()):
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
trial_skip_sets = dict(skip_sets)
|
|
199
|
+
if skip:
|
|
200
|
+
trial_skip_sets[lang] = skip
|
|
201
|
+
else:
|
|
202
|
+
trial_skip_sets.pop(lang, None)
|
|
203
|
+
|
|
204
|
+
trial_rendered = self._render_cluster_string(
|
|
205
|
+
programming_langs, cluster_results, None, trial_skip_sets
|
|
206
|
+
)
|
|
207
|
+
if len(trial_rendered.text) >= len(rendered.text):
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
skip_sets = trial_skip_sets
|
|
211
|
+
rendered = trial_rendered
|
|
212
|
+
progressed = True
|
|
213
|
+
break
|
|
214
|
+
|
|
215
|
+
if progressed:
|
|
216
|
+
break
|
|
217
|
+
|
|
218
|
+
if len(rendered.text) <= char_budget:
|
|
219
|
+
return skip_sets
|
|
220
|
+
if not progressed:
|
|
221
|
+
break
|
|
222
|
+
|
|
223
|
+
self._raise_cluster_budget_error(char_budget, rendered, skip_sets)
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _language_budget_targets(current_len: int, deficit: int) -> list[int]:
|
|
227
|
+
exact_target = max(0, current_len - deficit)
|
|
228
|
+
targets = {
|
|
229
|
+
exact_target,
|
|
230
|
+
int(current_len * 0.9),
|
|
231
|
+
int(current_len * 0.75),
|
|
232
|
+
int(current_len * 0.5),
|
|
233
|
+
0,
|
|
234
|
+
}
|
|
235
|
+
return sorted((target for target in targets if target < current_len), reverse=True)
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def _raise_cluster_budget_error(
|
|
239
|
+
char_budget: int,
|
|
240
|
+
rendered: _RenderedClusterString,
|
|
241
|
+
skip_sets: dict[str, set[str]],
|
|
242
|
+
) -> NoReturn:
|
|
243
|
+
per_lang_sizes = {lang: len(text) for lang, text in rendered.by_language.items()}
|
|
244
|
+
skipped_counts = {lang: len(skip) for lang, skip in skip_sets.items() if skip}
|
|
245
|
+
msg = (
|
|
246
|
+
f"Cluster render {len(rendered.text)} chars exceeds budget {char_budget}. "
|
|
247
|
+
f"Per-language sizes: {per_lang_sizes}; skipped nodes: {skipped_counts}."
|
|
248
|
+
)
|
|
249
|
+
logger.error("[CFG skip planner] %s", msg)
|
|
250
|
+
raise ContextBudgetExceededError(msg)
|
|
251
|
+
|
|
252
|
+
@staticmethod
|
|
253
|
+
def _cluster_prompt_budget(prompt_overhead_chars: int) -> int:
|
|
254
|
+
ctx = get_current_agent_context_window()
|
|
255
|
+
return ClusterPromptBudget(input_tokens=ctx.input_tokens).available_chars(prompt_overhead_chars)
|
|
103
256
|
|
|
104
257
|
def _ensure_unique_key_entities(self, analysis: AnalysisInsights):
|
|
105
258
|
"""
|
|
@@ -247,11 +400,11 @@ class ClusterMethodsMixin:
|
|
|
247
400
|
logger.warning(f"Component {component.name} has no assigned files")
|
|
248
401
|
return "No assigned files found for this component.", {}, {}
|
|
249
402
|
|
|
250
|
-
#
|
|
251
|
-
|
|
252
|
-
for
|
|
253
|
-
|
|
254
|
-
|
|
403
|
+
# Collect qualified names for method-level filtering
|
|
404
|
+
assigned_qnames: set[str] = set()
|
|
405
|
+
for group in component.file_methods:
|
|
406
|
+
for method in group.methods:
|
|
407
|
+
assigned_qnames.add(method.qualified_name)
|
|
255
408
|
|
|
256
409
|
cluster_results: dict[str, ClusterResult] = {}
|
|
257
410
|
subgraph_cfgs: dict[str, CallGraph] = {}
|
|
@@ -259,8 +412,8 @@ class ClusterMethodsMixin:
|
|
|
259
412
|
for lang in self.static_analysis.get_languages():
|
|
260
413
|
cfg = self.static_analysis.get_cfg(lang)
|
|
261
414
|
|
|
262
|
-
#
|
|
263
|
-
sub_cfg = cfg.
|
|
415
|
+
# Filter by exact method set to prevent scope leakage
|
|
416
|
+
sub_cfg = cfg.filter_by_nodes(assigned_qnames)
|
|
264
417
|
|
|
265
418
|
if sub_cfg.nodes:
|
|
266
419
|
subgraph_cfgs[lang] = sub_cfg
|
|
@@ -299,7 +452,7 @@ class ClusterMethodsMixin:
|
|
|
299
452
|
result = "".join(result_parts)
|
|
300
453
|
|
|
301
454
|
if not result.strip():
|
|
302
|
-
logger.warning(f"No CFG found for component {component.name} with {len(
|
|
455
|
+
logger.warning(f"No CFG found for component {component.name} with {len(assigned_qnames)} methods")
|
|
303
456
|
return "No relevant CFG clusters found for this component.", cluster_results, subgraph_cfgs
|
|
304
457
|
|
|
305
458
|
return result, cluster_results, subgraph_cfgs
|
|
@@ -556,7 +709,7 @@ class ClusterMethodsMixin:
|
|
|
556
709
|
for fmg in component.file_methods:
|
|
557
710
|
entry = files.get(fmg.file_path)
|
|
558
711
|
if entry is None:
|
|
559
|
-
entry = FileEntry(
|
|
712
|
+
entry = FileEntry(methods=[])
|
|
560
713
|
files[fmg.file_path] = entry
|
|
561
714
|
|
|
562
715
|
methods_by_qname = {m.qualified_name: m for m in entry.methods}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Constants for the agents module."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LLMDefaults:
|
|
5
|
+
DEFAULT_AGENT_TEMPERATURE = 0
|
|
6
|
+
DEFAULT_PARSING_TEMPERATURE = 0
|
|
7
|
+
AWS_MAX_TOKENS = 4096
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FileStructureConfig:
|
|
11
|
+
MAX_LINES = 500
|
|
12
|
+
DEFAULT_MAX_DEPTH = 10
|
|
13
|
+
FALLBACK_MAX_LINES = 50000
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ModelCapabilities:
|
|
17
|
+
FALLBACK_INPUT = 256_000
|
|
18
|
+
FALLBACK_OUTPUT = 64_000
|
|
19
|
+
CACHE_TTL_SECONDS = 24 * 3600
|
|
20
|
+
CHARS_PER_TOKEN = 3.5 # community consensus conversion is around 3 or 4 chars/token.
|
|
21
|
+
|
|
22
|
+
SOURCES = {
|
|
23
|
+
"litellm": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
24
|
+
"modelsdev": "https://models.dev/api.json",
|
|
25
|
+
"openrouter": "https://openrouter.ai/api/v1/models",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# models.dev uses slugs that diverge from our internal provider names.
|
|
29
|
+
MODELSDEV_SLUG = {
|
|
30
|
+
"aws": "amazon-bedrock",
|
|
31
|
+
"kimi": "moonshotai",
|
|
32
|
+
"glm": "zai",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
OPENROUTER_PREFIX = {
|
|
36
|
+
"kimi": "moonshotai",
|
|
37
|
+
"glm": "z-ai",
|
|
38
|
+
}
|
|
@@ -84,8 +84,18 @@ class DetailsAgent(ClusterMethodsMixin, CodeBoardingAgent):
|
|
|
84
84
|
|
|
85
85
|
programming_langs = self.static_analysis.get_languages()
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
overhead_chars = len(str(self.system_message.content)) + len(
|
|
88
|
+
self.prompts["group_clusters"].format(
|
|
89
|
+
project_name=self.project_name,
|
|
90
|
+
cfg_clusters="",
|
|
91
|
+
component=component.llm_str(),
|
|
92
|
+
meta_context=meta_context_str,
|
|
93
|
+
project_type=project_type,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
cluster_str = self._build_cluster_string(
|
|
97
|
+
programming_langs, subgraph_cluster_results, prompt_overhead_chars=overhead_chars
|
|
98
|
+
)
|
|
89
99
|
|
|
90
100
|
prompt = self.prompts["group_clusters"].format(
|
|
91
101
|
project_name=self.project_name,
|