code2llm 0.5.173__tar.gz → 0.5.175__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 (214) hide show
  1. {code2llm-0.5.173/code2llm.egg-info → code2llm-0.5.175}/PKG-INFO +6 -6
  2. {code2llm-0.5.173 → code2llm-0.5.175}/README.md +5 -5
  3. code2llm-0.5.175/VERSION +1 -0
  4. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/__init__.py +1 -1
  5. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_exports/orchestrator.py +4 -0
  6. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/base.py +26 -2
  7. code2llm-0.5.175/code2llm/exporters/flow_constants.py +109 -0
  8. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/map/header.py +4 -1
  9. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/map_exporter.py +1 -4
  10. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/planfile_tickets.py +51 -19
  11. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/_render_section_helpers.py +2 -1
  12. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/helpers.py +1 -1
  13. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/metrics_duplicates.py +8 -2
  14. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/metrics_health.py +8 -6
  15. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/renderer.py +1 -1
  16. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/mermaid/png.py +36 -0
  17. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/nlp/__init__.py +1 -1
  18. {code2llm-0.5.173 → code2llm-0.5.175/code2llm.egg-info}/PKG-INFO +6 -6
  19. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm.egg-info/SOURCES.txt +3 -0
  20. {code2llm-0.5.173 → code2llm-0.5.175}/pyproject.toml +1 -1
  21. code2llm-0.5.175/tests/test_dup_groups.py +140 -0
  22. code2llm-0.5.175/tests/test_export_profile_contract.py +28 -0
  23. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_format_quality.py +3 -0
  24. code2llm-0.5.175/tests/test_mermaid_png.py +29 -0
  25. code2llm-0.5.173/VERSION +0 -1
  26. code2llm-0.5.173/code2llm/exporters/flow_constants.py +0 -63
  27. {code2llm-0.5.173 → code2llm-0.5.175}/LICENSE +0 -0
  28. {code2llm-0.5.173 → code2llm-0.5.175}/MANIFEST.in +0 -0
  29. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/__main__.py +0 -0
  30. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/__init__.py +0 -0
  31. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/_data_impl.py +0 -0
  32. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/call_graph.py +0 -0
  33. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/cfg.py +0 -0
  34. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/coupling.py +0 -0
  35. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/data_analysis.py +0 -0
  36. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/dfg.py +0 -0
  37. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/pipeline_classifier.py +0 -0
  38. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/pipeline_detector.py +0 -0
  39. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/pipeline_resolver.py +0 -0
  40. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/side_effects.py +0 -0
  41. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/smells.py +0 -0
  42. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/type_inference.py +0 -0
  43. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/utils/__init__.py +0 -0
  44. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/analysis/utils/ast_helpers.py +0 -0
  45. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/api.py +0 -0
  46. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli.py +0 -0
  47. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_analysis.py +0 -0
  48. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_commands.py +0 -0
  49. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_exports/__init__.py +0 -0
  50. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_exports/code2logic.py +0 -0
  51. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_exports/formats.py +0 -0
  52. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_exports/orchestrator_chunked.py +0 -0
  53. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_exports/orchestrator_constants.py +0 -0
  54. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_exports/orchestrator_handlers.py +0 -0
  55. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_exports/prompt.py +0 -0
  56. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/cli_parser.py +0 -0
  57. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/__init__.py +0 -0
  58. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/analyzer.py +0 -0
  59. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/ast_registry.py +0 -0
  60. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/config.py +0 -0
  61. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/export_pipeline.py +0 -0
  62. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/file_analyzer.py +0 -0
  63. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/file_cache.py +0 -0
  64. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/file_filter.py +0 -0
  65. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/gitignore.py +0 -0
  66. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/incremental.py +0 -0
  67. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/__init__.py +0 -0
  68. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/_c_parser.py +0 -0
  69. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/_calls.py +0 -0
  70. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/_complexity.py +0 -0
  71. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/base.py +0 -0
  72. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/cpp.py +0 -0
  73. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/csharp.py +0 -0
  74. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/generic.py +0 -0
  75. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/go_lang.py +0 -0
  76. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/java.py +0 -0
  77. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/php.py +0 -0
  78. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/ruby.py +0 -0
  79. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/rust.py +0 -0
  80. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/ts_extractors.py +0 -0
  81. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/ts_parser.py +0 -0
  82. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/lang/typescript.py +0 -0
  83. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/large_repo.py +0 -0
  84. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/models.py +0 -0
  85. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/persistent_cache.py +0 -0
  86. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/refactoring.py +0 -0
  87. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/repo_files.py +0 -0
  88. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/source_classifier.py +0 -0
  89. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/streaming/__init__.py +0 -0
  90. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/streaming/cache.py +0 -0
  91. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/streaming/incremental.py +0 -0
  92. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/streaming/prioritizer.py +0 -0
  93. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/streaming/scanner.py +0 -0
  94. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/streaming/strategies.py +0 -0
  95. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/streaming_analyzer.py +0 -0
  96. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/core/toon_size_manager.py +0 -0
  97. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/__init__.py +0 -0
  98. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/article_view.py +0 -0
  99. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/context_exporter.py +0 -0
  100. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/context_view.py +0 -0
  101. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/dashboard_data.py +0 -0
  102. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/dashboard_renderer.py +0 -0
  103. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/evolution/__init__.py +0 -0
  104. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/evolution/computation.py +0 -0
  105. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/evolution/constants.py +0 -0
  106. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/evolution/exclusion.py +0 -0
  107. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/evolution/render.py +0 -0
  108. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/evolution/yaml_export.py +0 -0
  109. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/evolution_exporter.py +0 -0
  110. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/flow_exporter.py +0 -0
  111. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/flow_renderer.py +0 -0
  112. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/html_dashboard.py +0 -0
  113. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/index_generator/__init__.py +0 -0
  114. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/index_generator/renderer.py +0 -0
  115. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/index_generator/scanner.py +0 -0
  116. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/index_generator.py +0 -0
  117. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/json_exporter.py +0 -0
  118. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/llm_exporter.py +0 -0
  119. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/map/__init__.py +0 -0
  120. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/map/alerts.py +0 -0
  121. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/map/details.py +0 -0
  122. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/map/module_list.py +0 -0
  123. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/map/utils.py +0 -0
  124. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/map/yaml_export.py +0 -0
  125. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/mermaid/__init__.py +0 -0
  126. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/mermaid/calls.py +0 -0
  127. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/mermaid/classic.py +0 -0
  128. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/mermaid/compact.py +0 -0
  129. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/mermaid/flow_compact.py +0 -0
  130. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/mermaid/flow_detailed.py +0 -0
  131. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/mermaid/flow_full.py +0 -0
  132. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/mermaid/utils.py +0 -0
  133. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/mermaid_exporter.py +0 -0
  134. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
  135. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/project_yaml/__init__.py +0 -0
  136. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/project_yaml/constants.py +0 -0
  137. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/project_yaml/core.py +0 -0
  138. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/project_yaml/evolution.py +0 -0
  139. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/project_yaml/health.py +0 -0
  140. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/project_yaml/hotspots.py +0 -0
  141. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/project_yaml/modules.py +0 -0
  142. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/project_yaml_exporter.py +0 -0
  143. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/readme/__init__.py +0 -0
  144. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/readme/content.py +0 -0
  145. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/readme/files.py +0 -0
  146. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/readme/insights.py +0 -0
  147. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/readme/sections.py +0 -0
  148. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/readme_exporter.py +0 -0
  149. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/report_generators.py +0 -0
  150. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/__init__.py +0 -0
  151. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/_render_coupling_helpers.py +0 -0
  152. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/constants.py +0 -0
  153. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/metrics.py +0 -0
  154. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/metrics_core.py +0 -0
  155. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon/module_detail.py +0 -0
  156. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon.py +0 -0
  157. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/toon_view.py +0 -0
  158. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/validate_project.py +0 -0
  159. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/exporters/yaml_exporter.py +0 -0
  160. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/__init__.py +0 -0
  161. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/_utils.py +0 -0
  162. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/llm_flow/__init__.py +0 -0
  163. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/llm_flow/analysis.py +0 -0
  164. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/llm_flow/cli.py +0 -0
  165. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/llm_flow/generator.py +0 -0
  166. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/llm_flow/nodes.py +0 -0
  167. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/llm_flow/parsing.py +0 -0
  168. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/llm_flow/utils.py +0 -0
  169. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/llm_flow.py +0 -0
  170. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/llm_task.py +0 -0
  171. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/mermaid/__init__.py +0 -0
  172. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/mermaid/fix.py +0 -0
  173. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/mermaid/validation.py +0 -0
  174. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/generators/mermaid.py +0 -0
  175. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/nlp/config.py +0 -0
  176. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/nlp/entity_resolution.py +0 -0
  177. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/nlp/intent_matching.py +0 -0
  178. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/nlp/normalization.py +0 -0
  179. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/nlp/pipeline.py +0 -0
  180. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/parsers/toon_parser.py +0 -0
  181. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/patterns/__init__.py +0 -0
  182. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/patterns/detector.py +0 -0
  183. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/refactor/__init__.py +0 -0
  184. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm/refactor/prompt_engine.py +0 -0
  185. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm.egg-info/dependency_links.txt +0 -0
  186. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm.egg-info/entry_points.txt +0 -0
  187. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm.egg-info/requires.txt +0 -0
  188. {code2llm-0.5.173 → code2llm-0.5.175}/code2llm.egg-info/top_level.txt +0 -0
  189. {code2llm-0.5.173 → code2llm-0.5.175}/setup.cfg +0 -0
  190. {code2llm-0.5.173 → code2llm-0.5.175}/setup.py +0 -0
  191. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_advanced_analysis.py +0 -0
  192. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_analyzer.py +0 -0
  193. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_cache_invalidation_e2e.py +0 -0
  194. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_calls_toon_export.py +0 -0
  195. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_declarative_collection.py +0 -0
  196. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_deep_analysis.py +0 -0
  197. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_edge_cases.py +0 -0
  198. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_export_cache_flags.py +0 -0
  199. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_file_analyzer_tagging.py +0 -0
  200. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_flow_exporter.py +0 -0
  201. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_multilanguage_e2e.py +0 -0
  202. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_nlp_pipeline.py +0 -0
  203. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_nonpython_cc_calls.py +0 -0
  204. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_orchestrator_cache_mtime.py +0 -0
  205. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_persistent_cache.py +0 -0
  206. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_pipeline_detector.py +0 -0
  207. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_planfile_tickets_exporter.py +0 -0
  208. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_project_toon_export.py +0 -0
  209. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_prompt_engine.py +0 -0
  210. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_prompt_txt.py +0 -0
  211. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_refactoring_engine.py +0 -0
  212. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_smells_data_clumps.py +0 -0
  213. {code2llm-0.5.173 → code2llm-0.5.175}/tests/test_smells_shotgun_surgery.py +0 -0
  214. {code2llm-0.5.173 → code2llm-0.5.175}/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.173
