code2llm 0.5.145__tar.gz → 0.5.147__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 (198) hide show
  1. {code2llm-0.5.145/code2llm.egg-info → code2llm-0.5.147}/PKG-INFO +9 -9
  2. {code2llm-0.5.145 → code2llm-0.5.147}/README.md +8 -8
  3. code2llm-0.5.147/VERSION +1 -0
  4. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/__init__.py +1 -1
  5. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/smells.py +11 -7
  6. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_exports/formats.py +26 -6
  7. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_exports/orchestrator.py +52 -1
  8. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_exports/orchestrator_handlers.py +36 -11
  9. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_exports/prompt.py +6 -1
  10. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/evolution/computation.py +32 -13
  11. code2llm-0.5.147/code2llm/exporters/evolution/exclusion.py +17 -0
  12. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/flow_constants.py +5 -7
  13. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/project_yaml/core.py +1 -1
  14. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/toon/helpers.py +41 -9
  15. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/toon/metrics.py +1 -1
  16. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/toon/metrics_core.py +49 -57
  17. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/nlp/__init__.py +1 -1
  18. {code2llm-0.5.145 → code2llm-0.5.147/code2llm.egg-info}/PKG-INFO +9 -9
  19. {code2llm-0.5.145 → code2llm-0.5.147}/pyproject.toml +1 -1
  20. code2llm-0.5.145/VERSION +0 -1
  21. code2llm-0.5.145/code2llm/exporters/evolution/exclusion.py +0 -17
  22. {code2llm-0.5.145 → code2llm-0.5.147}/LICENSE +0 -0
  23. {code2llm-0.5.145 → code2llm-0.5.147}/MANIFEST.in +0 -0
  24. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/__main__.py +0 -0
  25. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/__init__.py +0 -0
  26. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/call_graph.py +0 -0
  27. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/cfg.py +0 -0
  28. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/coupling.py +0 -0
  29. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/data_analysis.py +0 -0
  30. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/dfg.py +0 -0
  31. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/pipeline_classifier.py +0 -0
  32. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/pipeline_detector.py +0 -0
  33. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/pipeline_resolver.py +0 -0
  34. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/side_effects.py +0 -0
  35. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/type_inference.py +0 -0
  36. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/utils/__init__.py +0 -0
  37. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/analysis/utils/ast_helpers.py +0 -0
  38. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/api.py +0 -0
  39. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli.py +0 -0
  40. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_analysis.py +0 -0
  41. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_commands.py +0 -0
  42. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_exports/__init__.py +0 -0
  43. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_exports/code2logic.py +0 -0
  44. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_exports/orchestrator_chunked.py +0 -0
  45. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_exports/orchestrator_constants.py +0 -0
  46. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/cli_parser.py +0 -0
  47. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/__init__.py +0 -0
  48. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/analyzer.py +0 -0
  49. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/ast_registry.py +0 -0
  50. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/config.py +0 -0
  51. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/export_pipeline.py +0 -0
  52. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/file_analyzer.py +0 -0
  53. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/file_cache.py +0 -0
  54. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/file_filter.py +0 -0
  55. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/gitignore.py +0 -0
  56. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/incremental.py +0 -0
  57. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/__init__.py +0 -0
  58. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/base.py +0 -0
  59. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/cpp.py +0 -0
  60. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/csharp.py +0 -0
  61. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/generic.py +0 -0
  62. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/go_lang.py +0 -0
  63. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/java.py +0 -0
  64. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/php.py +0 -0
  65. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/ruby.py +0 -0
  66. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/rust.py +0 -0
  67. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/ts_extractors.py +0 -0
  68. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/ts_parser.py +0 -0
  69. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/lang/typescript.py +0 -0
  70. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/large_repo.py +0 -0
  71. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/models.py +0 -0
  72. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/persistent_cache.py +0 -0
  73. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/refactoring.py +0 -0
  74. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/repo_files.py +0 -0
  75. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/streaming/__init__.py +0 -0
  76. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/streaming/cache.py +0 -0
  77. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/streaming/incremental.py +0 -0
  78. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/streaming/prioritizer.py +0 -0
  79. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/streaming/scanner.py +0 -0
  80. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/streaming/strategies.py +0 -0
  81. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/streaming_analyzer.py +0 -0
  82. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/core/toon_size_manager.py +0 -0
  83. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/__init__.py +0 -0
  84. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/article_view.py +0 -0
  85. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/base.py +0 -0
  86. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/context_exporter.py +0 -0
  87. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/context_view.py +0 -0
  88. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/dashboard_data.py +0 -0
  89. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/dashboard_renderer.py +0 -0
  90. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/evolution/__init__.py +0 -0
  91. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/evolution/constants.py +0 -0
  92. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/evolution/render.py +0 -0
  93. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/evolution/yaml_export.py +0 -0
  94. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/evolution_exporter.py +0 -0
  95. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/flow_exporter.py +0 -0
  96. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/flow_renderer.py +0 -0
  97. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/html_dashboard.py +0 -0
  98. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/index_generator/__init__.py +0 -0
  99. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/index_generator/renderer.py +0 -0
  100. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/index_generator/scanner.py +0 -0
  101. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/index_generator.py +0 -0
  102. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/json_exporter.py +0 -0
  103. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/llm_exporter.py +0 -0
  104. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/map/__init__.py +0 -0
  105. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/map/alerts.py +0 -0
  106. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/map/details.py +0 -0
  107. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/map/header.py +0 -0
  108. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/map/module_list.py +0 -0
  109. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/map/utils.py +0 -0
  110. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/map/yaml_export.py +0 -0
  111. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/map_exporter.py +0 -0
  112. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/mermaid/__init__.py +0 -0
  113. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/mermaid/calls.py +0 -0
  114. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/mermaid/classic.py +0 -0
  115. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/mermaid/compact.py +0 -0
  116. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/mermaid/flow_compact.py +0 -0
  117. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/mermaid/flow_detailed.py +0 -0
  118. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/mermaid/flow_full.py +0 -0
  119. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/mermaid/utils.py +0 -0
  120. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/mermaid_exporter.py +0 -0
  121. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
  122. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/project_yaml/__init__.py +0 -0
  123. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/project_yaml/constants.py +0 -0
  124. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/project_yaml/evolution.py +0 -0
  125. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/project_yaml/health.py +0 -0
  126. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/project_yaml/hotspots.py +0 -0
  127. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/project_yaml/modules.py +0 -0
  128. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/project_yaml_exporter.py +0 -0
  129. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/readme/__init__.py +0 -0
  130. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/readme/content.py +0 -0
  131. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/readme/files.py +0 -0
  132. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/readme/insights.py +0 -0
  133. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/readme/sections.py +0 -0
  134. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/readme_exporter.py +0 -0
  135. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/report_generators.py +0 -0
  136. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/toon/__init__.py +0 -0
  137. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/toon/metrics_duplicates.py +0 -0
  138. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/toon/metrics_health.py +0 -0
  139. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/toon/module_detail.py +0 -0
  140. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/toon/renderer.py +0 -0
  141. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/toon.py +0 -0
  142. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/toon_view.py +0 -0
  143. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/validate_project.py +0 -0
  144. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/exporters/yaml_exporter.py +0 -0
  145. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/__init__.py +0 -0
  146. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/_utils.py +0 -0
  147. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/llm_flow/__init__.py +0 -0
  148. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/llm_flow/analysis.py +0 -0
  149. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/llm_flow/cli.py +0 -0
  150. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/llm_flow/generator.py +0 -0
  151. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/llm_flow/nodes.py +0 -0
  152. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/llm_flow/parsing.py +0 -0
  153. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/llm_flow/utils.py +0 -0
  154. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/llm_flow.py +0 -0
  155. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/llm_task.py +0 -0
  156. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/mermaid/__init__.py +0 -0
  157. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/mermaid/fix.py +0 -0
  158. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/mermaid/png.py +0 -0
  159. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/mermaid/validation.py +0 -0
  160. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/generators/mermaid.py +0 -0
  161. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/nlp/config.py +0 -0
  162. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/nlp/entity_resolution.py +0 -0
  163. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/nlp/intent_matching.py +0 -0
  164. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/nlp/normalization.py +0 -0
  165. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/nlp/pipeline.py +0 -0
  166. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/parsers/toon_parser.py +0 -0
  167. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/patterns/__init__.py +0 -0
  168. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/patterns/detector.py +0 -0
  169. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/refactor/__init__.py +0 -0
  170. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm/refactor/prompt_engine.py +0 -0
  171. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm.egg-info/SOURCES.txt +0 -0
  172. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm.egg-info/dependency_links.txt +0 -0
  173. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm.egg-info/entry_points.txt +0 -0
  174. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm.egg-info/requires.txt +0 -0
  175. {code2llm-0.5.145 → code2llm-0.5.147}/code2llm.egg-info/top_level.txt +0 -0
  176. {code2llm-0.5.145 → code2llm-0.5.147}/setup.cfg +0 -0
  177. {code2llm-0.5.145 → code2llm-0.5.147}/setup.py +0 -0
  178. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_advanced_analysis.py +0 -0
  179. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_analyzer.py +0 -0
  180. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_cache_invalidation_e2e.py +0 -0
  181. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_calls_toon_export.py +0 -0
  182. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_declarative_collection.py +0 -0
  183. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_deep_analysis.py +0 -0
  184. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_edge_cases.py +0 -0
  185. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_file_analyzer_tagging.py +0 -0
  186. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_flow_exporter.py +0 -0
  187. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_format_quality.py +0 -0
  188. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_multilanguage_e2e.py +0 -0
  189. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_nlp_pipeline.py +0 -0
  190. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_nonpython_cc_calls.py +0 -0
  191. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_orchestrator_cache_mtime.py +0 -0
  192. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_persistent_cache.py +0 -0
  193. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_pipeline_detector.py +0 -0
  194. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_project_toon_export.py +0 -0
  195. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_prompt_engine.py +0 -0
  196. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_prompt_txt.py +0 -0
  197. {code2llm-0.5.145 → code2llm-0.5.147}/tests/test_refactoring_engine.py +0 -0
  198. {code2llm-0.5.145 → code2llm-0.5.147}/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.145
