code2llm 0.5.159__tar.gz → 0.5.161__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 (212) hide show
  1. {code2llm-0.5.159/code2llm.egg-info → code2llm-0.5.161}/PKG-INFO +10 -16
  2. {code2llm-0.5.159 → code2llm-0.5.161}/README.md +9 -15
  3. code2llm-0.5.161/VERSION +1 -0
  4. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/__init__.py +1 -1
  5. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/_data_impl.py +136 -104
  6. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/call_graph.py +1 -0
  7. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/cfg.py +2 -3
  8. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/coupling.py +1 -0
  9. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/dfg.py +1 -0
  10. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/pipeline_detector.py +18 -26
  11. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/side_effects.py +42 -64
  12. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/smells.py +1 -0
  13. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/type_inference.py +6 -0
  14. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_analysis.py +59 -86
  15. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_commands.py +51 -65
  16. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_exports/formats.py +67 -56
  17. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_exports/orchestrator.py +50 -68
  18. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_exports/prompt.py +47 -55
  19. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/__init__.py +1 -4
  20. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/analyzer.py +69 -40
  21. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/export_pipeline.py +4 -0
  22. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/file_analyzer.py +62 -39
  23. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/file_cache.py +5 -4
  24. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/__init__.py +1 -0
  25. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/_c_parser.py +43 -18
  26. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/base.py +3 -17
  27. code2llm-0.5.161/code2llm/core/lang/generic.py +71 -0
  28. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/ruby.py +65 -79
  29. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/ts_parser.py +2 -0
  30. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/large_repo.py +8 -0
  31. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/persistent_cache.py +4 -0
  32. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/refactoring.py +1 -0
  33. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/source_classifier.py +16 -20
  34. code2llm-0.5.161/code2llm/core/streaming/__init__.py +7 -0
  35. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/streaming/incremental.py +1 -0
  36. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/streaming/prioritizer.py +1 -0
  37. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/streaming/scanner.py +41 -42
  38. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/streaming_analyzer.py +42 -55
  39. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/context_exporter.py +20 -1
  40. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/context_view.py +12 -10
  41. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/dashboard_data.py +3 -3
  42. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/evolution/yaml_export.py +30 -56
  43. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/flow_exporter.py +6 -0
  44. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/flow_renderer.py +32 -35
  45. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/html_dashboard.py +1 -0
  46. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/map/details.py +16 -24
  47. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/mermaid/calls.py +15 -13
  48. code2llm-0.5.161/code2llm/exporters/mermaid/compact.py +52 -0
  49. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/mermaid_flow_helpers.py +33 -11
  50. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/planfile_tickets.py +54 -11
  51. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/project_yaml/core.py +15 -32
  52. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/project_yaml/health.py +30 -38
  53. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/project_yaml/hotspots.py +57 -63
  54. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/project_yaml/modules.py +18 -23
  55. code2llm-0.5.161/code2llm/exporters/readme/insights.py +58 -0
  56. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/report_generators.py +18 -10
  57. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/__init__.py +1 -0
  58. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/_render_section_helpers.py +9 -4
  59. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/helpers.py +42 -62
  60. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/metrics.py +1 -0
  61. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/metrics_core.py +45 -51
  62. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/metrics_duplicates.py +1 -0
  63. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/metrics_health.py +1 -0
  64. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/renderer.py +1 -1
  65. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon_view.py +2 -1
  66. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/validate_project.py +14 -21
  67. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/llm_flow/analysis.py +31 -35
  68. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/llm_flow/utils.py +19 -7
  69. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/llm_task.py +79 -46
  70. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/mermaid/fix.py +1 -0
  71. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/mermaid/validation.py +19 -12
  72. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/nlp/__init__.py +1 -1
  73. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/nlp/entity_resolution.py +20 -34
  74. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/patterns/detector.py +1 -0
  75. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/refactor/prompt_engine.py +42 -36
  76. {code2llm-0.5.159 → code2llm-0.5.161/code2llm.egg-info}/PKG-INFO +10 -16
  77. {code2llm-0.5.159 → code2llm-0.5.161}/pyproject.toml +1 -1
  78. code2llm-0.5.159/VERSION +0 -1
  79. code2llm-0.5.159/code2llm/core/lang/generic.py +0 -88
  80. code2llm-0.5.159/code2llm/core/streaming/__init__.py +0 -7
  81. code2llm-0.5.159/code2llm/exporters/mermaid/compact.py +0 -69
  82. code2llm-0.5.159/code2llm/exporters/readme/insights.py +0 -54
  83. {code2llm-0.5.159 → code2llm-0.5.161}/LICENSE +0 -0
  84. {code2llm-0.5.159 → code2llm-0.5.161}/MANIFEST.in +0 -0
  85. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/__main__.py +0 -0
  86. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/__init__.py +0 -0
  87. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/data_analysis.py +0 -0
  88. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/pipeline_classifier.py +0 -0
  89. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/pipeline_resolver.py +0 -0
  90. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/utils/__init__.py +0 -0
  91. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/analysis/utils/ast_helpers.py +0 -0
  92. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/api.py +0 -0
  93. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli.py +0 -0
  94. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_exports/__init__.py +0 -0
  95. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_exports/code2logic.py +0 -0
  96. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_exports/orchestrator_chunked.py +0 -0
  97. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_exports/orchestrator_constants.py +0 -0
  98. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_exports/orchestrator_handlers.py +0 -0
  99. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/cli_parser.py +0 -0
  100. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/ast_registry.py +0 -0
  101. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/config.py +0 -0
  102. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/file_filter.py +0 -0
  103. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/gitignore.py +0 -0
  104. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/incremental.py +0 -0
  105. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/_calls.py +0 -0
  106. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/_complexity.py +0 -0
  107. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/cpp.py +0 -0
  108. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/csharp.py +0 -0
  109. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/go_lang.py +0 -0
  110. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/java.py +0 -0
  111. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/php.py +0 -0
  112. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/rust.py +0 -0
  113. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/ts_extractors.py +0 -0
  114. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/lang/typescript.py +0 -0
  115. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/models.py +0 -0
  116. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/repo_files.py +0 -0
  117. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/streaming/cache.py +0 -0
  118. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/streaming/strategies.py +0 -0
  119. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/core/toon_size_manager.py +0 -0
  120. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/__init__.py +0 -0
  121. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/article_view.py +0 -0
  122. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/base.py +0 -0
  123. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/dashboard_renderer.py +0 -0
  124. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/evolution/__init__.py +0 -0
  125. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/evolution/computation.py +0 -0
  126. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/evolution/constants.py +0 -0
  127. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/evolution/exclusion.py +0 -0
  128. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/evolution/render.py +0 -0
  129. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/evolution_exporter.py +0 -0
  130. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/flow_constants.py +0 -0
  131. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/index_generator/__init__.py +0 -0
  132. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/index_generator/renderer.py +0 -0
  133. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/index_generator/scanner.py +0 -0
  134. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/index_generator.py +0 -0
  135. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/json_exporter.py +0 -0
  136. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/llm_exporter.py +0 -0
  137. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/map/__init__.py +0 -0
  138. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/map/alerts.py +0 -0
  139. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/map/header.py +0 -0
  140. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/map/module_list.py +0 -0
  141. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/map/utils.py +0 -0
  142. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/map/yaml_export.py +0 -0
  143. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/map_exporter.py +0 -0
  144. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/mermaid/__init__.py +0 -0
  145. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/mermaid/classic.py +0 -0
  146. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/mermaid/flow_compact.py +0 -0
  147. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/mermaid/flow_detailed.py +0 -0
  148. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/mermaid/flow_full.py +0 -0
  149. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/mermaid/utils.py +0 -0
  150. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/mermaid_exporter.py +0 -0
  151. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/project_yaml/__init__.py +0 -0
  152. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/project_yaml/constants.py +0 -0
  153. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/project_yaml/evolution.py +0 -0
  154. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/project_yaml_exporter.py +0 -0
  155. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/readme/__init__.py +0 -0
  156. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/readme/content.py +0 -0
  157. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/readme/files.py +0 -0
  158. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/readme/sections.py +0 -0
  159. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/readme_exporter.py +0 -0
  160. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/_render_coupling_helpers.py +0 -0
  161. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/constants.py +0 -0
  162. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon/module_detail.py +0 -0
  163. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/toon.py +0 -0
  164. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/exporters/yaml_exporter.py +0 -0
  165. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/__init__.py +0 -0
  166. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/_utils.py +0 -0
  167. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/llm_flow/__init__.py +0 -0
  168. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/llm_flow/cli.py +0 -0
  169. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/llm_flow/generator.py +0 -0
  170. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/llm_flow/nodes.py +0 -0
  171. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/llm_flow/parsing.py +0 -0
  172. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/llm_flow.py +0 -0
  173. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/mermaid/__init__.py +0 -0
  174. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/mermaid/png.py +0 -0
  175. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/generators/mermaid.py +0 -0
  176. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/nlp/config.py +0 -0
  177. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/nlp/intent_matching.py +0 -0
  178. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/nlp/normalization.py +0 -0
  179. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/nlp/pipeline.py +0 -0
  180. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/parsers/toon_parser.py +0 -0
  181. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/patterns/__init__.py +0 -0
  182. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm/refactor/__init__.py +0 -0
  183. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm.egg-info/SOURCES.txt +0 -0
  184. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm.egg-info/dependency_links.txt +0 -0
  185. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm.egg-info/entry_points.txt +0 -0
  186. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm.egg-info/requires.txt +0 -0
  187. {code2llm-0.5.159 → code2llm-0.5.161}/code2llm.egg-info/top_level.txt +0 -0
  188. {code2llm-0.5.159 → code2llm-0.5.161}/setup.cfg +0 -0
  189. {code2llm-0.5.159 → code2llm-0.5.161}/setup.py +0 -0
  190. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_advanced_analysis.py +0 -0
  191. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_analyzer.py +0 -0
  192. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_cache_invalidation_e2e.py +0 -0
  193. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_calls_toon_export.py +0 -0
  194. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_declarative_collection.py +0 -0
  195. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_deep_analysis.py +0 -0
  196. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_edge_cases.py +0 -0
  197. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_export_cache_flags.py +0 -0
  198. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_file_analyzer_tagging.py +0 -0
  199. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_flow_exporter.py +0 -0
  200. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_format_quality.py +0 -0
  201. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_multilanguage_e2e.py +0 -0
  202. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_nlp_pipeline.py +0 -0
  203. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_nonpython_cc_calls.py +0 -0
  204. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_orchestrator_cache_mtime.py +0 -0
  205. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_persistent_cache.py +0 -0
  206. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_pipeline_detector.py +0 -0
  207. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_planfile_tickets_exporter.py +0 -0
  208. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_project_toon_export.py +0 -0
  209. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_prompt_engine.py +0 -0
  210. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_prompt_txt.py +0 -0
  211. {code2llm-0.5.159 → code2llm-0.5.161}/tests/test_refactoring_engine.py +0 -0
  212. {code2llm-0.5.159 → code2llm-0.5.161}/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.159
