codeboarding 0.14.1__tar.gz → 0.14.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. {codeboarding-0.14.1/codeboarding.egg-info → codeboarding-0.14.2}/PKG-INFO +1 -1
  2. {codeboarding-0.14.1 → codeboarding-0.14.2}/README.md +5 -4
  3. {codeboarding-0.14.1 → codeboarding-0.14.2/codeboarding.egg-info}/PKG-INFO +1 -1
  4. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding.egg-info/SOURCES.txt +1 -0
  5. codeboarding-0.14.2/health_main.py +152 -0
  6. {codeboarding-0.14.1 → codeboarding-0.14.2}/pyproject.toml +1 -1
  7. {codeboarding-0.14.1 → codeboarding-0.14.2}/repo_utils/__init__.py +3 -2
  8. codeboarding-0.14.2/telemetry/__init__.py +11 -0
  9. {codeboarding-0.14.1 → codeboarding-0.14.2}/telemetry/service.py +32 -18
  10. codeboarding-0.14.2/tests/test_telemetry_service.py +205 -0
  11. codeboarding-0.14.1/telemetry/__init__.py +0 -10
  12. codeboarding-0.14.1/tests/test_telemetry_service.py +0 -99
  13. {codeboarding-0.14.1 → codeboarding-0.14.2}/LICENSE +0 -0
  14. {codeboarding-0.14.1 → codeboarding-0.14.2}/PYPI.md +0 -0
  15. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/__init__.py +0 -0
  16. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/agent_responses.py +0 -0
  17. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/change_status.py +0 -0
  18. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/component_ownership.py +0 -0
  19. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/constants.py +0 -0
  20. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/content_hash.py +0 -0
  21. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/file_index_models.py +0 -0
  22. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/incremental_results.py +0 -0
  23. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/llm_config.py +0 -0
  24. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/llm_errors.py +0 -0
  25. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/llm_renderers/__init__.py +0 -0
  26. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/llm_renderers/call_graph.py +0 -0
  27. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/llm_renderers/scope.py +0 -0
  28. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/model_capabilities.py +0 -0
  29. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/planner_agent.py +0 -0
  30. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/prompts/__init__.py +0 -0
  31. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/prompts/profiles.py +0 -0
  32. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/prompts/shared.py +0 -0
  33. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/relation_edges.py +0 -0
  34. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/retry.py +0 -0
  35. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/scope_analysis_agent.py +0 -0
  36. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/scope_ids.py +0 -0
  37. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/tools/__init__.py +0 -0
  38. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/tools/base.py +0 -0
  39. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/tools/get_method_calls.py +0 -0
  40. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/tools/read_file.py +0 -0
  41. {codeboarding-0.14.1 → codeboarding-0.14.2}/agents/tree_planner_agent.py +0 -0
  42. {codeboarding-0.14.1 → codeboarding-0.14.2}/clustering_ids.py +0 -0
  43. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding.egg-info/dependency_links.txt +0 -0
  44. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding.egg-info/entry_points.txt +0 -0
  45. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding.egg-info/requires.txt +0 -0
  46. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding.egg-info/top_level.txt +0 -0
  47. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_cli/__init__.py +0 -0
  48. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_cli/bootstrap.py +0 -0
  49. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_cli/commands/__init__.py +0 -0
  50. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_cli/commands/full_analysis.py +0 -0
  51. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_cli/commands/incremental_analysis.py +0 -0
  52. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_cli/commands/partial_analysis.py +0 -0
  53. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_cli/render.py +0 -0
  54. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_cli/view_instructions.py +0 -0
  55. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_workflows/__init__.py +0 -0
  56. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_workflows/analysis.py +0 -0
  57. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_workflows/orchestration.py +0 -0
  58. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_workflows/sources/__init__.py +0 -0
  59. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_workflows/sources/local.py +0 -0
  60. {codeboarding-0.14.1 → codeboarding-0.14.2}/codeboarding_workflows/sources/remote.py +0 -0
  61. {codeboarding-0.14.1 → codeboarding-0.14.2}/constants.py +0 -0
  62. {codeboarding-0.14.1 → codeboarding-0.14.2}/core/__init__.py +0 -0
  63. {codeboarding-0.14.1 → codeboarding-0.14.2}/core/plugin_loader.py +0 -0
  64. {codeboarding-0.14.1 → codeboarding-0.14.2}/core/protocols.py +0 -0
  65. {codeboarding-0.14.1 → codeboarding-0.14.2}/core/registry.py +0 -0
  66. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/__init__.py +0 -0
  67. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/analysis_json.py +0 -0
  68. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/diagram_generator.py +0 -0
  69. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/exceptions.py +0 -0
  70. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/file_coverage.py +0 -0
  71. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/file_index.py +0 -0
  72. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/incremental_changes.py +0 -0
  73. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/incremental_update.py +0 -0
  74. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/io_utils.py +0 -0
  75. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/run_context.py +0 -0
  76. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/run_mode.py +0 -0
  77. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/scope_assembly.py +0 -0
  78. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/scope_plan.py +0 -0
  79. {codeboarding-0.14.1 → codeboarding-0.14.2}/diagram_analysis/tree_shape.py +0 -0
  80. {codeboarding-0.14.1 → codeboarding-0.14.2}/github_action.py +0 -0
  81. {codeboarding-0.14.1 → codeboarding-0.14.2}/health/__init__.py +0 -0
  82. {codeboarding-0.14.1 → codeboarding-0.14.2}/health/checks/__init__.py +0 -0
  83. {codeboarding-0.14.1 → codeboarding-0.14.2}/health/checks/circular_deps.py +0 -0
  84. {codeboarding-0.14.1 → codeboarding-0.14.2}/health/checks/function_size.py +0 -0
  85. {codeboarding-0.14.1 → codeboarding-0.14.2}/health/checks/unused_code_diagnostics.py +0 -0
  86. {codeboarding-0.14.1 → codeboarding-0.14.2}/health/config.py +0 -0
  87. {codeboarding-0.14.1 → codeboarding-0.14.2}/health/models.py +0 -0
  88. {codeboarding-0.14.1 → codeboarding-0.14.2}/health/runner.py +0 -0
  89. {codeboarding-0.14.1 → codeboarding-0.14.2}/install.py +0 -0
  90. {codeboarding-0.14.1 → codeboarding-0.14.2}/logging_config.py +0 -0
  91. {codeboarding-0.14.1 → codeboarding-0.14.2}/main.py +0 -0
  92. {codeboarding-0.14.1 → codeboarding-0.14.2}/monitoring/__init__.py +0 -0
  93. {codeboarding-0.14.1 → codeboarding-0.14.2}/monitoring/callbacks.py +0 -0
  94. {codeboarding-0.14.1 → codeboarding-0.14.2}/monitoring/context.py +0 -0
  95. {codeboarding-0.14.1 → codeboarding-0.14.2}/monitoring/mixin.py +0 -0
  96. {codeboarding-0.14.1 → codeboarding-0.14.2}/monitoring/paths.py +0 -0
  97. {codeboarding-0.14.1 → codeboarding-0.14.2}/monitoring/stats.py +0 -0
  98. {codeboarding-0.14.1 → codeboarding-0.14.2}/monitoring/writers.py +0 -0
  99. {codeboarding-0.14.1 → codeboarding-0.14.2}/output_generators/__init__.py +0 -0
  100. {codeboarding-0.14.1 → codeboarding-0.14.2}/output_generators/html.py +0 -0
  101. {codeboarding-0.14.1 → codeboarding-0.14.2}/output_generators/html_template.py +0 -0
  102. {codeboarding-0.14.1 → codeboarding-0.14.2}/output_generators/markdown.py +0 -0
  103. {codeboarding-0.14.1 → codeboarding-0.14.2}/output_generators/mdx.py +0 -0
  104. {codeboarding-0.14.1 → codeboarding-0.14.2}/output_generators/rendering.py +0 -0
  105. {codeboarding-0.14.1 → codeboarding-0.14.2}/output_generators/sphinx.py +0 -0
  106. {codeboarding-0.14.1 → codeboarding-0.14.2}/repo_utils/change_detector.py +0 -0
  107. {codeboarding-0.14.1 → codeboarding-0.14.2}/repo_utils/errors.py +0 -0
  108. {codeboarding-0.14.1 → codeboarding-0.14.2}/repo_utils/fingerprint_diff.py +0 -0
  109. {codeboarding-0.14.1 → codeboarding-0.14.2}/repo_utils/git_ops.py +0 -0
  110. {codeboarding-0.14.1 → codeboarding-0.14.2}/repo_utils/ignore.py +0 -0
  111. {codeboarding-0.14.1 → codeboarding-0.14.2}/repo_utils/path_utils.py +0 -0
  112. {codeboarding-0.14.1 → codeboarding-0.14.2}/setup.cfg +0 -0
  113. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/__init__.py +0 -0
  114. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/analysis_cache.py +0 -0
  115. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/analysis_result.py +0 -0
  116. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/cfg/__init__.py +0 -0
  117. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/cfg/call_graph.py +0 -0
  118. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/cfg/edge.py +0 -0
  119. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/cfg/location_key.py +0 -0
  120. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/cluster_relations.py +0 -0
  121. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/__init__.py +0 -0
  122. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/exceptions.py +0 -0
  123. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/models.py +0 -0
  124. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/names/__init__.py +0 -0
  125. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/names/draft.py +0 -0
  126. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/names/frontier.py +0 -0
  127. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/names/inventory.py +0 -0
  128. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/names/replay.py +0 -0
  129. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/names/spec.py +0 -0
  130. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/names/tokens.py +0 -0
  131. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/clustering/service.py +0 -0
  132. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/config.py +0 -0
  133. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/csharp_config_scanner.py +0 -0
  134. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/dotnet_sdk.py +0 -0
  135. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/__init__.py +0 -0
  136. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/adapters/__init__.py +0 -0
  137. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/adapters/csharp_adapter.py +0 -0
  138. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/adapters/go_adapter.py +0 -0
  139. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/adapters/java_adapter.py +0 -0
  140. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/adapters/php_adapter.py +0 -0
  141. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/adapters/python_adapter.py +0 -0
  142. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/adapters/rust_adapter.py +0 -0
  143. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/adapters/typescript_adapter.py +0 -0
  144. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/call_graph_builder.py +0 -0
  145. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/edge_build_context.py +0 -0
  146. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/edge_builder.py +0 -0
  147. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/hierarchy_builder.py +0 -0
  148. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/language_adapter.py +0 -0
  149. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/lsp_client.py +0 -0
  150. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/lsp_constants.py +0 -0
  151. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/lsp_recycler.py +0 -0
  152. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/models.py +0 -0
  153. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/process_memory.py +0 -0
  154. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/progress.py +0 -0
  155. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/protocols.py +0 -0
  156. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/result_converter.py +0 -0
  157. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/source_inspector.py +0 -0
  158. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/symbol_table.py +0 -0
  159. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/engine/utils.py +0 -0
  160. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/incremental_orchestrator.py +0 -0
  161. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/internal_references.py +0 -0
  162. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/java_config_scanner.py +0 -0
  163. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/java_utils.py +0 -0
  164. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/language_results.py +0 -0
  165. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/lsp_client/__init__.py +0 -0
  166. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/lsp_client/diagnostics.py +0 -0
  167. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/node.py +0 -0
  168. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/programming_language.py +0 -0
  169. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/reference_resolver.py +0 -0
  170. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/scanner.py +0 -0
  171. {codeboarding-0.14.1 → codeboarding-0.14.2}/static_analyzer/typescript_config_scanner.py +0 -0
  172. {codeboarding-0.14.1 → codeboarding-0.14.2}/telemetry/device_id.py +0 -0
  173. {codeboarding-0.14.1 → codeboarding-0.14.2}/telemetry/events.py +0 -0
  174. {codeboarding-0.14.1 → codeboarding-0.14.2}/telemetry/schemas.py +0 -0
  175. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_cli_parser.py +0 -0
  176. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_github_action.py +0 -0
  177. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_install.py +0 -0
  178. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_logging_config.py +0 -0
  179. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_main.py +0 -0
  180. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_persisted_analysis_artifacts.py +0 -0
  181. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_pyproject_packages.py +0 -0
  182. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_registry_coverage.py +0 -0
  183. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_telemetry_events.py +0 -0
  184. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_tool_registry.py +0 -0
  185. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_user_config.py +0 -0
  186. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_view_instructions.py +0 -0
  187. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_vscode_constants.py +0 -0
  188. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_windows_compatibility.py +0 -0
  189. {codeboarding-0.14.1 → codeboarding-0.14.2}/tests/test_windows_encoding.py +0 -0
  190. {codeboarding-0.14.1 → codeboarding-0.14.2}/tool_registry/__init__.py +0 -0
  191. {codeboarding-0.14.1 → codeboarding-0.14.2}/tool_registry/installers.py +0 -0
  192. {codeboarding-0.14.1 → codeboarding-0.14.2}/tool_registry/manifest.py +0 -0
  193. {codeboarding-0.14.1 → codeboarding-0.14.2}/tool_registry/paths.py +0 -0
  194. {codeboarding-0.14.1 → codeboarding-0.14.2}/tool_registry/registry.py +0 -0
  195. {codeboarding-0.14.1 → codeboarding-0.14.2}/user_config.py +0 -0
  196. {codeboarding-0.14.1 → codeboarding-0.14.2}/utils.py +0 -0
  197. {codeboarding-0.14.1 → codeboarding-0.14.2}/vscode_constants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.14.1