3
+ Version: 0.5.147
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: STTS Project
@@ -64,20 +64,20 @@ Dynamic: requires-python
64
64
 
65
65
  # code2llm - Generated Analysis Files
66
66
 
67
-
68
67
  ## AI Cost Tracking
69
68
 
70
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.145-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-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-71.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
69
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.147-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
70
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-72.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
72
71
 
73
- - 🤖 **LLM usage:** $7.5000 (197 commits)
74
- - 👤 **Human dev:** ~$7124 (71.2h @ $100/h, 30min dedup)
72
+ - 🤖 **LLM usage:** $7.5000 (199 commits)
73
+ - 👤 **Human dev:** ~$7258 (72.6h @ $100/h, 30min dedup)
75
74
 
76
75
  Generated on 2026-05-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
77
76
 
78
77
  ---
79
78
 
80
79
 
80
+ <!-- generated in 0.00s -->
81
81
 
82
82
  This directory contains the complete analysis of your project generated by `code2llm`. Each file serves a specific purpose for understanding, refactoring, and documenting your codebase.
83
83
 
@@ -408,9 +408,9 @@ code2llm ./ -f yaml --separate-orphans
408
408
 
409
409
  **Generated by**: `code2llm ./ -f all --readme`
410
410
  **Analysis Date**: 2026-05-06
