codeboarding 0.12.4__tar.gz → 0.13.0__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 (215) hide show
  1. {codeboarding-0.12.4/codeboarding.egg-info → codeboarding-0.13.0}/PKG-INFO +1 -1
  2. {codeboarding-0.12.4 → codeboarding-0.13.0}/README.md +8 -0
  3. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/abstraction_agent.py +103 -33
  4. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/agent.py +104 -40
  5. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/agent_responses.py +364 -55
  6. codeboarding-0.13.0/agents/cluster_ids.py +49 -0
  7. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/cluster_methods_mixin.py +154 -86
  8. codeboarding-0.13.0/agents/content_hash.py +113 -0
  9. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/details_agent.py +130 -36
  10. codeboarding-0.13.0/agents/file_index_models.py +104 -0
  11. codeboarding-0.13.0/agents/incremental_agent.py +707 -0
  12. codeboarding-0.13.0/agents/incremental_planning_agent.py +295 -0
  13. codeboarding-0.13.0/agents/incremental_results.py +31 -0
  14. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/llm_config.py +17 -0
  15. codeboarding-0.13.0/agents/llm_errors.py +125 -0
  16. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/prompts/__init__.py +8 -0
  17. codeboarding-0.13.0/agents/prompts/abstract_prompt_factory.py +114 -0
  18. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/prompts/claude_prompts.py +63 -39
  19. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/prompts/deepseek_prompts.py +58 -41
  20. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/prompts/gemini_flash_prompts.py +55 -42
  21. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/prompts/glm_prompts.py +62 -42
  22. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/prompts/gpt_prompts.py +57 -42
  23. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/prompts/kimi_prompts.py +53 -42
  24. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/prompts/prompt_factory.py +28 -0
  25. codeboarding-0.13.0/agents/relation_edges.py +52 -0
  26. codeboarding-0.13.0/agents/repair.py +206 -0
  27. codeboarding-0.13.0/agents/scope_ids.py +1 -0
  28. codeboarding-0.13.0/agents/scope_operations.py +35 -0
  29. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/__init__.py +1 -0
  30. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/base.py +7 -0
  31. codeboarding-0.13.0/agents/tools/component_bridge_edges.py +84 -0
  32. codeboarding-0.13.0/agents/tools/list_git_changes.py +27 -0
  33. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/read_cfg.py +1 -1
  34. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/toolkit.py +17 -0
  35. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/validation.py +273 -193
  36. {codeboarding-0.12.4 → codeboarding-0.13.0/codeboarding.egg-info}/PKG-INFO +1 -1
  37. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding.egg-info/SOURCES.txt +21 -3
  38. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_cli/bootstrap.py +3 -12
  39. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_cli/commands/full_analysis.py +9 -12
  40. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_cli/commands/incremental_analysis.py +7 -45
  41. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_cli/commands/partial_analysis.py +7 -6
  42. codeboarding-0.13.0/codeboarding_cli/view_instructions.py +41 -0
  43. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_workflows/analysis.py +65 -88
  44. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_workflows/rendering.py +50 -3
  45. {codeboarding-0.12.4 → codeboarding-0.13.0}/constants.py +7 -0
  46. {codeboarding-0.12.4 → codeboarding-0.13.0}/diagram_analysis/__init__.py +5 -1
  47. {codeboarding-0.12.4 → codeboarding-0.13.0}/diagram_analysis/analysis_json.py +141 -56
  48. {codeboarding-0.12.4 → codeboarding-0.13.0}/diagram_analysis/cluster_delta.py +334 -68
  49. {codeboarding-0.12.4 → codeboarding-0.13.0}/diagram_analysis/diagram_generator.py +471 -218
  50. {codeboarding-0.12.4 → codeboarding-0.13.0}/diagram_analysis/exceptions.py +10 -0
  51. codeboarding-0.13.0/diagram_analysis/file_index.py +80 -0
  52. {codeboarding-0.12.4 → codeboarding-0.13.0}/diagram_analysis/io_utils.py +84 -113
  53. {codeboarding-0.12.4 → codeboarding-0.13.0}/diagram_analysis/run_context.py +9 -0
  54. {codeboarding-0.12.4 → codeboarding-0.13.0}/github_action.py +2 -2
  55. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/config.py +2 -1
  56. codeboarding-0.13.0/health_main.py +152 -0
  57. {codeboarding-0.12.4 → codeboarding-0.13.0}/install.py +22 -0
  58. {codeboarding-0.12.4 → codeboarding-0.13.0}/main.py +21 -7
  59. {codeboarding-0.12.4 → codeboarding-0.13.0}/pyproject.toml +1 -1
  60. {codeboarding-0.12.4 → codeboarding-0.13.0}/repo_utils/__init__.py +8 -15
  61. {codeboarding-0.12.4 → codeboarding-0.13.0}/repo_utils/change_detector.py +23 -11
  62. codeboarding-0.13.0/repo_utils/fingerprint_diff.py +71 -0
  63. {codeboarding-0.12.4 → codeboarding-0.13.0}/repo_utils/git_ops.py +2 -134
  64. {codeboarding-0.12.4 → codeboarding-0.13.0}/repo_utils/ignore.py +6 -4
  65. codeboarding-0.13.0/repo_utils/path_utils.py +41 -0
  66. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/__init__.py +103 -35
  67. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/analysis_cache.py +9 -6
  68. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/analysis_result.py +10 -0
  69. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/cluster_helpers.py +3 -1
  70. codeboarding-0.13.0/static_analyzer/cluster_relations.py +324 -0
  71. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/constants.py +3 -3
  72. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/adapters/java_adapter.py +2 -2
  73. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/adapters/rust_adapter.py +43 -1
  74. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/call_graph_builder.py +26 -8
  75. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/edge_builder.py +86 -61
  76. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/language_adapter.py +4 -0
  77. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/lsp_client.py +13 -0
  78. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/models.py +34 -4
  79. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/result_converter.py +16 -1
  80. codeboarding-0.13.0/static_analyzer/engine/source_inspector.py +365 -0
  81. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/utils.py +19 -0
  82. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/graph.py +109 -41
  83. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/incremental_orchestrator.py +83 -48
  84. codeboarding-0.13.0/static_analyzer/internal_references.py +72 -0
  85. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/java_utils.py +1 -1
  86. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/language_results.py +25 -4
  87. codeboarding-0.13.0/static_analyzer/method_cluster_paths.py +56 -0
  88. codeboarding-0.13.0/static_analyzer/reference_resolver.py +481 -0
  89. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/scanner.py +82 -9
  90. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_github_action.py +12 -11
  91. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_install.py +33 -0
  92. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_main.py +64 -74
  93. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_pyproject_packages.py +1 -1
  94. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_tool_registry.py +4 -4
  95. codeboarding-0.13.0/tests/test_view_instructions.py +32 -0
  96. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_windows_compatibility.py +30 -0
  97. {codeboarding-0.12.4 → codeboarding-0.13.0}/tool_registry/paths.py +20 -1
  98. {codeboarding-0.12.4 → codeboarding-0.13.0}/tool_registry/registry.py +3 -5
  99. {codeboarding-0.12.4 → codeboarding-0.13.0}/user_config.py +3 -1
  100. {codeboarding-0.12.4 → codeboarding-0.13.0}/utils.py +5 -21
  101. codeboarding-0.12.4/agents/incremental_agent.py +0 -787
  102. codeboarding-0.12.4/agents/prompts/abstract_prompt_factory.py +0 -67
  103. codeboarding-0.12.4/diagram_analysis/version.py +0 -6
  104. codeboarding-0.12.4/repo_utils/diff_parser.py +0 -290
  105. codeboarding-0.12.4/static_analyzer/cluster_relations.py +0 -175
  106. codeboarding-0.12.4/static_analyzer/engine/source_inspector.py +0 -203
  107. codeboarding-0.12.4/static_analyzer/reference_resolve_mixin.py +0 -191
  108. {codeboarding-0.12.4 → codeboarding-0.13.0}/LICENSE +0 -0
  109. {codeboarding-0.12.4 → codeboarding-0.13.0}/PYPI.md +0 -0
  110. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/__init__.py +0 -0
  111. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/change_status.py +0 -0
  112. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/cluster_budget.py +0 -0
  113. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/constants.py +0 -0
  114. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/dependency_discovery.py +0 -0
  115. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/meta_agent.py +0 -0
  116. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/model_capabilities.py +0 -0
  117. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/planner_agent.py +0 -0
  118. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/retry.py +0 -0
  119. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/get_external_deps.py +0 -0
  120. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/get_method_invocations.py +0 -0
  121. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/read_docs.py +0 -0
  122. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/read_file.py +0 -0
  123. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/read_file_structure.py +0 -0
  124. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/read_packages.py +0 -0
  125. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/read_source.py +0 -0
  126. {codeboarding-0.12.4 → codeboarding-0.13.0}/agents/tools/read_structure.py +0 -0
  127. {codeboarding-0.12.4 → codeboarding-0.13.0}/caching/__init__.py +0 -0
  128. {codeboarding-0.12.4 → codeboarding-0.13.0}/caching/cache.py +0 -0
  129. {codeboarding-0.12.4 → codeboarding-0.13.0}/caching/details_cache.py +0 -0
  130. {codeboarding-0.12.4 → codeboarding-0.13.0}/caching/meta_cache.py +0 -0
  131. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding.egg-info/dependency_links.txt +0 -0
  132. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding.egg-info/entry_points.txt +0 -0
  133. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding.egg-info/requires.txt +0 -0
  134. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding.egg-info/top_level.txt +0 -0
  135. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_cli/__init__.py +0 -0
  136. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_cli/commands/__init__.py +0 -0
  137. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_workflows/__init__.py +0 -0
  138. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_workflows/orchestration.py +0 -0
  139. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_workflows/sources/__init__.py +0 -0
  140. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_workflows/sources/local.py +0 -0
  141. {codeboarding-0.12.4 → codeboarding-0.13.0}/codeboarding_workflows/sources/remote.py +0 -0
  142. {codeboarding-0.12.4 → codeboarding-0.13.0}/core/__init__.py +0 -0
  143. {codeboarding-0.12.4 → codeboarding-0.13.0}/core/plugin_loader.py +0 -0
  144. {codeboarding-0.12.4 → codeboarding-0.13.0}/core/protocols.py +0 -0
  145. {codeboarding-0.12.4 → codeboarding-0.13.0}/core/registry.py +0 -0
  146. {codeboarding-0.12.4 → codeboarding-0.13.0}/diagram_analysis/cluster_snapshot.py +0 -0
  147. {codeboarding-0.12.4 → codeboarding-0.13.0}/diagram_analysis/file_coverage.py +0 -0
  148. {codeboarding-0.12.4 → codeboarding-0.13.0}/diagram_analysis/run_mode.py +0 -0
  149. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/__init__.py +0 -0
  150. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/checks/__init__.py +0 -0
  151. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/checks/circular_deps.py +0 -0
  152. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/checks/cohesion.py +0 -0
  153. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/checks/coupling.py +0 -0
  154. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/checks/function_size.py +0 -0
  155. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/checks/god_class.py +0 -0
  156. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/checks/inheritance.py +0 -0
  157. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/checks/instability.py +0 -0
  158. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/checks/unused_code_diagnostics.py +0 -0
  159. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/models.py +0 -0
  160. {codeboarding-0.12.4 → codeboarding-0.13.0}/health/runner.py +0 -0
  161. {codeboarding-0.12.4 → codeboarding-0.13.0}/logging_config.py +0 -0
  162. {codeboarding-0.12.4 → codeboarding-0.13.0}/monitoring/__init__.py +0 -0
  163. {codeboarding-0.12.4 → codeboarding-0.13.0}/monitoring/callbacks.py +0 -0
  164. {codeboarding-0.12.4 → codeboarding-0.13.0}/monitoring/context.py +0 -0
  165. {codeboarding-0.12.4 → codeboarding-0.13.0}/monitoring/mixin.py +0 -0
  166. {codeboarding-0.12.4 → codeboarding-0.13.0}/monitoring/paths.py +0 -0
  167. {codeboarding-0.12.4 → codeboarding-0.13.0}/monitoring/stats.py +0 -0
  168. {codeboarding-0.12.4 → codeboarding-0.13.0}/monitoring/writers.py +0 -0
  169. {codeboarding-0.12.4 → codeboarding-0.13.0}/output_generators/__init__.py +0 -0
  170. {codeboarding-0.12.4 → codeboarding-0.13.0}/output_generators/html.py +0 -0
  171. {codeboarding-0.12.4 → codeboarding-0.13.0}/output_generators/html_template.py +0 -0
  172. {codeboarding-0.12.4 → codeboarding-0.13.0}/output_generators/markdown.py +0 -0
  173. {codeboarding-0.12.4 → codeboarding-0.13.0}/output_generators/mdx.py +0 -0
  174. {codeboarding-0.12.4 → codeboarding-0.13.0}/output_generators/sphinx.py +0 -0
  175. {codeboarding-0.12.4 → codeboarding-0.13.0}/repo_utils/errors.py +0 -0
  176. {codeboarding-0.12.4 → codeboarding-0.13.0}/setup.cfg +0 -0
  177. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/cfg_skip_planner.py +0 -0
  178. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/csharp_config_scanner.py +0 -0
  179. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/dotnet_sdk.py +0 -0
  180. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/__init__.py +0 -0
  181. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/adapters/__init__.py +0 -0
  182. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/adapters/csharp_adapter.py +0 -0
  183. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/adapters/go_adapter.py +0 -0
  184. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/adapters/php_adapter.py +0 -0
  185. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/adapters/python_adapter.py +0 -0
  186. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/adapters/typescript_adapter.py +0 -0
  187. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/edge_build_context.py +0 -0
  188. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/hierarchy_builder.py +0 -0
  189. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/lsp_constants.py +0 -0
  190. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/progress.py +0 -0
  191. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/protocols.py +0 -0
  192. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/engine/symbol_table.py +0 -0
  193. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/java_config_scanner.py +0 -0
  194. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/leiden_utils.py +0 -0
  195. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/lsp_client/__init__.py +0 -0
  196. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/lsp_client/diagnostics.py +0 -0
  197. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/node.py +0 -0
  198. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/programming_language.py +0 -0
  199. {codeboarding-0.12.4 → codeboarding-0.13.0}/static_analyzer/typescript_config_scanner.py +0 -0
  200. {codeboarding-0.12.4 → codeboarding-0.13.0}/telemetry/__init__.py +0 -0
  201. {codeboarding-0.12.4 → codeboarding-0.13.0}/telemetry/device_id.py +0 -0
  202. {codeboarding-0.12.4 → codeboarding-0.13.0}/telemetry/events.py +0 -0
  203. {codeboarding-0.12.4 → codeboarding-0.13.0}/telemetry/schemas.py +0 -0
  204. {codeboarding-0.12.4 → codeboarding-0.13.0}/telemetry/service.py +0 -0
  205. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_cli_parser.py +0 -0
  206. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_logging_config.py +0 -0
  207. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_registry_coverage.py +0 -0
  208. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_telemetry_events.py +0 -0
  209. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_user_config.py +0 -0
  210. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_vscode_constants.py +0 -0
  211. {codeboarding-0.12.4 → codeboarding-0.13.0}/tests/test_windows_encoding.py +0 -0
  212. {codeboarding-0.12.4 → codeboarding-0.13.0}/tool_registry/__init__.py +0 -0
  213. {codeboarding-0.12.4 → codeboarding-0.13.0}/tool_registry/installers.py +0 -0
  214. {codeboarding-0.12.4 → codeboarding-0.13.0}/tool_registry/manifest.py +0 -0
  215. {codeboarding-0.12.4 → codeboarding-0.13.0}/vscode_constants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.12.4