3
+ Version: 0.5.161
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
@@ -66,16 +66,17 @@ Dynamic: requires-python
66
66
 
67
67
  ## AI Cost Tracking
68
68
 
69
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.159-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-80.7h-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.161-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-84.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
71
71
 
72
- - 🤖 **LLM usage:** $7.5000 (216 commits)
73
- - 👤 **Human dev:** ~$8072 (80.7h @ $100/h, 30min dedup)
72
+ - 🤖 **LLM usage:** $7.5000 (230 commits)
73
+ - 👤 **Human dev:** ~$8470 (84.7h @ $100/h, 30min dedup)
74
74
 
75
75
  Generated on 2026-05-25 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
76
76
 
77
77
  ---
78
78
 
79
+
79
80
  <!-- generated in 0.00s -->
80
81
 
81
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.
@@ -88,7 +89,6 @@ When you run `code2llm ./ -f all`, the following files are created:
88
89
 
89
90
  | File | Format | Purpose | Key Insights |
90
91
  |------|--------|---------|--------------|
91
- | `planfile-tickets.yaml` | **YAML** | **🎫 Koru-ready ticket feed** - Actionable planfile suggestions from code2llm findings | Import or apply tickets for autonomous execution |
92
92
  | `evolution.toon.yaml` | **YAML** | **📋 Refactoring queue** - Prioritized improvements | 0 refactoring actions needed |
