code2llm 0.5.172__tar.gz → 0.5.174__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 (211) hide show
  1. {code2llm-0.5.172/code2llm.egg-info → code2llm-0.5.174}/PKG-INFO +6 -6
  2. {code2llm-0.5.172 → code2llm-0.5.174}/README.md +5 -5
  3. code2llm-0.5.174/VERSION +1 -0
  4. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/__init__.py +1 -1
  5. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/smells.py +23 -7
  6. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/flow_constants.py +18 -0
  7. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/planfile_tickets.py +46 -19
  8. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/_render_section_helpers.py +2 -1
  9. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/helpers.py +1 -1
  10. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/metrics_duplicates.py +4 -2
  11. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/metrics_health.py +8 -6
  12. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/renderer.py +1 -1
  13. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/nlp/__init__.py +1 -1
  14. {code2llm-0.5.172 → code2llm-0.5.174/code2llm.egg-info}/PKG-INFO +6 -6
  15. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm.egg-info/SOURCES.txt +2 -0
  16. {code2llm-0.5.172 → code2llm-0.5.174}/pyproject.toml +1 -1
  17. code2llm-0.5.174/tests/test_dup_groups.py +80 -0
  18. code2llm-0.5.174/tests/test_smells_data_clumps.py +83 -0
  19. code2llm-0.5.172/VERSION +0 -1
  20. {code2llm-0.5.172 → code2llm-0.5.174}/LICENSE +0 -0
  21. {code2llm-0.5.172 → code2llm-0.5.174}/MANIFEST.in +0 -0
  22. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/__main__.py +0 -0
  23. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/__init__.py +0 -0
  24. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/_data_impl.py +0 -0
  25. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/call_graph.py +0 -0
  26. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/cfg.py +0 -0
  27. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/coupling.py +0 -0
  28. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/data_analysis.py +0 -0
  29. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/dfg.py +0 -0
  30. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/pipeline_classifier.py +0 -0
  31. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/pipeline_detector.py +0 -0
  32. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/pipeline_resolver.py +0 -0
  33. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/side_effects.py +0 -0
  34. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/type_inference.py +0 -0
  35. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/utils/__init__.py +0 -0
  36. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/analysis/utils/ast_helpers.py +0 -0
  37. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/api.py +0 -0
  38. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli.py +0 -0
  39. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_analysis.py +0 -0
  40. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_commands.py +0 -0
  41. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_exports/__init__.py +0 -0
  42. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_exports/code2logic.py +0 -0
  43. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_exports/formats.py +0 -0
  44. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_exports/orchestrator.py +0 -0
  45. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_exports/orchestrator_chunked.py +0 -0
  46. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_exports/orchestrator_constants.py +0 -0
  47. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_exports/orchestrator_handlers.py +0 -0
  48. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_exports/prompt.py +0 -0
  49. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/cli_parser.py +0 -0
  50. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/__init__.py +0 -0
  51. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/analyzer.py +0 -0
  52. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/ast_registry.py +0 -0
  53. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/config.py +0 -0
  54. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/export_pipeline.py +0 -0
  55. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/file_analyzer.py +0 -0
  56. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/file_cache.py +0 -0
  57. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/file_filter.py +0 -0
  58. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/gitignore.py +0 -0
  59. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/incremental.py +0 -0
  60. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/__init__.py +0 -0
  61. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/_c_parser.py +0 -0
  62. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/_calls.py +0 -0
  63. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/_complexity.py +0 -0
  64. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/base.py +0 -0
  65. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/cpp.py +0 -0
  66. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/csharp.py +0 -0
  67. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/generic.py +0 -0
  68. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/go_lang.py +0 -0
  69. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/java.py +0 -0
  70. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/php.py +0 -0
  71. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/ruby.py +0 -0
  72. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/rust.py +0 -0
  73. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/ts_extractors.py +0 -0
  74. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/ts_parser.py +0 -0
  75. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/lang/typescript.py +0 -0
  76. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/large_repo.py +0 -0
  77. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/models.py +0 -0
  78. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/persistent_cache.py +0 -0
  79. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/refactoring.py +0 -0
  80. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/repo_files.py +0 -0
  81. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/source_classifier.py +0 -0
  82. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/streaming/__init__.py +0 -0
  83. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/streaming/cache.py +0 -0
  84. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/streaming/incremental.py +0 -0
  85. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/streaming/prioritizer.py +0 -0
  86. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/streaming/scanner.py +0 -0
  87. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/streaming/strategies.py +0 -0
  88. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/streaming_analyzer.py +0 -0
  89. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/core/toon_size_manager.py +0 -0
  90. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/__init__.py +0 -0
  91. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/article_view.py +0 -0
  92. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/base.py +0 -0
  93. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/context_exporter.py +0 -0
  94. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/context_view.py +0 -0
  95. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/dashboard_data.py +0 -0
  96. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/dashboard_renderer.py +0 -0
  97. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/evolution/__init__.py +0 -0
  98. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/evolution/computation.py +0 -0
  99. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/evolution/constants.py +0 -0
  100. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/evolution/exclusion.py +0 -0
  101. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/evolution/render.py +0 -0
  102. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/evolution/yaml_export.py +0 -0
  103. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/evolution_exporter.py +0 -0
  104. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/flow_exporter.py +0 -0
  105. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/flow_renderer.py +0 -0
  106. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/html_dashboard.py +0 -0
  107. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/index_generator/__init__.py +0 -0
  108. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/index_generator/renderer.py +0 -0
  109. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/index_generator/scanner.py +0 -0
  110. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/index_generator.py +0 -0
  111. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/json_exporter.py +0 -0
  112. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/llm_exporter.py +0 -0
  113. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/map/__init__.py +0 -0
  114. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/map/alerts.py +0 -0
  115. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/map/details.py +0 -0
  116. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/map/header.py +0 -0
  117. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/map/module_list.py +0 -0
  118. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/map/utils.py +0 -0
  119. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/map/yaml_export.py +0 -0
  120. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/map_exporter.py +0 -0
  121. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/mermaid/__init__.py +0 -0
  122. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/mermaid/calls.py +0 -0
  123. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/mermaid/classic.py +0 -0
  124. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/mermaid/compact.py +0 -0
  125. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/mermaid/flow_compact.py +0 -0
  126. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/mermaid/flow_detailed.py +0 -0
  127. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/mermaid/flow_full.py +0 -0
  128. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/mermaid/utils.py +0 -0
  129. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/mermaid_exporter.py +0 -0
  130. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
  131. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/project_yaml/__init__.py +0 -0
  132. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/project_yaml/constants.py +0 -0
  133. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/project_yaml/core.py +0 -0
  134. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/project_yaml/evolution.py +0 -0
  135. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/project_yaml/health.py +0 -0
  136. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/project_yaml/hotspots.py +0 -0
  137. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/project_yaml/modules.py +0 -0
  138. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/project_yaml_exporter.py +0 -0
  139. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/readme/__init__.py +0 -0
  140. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/readme/content.py +0 -0
  141. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/readme/files.py +0 -0
  142. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/readme/insights.py +0 -0
  143. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/readme/sections.py +0 -0
  144. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/readme_exporter.py +0 -0
  145. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/report_generators.py +0 -0
  146. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/__init__.py +0 -0
  147. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/_render_coupling_helpers.py +0 -0
  148. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/constants.py +0 -0
  149. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/metrics.py +0 -0
  150. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/metrics_core.py +0 -0
  151. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon/module_detail.py +0 -0
  152. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon.py +0 -0
  153. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/toon_view.py +0 -0
  154. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/validate_project.py +0 -0
  155. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/exporters/yaml_exporter.py +0 -0
  156. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/__init__.py +0 -0
  157. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/_utils.py +0 -0
  158. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/llm_flow/__init__.py +0 -0
  159. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/llm_flow/analysis.py +0 -0
  160. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/llm_flow/cli.py +0 -0
  161. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/llm_flow/generator.py +0 -0
  162. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/llm_flow/nodes.py +0 -0
  163. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/llm_flow/parsing.py +0 -0
  164. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/llm_flow/utils.py +0 -0
  165. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/llm_flow.py +0 -0
  166. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/llm_task.py +0 -0
  167. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/mermaid/__init__.py +0 -0
  168. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/mermaid/fix.py +0 -0
  169. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/mermaid/png.py +0 -0
  170. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/mermaid/validation.py +0 -0
  171. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/generators/mermaid.py +0 -0
  172. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/nlp/config.py +0 -0
  173. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/nlp/entity_resolution.py +0 -0
  174. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/nlp/intent_matching.py +0 -0
  175. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/nlp/normalization.py +0 -0
  176. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/nlp/pipeline.py +0 -0
  177. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/parsers/toon_parser.py +0 -0
  178. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/patterns/__init__.py +0 -0
  179. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/patterns/detector.py +0 -0
  180. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/refactor/__init__.py +0 -0
  181. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm/refactor/prompt_engine.py +0 -0
  182. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm.egg-info/dependency_links.txt +0 -0
  183. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm.egg-info/entry_points.txt +0 -0
  184. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm.egg-info/requires.txt +0 -0
  185. {code2llm-0.5.172 → code2llm-0.5.174}/code2llm.egg-info/top_level.txt +0 -0
  186. {code2llm-0.5.172 → code2llm-0.5.174}/setup.cfg +0 -0
  187. {code2llm-0.5.172 → code2llm-0.5.174}/setup.py +0 -0
  188. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_advanced_analysis.py +0 -0
  189. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_analyzer.py +0 -0
  190. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_cache_invalidation_e2e.py +0 -0
  191. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_calls_toon_export.py +0 -0
  192. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_declarative_collection.py +0 -0
  193. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_deep_analysis.py +0 -0
  194. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_edge_cases.py +0 -0
  195. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_export_cache_flags.py +0 -0
  196. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_file_analyzer_tagging.py +0 -0
  197. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_flow_exporter.py +0 -0
  198. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_format_quality.py +0 -0
  199. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_multilanguage_e2e.py +0 -0
  200. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_nlp_pipeline.py +0 -0
  201. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_nonpython_cc_calls.py +0 -0
  202. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_orchestrator_cache_mtime.py +0 -0
  203. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_persistent_cache.py +0 -0
  204. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_pipeline_detector.py +0 -0
  205. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_planfile_tickets_exporter.py +0 -0
  206. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_project_toon_export.py +0 -0
  207. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_prompt_engine.py +0 -0
  208. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_prompt_txt.py +0 -0
  209. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_refactoring_engine.py +0 -0
  210. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_smells_shotgun_surgery.py +0 -0
  211. {code2llm-0.5.172 → code2llm-0.5.174}/tests/test_toon_v2.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2llm
