code2llm 0.5.183__tar.gz → 0.5.185__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 (233) hide show
  1. {code2llm-0.5.183/code2llm.egg-info → code2llm-0.5.185}/PKG-INFO +2 -1
  2. {code2llm-0.5.183 → code2llm-0.5.185}/README.md +1 -0
  3. code2llm-0.5.185/VERSION +1 -0
  4. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/__init__.py +1 -1
  5. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/smell_engine.py +11 -6
  6. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_exports/formats.py +11 -0
  7. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_exports/orchestrator_constants.py +3 -0
  8. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_parser.py +1 -1
  9. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/__init__.py +8 -0
  10. code2llm-0.5.185/code2llm/exporters/projections_exporter.py +139 -0
  11. {code2llm-0.5.183 → code2llm-0.5.185/code2llm.egg-info}/PKG-INFO +2 -1
  12. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm.egg-info/SOURCES.txt +3 -0
  13. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm.egg-info/top_level.txt +0 -1
  14. {code2llm-0.5.183 → code2llm-0.5.185}/pyproject.toml +1 -1
  15. {code2llm-0.5.183 → code2llm-0.5.185}/setup.py +1 -1
  16. code2llm-0.5.185/tests/test_analysis_env.py +46 -0
  17. code2llm-0.5.185/tests/test_projections_exporter.py +130 -0
  18. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_prompt_engine.py +2 -2
  19. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_smells_data_clumps.py +51 -0
  20. code2llm-0.5.183/VERSION +0 -1
  21. {code2llm-0.5.183 → code2llm-0.5.185}/LICENSE +0 -0
  22. {code2llm-0.5.183 → code2llm-0.5.185}/MANIFEST.in +0 -0
  23. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/__main__.py +0 -0
  24. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/__init__.py +0 -0
  25. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/_data_impl.py +0 -0
  26. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/call_graph.py +0 -0
  27. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/call_graph_engine.py +0 -0
  28. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/cfg.py +0 -0
  29. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/complexity.py +0 -0
  30. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/coupling.py +0 -0
  31. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/data_analysis.py +0 -0
  32. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/dfg.py +0 -0
  33. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/native_bridge.py +0 -0
  34. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/pipeline_classifier.py +0 -0
  35. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/pipeline_detector.py +0 -0
  36. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/pipeline_resolver.py +0 -0
  37. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/side_effects.py +0 -0
  38. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/smells.py +0 -0
  39. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/type_inference.py +0 -0
  40. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/utils/__init__.py +0 -0
  41. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/analysis/utils/ast_helpers.py +0 -0
  42. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/api.py +0 -0
  43. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/autoupdate.py +0 -0
  44. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli.py +0 -0
  45. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_analysis.py +0 -0
  46. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_commands.py +0 -0
  47. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_exports/__init__.py +0 -0
  48. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_exports/code2logic.py +0 -0
  49. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_exports/orchestrator.py +0 -0
  50. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_exports/orchestrator_chunked.py +0 -0
  51. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_exports/orchestrator_handlers.py +0 -0
  52. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/cli_exports/prompt.py +0 -0
  53. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/__init__.py +0 -0
  54. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/analyzer.py +0 -0
  55. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/ast_registry.py +0 -0
  56. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/config.py +0 -0
  57. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/export_pipeline.py +0 -0
  58. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/file_analyzer.py +0 -0
  59. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/file_cache.py +0 -0
  60. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/file_filter.py +0 -0
  61. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/gitignore.py +0 -0
  62. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/incremental.py +0 -0
  63. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/__init__.py +0 -0
  64. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/_c_parser.py +0 -0
  65. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/_calls.py +0 -0
  66. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/_complexity.py +0 -0
  67. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/base.py +0 -0
  68. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/cpp.py +0 -0
  69. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/csharp.py +0 -0
  70. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/generic.py +0 -0
  71. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/go_lang.py +0 -0
  72. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/java.py +0 -0
  73. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/js_analysis.py +0 -0
  74. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/php.py +0 -0
  75. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/ruby.py +0 -0
  76. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/rust.py +0 -0
  77. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/ts_extractors.py +0 -0
  78. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/ts_parser.py +0 -0
  79. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/lang/typescript.py +0 -0
  80. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/large_repo.py +0 -0
  81. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/models.py +0 -0
  82. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/persistent_cache.py +0 -0
  83. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/refactoring.py +0 -0
  84. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/repo_files.py +0 -0
  85. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/source_classifier.py +0 -0
  86. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/streaming/__init__.py +0 -0
  87. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/streaming/cache.py +0 -0
  88. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/streaming/incremental.py +0 -0
  89. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/streaming/prioritizer.py +0 -0
  90. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/streaming/scanner.py +0 -0
  91. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/streaming/strategies.py +0 -0
  92. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/streaming_analyzer.py +0 -0
  93. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/core/toon_size_manager.py +0 -0
  94. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/article_view.py +0 -0
  95. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/base.py +0 -0
  96. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/context_exporter.py +0 -0
  97. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/context_view.py +0 -0
  98. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/dashboard_data.py +0 -0
  99. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/dashboard_renderer.py +0 -0
  100. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/evolution/__init__.py +0 -0
  101. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/evolution/computation.py +0 -0
  102. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/evolution/constants.py +0 -0
  103. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/evolution/exclusion.py +0 -0
  104. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/evolution/render.py +0 -0
  105. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/evolution/yaml_export.py +0 -0
  106. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/evolution_exporter.py +0 -0
  107. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/flow_constants.py +0 -0
  108. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/flow_exporter.py +0 -0
  109. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/flow_renderer.py +0 -0
  110. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/html_dashboard.py +0 -0
  111. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/index_generator/__init__.py +0 -0
  112. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/index_generator/renderer.py +0 -0
  113. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/index_generator/scanner.py +0 -0
  114. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/index_generator.py +0 -0
  115. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/json_exporter.py +0 -0
  116. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/llm_exporter.py +0 -0
  117. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/map/__init__.py +0 -0
  118. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/map/alerts.py +0 -0
  119. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/map/details.py +0 -0
  120. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/map/header.py +0 -0
  121. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/map/module_list.py +0 -0
  122. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/map/utils.py +0 -0
  123. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/map/yaml_export.py +0 -0
  124. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/map_exporter.py +0 -0
  125. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/mermaid/__init__.py +0 -0
  126. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/mermaid/calls.py +0 -0
  127. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/mermaid/classic.py +0 -0
  128. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/mermaid/compact.py +0 -0
  129. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/mermaid/flow_compact.py +0 -0
  130. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/mermaid/flow_detailed.py +0 -0
  131. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/mermaid/flow_full.py +0 -0
  132. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/mermaid/utils.py +0 -0
  133. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/mermaid_exporter.py +0 -0
  134. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
  135. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/planfile_tickets.py +0 -0
  136. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/project_yaml/__init__.py +0 -0
  137. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/project_yaml/constants.py +0 -0
  138. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/project_yaml/core.py +0 -0
  139. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/project_yaml/evolution.py +0 -0
  140. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/project_yaml/health.py +0 -0
  141. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/project_yaml/hotspots.py +0 -0
  142. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/project_yaml/modules.py +0 -0
  143. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/project_yaml_exporter.py +0 -0
  144. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/readme/__init__.py +0 -0
  145. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/readme/content.py +0 -0
  146. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/readme/files.py +0 -0
  147. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/readme/insights.py +0 -0
  148. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/readme/sections.py +0 -0
  149. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/readme_exporter.py +0 -0
  150. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/report_generators.py +0 -0
  151. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/__init__.py +0 -0
  152. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/_render_coupling_helpers.py +0 -0
  153. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/_render_section_helpers.py +0 -0
  154. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/constants.py +0 -0
  155. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/helpers.py +0 -0
  156. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/metrics.py +0 -0
  157. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/metrics_core.py +0 -0
  158. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/metrics_duplicates.py +0 -0
  159. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/metrics_health.py +0 -0
  160. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/module_detail.py +0 -0
  161. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon/renderer.py +0 -0
  162. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon.py +0 -0
  163. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/toon_view.py +0 -0
  164. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/validate_project.py +0 -0
  165. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/exporters/yaml_exporter.py +0 -0
  166. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/__init__.py +0 -0
  167. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/_utils.py +0 -0
  168. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/llm_flow/__init__.py +0 -0
  169. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/llm_flow/analysis.py +0 -0
  170. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/llm_flow/cli.py +0 -0
  171. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/llm_flow/generator.py +0 -0
  172. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/llm_flow/nodes.py +0 -0
  173. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/llm_flow/parsing.py +0 -0
  174. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/llm_flow/utils.py +0 -0
  175. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/llm_flow.py +0 -0
  176. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/llm_task.py +0 -0
  177. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/mermaid/__init__.py +0 -0
  178. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/mermaid/fix.py +0 -0
  179. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/mermaid/png.py +0 -0
  180. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/mermaid/validation.py +0 -0
  181. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/generators/mermaid.py +0 -0
  182. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/nlp/__init__.py +0 -0
  183. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/nlp/config.py +0 -0
  184. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/nlp/entity_resolution.py +0 -0
  185. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/nlp/intent_matching.py +0 -0
  186. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/nlp/normalization.py +0 -0
  187. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/nlp/pipeline.py +0 -0
  188. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/parsers/toon_parser.py +0 -0
  189. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/patterns/__init__.py +0 -0
  190. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/patterns/detector.py +0 -0
  191. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/refactor/__init__.py +0 -0
  192. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/refactor/prompt_engine.py +0 -0
  193. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/templates/extract_method.md +0 -0
  194. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm/templates/move_method.md +0 -0
  195. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm.egg-info/dependency_links.txt +0 -0
  196. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm.egg-info/entry_points.txt +0 -0
  197. {code2llm-0.5.183 → code2llm-0.5.185}/code2llm.egg-info/requires.txt +0 -0
  198. {code2llm-0.5.183 → code2llm-0.5.185}/setup.cfg +0 -0
  199. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_advanced_analysis.py +0 -0
  200. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_analysis_cache_version.py +0 -0
  201. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_analyzer.py +0 -0
  202. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_cache_invalidation_e2e.py +0 -0
  203. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_call_graph_engine.py +0 -0
  204. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_calls_toon_export.py +0 -0
  205. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_complexity.py +0 -0
  206. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_declarative_collection.py +0 -0
  207. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_deep_analysis.py +0 -0
  208. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_dup_groups.py +0 -0
  209. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_edge_cases.py +0 -0
  210. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_export_cache_flags.py +0 -0
  211. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_export_cache_ownership.py +0 -0
  212. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_export_profile_contract.py +0 -0
  213. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_file_analyzer_tagging.py +0 -0
  214. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_flow_exporter.py +0 -0
  215. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_format_quality.py +0 -0
  216. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_js_complexity_boundaries.py +0 -0
  217. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_mermaid_png.py +0 -0
  218. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_multilanguage_e2e.py +0 -0
  219. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_native_rust_bridge.py +0 -0
  220. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_nlp_pipeline.py +0 -0
  221. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_nonpython_cc_calls.py +0 -0
  222. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_orchestrator_cache_mtime.py +0 -0
  223. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_perf_exporters.py +0 -0
  224. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_persistent_cache.py +0 -0
  225. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_pipeline_detector.py +0 -0
  226. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_planfile_tickets_exporter.py +0 -0
  227. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_project_toon_export.py +0 -0
  228. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_prompt_txt.py +0 -0
  229. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_refactoring_engine.py +0 -0
  230. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_smell_engine.py +0 -0
  231. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_smells_shotgun_surgery.py +0 -0
  232. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_toon_v2.py +0 -0
  233. {code2llm-0.5.183 → code2llm-0.5.185}/tests/test_version_parity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2llm
