codeboarding 0.15.0__tar.gz → 0.15.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. {codeboarding-0.15.0 → codeboarding-0.15.1}/PKG-INFO +1 -1
  2. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding.egg-info/PKG-INFO +1 -1
  3. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding.egg-info/SOURCES.txt +3 -0
  4. {codeboarding-0.15.0 → codeboarding-0.15.1}/pyproject.toml +5 -1
  5. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/__init__.py +11 -0
  6. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/analysis_cache.py +2 -1
  7. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/adapters/typescript_adapter.py +31 -1
  8. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/language_adapter.py +8 -0
  9. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/lsp_client.py +13 -3
  10. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/exceptions.py +4 -0
  11. codeboarding-0.15.1/static_analyzer/typescript_plugin/node_modules/codeboarding-workspace-sources/index.js +132 -0
  12. codeboarding-0.15.1/static_analyzer/typescript_plugin/node_modules/codeboarding-workspace-sources/package.json +6 -0
  13. codeboarding-0.15.1/static_analyzer/typescript_workspace.py +81 -0
  14. {codeboarding-0.15.0 → codeboarding-0.15.1}/LICENSE +0 -0
  15. {codeboarding-0.15.0 → codeboarding-0.15.1}/PYPI.md +0 -0
  16. {codeboarding-0.15.0 → codeboarding-0.15.1}/README.md +0 -0
  17. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/__init__.py +0 -0
  18. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/agent_responses.py +0 -0
  19. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/change_status.py +0 -0
  20. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/component_ownership.py +0 -0
  21. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/constants.py +0 -0
  22. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/content_hash.py +0 -0
  23. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/file_index_models.py +0 -0
  24. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/incremental_results.py +0 -0
  25. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/llm_config.py +0 -0
  26. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/llm_errors.py +0 -0
  27. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/llm_renderers/__init__.py +0 -0
  28. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/llm_renderers/call_graph.py +0 -0
  29. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/llm_renderers/scope.py +0 -0
  30. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/model_capabilities.py +0 -0
  31. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/planner_agent.py +0 -0
  32. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/prompts/__init__.py +0 -0
  33. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/prompts/profiles.py +0 -0
  34. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/prompts/shared.py +0 -0
  35. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/relation_edges.py +0 -0
  36. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/retry.py +0 -0
  37. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/scope_analysis_agent.py +0 -0
  38. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/scope_ids.py +0 -0
  39. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/tools/__init__.py +0 -0
  40. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/tools/base.py +0 -0
  41. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/tools/get_method_calls.py +0 -0
  42. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/tools/read_file.py +0 -0
  43. {codeboarding-0.15.0 → codeboarding-0.15.1}/agents/tree_planner_agent.py +0 -0
  44. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding.egg-info/dependency_links.txt +0 -0
  45. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding.egg-info/entry_points.txt +0 -0
  46. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding.egg-info/requires.txt +0 -0
  47. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding.egg-info/top_level.txt +0 -0
  48. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_cli/__init__.py +0 -0
  49. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_cli/bootstrap.py +0 -0
  50. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_cli/commands/__init__.py +0 -0
  51. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_cli/commands/full_analysis.py +0 -0
  52. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_cli/commands/incremental_analysis.py +0 -0
  53. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_cli/commands/partial_analysis.py +0 -0
  54. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_cli/github_action.py +0 -0
  55. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_cli/main.py +0 -0
  56. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_cli/render.py +0 -0
  57. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_cli/view_instructions.py +0 -0
  58. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_workflows/__init__.py +0 -0
  59. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_workflows/analysis.py +0 -0
  60. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_workflows/fingerprint_diff.py +0 -0
  61. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_workflows/orchestration.py +0 -0
  62. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_workflows/sources/__init__.py +0 -0
  63. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_workflows/sources/local.py +0 -0
  64. {codeboarding-0.15.0 → codeboarding-0.15.1}/codeboarding_workflows/sources/remote.py +0 -0
  65. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/__init__.py +0 -0
  66. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/analysis_json.py +0 -0
  67. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/cluster_relations.py +0 -0
  68. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/diagram_generator.py +0 -0
  69. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/exceptions.py +0 -0
  70. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/file_coverage.py +0 -0
  71. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/file_index.py +0 -0
  72. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/incremental_changes.py +0 -0
  73. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/incremental_update.py +0 -0
  74. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/io_utils.py +0 -0
  75. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/reference_resolver.py +0 -0
  76. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/run_context.py +0 -0
  77. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/run_mode.py +0 -0
  78. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/scope_assembly.py +0 -0
  79. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/scope_plan.py +0 -0
  80. {codeboarding-0.15.0 → codeboarding-0.15.1}/diagram_analysis/tree_shape.py +0 -0
  81. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/__init__.py +0 -0
  82. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/checks/__init__.py +0 -0
  83. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/checks/circular_deps.py +0 -0
  84. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/checks/function_size.py +0 -0
  85. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/checks/unused_code_diagnostics.py +0 -0
  86. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/config.py +0 -0
  87. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/main.py +0 -0
  88. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/models.py +0 -0
  89. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/plugins/__init__.py +0 -0
  90. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/plugins/plugin_loader.py +0 -0
  91. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/plugins/protocols.py +0 -0
  92. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/plugins/registry.py +0 -0
  93. {codeboarding-0.15.0 → codeboarding-0.15.1}/health/runner.py +0 -0
  94. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/__init__.py +0 -0
  95. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/clustering_ids.py +0 -0
  96. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/constants.py +0 -0
  97. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/install.py +0 -0
  98. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/logging_config.py +0 -0
  99. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/__init__.py +0 -0
  100. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/callbacks.py +0 -0
  101. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/context.py +0 -0
  102. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/mixin.py +0 -0
  103. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/paths.py +0 -0
  104. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/stats.py +0 -0
  105. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/telemetry/__init__.py +0 -0
  106. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/telemetry/device_id.py +0 -0
  107. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/telemetry/events.py +0 -0
  108. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/telemetry/schemas.py +0 -0
  109. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/telemetry/service.py +0 -0
  110. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/monitoring/writers.py +0 -0
  111. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/repo_utils/__init__.py +0 -0
  112. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/repo_utils/change_detector.py +0 -0
  113. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/repo_utils/errors.py +0 -0
  114. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/repo_utils/git_ops.py +0 -0
  115. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/repo_utils/ignore.py +0 -0
  116. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/repo_utils/path_utils.py +0 -0
  117. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/tool_registry/__init__.py +0 -0
  118. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/tool_registry/grammars.py +0 -0
  119. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/tool_registry/installers.py +0 -0
  120. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/tool_registry/java.py +0 -0
  121. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/tool_registry/manifest.py +0 -0
  122. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/tool_registry/paths.py +0 -0
  123. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/tool_registry/registry.py +0 -0
  124. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/user_config.py +0 -0
  125. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/utils.py +0 -0
  126. {codeboarding-0.15.0 → codeboarding-0.15.1}/infra/vscode_constants.py +0 -0
  127. {codeboarding-0.15.0 → codeboarding-0.15.1}/output_generators/__init__.py +0 -0
  128. {codeboarding-0.15.0 → codeboarding-0.15.1}/output_generators/html.py +0 -0
  129. {codeboarding-0.15.0 → codeboarding-0.15.1}/output_generators/html_template.py +0 -0
  130. {codeboarding-0.15.0 → codeboarding-0.15.1}/output_generators/markdown.py +0 -0
  131. {codeboarding-0.15.0 → codeboarding-0.15.1}/output_generators/mdx.py +0 -0
  132. {codeboarding-0.15.0 → codeboarding-0.15.1}/output_generators/rendering.py +0 -0
  133. {codeboarding-0.15.0 → codeboarding-0.15.1}/output_generators/sphinx.py +0 -0
  134. {codeboarding-0.15.0 → codeboarding-0.15.1}/setup.cfg +0 -0
  135. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/analysis_result.py +0 -0
  136. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/cfg/__init__.py +0 -0
  137. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/cfg/call_graph.py +0 -0
  138. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/cfg/edge.py +0 -0
  139. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/cfg/location_key.py +0 -0
  140. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/__init__.py +0 -0
  141. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/exceptions.py +0 -0
  142. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/models.py +0 -0
  143. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/names/__init__.py +0 -0
  144. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/names/draft.py +0 -0
  145. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/names/frontier.py +0 -0
  146. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/names/inventory.py +0 -0
  147. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/names/replay.py +0 -0
  148. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/names/spec.py +0 -0
  149. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/names/tokens.py +0 -0
  150. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/clustering/service.py +0 -0
  151. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/config.py +0 -0
  152. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/csharp_config_scanner.py +0 -0
  153. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/dotnet_sdk.py +0 -0
  154. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/dotnet_solution.py +0 -0
  155. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/__init__.py +0 -0
  156. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/adapters/__init__.py +0 -0
  157. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/adapters/csharp_adapter.py +0 -0
  158. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/adapters/go_adapter.py +0 -0
  159. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/adapters/java_adapter.py +0 -0
  160. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/adapters/kotlin_adapter.py +0 -0
  161. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/adapters/php_adapter.py +0 -0
  162. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/adapters/python_adapter.py +0 -0
  163. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/adapters/rust_adapter.py +0 -0
  164. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/call_graph_builder.py +0 -0
  165. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/edge_build_context.py +0 -0
  166. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/edge_builder.py +0 -0
  167. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/hierarchy_builder.py +0 -0
  168. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/lsp_constants.py +0 -0
  169. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/lsp_recycler.py +0 -0
  170. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/models.py +0 -0
  171. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/process_memory.py +0 -0
  172. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/progress.py +0 -0
  173. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/protocols.py +0 -0
  174. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/result_converter.py +0 -0
  175. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/source_inspector.py +0 -0
  176. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/symbol_table.py +0 -0
  177. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/engine/utils.py +0 -0
  178. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/external_calls.py +0 -0
  179. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/graph_definitions.py +0 -0
  180. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/incremental_orchestrator.py +0 -0
  181. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/internal_references.py +0 -0
  182. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/java_config_scanner.py +0 -0
  183. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/java_utils.py +0 -0
  184. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/language_results.py +0 -0
  185. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/lsp_client/__init__.py +0 -0
  186. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/lsp_client/diagnostics.py +0 -0
  187. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/node.py +0 -0
  188. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/programming_language.py +0 -0
  189. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/scanner.py +0 -0
  190. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/typescript_config_scanner.py +0 -0
  191. {codeboarding-0.15.0 → codeboarding-0.15.1}/static_analyzer/workspace_dependencies.py +0 -0
  192. {codeboarding-0.15.0 → codeboarding-0.15.1}/tests/test_persisted_analysis_artifacts.py +0 -0
  193. {codeboarding-0.15.0 → codeboarding-0.15.1}/tests/test_pyproject_packages.py +0 -0
  194. {codeboarding-0.15.0 → codeboarding-0.15.1}/tests/test_view_instructions.py +0 -0
  195. {codeboarding-0.15.0 → codeboarding-0.15.1}/tests/test_windows_compatibility.py +0 -0
  196. {codeboarding-0.15.0 → codeboarding-0.15.1}/tests/test_windows_encoding.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.15.0