3
- Version: 0.5.172
3
+ Version: 0.5.174
4
4
  Summary: High-performance Python code flow analysis with optimized TOON format - CFG, DFG, call graphs, and intelligent code queries
5
5
  Home-page: https://github.com/wronai/stts
6
6
  Author: Tom Sapletta
@@ -67,13 +67,13 @@ Dynamic: requires-python
67
67
 
68
68
  ## AI Cost Tracking
69
69
 
70
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.172-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
71
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$17.33-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-92.8h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fdeep%2Fdeep--v4--pro-lightgrey)
70
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.174-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
71
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.80-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-102.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fdeep%2Fdeep--v4--pro-lightgrey)
72
72
 
73
- - 🤖 **LLM usage:** $17.3260 (246 commits)
74
- - 👤 **Human dev:** ~$9280 (92.8h @ $100/h, 30min dedup)
73
+ - 🤖 **LLM usage:** $3.8031 (260 commits)
74
+ - 👤 **Human dev:** ~$10235 (102.3h @ $100/h, 30min dedup)
75
75
 
76
- Generated on 2026-07-06 using [openrouter/deep/deep-v4-pro](https://openrouter.ai/deep/deep-v4-pro)
76
+ Generated on 2026-07-13 using [openrouter/deep/deep-v4-pro](https://openrouter.ai/deep/deep-v4-pro)
77
77
 
78
78
  ---
79
79
 
@@ -2,13 +2,13 @@
2
2
 
3
3
  ## AI Cost Tracking
4
4
 
5
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.172-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
6
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$17.33-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-92.8h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fdeep%2Fdeep--v4--pro-lightgrey)
5
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.174-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
6
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.80-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-102.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fdeep%2Fdeep--v4--pro-lightgrey)
7
7
 