411
- **Total Functions**: 3625
412
- **Total Classes**: 250
413
- **Modules**: 492
411
+ **Total Functions**: 3800
412
+ **Total Classes**: 259
413
+ **Modules**: 497
414
414
 
415
415
  For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
416
416
 
@@ -1,19 +1,19 @@
1
1
  # code2llm - Generated Analysis Files
2
2
 
3
-
4
3
  ## AI Cost Tracking
5
4
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.145-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-71.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
5
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.147-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-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-72.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
7
 
9
- - 🤖 **LLM usage:** $7.5000 (197 commits)
10
- - 👤 **Human dev:** ~$7124 (71.2h @ $100/h, 30min dedup)
8
+ - 🤖 **LLM usage:** $7.5000 (199 commits)
9
+ - 👤 **Human dev:** ~$7258 (72.6h @ $100/h, 30min dedup)
11
10
 
12
11
  Generated on 2026-05-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
12
 
14
13
  ---
15
14
 
16
15
 
16
+ <!-- generated in 0.00s -->
17
17
 
18
18
  This directory contains the complete analysis of your project generated by `code2llm`. Each file serves a specific purpose for understanding, refactoring, and documenting your codebase.
19
19
 
@@ -344,9 +344,9 @@ code2llm ./ -f yaml --separate-orphans
344
344
 
345
345
  **Generated by**: `code2llm ./ -f all --readme`
346
346
  **Analysis Date**: 2026-05-06
347
- **Total Functions**: 3625
348
- **Total Classes**: 250
349
- **Modules**: 492
347
+ **Total Functions**: 3800
348
+ **Total Classes**: 259
349
+ **Modules**: 497
350
350
 
351
351
  For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
352
352
 
@@ -0,0 +1 @@
1
+ 0.5.147
@@ -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.145"
11
+ __version__ = "0.5.147"
12
12
  __author__ = "STTS Project"
13
13
 
14
14
  # Core analysis components (lightweight, always needed)
@@ -1,4 +1,5 @@
1
1
  """Detection of code smells using analysis metrics."""
2
+ from collections import defaultdict
2
3
  from typing import List, Dict, Any
3
4
  from code2llm.core.models import AnalysisResult, CodeSmell
4
5
 
@@ -7,6 +8,10 @@ class SmellDetector:
7
8
 
8
9
  def __init__(self, result: AnalysisResult):
9
10
  self.result = result
11
+ # Pre-index mutations by scope — avoids O(n×m) full scans
12
+ self._mutations_by_scope: Dict[str, list] = defaultdict(list)
13
+ for m in result.mutations:
14
+ self._mutations_by_scope[m.scope].append(m)
10
15
 
11
16
  def detect(self) -> List[CodeSmell]:
12
17
  """Record and return detected code smells."""
@@ -28,7 +33,7 @@ class SmellDetector:
28
33
  for func_name, func_info in self.result.functions.items():
29
34
  metrics = self.result.metrics.get(func_name, {})
30
35
  fan_out = metrics.get('fan_out', 0)
31
- mutation_count = len([m for m in self.result.mutations if m.scope == func_name])
36
+ mutation_count = len(self._mutations_by_scope.get(func_name, []))
32
37
 
33
38
  # Use cyclomatic complexity (now mapped to 'cc' in FunctionInfo.complexity)
34
39
  complexity = func_info.complexity.get('cyclomatic_complexity', 1)
@@ -78,12 +83,11 @@ class SmellDetector:
78
83
  mut_mod = func_name.split('.')[0]
79
84
  foreign_mutations = []
80
85
 
