codeboarding 0.13.3__tar.gz → 0.13.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. {codeboarding-0.13.3/codeboarding.egg-info → codeboarding-0.13.4}/PKG-INFO +5 -1
  2. {codeboarding-0.13.3 → codeboarding-0.13.4}/PYPI.md +4 -0
  3. {codeboarding-0.13.3 → codeboarding-0.13.4}/README.md +6 -2
  4. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/incremental_agent.py +71 -11
  5. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/relation_edges.py +93 -8
  6. {codeboarding-0.13.3 → codeboarding-0.13.4/codeboarding.egg-info}/PKG-INFO +5 -1
  7. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding.egg-info/SOURCES.txt +2 -0
  8. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/diagram_generator.py +113 -41
  9. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/scope_plan.py +4 -11
  10. codeboarding-0.13.4/diagram_analysis/tree_shape.py +122 -0
  11. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/checks/function_size.py +8 -8
  12. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/runner.py +9 -8
  13. {codeboarding-0.13.3 → codeboarding-0.13.4}/output_generators/html_template.py +3 -0
  14. {codeboarding-0.13.3 → codeboarding-0.13.4}/output_generators/markdown.py +1 -1
  15. {codeboarding-0.13.3 → codeboarding-0.13.4}/output_generators/sphinx.py +5 -1
  16. {codeboarding-0.13.3 → codeboarding-0.13.4}/pyproject.toml +1 -1
  17. {codeboarding-0.13.3 → codeboarding-0.13.4}/repo_utils/ignore.py +0 -16
  18. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/cluster_relations.py +212 -8
  19. codeboarding-0.13.4/static_analyzer/internal_references.py +98 -0
  20. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/method_cluster_paths.py +21 -0
  21. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/reference_resolver.py +23 -24
  22. {codeboarding-0.13.3 → codeboarding-0.13.4}/telemetry/service.py +36 -6
  23. codeboarding-0.13.4/tests/test_telemetry_service.py +99 -0
  24. codeboarding-0.13.3/static_analyzer/internal_references.py +0 -72
  25. {codeboarding-0.13.3 → codeboarding-0.13.4}/LICENSE +0 -0
  26. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/__init__.py +0 -0
  27. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/abstraction_agent.py +0 -0
  28. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/agent.py +0 -0
  29. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/agent_responses.py +0 -0
  30. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/change_status.py +0 -0
  31. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/cluster_ids.py +0 -0
  32. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/cluster_methods_mixin.py +0 -0
  33. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/constants.py +0 -0
  34. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/content_hash.py +0 -0
  35. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/dependency_discovery.py +0 -0
  36. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/details_agent.py +0 -0
  37. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/file_index_models.py +0 -0
  38. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/incremental_results.py +0 -0
  39. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/llm_config.py +0 -0
  40. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/llm_errors.py +0 -0
  41. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/meta_agent.py +0 -0
  42. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/model_capabilities.py +0 -0
  43. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/planner_agent.py +0 -0
  44. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/prompts/__init__.py +0 -0
  45. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/prompts/abstract_prompt_factory.py +0 -0
  46. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/prompts/claude_prompts.py +0 -0
  47. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/prompts/deepseek_prompts.py +0 -0
  48. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/prompts/gemini_flash_prompts.py +0 -0
  49. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/prompts/glm_prompts.py +0 -0
  50. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/prompts/gpt_prompts.py +0 -0
  51. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/prompts/kimi_prompts.py +0 -0
  52. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/prompts/prompt_factory.py +0 -0
  53. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/repair.py +0 -0
  54. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/retry.py +0 -0
  55. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/scope_ids.py +0 -0
  56. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/__init__.py +0 -0
  57. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/base.py +0 -0
  58. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/component_bridge_edges.py +0 -0
  59. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/get_external_deps.py +0 -0
  60. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/get_method_invocations.py +0 -0
  61. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/list_git_changes.py +0 -0
  62. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/read_cfg.py +0 -0
  63. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/read_docs.py +0 -0
  64. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/read_file.py +0 -0
  65. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/read_file_structure.py +0 -0
  66. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/read_packages.py +0 -0
  67. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/read_source.py +0 -0
  68. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/read_structure.py +0 -0
  69. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/tools/toolkit.py +0 -0
  70. {codeboarding-0.13.3 → codeboarding-0.13.4}/agents/validation.py +0 -0
  71. {codeboarding-0.13.3 → codeboarding-0.13.4}/caching/__init__.py +0 -0
  72. {codeboarding-0.13.3 → codeboarding-0.13.4}/caching/cache.py +0 -0
  73. {codeboarding-0.13.3 → codeboarding-0.13.4}/caching/details_cache.py +0 -0
  74. {codeboarding-0.13.3 → codeboarding-0.13.4}/caching/meta_cache.py +0 -0
  75. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding.egg-info/dependency_links.txt +0 -0
  76. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding.egg-info/entry_points.txt +0 -0
  77. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding.egg-info/requires.txt +0 -0
  78. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding.egg-info/top_level.txt +0 -0
  79. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_cli/__init__.py +0 -0
  80. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_cli/bootstrap.py +0 -0
  81. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_cli/commands/__init__.py +0 -0
  82. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_cli/commands/full_analysis.py +0 -0
  83. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_cli/commands/incremental_analysis.py +0 -0
  84. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_cli/commands/partial_analysis.py +0 -0
  85. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_cli/view_instructions.py +0 -0
  86. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_workflows/__init__.py +0 -0
  87. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_workflows/analysis.py +0 -0
  88. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_workflows/orchestration.py +0 -0
  89. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_workflows/rendering.py +0 -0
  90. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_workflows/sources/__init__.py +0 -0
  91. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_workflows/sources/local.py +0 -0
  92. {codeboarding-0.13.3 → codeboarding-0.13.4}/codeboarding_workflows/sources/remote.py +0 -0
  93. {codeboarding-0.13.3 → codeboarding-0.13.4}/constants.py +0 -0
  94. {codeboarding-0.13.3 → codeboarding-0.13.4}/core/__init__.py +0 -0
  95. {codeboarding-0.13.3 → codeboarding-0.13.4}/core/plugin_loader.py +0 -0
  96. {codeboarding-0.13.3 → codeboarding-0.13.4}/core/protocols.py +0 -0
  97. {codeboarding-0.13.3 → codeboarding-0.13.4}/core/registry.py +0 -0
  98. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/__init__.py +0 -0
  99. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/analysis_json.py +0 -0
  100. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/cluster_delta.py +0 -0
  101. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/cluster_snapshot.py +0 -0
  102. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/exceptions.py +0 -0
  103. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/file_coverage.py +0 -0
  104. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/file_index.py +0 -0
  105. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/io_utils.py +0 -0
  106. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/run_context.py +0 -0
  107. {codeboarding-0.13.3 → codeboarding-0.13.4}/diagram_analysis/run_mode.py +0 -0
  108. {codeboarding-0.13.3 → codeboarding-0.13.4}/github_action.py +0 -0
  109. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/__init__.py +0 -0
  110. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/checks/__init__.py +0 -0
  111. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/checks/circular_deps.py +0 -0
  112. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/checks/cohesion.py +0 -0
  113. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/checks/coupling.py +0 -0
  114. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/checks/god_class.py +0 -0
  115. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/checks/inheritance.py +0 -0
  116. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/checks/instability.py +0 -0
  117. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/checks/unused_code_diagnostics.py +0 -0
  118. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/config.py +0 -0
  119. {codeboarding-0.13.3 → codeboarding-0.13.4}/health/models.py +0 -0
  120. {codeboarding-0.13.3 → codeboarding-0.13.4}/install.py +0 -0
  121. {codeboarding-0.13.3 → codeboarding-0.13.4}/logging_config.py +0 -0
  122. {codeboarding-0.13.3 → codeboarding-0.13.4}/main.py +0 -0
  123. {codeboarding-0.13.3 → codeboarding-0.13.4}/monitoring/__init__.py +0 -0
  124. {codeboarding-0.13.3 → codeboarding-0.13.4}/monitoring/callbacks.py +0 -0
  125. {codeboarding-0.13.3 → codeboarding-0.13.4}/monitoring/context.py +0 -0
  126. {codeboarding-0.13.3 → codeboarding-0.13.4}/monitoring/mixin.py +0 -0
  127. {codeboarding-0.13.3 → codeboarding-0.13.4}/monitoring/paths.py +0 -0
  128. {codeboarding-0.13.3 → codeboarding-0.13.4}/monitoring/stats.py +0 -0
  129. {codeboarding-0.13.3 → codeboarding-0.13.4}/monitoring/writers.py +0 -0
  130. {codeboarding-0.13.3 → codeboarding-0.13.4}/output_generators/__init__.py +0 -0
  131. {codeboarding-0.13.3 → codeboarding-0.13.4}/output_generators/html.py +0 -0
  132. {codeboarding-0.13.3 → codeboarding-0.13.4}/output_generators/mdx.py +0 -0
  133. {codeboarding-0.13.3 → codeboarding-0.13.4}/repo_utils/__init__.py +0 -0
  134. {codeboarding-0.13.3 → codeboarding-0.13.4}/repo_utils/change_detector.py +0 -0
  135. {codeboarding-0.13.3 → codeboarding-0.13.4}/repo_utils/errors.py +0 -0
  136. {codeboarding-0.13.3 → codeboarding-0.13.4}/repo_utils/fingerprint_diff.py +0 -0
  137. {codeboarding-0.13.3 → codeboarding-0.13.4}/repo_utils/git_ops.py +0 -0
  138. {codeboarding-0.13.3 → codeboarding-0.13.4}/repo_utils/path_utils.py +0 -0
  139. {codeboarding-0.13.3 → codeboarding-0.13.4}/setup.cfg +0 -0
  140. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/__init__.py +0 -0
  141. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/analysis_cache.py +0 -0
  142. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/analysis_result.py +0 -0
  143. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/cluster_helpers.py +0 -0
  144. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/constants.py +0 -0
  145. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/csharp_config_scanner.py +0 -0
  146. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/dotnet_sdk.py +0 -0
  147. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/__init__.py +0 -0
  148. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/adapters/__init__.py +0 -0
  149. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/adapters/csharp_adapter.py +0 -0
  150. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/adapters/go_adapter.py +0 -0
  151. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/adapters/java_adapter.py +0 -0
  152. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/adapters/php_adapter.py +0 -0
  153. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/adapters/python_adapter.py +0 -0
  154. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/adapters/rust_adapter.py +0 -0
  155. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/adapters/typescript_adapter.py +0 -0
  156. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/call_graph_builder.py +0 -0
  157. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/edge_build_context.py +0 -0
  158. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/edge_builder.py +0 -0
  159. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/hierarchy_builder.py +0 -0
  160. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/language_adapter.py +0 -0
  161. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/lsp_client.py +0 -0
  162. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/lsp_constants.py +0 -0
  163. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/models.py +0 -0
  164. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/progress.py +0 -0
  165. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/protocols.py +0 -0
  166. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/result_converter.py +0 -0
  167. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/source_inspector.py +0 -0
  168. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/symbol_table.py +0 -0
  169. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/engine/utils.py +0 -0
  170. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/graph.py +0 -0
  171. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/incremental_orchestrator.py +0 -0
  172. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/java_config_scanner.py +0 -0
  173. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/java_utils.py +0 -0
  174. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/language_results.py +0 -0
  175. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/leiden_utils.py +0 -0
  176. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/lsp_client/__init__.py +0 -0
  177. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/lsp_client/diagnostics.py +0 -0
  178. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/node.py +0 -0
  179. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/programming_language.py +0 -0
  180. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/scanner.py +0 -0
  181. {codeboarding-0.13.3 → codeboarding-0.13.4}/static_analyzer/typescript_config_scanner.py +0 -0
  182. {codeboarding-0.13.3 → codeboarding-0.13.4}/telemetry/__init__.py +0 -0
  183. {codeboarding-0.13.3 → codeboarding-0.13.4}/telemetry/device_id.py +0 -0
  184. {codeboarding-0.13.3 → codeboarding-0.13.4}/telemetry/events.py +0 -0
  185. {codeboarding-0.13.3 → codeboarding-0.13.4}/telemetry/schemas.py +0 -0
  186. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_cli_parser.py +0 -0
  187. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_github_action.py +0 -0
  188. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_install.py +0 -0
  189. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_logging_config.py +0 -0
  190. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_main.py +0 -0
  191. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_pyproject_packages.py +0 -0
  192. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_registry_coverage.py +0 -0
  193. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_telemetry_events.py +0 -0
  194. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_tool_registry.py +0 -0
  195. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_user_config.py +0 -0
  196. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_view_instructions.py +0 -0
  197. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_vscode_constants.py +0 -0
  198. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_windows_compatibility.py +0 -0
  199. {codeboarding-0.13.3 → codeboarding-0.13.4}/tests/test_windows_encoding.py +0 -0
  200. {codeboarding-0.13.3 → codeboarding-0.13.4}/tool_registry/__init__.py +0 -0
  201. {codeboarding-0.13.3 → codeboarding-0.13.4}/tool_registry/installers.py +0 -0
  202. {codeboarding-0.13.3 → codeboarding-0.13.4}/tool_registry/manifest.py +0 -0
  203. {codeboarding-0.13.3 → codeboarding-0.13.4}/tool_registry/paths.py +0 -0
  204. {codeboarding-0.13.3 → codeboarding-0.13.4}/tool_registry/registry.py +0 -0
  205. {codeboarding-0.13.3 → codeboarding-0.13.4}/user_config.py +0 -0
  206. {codeboarding-0.13.3 → codeboarding-0.13.4}/utils.py +0 -0
  207. {codeboarding-0.13.3 → codeboarding-0.13.4}/vscode_constants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.13.3