8
- - 🤖 **LLM usage:** $17.3260 (246 commits)
9
- - 👤 **Human dev:** ~$9280 (92.8h @ $100/h, 30min dedup)
8
+ - 🤖 **LLM usage:** $3.8031 (260 commits)
9
+ - 👤 **Human dev:** ~$10235 (102.3h @ $100/h, 30min dedup)
10
10
 
11
- Generated on 2026-07-06 using [openrouter/deep/deep-v4-pro](https://openrouter.ai/deep/deep-v4-pro)
11
+ Generated on 2026-07-13 using [openrouter/deep/deep-v4-pro](https://openrouter.ai/deep/deep-v4-pro)
12
12
 
13
13
  ---
14
14
 
@@ -0,0 +1 @@
1
+ 0.5.174
@@ -8,7 +8,7 @@ Includes NLP Processing Pipeline for query normalization, intent matching,
8
8
  and entity resolution with multilingual support.
9
9
  """
10
10
 
11
- __version__ = "0.5.172"
11
+ __version__ = "0.5.174"
12
12
  __author__ = "STTS Project"
13
13
 
14
14
  # Core analysis components (lightweight, always needed)
@@ -131,18 +131,34 @@ class SmellDetector:
131
131
  return smells
132
132
 
133
133
  def _detect_data_clumps(self) -> List[CodeSmell]:
134
- """Detect 3+ variables frequently passed together."""
134
+ """Detect 3+ variables frequently passed together.
135
+
136
+ Grouped by (file, argument-set), not argument-set alone: a common
137
+ constructor shape like `(self, project_root, config)` is a
138
+ conventional pattern reused independently by unrelated classes
139
+ across unrelated files (any two classes that each take a project
140
+ root and a config object) -- that's coincidence, not evidence the
141
+ two classes should share a bundled parameter object. Grouping by
142
+ bare argument-set alone falsely flagged such coincidences as data
143
+ clumps (reproduced: `wup.anomaly_detector.AnomalyDetector.__init__`
144
+ and `wup.testql_monitor.TestQLMonitor.__init__` -- two unrelated
145
+ classes in unrelated files -- flagged as clumped on `project_root,
146
+ self, config`). A genuine data clump -- args that should become one
147
+ object -- is a same-file (usually same-class) phenomenon; scoping
148
+ by file preserves that signal while dropping cross-file
149
+ coincidental-signature noise.
150
+ """
135
151
  smells = []
136
152
  # Simplified: find functions with same 3+ arguments
137
- arg_sets = {} # frozenset(args) -> List[func_names]
153
+ arg_sets = {} # (file, frozenset(args)) -> List[func_names]
138
154
  for func_name, func_info in self.result.functions.items():
139
155
  if len(func_info.args) >= 3:
140
- args = frozenset(func_info.args)
141
- if args not in arg_sets:
142
- arg_sets[args] = []
143
- arg_sets[args].append(func_name)
156
+ key = (func_info.file, frozenset(func_info.args))
157
+ if key not in arg_sets:
158
+ arg_sets[key] = []
159
+ arg_sets[key].append(func_name)
144
160
 
145
- for args, funcs in arg_sets.items():
161
+ for (file, args), funcs in arg_sets.items():
146
162
  if len(funcs) >= 2:
147
163
  for func_name in funcs:
148
164
  func_info = self.result.functions[func_name]
@@ -39,6 +39,24 @@ EXCLUDE_PATTERNS = {
39
39
  ".envs",
40
40
  }
41
41
 
42
+ # Paths where duplicate class names are intentional (twin sites, examples, contracts).
43
+ # Excluded from duplicate *detection* only — other metrics still apply.
44
+ INTENTIONAL_DUP_COPY_MARKERS: tuple[str, ...] = (
45
+ "pc1/",
46
+ "pc2/",
47
+ "examples/",
48
+ "-contract-",
49
+ )
50
+
51
+
52
+ @lru_cache(maxsize=4096)
53
+ def is_intentional_duplicate_copy(path: str) -> bool:
54
+ """True when *path* is an intentional parallel copy (not actionable duplication)."""
55
+ if not path:
56
+ return False
57
+ norm = path.replace("\\", "/").lower()
58
+ return any(marker in norm for marker in INTENTIONAL_DUP_COPY_MARKERS)
59
+
42
60
 
43
61
  @lru_cache(maxsize=4096)
44
62
  def is_excluded_path(path: str) -> bool:
@@ -256,9 +256,51 @@ def _god_module_tickets(result: AnalysisResult) -> list[PlanfileTicketSuggestion
256
256
 
257
257
 
258
258
  def _duplicate_class_tickets(result: AnalysisResult) -> list[PlanfileTicketSuggestion]:
259
- """Yield deduplication tickets for class pairs with ≥60% method-name overlap."""
259
+ """Yield one deduplication ticket per duplicate *class name group* (not per pair)."""
260
+ from collections import defaultdict
261
+
262
+ from code2llm.exporters.flow_constants import is_intentional_duplicate_copy
263
+
264
+ pairs = _duplicate_class_pairs(result)
265
+ groups: dict[str, set[str]] = defaultdict(set)
266
+ for left_name, left_file, right_file in pairs:
267
+ groups[left_name].add(left_file)
268
+ groups[left_name].add(right_file)
269
+
260
270
  tickets: list[PlanfileTicketSuggestion] = []
261
- classes = list(result.classes.values())
271
+ for class_name, files in sorted(groups.items()):
272
+ rel_files = tuple(sorted(files))
273
+ if all(is_intentional_duplicate_copy(f) for f in rel_files):
274
+ continue
275
+ tickets.append(
276
+ PlanfileTicketSuggestion(
277
+ signal="code2llm_dup",
278
+ title=f"Consolidate duplicate class {class_name}",
279
+ description=(
280
+ f"code2llm found `{class_name}` in {len(rel_files)} files: "
281
+ f"{', '.join(rel_files)}.\n\n"
282
+ "Extract a shared base/helper or merge the duplicated behavior, then "
283
+ "re-run code2llm to confirm the duplicate signal is gone."
284
+ ),
285
+ priority="high",
286
+ labels=("llm-ready", "code2llm", "duplication", "refactor"),
287
+ files=rel_files,
288
+ dedupe_key=f"code2llm:dup:{class_name}",
289
+ ),
290
+ )
291
+ return tickets
292
+
293
+
294
+ def _duplicate_class_pairs(result: AnalysisResult) -> list[tuple[str, str, str]]:
295
+ """Return (class_name, file_a, file_b) for each similar class pair."""
296
+ from code2llm.exporters.flow_constants import is_intentional_duplicate_copy
297
+
298
+ pairs: list[tuple[str, str, str]] = []
299
+ classes = [
300
+ c
301
+ for c in result.classes.values()
302
+ if not is_intentional_duplicate_copy(c.file)
303
+ ]
262
304
  for index, left in enumerate(classes):
263
305
  left_methods = _method_names(left)
264
306
  if len(left_methods) < 3:
@@ -275,23 +317,8 @@ def _duplicate_class_tickets(result: AnalysisResult) -> list[PlanfileTicketSugge
275
317
  continue
276
318
  left_file = _rel_path(left.file, result.project_path)
277
319
  right_file = _rel_path(right.file, result.project_path)
278
- tickets.append(
279
- PlanfileTicketSuggestion(
280
- signal="code2llm_dup",
281
- title=f"Consolidate duplicate class {left.name}",
282
- description=(
283
- f"code2llm found similar `{left.name}` classes in `{left_file}` "
284
- f"and `{right_file}` with {len(overlap)} overlapping methods.\n\n"
285
- "Extract a shared base/helper or merge the duplicated behavior, then "
286
- "re-run code2llm to confirm the duplicate signal is gone."
287
- ),
288
- priority="high",
289
- labels=("llm-ready", "code2llm", "duplication", "refactor"),
290
- files=(left_file, right_file),
291
- dedupe_key=f"code2llm:dup:{left.name}:{left_file}:{right_file}",
292
- ),
293
- )
294
- return tickets
320
+ pairs.append((left.name, left_file, right_file))
321
+ return pairs
295
322
 
296
323
 
297
324
  def _smell_tickets(result: AnalysisResult) -> list[PlanfileTicketSuggestion]:
@@ -54,7 +54,8 @@ def _build_refactor_steps(ctx: Dict[str, Any]) -> List[str]:
54
54
  """Build list of refactoring step strings from health, duplicates, and cycles."""
55
55
  steps: List[str] = []
56
56
  if ctx["duplicates"]:
57
- steps.append(f"rm duplicates (-{len(ctx['duplicates'])} dup classes)")
57
+ group_count = len({d["class_name"] for d in ctx["duplicates"]})
58
+ steps.append(f"rm duplicates (-{group_count} dup groups)")
58
59
  for gi in [h for h in ctx["health"] if h["code"] == "GOD"]:
59
60
  steps.append(f"split {gi['message'].split('=')[0].strip()} (god module)")
60
61
  cc_issues = [h for h in ctx["health"] if h["code"] == "CC"]
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
  from typing import Any, Dict, Set
6
6
 
7
7
  from code2llm.core.models import AnalysisResult, FunctionInfo
8
- from code2llm.exporters.flow_constants import is_excluded_path
8
+ from code2llm.exporters.flow_constants import is_excluded_path, is_intentional_duplicate_copy
9
9
 
10
10
  # Re-export is_excluded_path from flow_constants to eliminate duplication
11
11
 
@@ -7,7 +7,7 @@ from typing import Any, Dict, List, Optional, Set
7
7
 
8
8
  from code2llm.core.models import AnalysisResult, ClassInfo
9
9
 
10
- from .helpers import _is_excluded, _rel_path
10
+ from .helpers import _is_excluded, _rel_path, is_intentional_duplicate_copy
11
11
 
12
12
 
13
13
  class DuplicatesMetricsComputer:
@@ -22,7 +22,9 @@ class DuplicatesMetricsComputer:
22
22
  dupes: List[Dict[str, Any]] = []
23
23
  # Filter out excluded classes first
24
24
  class_list = [
25
- (q, c) for q, c in result.classes.items() if not _is_excluded(c.file)
25
+ (q, c)
26
+ for q, c in result.classes.items()
27
+ if not _is_excluded(c.file) and not is_intentional_duplicate_copy(c.file)
26
28
  ]
27
29
 
28
30
  for i, (qa, ca) in enumerate(class_list):
@@ -31,21 +31,23 @@ class HealthMetricsComputer:
31
31
  return issues[:20] # MAX_HEALTH_ISSUES
32
32
 
33
33
  def _check_duplicates_health(self, ctx, issues):
34
- """Check for duplicate classes."""
35
- ndups = len(ctx["duplicates"])
34
+ """Check for duplicate classes (report merged name-groups, not pair count)."""
35
+ dupes = ctx["duplicates"]
36
+ group_names = {d.get("class_name") for d in dupes if d.get("class_name")}
37
+ ndups = len(group_names)
36
38
  if ndups > 0:
37
39
  dup_lines = sum(
38
40
  ctx["files"].get(d.get("fileA_abs", ""), {}).get("lines", 0)
39
- for d in ctx["duplicates"]
41
+ for d in dupes
40
42
  )
41
43
  issues.append(
42
44
  {
43
45
  "severity": "red",
44
46
  "code": "DUP",
45
- "message": f"{ndups} classes duplicated ({dup_lines}L wasted)"
47
+ "message": f"{ndups} duplicate class groups ({dup_lines}L wasted)"
46
48
  if dup_lines
47
- else f"{ndups} classes duplicated",
48
- "impact": f"-{ndups} dup classes",
49
+ else f"{ndups} duplicate class groups",
50
+ "impact": f"-{ndups} dup groups",
49
51
  }
50
52
  )
51
53
 
@@ -41,7 +41,7 @@ class ToonRenderer:
41
41
  all_cc = [f["cc"] for f in ctx["func_metrics"]]
42
42
  avg_cc = round(sum(all_cc) / len(all_cc), 1) if all_cc else 0.0
43
43
  critical = len([f for f in ctx["func_metrics"] if f["cc"] >= CC_CRITICAL])
44
- ndups = len(ctx["duplicates"])
44
+ ndups = len({d["class_name"] for d in ctx["duplicates"]})
45
45
  ncycles = len(ctx["cycles"])
46
46
  lang_label = _detect_language_label(result)
47
47
  return [
@@ -4,7 +4,7 @@ Provides query normalization, intent matching, and entity resolution
4
4
  with multilingual support and fuzzy matching.
5
5
  """
6
6
 
7
- __version__ = "0.5.172"
7
+ __version__ = "0.5.174"
8
8
 
9
9
  from .pipeline import NLPPipeline
10
10
  from .normalization import QueryNormalizer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2llm
3
- Version: 0.5.172
3
+ Version: 0.5.174
4
4
  Summary: High-performance Python code flow analysis with optimized TOON format - CFG, DFG, call graphs, and intelligent code queries
5
5
  Home-page: https://github.com/wronai/stts
6
6
  Author: Tom Sapletta
@@ -67,13 +67,13 @@ Dynamic: requires-python
67
67
 
68
68
  ## AI Cost Tracking
69
69
 
70
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.172-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
71
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$17.33-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-92.8h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fdeep%2Fdeep--v4--pro-lightgrey)
70
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.174-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
71
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.80-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-102.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fdeep%2Fdeep--v4--pro-lightgrey)
72
72
 
