codeboarding 0.15.0__tar.gz → 0.15.2__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.15.0 → codeboarding-0.15.2}/PKG-INFO +1 -1
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/llm_errors.py +66 -39
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/tree_planner_agent.py +4 -2
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding.egg-info/PKG-INFO +1 -1
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding.egg-info/SOURCES.txt +3 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/commands/full_analysis.py +3 -3
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/commands/incremental_analysis.py +3 -3
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/main.py +10 -15
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/diagram_generator.py +6 -4
- {codeboarding-0.15.0 → codeboarding-0.15.2}/pyproject.toml +5 -1
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/__init__.py +11 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/analysis_cache.py +2 -1
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/adapters/typescript_adapter.py +31 -1
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/language_adapter.py +8 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/lsp_client.py +13 -3
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/exceptions.py +4 -0
- codeboarding-0.15.2/static_analyzer/typescript_plugin/node_modules/codeboarding-workspace-sources/index.js +132 -0
- codeboarding-0.15.2/static_analyzer/typescript_plugin/node_modules/codeboarding-workspace-sources/package.json +6 -0
- codeboarding-0.15.2/static_analyzer/typescript_workspace.py +81 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/LICENSE +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/PYPI.md +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/README.md +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/agent_responses.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/change_status.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/component_ownership.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/constants.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/content_hash.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/file_index_models.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/incremental_results.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/llm_config.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/llm_renderers/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/llm_renderers/call_graph.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/llm_renderers/scope.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/model_capabilities.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/planner_agent.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/prompts/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/prompts/profiles.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/prompts/shared.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/relation_edges.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/retry.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/scope_analysis_agent.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/scope_ids.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/tools/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/tools/base.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/tools/get_method_calls.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/agents/tools/read_file.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding.egg-info/dependency_links.txt +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding.egg-info/entry_points.txt +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding.egg-info/requires.txt +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding.egg-info/top_level.txt +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/bootstrap.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/commands/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/commands/partial_analysis.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/github_action.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/render.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/view_instructions.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_workflows/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_workflows/analysis.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_workflows/fingerprint_diff.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_workflows/orchestration.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_workflows/sources/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_workflows/sources/local.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_workflows/sources/remote.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/analysis_json.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/cluster_relations.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/exceptions.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/file_coverage.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/file_index.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/incremental_changes.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/incremental_update.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/io_utils.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/reference_resolver.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/run_context.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/run_mode.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/scope_assembly.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/scope_plan.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/diagram_analysis/tree_shape.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/checks/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/checks/circular_deps.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/checks/function_size.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/checks/unused_code_diagnostics.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/config.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/main.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/models.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/plugins/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/plugins/plugin_loader.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/plugins/protocols.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/plugins/registry.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/health/runner.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/clustering_ids.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/constants.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/install.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/logging_config.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/callbacks.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/context.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/mixin.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/paths.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/stats.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/telemetry/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/telemetry/device_id.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/telemetry/events.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/telemetry/schemas.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/telemetry/service.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/monitoring/writers.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/repo_utils/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/repo_utils/change_detector.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/repo_utils/errors.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/repo_utils/git_ops.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/repo_utils/ignore.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/repo_utils/path_utils.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/tool_registry/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/tool_registry/grammars.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/tool_registry/installers.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/tool_registry/java.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/tool_registry/manifest.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/tool_registry/paths.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/tool_registry/registry.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/user_config.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/utils.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/infra/vscode_constants.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/output_generators/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/output_generators/html.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/output_generators/html_template.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/output_generators/markdown.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/output_generators/mdx.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/output_generators/rendering.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/output_generators/sphinx.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/setup.cfg +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/analysis_result.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/cfg/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/cfg/call_graph.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/cfg/edge.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/cfg/location_key.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/exceptions.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/models.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/names/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/names/draft.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/names/frontier.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/names/inventory.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/names/replay.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/names/spec.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/names/tokens.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/clustering/service.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/config.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/csharp_config_scanner.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/dotnet_sdk.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/dotnet_solution.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/adapters/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/adapters/csharp_adapter.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/adapters/go_adapter.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/adapters/java_adapter.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/adapters/kotlin_adapter.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/adapters/php_adapter.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/adapters/python_adapter.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/adapters/rust_adapter.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/call_graph_builder.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/edge_build_context.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/edge_builder.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/hierarchy_builder.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/lsp_constants.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/lsp_recycler.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/models.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/process_memory.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/progress.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/protocols.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/result_converter.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/source_inspector.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/symbol_table.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/utils.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/external_calls.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/graph_definitions.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/incremental_orchestrator.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/internal_references.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/java_config_scanner.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/java_utils.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/language_results.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/lsp_client/__init__.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/lsp_client/diagnostics.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/node.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/programming_language.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/scanner.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/typescript_config_scanner.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/workspace_dependencies.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/tests/test_persisted_analysis_artifacts.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/tests/test_pyproject_packages.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/tests/test_view_instructions.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/tests/test_windows_compatibility.py +0 -0
- {codeboarding-0.15.0 → codeboarding-0.15.2}/tests/test_windows_encoding.py +0 -0
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
"""Provider-agnostic
|
|
2
|
-
|
|
3
|
-
A rejected API key (HTTP 401, or a provider's equivalent auth/permission
|
|
4
|
-
error) is *permanent* for the run: retrying it wastes minutes of backoff and
|
|
5
|
-
floods telemetry with identical ``$exception`` events. The agent retry loop and
|
|
6
|
-
the CLI both need to recognize one when they see it, regardless of which SDK
|
|
7
|
-
raised it.
|
|
8
|
-
|
|
9
|
-
``detect_auth_error`` maps a raw provider exception to an :class:`LLMAuthError`
|
|
10
|
-
(or ``None`` when it isn't an auth failure). The typed error carries the
|
|
11
|
-
provider, a masked key tail, and the provider's own message, and forwards them
|
|
12
|
-
as ``telemetry_properties`` so the PostHog ``$exception`` event — and the
|
|
13
|
-
dashboard's structured ``error`` columns — populate instead of staying null.
|
|
14
|
-
"""
|
|
1
|
+
"""Provider-agnostic LLM failure classification, exceptions, and retry helpers."""
|
|
15
2
|
|
|
16
3
|
from __future__ import annotations
|
|
17
4
|
|
|
@@ -33,6 +20,11 @@ EXIT_AUTH_ERROR = 2
|
|
|
33
20
|
# Process exit code for an exhausted token or credit quota: the key is fine, the account is not.
|
|
34
21
|
EXIT_QUOTA_EXHAUSTED = 3
|
|
35
22
|
|
|
23
|
+
CODEBOARDING_QUOTA_CODE = "free_credits_exhausted"
|
|
24
|
+
# Response contract: licensing-aws/gha_proxy/handler.py.
|
|
25
|
+
PROXY_ERROR_TYPE = "gha_proxy"
|
|
26
|
+
PROXY_QUOTA_MESSAGE = "Resource exhausted: token limit reached"
|
|
27
|
+
|
|
36
28
|
# Class names, across SDKs, that always mean "credentials were rejected".
|
|
37
29
|
# openai/anthropic/cerebras raise ``AuthenticationError``; google raises
|
|
38
30
|
# ``Unauthenticated``/``PermissionDenied``; Bedrock (botocore) surfaces
|
|
@@ -114,6 +106,65 @@ class LLMAuthError(RuntimeError):
|
|
|
114
106
|
self.telemetry_properties = telemetry_properties
|
|
115
107
|
|
|
116
108
|
|
|
109
|
+
class LLMQuotaError(RuntimeError):
|
|
110
|
+
"""An LLM quota is exhausted, with recovery guidance and failure telemetry."""
|
|
111
|
+
|
|
112
|
+
def __init__(self, error: BaseException):
|
|
113
|
+
self.telemetry_properties = llm_failure_properties(error)
|
|
114
|
+
self.is_codeboarding_quota = _is_codeboarding_quota(error)
|
|
115
|
+
_, key_tail = current_provider_key_context()
|
|
116
|
+
if self.is_codeboarding_quota:
|
|
117
|
+
message = (
|
|
118
|
+
"Your CodeBoarding quota is exhausted. "
|
|
119
|
+
"Buy a CodeBoarding license at https://codeboarding.org/pricing, "
|
|
120
|
+
"or wait for your weekly reset."
|
|
121
|
+
)
|
|
122
|
+
elif key_tail == CODEBOARDING_KEY_TAIL:
|
|
123
|
+
message = (
|
|
124
|
+
"The hosted LLM provider's quota is exhausted. "
|
|
125
|
+
"This is a CodeBoarding service issue. Please try again later or contact CodeBoarding support."
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
message = (
|
|
129
|
+
"The LLM provider's token or credit quota is exhausted. "
|
|
130
|
+
"Add credits or raise the quota for this provider, then re-run."
|
|
131
|
+
)
|
|
132
|
+
super().__init__(message)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def llm_failure_properties(exc: BaseException) -> dict:
|
|
136
|
+
"""Classify a failed LLM call for exception telemetry."""
|
|
137
|
+
quota = is_quota_exhausted(exc)
|
|
138
|
+
context = isinstance(exc, ScopeContextTooLargeError) or any(p.search(str(exc)) for p in _CONTEXT_OVERFLOW_PATTERNS)
|
|
139
|
+
provider, _key_tail = current_provider_key_context()
|
|
140
|
+
properties = {
|
|
141
|
+
"error_type": "quota" if quota else "context" if context else "llm",
|
|
142
|
+
"error_provider": provider,
|
|
143
|
+
"error_status_code": _status_code(exc),
|
|
144
|
+
"error_message": str(exc)[:500],
|
|
145
|
+
}
|
|
146
|
+
if _is_codeboarding_quota(exc):
|
|
147
|
+
properties["error_code"] = CODEBOARDING_QUOTA_CODE
|
|
148
|
+
return properties
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def is_quota_exhausted(exc: BaseException) -> bool:
|
|
152
|
+
status = _status_code(exc)
|
|
153
|
+
quota_code = getattr(exc, "code", None) == "insufficient_quota" or "insufficient_quota" in str(exc)
|
|
154
|
+
return status == 402 or (status == 429 and quota_code)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _is_codeboarding_quota(exc: BaseException) -> bool:
|
|
158
|
+
body = getattr(exc, "body", {})
|
|
159
|
+
error = body.get("error", body) if isinstance(body, dict) else {}
|
|
160
|
+
return (
|
|
161
|
+
_status_code(exc) == 402
|
|
162
|
+
and isinstance(error, dict)
|
|
163
|
+
and error.get("type") == PROXY_ERROR_TYPE
|
|
164
|
+
and error.get("message") == PROXY_QUOTA_MESSAGE
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
117
168
|
def _status_code(exc: BaseException) -> int | None:
|
|
118
169
|
code = getattr(exc, "status_code", None)
|
|
119
170
|
if code is None:
|
|
@@ -183,30 +234,6 @@ def detect_auth_error(exc: BaseException, *, provider: str, key_tail: str) -> LL
|
|
|
183
234
|
)
|
|
184
235
|
|
|
185
236
|
|
|
186
|
-
def _is_quota_exhausted(exc: BaseException) -> bool:
|
|
187
|
-
status = _status_code(exc)
|
|
188
|
-
quota_code = getattr(exc, "code", None) == "insufficient_quota" or "insufficient_quota" in str(exc)
|
|
189
|
-
return status == 402 or (status == 429 and quota_code)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def llm_failure_properties(exc: BaseException) -> dict:
|
|
193
|
-
"""``$exception`` properties for a failed LLM call, telling an exhausted quota or an oversized prompt
|
|
194
|
-
apart from other failures.
|
|
195
|
-
|
|
196
|
-
Why: the hosted proxy's paywall is a 402, and OpenAI reports an empty balance as a 429 ``insufficient_quota``.
|
|
197
|
-
An oversized prompt is ours to fix, not the provider's, so it gets its own type on the dashboard.
|
|
198
|
-
"""
|
|
199
|
-
quota = _is_quota_exhausted(exc)
|
|
200
|
-
context = isinstance(exc, ScopeContextTooLargeError) or any(p.search(str(exc)) for p in _CONTEXT_OVERFLOW_PATTERNS)
|
|
201
|
-
provider, _key_tail = current_provider_key_context()
|
|
202
|
-
return {
|
|
203
|
-
"error_type": "quota" if quota else "context" if context else "llm",
|
|
204
|
-
"error_provider": provider,
|
|
205
|
-
"error_status_code": _status_code(exc),
|
|
206
|
-
"error_message": str(exc)[:500],
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
237
|
def is_transient_llm_error(exc: BaseException) -> bool:
|
|
211
238
|
"""Whether the same request may succeed if sent again: throttled, a server error, or no answer at all.
|
|
212
239
|
|
|
@@ -216,7 +243,7 @@ def is_transient_llm_error(exc: BaseException) -> bool:
|
|
|
216
243
|
"""
|
|
217
244
|
status = _status_code(exc)
|
|
218
245
|
if status is not None:
|
|
219
|
-
return status in (408, 429) and not
|
|
246
|
+
return status in (408, 429) and not is_quota_exhausted(exc) or status >= 500
|
|
220
247
|
return any(cls.__name__ in _TRANSIENT_TYPE_NAMES for cls in type(exc).__mro__)
|
|
221
248
|
|
|
222
249
|
|
|
@@ -16,7 +16,7 @@ from langchain_core.prompts import PromptTemplate
|
|
|
16
16
|
from agents.agent_responses import PlannedGroup, TreePlanInsights
|
|
17
17
|
from agents.llm_config import get_current_prompt_profile, supports_json_mode
|
|
18
18
|
from infra.monitoring.callbacks import MONITORING_CALLBACK
|
|
19
|
-
from agents.llm_errors import LLMAuthError, raise_if_auth_error
|
|
19
|
+
from agents.llm_errors import LLMAuthError, LLMQuotaError, is_quota_exhausted, raise_if_auth_error
|
|
20
20
|
from agents.prompts import get_tree_plan_prompts
|
|
21
21
|
from agents.retry import LLM_MAX_ATTEMPTS, RetryAction, RetryDecision, classify_llm_error, with_retries
|
|
22
22
|
from infra.monitoring import trace
|
|
@@ -103,7 +103,7 @@ class TreePlannerAgent(MonitoringMixin):
|
|
|
103
103
|
return folded
|
|
104
104
|
|
|
105
105
|
def _draw(self, prompt: str, labelled: dict[str, CandidateGroup]) -> list[TreePlanInsights]:
|
|
106
|
-
"""
|
|
106
|
+
"""Draw concurrently, dropping failed draws unless credentials or quota prevent further work."""
|
|
107
107
|
# Not ``with``: its ``__exit__`` waits for a stalled draw; a copied context carries the
|
|
108
108
|
# monitoring step into each worker.
|
|
109
109
|
pool = ThreadPoolExecutor(max_workers=self.draws)
|
|
@@ -117,6 +117,8 @@ class TreePlannerAgent(MonitoringMixin):
|
|
|
117
117
|
except LLMAuthError:
|
|
118
118
|
raise
|
|
119
119
|
except Exception as exc:
|
|
120
|
+
if is_quota_exhausted(exc):
|
|
121
|
+
raise LLMQuotaError(exc) from exc
|
|
120
122
|
failures.append(exc)
|
|
121
123
|
logger.warning("[Planner] draw failed: %s", exc)
|
|
122
124
|
finally:
|
|
@@ -140,6 +140,7 @@ static_analyzer/node.py
|
|
|
140
140
|
static_analyzer/programming_language.py
|
|
141
141
|
static_analyzer/scanner.py
|
|
142
142
|
static_analyzer/typescript_config_scanner.py
|
|
143
|
+
static_analyzer/typescript_workspace.py
|
|
143
144
|
static_analyzer/workspace_dependencies.py
|
|
144
145
|
static_analyzer/cfg/__init__.py
|
|
145
146
|
static_analyzer/cfg/call_graph.py
|
|
@@ -184,6 +185,8 @@ static_analyzer/engine/adapters/rust_adapter.py
|
|
|
184
185
|
static_analyzer/engine/adapters/typescript_adapter.py
|
|
185
186
|
static_analyzer/lsp_client/__init__.py
|
|
186
187
|
static_analyzer/lsp_client/diagnostics.py
|
|
188
|
+
static_analyzer/typescript_plugin/node_modules/codeboarding-workspace-sources/index.js
|
|
189
|
+
static_analyzer/typescript_plugin/node_modules/codeboarding-workspace-sources/package.json
|
|
187
190
|
tests/test_persisted_analysis_artifacts.py
|
|
188
191
|
tests/test_pyproject_packages.py
|
|
189
192
|
tests/test_view_instructions.py
|
|
@@ -5,7 +5,8 @@ from pathlib import Path
|
|
|
5
5
|
from tqdm import tqdm
|
|
6
6
|
|
|
7
7
|
from agents.llm_config import LLMConfigError
|
|
8
|
-
from agents.llm_errors import LLMAuthError
|
|
8
|
+
from agents.llm_errors import LLMAuthError, LLMQuotaError
|
|
9
|
+
from diagram_analysis.exceptions import ScopeSemanticsError
|
|
9
10
|
from codeboarding_cli.bootstrap import bootstrap_environment, resolve_local_run_paths
|
|
10
11
|
from codeboarding_cli.view_instructions import print_view_instructions
|
|
11
12
|
from codeboarding_workflows.analysis import run_full
|
|
@@ -13,7 +14,6 @@ from codeboarding_workflows.orchestration import run_analysis_pipeline
|
|
|
13
14
|
from codeboarding_workflows.sources import SourceContext, local_source, remote_source
|
|
14
15
|
from infra.constants import CLI_ROOT_DOCUMENT_NAME
|
|
15
16
|
from diagram_analysis import DEFAULT_DEPTH_CAP, RunContext, RunPaths
|
|
16
|
-
from diagram_analysis.exceptions import ScopeSemanticsError
|
|
17
17
|
from infra.monitoring import monitor_execution
|
|
18
18
|
from infra.monitoring.paths import get_monitoring_run_dir
|
|
19
19
|
from output_generators.rendering import render_docs
|
|
@@ -161,7 +161,7 @@ def _run_remote(args: argparse.Namespace) -> None:
|
|
|
161
161
|
upload=args.upload,
|
|
162
162
|
should_monitor=should_monitor,
|
|
163
163
|
)
|
|
164
|
-
except (LLMAuthError, ScopeSemanticsError):
|
|
164
|
+
except (LLMAuthError, LLMQuotaError, ScopeSemanticsError):
|
|
165
165
|
# The next repository would fail the same way, so stop the run.
|
|
166
166
|
raise
|
|
167
167
|
except Exception:
|
{codeboarding-0.15.0 → codeboarding-0.15.2}/codeboarding_cli/commands/incremental_analysis.py
RENAMED
|
@@ -5,12 +5,12 @@ import sys
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from agents.llm_config import LLMConfigError
|
|
8
|
-
from agents.llm_errors import LLMAuthError
|
|
8
|
+
from agents.llm_errors import LLMAuthError, LLMQuotaError
|
|
9
|
+
from diagram_analysis.exceptions import ScopeSemanticsError
|
|
9
10
|
from codeboarding_cli.bootstrap import bootstrap_environment, resolve_local_run_paths
|
|
10
11
|
from codeboarding_cli.view_instructions import print_view_instructions
|
|
11
12
|
from codeboarding_workflows.analysis import BaselineUnavailableError, run_incremental
|
|
12
13
|
from diagram_analysis import RunContext
|
|
13
|
-
from diagram_analysis.exceptions import ScopeSemanticsError
|
|
14
14
|
from diagram_analysis.run_mode import RunMode
|
|
15
15
|
from infra.utils import monitoring_enabled
|
|
16
16
|
|
|
@@ -61,7 +61,7 @@ def run_from_args(args: argparse.Namespace, parser: argparse.ArgumentParser) ->
|
|
|
61
61
|
# to prompt for a full run; no stack trace needed.
|
|
62
62
|
logger.info("Incremental unavailable: %s", exc)
|
|
63
63
|
_emit_error(str(exc))
|
|
64
|
-
except (LLMAuthError, ScopeSemanticsError):
|
|
64
|
+
except (LLMAuthError, LLMQuotaError, ScopeSemanticsError):
|
|
65
65
|
# A full run would fail the same way, so don't ask for one.
|
|
66
66
|
raise
|
|
67
67
|
except Exception as exc:
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import logging
|
|
2
3
|
import os
|
|
3
4
|
import sys
|
|
4
|
-
import traceback
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from agents.llm_config import CODEBOARDING_KEY_TAIL
|
|
8
|
-
from agents.llm_errors import EXIT_AUTH_ERROR, EXIT_QUOTA_EXHAUSTED, LLMAuthError
|
|
8
|
+
from agents.llm_errors import EXIT_AUTH_ERROR, EXIT_QUOTA_EXHAUSTED, LLMAuthError, LLMQuotaError
|
|
9
9
|
from codeboarding_cli.bootstrap import resolve_local_run_paths
|
|
10
10
|
from codeboarding_cli.commands import full_analysis, incremental_analysis, partial_analysis
|
|
11
|
-
from diagram_analysis.exceptions import ScopeSemanticsError
|
|
12
11
|
from output_generators import SUPPORTED_FORMATS, render as render_output
|
|
13
12
|
from infra.utils import ANALYSIS_FILENAME
|
|
14
13
|
|
|
15
14
|
_SUBCOMMANDS = {"full", "incremental", "partial"}
|
|
15
|
+
# CodeBoarding-action/scripts/analyze_repository.py parses this prefix; keep it stable.
|
|
16
|
+
_ERROR_PREFIX = "CodeBoarding: "
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
def _build_shared_parser() -> argparse.ArgumentParser:
|
|
@@ -108,22 +110,15 @@ def _dispatch(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None
|
|
|
108
110
|
output_dir=run_paths.output_dir,
|
|
109
111
|
)
|
|
110
112
|
except LLMAuthError as exc:
|
|
111
|
-
|
|
112
|
-
# actionable line (no traceback) and exit with a distinct code.
|
|
113
|
-
print(f"\nCodeBoarding: {exc}", file=sys.stderr)
|
|
113
|
+
logger.error("%s%s", _ERROR_PREFIX, exc)
|
|
114
114
|
if exc.key_tail != CODEBOARDING_KEY_TAIL:
|
|
115
|
-
|
|
115
|
+
logger.error(
|
|
116
116
|
"Check your LLM provider API key (shell env or ~/.codeboarding/config.toml) and re-run.",
|
|
117
|
-
file=sys.stderr,
|
|
118
117
|
)
|
|
119
118
|
raise SystemExit(EXIT_AUTH_ERROR) from exc
|
|
120
|
-
except
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
# Same exit as any failure, but with a code callers (the GitHub Action) can name.
|
|
124
|
-
traceback.print_exception(exc)
|
|
125
|
-
print("\nCodeBoarding: the LLM provider's token or credit quota is exhausted.", file=sys.stderr)
|
|
126
|
-
print("Add credits or raise the quota for this provider, then re-run.", file=sys.stderr)
|
|
119
|
+
except LLMQuotaError as exc:
|
|
120
|
+
logger.exception("LLM quota failure")
|
|
121
|
+
logger.error("%s%s", _ERROR_PREFIX, exc)
|
|
127
122
|
raise SystemExit(EXIT_QUOTA_EXHAUSTED) from exc
|
|
128
123
|
|
|
129
124
|
|
|
@@ -30,7 +30,7 @@ from diagram_analysis.incremental_update import (
|
|
|
30
30
|
from agents.incremental_results import RecursiveScopeUpdateResult
|
|
31
31
|
from agents.file_index_models import FileEntry, FileMethodGroup, MethodEntry
|
|
32
32
|
from agents.llm_config import initialize_agent_llm
|
|
33
|
-
from agents.llm_errors import LLMAuthError, llm_failure_properties
|
|
33
|
+
from agents.llm_errors import LLMAuthError, LLMQuotaError, is_quota_exhausted, llm_failure_properties
|
|
34
34
|
from agents.relation_edges import (
|
|
35
35
|
drop_misattributed_edges,
|
|
36
36
|
index_relation_endpoints,
|
|
@@ -818,9 +818,11 @@ class DiagramGenerator:
|
|
|
818
818
|
incremental,
|
|
819
819
|
enclosing_names=enclosing_names,
|
|
820
820
|
)
|
|
821
|
-
except LLMAuthError:
|
|
821
|
+
except (LLMAuthError, LLMQuotaError, ScopeSemanticsError):
|
|
822
822
|
raise
|
|
823
823
|
except Exception as error:
|
|
824
|
+
if is_quota_exhausted(error):
|
|
825
|
+
raise LLMQuotaError(error) from error
|
|
824
826
|
raise ScopeSemanticsError(scope.scope_id, telemetry_properties=llm_failure_properties(error)) from error
|
|
825
827
|
if semantics is None:
|
|
826
828
|
with self._naming_counts_lock:
|
|
@@ -1120,7 +1122,7 @@ class DiagramGenerator:
|
|
|
1120
1122
|
new_components = [child for child in analysis.components if child.component_id in preclustered_scopes]
|
|
1121
1123
|
|
|
1122
1124
|
return component.component_id, analysis, new_components
|
|
1123
|
-
except (LLMAuthError, ScopeSemanticsError):
|
|
1125
|
+
except (LLMAuthError, LLMQuotaError, ScopeSemanticsError):
|
|
1124
1126
|
# An LLM failure aborts the whole run rather than shipping this component with deterministic names.
|
|
1125
1127
|
raise
|
|
1126
1128
|
except Exception:
|
|
@@ -1335,7 +1337,7 @@ class DiagramGenerator:
|
|
|
1335
1337
|
stats["completed"] += 1
|
|
1336
1338
|
try:
|
|
1337
1339
|
outcomes.append((component, level, future.result()))
|
|
1338
|
-
except (LLMAuthError, ScopeSemanticsError):
|
|
1340
|
+
except (LLMAuthError, LLMQuotaError, ScopeSemanticsError):
|
|
1339
1341
|
for pending in future_to_task:
|
|
1340
1342
|
pending.cancel()
|
|
1341
1343
|
raise
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codeboarding"
|
|
7
|
-
version = "0.15.
|
|
7
|
+
version = "0.15.2"
|
|
8
8
|
description = "Interactive Diagrams for Code"
|
|
9
9
|
readme = "PYPI.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -106,6 +106,10 @@ packages = [
|
|
|
106
106
|
]
|
|
107
107
|
include-package-data = true
|
|
108
108
|
|
|
109
|
+
[tool.setuptools.package-data]
|
|
110
|
+
# tsserver loads a plugin only from <location>/node_modules/<name>.
|
|
111
|
+
static_analyzer = ["typescript_plugin/node_modules/codeboarding-workspace-sources/*"]
|
|
112
|
+
|
|
109
113
|
[project.scripts]
|
|
110
114
|
codeboarding = "codeboarding_cli.main:main"
|
|
111
115
|
codeboarding-setup = "infra.install:main"
|
|
@@ -363,6 +363,10 @@ class StaticAnalyzer:
|
|
|
363
363
|
continue
|
|
364
364
|
try:
|
|
365
365
|
started.append((engine_config, self._spawn_engine_client(engine_config)))
|
|
366
|
+
except StaticAnalysisFatalError:
|
|
367
|
+
for _, client in started: # never handed to ``stop_clients``
|
|
368
|
+
client.shutdown()
|
|
369
|
+
raise
|
|
366
370
|
except Exception as exc:
|
|
367
371
|
logger.exception(
|
|
368
372
|
f"Failed to start engine LSP client for {adapter.language}; "
|
|
@@ -500,6 +504,7 @@ class StaticAnalyzer:
|
|
|
500
504
|
extra_client_capabilities=getattr(adapter, "extra_client_capabilities", {}) or {},
|
|
501
505
|
error_verdict=adapter.error_verdict,
|
|
502
506
|
workspace_folders=workspace_folders,
|
|
507
|
+
commands=adapter.get_lsp_commands(project_path),
|
|
503
508
|
)
|
|
504
509
|
try:
|
|
505
510
|
engine_client.start()
|
|
@@ -918,6 +923,12 @@ class StaticAnalyzer:
|
|
|
918
923
|
adapter, project_path = engine_config.adapter, engine_config.project_path
|
|
919
924
|
language = adapter.results_language
|
|
920
925
|
changed_files = self._changed_files_for_language(project_path, cached_sha, adapter.language)
|
|
926
|
+
if changed_files is not None and adapter.rebuilds_for(changed_files, project_path):
|
|
927
|
+
logger.info(f"warmstart {adapter.language}: a change alters how imports resolve; rebuilding")
|
|
928
|
+
changed_files = None
|
|
929
|
+
if resident_client is not None:
|
|
930
|
+
# Why: a server resolves imports as the project was when it started; a stopped one is replaced.
|
|
931
|
+
resident_client.shutdown()
|
|
921
932
|
# A full re-analysis is the full pass's, so a split config runs as its parts. Changed files stay on the
|
|
922
933
|
# config's own server: tsserver loads only the projects of the files it opens.
|
|
923
934
|
if changed_files is None:
|
|
@@ -74,8 +74,9 @@ _LEGACY_CACHE_SUBDIR = "cache"
|
|
|
74
74
|
# v11: every language builds its call edges from definitions at the call site.
|
|
75
75
|
# v12: a large TypeScript monorepo runs one server per tsconfig project, so a graph one out-of-memory
|
|
76
76
|
# server left without edges is rebuilt; bases, implementations and edge-only declarations link across engines.
|
|
77
|
+
# v13: a TypeScript call into a sibling workspace package resolves to its sources without an install or build.
|
|
77
78
|
# Older pickles are treated as cache misses and re-run.
|
|
78
|
-
_TAG_VERSION = "
|
|
79
|
+
_TAG_VERSION = "v13"
|
|
79
80
|
|
|
80
81
|
|
|
81
82
|
class StaticAnalysisCache:
|
{codeboarding-0.15.0 → codeboarding-0.15.2}/static_analyzer/engine/adapters/typescript_adapter.py
RENAMED
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
from collections.abc import Collection
|
|
5
8
|
from pathlib import Path
|
|
6
9
|
|
|
7
10
|
from infra.repo_utils.ignore import RepoIgnoreManager
|
|
8
11
|
from static_analyzer.config import Language
|
|
9
12
|
from static_analyzer.engine.language_adapter import LanguageAdapter
|
|
13
|
+
from static_analyzer.exceptions import WorkspaceSetupError
|
|
14
|
+
from static_analyzer.typescript_workspace import PLUGIN_LOCATION, PLUGIN_NAME, workspace_members
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
10
17
|
|
|
11
18
|
|
|
12
19
|
class TypeScriptAdapter(LanguageAdapter):
|
|
@@ -34,7 +41,30 @@ class TypeScriptAdapter(LanguageAdapter):
|
|
|
34
41
|
def get_lsp_init_options(self, ignore_manager: RepoIgnoreManager | None = None) -> dict:
|
|
35
42
|
# Why: by default a syntax-only server answers while the project still loads, and a definition
|
|
36
43
|
# that needs an inferred type -- ``.then(t => t.serialize())`` -- comes back empty on a cold start.
|
|
37
|
-
|
|
44
|
+
# Why the plugin: a call into a sibling workspace package of an uninstalled, unbuilt checkout resolves nowhere;
|
|
45
|
+
# the plugin resolves it to the member's sources (see ``typescript_workspace``).
|
|
46
|
+
return {
|
|
47
|
+
"tsserver": {"useSyntaxServer": "never"},
|
|
48
|
+
"plugins": [{"name": PLUGIN_NAME, "location": str(PLUGIN_LOCATION)}],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def get_lsp_commands(self, project_root: Path) -> list[dict]:
|
|
52
|
+
"""Hands the workspace members to the plugin; tsserver gives the configuration to each project it loads."""
|
|
53
|
+
members = workspace_members(project_root)
|
|
54
|
+
if not members:
|
|
55
|
+
return []
|
|
56
|
+
if not (PLUGIN_LOCATION / "node_modules" / PLUGIN_NAME / "index.js").is_file():
|
|
57
|
+
raise WorkspaceSetupError(f"{PLUGIN_NAME} is missing from this install; reinstall CodeBoarding")
|
|
58
|
+
logger.info(f"tsserver resolves {len(members)} workspace members of {project_root} to their sources")
|
|
59
|
+
configuration = {"root": str(project_root), "members": {name: str(path) for name, path in members.items()}}
|
|
60
|
+
return [{"command": "_typescript.configurePlugin", "arguments": [PLUGIN_NAME, configuration]}]
|
|
61
|
+
|
|
62
|
+
def rebuilds_for(self, changed_files: Collection[Path], project_root: Path) -> bool:
|
|
63
|
+
"""Whether a change alters how imports resolve: a tsconfig, a workspace declaration, or a workspace's manifest."""
|
|
64
|
+
names = {path.name for path in changed_files}
|
|
65
|
+
if "pnpm-workspace.yaml" in names or any(re.fullmatch(r"[tj]sconfig.*\.json", name) for name in names):
|
|
66
|
+
return True
|
|
67
|
+
return "package.json" in names and bool(workspace_members(project_root))
|
|
38
68
|
|
|
39
69
|
def get_lsp_heap_options(self, heap_bytes: int) -> dict:
|
|
40
70
|
# Why: at Node's default heap tsserver runs out of memory on a large tsconfig project and answers nothing.
|
|
@@ -271,6 +271,14 @@ class LanguageAdapter(ABC):
|
|
|
271
271
|
"""
|
|
272
272
|
return {}
|
|
273
273
|
|
|
274
|
+
def get_lsp_commands(self, project_root: Path) -> list[dict]:
|
|
275
|
+
"""``workspace/executeCommand`` params the server is sent once initialized, and again on a restart."""
|
|
276
|
+
return []
|
|
277
|
+
|
|
278
|
+
def rebuilds_for(self, changed_files: Collection[Path], project_root: Path) -> bool:
|
|
279
|
+
"""Whether a warm start over *changed_files* must re-analyse the whole project on a fresh server."""
|
|
280
|
+
return False
|
|
281
|
+
|
|
274
282
|
def workspace_folders(self, project_root: Path, source_files: Sequence[Path]) -> list[Path]:
|
|
275
283
|
"""Workspace folders to announce to the server in place of the project, or none."""
|
|
276
284
|
return []
|
|
@@ -21,7 +21,7 @@ from pathlib import Path
|
|
|
21
21
|
from static_analyzer.config import LANGUAGE_ID_BY_SUFFIX
|
|
22
22
|
from static_analyzer.engine.lsp_constants import REQUEST_BATCH_SIZE
|
|
23
23
|
from static_analyzer.engine.utils import uri_to_path
|
|
24
|
-
from static_analyzer.exceptions import LanguageServerStalledError, StaticAnalysisFatalError
|
|
24
|
+
from static_analyzer.exceptions import LanguageServerStalledError, StaticAnalysisFatalError, WorkspaceSetupError
|
|
25
25
|
from static_analyzer.lsp_client.diagnostics import FileDiagnosticsMap, LSPDiagnostic
|
|
26
26
|
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
@@ -113,6 +113,7 @@ class LSPClient:
|
|
|
113
113
|
extra_client_capabilities: dict | None = None,
|
|
114
114
|
error_verdict: Callable[[dict], ErrorVerdict] | None = None,
|
|
115
115
|
workspace_folders: list[Path] | None = None,
|
|
116
|
+
commands: list[dict] | None = None,
|
|
116
117
|
) -> None:
|
|
117
118
|
self._command = command
|
|
118
119
|
self._project_root = project_root
|
|
@@ -129,6 +130,7 @@ class LSPClient:
|
|
|
129
130
|
self._error_verdict = error_verdict
|
|
130
131
|
# Announced in place of the project folder, for a server handed a workspace described apart from it.
|
|
131
132
|
self._workspace_folders = workspace_folders or []
|
|
133
|
+
self._commands = commands or []
|
|
132
134
|
self._process: subprocess.Popen | None = None # type: ignore[type-arg]
|
|
133
135
|
self._stdout_fd: int | None = None
|
|
134
136
|
self._request_id = 0
|
|
@@ -257,6 +259,8 @@ class LSPClient:
|
|
|
257
259
|
if isinstance(init_result, dict):
|
|
258
260
|
self._server_capabilities = init_result.get("capabilities") or {}
|
|
259
261
|
self._send_notification("initialized", {})
|
|
262
|
+
for command in self._commands:
|
|
263
|
+
self._send_request("workspace/executeCommand", command, timeout=self._default_timeout, required=True)
|
|
260
264
|
|
|
261
265
|
# LSP servers receive configuration through three mechanisms:
|
|
262
266
|
# 1. initializationOptions — sent in the initialize request above.
|
|
@@ -706,8 +710,10 @@ class LSPClient:
|
|
|
706
710
|
answers.append(BatchAnswer(parsed, req_id not in unserved, req_id in retryable))
|
|
707
711
|
return answers
|
|
708
712
|
|
|
709
|
-
def _send_request(
|
|
710
|
-
|
|
713
|
+
def _send_request(
|
|
714
|
+
self, method: str, params: dict | list | None, timeout: int, required: bool = False
|
|
715
|
+
) -> dict | list | None:
|
|
716
|
+
"""Send a JSON-RPC request and wait for the response; a *required* one that does not succeed is fatal."""
|
|
711
717
|
if self.server_failure:
|
|
712
718
|
self.check_alive()
|
|
713
719
|
self._request_id += 1
|
|
@@ -731,6 +737,8 @@ class LSPClient:
|
|
|
731
737
|
|
|
732
738
|
if "error" in msg:
|
|
733
739
|
error = msg["error"]
|
|
740
|
+
if required:
|
|
741
|
+
raise WorkspaceSetupError(f"The language server rejected {method} {params}: {error}")
|
|
734
742
|
if isinstance(error, dict) and error.get("code") == LSP_METHOD_NOT_FOUND:
|
|
735
743
|
raise MethodNotFoundError(error.get("message", "Method not found"))
|
|
736
744
|
logger.warning("LSP error for request %d (%s): %s", req_id, method, error)
|
|
@@ -742,6 +750,8 @@ class LSPClient:
|
|
|
742
750
|
return None
|
|
743
751
|
return msg.get("result")
|
|
744
752
|
|
|
753
|
+
if required:
|
|
754
|
+
raise WorkspaceSetupError(f"The language server did not answer {method} {params} within {timeout}s")
|
|
745
755
|
raise TimeoutError(f"Timeout waiting for LSP response to request {req_id}")
|
|
746
756
|
|
|
747
757
|
def _send_notification(self, method: str, params: dict | list | None) -> None:
|
|
@@ -5,5 +5,9 @@ class StaticAnalysisFatalError(RuntimeError):
|
|
|
5
5
|
"""Raised when continuing would produce misleading cached analysis."""
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
class WorkspaceSetupError(StaticAnalysisFatalError):
|
|
9
|
+
"""Raised when the workspace a language server must be set up with cannot be, so its answers would miss edges."""
|
|
10
|
+
|
|
11
|
+
|
|
8
12
|
class LanguageServerStalledError(StaticAnalysisFatalError):
|
|
9
13
|
"""Raised when a language server stays up but the backend that answers for it has died."""
|