3
+ Version: 0.13.4
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -73,6 +73,7 @@ Dynamic: license-file
73
73
  # CodeBoarding
74
74
 
75
75
  [![Website](https://img.shields.io/badge/Site-CodeBoarding.org-5865F2?style=for-the-badge&logoColor=white)](https://codeboarding.org)
76
+ [![Web platform](https://img.shields.io/badge/Web%20platform-app.codeboarding.org-2563EB?style=for-the-badge&logoColor=white)](https://app.codeboarding.org)
76
77
  [![Discord](https://img.shields.io/badge/Discord-Join%20Us-5865F2?style=for-the-badge&logoColor=white)](https://discord.gg/T5zHTJYFuy)
77
78
  [![GitHub](https://img.shields.io/badge/GitHub-CodeBoarding-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/CodeBoarding/CodeBoarding)
78
79
 
@@ -130,6 +131,9 @@ codeboarding full --local /path/to/repo
130
131
  codeboarding full https://github.com/user/repo
131
132
  ```
132
133
 
134
+ To explore the result interactively, open the [web platform](https://app.codeboarding.org) and load the
135
+ generated `.codeboarding/analysis.json` — it is parsed in your browser, nothing is uploaded.
136
+
133
137
  ### Python API
134
138
 
135
139
  ```python
@@ -1,6 +1,7 @@
1
1
  # CodeBoarding
2
2
 
3
3
  [![Website](https://img.shields.io/badge/Site-CodeBoarding.org-5865F2?style=for-the-badge&logoColor=white)](https://codeboarding.org)
4
+ [![Web platform](https://img.shields.io/badge/Web%20platform-app.codeboarding.org-2563EB?style=for-the-badge&logoColor=white)](https://app.codeboarding.org)
4
5
  [![Discord](https://img.shields.io/badge/Discord-Join%20Us-5865F2?style=for-the-badge&logoColor=white)](https://discord.gg/T5zHTJYFuy)
5
6
  [![GitHub](https://img.shields.io/badge/GitHub-CodeBoarding-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/CodeBoarding/CodeBoarding)
6
7
 
@@ -58,6 +59,9 @@ codeboarding full --local /path/to/repo
58
59
  codeboarding full https://github.com/user/repo
59
60
  ```
60
61
 
62
+ To explore the result interactively, open the [web platform](https://app.codeboarding.org) and load the
63
+ generated `.codeboarding/analysis.json` — it is parsed in your browser, nothing is uploaded.
64
+
61
65
  ### Python API
62
66
 
63
67
  ```python
@@ -4,7 +4,7 @@ See what your AI is building before it breaks.
4
4
 
5
5
  CodeBoarding gives developers and coding agents a visual map of a codebase. It combines static analysis with LLM reasoning to generate architecture diagrams, component-level documentation, and navigable outputs you can use in your IDE, CI, and docs.
6
6
 
7
- [Website](https://codeboarding.org) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=0855d476-b2d0-44cc-b93d-69b47504719c" width="0" height="0" /> · [Open VSX extension](https://open-vsx.org/extension/CodeBoarding/codeboarding) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=ce87464c-2792-46b0-9ea2-87eefe853d7e" width="0" height="0" /> · [Explore examples](https://codeboarding.org/diagrams) · [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=8a3d26e0-6f6b-49c0-8482-114445de56a5" width="0" height="0" /> · [GitHub Action](https://github.com/marketplace/actions/codeboarding-action) ·[Discord](https://discord.gg/T5zHTJYFuy)
7
+ [Website](https://codeboarding.org) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=0855d476-b2d0-44cc-b93d-69b47504719c" width="0" height="0" /> · [Web platform](https://app.codeboarding.org) · [Open VSX extension](https://open-vsx.org/extension/CodeBoarding/codeboarding) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=ce87464c-2792-46b0-9ea2-87eefe853d7e" width="0" height="0" /> · [Explore examples](https://codeboarding.org/diagrams) · [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=8a3d26e0-6f6b-49c0-8482-114445de56a5" width="0" height="0" /> · [GitHub Action](https://github.com/marketplace/actions/codeboarding-action) · [Discord](https://discord.gg/T5zHTJYFuy)
8
8
 
9
9
  [![CodeBoarding demo](https://gist.githubusercontent.com/ivanmilevtues/1c4f921066613516cfd7b938014a6877/raw/611aec7711556807860ff2e1679a5dc4c0c23fed/CodeBoarding_extension_demo.gif)](https://open-vsx.org/extension/CodeBoarding/codeboarding) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=ce87464c-2792-46b0-9ea2-87eefe853d7e" width="0" height="0" />
10
10
 
@@ -91,7 +91,9 @@ codeboarding-setup
91
91
  codeboarding full --local /path/to/repo
92
92
  ```
93
93
 
94
- Output is written to `/path/to/repo/.codeboarding/`.
94
+ Output is written to `/path/to/repo/.codeboarding/`. To explore it interactively, open the
95
+ [web platform](https://app.codeboarding.org) and drop in the generated `analysis.json` — it stays
96
+ in your browser, nothing is uploaded. (The CLI prints this reminder after every successful run.)
95
97
 
96
98
  `python install.py` and `codeboarding-setup` download language server binaries to `~/.codeboarding/servers/`, shared across projects. Node.js (and its bundled `npm`) is required for the Python, TypeScript, JavaScript, and PHP language servers; if neither `node` nor `CODEBOARDING_NODE_PATH` is set, setup downloads a pinned Node.js runtime into `~/.codeboarding/servers/nodeenv/` automatically.
97
99
 
@@ -150,6 +152,7 @@ python main.py full https://github.com/pytorch/pytorch
150
152
  ## Where to use it
151
153
 
152
154
  - [CLI](https://github.com/CodeBoarding/CodeBoarding) for local analysis, automation, and CI workflows.
155
+ - [Web platform](https://app.codeboarding.org) to explore any analysis in the browser — open a public repo, load an `analysis.json`, or review an architecture diff on a pull request.
153
156
  - [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=8a3d26e0-6f6b-49c0-8482-114445de56a5" width="0" height="0" /> for in-editor visual architecture.
154
157
  - [GitHub Action](https://github.com/marketplace/actions/codeboarding-action) to keep diagrams updated in CI.
155
158
 
@@ -163,6 +166,7 @@ python main.py full https://github.com/pytorch/pytorch
163
166
  - Visualized 800+ open-source repositories.
164
167
  - Browse generated examples in [GeneratedOnBoardings](https://github.com/CodeBoarding/GeneratedOnBoardings).
165
168
  - Try the hosted explorer at [codeboarding.org/diagrams](https://codeboarding.org/diagrams) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=0855d476-b2d0-44cc-b93d-69b47504719c" width="0" height="0" />.
169
+ - Open any public repo that has a committed `.codeboarding/analysis.json` at [app.codeboarding.org](https://app.codeboarding.org) — no install, no login. This repo included: [app.codeboarding.org/CodeBoarding/CodeBoarding](https://app.codeboarding.org/CodeBoarding/CodeBoarding).
166
170
 
167
171
  ## Telemetry
168
172
 
@@ -40,6 +40,11 @@ from agents.prompts import (
40
40
  get_system_message,
41
41
  )
42
42
  from agents.relation_edges import index_relation_endpoints, preserve_unchanged_relations
43
+ from static_analyzer.cluster_relations import (
44
+ build_node_to_component_map,
45
+ build_owner_index,
46
+ prune_ungrounded_edges,
47
+ )
43
48
  from agents.scope_ids import ROOT_SCOPE_ID
44
49
  from agents.validation import ValidationContext, validate_relations
45
50
  from diagram_analysis.file_index import build_files_index
@@ -137,12 +142,9 @@ class IncrementalAgent(ClusterMethodsMixin, CodeBoardingAgent):
137
142
  continue
138
143
  if operation.action == ScopeOperationAction.NOOP:
139
144
  component = components_by_id.get(operation.component_id or "")
140
- if component is not None:
141
- component.source_cluster_ids = CodeBoardingClusterIds.sort(
142
- set(component.source_cluster_ids) | set(_operation_source_cluster_ids(scope_id, operation))
143
- )
144
- if component.component_id:
145
- refresh_ids.add(component.component_id)
145
+ if component is None:
146
+ continue
147
+ self._sync_noop_component_cluster_ids(scope_id, component, operation, refresh_ids)
146
148
  continue
147
149
 
148
150
  component = components_by_id.get(operation.component_id or "")
@@ -209,6 +211,20 @@ class IncrementalAgent(ClusterMethodsMixin, CodeBoardingAgent):
209
211
  new_component_ids.add(component.component_id)
210
212
  components_by_id[component.component_id] = component
211
213
 
214
+ def _sync_noop_component_cluster_ids(
215
+ self,
216
+ scope_id: str,
217
+ component: Component,
218
+ operation: ScopeOperation,
219
+ refresh_ids: set[str],
220
+ ) -> None:
221
+ merged_cluster_ids = CodeBoardingClusterIds.sort(
222
+ set(component.source_cluster_ids) | set(_operation_source_cluster_ids(scope_id, operation))
223
+ )
224
+ if merged_cluster_ids != component.source_cluster_ids and component.component_id:
225
+ refresh_ids.add(component.component_id)
226
+ component.source_cluster_ids = merged_cluster_ids
227
+
212
228
  @trace
213
229
  def detail_new_components(self, components: list[Component]) -> None:
214
230
  """Replace new components' provisional names and descriptions in one LLM call."""
@@ -383,6 +399,17 @@ class IncrementalAgent(ClusterMethodsMixin, CodeBoardingAgent):
383
399
  for relation in scope.components_relations
384
400
  if relation.src_id and relation.dst_id
385
401
  }
402
+ # Also keyed by NAME. Ids are assigned per run by `assign_relation_ids`, so a
403
+ # re-partition can hand the same two components different numbers; the id lookup then
404
+ # misses and the pair is treated as brand new, which re-rolls its wording. Measured on
405
+ # `referenced-symbol-deleted`: 9 statically-backed relations whose call set was
406
+ # byte-identical came back re-worded for exactly this reason. Names survive
407
+ # renumbering, so they are the fallback identity.
408
+ baseline_by_names = {
409
+ (relation.src_name, relation.dst_name): relation.model_copy(deep=True)
410
+ for relation in scope.components_relations
411
+ if relation.src_name and relation.dst_name
412
+ }
386
413
 
387
414
  if context.changed_ids:
388
415
  cluster_analysis = _cluster_analysis_for_scope(scope, scope_name, context.cluster_results)
@@ -404,20 +431,52 @@ class IncrementalAgent(ClusterMethodsMixin, CodeBoardingAgent):
404
431
 
405
432
  if baseline_by_pair:
406
433
  live_ids = {component.component_id for component in scope.components if component.component_id}
434
+ # A relation is ADDED or REMOVED because code changed, not because a partitioner
435
+ # moved untouched methods around. `context.changed_ids` carries both — it is the
436
+ # right scope to re-derive, and the wrong reason to change the edge set — so the
437
+ # preservation gate sees only components the COMMIT reached. Components that are
438
+ # new or gone keep their ids here: they are changes the commit does account for.
439
+ changed_component_ids = {
440
+ component.component_id
441
+ for component in scope.components
442
+ if component.component_id
443
+ and any(
444
+ method.qualified_name in (changed_members or set())
445
+ for group in component.file_methods
446
+ for method in group.methods
447
+ )
448
+ }
449
+ commit_changed = changed_component_ids | (set(context.changed_ids) - live_ids)
407
450
  live_qnames = {
408
451
  qualified_name
409
452
  for cluster_result in context.cluster_results.values()
410
453
  for members in cluster_result.clusters.values()
411
454
  for qualified_name in members
412
455
  }
456
+ for relation in scope.components_relations:
457
+ # Carry the baseline forward when regenerated component ids changed but names did not.
458
+ pair = (relation.src_id, relation.dst_id)
459
+ if pair not in baseline_by_pair:
460
+ by_name = baseline_by_names.get((relation.src_name, relation.dst_name))
461
+ if by_name is not None:
462
+ baseline_by_pair[pair] = by_name
413
463
  scope.components_relations = preserve_unchanged_relations(
414
464
  scope.components_relations,
415
465
  baseline_by_pair,
416
- set(context.changed_ids),
466
+ commit_changed,
417
467
  live_ids,
418
468
  live_qnames,
419
469
  changed_members,
420
470
  )
471
+ # Preservation runs after the grounding filters and re-injects baseline edges, so
472
+ # the assembled list is filtered once more — otherwise a baseline's invented or
473
+ # mis-attributed edges survive every update that leaves their methods alone.
474
+ scope.components_relations = prune_ungrounded_edges(
475
+ scope.components_relations,
476
+ build_owner_index(build_node_to_component_map(scope)),
477
+ self.reference_resolver.keep_relation_edge,
478
+ changed_members or set(),
479
+ )
421
480
  rels = scope.components_relations
422
481
  return rels
423
482
 
@@ -793,6 +852,7 @@ def _scrub_one_analysis(analysis: AnalysisInsights, live_files: set[str]) -> set
793
852
  """Drop dead-file references in one analysis and return dropped paths."""
794
853
  dropped: set[str] = set()
795
854
  for component in analysis.components:
855
+ had_methods = any(group.methods for group in component.file_methods)
796
856
  kept_groups = []
797
857
  for group in component.file_methods:
798
858
  if group.file_path in live_files:
@@ -805,6 +865,8 @@ def _scrub_one_analysis(analysis: AnalysisInsights, live_files: set[str]) -> set
805
865
  for key_entity in component.key_entities
806
866
  if key_entity.reference_file is None or key_entity.reference_file in live_files
807
867
  ]
868
+ if had_methods and not any(group.methods for group in component.file_methods):
869
+ component.source_cluster_ids = []
808
870
  dropped |= {file_path for file_path in analysis.files if file_path not in live_files}
809
871
  analysis.files = {file_path: entry for file_path, entry in analysis.files.items() if file_path in live_files}
810
872
  return dropped
@@ -813,17 +875,15 @@ def _scrub_one_analysis(analysis: AnalysisInsights, live_files: set[str]) -> set
813
875
  def prune_empty_components(
814
876
  root_analysis: AnalysisInsights,
815
877
  sub_analyses: dict[str, AnalysisInsights],
816
- protected_empty_ids: set[str] | None = None,
817
878
  ) -> set[str]:
818
- """Remove components with no methods and strip relations pointing to them."""
879
+ """Remove components with neither members nor live cluster lineage."""
819
880
  removed_ids: set[str] = set()
820
- protected_empty_ids = protected_empty_ids or set()
821
881
 
822
882
  def has_methods(component: Component) -> bool:
823
883
  return (
824
884
  any(group.methods for group in component.file_methods)
825
885
  or bool(component.key_entities)
826
- or bool(component.component_id in protected_empty_ids and component.source_cluster_ids)
886
+ or bool(component.source_cluster_ids)
827
887
  )
828
888
 
829
889
  def collect_empty(analysis: AnalysisInsights) -> None:
@@ -55,7 +55,8 @@ def index_relation_endpoints(analysis: AnalysisInsights, repo_dir: Path) -> None
55
55
  def _is_internal_self_relation(relation: Relation) -> bool:
56
56
  """A component related to itself by concrete intra-component calls.
57
57
 
58
- The backing edges are real CFG calls whose endpoints currently fall in the same
58
+ Its supporting calls the concrete method-to-method calls stored under the relation
59
+ are real CFG calls whose endpoints currently fall in the same
59
60
  component, so at this granularity there is no cross-component connection to draw — the
60
61
  loop is the residue of a rollup that collapsed a cross-child edge onto its shared parent.
61
62
  The CFG still holds every such edge, so expanding the component re-materialises it across
@@ -76,7 +77,7 @@ def drop_internal_self_relations(relations: list[Relation]) -> list[Relation]:
76
77
  def _relation_backing_survives(relation: Relation, live_qnames: set[str]) -> bool:
77
78
  """Whether a baseline relation's static backing still exists in live code.
78
79
 
79
- A relation restored verbatim keeps its call edges. If the only edges connecting the two
80
+ A relation is supported by concrete method-to-method calls; restoring it verbatim keeps them. If the only edges connecting the two
80
81
  components pointed at a symbol since deleted, restoring the relation resurrects an edge
81
82
  citing code that is gone — a phantom. A relation with no static edge at all is a
82
83
  runtime/config relation with nothing to invalidate, so it is left alone.
@@ -90,6 +91,7 @@ def _relation_backing_survives(relation: Relation, live_qnames: set[str]) -> boo
90
91
 
91
92
 
92
93
  def _backing_edge_pairs(relation: Relation) -> set[tuple[str, str]]:
94
+ """The (source, target) method pairs supporting this relation — the calls under the arrow."""
93
95
  edges = relation.all_edges or relation.key_edges
94
96
  return {(edge.source.qualified_name, edge.target.qualified_name) for edge in edges}
95
97
 
@@ -107,7 +109,65 @@ def _relation_edges_unmoved(rebuilt: Relation, previous: Relation) -> bool:
107
109
 
108
110
 
109
111
  def _edge_touches_changed_method(edge: RelationEdge, changed_members: set[str]) -> bool:
110
- return edge.source.qualified_name in changed_members or edge.target.qualified_name in changed_members
112
+ return edge.source.qualified_name in changed_members
113
+
114
+
115
+ def _restore_baseline_orientation(relation: Relation, baseline_by_pair: dict) -> Relation:
116
+ """Put an ungrounded relation back the way round the reader last saw it.
117
+
118
+ Why only ungrounded ones: a statically-backed relation takes its direction from real CFG
119
+ edges, so the arrow means something and must not be touched. A runtime/config relation has
120
+ no call to fix it, so the model picks an orientation afresh each run — and a swap keyed on
121
+ the ordered pair reads as one relation deleted and another added. On the reported case
122
+ (CodeBoarding-action#65) two of the five changed edges were exactly that: the same two
123
+ component pairs with their arrows reversed and relabelled.
124
+
125
+ Applied only when the fresh relation carries no backing edges at all, because an edge is
126
+ what would make the direction a claim rather than a phrasing — flipping a relation that has
127
+ one would point its edges the wrong way.
128
+ """
129
+ if relation.is_static or relation.all_edges or relation.key_edges:
130
+ return relation
131
+ flipped = baseline_by_pair.get((relation.dst_id, relation.src_id))
132
+ if flipped is None or flipped.is_static or flipped.all_edges or flipped.key_edges:
133
+ return relation
134
+ return relation.model_copy(
135
+ update={
136
+ "src_id": flipped.src_id,
137
+ "dst_id": flipped.dst_id,
138
+ "src_name": flipped.src_name,
139
+ "dst_name": flipped.dst_name,
140
+ "relation": flipped.relation,
141
+ "evidence": flipped.evidence,
142
+ }
143
+ )
144
+
145
+
146
+ def _filter_edges_touched_by_change(relation: Relation, changed_members: set[str]) -> Relation:
147
+ """Keep only the edges a code change can account for, for a pair with no baseline.
148
+
149
+ Why: a call lives in its source method's body, so an edge can only appear when one of its
150
+ endpoints changed. ``_reconcile_unchanged_edges`` enforces that by falling back to the
151
+ baseline, but re-clustering invents component-pairs that have no baseline to fall back to —
152
+ there every re-attributed edge would otherwise enter as if the commit had written it.
153
+ """
154
+ return relation.model_copy(
155
+ update={
156
+ "all_edges": [e for e in relation.all_edges if _edge_touches_changed_method(e, changed_members)],
157
+ "key_edges": [e for e in relation.key_edges if _edge_touches_changed_method(e, changed_members)],
158
+ }
159
+ )
160
+
161
+
162
+ def _commit_deleted_the_backing(rebuilt: Relation, previous: Relation, changed_members: set[str]) -> bool:
163
+ """Return whether a changed source removed every baseline backing edge.
164
+
165
+ A call belongs to its source method, so only lost edges from changed sources prove deletion.
166
+ """
167
+ baseline_edges = previous.all_edges or previous.key_edges
168
+ if not baseline_edges or rebuilt.all_edges or rebuilt.key_edges or rebuilt.evidence.strip():
169
+ return False
170
+ return any(_edge_touches_changed_method(edge, changed_members) for edge in baseline_edges)
111
171
 
112
172
 
113
173
  def _reconcile_unchanged_edges(fresh: Relation, previous: Relation, changed_members: set[str]) -> Relation:
@@ -160,6 +220,9 @@ def preserve_unchanged_relations(
160
220
  their inter-component edges are byte-identical. Gating on the pair's own edges instead of
161
221
  on its endpoints' change flags is what keeps those untouched connections stable.
162
222
 
223
+ A pair whose changed source lost every baseline call is dropped unless the rebuild supplies
224
+ alternate runtime/config evidence.
225
+
163
226
  A baseline relation between two unchanged, still-live components that regeneration
164
227
  dropped is restored — but only when its backing edges still exist in live code. The
165
228
  deterministic rebuild drops a pair when the code connecting the two components was
@@ -176,6 +239,8 @@ def preserve_unchanged_relations(
176
239
  kept: list[Relation] = []
177
240
  rebuilt_pairs: set[tuple[str, str]] = set()
178
241
  for relation in rebuilt_relations:
242
+ if (relation.src_id, relation.dst_id) not in baseline_by_pair:
243
+ relation = _restore_baseline_orientation(relation, baseline_by_pair)
179
244
  pair = (relation.src_id, relation.dst_id)
180
245
  rebuilt_pairs.add(pair)
181
246
  previous = baseline_by_pair.get(pair)
@@ -183,8 +248,14 @@ def preserve_unchanged_relations(
183
248
  # No baseline for this pair. A genuinely new connection into a changed area is
184
249
  # kept; a fresh edge invented between two untouched components is a re-attribution
185
250
  # artifact and is dropped.
186
- if touches_change(*pair):
187
- kept.append(relation)
251
+ if not touches_change(*pair):
252
+ continue
253
+ if changed_members is not None:
254
+ relation = _filter_edges_touched_by_change(relation, changed_members)
255
+ # Nothing survived and nothing else is claimed: the pair asserts no connection.
256
+ if not relation.all_edges and not relation.key_edges and not relation.evidence.strip():
257
+ continue
258
+ kept.append(relation)
188
259
  continue
189
260
  # Every edge between two byte-identical methods is taken from the baseline; only edges
190
261
  # that touch a changed/added/deleted method come from the fresh rebuild. This stops
@@ -192,9 +263,23 @@ def preserve_unchanged_relations(
192
263
  # pairs alike.
193
264
  if changed_members is not None:
194
265
  relation = _reconcile_unchanged_edges(relation, previous, changed_members)
195
- # Keep the reader's wording unless a real, code-backed edge change re-worded it: an
196
- # untouched pair, or one whose edges came through unmoved, carries its label over.
197
- if not touches_change(*pair) or _relation_edges_unmoved(relation, previous):
266
+ if _commit_deleted_the_backing(relation, previous, changed_members):
267
+ continue
268
+ # Keep the reader's wording unless this pair's own supporting calls moved.
269
+ #
270
+ # The supporting calls are the concrete method-to-method calls under the relation — the
271
+ # ones that show WHERE one component reaches the other. They are the only evidence that
272
+ # the connection itself changed, so they are what decides whether it may be re-worded.
273
+ # Gating on the endpoint components' change flags instead re-words far more: a component
274
+ # is flagged for any edit to a file it merely co-owns, so deleting one function re-worded
275
+ # 17 of the relations in `referenced-symbol-deleted` whose calls had not moved at all.
276
+ # A pair with no supporting calls has nothing that could have moved, so its wording is
277
+ # kept too — an edgeless runtime relation is prose, and re-rolling prose is the churn.
278
+ if (
279
+ not touches_change(*pair)
280
+ or _relation_edges_unmoved(relation, previous)
281
+ or (not _backing_edge_pairs(relation) and not relation.evidence.strip())
282
+ ):
198
283
  relation = relation.model_copy(update={"relation": previous.relation, "evidence": previous.evidence})
199
284
  kept.append(relation)
200
285
  for pair, relation in baseline_by_pair.items():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.13.3
3
+ Version: 0.13.4
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -73,6 +73,7 @@ Dynamic: license-file
73
73
  # CodeBoarding
74
74
 
75
75
  [![Website](https://img.shields.io/badge/Site-CodeBoarding.org-5865F2?style=for-the-badge&logoColor=white)](https://codeboarding.org)
76
+ [![Web platform](https://img.shields.io/badge/Web%20platform-app.codeboarding.org-2563EB?style=for-the-badge&logoColor=white)](https://app.codeboarding.org)
76
77
  [![Discord](https://img.shields.io/badge/Discord-Join%20Us-5865F2?style=for-the-badge&logoColor=white)](https://discord.gg/T5zHTJYFuy)
77
78
  [![GitHub](https://img.shields.io/badge/GitHub-CodeBoarding-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/CodeBoarding/CodeBoarding)
78
79
 
@@ -130,6 +131,9 @@ codeboarding full --local /path/to/repo
130
131
  codeboarding full https://github.com/user/repo
131
132
  ```
132
133
 
134
+ To explore the result interactively, open the [web platform](https://app.codeboarding.org) and load the
135
+ generated `.codeboarding/analysis.json` — it is parsed in your browser, nothing is uploaded.
136
+
133
137
  ### Python API
134
138
 
135
139
  ```python
@@ -97,6 +97,7 @@ diagram_analysis/io_utils.py
97
97
  diagram_analysis/run_context.py
98
98
  diagram_analysis/run_mode.py
99
99
  diagram_analysis/scope_plan.py
100
+ diagram_analysis/tree_shape.py
100
101
  health/__init__.py
101
102
  health/config.py
102
103
  health/models.py
@@ -189,6 +190,7 @@ tests/test_main.py
189
190
  tests/test_pyproject_packages.py
190
191
  tests/test_registry_coverage.py
191
192
  tests/test_telemetry_events.py
193
+ tests/test_telemetry_service.py
192
194
  tests/test_tool_registry.py
193
195
  tests/test_user_config.py
194
196
  tests/test_view_instructions.py