3
+ Version: 0.5.175
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.173-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.73-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-93.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.175-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-104.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fdeep%2Fdeep--v4--pro-lightgrey)
72
72
 
73
- - 🤖 **LLM usage:** $17.7289 (247 commits)
74
- - 👤 **Human dev:** ~$9380 (93.8h @ $100/h, 30min dedup)
73
+ - 🤖 **LLM usage:** $3.8035 (262 commits)
74
+ - 👤 **Human dev:** ~$10435 (104.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-08-01 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.173-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.73-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-93.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.175-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-104.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fdeep%2Fdeep--v4--pro-lightgrey)
7
7
 
8
- - 🤖 **LLM usage:** $17.7289 (247 commits)
9
- - 👤 **Human dev:** ~$9380 (93.8h @ $100/h, 30min dedup)
8
+ - 🤖 **LLM usage:** $3.8035 (262 commits)
9
+ - 👤 **Human dev:** ~$10435 (104.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-08-01 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.175
@@ -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.173"
11
+ __version__ = "0.5.175"
12
12
  __author__ = "STTS Project"
13
13
 
14
14
  # Core analysis components (lightweight, always needed)
@@ -42,6 +42,10 @@ def _build_export_config(args, formats: List[str]) -> Dict[str, Any]:
42
42
  """Build config dict for export caching."""
43
43
  return {
44
44
  "formats": sorted(formats),
45
+ "mode": getattr(args, "mode", None),
46
+ "strategy": getattr(args, "strategy", None),
47
+ "exclude": sorted(str(item) for item in (getattr(args, "exclude", None) or [])),
48
+ "gitignore": getattr(args, "gitignore", None),
45
49
  "png": getattr(args, "png", False),
46
50
  "no_png": getattr(args, "no_png", False),
47
51
  "flow_include_examples": getattr(args, "flow_include_examples", False),
@@ -7,6 +7,8 @@ Provides:
7
7
  """
8
8
 
9
9
  from abc import ABC, abstractmethod
10
+ import os
11
+ import tempfile
10
12
  from pathlib import Path
11
13
  from typing import Any, Dict, Optional, Type, ClassVar
12
14
  from code2llm.core.models import AnalysisResult
@@ -80,9 +82,31 @@ class BaseExporter(ABC):
80
82
  return path
81
83
 
82
84
  def _write_text(self, output_path: str, content: str) -> Path:
83
- """Write text content to file, ensuring directory exists."""
85
+ """Atomically write text content, ensuring the directory exists.
86
+
87
+ Analysis artifacts are consumed by background tools immediately after
88
+ generation. A same-directory temporary file plus ``os.replace`` keeps
89
+ readers from observing a truncated map while an export is in progress.
90
+ """
84
91
  path = self._ensure_dir(output_path)
85
- path.write_text(content, encoding="utf-8")
92
+ temporary = None
93
+ try:
94
+ with tempfile.NamedTemporaryFile(
95
+ mode="w",
96
+ encoding="utf-8",
97
+ dir=path.parent,
98
+ prefix=f".{path.name}.",
99
+ suffix=".tmp",
100
+ delete=False,
101
+ ) as handle:
102
+ handle.write(content)
103
+ handle.flush()
104
+ os.fsync(handle.fileno())
105
+ temporary = Path(handle.name)
106
+ os.replace(temporary, path)
107
+ finally:
108
+ if temporary is not None:
109
+ temporary.unlink(missing_ok=True)
86
110
  return path
87
111
 
88
112
 
@@ -0,0 +1,109 @@
1
+ """Stałe dla FlowExporter.
2
+
3
+ Zawiera progi, wzorce wykluczeń i rekomendacje dotyczące podziału typów hub.
4
+ """
5
+
6
+ from functools import lru_cache
7
+
8
+ # Progi dla wykrywania problemów
9
+ CC_HIGH = 15
10
+ FAN_OUT_THRESHOLD = 10
11
+ HUB_TYPE_THRESHOLD = 10
12
+
13
+ # Wzorce do wykluczenia (venv, cache, etc.)
14
+ EXCLUDE_PATTERNS = {
15
+ "venv",
16
+ ".venv",
17
+ "env",
18
+ ".env",
19
+ "publish-env",
20
+ "test-env",
21
+ "site-packages",
22
+ "node_modules",
23
+ "__pycache__",
24
+ ".git",
25
+ "dist",
26
+ "build",
27
+ "egg-info",
28
+ ".tox",
29
+ ".mypy_cache",
30
+ # Backup directories that often contain nested venvs
31
+ ".algitex",
32
+ ".backup",
33
+ "backups",
34
+ ".bak",
35
+ # Additional venv patterns
36
+ "virtualenv",
37
+ ".virtualenv",
38
+ "envs",
39
+ ".envs",
40
+ }
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
+ "work/repos/",
50
+ )
51
+
52
+ # These repositories are deliberately separate execution planes. Classes with
53
+ # similar infrastructure-shaped APIs across two different agents are not
54
+ # consolidation candidates: sharing their implementations would couple the
55
+ # independent diagnostic, repair, and validation boundaries.
56
+ INDEPENDENT_AGENT_REPOSITORIES = frozenset(
57
+ {"doctor-agent", "repair-agent", "validator-agent"}
58
+ )
59
+
60
+
61
+ @lru_cache(maxsize=4096)
62
+ def is_intentional_duplicate_copy(path: str) -> bool:
63
+ """True when *path* is an intentional parallel copy (not actionable duplication)."""
64
+ if not path:
65
+ return False
66
+ norm = path.replace("\\", "/").lower()
67
+ return any(marker in norm for marker in INTENTIONAL_DUP_COPY_MARKERS)
68
+
69
+
70
+ @lru_cache(maxsize=4096)
71
+ def _independent_agent_repository(path: str) -> str | None:
72
+ """Return the independent agent repository segment present in *path*."""
73
+ if not path:
74
+ return None
75
+ parts = path.replace("\\", "/").lower().split("/")
76
+ return next((part for part in parts if part in INDEPENDENT_AGENT_REPOSITORIES), None)
77
+
78
+
79
+ @lru_cache(maxsize=4096)
80
+ def is_intentional_duplicate_pair(path_a: str, path_b: str) -> bool:
81
+ """True when two similar classes belong to intentionally parallel copies."""
82
+ if is_intentional_duplicate_copy(path_a) or is_intentional_duplicate_copy(path_b):
83
+ return True
84
+ repo_a = _independent_agent_repository(path_a)
85
+ repo_b = _independent_agent_repository(path_b)
86
+ return repo_a is not None and repo_b is not None and repo_a != repo_b
87
+
88
+
89
+ @lru_cache(maxsize=4096)
90
+ def is_excluded_path(path: str) -> bool:
91
+ """Return True if *path* matches any standard exclusion pattern (venv, cache, etc.)."""
92
+ if not path:
93
+ return False
94
+ parts = set(path.lower().replace("\\", "/").split("/"))
95
+ return bool(parts & EXCLUDE_PATTERNS)
96
+
97
+
98
+ # Rekomendacje podziału typów hub: typ -> sugerowane pod-interfejsy
99
+ HUB_SPLIT_RECOMMENDATIONS = {
100
+ "AnalysisResult": [
101
+ "StructureResult (modules, classes, functions)",
102
+ "MetricsResult (complexity, coupling)",
103
+ "FlowResult (call_graph, cfg, dfg)",
104
+ ],
105
+ "dict": ["replace with typed alternatives (dataclass/TypedDict)"],
106
+ "str": [], # primitive, expected to be ubiquitous
107
+ "list": [],
108
+ "Any": [],
109
+ }
@@ -8,6 +8,8 @@ from code2llm.core.models import AnalysisResult, FunctionInfo, ModuleInfo
8
8
  from .utils import count_total_lines, detect_languages
9
9
  from .alerts import build_alerts, build_hotspots, load_evolution_trend
10
10
 
11
+ MAP_PRODUCER_HEADER = "# producer: code2llm | artifact: map.toon.yaml | schema: 1"
12
+
11
13
 
12
14
  def render_header(
13
15
  result: AnalysisResult, output_path: str, is_excluded_path
@@ -38,6 +40,7 @@ def render_header(
38
40
 
39
41
  lines = [
40
42
  f"# {project_name} | {stats_line['files']}f {stats_line['lines']}L | {lang_str} | {datetime.now().strftime('%Y-%m-%d')}", # noqa: E501
43
+ MAP_PRODUCER_HEADER,
41
44
  f"# stats: {stats_line['funcs']} func | {stats_line['classes']} cls | {stats_line['files']} mod | CC̄={stats_line['avg_cc']} | critical:{stats_line['critical']} | cycles:{stats_line['cycles']}", # noqa: E501
42
45
  alerts_line,
43
46
  hotspots_line,
@@ -78,4 +81,4 @@ def _render_hotspots_line(funcs: List[FunctionInfo]) -> str:
78
81
  return f"# hotspots[{len(hotspots)}]: {'; '.join(hotspots) if hotspots else 'none'}"
79
82
 
80
83
 
81
- __all__ = ["render_header"]
84
+ __all__ = ["MAP_PRODUCER_HEADER", "render_header"]
@@ -41,10 +41,7 @@ class MapExporter(BaseExporter):
41
41
  lines.extend(render_module_list(result, is_excluded_path))
42
42
  lines.extend(render_details(result, is_excluded_path))
43
43
 
44
- path = self._ensure_dir(output_path)
45
- with open(path, "w", encoding="utf-8") as f:
46
- f.write("\n".join(lines) + "\n")
47
- return path
44
+ return self._write_text(output_path, "\n".join(lines) + "\n")
48
45
 
49
46
  def export_to_yaml(
50
47
  self, result: AnalysisResult, output_path: str, **kwargs
@@ -256,9 +256,54 @@ 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 (
297
+ is_intentional_duplicate_copy,
298
+ is_intentional_duplicate_pair,
299
+ )
300
+
301
+ pairs: list[tuple[str, str, str]] = []
302
+ classes = [
303
+ c
304
+ for c in result.classes.values()
305
+ if not is_intentional_duplicate_copy(c.file)
306
+ ]
262
307
  for index, left in enumerate(classes):
263
308
  left_methods = _method_names(left)
264
309
  if len(left_methods) < 3:
@@ -266,6 +311,8 @@ def _duplicate_class_tickets(result: AnalysisResult) -> list[PlanfileTicketSugge
266
311
  for right in classes[index + 1 :]:
267
312
  if left.name != right.name:
268
313
  continue
314
+ if is_intentional_duplicate_pair(left.file, right.file):
315
+ continue
269
316
  right_methods = _method_names(right)
270
317
  if len(right_methods) < 3:
271
318
  continue
@@ -275,23 +322,8 @@ def _duplicate_class_tickets(result: AnalysisResult) -> list[PlanfileTicketSugge
275
322
  continue
276
323
  left_file = _rel_path(left.file, result.project_path)
277
324
  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
325
+ pairs.append((left.name, left_file, right_file))
326
+ return pairs
295
327
 
296
328
 
297
329
  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,9 @@ 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 code2llm.exporters.flow_constants import is_intentional_duplicate_pair
11
+
12
+ from .helpers import _is_excluded, _rel_path, is_intentional_duplicate_copy
11
13
 
12
14
 
13
15
  class DuplicatesMetricsComputer:
@@ -22,7 +24,9 @@ class DuplicatesMetricsComputer:
22
24
  dupes: List[Dict[str, Any]] = []
23
25
  # Filter out excluded classes first
24
26
  class_list = [
25
- (q, c) for q, c in result.classes.items() if not _is_excluded(c.file)
27
+ (q, c)
28
+ for q, c in result.classes.items()
29
+ if not _is_excluded(c.file) and not is_intentional_duplicate_copy(c.file)
26
30
  ]
27
31
 
28
32
  for i, (qa, ca) in enumerate(class_list):
@@ -45,6 +49,8 @@ class DuplicatesMetricsComputer:
45
49
  qb, cb = class_list[j]
46
50
  if ca.name != cb.name:
47
51
  continue
52
+ if is_intentional_duplicate_pair(ca.file, cb.file):
53
+ continue
48
54
 
49
55
  methods_b = {m.split(".")[-1] for m in cb.methods}
50
56
  if len(methods_b) < 3:
@@ -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,6 +4,7 @@ import os
4
4
  import subprocess
5
5
  import tempfile
6
6
  import json
7
+ import shutil
7
8
  from concurrent.futures import ThreadPoolExecutor, as_completed
8
9
  from pathlib import Path
9
10
  from typing import List, Optional
@@ -17,6 +18,38 @@ from .validation import validate_mermaid_file
17
18
  from .fix import fix_mermaid_file
18
19
 
19
20
 
21
+ _BROWSER_ENV_VARS = (
22
+ "PUPPETEER_EXECUTABLE_PATH",
23
+ "CHROME_PATH",
24
+ "GOOGLE_CHROME_BIN",
25
+ )
26
+ _BROWSER_COMMANDS = (
27
+ "google-chrome",
28
+ "google-chrome-stable",
29
+ "chromium",
30
+ "chromium-browser",
31
+ )
32
+
33
+
34
+ def _find_browser_executable() -> Optional[str]:
35
+ """Return an explicit Chrome/Chromium path suitable for Puppeteer.
36
+
37
+ Mermaid CLI bundles puppeteer-core, which does not download Chromium on
38
+ installation. Prefer an explicitly configured browser and otherwise use
39
+ the system browser already available on the PATH.
40
+ """
41
+ for variable in _BROWSER_ENV_VARS:
42
+ candidate = os.getenv(variable)
43
+ if candidate and Path(candidate).is_file() and os.access(candidate, os.X_OK):
44
+ return candidate
45
+
46
+ for command in _BROWSER_COMMANDS:
47
+ candidate = shutil.which(command)
48
+ if candidate:
49
+ return candidate
50
+ return None
51
+
52
+
20
53
  def _is_png_fresh(mmd_file: Path, output_dir: Path) -> bool:
21
54
  """Check if PNG exists and is newer than MMD source."""
22
55
  png_file = output_dir / f"{mmd_file.stem}.png"
@@ -115,6 +148,9 @@ def _setup_puppeteer_config() -> tuple[int, int, Optional[str], Optional[str]]:
115
148
  puppeteer_cfg = {
116
149
  "args": ["--no-sandbox", "--disable-setuid-sandbox"],
117
150
  }
151
+ browser_executable = _find_browser_executable()
152
+ if browser_executable:
153
+ puppeteer_cfg["executablePath"] = browser_executable
118
154
  with tempfile.NamedTemporaryFile(
119
155
  mode="w", suffix=".json", delete=False
120
156
  ) as tmp_pcfg:
@@ -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.173"
7
+ __version__ = "0.5.175"
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.173
3
+ Version: 0.5.175
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.173-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.73-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-93.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.175-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-104.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fdeep%2Fdeep--v4--pro-lightgrey)
72
72
 
73
- - 🤖 **LLM usage:** $17.7289 (247 commits)
74
- - 👤 **Human dev:** ~$9380 (93.8h @ $100/h, 30min dedup)
73
+ - 🤖 **LLM usage:** $3.8035 (262 commits)
74
+ - 👤 **Human dev:** ~$10435 (104.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-08-01 using [openrouter/deep/deep-v4-pro](https://openrouter.ai/deep/deep-v4-pro)
77
77
 
78
78
  ---
79
79
 
@@ -186,11 +186,14 @@ 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
192
+ tests/test_export_profile_contract.py
191
193
  tests/test_file_analyzer_tagging.py
192
194
  tests/test_flow_exporter.py
193
195
  tests/test_format_quality.py
196
+ tests/test_mermaid_png.py
194
197
  tests/test_multilanguage_e2e.py
195
198
  tests/test_nlp_pipeline.py
196
199
  tests/test_nonpython_cc_calls.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code2llm"
7
- version = "0.5.173"
7
+ version = "0.5.175"
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"