3
+ Version: 0.13.0
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -137,6 +137,14 @@ python main.py partial --local ./my-project --component-id "1.2"
137
137
  python main.py full https://github.com/pytorch/pytorch
138
138
  ```
139
139
 
140
+ > **Incremental needs a baseline.** `incremental` diffs the working tree against the previous
141
+ > analysis in `.codeboarding/` (`analysis.json` + `fingerprint.json`). That baseline can live
142
+ > purely locally — a prior `full`/`incremental` run in the same output dir is enough. Commit
143
+ > `.codeboarding/` only if you want the baseline to travel with the branch (so a teammate or a
144
+ > fresh checkout can run incremental too). With no baseline at all — or one that predates content
145
+ > versioning — `incremental` fails fast with "run a full analysis first" rather than silently
146
+ > doing a full run.
147
+
140
148
  ## Where to use it
141
149
 
142
150
  - [CLI](https://github.com/CodeBoarding/CodeBoarding) for local analysis, automation, and CI workflows.
@@ -7,22 +7,31 @@ from langchain_core.prompts import PromptTemplate
7
7
  from agents.agent import CodeBoardingAgent
8
8
  from agents.agent_responses import (
9
9
  AnalysisInsights,
10
+ ComponentApiSurfaces,
11
+ ComponentArchitecture,
12
+ ComponentRelations,
10
13
  ClusterAnalysis,
11
14
  MetaAnalysisInsights,
12
15
  assign_component_ids,
16
+ assign_relation_ids,
13
17
  )
14
18
  from agents.cluster_methods_mixin import ClusterMethodsMixin
15
19
  from agents.prompts import (
16
20
  get_cluster_grouping_message,
17
21
  get_final_analysis_message,
22
+ get_api_surfaces_message,
23
+ get_relation_analysis_message,
18
24
  get_system_message,
25
+ format_project_system_message,
19
26
  )
27
+ from agents.relation_edges import index_relation_endpoints
28
+ from agents.repair import ComponentRepairContext, repair_component_group_names, repair_key_entities
20
29
  from agents.validation import (
21
30
  ValidationContext,
22
31
  validate_cluster_coverage,
23
32
  validate_group_name_coverage,
24
33
  validate_key_entities,
25
- validate_relation_component_names,
34
+ validate_relations,
26
35
  )
27
36
  from monitoring import trace
28
37
  from static_analyzer.analysis_result import StaticAnalysisResults
@@ -45,7 +54,8 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
45
54
  agent_llm: BaseChatModel,
46
55
  parsing_llm: BaseChatModel,
47
56
  ):
48
- super().__init__(repo_dir, static_analysis, get_system_message(), agent_llm, parsing_llm)
57
+ system_message = format_project_system_message(get_system_message(), project_name, meta_context)
58
+ super().__init__(repo_dir, static_analysis, system_message, agent_llm, parsing_llm)
49
59
 
50
60
  self.project_name = project_name
51
61
  self.meta_context = meta_context
@@ -53,11 +63,26 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
53
63
  self.prompts = {
54
64
  "group_clusters": PromptTemplate(
55
65
  template=get_cluster_grouping_message(),
56
- input_variables=["project_name", "cfg_clusters", "meta_context", "project_type"],
66
+ input_variables=["cfg_clusters"],
57
67
  ),
58
68
  "final_analysis": PromptTemplate(
59
69
  template=get_final_analysis_message(),
60
- input_variables=["project_name", "cluster_analysis", "meta_context", "project_type"],
70
+ input_variables=["cluster_analysis"],
71
+ ),
72
+ "api_surfaces": PromptTemplate(
73
+ template=get_api_surfaces_message(),
74
+ input_variables=[
75
+ "component_summaries",
76
+ "static_call_evidence",
77
+ ],
78
+ ),
79
+ "relation_analysis": PromptTemplate(
80
+ template=get_relation_analysis_message(),
81
+ input_variables=[
82
+ "component_summaries",
83
+ "api_surfaces",
84
+ "static_call_evidence",
85
+ ],
61
86
  ),
62
87
  }
63
88
 
@@ -65,9 +90,6 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
65
90
  def step_clusters_grouping(self, cluster_results: dict[str, ClusterResult]) -> ClusterAnalysis:
66
91
  logger.info(f"[AbstractionAgent] Grouping CFG clusters for: {self.project_name}")
67
92
 
68
- meta_context_str = self.meta_context.llm_str() if self.meta_context else "No project context available."
69
- project_type = self.meta_context.project_type if self.meta_context else "unknown"
70
-
71
93
  programming_langs = self.static_analysis.get_languages()
72
94
 
73
95
  # Measure everything that wraps cfg_clusters (system message + rendered
@@ -75,10 +97,7 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
75
97
  # the input window before budgeting the cluster string.
76
98
  overhead_chars = len(str(self.system_message.content)) + len(
77
99
  self.prompts["group_clusters"].format(
78
- project_name=self.project_name,
79
100
  cfg_clusters="",
80
- meta_context=meta_context_str,
81
- project_type=project_type,
82
101
  )
83
102
  )
84
103
  cluster_str = self._build_cluster_string(
@@ -86,17 +105,14 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
86
105
  )
87
106
 
88
107
  prompt = self.prompts["group_clusters"].format(
89
- project_name=self.project_name,
90
108
  cfg_clusters=cluster_str,
91
- meta_context=meta_context_str,
92
- project_type=project_type,
93
109
  )
94
110
 
95
- cluster_analysis = self._validation_invoke(
111
+ cluster_analysis = self._invoke_validate(
96
112
  prompt,
97
113
  ClusterAnalysis,
98
114
  validators=[validate_cluster_coverage],
99
- context=ValidationContext(
115
+ validation_context=ValidationContext(
100
116
  cluster_results=cluster_results,
101
117
  expected_cluster_ids=get_all_cluster_ids(cluster_results),
102
118
  ),
@@ -108,20 +124,14 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
108
124
  def step_final_analysis(
109
125
  self, llm_cluster_analysis: ClusterAnalysis, cluster_results: dict[str, ClusterResult]
110
126
  ) -> AnalysisInsights:
111
- logger.info(f"[AbstractionAgent] Generating final analysis for: {self.project_name}")
112
-
113
- meta_context_str = self.meta_context.llm_str() if self.meta_context else "No project context available."
114
- project_type = self.meta_context.project_type if self.meta_context else "unknown"
127
+ logger.info(f"[AbstractionAgent] Generating final component analysis for: {self.project_name}")
115
128
 
116
129
  cluster_str = llm_cluster_analysis.llm_str() if llm_cluster_analysis else "No cluster analysis available."
117
130
 
118
131
  group_names = [cc.name for cc in llm_cluster_analysis.cluster_components] if llm_cluster_analysis else []
119
132
 
120
133
  prompt = self.prompts["final_analysis"].format(
121
- project_name=self.project_name,
122
134
  cluster_analysis=cluster_str,
123
- meta_context=meta_context_str,
124
- project_type=project_type,
125
135
  )
126
136
 
127
137
  if group_names:
@@ -130,25 +140,80 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
130
140
  f"Every one of these names must appear in exactly one component's source_group_names: {group_names}\n"
131
141
  )
132
142
 
133
- # Build validation context with CFG graphs for edge checking
134
143
  context = ValidationContext(
135
144
  cluster_results=cluster_results,
136
- cfg_graphs={lang: self.static_analysis.get_cfg(lang) for lang in self.static_analysis.get_languages()},
137
145
  static_analysis=self.static_analysis,
138
146
  llm_cluster_analysis=llm_cluster_analysis,
139
147
  )
140
148
 
141
- return self._validation_invoke(
149
+ architecture = self._invoke_repair_validate(
142
150
  prompt,
143
- AnalysisInsights,
151
+ ComponentArchitecture,
152
+ repairs=[repair_component_group_names, repair_key_entities],
144
153
  validators=[
145
- validate_relation_component_names,
146
154
  validate_group_name_coverage,
147
155
  validate_key_entities,
148
156
  ],
149
- context=context,
157
+ repair_context=ComponentRepairContext(
158
+ reference_resolver=self.reference_resolver,
159
+ cluster_results=cluster_results,
160
+ llm_cluster_analysis=llm_cluster_analysis,
161
+ ),
162
+ validation_context=context,
163
+ max_validation_attempts=3,
164
+ )
165
+ return AnalysisInsights(
166
+ description=architecture.description,
167
+ components=architecture.components,
168
+ components_relations=[],
169
+ )
170
+
171
+ @trace
172
+ def step_api_surfaces(self, analysis: AnalysisInsights) -> ComponentApiSurfaces:
173
+ logger.info(f"[AbstractionAgent] Analyzing component API surfaces for: {self.project_name}")
174
+ static_call_evidence = self.build_scope_cfg_string(analysis)
175
+ prompt = self.prompts["api_surfaces"].format(
176
+ component_summaries=analysis.llm_str(),
177
+ static_call_evidence=static_call_evidence,
178
+ )
179
+ return self._parse_invoke(prompt, ComponentApiSurfaces)
180
+
181
+ @trace
182
+ def step_relation_analysis(
183
+ self,
184
+ analysis: AnalysisInsights,
185
+ api_surfaces: ComponentApiSurfaces,
186
+ cluster_analysis: ClusterAnalysis,
187
+ cluster_results: dict[str, ClusterResult],
188
+ ) -> None:
189
+ logger.info(f"[AbstractionAgent] Discovering component relations for: {self.project_name}")
190
+ static_call_evidence = self.build_scope_cfg_string(analysis)
191
+ cfg_graphs = self.static_analysis.available_cfgs()
192
+ self.toolkit.context.cluster_analysis = cluster_analysis
193
+ self.toolkit.context.cluster_results = cluster_results
194
+ self.toolkit.context.cfg_graphs = cfg_graphs
195
+ prompt = self.prompts["relation_analysis"].format(
196
+ component_summaries=analysis.llm_str(),
197
+ api_surfaces=api_surfaces.llm_str(),
198
+ static_call_evidence=static_call_evidence,
199
+ )
200
+ relation_result = self._invoke_validate(
201
+ prompt,
202
+ ComponentRelations,
203
+ validators=[validate_relations],
204
+ validation_context=ValidationContext(
205
+ cluster_results=cluster_results,
206
+ cfg_graphs=cfg_graphs,
207
+ repo_dir=str(self.repo_dir),
208
+ static_analysis=self.static_analysis,
209
+ llm_cluster_analysis=cluster_analysis,
210
+ components=analysis.components,
211
+ ),
150
212
  max_validation_attempts=3,
151
213
  )
214
+ analysis.components_relations = relation_result.components_relations
215
+ assign_relation_ids(analysis)
216
+ self.build_static_relations(analysis)
152
217
 
153
218
  def run(self):
154
219
  # Build full cluster results dict for all languages ONCE
@@ -166,12 +231,17 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
166
231
  # Step 5: Populate file_methods deterministically from cluster results + orphan assignment
167
232
  self.populate_file_methods(analysis, cluster_results)
168
233
 
169
- # Step 6: Build static inter-component relations from CFG edges
170
- self.build_static_relations(analysis)
234
+ # Step 6: Analyze component API surfaces
235
+ api_surfaces = self.step_api_surfaces(analysis)
236
+
237
+ # Step 7: Discover relations from API surfaces and attach deterministic all_edges
238
+ self.step_relation_analysis(analysis, api_surfaces, cluster_analysis, cluster_results)
171
239
 
172
- # Step 7: Fix source code reference lines (resolves reference_file paths for key_entities)
173
- analysis = self.fix_source_code_reference_lines(analysis)
174
- # Step 8: Ensure unique key entities across components
240
+ # Step 8: Fix source code reference lines (resolves reference_file paths for key_entities and key_edges)
241
+ analysis = self.reference_resolver.fix_source_code_reference_lines(analysis)
242
+ # Step 9: Index relation endpoints after reference resolution
243
+ index_relation_endpoints(analysis, self.repo_dir)
244
+ # Step 10: Ensure unique key entities across components
175
245
  self._ensure_unique_key_entities(analysis)
176
246
 
177
247
  return analysis, cluster_results
@@ -1,6 +1,8 @@
1
1
  import json
2
2
  import logging
3
+ from collections.abc import Callable
3
4
  from pathlib import Path
5
+ from typing import Protocol, TypeVar
4
6
 
5
7
  from google.api_core.exceptions import ResourceExhausted
6
8
  from langchain_core.exceptions import OutputParserException
@@ -21,18 +23,43 @@ from agents.validation import ValidationResult, score_validation_results, VALIDA
21
23
  from monitoring.mixin import MonitoringMixin
22
24
  from repo_utils.ignore import RepoIgnoreManager
23
25
  from agents.agent_responses import LLMBaseModel
24
- from agents.llm_config import MONITORING_CALLBACK
26
+ from agents.llm_config import MONITORING_CALLBACK, current_provider_key_context
27
+ from agents.llm_errors import detect_auth_error
25
28
  from static_analyzer.analysis_result import StaticAnalysisResults
26
- from static_analyzer.reference_resolve_mixin import ReferenceResolverMixin
29
+ from static_analyzer.reference_resolver import StaticReferenceResolver
27
30
 
28
31
  logger = logging.getLogger(__name__)
29
32
 
33
+ ParseResultT = TypeVar("ParseResultT")
34
+ ResultT = TypeVar("ResultT", bound="RepairValidationResult")
35
+ RepairContextT = TypeVar("RepairContextT")
36
+ ValidationContextT = TypeVar("ValidationContextT")
37
+
38
+
39
+ class RepairValidationResult(Protocol):
40
+ def llm_str(self) -> str: ...
41
+
30
42
 
31
43
  class EmptyExtractorMessageError(ValueError):
32
44
  """Raised when extractor returns an empty message payload."""
33
45
 
34
46
 
35
- class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
47
+ def _raise_if_auth_error(exc: Exception) -> None:
48
+ """Re-raise *exc* as a typed :class:`LLMAuthError` when it's a rejected key.
49
+
50
+ Shared by the retry classifiers in ``_invoke`` and ``_parse_response``: a
51
+ 401 is permanent for the run, so raising here propagates it terminally (the
52
+ retry loop never sees a RETRY decision) and the CLI can surface an
53
+ actionable message. Non-auth exceptions fall through untouched.
54
+ """
55
+ provider, key_tail = current_provider_key_context()
56
+ auth_error = detect_auth_error(exc, provider=provider, key_tail=key_tail)
57
+ if auth_error is not None:
58
+ logger.error("LLM auth failure — not retrying: %s", auth_error)
59
+ raise auth_error from exc
60
+
61
+
62
+ class CodeBoardingAgent(MonitoringMixin):
36
63
  def __init__(
37
64
  self,
38
65
  repo_dir: Path,
@@ -41,11 +68,12 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
41
68
  agent_llm: BaseChatModel,
42
69
  parsing_llm: BaseChatModel,
43
70
  ):
44
- ReferenceResolverMixin.__init__(self, repo_dir, static_analysis)
45
71
  MonitoringMixin.__init__(self)
46
72
  self.parsing_llm = parsing_llm
47
73
  self.agent_llm = agent_llm
48
74
  self.repo_dir = repo_dir
75
+ self.static_analysis = static_analysis
76
+ self.reference_resolver = StaticReferenceResolver(repo_dir, static_analysis)
49
77
  self.ignore_manager = RepoIgnoreManager(repo_dir)
50
78
 
51
79
  context = RepoContext(repo_dir=repo_dir, ignore_manager=self.ignore_manager, static_analysis=static_analysis)
@@ -55,7 +83,6 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
55
83
  model=agent_llm,
56
84
  tools=self.toolkit.get_agent_tools(),
57
85
  )
58
- self.static_analysis = static_analysis
59
86
  self.system_message = SystemMessage(content=system_message)
60
87
 
61
88
  @property
@@ -82,6 +109,10 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
82
109
  def read_method_invocations_tool(self):
83
110
  return self.toolkit.read_method_invocations
84
111
 
112
+ @property
113
+ def component_bridge_edges_tool(self):
114
+ return self.toolkit.component_bridge_edges
115
+
85
116
  @property
86
117
  def read_file_tool(self):
87
118
  return self.toolkit.read_file
@@ -132,6 +163,7 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
132
163
  return "" # unreachable for AIMessage but satisfies typing
133
164
 
134
165
  def classify(exc: Exception, attempt: int) -> RetryDecision:
166
+ _raise_if_auth_error(exc)
135
167
  if getattr(exc, "status_code", None) == 404:
136
168
  logger.error(f"Permanent HTTP 404 — not retrying: {type(exc).__name__}: {exc}")
137
169
  return RetryDecision(action=RetryAction.GIVE_UP)
@@ -201,12 +233,33 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
201
233
  except Empty:
202
234
  raise RuntimeError("Agent invocation completed but no result was returned")
203
235
 
204
- def _parse_invoke(self, prompt: str, type: type, include_hidden: bool = False):
236
+ def _parse_invoke(
237
+ self,
238
+ prompt: str,
239
+ return_type: type[ParseResultT],
240
+ include_hidden: bool = False,
241
+ ) -> ParseResultT:
205
242
  response = self._invoke(prompt)
206
243
  assert isinstance(response, str), f"Expected a string as response type got {response}"
207
- return self._parse_response(prompt, response, type, include_hidden=include_hidden)
244
+ return self._parse_response(prompt, response, return_type, include_hidden=include_hidden)
208
245
 
209
- def _score_result(self, result, validators: list, context) -> tuple[float, list[tuple[float, str]]]:
246
+ def _repair_result(
247
+ self,
248
+ result: ResultT,
249
+ repairs: list[Callable[[ResultT, RepairContextT], None]],
250
+ repair_context: RepairContextT,
251
+ ) -> None:
252
+ """Apply deterministic repairs to one parsed candidate."""
253
+ for repair in repairs:
254
+ logger.info("[Repair] Applying %s", repair.__name__)
255
+ repair(result, repair_context)
256
+
257
+ def _score_result(
258
+ self,
259
+ result: ResultT,
260
+ validators: list[Callable[[ResultT, ValidationContextT], ValidationResult]],
261
+ validation_context: ValidationContextT,
262
+ ) -> tuple[float, list[tuple[float, str]]]:
210
263
  """Run all validators on a result and return (score, prioritized_feedback).
211
264
 
212
265
  The score is computed using weighted validators where coverage-related
@@ -217,10 +270,10 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
217
270
  weight descending, so that the LLM focuses on the most critical issues
218
271
  (cluster/group coverage) before lower-priority ones (key entities).
219
272
  """
220
- validator_results: list[tuple] = []
273
+ validator_results: list[tuple[Callable[[ResultT, ValidationContextT], ValidationResult], ValidationResult]] = []
221
274
  weighted_feedback: list[tuple[float, str]] = []
222
275
  for validator in validators:
223
- validator_result: ValidationResult = validator(result, context)
276
+ validator_result: ValidationResult = validator(result, validation_context)
224
277
  validator_results.append((validator, validator_result))
225
278
  if not validator_result.is_valid:
226
279
  weight = VALIDATOR_WEIGHTS.get(validator.__name__, DEFAULT_VALIDATOR_WEIGHT)
@@ -233,44 +286,52 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
233
286
  score = score_validation_results(validator_results)
234
287
  return score, weighted_feedback
235
288
 
236
- def _validation_invoke(
289
+ def _invoke_repair_validate(
237
290
  self,
238
291
  prompt: str,
239
- return_type: type,
240
- validators: list,
241
- context,
292
+ return_type: type[ResultT],
293
+ repairs: list[Callable[[ResultT, RepairContextT], None]],
294
+ validators: list[Callable[[ResultT, ValidationContextT], ValidationResult]],
295
+ repair_context: RepairContextT,
296
+ validation_context: ValidationContextT,
242
297
  max_validation_attempts: int = 1,
243
298
  include_hidden: bool = False,
244
- ):
245
- """
246
- Invoke LLM with validation, feedback loop, and best-of-N selection.
247
-
248
- Each attempt (initial + retries) is scored using weighted validators.
249
- Coverage validators (validate_cluster_coverage, validate_group_name_coverage)
250
- are weighted ~2x higher than other validators, so the selection strongly
251
- favours results with complete coverage.
252
-
253
- If any attempt scores perfectly (all validators pass), it is returned
254
- immediately. Otherwise the highest-scoring result across all attempts is
255
- returned.
256
-
257
- Args:
258
- prompt: The original prompt
259
- return_type: Pydantic type to parse into
260
- validators: List of validation functions to run
261
- context: ValidationContext with data needed for validation
262
- max_validation_attempts: Maximum validation attempts (initial attempt included).
263
- Retries occur only when this value is greater than 1. (default: 1)
264
-
265
- Returns:
266
- The highest-scoring result of return_type across all attempts
267
- """
299
+ ) -> ResultT:
300
+ """Bind deterministic repairs to every candidate before validation."""
301
+
302
+ def repair_candidate(result: ResultT) -> None:
303
+ self._repair_result(result, repairs, repair_context)
304
+
305
+ return self._invoke_validate(
306
+ prompt,
307
+ return_type,
308
+ validators,
309
+ validation_context,
310
+ max_validation_attempts=max_validation_attempts,
311
+ include_hidden=include_hidden,
312
+ candidate_repair=repair_candidate,
313
+ )
314
+
315
+ def _invoke_validate(
316
+ self,
317
+ prompt: str,
318
+ return_type: type[ResultT],
319
+ validators: list[Callable[[ResultT, ValidationContextT], ValidationResult]],
320
+ validation_context: ValidationContextT,
321
+ max_validation_attempts: int = 1,
322
+ include_hidden: bool = False,
323
+ candidate_repair: Callable[[ResultT], None] | None = None,
324
+ ) -> ResultT:
325
+ """Validate parsed candidates and return the best-scoring result."""
268
326
  # Compute the maximum possible score so we can detect a perfect result
269
327
  max_possible_score = sum(VALIDATOR_WEIGHTS.get(v.__name__, DEFAULT_VALIDATOR_WEIGHT) for v in validators)
270
328
 
271
329
  result = self._parse_invoke(prompt, return_type, include_hidden=include_hidden)
330
+ if candidate_repair is not None:
331
+ candidate_repair(result)
272
332
  logger.info(
273
- "[Validation] Parsed %s: %s",
333
+ "[Validation] Parsed%s %s: %s",
334
+ " and repaired" if candidate_repair is not None else "",
274
335
  return_type.__name__,
275
336
  result.llm_str()[:500],
276
337
  )
@@ -283,7 +344,7 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
283
344
  critical_threshold = 10.0
284
345
 
285
346
  for attempt in range(1, max_validation_attempts + 1):
286
- score, weighted_feedback = self._score_result(result, validators, context)
347
+ score, weighted_feedback = self._score_result(result, validators, validation_context)
287
348
 
288
349
  logger.info(
289
350
  f"[Validation] Attempt {attempt}/{max_validation_attempts} "
@@ -327,6 +388,8 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
327
388
  f"with {len(weighted_feedback)} feedback items"
328
389
  )
329
390
  result = self._parse_invoke(feedback_prompt, return_type, include_hidden=include_hidden)
391
+ if candidate_repair is not None:
392
+ candidate_repair(result)
330
393
 
331
394
  return best_result
332
395
 
@@ -355,6 +418,7 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
355
418
  return self._extractor_parse(response, return_type, parser, include_hidden=include_hidden)
356
419
 
357
420
  def classify(exc: Exception, attempt: int) -> RetryDecision:
421
+ _raise_if_auth_error(exc)
358
422
  if isinstance(exc, ResourceExhausted):
359
423
  return RetryDecision(
360
424
  action=RetryAction.RETRY,