3
+ Version: 0.14.2
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -212,10 +212,11 @@ for the actual depth. Existing baseline loading behavior is unchanged: prefer
212
212
 
213
213
  ## Telemetry
214
214
 
215
- CodeBoarding collects anonymous, aggregate usage telemetry (which command ran,
216
- success/failure, duration, and token cost) to help us improve the tool. It is on
217
- by default and never collects source code, file names, repository names, paths,
218
- prompts, model outputs, API keys, or any personal information. Opt out anytime:
215
+ CodeBoarding collects usage telemetry (which command ran, success/failure,
216
+ duration, token cost, repository size and languages, and the account the
217
+ repository belongs to) to help us improve the tool. It is on by default and
218
+ never collects source code, file names, repository names, paths, prompts, model
219
+ outputs, or API keys. Opt out anytime:
219
220
 
220
221
  ```bash
221
222
  export CODEBOARDING_TELEMETRY=false # or: export DO_NOT_TRACK=1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.14.1
3
+ Version: 0.14.2
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -4,6 +4,7 @@ README.md
4
4
  clustering_ids.py
5
5
  constants.py
6
6
  github_action.py
7
+ health_main.py
7
8
  install.py
8
9
  logging_config.py
9
10
  main.py
@@ -0,0 +1,152 @@
1
+ """Standalone entry point for running health checks on a repository.
2
+
3
+ Runs static analysis and health checks only — no LLM agents or diagram generation.
4
+ Useful for testing health checks in isolation and for CI/CD health gates.
5
+
6
+ Usage:
7
+ # Local repository (output written to <repo>/.codeboarding/health/)
8
+ python health_main.py --local /path/to/repo
9
+
10
+ # Remote repository (cloned to cwd/repos/, output to cwd/<repo_name>/.codeboarding/health/)
11
+ python health_main.py https://github.com/user/repo
12
+ """
13
+
14
+ import argparse
15
+ import logging
16
+ from pathlib import Path
17
+
18
+ from health.runner import run_health_checks
19
+ from health.config import initialize_health_dir, load_health_config
20
+ from logging_config import setup_logging
21
+ from repo_utils import clone_repository, get_repo_name
22
+ from static_analyzer import get_static_analysis
23
+ from utils import CODEBOARDING_DIR_NAME, get_artifact_dir
24
+ from vscode_constants import update_config
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ def run_health_check_local(
30
+ repo_path: Path,
31
+ project_name: str | None = None,
32
+ ) -> None:
33
+ """Run health checks on a local repository.
34
+
35
+ Args:
36
+ repo_path: Path to a local repository
37
+ project_name: Optional project name (default: repo directory name)
38
+ """
39
+ resolved_repo_path = repo_path.resolve()
40
+ if not resolved_repo_path.is_dir():
41
+ raise ValueError(f"Repository path does not exist: {resolved_repo_path}")
42
+
43
+ resolved_project_name = project_name or resolved_repo_path.name
44
+ output_dir = resolved_repo_path / CODEBOARDING_DIR_NAME
45
+
46
+ output_dir.mkdir(parents=True, exist_ok=True)
47
+ setup_logging(log_dir=output_dir)
48
+
49
+ _run_health_checks(resolved_repo_path, resolved_project_name, output_dir)
50
+
51
+
52
+ def run_health_check_remote(
53
+ repo_url: str,
54
+ project_name: str | None = None,
55
+ ) -> None:
56
+ """Run health checks on a remote repository.
57
+
58
+ Args:
59
+ repo_url: URL of a remote Git repository
60
+ project_name: Optional project name (default: extracted from URL)
61
+ """
62
+ repo_root = Path("repos")
63
+ repo_name = clone_repository(repo_url, repo_root)
64
+ resolved_repo_path = repo_root / repo_name
65
+ resolved_project_name = project_name or get_repo_name(repo_url)
66
+ output_dir = Path.cwd() / repo_name / CODEBOARDING_DIR_NAME
67
+
68
+ output_dir.mkdir(parents=True, exist_ok=True)
69
+ setup_logging(log_dir=output_dir)
70
+
71
+ _run_health_checks(resolved_repo_path, resolved_project_name, output_dir)
72
+
73
+
74
+ def _run_health_checks(repo_path: Path, project_name: str, output_dir: Path) -> None:
75
+ """Core health check logic shared by local and remote paths."""
76
+ logger.info(f"Running health checks on '{project_name}' at {repo_path}")
77
+
78
+ static_analysis = get_static_analysis(repo_path, cache_dir=get_artifact_dir(repo_path))
79
+
80
+ # Load health check configuration and initialize health config dir
81
+ health_config_dir = output_dir / "health"
82
+ initialize_health_dir(health_config_dir)
83
+ health_config = load_health_config(health_config_dir)
84
+
85
+ report = run_health_checks(static_analysis, project_name, config=health_config, repo_path=repo_path)
86
+
87
+ if report is None:
88
+ logger.warning("Health checks skipped: no languages found in static analysis results")
89
+ return
90
+
91
+ report_path = health_config_dir / "health_report.json"
92
+ report_path.write_text(report.model_dump_json(indent=2, exclude_none=True))
93
+
94
+ logger.info(f"Health report written to {report_path} (overall score: {report.overall_score:.3f})")
95
+
96
+
97
+ def main():
98
+ """Main entry point that parses CLI arguments and routes to subcommands."""
99
+ parser = argparse.ArgumentParser(
100
+ description="Run static analysis health checks on a repository (local or remote)",
101
+ formatter_class=argparse.RawDescriptionHelpFormatter,
102
+ epilog="""
103
+ Examples:
104
+ # Local repository (output written to <repo>/.codeboarding/health/)
105
+ python health_main.py --local /path/to/repo
106
+
107
+ # Remote repository (cloned to cwd/repos/, output to cwd/<repo_name>/.codeboarding/health/)
108
+ python health_main.py https://github.com/user/repo
109
+
110
+ """,
111
+ )
112
+ parser.add_argument(
113
+ "repositories",
114
+ nargs="*",
115
+ help="One or more remote Git repository URLs to run health checks on",
116
+ )
117
+ parser.add_argument("--local", type=Path, help="Path to a local repository")
118
+ parser.add_argument(
119
+ "--project-name",
120
+ type=str,
121
+ default=None,
122
+ help="Name of the project (default: extracted from repo path or URL)",
123
+ )
124
+ parser.add_argument(
125
+ "--binary-location", type=Path, help="Path to the binary directory for language servers and tools"
126
+ )
127
+
128
+ args = parser.parse_args()
129
+
130
+ # Validate: must provide either remote repos or --local, not both
131
+ has_remote = bool(args.repositories)
132
+ has_local = args.local is not None
133
+
134
+ if has_remote == has_local:
135
+ parser.error("Provide either one or more remote repositories or --local, but not both.")
136
+
137
+ # Resolve binary paths for tools (tokei, LSP servers, etc.)
138
+ if args.binary_location:
139
+ update_config(args.binary_location)
140
+
141
+ try:
142
+ if has_local:
143
+ run_health_check_local(args.local, args.project_name)
144
+ else:
145
+ for repo_url in args.repositories:
146
+ run_health_check_remote(repo_url, args.project_name)
147
+ except ValueError as e:
148
+ parser.error(str(e))
149
+
150
+
151
+ if __name__ == "__main__":
152
+ main()
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codeboarding"
7
- version = "0.14.1"
7
+ version = "0.14.2"
8
8
  description = "Interactive Diagrams for Code"
9
9
  readme = "PYPI.md"
10
10
  license = "MIT"
@@ -66,6 +66,8 @@ def sanitize_repo_url(repo_url: str) -> str:
66
66
  Normalizes Git URLs to ensure proper format for cloning.
67
67
  Preserves HTTPS URLs for CI compatibility while supporting SSH URLs.
68
68
  """
69
+ repo_url = repo_url.rstrip("/") # else the suffix below yields ``.../repo/.git``
70
+
69
71
  if repo_url.startswith("git@") or repo_url.startswith("ssh://"):
70
72
  return repo_url # already in SSH format
71
73
  elif repo_url.startswith("https://") or repo_url.startswith("http://"):
@@ -94,8 +96,7 @@ def remote_repo_exists(repo_url: str) -> bool:
94
96
 
95
97
 
96
98
  def get_repo_name(repo_url: str):
97
- repo_url = sanitize_repo_url(repo_url)
98
- base = repo_url.rstrip("/").split("/")[-1]
99
+ base = sanitize_repo_url(repo_url).split("/")[-1]
99
100
  repo_name, _ = os.path.splitext(base)
100
101
  return repo_name
101
102
 
@@ -0,0 +1,11 @@
1
+ """Opt-out usage telemetry.
2
+
3
+ One singleton (``telemetry``) sends events to PostHog. Disabled with
4
+ ``CODEBOARDING_TELEMETRY=false`` (or ``DO_NOT_TRACK=1``). No repo content, paths,
5
+ or API keys are ever sent; the repository's owning account is, so events are
6
+ pseudonymous rather than anonymous. See ``service.py`` for the identity rules.
7
+ """
8
+
9
+ from telemetry.service import ProductTelemetry, telemetry
10
+
11
+ __all__ = ["ProductTelemetry", "telemetry"]
@@ -30,16 +30,40 @@ def _telemetry_disabled() -> bool:
30
30
  return os.getenv("CODEBOARDING_TELEMETRY", "true").strip().lower() == "false"
31
31
 
32
32
 
33
+ def _org() -> str:
34
+ """The account a run's repository belongs to, or '' when none is named.
35
+
36
+ Why: ``CODEBOARDING_ORG`` when a caller knows the owner, else the owner half
37
+ of ``GITHUB_REPOSITORY``; the repository's own name is never read.
38
+ """
39
+ owner = os.getenv("CODEBOARDING_ORG", "").strip() or os.getenv("GITHUB_REPOSITORY", "").strip().partition("/")[0]
40
+ return owner.lower()
41
+
42
+
43
+ #: Properties describing the process rather than the event. A payload that could
44
+ #: set one could hide automated traffic inside usage, or claim an owner it does
45
+ #: not have — so they are dropped from caller properties before the origin is
46
+ #: applied. Listed rather than derived from ``_origin()``: ``org`` is absent
47
+ #: there when no owner is known, which is exactly the case a caller could fill.
48
+ ORIGIN_KEYS = frozenset({"source", "internal", "org"})
49
+
50
+
33
51
  def _origin() -> dict[str, object]:
34
- """Who invoked this run, and whether that is a person using the product.
52
+ """Who invoked this run, whether that is product usage, and which deployment.
35
53
 
36
- ``source`` is the existing discriminator: "vscode" when invoked by the
37
- extension, "oss" for the OSS CLI, "github_action" in CI, "tests" for the
38
- engine's own suite, "evals" for the benchmark harness, "core" for any other
39
- embedding. ``internal`` is derived from it so the two can never disagree.
54
+ Why: ``internal`` is derived from ``source`` so the two cannot disagree, and
55
+ ``org`` is omitted rather than blank so an absent key reads as unknown.
40
56
  """
41
57
  source = os.getenv("CODEBOARDING_SOURCE", "core")
42
- return {"source": source, "internal": source in INTERNAL_SOURCES}
58
+ origin: dict[str, object] = {"source": source, "internal": source in INTERNAL_SOURCES}
59
+ if org := _org():
60
+ origin["org"] = org
61
+ return origin
62
+
63
+
64
+ def _with_origin(properties: dict | None) -> dict:
65
+ """Caller properties, unable to claim any of :data:`ORIGIN_KEYS`."""
66
+ return {k: v for k, v in (properties or {}).items() if k not in ORIGIN_KEYS} | _origin()
43
67
 
44
68
 
45
69
  class ProductTelemetry:
@@ -88,12 +112,7 @@ class ProductTelemetry:
88
112
  self._client.capture(
89
113
  distinct_id=self.user_id,
90
114
  event=event,
91
- # Origin LAST, so it wins. Who invoked the run is a property of
92
- # the process, not a field an event model gets to claim — and
93
- # `internal` is the one property a product metric filters on, so
94
- # a payload that could overwrite it is a payload that could hide
95
- # automated traffic inside usage.
96
- properties={**(properties or {}), **_origin()},
115
+ properties=_with_origin(properties),
97
116
  )
98
117
  except Exception as e:
99
118
  logger.debug("Telemetry capture failed: %s", e)
@@ -106,12 +125,7 @@ class ProductTelemetry:
106
125
  self._client.capture_exception(
107
126
  exc,
108
127
  distinct_id=self.user_id,
109
- # Origin LAST, so it wins. Who invoked the run is a property of
110
- # the process, not a field an event model gets to claim — and
111
- # `internal` is the one property a product metric filters on, so
112
- # a payload that could overwrite it is a payload that could hide
113
- # automated traffic inside usage.
114
- properties={**(properties or {}), **_origin()},
128
+ properties=_with_origin(properties),
115
129
  )
116
130
  except Exception as e:
117
131
  logger.debug("Telemetry capture_exception failed: %s", e)
@@ -0,0 +1,205 @@
1
+ """What every event carries about who produced it.
2
+
3
+ ``source`` has always said which invoker a run came from. What it could not say
4
+ is whether that invoker is a *person using the product*: a dashboard measuring
5
+ usage had to spell out ``source not in ('tests', 'evals')`` and was silently
6
+ wrong from the moment another internal source was added. ``internal`` is derived
7
+ from the source in one place so that filter is one condition and stays correct.
8
+
9
+ These exercise ``ProductTelemetry`` itself rather than ``telemetry.events``,
10
+ which stubs ``capture`` out — the stamping happens below that stub, so it is
11
+ invisible to every test in ``test_telemetry_events.py``.
12
+ """
13
+
14
+ from types import SimpleNamespace
15
+
16
+ import pytest
17
+
18
+ from telemetry.service import INTERNAL_SOURCES, ProductTelemetry
19
+
20
+
21
+ @pytest.fixture
22
+ def client(monkeypatch):
23
+ """A recorder in place of the PostHog SDK, on the live singleton."""
24
+ seen = SimpleNamespace(captures=[], exceptions=[])
25
+ service = ProductTelemetry()
26
+ monkeypatch.setattr(
27
+ service,
28
+ "_client",
29
+ SimpleNamespace(
30
+ capture=lambda **kw: seen.captures.append(kw),
31
+ capture_exception=lambda exc, **kw: seen.exceptions.append((exc, kw)),
32
+ flush=lambda: None,
33
+ ),
34
+ )
35
+ seen.service = service
36
+ return seen
37
+
38
+
39
+ def test_a_product_run_is_not_marked_internal(client, monkeypatch):
40
+ monkeypatch.setenv("CODEBOARDING_SOURCE", "oss")
41
+
42
+ client.service.capture("analysis_started", {"command": "run_full"})
43
+
44
+ props = client.captures[0]["properties"]
45
+ assert props["source"] == "oss"
46
+ assert props["internal"] is False
47
+
48
+
49
+ @pytest.mark.parametrize("source", sorted(INTERNAL_SOURCES))
50
+ def test_every_internal_source_is_marked_internal(client, monkeypatch, source):
51
+ """The benchmark harness sets ``evals``; the engine's own suite sets ``tests``.
52
+ Neither is a person using the product, and neither may be counted as one."""
53
+ monkeypatch.setenv("CODEBOARDING_SOURCE", source)
54
+
55
+ client.service.capture("analysis_completed", {"status": "success"})
56
+
57
+ props = client.captures[0]["properties"]
58
+ assert props["source"] == source
59
+ assert props["internal"] is True
60
+
61
+
62
+ def test_an_unknown_source_defaults_to_product_traffic(client, monkeypatch):
63
+ """A source nobody declared is an embedding of the library, which IS usage.
64
+ Defaulting the other way would quietly drop a real integration's runs."""
65
+ monkeypatch.setenv("CODEBOARDING_SOURCE", "some-new-wrapper")
66
+
67
+ client.service.capture("analysis_started", {})
68
+
69
+ assert client.captures[0]["properties"]["internal"] is False
70
+
71
+
72
+ def test_the_origin_travels_on_exceptions_too(client, monkeypatch):
73
+ """``$exception`` is an event like any other, and error-rate dashboards are
74
+ exactly where a run of the benchmark's deliberate failure cases would hurt."""
75
+ monkeypatch.setenv("CODEBOARDING_SOURCE", "evals")
76
+ exc = RuntimeError("boom")
77
+
78
+ client.service.capture_exception(exc, properties={"command": "run_incremental"})
79
+
80
+ _, kwargs = client.exceptions[0]
81
+ assert kwargs["properties"]["internal"] is True
82
+ assert kwargs["properties"]["command"] == "run_incremental"
83
+
84
+
85
+ def test_a_caller_property_never_overwrites_the_origin(client, monkeypatch):
86
+ """The origin merges LAST, so a caller cannot claim it.
87
+
88
+ It used to go first, which left an event model free to overwrite `source`
89
+ simply by having a field of that name. No schema does today — but `internal`
90
+ is the one property a product metric filters on, so a payload that could
91
+ overwrite it is a payload that could hide automated traffic inside usage, and
92
+ that is not a thing to leave resting on nobody having added the field yet."""
93
+ monkeypatch.setenv("CODEBOARDING_SOURCE", "evals")
94
+
95
+ client.service.capture("repo_scanned", {"source": "oss", "internal": False})
96
+
97
+ props = client.captures[0]["properties"]
98
+ assert props["source"] == "evals"
99
+ assert props["internal"] is True
100
+
101
+
102
+ def test_ci_runs_report_the_repository_owner(client, monkeypatch):
103
+ monkeypatch.delenv("CODEBOARDING_ORG", raising=False)
104
+ monkeypatch.setenv("GITHUB_REPOSITORY", "Acme-Corp/widgets")
105
+
106
+ client.service.capture("analysis_started", {})
107
+
108
+ assert client.captures[0]["properties"]["org"] == "acme-corp"
109
+
110
+
111
+ def test_the_same_owner_always_reads_the_same_way(client, monkeypatch):
112
+ """The owner is the join key across surfaces and across runs, so casing and
113
+ the two ways of naming one have to agree on a single value."""
114
+ monkeypatch.delenv("CODEBOARDING_ORG", raising=False)
115
+ monkeypatch.setenv("GITHUB_REPOSITORY", "Acme/widgets")
116
+ client.service.capture("analysis_started", {})
117
+
118
+ monkeypatch.delenv("GITHUB_REPOSITORY", raising=False)
119
+ monkeypatch.setenv("CODEBOARDING_ORG", "acme")
120
+ client.service.capture("analysis_started", {})
121
+
122
+ assert client.captures[0]["properties"]["org"] == client.captures[1]["properties"]["org"]
123
+
124
+
125
+ def test_only_the_owner_is_taken_from_the_repository_slug(client, monkeypatch):
126
+ """Why: the name is the half that says something about the code."""
127
+ monkeypatch.delenv("CODEBOARDING_ORG", raising=False)
128
+ monkeypatch.setenv("GITHUB_REPOSITORY", "acme/secret-prototype")
129
+
130
+ client.service.capture("analysis_started", {})
131
+
132
+ props = client.captures[0]["properties"]
133
+ assert props["org"] == "acme"
134
+ assert "secret-prototype" not in str(props)
135
+
136
+
137
+ def test_an_embedding_can_name_the_owner_itself(client, monkeypatch):
138
+ """The VSCode extension resolves the owner from the workspace's git remote
139
+ and sets ``CODEBOARDING_ORG``, which has to win: a run driven by the editor
140
+ inside a CI-like environment belongs to the workspace, not the runner."""
141
+ monkeypatch.setenv("CODEBOARDING_ORG", "Acme")
142
+ monkeypatch.setenv("GITHUB_REPOSITORY", "someone-else/runner")
143
+
144
+ client.service.capture("analysis_started", {})
145
+
146
+ assert client.captures[0]["properties"]["org"] == "acme"
147
+
148
+
149
+ def test_a_local_run_reports_no_owner_at_all(client, monkeypatch):
150
+ """Nothing infers an owner. With neither variable set the key is absent
151
+ rather than empty, so a query can tell "no owner" from "owner is blank"."""
152
+ monkeypatch.delenv("CODEBOARDING_ORG", raising=False)
153
+ monkeypatch.delenv("GITHUB_REPOSITORY", raising=False)
154
+
155
+ client.service.capture("analysis_started", {})
156
+
157
+ assert "org" not in client.captures[0]["properties"]
158
+
159
+
160
+ def test_a_caller_property_never_overwrites_the_owner(client, monkeypatch):
161
+ """``org`` merges with the rest of the origin, after the payload, for the
162
+ same reason ``internal`` does: it is a property of the process."""
163
+ monkeypatch.delenv("CODEBOARDING_ORG", raising=False)
164
+ monkeypatch.setenv("GITHUB_REPOSITORY", "acme/widgets")
165
+
166
+ client.service.capture("repo_scanned", {"org": "somebody-else"})
167
+
168
+ assert client.captures[0]["properties"]["org"] == "acme"
169
+
170
+
171
+ def test_a_caller_cannot_supply_an_owner_when_there_is_none(client, monkeypatch):
172
+ """Merging the origin last stops a payload *overwriting* an owner, but not
173
+ inventing one: with no owner resolved the key is absent, so a caller's value
174
+ would have survived into it and attributed the run to an account it has
175
+ nothing to do with. The reserved keys come off the payload first."""
176
+ monkeypatch.delenv("CODEBOARDING_ORG", raising=False)
177
+ monkeypatch.delenv("GITHUB_REPOSITORY", raising=False)
178
+
179
+ client.service.capture("repo_scanned", {"org": "somebody-else", "total_loc": 10})
180
+
181
+ props = client.captures[0]["properties"]
182
+ assert "org" not in props
183
+ assert props["total_loc"] == 10
184
+
185
+
186
+ def test_a_caller_cannot_supply_an_owner_on_exceptions_either(client, monkeypatch):
187
+ monkeypatch.delenv("CODEBOARDING_ORG", raising=False)
188
+ monkeypatch.delenv("GITHUB_REPOSITORY", raising=False)
189
+
190
+ client.service.capture_exception(RuntimeError("boom"), properties={"org": "somebody-else"})
191
+
192
+ _, kwargs = client.exceptions[0]
193
+ assert "org" not in kwargs["properties"]
194
+
195
+
196
+ def test_the_owner_travels_on_exceptions_too(client, monkeypatch):
197
+ """Why: exceptions take a different code path, and error-rate dashboards are
198
+ where knowing whether one deployment or all of them is hitting a bug pays."""
199
+ monkeypatch.delenv("CODEBOARDING_ORG", raising=False)
200
+ monkeypatch.setenv("GITHUB_REPOSITORY", "acme/widgets")
201
+
202
+ client.service.capture_exception(RuntimeError("boom"), properties={"command": "run_full"})
203
+
204
+ _, kwargs = client.exceptions[0]
205
+ assert kwargs["properties"]["org"] == "acme"
@@ -1,10 +0,0 @@
1
- """Anonymous, opt-out usage telemetry.
2
-
3
- One singleton (``telemetry``) sends anonymous events to PostHog. Disabled with
4
- ``CODEBOARDING_TELEMETRY=false`` (or ``DO_NOT_TRACK=1``). No repo content, paths,
5
- or API keys are ever sent. See ``service.py`` for the identity rules.
6
- """
7
-
8
- from telemetry.service import ProductTelemetry, telemetry
9
-
10
- __all__ = ["ProductTelemetry", "telemetry"]
@@ -1,99 +0,0 @@
1
- """What every event carries about who produced it.
2
-
3
- ``source`` has always said which invoker a run came from. What it could not say
4
- is whether that invoker is a *person using the product*: a dashboard measuring
5
- usage had to spell out ``source not in ('tests', 'evals')`` and was silently
6
- wrong from the moment another internal source was added. ``internal`` is derived
7
- from the source in one place so that filter is one condition and stays correct.
8
-
9
- These exercise ``ProductTelemetry`` itself rather than ``telemetry.events``,
10
- which stubs ``capture`` out — the stamping happens below that stub, so it is
11
- invisible to every test in ``test_telemetry_events.py``.
12
- """
13
-
14
- from types import SimpleNamespace
15
-
16
- import pytest
17
-
18
- from telemetry.service import INTERNAL_SOURCES, ProductTelemetry
19
-
20
-
21
- @pytest.fixture
22
- def client(monkeypatch):
23
- """A recorder in place of the PostHog SDK, on the live singleton."""
24
- seen = SimpleNamespace(captures=[], exceptions=[])
25
- service = ProductTelemetry()
26
- monkeypatch.setattr(
27
- service,
28
- "_client",
29
- SimpleNamespace(
30
- capture=lambda **kw: seen.captures.append(kw),
31
- capture_exception=lambda exc, **kw: seen.exceptions.append((exc, kw)),
32
- flush=lambda: None,
33
- ),
34
- )
35
- seen.service = service
36
- return seen
37
-
38
-
39
- def test_a_product_run_is_not_marked_internal(client, monkeypatch):
40
- monkeypatch.setenv("CODEBOARDING_SOURCE", "oss")
41
-
42
- client.service.capture("analysis_started", {"command": "run_full"})
43
-
44
- props = client.captures[0]["properties"]
45
- assert props["source"] == "oss"
46
- assert props["internal"] is False
47
-
48
-
49
- @pytest.mark.parametrize("source", sorted(INTERNAL_SOURCES))
50
- def test_every_internal_source_is_marked_internal(client, monkeypatch, source):
51
- """The benchmark harness sets ``evals``; the engine's own suite sets ``tests``.
52
- Neither is a person using the product, and neither may be counted as one."""
53
- monkeypatch.setenv("CODEBOARDING_SOURCE", source)
54
-
55
- client.service.capture("analysis_completed", {"status": "success"})
56
-
57
- props = client.captures[0]["properties"]
58
- assert props["source"] == source
59
- assert props["internal"] is True
60
-
61
-
62
- def test_an_unknown_source_defaults_to_product_traffic(client, monkeypatch):
63
- """A source nobody declared is an embedding of the library, which IS usage.
64
- Defaulting the other way would quietly drop a real integration's runs."""
65
- monkeypatch.setenv("CODEBOARDING_SOURCE", "some-new-wrapper")
66
-
67
- client.service.capture("analysis_started", {})
68
-
69
- assert client.captures[0]["properties"]["internal"] is False
70
-
71
-
72
- def test_the_origin_travels_on_exceptions_too(client, monkeypatch):
73
- """``$exception`` is an event like any other, and error-rate dashboards are
74
- exactly where a run of the benchmark's deliberate failure cases would hurt."""
75
- monkeypatch.setenv("CODEBOARDING_SOURCE", "evals")
76
- exc = RuntimeError("boom")
77
-
78
- client.service.capture_exception(exc, properties={"command": "run_incremental"})
79
-
80
- _, kwargs = client.exceptions[0]
81
- assert kwargs["properties"]["internal"] is True
82
- assert kwargs["properties"]["command"] == "run_incremental"
83
-
84
-
85
- def test_a_caller_property_never_overwrites_the_origin(client, monkeypatch):
86
- """The origin merges LAST, so a caller cannot claim it.
87
-
88
- It used to go first, which left an event model free to overwrite `source`
89
- simply by having a field of that name. No schema does today — but `internal`
90
- is the one property a product metric filters on, so a payload that could
91
- overwrite it is a payload that could hide automated traffic inside usage, and
92
- that is not a thing to leave resting on nobody having added the field yet."""
93
- monkeypatch.setenv("CODEBOARDING_SOURCE", "evals")
94
-
95
- client.service.capture("repo_scanned", {"source": "oss", "internal": False})
96
-
97
- props = client.captures[0]["properties"]
98
- assert props["source"] == "evals"
99
- assert props["internal"] is True
File without changes
File without changes