93
93
  | `map.toon.yaml` | **YAML** | **🗺️ Structural map + project header** - Modules, imports, exports, signatures, stats, alerts, hotspots, trend | Project architecture overview |
94
94
 
@@ -114,12 +114,6 @@ code2llm ./ -f toon
114
114
  # Generate all formats (what created these files)
115
115
  code2llm ./ -f all
116
116
 
117
- # Generate planfile suggestions and create executable tickets for Koru
118
- code2llm ./ -f all -o ./project --no-chunk --planfile-apply
119
-
120
- # Or import the generated manifest later
121
- planfile ticket import --from ./project/planfile-tickets.yaml --source code2llm
122
-
123
117
  # LLM-ready context only
124
118
  code2llm ./ -f context
125
119
  ```
@@ -413,10 +407,10 @@ code2llm ./ -f yaml --separate-orphans
413
407
  ---
414
408
 
415
409
  **Generated by**: `code2llm ./ -f all --readme`
416
- **Analysis Date**: 2026-05-06
417
- **Total Functions**: 3800
418
- **Total Classes**: 259
419
- **Modules**: 497
410
+ **Analysis Date**: 2026-05-25
411
+ **Total Functions**: 1312
412
+ **Total Classes**: 143
413
+ **Modules**: 427
420
414
 
421
415
  For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
422
416
 
@@ -2,16 +2,17 @@
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.159-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-80.7h-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.161-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-84.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
7
7
 
8
- - 🤖 **LLM usage:** $7.5000 (216 commits)
9
- - 👤 **Human dev:** ~$8072 (80.7h @ $100/h, 30min dedup)
8
+ - 🤖 **LLM usage:** $7.5000 (230 commits)
9
+ - 👤 **Human dev:** ~$8470 (84.7h @ $100/h, 30min dedup)
10
10
 
11
11
  Generated on 2026-05-25 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
12
12
 
13
13
  ---
14
14
 
15
+
15
16
  <!-- generated in 0.00s -->
16
17
 
17
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.
@@ -24,7 +25,6 @@ When you run `code2llm ./ -f all`, the following files are created:
24
25
 
25
26
  | File | Format | Purpose | Key Insights |
26
27
  |------|--------|---------|--------------|
27
- | `planfile-tickets.yaml` | **YAML** | **🎫 Koru-ready ticket feed** - Actionable planfile suggestions from code2llm findings | Import or apply tickets for autonomous execution |
28
28
  | `evolution.toon.yaml` | **YAML** | **📋 Refactoring queue** - Prioritized improvements | 0 refactoring actions needed |
29
29
  | `map.toon.yaml` | **YAML** | **🗺️ Structural map + project header** - Modules, imports, exports, signatures, stats, alerts, hotspots, trend | Project architecture overview |
30
30
 
@@ -50,12 +50,6 @@ code2llm ./ -f toon
50
50
  # Generate all formats (what created these files)
51
51
  code2llm ./ -f all
52
52
 
53
- # Generate planfile suggestions and create executable tickets for Koru
54
- code2llm ./ -f all -o ./project --no-chunk --planfile-apply
55
-
56
- # Or import the generated manifest later
57
- planfile ticket import --from ./project/planfile-tickets.yaml --source code2llm
58
-
59
53
  # LLM-ready context only
60
54
  code2llm ./ -f context
61
55
  ```
