codeboarding 0.10.0__tar.gz → 0.10.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 (160) hide show
  1. {codeboarding-0.10.0/codeboarding.egg-info → codeboarding-0.10.2}/PKG-INFO +1 -1
  2. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/abstraction_agent.py +8 -0
  3. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/agent_responses.py +1 -1
  4. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/cluster_methods_mixin.py +38 -6
  5. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/details_agent.py +8 -0
  6. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/meta_agent.py +0 -1
  7. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/prompts/claude_prompts.py +2 -0
  8. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/prompts/deepseek_prompts.py +2 -0
  9. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/prompts/gemini_flash_prompts.py +6 -0
  10. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/prompts/glm_prompts.py +2 -0
  11. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/prompts/gpt_prompts.py +2 -0
  12. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/prompts/kimi_prompts.py +2 -0
  13. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/validation.py +36 -60
  14. {codeboarding-0.10.0 → codeboarding-0.10.2/codeboarding.egg-info}/PKG-INFO +1 -1
  15. {codeboarding-0.10.0 → codeboarding-0.10.2}/diagram_analysis/analysis_json.py +5 -1
  16. {codeboarding-0.10.0 → codeboarding-0.10.2}/diagram_analysis/incremental_updater.py +27 -2
  17. {codeboarding-0.10.0 → codeboarding-0.10.2}/install.py +64 -9
  18. {codeboarding-0.10.0 → codeboarding-0.10.2}/main.py +2 -2
  19. {codeboarding-0.10.0 → codeboarding-0.10.2}/pyproject.toml +1 -1
  20. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/__init__.py +44 -7
  21. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/analysis_result.py +1 -1
  22. codeboarding-0.10.2/static_analyzer/cluster_helpers.py +487 -0
  23. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/lsp_client.py +40 -13
  24. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/graph.py +3 -7
  25. {codeboarding-0.10.0 → codeboarding-0.10.2}/tool_registry.py +83 -25
  26. codeboarding-0.10.0/static_analyzer/cluster_helpers.py +0 -79
  27. {codeboarding-0.10.0 → codeboarding-0.10.2}/LICENSE +0 -0
  28. {codeboarding-0.10.0 → codeboarding-0.10.2}/PYPI.md +0 -0
  29. {codeboarding-0.10.0 → codeboarding-0.10.2}/README.md +0 -0
  30. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/__init__.py +0 -0
  31. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/agent.py +0 -0
  32. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/change_status.py +0 -0
  33. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/constants.py +0 -0
  34. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/dependency_discovery.py +0 -0
  35. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/llm_config.py +0 -0
  36. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/planner_agent.py +0 -0
  37. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/prompts/__init__.py +0 -0
  38. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/prompts/abstract_prompt_factory.py +0 -0
  39. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/prompts/prompt_factory.py +0 -0
  40. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/__init__.py +0 -0
  41. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/base.py +0 -0
  42. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/get_external_deps.py +0 -0
  43. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/get_method_invocations.py +0 -0
  44. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/read_cfg.py +0 -0
  45. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/read_docs.py +0 -0
  46. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/read_file.py +0 -0
  47. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/read_file_structure.py +0 -0
  48. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/read_git_diff.py +0 -0
  49. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/read_packages.py +0 -0
  50. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/read_source.py +0 -0
  51. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/read_structure.py +0 -0
  52. {codeboarding-0.10.0 → codeboarding-0.10.2}/agents/tools/toolkit.py +0 -0
  53. {codeboarding-0.10.0 → codeboarding-0.10.2}/caching/__init__.py +0 -0
  54. {codeboarding-0.10.0 → codeboarding-0.10.2}/caching/cache.py +0 -0
  55. {codeboarding-0.10.0 → codeboarding-0.10.2}/caching/details_cache.py +0 -0
  56. {codeboarding-0.10.0 → codeboarding-0.10.2}/caching/meta_cache.py +0 -0
  57. {codeboarding-0.10.0 → codeboarding-0.10.2}/codeboarding.egg-info/SOURCES.txt +0 -0
  58. {codeboarding-0.10.0 → codeboarding-0.10.2}/codeboarding.egg-info/dependency_links.txt +0 -0
  59. {codeboarding-0.10.0 → codeboarding-0.10.2}/codeboarding.egg-info/entry_points.txt +0 -0
  60. {codeboarding-0.10.0 → codeboarding-0.10.2}/codeboarding.egg-info/requires.txt +0 -0
  61. {codeboarding-0.10.0 → codeboarding-0.10.2}/codeboarding.egg-info/top_level.txt +0 -0
  62. {codeboarding-0.10.0 → codeboarding-0.10.2}/constants.py +0 -0
  63. {codeboarding-0.10.0 → codeboarding-0.10.2}/core/__init__.py +0 -0
  64. {codeboarding-0.10.0 → codeboarding-0.10.2}/core/plugin_loader.py +0 -0
  65. {codeboarding-0.10.0 → codeboarding-0.10.2}/core/protocols.py +0 -0
  66. {codeboarding-0.10.0 → codeboarding-0.10.2}/core/registry.py +0 -0
  67. {codeboarding-0.10.0 → codeboarding-0.10.2}/diagram_analysis/__init__.py +0 -0
  68. {codeboarding-0.10.0 → codeboarding-0.10.2}/diagram_analysis/diagram_generator.py +0 -0
  69. {codeboarding-0.10.0 → codeboarding-0.10.2}/diagram_analysis/file_coverage.py +0 -0
  70. {codeboarding-0.10.0 → codeboarding-0.10.2}/diagram_analysis/incremental_types.py +0 -0
  71. {codeboarding-0.10.0 → codeboarding-0.10.2}/diagram_analysis/io_utils.py +0 -0
  72. {codeboarding-0.10.0 → codeboarding-0.10.2}/diagram_analysis/manifest.py +0 -0
  73. {codeboarding-0.10.0 → codeboarding-0.10.2}/diagram_analysis/run_context.py +0 -0
  74. {codeboarding-0.10.0 → codeboarding-0.10.2}/diagram_analysis/version.py +0 -0
  75. {codeboarding-0.10.0 → codeboarding-0.10.2}/duckdb_crud.py +0 -0
  76. {codeboarding-0.10.0 → codeboarding-0.10.2}/github_action.py +0 -0
  77. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/__init__.py +0 -0
  78. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/checks/__init__.py +0 -0
  79. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/checks/circular_deps.py +0 -0
  80. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/checks/cohesion.py +0 -0
  81. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/checks/coupling.py +0 -0
  82. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/checks/function_size.py +0 -0
  83. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/checks/god_class.py +0 -0
  84. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/checks/inheritance.py +0 -0
  85. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/checks/instability.py +0 -0
  86. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/checks/unused_code_diagnostics.py +0 -0
  87. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/config.py +0 -0
  88. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/constants.py +0 -0
  89. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/models.py +0 -0
  90. {codeboarding-0.10.0 → codeboarding-0.10.2}/health/runner.py +0 -0
  91. {codeboarding-0.10.0 → codeboarding-0.10.2}/health_main.py +0 -0
  92. {codeboarding-0.10.0 → codeboarding-0.10.2}/logging_config.py +0 -0
  93. {codeboarding-0.10.0 → codeboarding-0.10.2}/monitoring/__init__.py +0 -0
  94. {codeboarding-0.10.0 → codeboarding-0.10.2}/monitoring/callbacks.py +0 -0
  95. {codeboarding-0.10.0 → codeboarding-0.10.2}/monitoring/context.py +0 -0
  96. {codeboarding-0.10.0 → codeboarding-0.10.2}/monitoring/mixin.py +0 -0
  97. {codeboarding-0.10.0 → codeboarding-0.10.2}/monitoring/paths.py +0 -0
  98. {codeboarding-0.10.0 → codeboarding-0.10.2}/monitoring/stats.py +0 -0
  99. {codeboarding-0.10.0 → codeboarding-0.10.2}/monitoring/writers.py +0 -0
  100. {codeboarding-0.10.0 → codeboarding-0.10.2}/output_generators/__init__.py +0 -0
  101. {codeboarding-0.10.0 → codeboarding-0.10.2}/output_generators/html.py +0 -0
  102. {codeboarding-0.10.0 → codeboarding-0.10.2}/output_generators/html_template.py +0 -0
  103. {codeboarding-0.10.0 → codeboarding-0.10.2}/output_generators/markdown.py +0 -0
  104. {codeboarding-0.10.0 → codeboarding-0.10.2}/output_generators/mdx.py +0 -0
  105. {codeboarding-0.10.0 → codeboarding-0.10.2}/output_generators/sphinx.py +0 -0
  106. {codeboarding-0.10.0 → codeboarding-0.10.2}/repo_utils/__init__.py +0 -0
  107. {codeboarding-0.10.0 → codeboarding-0.10.2}/repo_utils/change_detector.py +0 -0
  108. {codeboarding-0.10.0 → codeboarding-0.10.2}/repo_utils/errors.py +0 -0
  109. {codeboarding-0.10.0 → codeboarding-0.10.2}/repo_utils/git_diff.py +0 -0
  110. {codeboarding-0.10.0 → codeboarding-0.10.2}/repo_utils/ignore.py +0 -0
  111. {codeboarding-0.10.0 → codeboarding-0.10.2}/repo_utils/method_diff.py +0 -0
  112. {codeboarding-0.10.0 → codeboarding-0.10.2}/setup.cfg +0 -0
  113. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/analysis_cache.py +0 -0
  114. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/cluster_change_analyzer.py +0 -0
  115. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/cluster_relations.py +0 -0
  116. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/constants.py +0 -0
  117. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/__init__.py +0 -0
  118. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/adapters/__init__.py +0 -0
  119. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/adapters/go_adapter.py +0 -0
  120. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/adapters/java_adapter.py +0 -0
  121. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/adapters/php_adapter.py +0 -0
  122. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/adapters/python_adapter.py +0 -0
  123. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/adapters/typescript_adapter.py +0 -0
  124. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/call_graph_builder.py +0 -0
  125. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/edge_build_context.py +0 -0
  126. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/edge_builder.py +0 -0
  127. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/hierarchy_builder.py +0 -0
  128. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/language_adapter.py +0 -0
  129. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/lsp_constants.py +0 -0
  130. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/models.py +0 -0
  131. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/progress.py +0 -0
  132. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/protocols.py +0 -0
  133. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/result_converter.py +0 -0
  134. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/source_inspector.py +0 -0
  135. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/symbol_table.py +0 -0
  136. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/engine/utils.py +0 -0
  137. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/git_diff_analyzer.py +0 -0
  138. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/incremental_orchestrator.py +0 -0
  139. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/java_config_scanner.py +0 -0
  140. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/java_utils.py +0 -0
  141. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/lsp_client/__init__.py +0 -0
  142. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/lsp_client/diagnostics.py +0 -0
  143. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/node.py +0 -0
  144. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/programming_language.py +0 -0
  145. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/reference_resolve_mixin.py +0 -0
  146. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/scanner.py +0 -0
  147. {codeboarding-0.10.0 → codeboarding-0.10.2}/static_analyzer/typescript_config_scanner.py +0 -0
  148. {codeboarding-0.10.0 → codeboarding-0.10.2}/tests/test_github_action.py +0 -0
  149. {codeboarding-0.10.0 → codeboarding-0.10.2}/tests/test_install.py +0 -0
  150. {codeboarding-0.10.0 → codeboarding-0.10.2}/tests/test_logging_config.py +0 -0
  151. {codeboarding-0.10.0 → codeboarding-0.10.2}/tests/test_main.py +0 -0
  152. {codeboarding-0.10.0 → codeboarding-0.10.2}/tests/test_pyproject_packages.py +0 -0
  153. {codeboarding-0.10.0 → codeboarding-0.10.2}/tests/test_tool_registry.py +0 -0
  154. {codeboarding-0.10.0 → codeboarding-0.10.2}/tests/test_user_config.py +0 -0
  155. {codeboarding-0.10.0 → codeboarding-0.10.2}/tests/test_vscode_constants.py +0 -0
  156. {codeboarding-0.10.0 → codeboarding-0.10.2}/tests/test_windows_compatibility.py +0 -0
  157. {codeboarding-0.10.0 → codeboarding-0.10.2}/tests/test_windows_encoding.py +0 -0
  158. {codeboarding-0.10.0 → codeboarding-0.10.2}/user_config.py +0 -0
  159. {codeboarding-0.10.0 → codeboarding-0.10.2}/utils.py +0 -0
  160. {codeboarding-0.10.0 → codeboarding-0.10.2}/vscode_constants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.10.0