3
+ Version: 0.15.1
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.15.0
3
+ Version: 0.15.1
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codeboarding"
7
- version = "0.15.0"
7
+ version = "0.15.1"
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 = "v12"
79
+ _TAG_VERSION = "v13"
79
80
 
80
81
 
81
82
  class StaticAnalysisCache:
@@ -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
- return {"tsserver": {"useSyntaxServer": "never"}}
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(self, method: str, params: dict | list | None, timeout: int) -> dict | list | None:
710
- """Send a JSON-RPC request and wait for the response."""
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."""
@@ -0,0 +1,132 @@
1
+ // tsserver plugin: a call into a sibling workspace package resolves to its sources in a checkout neither installed nor
2
+ // built. Core hands it {root, members} through `_typescript.configurePlugin` (static_analyzer/typescript_workspace.py).
3
+ // 1. Links: a lookup under <dir>/node_modules/<member> answers from the member's folder, as an install links it.
4
+ // 2. References: a project references the configs of each member it imports, so TypeScript's own
5
+ // source-of-project-reference redirect maps the member's missing build output (outDir) back to its sources.
6
+ // 3. An entry no config emits (a bundler builds it) is read as its source under src/, as Knip maps it.
7
+ "use strict";
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+
11
+ const HOOKED = Symbol("codeboarding-workspace-sources");
12
+ // <before>/node_modules/<package name><rest>, the last node_modules segment.
13
+ const NODE_MODULES_PATH = /^(.*)\/node_modules\/((?:@[^/]+\/)?[^/@][^/]*)(\/.*)?$/;
14
+ // The package a module specifier names: `from "x"`, `import "x"`, `import("x")`, `require("x")`.
15
+ const SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(\s*|\brequire\s*\(\s*|\bimport\s+)["'`]((?:@[^/"'`]+\/)?[^./"'`@][^/"'`]*)/g;
16
+ const ENTRY_FIELDS = ["main", "module", "types", "typings", "exports"];
17
+ const SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
18
+
19
+ // Forward slashes and a lower-case drive letter, as tsserver spells a path.
20
+ const norm = (file) => file.replace(/\\/g, "/").replace(/^[A-Z]:/, (drive) => drive.toLowerCase());
21
+
22
+ function linkMembers(host, root, members) {
23
+ const linked = (file) => {
24
+ const match = typeof file === "string" && norm(file).startsWith(`${root}/`) && NODE_MODULES_PATH.exec(norm(file));
25
+ const folder = match && members.get(match[2]);
26
+ return folder ? folder + (match[3] || "") : null;
27
+ };
28
+ // Why: module resolution skips a node_modules folder that is not on disk; an install hoists the links to the root.
29
+ const scopes = [...members.keys()].filter((name) => name.startsWith("@")).map((name) => name.split("/")[0]);
30
+ const linkParents = new Set([`${root}/node_modules`, ...scopes.map((scope) => `${root}/node_modules/${scope}`)]);
31
+ const manifests = new Map([...members.values()].map((folder) => [`${folder}/package.json`, folder]));
32
+ for (const name of ["fileExists", "realpath"]) {
33
+ const original = host[name];
34
+ if (typeof original === "function") host[name] = (file, ...rest) => original.call(host, linked(file) || file, ...rest);
35
+ }
36
+ const { readFile, directoryExists } = host;
37
+ host.readFile = (file, ...rest) => {
38
+ const target = linked(file) || file;
39
+ const text = readFile.call(host, target, ...rest);
40
+ const folder = text !== undefined && typeof target === "string" && manifests.get(norm(target));
41
+ return folder ? withSources(text, folder) : text;
42
+ };
43
+ host.directoryExists = (dir) => {
44
+ const target = linked(dir);
45
+ if (target) return directoryExists.call(host, target);
46
+ return directoryExists.call(host, dir) || (typeof dir === "string" && linkParents.has(norm(dir)));
47
+ };
48
+ }
49
+
50
+ // *text*, a member's package.json, with each entry missing from disk read as its source under src/ when there is one.
51
+ function withSources(text, folder) {
52
+ let manifest;
53
+ try {
54
+ manifest = JSON.parse(text);
55
+ } catch (error) {
56
+ return text;
57
+ }
58
+ const source = (entry) => {
59
+ if (fs.existsSync(path.resolve(folder, entry))) return entry;
60
+ const stem = entry.replace(/^(\.\/)?[^/]+\//, "src/").replace(/(\.d)?\.[cm]?[jt]sx?$/, "");
61
+ const found = SOURCE_EXTENSIONS.map((extension) => stem + extension).find((file) => fs.existsSync(path.resolve(folder, file)));
62
+ return found ? `./${found}` : entry;
63
+ };
64
+ const replace = (value) => {
65
+ if (typeof value === "string") return source(value);
66
+ if (Array.isArray(value)) return value.map(replace);
67
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, replace(nested)]));
68
+ return value;
69
+ };
70
+ for (const field of ENTRY_FIELDS) if (field in manifest) manifest[field] = replace(manifest[field]);
71
+ return JSON.stringify(manifest);
72
+ }
73
+
74
+ function referenceMembers(project, host, members, configured) {
75
+ const own = configured ? norm(project.getProjectName()) : null;
76
+ const original = host.getProjectReferences;
77
+ const scanned = new Set();
78
+ const used = new Set();
79
+ let added = [];
80
+ // Why scanned as files join, not at create: tsserver enables plugins before it adds a project's root files.
81
+ host.getProjectReferences = () => {
82
+ const declared = (typeof original === "function" && original.call(host)) || [];
83
+ const files = project.getRootFiles().filter((file) => !scanned.has(file));
84
+ if (files.length) {
85
+ for (const file of files) {
86
+ scanned.add(file);
87
+ try {
88
+ for (const match of fs.readFileSync(file, "utf8").matchAll(SPECIFIER)) used.add(match[1]);
89
+ } catch (error) {
90
+ continue;
91
+ }
92
+ }
93
+ // Every tsconfig and jsconfig of each member. Why every one: the redirect then finds whichever emits an entry.
94
+ const have = new Set(declared.map((reference) => norm(path.resolve(reference.path))));
95
+ const folders = [...used].map((name) => members.get(name)).filter((folder) => folder && !have.has(folder));
96
+ added = folders
97
+ .flatMap((folder) => fs.readdirSync(folder).filter((file) => /^[tj]sconfig.*\.json$/.test(file)).sort().map((file) => `${folder}/${file}`))
98
+ .filter((config) => config !== own && !have.has(config))
99
+ .map((config) => ({ path: config }));
100
+ }
101
+ return declared.length || added.length ? [...declared, ...added] : undefined;
102
+ };
103
+ if (!configured && typeof host.getCompilerHost !== "function") {
104
+ // An inferred project has no references of its own, so tsserver gives it neither the redirect nor the compiler
105
+ // host the redirect answers module resolution through. Both as a configured project has them.
106
+ let compilerHost;
107
+ host.setCompilerHost = (created) => {
108
+ compilerHost = created;
109
+ };
110
+ host.getCompilerHost = () => compilerHost;
111
+ host.useSourceOfProjectReferenceRedirect = () => added.length > 0;
112
+ }
113
+ }
114
+
115
+ module.exports = function init({ typescript: ts }) {
116
+ return {
117
+ create(info) {
118
+ const host = info.languageServiceHost;
119
+ const { root, members } = info.config || {};
120
+ if (!root || !members || host[HOOKED]) return info.languageService;
121
+ host[HOOKED] = true;
122
+ const folders = new Map(Object.entries(members).map(([name, folder]) => [name, norm(folder)]));
123
+ linkMembers(host, norm(root), folders);
124
+ const kind = info.project.projectKind;
125
+ if (kind === ts.server.ProjectKind.Configured || kind === ts.server.ProjectKind.Inferred) {
126
+ referenceMembers(info.project, host, folders, kind === ts.server.ProjectKind.Configured);
127
+ }
128
+ info.project.projectService.logger.info(`codeboarding-workspace-sources: ${folders.size} members under ${root}`);
129
+ return info.languageService;
130
+ },
131
+ };
132
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "codeboarding-workspace-sources",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "main": "index.js"
6
+ }
@@ -0,0 +1,81 @@
1
+ """A JavaScript workspace's members, for the tsserver plugin (``typescript_plugin/``) that resolves calls into them.
2
+
3
+ Why: in a checkout neither installed nor built, ``import { f } from "@acme/core"`` resolves nowhere: there is no
4
+ ``node_modules/@acme/core`` link, and the member's ``main``/``types``/``exports`` name a ``dist/`` that does not exist.
5
+ """
6
+
7
+ import json
8
+ import re
9
+ from pathlib import Path
10
+
11
+ import yaml
12
+
13
+ from static_analyzer.exceptions import WorkspaceSetupError
14
+
15
+ PLUGIN_NAME = "codeboarding-workspace-sources"
16
+ PLUGIN_LOCATION = Path(__file__).resolve().parent / "typescript_plugin"
17
+ """tsserver loads a plugin from ``<location>/node_modules/<name>``."""
18
+
19
+
20
+ def workspace_members(repo: Path) -> dict[str, Path]:
21
+ """Each declared member's package name and folder, the first by path winning; empty when *repo* declares none.
22
+
23
+ Why pnpm-workspace.yaml alone when present: pnpm reads no ``workspaces`` from package.json.
24
+ """
25
+ pnpm = repo / "pnpm-workspace.yaml"
26
+ try:
27
+ if pnpm.is_file():
28
+ patterns = (yaml.safe_load(pnpm.read_text(encoding="utf-8")) or {}).get("packages")
29
+ elif (repo / "package.json").is_file():
30
+ patterns = json.loads((repo / "package.json").read_text(encoding="utf-8")).get("workspaces")
31
+ patterns = patterns.get("packages") if isinstance(patterns, dict) else patterns
32
+ else:
33
+ return {}
34
+ patterns = patterns or []
35
+ if not isinstance(patterns, list) or not all(isinstance(pattern, str) for pattern in patterns):
36
+ raise ValueError(f"the member patterns are no list of strings: {patterns!r}")
37
+ expanded = [
38
+ (pattern.startswith("!"), alternative.removeprefix("./").strip("/"))
39
+ for pattern in patterns
40
+ for alternative in expand_braces(pattern.removeprefix("!"))
41
+ ]
42
+ excluded = [glob_regex(pattern) for negated, pattern in expanded if negated]
43
+ members: dict[str, Path] = {}
44
+ for negated, pattern in expanded:
45
+ if negated or pattern in ("", "."):
46
+ continue
47
+ for manifest in sorted(repo.glob(f"{pattern}/package.json")):
48
+ folder = manifest.parent.relative_to(repo)
49
+ if "node_modules" in folder.parts or any(
50
+ regex.fullmatch(f"{folder.as_posix()}/") for regex in excluded
51
+ ):
52
+ continue
53
+ name = json.loads(manifest.read_text(encoding="utf-8")).get("name")
54
+ if isinstance(name, str) and name:
55
+ members.setdefault(name, manifest.parent)
56
+ except (OSError, ValueError, AttributeError, yaml.YAMLError) as error:
57
+ raise WorkspaceSetupError(f"Cannot read the workspace {repo} declares: {error}") from error
58
+ return members
59
+
60
+
61
+ def expand_braces(pattern: str) -> list[str]:
62
+ """*pattern* with each ``{a,b}`` group spelled out, as the package managers' globs do."""
63
+ group = re.search(r"\{([^{}]*,[^{}]*)\}", pattern)
64
+ if group is None:
65
+ return [pattern]
66
+ head, tail = pattern[: group.start()], pattern[group.end() :]
67
+ return [expanded for alternative in group[1].split(",") for expanded in expand_braces(head + alternative + tail)]
68
+
69
+
70
+ def glob_regex(pattern: str) -> re.Pattern[str]:
71
+ """A workspace glob over a folder's path from the root plus ``/``: ``*`` within a segment, ``**`` any depth."""
72
+ return re.compile(
73
+ "".join(
74
+ (
75
+ "(?:[^/]+/)*"
76
+ if segment == "**"
77
+ else re.escape(segment).replace(r"\*", "[^/]*").replace(r"\?", "[^/]") + "/"
78
+ )
79
+ for segment in pattern.split("/")
80
+ )
81
+ )
File without changes
File without changes
File without changes