3
- Version: 0.5.183
3
+ Version: 0.5.185
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
@@ -101,6 +101,7 @@ When you run `code2llm ./ -f all`, the following files are created:
101
101
  |------|--------|---------|--------------|
102
102
  | `evolution.toon.yaml` | **YAML** | **📋 Refactoring queue** - Prioritized improvements | 0 refactoring actions needed |
103
103
  | `map.toon.yaml` | **YAML** | **🗺️ Structural map + project header** - Modules, imports, exports, signatures, stats, alerts, hotspots, trend | Project architecture overview |
104
+ | `projections.json` | **JSON** | **🔎 Bounded symbol catalog** (`code2llm.projection/v1`) for intent retrieval / external indexers — deterministic, digested, capped at 2048 records | Reuse contract without analyzer internals |
104
105
 
105
106
  ### 🤖 LLM-Ready Documentation
106
107
 
@@ -26,6 +26,7 @@ When you run `code2llm ./ -f all`, the following files are created:
26
26
  |------|--------|---------|--------------|
27
27
  | `evolution.toon.yaml` | **YAML** | **📋 Refactoring queue** - Prioritized improvements | 0 refactoring actions needed |
28
28
  | `map.toon.yaml` | **YAML** | **🗺️ Structural map + project header** - Modules, imports, exports, signatures, stats, alerts, hotspots, trend | Project architecture overview |