3
+ Version: 0.10.2
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -103,6 +103,8 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
103
103
 
104
104
  cluster_str = llm_cluster_analysis.llm_str() if llm_cluster_analysis else "No cluster analysis available."
105
105
 
106
+ group_names = [cc.name for cc in llm_cluster_analysis.cluster_components] if llm_cluster_analysis else []
107
+
106
108
  prompt = self.prompts["final_analysis"].format(
107
109
  project_name=self.project_name,
108
110
  cluster_analysis=cluster_str,
@@ -110,6 +112,12 @@ class AbstractionAgent(ClusterMethodsMixin, CodeBoardingAgent):
110
112
  project_type=project_type,
111
113
  )
112
114
 
115
+ if group_names:
116
+ prompt += (
117
+ f"\n\n## All Group Names ({len(group_names)} total)\n"
118
+ f"Every one of these names must appear in exactly one component's source_group_names: {group_names}\n"
119
+ )
120
+
113
121
  # Build validation context with CFG graphs for edge checking
114
122
  context = ValidationContext(
115
123
  cluster_results=cluster_results,
@@ -111,7 +111,7 @@ class ClustersComponent(LLMBaseModel):
111
111
  description="List of cluster IDs from the CFG analysis that are grouped together (e.g., [1, 3, 5])"
112
112
  )