@@ -349,10 +343,10 @@ code2llm ./ -f yaml --separate-orphans
349
343
  ---
350
344
 
351
345
  **Generated by**: `code2llm ./ -f all --readme`
352
- **Analysis Date**: 2026-05-06
353
- **Total Functions**: 3800
354
- **Total Classes**: 259
355
- **Modules**: 497
346
+ **Analysis Date**: 2026-05-25
347
+ **Total Functions**: 1312
348
+ **Total Classes**: 143
349
+ **Modules**: 427
356
350
 
357
351
  For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
358
352
 
@@ -0,0 +1 @@
1
+ 0.5.161
@@ -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.159"
11
+ __version__ = "0.5.161"
12
12
  __author__ = "STTS Project"
13
13
 
14
14
  # Core analysis components (lightweight, always needed)
@@ -4,6 +4,26 @@ Extracted to keep data_analysis.py under the MI hard-gate threshold.
4
4
  All functions here are module-private (leading underscore) and called
5
5
  exclusively from DataAnalyzer in data_analysis.py.
6
6
  """
7
+ # ── Data pipeline helpers ─────────────────────────────────────────────────────
8
+ # _find_data_pipelines() : detect sequential transform chains from call graph.
9
+ # ── State-machine helpers ─────────────────────────────────────────────────────
10
+ # _is_state_func() : heuristic; True if name contains state keyword.
11
+ # _state_affected_by() : callers that mutate the given function's state.
12
+ # _find_state_patterns() : aggregate state-machine detections per module.
13
+ # ── Data-dependency helpers ───────────────────────────────────────────────────
14
+ # _find_data_dependencies() : build producer→consumer edges from shared types.
15
+ # ── Event-flow helpers ────────────────────────────────────────────────────────
16
+ # _is_event_func() : heuristic; True if name contains event keyword.
17
+ # _event_handlers() : functions that consume outputs of an event source.
18
+ # _find_event_flows() : aggregate event-flow detections across the project.
19
+ # ── Type-inference helpers ────────────────────────────────────────────────────
20
+ # _detect_types_from_name(): keyword-match on function name + docstring.
21
+ # _create_type_entry() : initialise a type-usage dict entry.
22
+ # _update_type_stats() : accumulate consumed/produced counts per type.
23
+ # _infer_parameter_types() : extract type hints from function parameters.
24
+ # _infer_return_types() : extract type hints from function return annotation.
25
+ # _analyze_data_types() : top-level driver; returns list of type-usage entries.
26
+ # _get_function_data_types(): merge name-based + annotation-based type signals.
7
27
 
8
28
  from typing import Any, Dict, List
9
29
  from code2llm.core.models import AnalysisResult
@@ -42,32 +62,38 @@ def _find_data_pipelines(result: AnalysisResult, categorize_fn, make_stage_fn, m
42
62
  return pipelines
43
63
 
44
64
 
65
+ _STATE_INDICATORS = ["state", "status", "mode", "phase", "lifecycle", "session", "context"]
66
+ _TRANSITION_INDICATORS = ["transition", "change", "update", "set_state", "switch"]
67
+
68
+
69
+ def _is_state_func(name_lower: str) -> bool:
70
+ """Return True if the function name suggests state management."""
71
+ return any(ind in name_lower for ind in _STATE_INDICATORS + _TRANSITION_INDICATORS)
72
+
73
+
74
+ def _state_affected_by(func, functions: dict) -> list:
75
+ """Return calls that look like state-managing functions."""
76
+ return [
77
+ call for call in list(func.calls)[:10]
78
+ if (cf := functions.get(call)) and any(ind in cf.name.lower() for ind in _STATE_INDICATORS)
79
+ ]
80
+
81
+
45
82
  def _find_state_patterns(result: AnalysisResult) -> list:
46
83
  """Find state management patterns."""
47
84
  patterns: list = []
48
- state_indicators = ["state", "status", "mode", "phase", "lifecycle", "session", "context"]
49
- transition_indicators = ["transition", "change", "update", "set_state", "switch"]
50
-
51
85
  for func_name, func in result.functions.items():
52
86
  name_lower = func.name.lower()
53
- if any(ind in name_lower for ind in state_indicators + transition_indicators):
54
- affected_states = []
55
- for call in list(func.calls)[:10]:
56
- call_func = result.functions.get(call)
57
- if call_func and any(ind in call_func.name.lower() for ind in state_indicators):
58
- affected_states.append(call)
59
- patterns.append(
60
- {
61
- "function": func_name,
62
- "type": "state_manager"
63
- if "set" in name_lower or "update" in name_lower
64
- else "state_reader",
65
- "affects_states": affected_states[:5],
66
- "description": func.docstring[:150] if func.docstring else "N/A",
67
- }
68
- )
69
- if len(patterns) >= 20:
70
- break
87
+ if not _is_state_func(name_lower):
88
+ continue
89
+ patterns.append({
90
+ "function": func_name,
91
+ "type": "state_manager" if ("set" in name_lower or "update" in name_lower) else "state_reader",
92
+ "affects_states": _state_affected_by(func, result.functions)[:5],
93
+ "description": func.docstring[:150] if func.docstring else "N/A",
94
+ })
95
+ if len(patterns) >= 20:
96
+ break
71
97
  return patterns
72
98
 
73
99
 
@@ -95,33 +121,43 @@ def _find_data_dependencies(result: AnalysisResult) -> list:
95
121
  return deps[:15]
96
122
 
97
123
 
124
+ _EVENT_INDICATORS = ["event", "emit", "trigger", "notify", "callback", "handler", "listen", "subscribe"]
125
+ _HOOK_INDICATORS = ["hook", "on_", "before_", "after_", "pre_", "post_"]
126
+ _EVENT_HANDLER_WORDS = _EVENT_INDICATORS + ["handle", "process"]
127
+
128
+
129
+ def _is_event_func(name_lower: str) -> bool:
130
+ """Return True if the function name suggests event handling or emission."""
131
+ return any(ind in name_lower for ind in _EVENT_INDICATORS) or any(
132
+ name_lower.startswith(ind) for ind in _HOOK_INDICATORS
133
+ )
134
+
135
+
136
+ def _event_handlers(func, functions: dict) -> list:
137
+ """Return list of called functions that look like event handlers."""
138
+ result = []
139
+ for called in list(func.calls)[:10]:
140
+ cf = functions.get(called)
141
+ if cf and any(ind in cf.name.lower() for ind in _EVENT_HANDLER_WORDS):
142
+ result.append(called)
143
+ return result
144
+
145
+
98
146
  def _find_event_flows(result: AnalysisResult) -> list:
99
147
  """Find event-driven patterns."""
100
148
  flows: list = []
101
- event_indicators = ["event", "emit", "trigger", "notify", "callback", "handler", "listen", "subscribe"]
102
- hook_indicators = ["hook", "on_", "before_", "after_", "pre_", "post_"]
103
149
  for func_name, func in result.functions.items():
104
150
  name_lower = func.name.lower()
105
- if any(ind in name_lower for ind in event_indicators) or any(
106
- name_lower.startswith(ind) for ind in hook_indicators
107
- ):
108
- handlers = []
109
- for called in list(func.calls)[:10]:
110
- called_func = result.functions.get(called)
111
- if called_func and any(
112
- ind in called_func.name.lower() for ind in event_indicators + ["handle", "process"]
113
- ):
114
- handlers.append(called)
115
- flows.append(
116
- {
117
- "event_source": func_name,
118
- "type": "emitter" if "emit" in name_lower or "trigger" in name_lower else "handler",
119
- "handlers": handlers[:5],
120
- "description": func.docstring[:150] if func.docstring else "N/A",
121
- }
122
- )
123
- if len(flows) >= 20:
124
- break
151
+ if not _is_event_func(name_lower):
152
+ continue
153
+ flows.append({
154
+ "event_source": func_name,
155
+ "type": "emitter" if ("emit" in name_lower or "trigger" in name_lower) else "handler",
156
+ "handlers": _event_handlers(func, result.functions)[:5],
157
+ "description": func.docstring[:150] if func.docstring else "N/A",
158
+ })
159
+ if len(flows) >= 20:
160
+ break
125
161
  return flows
126
162
 
127
163
 
@@ -222,26 +258,26 @@ def _analyze_data_types(result: AnalysisResult) -> list:
222
258
  # ---------------------------------------------------------------------------
223
259
 
224
260
 
261
+ _NAME_TYPE_KEYWORDS: List[tuple] = [
262
+ ("list", ("list", "items")),
263
+ ("dict", ("dict", "map")),
264
+ ("str", ("text", "string")),
265
+ ("int", ("count", "index")),
266
+ ]
267
+ _DOC_TYPE_KEYWORDS: List[tuple] = [
268
+ ("list", ("list",)),
269
+ ("dict", ("dict",)),
270
+ ("str", ("string", "text")),
271
+ ]
272
+
273
+
225
274
  def _get_function_data_types(func) -> list:
226
275
  """Derive data type labels for a function from its name and docstring."""
227
- types: List[str] = []
228
276
  name = func.name.lower()
229
- if "list" in name or "items" in name:
230
- types.append("list")
231
- if "dict" in name or "map" in name:
232
- types.append("dict")
233
- if "text" in name or "string" in name:
234
- types.append("str")
235
- if "count" in name or "index" in name:
236
- types.append("int")
277
+ types = [dt for dt, kws in _NAME_TYPE_KEYWORDS if any(kw in name for kw in kws)]
237
278
  if func.docstring:
238
279
  doc = func.docstring.lower()
239
- if "list" in doc:
240
- types.append("list")
241
- if "dict" in doc:
242
- types.append("dict")
243
- if "string" in doc or "text" in doc:
244
- types.append("str")
280
+ types += [dt for dt, kws in _DOC_TYPE_KEYWORDS if any(kw in doc for kw in kws)]
245
281
  return list(set(types))
246
282
 
247
283
 
@@ -306,55 +342,51 @@ def _identify_process_patterns(result: AnalysisResult) -> list:
306
342
  return sorted(res, key=lambda x: x["count"], reverse=True)
307
343
 
308
344
 
309
- def _analyze_optimization_opportunities(
310
- result: AnalysisResult, data_types: list, dfg: dict
311
- ) -> dict:
312
- """Analyze optimization opportunities in data handling."""
313
- opt: Dict[str, Any] = {
314
- "potential_score": 0.0,
315
- "type_consolidation": [],
316
- "process_consolidation": [],
317
- "hub_optimization": [],
318
- "recommendations": [],
319
- }
345
+ def _type_consolidations(data_types: list) -> list:
346
+ """Find groups of similar data types that could be consolidated."""
320
347
  similar: Dict[str, list] = {}
321
348
  for dt in data_types:
322
349
  sig = ",".join(sorted(dt["detected_types"]))
323
- if sig not in similar:
324
- similar[sig] = []
325
- similar[sig].append(dt)
326
- for _sig, sims in similar.items():
350
+ similar.setdefault(sig, []).append(dt)
351
+ result = []
352
+ for sig, sims in similar.items():
327
353
  if len(sims) > 1:
328
354
  usage = sum(s["usage_count"] for s in sims)
329
355
  if usage > 10:
330
- opt["type_consolidation"].append(
331
- {
332
- "type_signature": _sig,
333
- "similar_types": [s["type_name"] for s in sims],
334
- "total_usage": usage,
335
- "potential_reduction": len(sims) - 1,
336
- }
337
- )
338
- for p in _identify_process_patterns(result):
339
- if p["count"] > 5:
340
- opt["process_consolidation"].append(
341
- {
342
- "pattern_type": p["pattern_type"],
343
- "function_count": p["count"],
344
- "potential_reduction": p["count"] // 3,
345
- }
346
- )
347
- for hub in [n for n in dfg["nodes"].values() if n["is_hub"]][:10]:
348
- opt["hub_optimization"].append(
349
- {
350
- "function": hub["id"],
351
- "connections": hub["in_degree"] + hub["out_degree"],
352
- "optimization_type": "split" if hub["out_degree"] > 10 else "cache",
353
- }
354
- )
355
- opt["potential_score"] = (
356
- len(opt["type_consolidation"]) * 10
357
- + len(opt["process_consolidation"]) * 15
358
- + len(opt["hub_optimization"]) * 5
359
- ) / 100.0
360
- return opt
356
+ result.append({"type_signature": sig, "similar_types": [s["type_name"] for s in sims],
357
+ "total_usage": usage, "potential_reduction": len(sims) - 1})
358
+ return result
359
+
360
+
361
+ def _process_consolidations(result: AnalysisResult) -> list:
362
+ """Find repeated process patterns that could be consolidated."""
363
+ return [
364
+ {"pattern_type": p["pattern_type"], "function_count": p["count"],
365
+ "potential_reduction": p["count"] // 3}
366
+ for p in _identify_process_patterns(result) if p["count"] > 5
367
+ ]
368
+
369
+
370
+ def _hub_optimizations(dfg: dict) -> list:
371
+ """Identify hub nodes that are candidates for splitting or caching."""
372
+ return [
373
+ {"function": hub["id"], "connections": hub["in_degree"] + hub["out_degree"],
374
+ "optimization_type": "split" if hub["out_degree"] > 10 else "cache"}
375
+ for hub in list(n for n in dfg["nodes"].values() if n["is_hub"])[:10]
376
+ ]
377
+
378
+
379
+ def _analyze_optimization_opportunities(
380
+ result: AnalysisResult, data_types: list, dfg: dict
381
+ ) -> dict:
382
+ """Analyze optimization opportunities in data handling."""
383
+ type_cons = _type_consolidations(data_types)
384
+ proc_cons = _process_consolidations(result)
385
+ hub_opts = _hub_optimizations(dfg)
386
+ return {
387
+ "potential_score": (len(type_cons) * 10 + len(proc_cons) * 15 + len(hub_opts) * 5) / 100.0,
388
+ "type_consolidation": type_cons,
389
+ "process_consolidation": proc_cons,
390
+ "hub_optimization": hub_opts,
391
+ "recommendations": [],
392
+ }
@@ -204,4 +204,5 @@ class CallGraphExtractor(ast.NodeVisitor):
204
204
  return None
205
205
 
206
206
  def _expr_to_str(self, node: ast.AST) -> str:
207
+ """Unparse an AST expression node to a source string (empty string on failure)."""
207
208
  return ast_unparse(node, default_none="")
@@ -125,7 +125,6 @@ class CFGExtractor(ast.NodeVisitor):
125
125
  # Visit then branch
126
126
  then_last = []
127
127
  for stmt in node.body:
128
- prev = self.current_node
129
128
  self.current_node = branch_entry
130
129
  self.visit(stmt)
131
130
  then_last.append(self.current_node)
@@ -136,7 +135,6 @@ class CFGExtractor(ast.NodeVisitor):
136
135
  if node.orelse:
137
136
  branch_entry = cond_node
138
137
  for stmt in node.orelse:
139
- prev = self.current_node
140
138
  self.current_node = branch_entry
141
139
  self.visit(stmt)
142
140
  else_last.append(self.current_node)
@@ -270,10 +268,11 @@ class CFGExtractor(ast.NodeVisitor):
270
268
  """Extract condition as string."""
271
269
  try:
272
270
  return ast.unparse(node) if hasattr(ast, "unparse") else str(node)[:50]
273
- except:
271
+ except Exception:
274
272
  return str(node)[:50]
275
273
 
276
274
  def _expr_to_str(self, node: ast.AST) -> str:
275
+ """Unparse an AST expression node to a source string."""
277
276
  return ast_unparse(node)
278
277
 
279
278
  def _format_except(self, handler: ast.ExceptHandler) -> str:
@@ -8,6 +8,7 @@ class CouplingAnalyzer:
8
8
  """Analyze coupling between modules."""
9
9
 
10
10
  def __init__(self, result: AnalysisResult):
11
+ """Initialise with the analysis result to analyse for coupling."""
11
12
  self.result = result
12
13
 
13
14
  def analyze(self) -> Dict[str, Any]:
@@ -230,6 +230,7 @@ class DFGExtractor(ast.NodeVisitor):
230
230
  return names
231
231
 
232
232
  def _expr_to_str(self, node: ast.AST) -> str:
233
+ """Unparse an AST expression node to a source string."""
233
234
  return ast_unparse(node)
234
235
 
235
236
  def _build_data_flow_edges(self):
@@ -191,6 +191,22 @@ class PipelineDetector:
191
191
  # ------------------------------------------------------------------
192
192
  # path finding
193
193
  # ------------------------------------------------------------------
194
+ def _process_components(
195
+ self, graph: nx.DiGraph, paths: List[List[str]], used_nodes: Set[str]
196
+ ) -> None:
197
+ """Extend paths with longest paths from each weakly connected component."""
198
+ for component in nx.weakly_connected_components(graph):
199
+ if len(component) < MIN_PIPELINE_LENGTH:
200
+ continue
201
+ if len(component & used_nodes) > len(component) * 0.5:
202
+ continue
203
+ path = self._longest_path_in_dag(graph.subgraph(component))
204
+ if len(path) >= MIN_PIPELINE_LENGTH:
205
+ new_overlap = sum(1 for n in path if n in used_nodes)
206
+ if new_overlap <= len(path) * 0.5:
207
+ paths.append(path)
208
+ used_nodes.update(path)
209
+
194
210
  def _find_pipeline_paths(self, graph: nx.DiGraph) -> List[List[str]]:
195
211
  """Find longest paths in the call graph as pipeline candidates.