73
- - 🤖 **LLM usage:** $17.3260 (246 commits)
74
- - 👤 **Human dev:** ~$9280 (92.8h @ $100/h, 30min dedup)
73
+ - 🤖 **LLM usage:** $3.8031 (260 commits)
74
+ - 👤 **Human dev:** ~$10235 (102.3h @ $100/h, 30min dedup)
75
75
 
76
- Generated on 2026-07-06 using [openrouter/deep/deep-v4-pro](https://openrouter.ai/deep/deep-v4-pro)
76
+ Generated on 2026-07-13 using [openrouter/deep/deep-v4-pro](https://openrouter.ai/deep/deep-v4-pro)
77
77
 
78
78
  ---
79
79
 
@@ -186,6 +186,7 @@ tests/test_cache_invalidation_e2e.py
186
186
  tests/test_calls_toon_export.py
187
187
  tests/test_declarative_collection.py
188
188
  tests/test_deep_analysis.py
189
+ tests/test_dup_groups.py
189
190
  tests/test_edge_cases.py
190
191
  tests/test_export_cache_flags.py
191
192
  tests/test_file_analyzer_tagging.py
@@ -202,5 +203,6 @@ tests/test_project_toon_export.py
202
203
  tests/test_prompt_engine.py
203
204
  tests/test_prompt_txt.py
204
205
  tests/test_refactoring_engine.py
206
+ tests/test_smells_data_clumps.py
205
207
  tests/test_smells_shotgun_surgery.py
206
208
  tests/test_toon_v2.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code2llm"
7
- version = "0.5.172"
7
+ version = "0.5.174"
8
8
  description = "High-performance Python code flow analysis with optimized TOON format - CFG, DFG, call graphs, and intelligent code queries"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,80 @@
1
+ """Regression: duplicate class groups vs pairwise pairs (IFURI-245)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from code2llm.core.models import AnalysisResult, ClassInfo
8
+ from code2llm.exporters.flow_constants import is_intentional_duplicate_copy
9
+ from code2llm.exporters.planfile_tickets import collect_planfile_tickets
10
+ from code2llm.exporters.toon.metrics_duplicates import DuplicatesMetricsComputer
11
+ from code2llm.exporters.toon.metrics_health import HealthMetricsComputer
12
+
13
+
14
+ def _worker(file: Path, pkg: str) -> ClassInfo:
15
+ methods = [f"{pkg}.Worker.load", f"{pkg}.Worker.save", f"{pkg}.Worker.run"]
16
+ return ClassInfo(
17
+ name="Worker",
18
+ qualified_name=f"{pkg}.Worker",
19
+ file=str(file),
20
+ line=1,
21
+ methods=methods,
22
+ )
23
+
24
+
25
+ def test_intentional_copy_paths_are_detected() -> None:
26
+ assert is_intentional_duplicate_copy("/repo/pc1/net-user-pl/handler.py")
27
+ assert is_intentional_duplicate_copy("/repo/examples/demo/foo.py")
28
+ assert is_intentional_duplicate_copy("/repo/urirun-contract-windowpair/x.py")
29
+ assert not is_intentional_duplicate_copy("/repo/app/src/handler.py")
30
+
31
+
32
+ def test_duplicate_groups_not_pair_count(tmp_path: Path) -> None:
33
+ project = tmp_path
34
+ result = AnalysisResult(project_path=str(project))
35
+ for index in range(4):
36
+ pkg = f"pkg{index}"
37
+ path = project / f"{pkg}.py"
38
+ path.write_text("# stub\n", encoding="utf-8")
39
+ result.classes[f"{pkg}.Worker"] = _worker(path, pkg)
40
+
41
+ dupes = DuplicatesMetricsComputer(str(project)).detect_duplicates(result)
42
+ assert len(dupes) == 6 # C(4,2) pairs for same class name
43
+
44
+ ctx = {
45
+ "duplicates": dupes,
46
+ "files": {str(project / f"pkg{i}.py"): {"lines": 10} for i in range(4)},
47
+ "result": result,
48
+ "health": [],
49
+ "func_metrics": [],
50
+ }
51
+ issues = HealthMetricsComputer().compute_health(ctx)
52
+ dup_issue = next(i for i in issues if i["code"] == "DUP")
53
+ assert "1 duplicate class groups" in dup_issue["message"]
54
+
55
+
56
+ def test_planfile_dup_ticket_uses_stable_group_dedupe_key(tmp_path: Path) -> None:
57
+ project = tmp_path
58
+ result = AnalysisResult(project_path=str(project))
59
+ for pkg in ("one", "two", "three"):
60
+ path = project / f"{pkg}.py"
61
+ path.write_text("# stub\n", encoding="utf-8")
62
+ result.classes[f"pkg.{pkg}.Worker"] = _worker(path, f"pkg.{pkg}")
63
+
64
+ dup_tickets = [t for t in collect_planfile_tickets(result) if t.signal == "code2llm_dup"]
65
+ assert len(dup_tickets) == 1
66
+ assert dup_tickets[0].dedupe_key == "code2llm:dup:Worker"
67
+ assert len(dup_tickets[0].files) == 3
68
+
69
+
70
+ def test_examples_dir_excluded_from_duplicate_pairs(tmp_path: Path) -> None:
71
+ project = tmp_path
72
+ result = AnalysisResult(project_path=str(project))
73
+ for pkg in ("app/main", "examples/demo"):
74
+ path = project / f"{pkg}.py"
75
+ path.parent.mkdir(parents=True, exist_ok=True)
76
+ path.write_text("# stub\n", encoding="utf-8")
77
+ result.classes[f"{pkg}.Worker"] = _worker(path, pkg.replace("/", "."))
78
+
79
+ dupes = DuplicatesMetricsComputer(str(project)).detect_duplicates(result)
80
+ assert dupes == []
@@ -0,0 +1,83 @@
1
+ """Tests for SmellDetector._detect_data_clumps.
2
+
3
+ 2026-07-06 incident (same root cause as the Shotgun Surgery fix): grouping
4
+ functions by their argument-name set alone, ignoring file, meant a common
5
+ constructor shape like `(self, project_root, config)` -- conventionally
6
+ reused independently by unrelated classes across unrelated files -- falsely
7
+ triggered "Data Clump". Reproduced live:
8
+ `wup.anomaly_detector.AnomalyDetector.__init__` and
9
+ `wup.testql_monitor.TestQLMonitor.__init__` are two unrelated classes in
10
+ unrelated files, flagged as clumped on `project_root, self, config` purely
11
+ because both happen to take a project root and a config object.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from code2llm.analysis.smells import SmellDetector
17
+ from code2llm.core.models import AnalysisResult, FunctionInfo
18
+
19
+
20
+ def _function(name: str, file: str, args: list[str], line: int = 1) -> FunctionInfo:
21
+ return FunctionInfo(
22
+ name=name, qualified_name=f"{file}::{name}", file=file, line=line, args=args
23
+ )
24
+
25
+
26
+ def test_same_args_across_unrelated_files_is_not_a_data_clump():
27
+ """The core regression: two unrelated classes in unrelated files each
28
+ taking (self, project_root, config) must NOT be flagged."""
29
+ result = AnalysisResult()
30
+ result.functions["a::AnomalyDetector.__init__"] = _function(
31
+ "AnomalyDetector.__init__", "wup/anomaly_detector.py", ["self", "project_root", "config"]
32
+ )
33
+ result.functions["b::TestQLMonitor.__init__"] = _function(
34
+ "TestQLMonitor.__init__", "wup/testql_monitor.py", ["self", "project_root", "config"]
35
+ )
36
+
37
+ smells = SmellDetector(result)._detect_data_clumps()
38
+
39
+ assert smells == []
40
+
41
+
42
+ def test_genuine_same_file_data_clump_still_detected():
43
+ """Two methods in the SAME file sharing a 3+ arg signature is the real
44
+ smell and must still be caught."""
45
+ result = AnalysisResult()
46
+ file = "core/session.py"
47
+ result.functions["core.session::start"] = _function(
48
+ "start", file, ["self", "user_id", "token", "scope"]
49
+ )
50
+ result.functions["core.session::refresh"] = _function(
51
+ "refresh", file, ["self", "user_id", "token", "scope"]
52
+ )
53
+
54
+ smells = SmellDetector(result)._detect_data_clumps()
55
+
56
+ assert len(smells) == 2
57
+ assert all(s.name == "Data Clump: self, user_id, token, scope"
58
+ or set(s.context["clump"]) == {"self", "user_id", "token", "scope"}
59
+ for s in smells)
60
+ assert all(s.file == file for s in smells)
61
+
62
+
63
+ def test_single_function_with_matching_args_in_other_file_not_flagged():
64
+ """Only one function per file matches the arg-set -- no clump within
65
+ either file, and cross-file coincidence must not count."""
66
+ result = AnalysisResult()
67
+ result.functions["a::f"] = _function("f", "a.py", ["self", "x", "y", "z"])
68
+ result.functions["b::g"] = _function("g", "b.py", ["self", "x", "y", "z"])
69
+
70
+ smells = SmellDetector(result)._detect_data_clumps()
71
+
72
+ assert smells == []
73
+
74
+
75
+ def test_fewer_than_three_args_never_counted():
76
+ result = AnalysisResult()
77
+ file = "core/session.py"
78
+ result.functions["core.session::start"] = _function("start", file, ["self", "user_id"])
79
+ result.functions["core.session::refresh"] = _function("refresh", file, ["self", "user_id"])
80
+
81
+ smells = SmellDetector(result)._detect_data_clumps()
82
+
83
+ assert smells == []
code2llm-0.5.172/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.5.172
File without changes
File without changes
File without changes
File without changes