29
+ | `projections.json` | **JSON** | **🔎 Bounded symbol catalog** (`code2llm.projection/v1`) for intent retrieval / external indexers — deterministic, digested, capped at 2048 records | Reuse contract without analyzer internals |
29
30
 
30
31
  ### 🤖 LLM-Ready Documentation
31
32
 
@@ -0,0 +1 @@
1
+ 0.5.185
@@ -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.183"
11
+ __version__ = "0.5.185"
12
12
  __author__ = "STTS Project"
13
13
 
14
14
  # Core analysis components (lightweight, always needed)
@@ -212,22 +212,27 @@ def detect_data_clumps(
212
212
 
213
213
  for (file, args), funcs in arg_sets.items():
214
214
  if len(funcs) >= 2:
215
- for func_name in funcs:
215
+ # Names feed exported ticket identities, so hash/discovery order
216
+ # must not turn the same finding into a new ticket after restart.
217
+ ordered_args = sorted(args)
218
+ ordered_funcs = sorted(funcs)
219
+ arg_names = ", ".join(ordered_args)
220
+ for func_name in ordered_funcs:
216
221
  func_info = functions[func_name]
217
222
  smells.append(
218
223
  CodeSmell(
219
- name=f"Data Clump: {', '.join(args)}",
224
+ name=f"Data Clump: {arg_names}",
220
225
  type="data_clump",
221
226
  file=func_info.file,
222
227
  line=func_info.line,
223
228
  severity=0.6,
224
229
  description=(
225
- f"Arguments ({', '.join(args)}) are used together"
226
- f" in multiple functions: {', '.join(funcs)}."
230
+ f"Arguments ({arg_names}) are used together"
231
+ f" in multiple functions: {', '.join(ordered_funcs)}."
227
232
  ),
228
233
  context={
229
- "clump": list(args),
230
- "related_functions": funcs,
234
+ "clump": ordered_args,
235
+ "related_functions": ordered_funcs,
231
236
  },
232
237
  )
233
238
  )
@@ -30,6 +30,8 @@ from code2llm.exporters import (
30
30
  EvolutionExporter,
31
31
  READMEExporter,
32
32
  ProjectYAMLExporter,
33
+ ProjectionsExporter,
34
+ PROJECTION_FILENAME,
33
35
  ToonViewGenerator,
34
36
  ContextViewGenerator,
35
37
  ArticleViewGenerator,
@@ -195,6 +197,15 @@ def _export_simple_formats(args, result, output_dir: Path, formats):
195
197
  if args.verbose:
196
198
  print(f" - JSON: {filepath}")
197
199
 
200
+ if "projections" in formats:
201
+ exporter = ProjectionsExporter()
202
+ filepath = output_dir / PROJECTION_FILENAME
203
+ t0 = time.monotonic()
204
+ exporter.export(result, str(filepath))
205
+ elapsed = time.monotonic() - t0
206
+ if args.verbose:
207
+ print(f" - PROJECTIONS: {filepath} ({elapsed:.2f}s)")
208
+
198
209
 
199
210
  def _export_yaml(args, result, output_dir: Path):
200
211
  """Export YAML with optional split/separated modes."""
@@ -20,6 +20,7 @@ FORMAT_FILENAMES: Dict[str, str] = {
20
20
  "planfile": "planfile-tickets.yaml",
21
21
  "readme": "README.md",
22
22
  "project-yaml": "project.yaml",
23
+ "projections": "projections.json",
23
24
  }
24
25
 
25
26
  # Files produced per format in dry-run preview
@@ -33,6 +34,7 @@ FORMAT_DRY_RUN_FILES: Dict[str, List[str]] = {
33
34
  "yaml": ["analysis.yaml"],
34
35
  "json": ["analysis.json"],
35
36
  "readme": ["README.md"],
37
+ "projections": ["projections.json"],
36
38
  }
37
39
 
38
40
  # Human-readable labels
@@ -47,6 +49,7 @@ FORMAT_LABELS: Dict[str, str] = {
47
49
  "planfile": "PLANFILE (ticket suggestions)",
48
50
  "readme": "README (documentation)",
49
51
  "project-yaml": "PROJECT-YAML (single source of truth)",
52
+ "projections": "PROJECTIONS (intent retrieval)",
50
53
  }
51
54
 
52
55
  # ---------------------------------------------------------------------------
@@ -97,7 +97,7 @@ Strategy Options (--strategy):
97
97
  default="toon",
98
98
  help=(
99
99
  "Output formats: toon,map,flow,context,code2logic,yaml,json,mermaid,"
100
- "evolution,calls,png,project-yaml,planfile,all (default: toon)"
100
+ "evolution,calls,png,project-yaml,planfile,projections,all (default: toon)"
101
101
  ),
102
102
  )
103
103
 
@@ -9,6 +9,7 @@ Available exporters:
9
9
  - READMEExporter → README.md (documentation of all files)
10
10
  - YAMLExporter → analysis.yaml
11
11
  - JSONExporter → analysis.json
12
+ - ProjectionsExporter → projections.json (bounded symbol catalog for intent retrieval)
12
13
  - MermaidExporter → *.mmd
13
14
 
14
15
  Registry API:
@@ -33,6 +34,13 @@ from .base import (
33
34
 
34
35
  # Standard exporters (auto-registered via @export_format decorator)
35
36
  from .json_exporter import JSONExporter
37
+ from .projections_exporter import (
38
+ MAX_RECORDS,
39
+ PROJECTION_FILENAME,
40
+ PROJECTION_SCHEMA,
41
+ ProjectionsExporter,
42
+ build_projection,
43
+ )
36
44
  from .yaml_exporter import YAMLExporter
37
45
  from .mermaid_exporter import MermaidExporter
38
46
  from .context_exporter import ContextExporter
@@ -0,0 +1,139 @@
1
+ """Bounded, versioned projections of code2llm analysis for intent retrieval.
2
+
3
+ Emits ``projections.json``: a deterministic, capped catalog of source
4
+ symbols with provenance (kind, symbol, file, line) and content digests.
5
+ Downstream consumers (e.g. semcod/search) index it without parsing the
6
+ full analysis or importing analyzer internals — the reuse contract is
7
+ this artifact, not the Python API (PLF-035 / issue #25).
8
+
9
+ Contract invariants:
10
+ - ``schema`` pins the record shape; ``producer.version`` pins the
11
+ analyzer. Bumping either invalidates downstream caches by equality
12
+ of the embedded digests alone.
13
+ - Byte-deterministic for identical analysis results: canonical JSON
14
+ (sorted keys), records sorted by (file, line, kind, symbol), no
15
+ timestamps inside records. ``generated_at`` is optional and never
16
+ contributes to the digest.
17
+ - Bounded: at most ``MAX_RECORDS`` records; symbol and file strings
18
+ truncated at ``FIELD_LIMIT`` characters; ``stats.truncated`` flags
19
+ an overflow so consumers can refuse silently-stale catalogs.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import hashlib
25
+ import json
26
+ from pathlib import Path
27
+ from typing import Any, Dict, Iterable, List, Optional
28
+
29
+ from code2llm import __version__
30
+
31
+ from .base import BaseExporter, export_format
32
+
33
+ PROJECTION_SCHEMA = "code2llm.projection/v1"
34
+ PROJECTION_FILENAME = "projections.json"
35
+ MAX_RECORDS = 2048
36
+ FIELD_LIMIT = 200
37
+
38
+ _KIND_ORDER = {"module": 0, "class": 1, "function": 2}
39
+
40
+
41
+ def _canonical(obj: Any) -> str:
42
+ return json.dumps(obj, sort_keys=True, ensure_ascii=False, separators=(",", ":"))
43
+
44
+
45
+ def _digest(obj: Any) -> str:
46
+ return "sha256:" + hashlib.sha256(_canonical(obj).encode("utf-8")).hexdigest()
47
+
48
+
49
+ def _bounded(value: str) -> str:
50
+ return value[:FIELD_LIMIT]
51
+
52
+
53
+ def _record(kind: str, symbol: str, file: str, line: Optional[int]) -> Dict[str, Any]:
54
+ record: Dict[str, Any] = {"kind": kind, "symbol": _bounded(symbol), "file": _bounded(file)}
55
+ if line is not None:
56
+ record["line"] = int(line)
57
+ record["digest"] = _digest(record)
58
+ return record
59
+
60
+
61
+ def _items(value: Any) -> Iterable[Any]:
62
+ """Yield info objects from either a Dict[str, Info] or a list."""
63
+ if isinstance(value, dict):
64
+ return value.values()
65
+ return value or []
66
+
67
+
68
+ def _iter_records(result: Any) -> Iterable[Dict[str, Any]]:
69
+ modules = _items(getattr(result, "modules", None))
70
+ classes = _items(getattr(result, "classes", None))
71
+ functions = _items(getattr(result, "functions", None))
72
+ for module in modules:
73
+ yield _record("module", module.name, module.file, None)
74
+ for cls in classes:
75
+ yield _record("class", cls.qualified_name or cls.name, cls.file, cls.line)
76
+ for func in functions:
77
+ yield _record("function", func.qualified_name or func.name, func.file, func.line)
78
+
79
+
80
+ def build_projection(
81
+ result: Any,
82
+ *,
83
+ max_records: int = MAX_RECORDS,
84
+ generated_at: Optional[str] = None,
85
+ ) -> Dict[str, Any]:
86
+ """Build the projection document from an :class:`AnalysisResult`.
87
+
88
+ Duck-typed on purpose: only ``modules``/``classes``/``functions``
89
+ (``name``/``qualified_name``/``file``/``line``) are read, so tests and
90
+ alternative frontends can feed lightweight structures.
91
+ """
92
+ records: List[Dict[str, Any]] = list(_iter_records(result))
93
+ records.sort(key=lambda r: (r["file"], r.get("line", 0), _KIND_ORDER[r["kind"]], r["symbol"]))
94
+ truncated = len(records) > max_records
95
+ if truncated:
96
+ records = records[:max_records]
97
+
98
+ stats = getattr(result, "stats", None) or {}
99
+ document: Dict[str, Any] = {
100
+ "schema": PROJECTION_SCHEMA,
101
+ "producer": {"tool": "code2llm", "version": __version__},
102
+ "project": {
103
+ "path": _bounded(str(getattr(result, "project_path", "") or "")),
104
+ "analysis_mode": str(getattr(result, "analysis_mode", "") or ""),
105
+ },
106
+ "stats": {
107
+ "files": int(stats.get("files", 0) or 0),
108
+ "lines": int(stats.get("lines", 0) or 0),
109
+ "symbols": len(records),
110
+ "truncated": truncated,
111
+ },
112
+ "records": records,
113
+ "digest": _digest(records),
114
+ }
115
+ if generated_at:
116
+ document["generated_at"] = generated_at
117
+ return document
118
+
119
+
120
+ @export_format(
121
+ "projections",
122
+ description="Bounded symbol projections for intent retrieval (projections.json)",
123
+ extension=".json",
124
+ )
125
+ class ProjectionsExporter(BaseExporter):
126
+ """Export the deterministic projections catalog."""
127
+
128
+ def export(
129
+ self,
130
+ result: Any,
131
+ output_path: str,
132
+ generated_at: Optional[str] = None,
133
+ **kwargs: Any,
134
+ ) -> Optional[Path]:
135
+ document = build_projection(result, generated_at=generated_at)
136
+ path = self._ensure_dir(output_path)
137
+ payload = json.dumps(document, sort_keys=True, ensure_ascii=False, indent=1) + "\n"
138
+ self._write_text(path, payload)
139
+ return path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2llm
3
- Version: 0.5.183
3
+ Version: 0.5.185
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
@@ -101,6 +101,7 @@ When you run `code2llm ./ -f all`, the following files are created:
101
101
  |------|--------|---------|--------------|
102
102
  | `evolution.toon.yaml` | **YAML** | **📋 Refactoring queue** - Prioritized improvements | 0 refactoring actions needed |
103
103
  | `map.toon.yaml` | **YAML** | **🗺️ Structural map + project header** - Modules, imports, exports, signatures, stats, alerts, hotspots, trend | Project architecture overview |
104
+ | `projections.json` | **JSON** | **🔎 Bounded symbol catalog** (`code2llm.projection/v1`) for intent retrieval / external indexers — deterministic, digested, capped at 2048 records | Reuse contract without analyzer internals |
104
105
 
105
106
  ### 🤖 LLM-Ready Documentation
106
107
 
@@ -106,6 +106,7 @@ code2llm/exporters/mermaid_exporter.py
106
106
  code2llm/exporters/mermaid_flow_helpers.py
107
107
  code2llm/exporters/planfile_tickets.py
108
108
  code2llm/exporters/project_yaml_exporter.py
109
+ code2llm/exporters/projections_exporter.py
109
110
  code2llm/exporters/readme_exporter.py
110
111
  code2llm/exporters/report_generators.py
111
112
  code2llm/exporters/toon.py
@@ -190,6 +191,7 @@ code2llm/templates/extract_method.md
190
191
  code2llm/templates/move_method.md
191
192
  tests/test_advanced_analysis.py
192
193
  tests/test_analysis_cache_version.py
194
+ tests/test_analysis_env.py
193
195
  tests/test_analyzer.py
194
196
  tests/test_cache_invalidation_e2e.py
195
197
  tests/test_call_graph_engine.py
@@ -217,6 +219,7 @@ tests/test_persistent_cache.py
217
219
  tests/test_pipeline_detector.py
218
220
  tests/test_planfile_tickets_exporter.py
219
221
  tests/test_project_toon_export.py
222
+ tests/test_projections_exporter.py
220
223
  tests/test_prompt_engine.py
221
224
  tests/test_prompt_txt.py
222
225
  tests/test_refactoring_engine.py
@@ -1,3 +1,2 @@
1
1
  code2llm
2
- code2llm_output
3
2
  code2llm_part2
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code2llm"
7
- version = "0.5.183"
7
+ version = "0.5.185"
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"
@@ -25,7 +25,7 @@ def read_readme():
25
25
 
26
26
  setup(
27
27
  name="code2llm",
28
- version="0.5.183",
28
+ version="0.5.185",
29
29
  description="Python code flow analysis tool - CFG, DFG, and call graph extraction",
30
30
  long_description=read_readme(),
31
31
  long_description_content_type="text/markdown",
@@ -0,0 +1,46 @@
1
+ """Guardrails for the pinned Python 3.12 analysis environment (PLF-036 / #26).
2
+
3
+ The analyzer chain clickmd/code2logic -> pfix -> pipreqs cannot run on the
4
+ Python 3.13 application venv: pipreqs 0.5.0 declares Requires-Python
5
+ >=3.8.1,<3.13 and no newer release exists. These tests keep the analysis
6
+ environment script and its documentation discoverable and pinned.
7
+ """
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ ROOT = Path(__file__).resolve().parents[1]
13
+ SCRIPT = ROOT / "scripts" / "analysis-env.sh"
14
+ DOCS = ROOT / "docs" / "dependencies.md"
15
+
16
+
17
+ def test_analysis_env_script_exists_and_is_executable():
18
+ assert SCRIPT.is_file()
19
+ assert SCRIPT.stat().st_mode & 0o111, "analysis-env.sh must be executable"
20
+
21
+
22
+ def test_analysis_env_script_pins_python_312_and_locked_sync():
23
+ content = SCRIPT.read_text(encoding="utf-8")
24
+ assert 'ANALYSIS_PYTHON="${ANALYSIS_PYTHON:-3.12}"' in content
25
+ assert "uv sync --locked" in content
26
+ # The application venv must never be the target of the sync.
27
+ assert "UV_PROJECT_ENVIRONMENT" in content
28
+ assert "--all-extras" in content
29
+
30
+
31
+ @pytest.mark.parametrize(
32
+ ("fragment",),
33
+ [
34
+ ("pipreqs",),
35
+ (">=3.8.1,<3.13",),
36
+ ("scripts/analysis-env.sh",),
37
+ (".venv-analysis",),
38
+ ],
39
+ )
40
+ def test_dependency_docs_explain_the_analysis_environment(fragment):
41
+ assert fragment in DOCS.read_text(encoding="utf-8")
42
+
43
+
44
+ def test_gitignore_covers_analysis_environment():
45
+ ignores = (ROOT / ".gitignore").read_text(encoding="utf-8").splitlines()
46
+ assert ".venv-analysis/" in ignores
@@ -0,0 +1,130 @@
1
+ """Contract tests for the bounded intent-retrieval projection (PLF-035 / #25).
2
+
3
+ The projection is a reuse contract consumed by external indexers
4
+ (semcod/search): determinism, provenance, bounds and version stamping
5
+ are load-bearing, not incidental.
6
+ """
7
+ from pathlib import Path
8
+ from types import SimpleNamespace
9
+
10
+ import pytest
11
+
12
+ from code2llm import __version__
13
+ from code2llm.exporters.projections_exporter import (
14
+ FIELD_LIMIT,
15
+ MAX_RECORDS,
16
+ PROJECTION_SCHEMA,
17
+ ProjectionsExporter,
18
+ build_projection,
19
+ )
20
+
21
+
22
+ def _fake_result(modules=(), classes=(), functions=(), stats=None, path="proj", mode="standard"):
23
+ def mod(name, file):
24
+ return SimpleNamespace(name=name, file=file)
25
+
26
+ def cls(name, file, line):
27
+ return SimpleNamespace(name=name, qualified_name=f"mod.{name}", file=file, line=line)
28
+
29
+ def fn(name, file, line):
30
+ return SimpleNamespace(name=name, qualified_name=f"mod.{name}", file=file, line=line)
31
+
32
+ return SimpleNamespace(
33
+ modules=[mod(*m) for m in modules],
34
+ classes=[cls(*c) for c in classes],
35
+ functions=[fn(*f) for f in functions],
36
+ stats=stats or {"files": 3, "lines": 100},
37
+ project_path=path,
38
+ analysis_mode=mode,
39
+ )
40
+
41
+
42
+ def test_schema_and_producer_version_are_pinned():
43
+ doc = build_projection(_fake_result())
44
+ assert doc["schema"] == PROJECTION_SCHEMA
45
+ assert doc["producer"] == {"tool": "code2llm", "version": __version__}
46
+
47
+
48
+ def test_records_carry_source_and_symbol_provenance():
49
+ doc = build_projection(
50
+ _fake_result(
51
+ modules=[("core", "pkg/core/__init__.py")],
52
+ classes=[("Engine", "pkg/core/engine.py", 12)],
53
+ functions=[("run", "pkg/core/engine.py", 40)],
54
+ )
55
+ )
56
+ by_kind = {r["kind"]: r for r in doc["records"]}
57
+ assert by_kind["module"] == {
58
+ "kind": "module",
59
+ "symbol": "core",
60
+ "file": "pkg/core/__init__.py",
61
+ "digest": by_kind["module"]["digest"],
62
+ }
63
+ assert by_kind["class"]["symbol"] == "mod.Engine"
64
+ assert by_kind["class"]["file"] == "pkg/core/engine.py"
65
+ assert by_kind["class"]["line"] == 12
66
+ assert by_kind["function"]["line"] == 40
67
+ assert all(r["digest"].startswith("sha256:") for r in doc["records"])
68
+
69
+
70
+ def test_deterministic_bytes_and_digest():
71
+ result = _fake_result(
72
+ classes=[("B", "b.py", 2), ("A", "a.py", 1)],
73
+ functions=[("z", "a.py", 9), ("a", "b.py", 1)],
74
+ )
75
+ first = build_projection(result)
76
+ second = build_projection(result)
77
+ assert first == second
78
+ assert first["digest"] == second["digest"]
79
+ # Record order is canonical: (file, line, kind, symbol).
80
+ order = [(r["file"], r.get("line", 0), r["kind"], r["symbol"]) for r in first["records"]]
81
+ assert order == sorted(order)
82
+ # generated_at never leaks into the digest.
83
+ stamped = build_projection(result, generated_at="2026-10-23T00:00:00Z")
84
+ assert stamped["digest"] == first["digest"]
85
+
86
+
87
+ def test_bounds_cap_records_and_flag_truncation():
88
+ functions = [(f"f{i}", "m.py", i + 1) for i in range(MAX_RECORDS + 5)]
89
+ doc = build_projection(_fake_result(functions=functions))
90
+ assert len(doc["records"]) == MAX_RECORDS
91
+ assert doc["stats"]["truncated"] is True
92
+ assert doc["stats"]["symbols"] == MAX_RECORDS
93
+ small = build_projection(_fake_result(functions=functions[:4]))
94
+ assert small["stats"]["truncated"] is False
95
+
96
+
97
+ def test_long_fields_are_truncated():
98
+ long_name = "x" * (FIELD_LIMIT + 50)
99
+ doc = build_projection(_fake_result(functions=[(long_name, long_name + ".py", 1)]))
100
+ record = doc["records"][0]
101
+ assert len(record["symbol"]) == FIELD_LIMIT
102
+ assert len(record["file"]) == FIELD_LIMIT
103
+
104
+
105
+ def test_exporter_writes_canonical_json(tmp_path: Path):
106
+ result = _fake_result(classes=[("E", "e.py", 3)])
107
+ exporter = ProjectionsExporter()
108
+ out = tmp_path / "projections.json"
109
+ path = exporter.export(result, str(out))
110
+ assert path == out and out.exists()
111
+ body = out.read_text(encoding="utf-8")
112
+ again = exporter.export(result, str(tmp_path / "again.json"))
113
+ assert again.read_text(encoding="utf-8") == body
114
+ assert body.endswith("\n")
115
+
116
+
117
+ def test_registry_lists_projections():
118
+ from code2llm.exporters import EXPORT_REGISTRY, get_exporter
119
+
120
+ assert "projections" in EXPORT_REGISTRY
121
+ assert get_exporter("projections") is ProjectionsExporter
122
+
123
+
124
+ def test_cli_help_mentions_projections(capsys):
125
+ from code2llm.cli_parser import create_parser
126
+
127
+ with pytest.raises(SystemExit) as exc:
128
+ create_parser().parse_args(["--help"])
129
+ assert exc.value.code == 0
130
+ assert "projections" in (capsys.readouterr().out or "")
@@ -17,7 +17,7 @@ def test_tiktoken_truncation():
17
17
 
18
18
  engine = PromptEngine(result)
19
19
  # Mock source context to be very large
20
- engine._get_source_context = lambda f, l: "print('Hello world')\n" * 1000
20
+ engine._get_source_context = lambda f, l, *, max_lines: "print('Hello world')\n" * 1000
21
21
 
22
22
  prompts = engine.generate_prompts()
23
23
  assert len(prompts) == 1
@@ -59,7 +59,7 @@ def test_template_rendering_with_metrics():
59
59
  }
60
60
 
61
61
  engine = PromptEngine(result)
62
- engine._get_source_context = lambda f, l: "def complex_func():\n pass"
62
+ engine._get_source_context = lambda f, l, *, max_lines: "def complex_func():\n pass"
63
63
 
64
64
  prompts = engine.generate_prompts()
65
65
  prompt_content = list(prompts.values())[0]
@@ -81,3 +81,54 @@ def test_fewer_than_three_args_never_counted():
81
81
  smells = SmellDetector(result)._detect_data_clumps()
82
82
 
83
83
  assert smells == []
84
+
85
+
86
+ def test_exported_clump_tickets_are_stable_across_processes(tmp_path):
87
+ """The same finding must not create new ticket keys after a process restart."""
88
+ import json
89
+ import os
90
+ from pathlib import Path
91
+ import subprocess
92
+ import sys
93
+ import textwrap
94
+
95
+ script = textwrap.dedent("""\
96
+ import json
97
+ import sys
98
+ from dataclasses import asdict
99
+ from code2llm.analysis.smells import SmellDetector
100
+ from code2llm.core.models import AnalysisResult, FunctionInfo
101
+ from code2llm.exporters.planfile_tickets import _smell_tickets
102
+
103
+ reverse = sys.argv[1] == "reverse"
104
+ functions = [("create_github_pr", 47), ("create_gitlab_mr", 80)]
105
+ arguments = ["self", "provider", "group", "template"]
106
+ if reverse:
107
+ functions.reverse()
108
+ arguments.reverse()
109
+ result = AnalysisResult(project_path="/fixture")
110
+ for name, line in functions:
111
+ result.functions[name] = FunctionInfo(
112
+ name=name, qualified_name="publish." + name,
113
+ file="/fixture/publish.py", line=line, args=arguments,
114
+ )
115
+ result.smells = SmellDetector(result)._detect_data_clumps()
116
+ tickets = [asdict(ticket) for ticket in _smell_tickets(result)]
117
+ print(json.dumps(sorted(tickets, key=lambda t: t["dedupe_key"]), sort_keys=True))
118
+ """)
119
+ outputs = []
120
+ root = str(Path(__file__).resolve().parents[1])
121
+ for seed, order in [("1", "forward"), ("2", "forward"), ("42", "reverse")]:
122
+ env = os.environ.copy()
123
+ env["PYTHONHASHSEED"] = seed
124
+ env["PYTHONPATH"] = root + os.pathsep + env.get("PYTHONPATH", "")
125
+ child = subprocess.run(
126
+ [sys.executable, "-c", script, order],
127
+ cwd=tmp_path, env=env, check=True, capture_output=True,
128
+ text=True, timeout=30,
129
+ )
130
+ tickets = json.loads(child.stdout)
131
+ assert len(tickets) == 2
132
+ assert len({ticket["dedupe_key"] for ticket in tickets}) == 2
133
+ outputs.append(tickets)
134
+ assert outputs[0] == outputs[1] == outputs[2]
code2llm-0.5.183/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.5.183
File without changes
File without changes