196
212
 
@@ -200,41 +216,17 @@ class PipelineDetector:
200
216
  3. For each source, find longest simple path to any sink
201
217
  4. Also consider longest paths in each weakly connected component
202
218
  """
203
- paths: List[List[str]] = []
204
-
205
- # Get source nodes (in-degree 0) as potential pipeline entry points
206
219
  sources = [n for n in graph.nodes() if graph.in_degree(n) == 0]
207
-
208
- # If no natural sources, use nodes with low in-degree
209
220
  if not sources:
210
221
  sources = sorted(graph.nodes(), key=lambda n: graph.in_degree(n))[:5]
211
-
212
- # Try to find longest paths from each source
222
+ paths: List[List[str]] = []
213
223
  used_nodes: Set[str] = set()
214
224
  for source in sources:
215
225
  best_path = self._longest_path_from(graph, source, used_nodes)
216
226
  if len(best_path) >= MIN_PIPELINE_LENGTH:
217
227
  paths.append(best_path)
218
228
  used_nodes.update(best_path)
219
-
220
- # Also try: for each weakly connected component, find the longest path
221
- for component in nx.weakly_connected_components(graph):
222
- if len(component) < MIN_PIPELINE_LENGTH:
223
- continue
224
- # Skip if heavily overlapping with existing paths
225
- overlap = len(component & used_nodes)
226
- if overlap > len(component) * 0.5:
227
- continue
228
-
229
- subgraph = graph.subgraph(component)
230
- path = self._longest_path_in_dag(subgraph)
231
- if len(path) >= MIN_PIPELINE_LENGTH:
232
- # Check overlap with existing paths
233
- new_overlap = sum(1 for n in path if n in used_nodes)
234
- if new_overlap <= len(path) * 0.5:
235
- paths.append(path)
236
- used_nodes.update(path)
237
-
229
+ self._process_components(graph, paths, used_nodes)
238
230
  return paths
239
231
 
240
232
  def _longest_path_from(