81
- for mutation in self.result.mutations:
82
- if mutation.scope == func_name:
83
- if '.' in mutation.variable:
84
- origin_mod = mutation.variable.split('.')[0]
85
- if origin_mod != mut_mod:
86
- foreign_mutations.append(mutation.variable)
86
+ for mutation in self._mutations_by_scope.get(func_name, []):
87
+ if '.' in mutation.variable:
88
+ origin_mod = mutation.variable.split('.')[0]
89
+ if origin_mod != mut_mod:
90
+ foreign_mutations.append(mutation.variable)
87
91
 
88
92
  if len(set(foreign_mutations)) >= 3:
89
93
  smells.append(CodeSmell(
@@ -2,6 +2,7 @@
2
2
 
3
3
  import os
4
4
  import sys
5
+ import time
5
6
  from pathlib import Path
6
7
  from typing import Optional
7
8
 
@@ -13,6 +14,7 @@ from code2llm.exporters import (
13
14
  ArticleViewGenerator, HTMLDashboardGenerator,
14
15
  load_project_yaml, IndexHTMLGenerator,
15
16
  )
17
+ from .orchestrator import _inject_generation_time as _inject_time
16
18
 
17
19
 
18
20
  def _export_evolution(args, result, output_dir: Path):
@@ -21,9 +23,12 @@ def _export_evolution(args, result, output_dir: Path):
21
23
  return
22
24
  exporter = EvolutionExporter()
23
25
  filepath = output_dir / 'evolution.toon.yaml'
26
+ t0 = time.monotonic()
24
27
  exporter.export(result, str(filepath))
28
+ elapsed = time.monotonic() - t0
29
+ _inject_time(filepath, elapsed)
25
30
  if args.verbose:
26
- print(f" - EVOLUTION (refactoring queue): {filepath}")
31
+ print(f" - EVOLUTION (refactoring queue): {filepath} ({elapsed:.2f}s)")
27
32
 
28
33
 
29
34
  def _export_data_structures(args, result, output_dir: Path):
@@ -43,9 +48,12 @@ def _export_context_fallback(args, result, output_dir: Path, formats: list):
43
48
  return
44
49
  exporter = ContextExporter()
45
50
  filepath = output_dir / 'context.md'
51
+ t0 = time.monotonic()
46
52
  exporter.export(result, str(filepath))
53
+ elapsed = time.monotonic() - t0
54
+ _inject_time(filepath, elapsed)
47
55
  if args.verbose:
48
- print(f" - CONTEXT (LLM narrative): {filepath}")
56
+ print(f" - CONTEXT (LLM narrative): {filepath} ({elapsed:.2f}s)")
49
57
 
50
58
 
51
59
  def _export_readme(args, result, output_dir: Path):
@@ -54,18 +62,24 @@ def _export_readme(args, result, output_dir: Path):
54
62
  return
55
63
  exporter = READMEExporter()
56
64
  filepath = output_dir / 'README.md'
65
+ t0 = time.monotonic()
57
66
  exporter.export(result, str(filepath))
67
+ elapsed = time.monotonic() - t0
68
+ _inject_time(filepath, elapsed)
58
69
  if args.verbose:
59
- print(f" - README (documentation): {filepath}")
70
+ print(f" - README (documentation): {filepath} ({elapsed:.2f}s)")
60
71
 
61
72
 
62
73
  def _export_project_yaml(args, result, output_dir: Path):
63
74
  """Export unified project.yaml — single source of truth."""
64
75
  exporter = ProjectYAMLExporter()
65
76
  filepath = output_dir / 'project.yaml'
77
+ t0 = time.monotonic()
66
78
  exporter.export(result, str(filepath))
79
+ elapsed = time.monotonic() - t0
80
+ _inject_time(filepath, elapsed)
67
81
  if getattr(args, 'verbose', False):
68
- print(f" - PROJECT-YAML (single source of truth): {filepath}")
82
+ print(f" - PROJECT-YAML (single source of truth): {filepath} ({elapsed:.2f}s)")
69
83
  return filepath
70
84
 
71
85
 
@@ -79,10 +93,13 @@ def _export_project_toon(args, result, output_dir: Path):
79
93
 
80
94
  exporter = ToonViewGenerator()
81
95
  filepath = output_dir / 'project.toon.yaml'
96
+ t0 = time.monotonic()
82
97
  exporter.generate(data, str(filepath))
98
+ elapsed = time.monotonic() - t0
99
+ _inject_time(filepath, elapsed)
83
100
 
84
101
  if getattr(args, 'verbose', False):
85
- print(f" - PROJECT-TOON (project overview): {filepath}")
102
+ print(f" - PROJECT-TOON (project overview): {filepath} ({elapsed:.2f}s)")
86
103
 
87
104
  return filepath
88
105
 
@@ -129,9 +146,12 @@ def _export_simple_formats(args, result, output_dir: Path, formats):
129
146
  exporter = exporter_cls()
130
147
  # Export as plain text TOON format with .toon.yaml extension
131
148
  filepath = output_dir / filename
149
+ t0 = time.monotonic()
132
150
  exporter.export(result, str(filepath))
151
+ elapsed = time.monotonic() - t0
152
+ _inject_time(filepath, elapsed)
133
153
  if args.verbose:
134
- print(f" - {label}: {filepath}")
154
+ print(f" - {label}: {filepath} ({elapsed:.2f}s)")
135
155
 
136
156
  # Unified project.yaml (single source of truth)
137
157
  if 'project-yaml' in formats:
@@ -7,6 +7,7 @@ Maintains backward compatibility with all existing --format values.
7
7
  import os
8
8
  import shutil
9
9
  import sys
10
+ import time
10
11
  from pathlib import Path
11
12
  from typing import Optional, List, Dict, Any
12
13
 
@@ -280,9 +281,12 @@ def _export_registry_formats(args, result, output_dir: Path, formats: List[str])
280
281
  kwargs = _get_format_kwargs(fmt, args)
281
282
 
282
283
  try:
284
+ t0 = time.monotonic()
283
285
  exporter.export(result, str(filepath), **kwargs)
286
+ elapsed = time.monotonic() - t0
287
+ _inject_generation_time(filepath, elapsed)
284
288
  if args.verbose:
285
- print(f" - {label}: {filepath}")
289
+ print(f" - {label}: {filepath} ({elapsed:.2f}s)")
286
290
  except Exception as e:
287
291
  if args.verbose:
288
292
  print(f" - {label} export failed: {e}", file=sys.stderr)
@@ -306,6 +310,53 @@ def _export_chunked(
306
310
  _chunked_impl(args, result, output_dir, source_path, formats, requested_formats)
307
311
 
308
312
 
313
+ def _inject_generation_time(filepath: Path, elapsed: float) -> None:
314
+ """Inject generation time comment into the second line of an exported file."""
315
+ try:
316
+ path = Path(filepath)
317
+ if not path.exists():
318
+ return
319
+ suffix = path.suffix.lower()
320
+ name = path.name.lower()
321
+
322
+ # Only inject into text-based files
323
+ if suffix not in ('.yaml', '.yml', '.md', '.txt', '.mmd', '.html', '.json', '.export'):
324
+ return
325
+
326
+ content = path.read_text(encoding='utf-8')
327
+ if not content:
328
+ return
329
+
330
+ tag = f"generated in {elapsed:.2f}s"
331
+
332
+ if suffix in ('.yaml', '.yml', '.mmd', '.export', '.txt') or name.endswith('.toon.yaml'):
333
+ # YAML/Mermaid/text: insert '# generated in X.XXs' after first line
334
+ lines = content.split('\n', 1)
335
+ if len(lines) == 2:
336
+ content = f"{lines[0]}\n# {tag}\n{lines[1]}"
337
+ else:
338
+ content = f"{lines[0]}\n# {tag}\n"
339
+ elif suffix == '.md':
340
+ # Markdown: insert HTML comment after first line
341
+ lines = content.split('\n', 1)
342
+ if len(lines) == 2:
343
+ content = f"{lines[0]}\n<!-- {tag} -->\n{lines[1]}"
344
+ else:
345
+ content = f"{lines[0]}\n<!-- {tag} -->\n"
346
+ elif suffix == '.html':
347
+ # HTML: insert comment after <!DOCTYPE or <html>
348
+ content = content.replace('\n', f'\n<!-- {tag} -->\n', 1)
349
+ elif suffix == '.json':
350
+ # JSON doesn't support comments — skip
351
+ return
352
+ else:
353
+ return
354
+
355
+ path.write_text(content, encoding='utf-8')
356
+ except Exception:
357
+ pass # Never fail the export pipeline for a comment
358
+
359
+
309
360
  # Backward-compatible aliases
310
361
  _export_single_project = _export_single
311
362
  _export_chunked_results = _export_chunked
@@ -4,6 +4,7 @@ This module contains all the individual export handler functions
4
4
  that were extracted from orchestrator.py to reduce its size.
5
5
  """
6
6
 
7
+ import time
7
8
  from pathlib import Path
8
9
  from typing import Any, Dict, List
9
10
 
@@ -20,10 +21,14 @@ from .orchestrator_constants import FORMAT_LABELS
20
21
 
21
22
  def _export_mermaid(args, result, output_dir: Path):
22
23
  """Export mermaid diagrams."""
24
+ from .orchestrator import _inject_generation_time
25
+
23
26
  exporter = MermaidExporter()
24
27
  include_examples = getattr(args, 'flow_include_examples', False)
25
28
 
26
29
  # Core diagrams
30
+ mmd_files = ['flow.mmd', 'calls.mmd', 'compact_flow.mmd']
31
+ t0 = time.monotonic()
27
32
  exporter.export_flow_compact(result, str(output_dir / 'flow.mmd'), include_examples)
28
33
  exporter.export_call_graph(result, str(output_dir / 'calls.mmd'))
29
34
  exporter.export_compact(result, str(output_dir / 'compact_flow.mmd'))
@@ -31,21 +36,27 @@ def _export_mermaid(args, result, output_dir: Path):
31
36
  # Optional detailed diagrams
32
37
  if getattr(args, 'flow_detail', False):
33
38
  exporter.export_flow_detailed(result, str(output_dir / 'flow_detailed.mmd'), include_examples)
39
+ mmd_files.append('flow_detailed.mmd')
34
40
  if getattr(args, 'flow_full', False):
35
41
  exporter.export_flow_full(result, str(output_dir / 'flow_full.mmd'), include_examples)
42
+ mmd_files.append('flow_full.mmd')
43
+ elapsed_mmd = time.monotonic() - t0
44
+
45
+ # Inject timing into each .mmd file
46
+ for mf in mmd_files:
47
+ _inject_generation_time(output_dir / mf, elapsed_mmd)
36
48
 
37
49
  # Also export calls.yaml/toon
38
50
  yaml_exporter = YAMLExporter()
51
+ t0 = time.monotonic()
39
52
  yaml_exporter.export_calls(result, str(output_dir / 'calls.yaml'))
40
53
  yaml_exporter.export_calls_toon(result, str(output_dir / 'calls.toon.yaml'))
54
+ elapsed_calls = time.monotonic() - t0
55
+ _inject_generation_time(output_dir / 'calls.toon.yaml', elapsed_calls)
41
56
 
42
57
  if args.verbose:
43
- files = ['flow.mmd', 'calls.mmd', 'compact_flow.mmd', 'calls.yaml']
44
- if getattr(args, 'flow_detail', False):
45
- files.append('flow_detailed.mmd')
46
- if getattr(args, 'flow_full', False):
47
- files.append('flow_full.mmd')
48
- print(f" - Mermaid: {output_dir}/*.mmd ({', '.join(files)})")
58
+ files = mmd_files + ['calls.yaml']
59
+ print(f" - Mermaid: {output_dir}/*.mmd ({', '.join(files)}) ({elapsed_mmd:.2f}s)")
49
60
 
50
61
  # PNG generation
51
62
  _export_mermaid_pngs(args, output_dir)
@@ -99,28 +110,38 @@ def _export_data_structures(args, result, output_dir: Path):
99
110
  def _export_project_toon(args, result, output_dir: Path):
100
111
  """Export project.toon.yaml from project.yaml data."""
101
112
  from ..exporters.project_yaml_exporter import ProjectYAMLExporter
113
+ from .orchestrator import _inject_generation_time
102
114
 
103
115
  project_yaml_exporter = ProjectYAMLExporter()
104
116
  prev_evolution = load_previous_evolution(output_dir / 'project.yaml')
105
117
  data = project_yaml_exporter._build_project_yaml(result, prev_evolution)
106
118
 
107
119
  generator = ToonViewGenerator()
108
- generator.generate(data, str(output_dir / 'project.toon.yaml'))
120
+ filepath = output_dir / 'project.toon.yaml'
121
+ t0 = time.monotonic()
122
+ generator.generate(data, str(filepath))
123
+ elapsed = time.monotonic() - t0
124
+ _inject_generation_time(filepath, elapsed)
109
125
 
110
126
  if args.verbose:
111
- print(f" - PROJECT-TOON (project overview): {output_dir / 'project.toon.yaml'}")
127
+ print(f" - PROJECT-TOON (project overview): {filepath} ({elapsed:.2f}s)")
112
128
 
113
129
 
114
130
  def _export_readme(args, result, output_dir: Path):
115
131
  """Export README.md."""
116
132
  if getattr(args, 'no_readme', False):
117
133
  return
134
+ from .orchestrator import _inject_generation_time
118
135
  exporter_cls = get_exporter('readme')
119
136
  if exporter_cls:
120
137
  exporter = exporter_cls()
121
- exporter.export(result, str(output_dir / 'README.md'))
138
+ filepath = output_dir / 'README.md'
139
+ t0 = time.monotonic()
140
+ exporter.export(result, str(filepath))
141
+ elapsed = time.monotonic() - t0
142
+ _inject_generation_time(filepath, elapsed)
122
143
  if args.verbose:
123
- print(f" - README (documentation): {output_dir / 'README.md'}")
144
+ print(f" - README (documentation): {filepath} ({elapsed:.2f}s)")
124
145
 
125
146
 
126
147
  def _export_index_html(args, output_dir: Path):
@@ -128,10 +149,14 @@ def _export_index_html(args, output_dir: Path):
128
149
  if 'all' not in getattr(args, 'format', ''):
129
150
  return
130
151
  try:
152
+ from .orchestrator import _inject_generation_time
131
153
  generator = IndexHTMLGenerator(output_dir)
154
+ t0 = time.monotonic()
132
155
  index_path = generator.generate()
156
+ elapsed = time.monotonic() - t0
157
+ _inject_generation_time(index_path, elapsed)
133
158
  if args.verbose:
134
- print(f" - INDEX (file browser): {index_path}")
159
+ print(f" - INDEX (file browser): {index_path} ({elapsed:.2f}s)")
135
160
  except Exception as e:
136
161
  if args.verbose:
137
162
  print(f" - INDEX generation failed: {e}", file=__import__('sys').stderr)
@@ -1,6 +1,7 @@
1
1
  """Prompt generation — prompt.txt for LLM consumption (regular and chunked)."""
2
2
 
3
3
  import sys
4
+ import time
4
5
  from pathlib import Path
5
6
  from typing import List, Optional, Tuple
6
7
 
@@ -30,9 +31,13 @@ def _export_prompt_txt(args, output_dir: Path, formats: list[str], source_path:
30
31
  lines.extend(_build_prompt_footer(chunked=False, file_analysis=file_analysis))
31
32
 
32
33
  prompt_path = output_dir / 'prompt.txt'
34
+ t0 = time.monotonic()
33
35
  prompt_path.write_text("\n".join(lines) + "\n", encoding='utf-8')
36
+ elapsed = time.monotonic() - t0
37
+ from .orchestrator import _inject_generation_time
38
+ _inject_generation_time(prompt_path, elapsed)
34
39
  if args.verbose:
35
- print(f" - PROMPT: {prompt_path}")
40
+ print(f" - PROMPT: {prompt_path} ({elapsed:.2f}s)")
36
41
 
37
42
 
38
43
  def _export_chunked_prompt_txt(args, output_dir: Path, formats: list[str], source_path: Optional[Path] = None, subprojects: list = None) -> None:
@@ -29,21 +29,40 @@ def compute_func_data(result: AnalysisResult) -> List[Dict]:
29
29
  return sorted(func_data, key=lambda x: x["impact"], reverse=True)
30
30
 
31
31
 
32
- def scan_file_sizes(project_path: Optional[Path]) -> Dict[str, int]:
33
- """Scan Python files and return line counts."""
32
+ def scan_file_sizes(project_path: Optional[Path], result: Optional[AnalysisResult] = None) -> Dict[str, int]:
33
+ """Return per-file line counts, preferring already-analyzed module data."""
34
34
  file_lines: Dict[str, int] = {}
35
+
36
+ # Fast path: derive from AnalysisResult modules (no I/O)
37
+ if result and result.modules:
38
+ for mi in result.modules.values():
39
+ if mi.file and not is_excluded(mi.file):
40
+ lc = mi.line_count if hasattr(mi, 'line_count') and mi.line_count else 0
41
+ if lc == 0:
42
+ lc = len(mi.functions) + len(mi.classes)
43
+ if lc > 0:
44
+ file_lines[mi.file] = lc
45
+ if file_lines:
46
+ return file_lines
47
+
48
+ # Slow fallback: single os.walk (only if result is unavailable)
35
49
  if not project_path or not project_path.is_dir():
36
50
  return file_lines
37
-
38
- for py in project_path.rglob("*.py"):
39
- fpath = str(py)
40
- if is_excluded(fpath):
41
- continue
42
- try:
43
- lc = len(py.read_text(encoding="utf-8", errors="ignore").splitlines())
44
- file_lines[fpath] = lc
45
- except Exception:
46
- pass
51
+
52
+ import os
53
+ exclude = {'.git', '__pycache__', 'node_modules', 'venv', '.venv',
54
+ 'env', '.env', 'site-packages', 'dist', 'build', '.tox'}
55
+ for dirpath, dirnames, filenames in os.walk(str(project_path)):
56
+ dirnames[:] = [d for d in dirnames if d not in exclude]
57
+ for fn in filenames:
58
+ if not fn.endswith('.py'):
59
+ continue
60
+ fpath = os.path.join(dirpath, fn)
61
+ try:
62
+ with open(fpath, encoding='utf-8', errors='ignore') as f:
63
+ file_lines[fpath] = sum(1 for _ in f)
64
+ except Exception:
65
+ pass
47
66
  return file_lines
48
67
 
49
68
 
@@ -109,7 +128,7 @@ def compute_god_modules(result: AnalysisResult) -> List[Dict]:
109
128
  """Identify god modules (≥500 lines) from project files."""
110
129
  pp = Path(result.project_path) if result.project_path else None
111
130
 
112
- file_lines = scan_file_sizes(pp)
131
+ file_lines = scan_file_sizes(pp, result)
113
132
  file_stats = aggregate_file_stats(result, file_lines)
114
133
  return filter_god_modules(file_stats, pp)
115
134
 
@@ -0,0 +1,17 @@
1
+ """Evolution exporter exclusion logic — path filtering."""
2
+
3
+ from functools import lru_cache
4
+
5
+ from .constants import EXCLUDE_PATTERNS
6
+
7
+
8
+ @lru_cache(maxsize=4096)
9
+ def is_excluded(path: str) -> bool:
10
+ """Check if path should be excluded (venv, site-packages, etc.)."""
11
+ if not path:
12
+ return False
13
+ parts = set(path.lower().replace('\\', '/').split('/'))
14
+ return bool(parts & EXCLUDE_PATTERNS)
15
+
16
+
17
+ __all__ = ['is_excluded']
@@ -3,6 +3,8 @@
3
3
  Zawiera progi, wzorce wykluczeń i rekomendacje dotyczące podziału typów hub.
4
4
  """
5
5
 
6
+ from functools import lru_cache
7
+
6
8
  # Progi dla wykrywania problemów
7
9
  CC_HIGH = 15
8
10
  FAN_OUT_THRESHOLD = 10
@@ -19,17 +21,13 @@ EXCLUDE_PATTERNS = {
19
21
  'virtualenv', '.virtualenv', 'envs', '.envs',
20
22
  }
21
23
 
24
+ @lru_cache(maxsize=4096)
22
25
  def is_excluded_path(path: str) -> bool:
23
26
  """Return True if *path* matches any standard exclusion pattern (venv, cache, etc.)."""
24
27
  if not path:
25
28
  return False
26
- path_lower = path.lower().replace('\\', '/')
27
- for pattern in EXCLUDE_PATTERNS:
28
- if f'/{pattern}/' in path_lower or path_lower.startswith(f'{pattern}/'):
29
- return True
30
- if pattern in path_lower.split('/'):
31
- return True
32
- return False
29
+ parts = set(path.lower().replace('\\', '/').split('/'))
30
+ return bool(parts & EXCLUDE_PATTERNS)
33
31
 
34
32
 
35
33
  # Rekomendacje podziału typów hub: typ -> sugerowane pod-interfejsy
@@ -48,7 +48,7 @@ class ProjectYAMLExporter(BaseExporter):
48
48
  self, result: AnalysisResult, prev_evolution: List[Dict]
49
49
  ) -> Dict[str, Any]:
50
50
  """Build complete project.yaml structure."""
51
- line_counts = _scan_line_counts(result.project_path)
51
+ line_counts = _scan_line_counts(result.project_path, result=result)
52
52
  # Filter out venv/site-packages/etc — only count lines of non-excluded files
53
53
  filtered_lines = {
54
54
  k: v for k, v in line_counts.items()
@@ -10,6 +10,7 @@ from code2llm.core.models import AnalysisResult, FunctionInfo
10
10
  from ..flow_constants import is_excluded_path as _is_excluded
11
11
 
12
12
 
13
+ @lru_cache(maxsize=4096)
13
14
  def _rel_path(fpath: str, project_path: str) -> str:
14
15
  if not project_path or not fpath:
15
16
  return fpath or ""
@@ -83,15 +84,12 @@ def _hotspot_description(fi: FunctionInfo, fan_out: int) -> str:
83
84
  return f"calls {fan_out} functions"
84
85
 
85
86
 
86
- @lru_cache(maxsize=8)
87
- def _scan_line_counts(project_path) -> Dict[str, int]:
88
- """Scan project directory for all source file line counts.
87
+ def _scan_line_counts(project_path, result=None) -> Dict[str, int]:
88
+ """Get line counts for project files.
89
89
 
90
- Cached per project_path callers within the same process reuse the result
91
- instead of repeating the rglob + read_text I/O.
90
+ Fast path: derive from AnalysisResult modules (already parsed, no extra I/O).
91
+ Slow fallback: single os.walk pass reading files from disk.
92
92
  """
93
- from ...core.config import ALL_EXTENSIONS
94
-
95
93
  line_counts: Dict[str, int] = {}
96
94
  if not project_path:
97
95
  return line_counts
@@ -99,8 +97,35 @@ def _scan_line_counts(project_path) -> Dict[str, int]:
99
97
  if not pp.is_dir():
100
98
  return line_counts
101
99
 
102
- for ext in ALL_EXTENSIONS:
103
- for src_file in pp.rglob(f"*{ext}"):
100
+ # Fast path: use already-analyzed file data when available
101
+ if result is not None:
102
+ for mname, mi in getattr(result, 'modules', {}).items():
103
+ fpath = mi.file
104
+ if not fpath:
105
+ continue
106
+ try:
107
+ lc = len(Path(fpath).read_text(encoding="utf-8", errors="ignore").splitlines())
108
+ rel = str(Path(fpath).relative_to(pp))
109
+ line_counts[str(fpath)] = lc
110
+ line_counts[rel] = lc
111
+ except Exception:
112
+ pass
113
+ return line_counts
114
+
115
+ # Slow fallback: single walk instead of 73 rglob calls
116
+ from ...core.config import ALL_EXTENSIONS
117
+ ext_set = set(ALL_EXTENSIONS)
118
+ for root, dirs, files in Path(project_path).walk() if hasattr(Path, 'walk') else _walk_compat(pp):
119
+ # Prune excluded directories
120
+ dirs[:] = [d for d in dirs if d not in {
121
+ 'venv', '.venv', 'node_modules', '__pycache__', '.git',
122
+ 'dist', 'build', '.tox', '.mypy_cache', 'egg-info',
123
+ }]
124
+ for fname in files:
125
+ ext = Path(fname).suffix
126
+ if ext not in ext_set:
127
+ continue
128
+ src_file = root / fname
104
129
  try:
105
130
  lc = len(src_file.read_text(encoding="utf-8", errors="ignore").splitlines())
106
131
  rel = str(src_file.relative_to(pp))
@@ -109,3 +134,10 @@ def _scan_line_counts(project_path) -> Dict[str, int]:
109
134
  except Exception:
110
135
  pass
111
136
  return line_counts
137
+
138
+
139
+ def _walk_compat(path):
140
+ """os.walk compatibility wrapper for Path (Python < 3.12)."""
141
+ import os
142
+ for root, dirs, files in os.walk(path):
143
+ yield Path(root), dirs, files