113
113
  description: str = Field(
114
- description="Explanation of what this component does, its main flow, WHY these clusters are grouped together, and how it interacts with other cluster groups"
114
+ description="Explanation of what this component does, its main flow, WHY these clusters are grouped together, how it interacts with other cluster groups, and the most important classes/methods (by their exact qualified names from the clusters)"
115
115
  )
116
116
 
117
117
  def llm_str(self):
@@ -16,8 +16,11 @@ from agents.agent_responses import (
16
16
  from constants import MIN_CLUSTERS_THRESHOLD
17
17
  from static_analyzer.analysis_result import StaticAnalysisResults
18
18
  from static_analyzer.cluster_helpers import (
19
+ MAX_LLM_CLUSTERS,
20
+ enforce_cross_language_budget,
19
21
  get_all_cluster_ids,
20
22
  get_files_for_cluster_ids,
23
+ merge_clusters,
21
24
  )
22
25
  from static_analyzer.cluster_relations import (
23
26
  build_component_relations,
@@ -71,6 +74,7 @@ class ClusterMethodsMixin:
71
74
  Formatted cluster string with headers per language
72
75
  """
73
76
  cluster_lines = []
77
+ all_cluster_ids: set[int] = set()
74
78
 
75
79
  for lang in programming_langs:
76
80
  cfg = self.static_analysis.get_cfg(lang)
@@ -83,6 +87,17 @@ class ClusterMethodsMixin:
83
87
  cluster_lines.append(f"\n## {lang.capitalize()} - {header}\n")
84
88
  cluster_lines.append(cluster_str)
85
89
  cluster_lines.append("\n")
90
+ if cluster_result:
91
+ lang_ids = cluster_ids if cluster_ids else cluster_result.get_cluster_ids()
92
+ all_cluster_ids.update(lang_ids)
93
+
94
+ # Add explicit ID checklist so the LLM knows exactly which IDs to assign
95
+ if all_cluster_ids and not cluster_ids:
96
+ sorted_cluster_ids = sorted(all_cluster_ids)
97
+ cluster_lines.append(
98
+ f"\n## All Cluster IDs ({len(sorted_cluster_ids)} total)\n"
99
+ f"Every one of these IDs: {sorted_cluster_ids} must appear in exactly one group."
100
+ )
86
101
 
87
102
  return "".join(cluster_lines)
88
103
 
@@ -238,7 +253,6 @@ class ClusterMethodsMixin:
238
253
  abs_path = os.path.join(self.repo_dir, f) if not os.path.isabs(f) else f
239
254
  assigned_file_set.add(abs_path)
240
255
 
241
- result_parts = []
242
256
  cluster_results: dict[str, ClusterResult] = {}
243
257
  subgraph_cfgs: dict[str, CallGraph] = {}
244
258
 
@@ -254,15 +268,33 @@ class ClusterMethodsMixin:
254
268
  # Calculate clusters for the subgraph
255
269
  sub_cluster_result = sub_cfg.cluster()
256
270
 
271
+ # Merge into super-clusters if too many (same limit as AbstractionAgent)
272
+ if len(sub_cluster_result.clusters) > MAX_LLM_CLUSTERS:
273
+ n_before = len(sub_cluster_result.clusters)
274
+ sub_cluster_result = merge_clusters(sub_cluster_result, sub_cfg.to_networkx(), MAX_LLM_CLUSTERS)
275
+ logger.info(
276
+ f"[DetailsAgent] Subgraph for '{component.name}': "
277
+ f"merged {n_before} -> {len(sub_cluster_result.clusters)} super-clusters"
278
+ )
279
+
257
280
  # Expand to method-level if insufficient clusters
258
281
  sub_cluster_result = self._expand_to_method_level_clusters(sub_cfg, sub_cluster_result)
259
282
  cluster_results[lang] = sub_cluster_result
260
283
 
261
- cluster_str = sub_cfg.to_cluster_string(cluster_result=sub_cluster_result)
262
- if cluster_str.strip() and cluster_str not in ("empty", "none", "No clusters found."):
263
- result_parts.append(f"\n## {lang.capitalize()} - Component CFG\n")
264
- result_parts.append(cluster_str)
265
- result_parts.append("\n")
284
+ # Cross-language: enforce combined budget and unique IDs
285
+ if len(cluster_results) > 1:
286
+ cfg_nx = {lang: subgraph_cfgs[lang].to_networkx() for lang in cluster_results}
287
+ enforce_cross_language_budget(cluster_results, cfg_nx)
288
+
289
+ result_parts = []
290
+ for lang in self.static_analysis.get_languages():
291
+ if lang not in cluster_results:
292
+ continue
293
+ cluster_str = subgraph_cfgs[lang].to_cluster_string(cluster_result=cluster_results[lang])
294
+ if cluster_str.strip() and cluster_str not in ("empty", "none", "No clusters found."):
295
+ result_parts.append(f"\n## {lang.capitalize()} - Component CFG\n")
296
+ result_parts.append(cluster_str)
297
+ result_parts.append("\n")
266
298
 
267
299
  result = "".join(result_parts)
268
300
 
@@ -138,6 +138,8 @@ class DetailsAgent(ClusterMethodsMixin, CodeBoardingAgent):
138
138
 
139
139
  cluster_str = cluster_analysis.llm_str() if cluster_analysis else "No cluster analysis available."
140
140
 
141
+ group_names = [cc.name for cc in cluster_analysis.cluster_components] if cluster_analysis else []
142
+
141
143
  prompt = self.prompts["final_analysis"].format(
142
144
  project_name=self.project_name,
143
145
  cluster_analysis=cluster_str,
@@ -146,6 +148,12 @@ class DetailsAgent(ClusterMethodsMixin, CodeBoardingAgent):
146
148
  project_type=project_type,
147
149
  )
148
150
 
151
+ if group_names:
152
+ prompt += (
153
+ f"\n\n## All Group Names ({len(group_names)} total)\n"
154
+ f"Every one of these names: {group_names} must appear in exactly one component's source_group_names\n"
155
+ )
156
+
149
157
  # Build validation context with subgraph CFG graphs for edge checking
150
158
  context = ValidationContext(
151
159
  cluster_results=subgraph_cluster_results,
@@ -46,7 +46,6 @@ class MetaAgent(CodeBoardingAgent):
46
46
  )
47
47
 
48
48
  self._meta_cache = MetaCache(repo_dir=repo_dir, ignore_manager=self.ignore_manager)
49
- self._cache = self._meta_cache # Backward-compatible alias for tests/callers.
50
49
 
51
50
  @trace
52
51
  def analyze_project_metadata(self, skip_cache: bool = False) -> MetaAnalysisInsights:
@@ -67,6 +67,7 @@ Instructions:
67
67
  * What is its main flow/purpose
68
68
  * WHY these specific clusters are grouped together (provide clear rationale for the grouping decision)
69
69
  * How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
70
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
70
71
 
71
72
  Focus on:
72
73
  - Creating cohesive, logical groupings that reflect the actual {project_type} architecture
@@ -294,6 +295,7 @@ Instructions:
294
295
  * What is its main flow/purpose
295
296
  * WHY these specific clusters are grouped together (provide clear rationale)
296
297
  * How this group interacts with other cluster groups
298
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
297
299
 
298
300
  Focus on core subsystem functionality only. Avoid cross-cutting concerns like logging or error handling.
299
301
 
@@ -63,6 +63,7 @@ The CFG has been pre-clustered into groups of related methods/functions. Each cl
63
63
  * What is its main flow/purpose
64
64
  * WHY these specific clusters are grouped together (provide clear rationale for the grouping decision)
65
65
  * How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
66
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
66
67
 
67
68
  # Focus areas
68
69
  - Create cohesive, logical groupings that reflect the actual {project_type} architecture
@@ -296,6 +297,7 @@ The CFG has been pre-clustered into groups of related methods/functions. Each cl
296
297
  * What is its main flow/purpose
297
298
  * WHY these specific clusters are grouped together (provide clear rationale)
298
299
  * How this group interacts with other cluster groups
300
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
299
301
 
300
302
  # Focus
301
303
  Analyze core subsystem functionality only. Avoid cross-cutting concerns like logging or error handling.
@@ -49,6 +49,10 @@ CFG Clusters:
49
49
  Your Task:
50
50
  GROUP similar clusters together into logical components based on their relationships and purpose.
51
51
 
52
+ CRITICAL requirements:
53
+ - Every cluster ID must be included in exactly one group — no cluster left out, no duplicates
54
+ - Each group must represent a coherent architectural concept justified by method names, call patterns, and inter-cluster connections
55
+
52
56
  Instructions:
53
57
  1. Analyze the clusters shown above and identify which ones work together or are functionally related
54
58
  2. Group related clusters into meaningful components
@@ -61,6 +65,7 @@ Instructions:
61
65
  * What is its main flow/purpose
62
66
  * WHY these specific clusters are grouped together (provide clear rationale for the grouping decision)
63
67
  * How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
68
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
64
69
 
65
70
  Focus on:
66
71
  - Creating cohesive, logical groupings that reflect the actual {project_type} architecture
@@ -275,6 +280,7 @@ Instructions:
275
280
  * What is its main flow/purpose
276
281
  * WHY these specific clusters are grouped together (provide clear rationale)
277
282
  * How this group interacts with other cluster groups
283
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
278
284
 
279
285
  Focus on core subsystem functionality only. Avoid cross-cutting concerns like logging or error handling.
280
286
 
@@ -67,6 +67,7 @@ REQUIRED STEPS (execute in order):
67
67
  * What is its main flow/purpose
68
68
  * WHY these specific clusters are grouped together (MUST provide clear rationale)
69
69
  * How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
70
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
70
71
 
71
72
  FOCUS AREAS (prioritize):
72
73
  - Create cohesive, logical groupings that reflect the actual {project_type} architecture
@@ -313,6 +314,7 @@ REQUIRED STEPS (execute in order):
313
314
  * What is its main flow/purpose
314
315
  * WHY these specific clusters are grouped together (MUST provide clear rationale)
315
316
  * How this group interacts with other cluster groups
317
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
316
318
 
317
319
  FOCUS:
318
320
  MUST analyze core subsystem functionality only. STRICTLY avoid cross-cutting concerns like logging or error handling.
@@ -69,6 +69,7 @@ Instructions:
69
69
  * What is its main flow/purpose
70
70
  * WHY these specific clusters are grouped together (provide clear rationale for the grouping decision)
71
71
  * How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
72
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
72
73
 
73
74
  Focus on:
74
75
  - Creating cohesive, logical groupings that reflect the actual {project_type} architecture
@@ -383,6 +384,7 @@ Instructions:
383
384
  * What is its main flow/purpose
384
385
  * WHY these specific clusters are grouped together (provide clear rationale)
385
386
  * How this group interacts with other cluster groups
387
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
386
388
 
387
389
  Focus on core subsystem functionality only. Avoid cross-cutting concerns like logging or error handling.
388
390
 
@@ -62,6 +62,7 @@ Reason carefully, then execute:
62
62
  * What is its main flow/purpose
63
63
  * WHY these specific clusters are grouped together (provide clear rationale for the grouping decision)
64
64
  * How this group interacts with other cluster groups (which groups it calls, receives data from, or depends on)
65
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
65
66
 
66
67
  Focus on creating cohesive, logical groupings that reflect the actual {project_type} architecture based on semantic meaning from method names, call patterns, and architectural context. Describe inter-group interactions based on the inter-cluster connections.
67
68
 
@@ -303,6 +304,7 @@ Reason carefully, then execute:
303
304
  * What is its main flow/purpose
304
305
  * WHY these specific clusters are grouped together (provide clear rationale)
305
306
  * How this group interacts with other cluster groups
307
+ * The most important classes/methods in this group — mention their exact qualified names as shown in the clusters above
306
308
 
307
309
  Focus on core subsystem functionality only. Avoid cross-cutting concerns like logging or error handling.
308
310
 
@@ -322,11 +322,9 @@ def validate_group_name_coverage(result: AnalysisInsights, context: ValidationCo
322
322
  def validate_key_entities(result: AnalysisInsights, context: ValidationContext) -> ValidationResult:
323
323
  """
324
324
  Validate key_entities on every component:
325
- 1. Every component must have at least one key_entity.
326
- 2. Key entities must reference code within the component's cluster scope.
327
- If cluster_results are provided, validates strictly against the scope.
328
- Otherwise, validates that qualified names exist in the static analysis.
329
- Uses loose matching to auto-correct shortened paths in-place.
325
+ 1. Auto-correct qualified names via loose matching.
326
+ 2. Silently drop invalid key entities (out of scope or not found).
327
+ 3. Only fail if a component ends up with zero key_entities after dropping.
330
328
 
331
329
  Args:
332
330
  result: AnalysisInsights containing components with key_entities
@@ -334,26 +332,8 @@ def validate_key_entities(result: AnalysisInsights, context: ValidationContext)
334
332
  and cluster_results for scope validation
335
333
 
336
334
  Returns:
337
- ValidationResult with feedback for missing or invalid key entities
335
+ ValidationResult with feedback only for components left with zero key entities
338
336
  """
339
- feedback_messages: list[str] = []
340
-
341
- # Check 1: components without any key_entities
342
- components_without_key_entities = [c.name for c in result.components if not c.key_entities]
343
- if components_without_key_entities:
344
- missing_str = ", ".join(components_without_key_entities)
345
- feedback_messages.append(
346
- f"The following components are missing key entities: {missing_str}. "
347
- f"Every component must have at least one key entity (critical class or method) "
348
- f"that represents its core functionality. Please identify and add 2-5 key entities "
349
- f"for each component."
350
- )
351
- logger.warning(f"[Validation] Components without key entities: {missing_str}")
352
-
353
- # Check 2: Unified scope/existence check — key entities must be valid for this component.
354
- # Auto-correct via loose matching first (fixes typos in qualified names).
355
- # Then validate against cluster scope (strict) or static analysis existence (fallback).
356
- invalid_entities: list[str] = []
357
337
  auto_corrected = 0
358
338
 
359
339
  # Auto-correct qualified names via loose matching (always, when static_analysis available)
@@ -362,13 +342,11 @@ def validate_key_entities(result: AnalysisInsights, context: ValidationContext)
362
342
  for key_entity in component.key_entities:
363
343
  qname = key_entity.qualified_name.replace("/", ".")
364
344
  for lang in context.static_analysis.get_languages():
365
- # Exact / case-insensitive match
366
345
  try:
367
346
  context.static_analysis.get_reference(lang, qname)
368
347
  break
369
348
  except (ValueError, FileExistsError):
370
349
  pass
371
- # Loose match – auto-correct the qualified name in-place
372
350
  _text, loose_node = context.static_analysis.get_loose_reference(lang, qname)
373
351
  if loose_node is not None:
374
352
  logger.info(
@@ -381,7 +359,8 @@ def validate_key_entities(result: AnalysisInsights, context: ValidationContext)
381
359
  if auto_corrected:
382
360
  logger.info(f"[Validation] Auto-corrected {auto_corrected} qualified names via loose matching")
383
361
 
384
- # Validate: cluster scope if available, else static analysis existence
362
+ # Silently drop invalid key entities
363
+ dropped = 0
385
364
  if context.cluster_results:
386
365
  nodes_in_scope: set[str] = set()
387
366
  for cr in context.cluster_results.values():
@@ -389,35 +368,24 @@ def validate_key_entities(result: AnalysisInsights, context: ValidationContext)
389
368
  nodes_in_scope.update(members)
390
369
 
391
370
  for component in result.components:
371
+ valid = []
392
372
  for key_entity in component.key_entities:
393
373
  qname = key_entity.qualified_name
394
374
  in_scope = qname in nodes_in_scope
395
375
  if not in_scope:
396
376
  for scope_node in nodes_in_scope:
397
- if qname.startswith(scope_node + "."):
377
+ if qname.startswith(scope_node + ".") or scope_node.startswith(qname + "."):
398
378
  in_scope = True
399
379
  break
400
- if not in_scope:
401
- for scope_node in nodes_in_scope:
402
- if scope_node.startswith(qname + "."):
403
- in_scope = True
404
- break
405
- if not in_scope:
406
- invalid_entities.append(f"{component.name}: '{qname}'")
407
-
408
- if invalid_entities:
409
- invalid_str = "; ".join(invalid_entities[:10])
410
- more_msg = f" and {len(invalid_entities) - 10} more" if len(invalid_entities) > 10 else ""
411
- feedback_messages.append(
412
- f"The following key_entities are outside the component's cluster scope: {invalid_str}{more_msg}. "
413
- f"Key entities must reference code that is within the component's assigned clusters. "
414
- f"Please choose key entities from the code shown in the cluster analysis."
415
- )
416
- logger.warning(f"[Validation] Invalid key entities (out of scope): {len(invalid_entities)} found")
380
+ if in_scope:
381
+ valid.append(key_entity)
382
+ else:
383
+ dropped += 1
384
+ component.key_entities = valid
417
385
 
418
386
  elif context.static_analysis:
419
- # No cluster_results — fall back to checking if qualified names exist in static analysis
420
387
  for component in result.components:
388
+ valid = []
421
389
  for key_entity in component.key_entities:
422
390
  qname = key_entity.qualified_name.replace("/", ".")
423
391
  found = False
@@ -433,23 +401,31 @@ def validate_key_entities(result: AnalysisInsights, context: ValidationContext)
433
401
  key_entity.qualified_name = loose_node.fully_qualified_name
434
402
  found = True
435
403
  break
436
- if not found:
437
- invalid_entities.append(f"{component.name}: '{key_entity.qualified_name}'")
404
+ if found:
405
+ valid.append(key_entity)
406
+ else:
407
+ dropped += 1
408
+ component.key_entities = valid
438
409
 
439
- if invalid_entities:
440
- invalid_str = "; ".join(invalid_entities[:10])
441
- more_msg = f" and {len(invalid_entities) - 10} more" if len(invalid_entities) > 10 else ""
442
- feedback_messages.append(
443
- f"The following qualified names do not exist in the static analysis: {invalid_str}{more_msg}. "
444
- f"Please ensure all key_entities use qualified names that were found during static analysis."
445
- )
446
- logger.warning(f"[Validation] Invalid key entities (not found): {len(invalid_entities)} found")
410
+ if dropped:
411
+ logger.info(f"[Validation] Silently dropped {dropped} invalid key entities")
447
412
 
448
- if not feedback_messages:
449
- logger.info("[Validation] All key entities are valid")
450
- return ValidationResult(is_valid=True)
413
+ # Only fail if any component ended up with zero key_entities
414
+ empty_components = [c.name for c in result.components if not c.key_entities]
415
+ if empty_components:
416
+ missing_str = ", ".join(empty_components)
417
+ logger.warning(f"[Validation] Components with no valid key entities after cleanup: {missing_str}")
418
+ return ValidationResult(
419
+ is_valid=False,
420
+ feedback_messages=[
421
+ f"The following components have no valid key entities: {missing_str}. "
422
+ f"Every component must have at least one key entity (critical class or method) "
423
+ f"that represents its core functionality. Use exact qualified names from the cluster analysis."
424
+ ],
425
+ )
451
426
 
452
- return ValidationResult(is_valid=False, feedback_messages=feedback_messages)
427
+ logger.info("[Validation] All key entities are valid")
428
+ return ValidationResult(is_valid=True)
453
429
 
454
430
 
455
431
  def validate_file_classifications(result: ComponentFiles, context: ValidationContext) -> ValidationResult:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.10.0
3
+ Version: 0.10.2
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -80,6 +80,7 @@ def _hydrate_component_methods_from_refs(
80
80
  files_index: dict[str, FileEntry],
81
81
  methods_index: dict[str, "MethodIndexEntry"],
82
82
  ) -> None:
83
+ missing: list[str] = []
83
84
  for component in analysis.components:
84
85
  rebuilt: list[FileMethodGroup] = []
85
86
  for group in component.file_methods:
@@ -91,7 +92,7 @@ def _hydrate_component_methods_from_refs(
91
92
  qname = _to_method_qualified_name(method)
92
93
  indexed = methods_index.get(_method_key(file_path, qname))
93
94
  if indexed is None:
94
- logger.warning(f"Missing method index entry for {file_path}|{qname}")
95
+ missing.append(f"{file_path}|{qname}")
95
96
  continue
96
97
  methods.append(
97
98
  MethodEntry(
@@ -108,6 +109,9 @@ def _hydrate_component_methods_from_refs(
108
109
 
109
110
  component.file_methods = rebuilt
110
111
 
112
+ if missing:
113
+ logger.warning("Missing method index entry for %d ref(s): %s", len(missing), missing)
114
+
111
115
 
112
116
  class RelationJson(Relation):
113
117
  """Relation subclass that includes src_id/dst_id and static analysis evidence in JSON serialization."""
@@ -10,6 +10,7 @@ NOTE: Method status assignment is delegated to repo_utils.method_diff to ensure
10
10
  consistent behavior between the main analysis pipeline and incremental updates.
11
11
  """
12
12
 
13
+ import logging
13
14
  import time
14
15
  from pathlib import Path
15
16
  from typing import Callable
@@ -21,6 +22,8 @@ from diagram_analysis.manifest import AnalysisManifest
21
22
  from repo_utils.change_detector import ChangeSet
22
23
  from repo_utils.method_diff import get_method_statuses_for_file
23
24
 
25
+ logger = logging.getLogger(__name__)
26
+
24
27
  # Callable that resolves a repo-relative file path to its current MethodEntry list.
25
28
  SymbolResolver = Callable[[str], list[MethodEntry]]
26
29
 
@@ -124,7 +127,29 @@ class IncrementalUpdater:
124
127
 
125
128
  # Modified
126
129
  prev_active = self._get_previous_active_methods(file_path)
127
- current = self._get_current_methods(file_path)
130
+ try:
131
+ current = self._get_current_methods(file_path)
132
+ except Exception as exc:
133
+ logger.warning("Symbol resolution failed for %s: %s", file_path, exc)
134
+ current = []
135
+
136
+ if not current and prev_active:
137
+ logger.warning(
138
+ "Symbol resolution returned no methods for %s; marking all existing methods as modified",
139
+ file_path,
140
+ )
141
+ return (
142
+ FileDelta(
143
+ file_path=file_path,
144
+ file_status=ChangeStatus.MODIFIED,
145
+ component_id=component_id,
146
+ modified_methods=[
147
+ self._to_method_change(file_path, m, change_type=ChangeStatus.MODIFIED)
148
+ for _, m in sorted(prev_active.items())
149
+ ],
150
+ ),
151
+ missing,
152
+ )
128
153
  current_by_name = {m.qualified_name: m for m in current}
129
154
 
130
155
  prev_keys = set(prev_active.keys())
@@ -138,7 +163,7 @@ class IncrementalUpdater:
138
163
  file_status=ChangeStatus.MODIFIED,
139
164
  component_id=component_id,
140
165
  added_methods=[
141
- self._to_method_change(file_path, m)
166
+ self._to_method_change(file_path, m, change_type=ChangeStatus.ADDED)
142
167
  for m in current
143
168
  if m.qualified_name in current_keys - prev_keys
144
169
  ],