code2llm 0.5.111__tar.gz → 0.5.113__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 (137) hide show
  1. {code2llm-0.5.111 → code2llm-0.5.113}/PKG-INFO +7 -51
  2. {code2llm-0.5.111 → code2llm-0.5.113}/README.md +6 -50
  3. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/__init__.py +1 -1
  4. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/cli_exports/formats.py +9 -18
  5. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/cli_exports/orchestrator.py +0 -1
  6. code2llm-0.5.113/code2llm/exporters/project_yaml/__init__.py +15 -0
  7. code2llm-0.5.113/code2llm/exporters/project_yaml/constants.py +15 -0
  8. code2llm-0.5.113/code2llm/exporters/project_yaml/core.py +118 -0
  9. code2llm-0.5.113/code2llm/exporters/project_yaml/evolution.py +46 -0
  10. code2llm-0.5.113/code2llm/exporters/project_yaml/health.py +103 -0
  11. code2llm-0.5.113/code2llm/exporters/project_yaml/hotspots.py +106 -0
  12. code2llm-0.5.113/code2llm/exporters/project_yaml/modules.py +151 -0
  13. code2llm-0.5.113/code2llm/exporters/project_yaml_exporter.py +15 -0
  14. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/nlp/__init__.py +1 -1
  15. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm.egg-info/PKG-INFO +7 -51
  16. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm.egg-info/SOURCES.txt +8 -0
  17. {code2llm-0.5.111 → code2llm-0.5.113}/pyproject.toml +1 -1
  18. {code2llm-0.5.111 → code2llm-0.5.113}/setup.py +1 -1
  19. code2llm-0.5.113/tests/test_calls_toon_export.py +241 -0
  20. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_project_toon_export.py +1 -1
  21. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_prompt_txt.py +2 -1
  22. code2llm-0.5.111/code2llm/exporters/project_yaml_exporter.py +0 -513
  23. {code2llm-0.5.111 → code2llm-0.5.113}/LICENSE +0 -0
  24. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/__main__.py +0 -0
  25. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/__init__.py +0 -0
  26. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/call_graph.py +0 -0
  27. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/cfg.py +0 -0
  28. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/coupling.py +0 -0
  29. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/data_analysis.py +0 -0
  30. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/dfg.py +0 -0
  31. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/pipeline_detector.py +0 -0
  32. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/side_effects.py +0 -0
  33. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/smells.py +0 -0
  34. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/type_inference.py +0 -0
  35. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/utils/__init__.py +0 -0
  36. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/analysis/utils/ast_helpers.py +0 -0
  37. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/api.py +0 -0
  38. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/cli.py +0 -0
  39. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/cli_analysis.py +0 -0
  40. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/cli_commands.py +0 -0
  41. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/cli_exports/__init__.py +0 -0
  42. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/cli_exports/code2logic.py +0 -0
  43. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/cli_exports/prompt.py +0 -0
  44. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/cli_parser.py +0 -0
  45. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/__init__.py +0 -0
  46. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/analyzer.py +0 -0
  47. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/ast_registry.py +0 -0
  48. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/config.py +0 -0
  49. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/export_pipeline.py +0 -0
  50. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/file_analyzer.py +0 -0
  51. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/file_cache.py +0 -0
  52. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/file_filter.py +0 -0
  53. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/gitignore.py +0 -0
  54. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/incremental.py +0 -0
  55. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/__init__.py +0 -0
  56. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/base.py +0 -0
  57. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/cpp.py +0 -0
  58. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/csharp.py +0 -0
  59. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/generic.py +0 -0
  60. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/go_lang.py +0 -0
  61. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/java.py +0 -0
  62. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/php.py +0 -0
  63. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/ruby.py +0 -0
  64. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/rust.py +0 -0
  65. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/ts_extractors.py +0 -0
  66. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/ts_parser.py +0 -0
  67. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/lang/typescript.py +0 -0
  68. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/large_repo.py +0 -0
  69. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/models.py +0 -0
  70. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/refactoring.py +0 -0
  71. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/repo_files.py +0 -0
  72. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/streaming/__init__.py +0 -0
  73. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/streaming/cache.py +0 -0
  74. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/streaming/incremental.py +0 -0
  75. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/streaming/prioritizer.py +0 -0
  76. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/streaming/scanner.py +0 -0
  77. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/streaming/strategies.py +0 -0
  78. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/streaming_analyzer.py +0 -0
  79. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/core/toon_size_manager.py +0 -0
  80. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/__init__.py +0 -0
  81. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/article_view.py +0 -0
  82. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/base.py +0 -0
  83. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/context_exporter.py +0 -0
  84. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/context_view.py +0 -0
  85. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/evolution_exporter.py +0 -0
  86. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/flow_constants.py +0 -0
  87. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/flow_exporter.py +0 -0
  88. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/flow_renderer.py +0 -0
  89. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/html_dashboard.py +0 -0
  90. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/index_generator.py +0 -0
  91. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/json_exporter.py +0 -0
  92. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/llm_exporter.py +0 -0
  93. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/map_exporter.py +0 -0
  94. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/mermaid_exporter.py +0 -0
  95. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
  96. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/readme_exporter.py +0 -0
  97. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/report_generators.py +0 -0
  98. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/toon/__init__.py +0 -0
  99. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/toon/helpers.py +0 -0
  100. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/toon/metrics.py +0 -0
  101. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/toon/module_detail.py +0 -0
  102. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/toon/renderer.py +0 -0
  103. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/toon.py +0 -0
  104. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/toon_view.py +0 -0
  105. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/validate_project.py +0 -0
  106. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/exporters/yaml_exporter.py +0 -0
  107. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/generators/__init__.py +0 -0
  108. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/generators/llm_flow.py +0 -0
  109. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/generators/llm_task.py +0 -0
  110. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/generators/mermaid.py +0 -0
  111. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/nlp/config.py +0 -0
  112. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/nlp/entity_resolution.py +0 -0
  113. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/nlp/intent_matching.py +0 -0
  114. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/nlp/normalization.py +0 -0
  115. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/nlp/pipeline.py +0 -0
  116. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/patterns/__init__.py +0 -0
  117. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/patterns/detector.py +0 -0
  118. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/refactor/__init__.py +0 -0
  119. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm/refactor/prompt_engine.py +0 -0
  120. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm.egg-info/dependency_links.txt +0 -0
  121. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm.egg-info/entry_points.txt +0 -0
  122. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm.egg-info/requires.txt +0 -0
  123. {code2llm-0.5.111 → code2llm-0.5.113}/code2llm.egg-info/top_level.txt +0 -0
  124. {code2llm-0.5.111 → code2llm-0.5.113}/setup.cfg +0 -0
  125. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_advanced_analysis.py +0 -0
  126. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_analyzer.py +0 -0
  127. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_deep_analysis.py +0 -0
  128. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_edge_cases.py +0 -0
  129. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_flow_exporter.py +0 -0
  130. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_format_quality.py +0 -0
  131. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_multilanguage_e2e.py +0 -0
  132. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_nlp_pipeline.py +0 -0
  133. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_nonpython_cc_calls.py +0 -0
  134. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_pipeline_detector.py +0 -0
  135. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_prompt_engine.py +0 -0
  136. {code2llm-0.5.111 → code2llm-0.5.113}/tests/test_refactoring_engine.py +0 -0
  137. {code2llm-0.5.111 → code2llm-0.5.113}/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.111
3
+ Version: 0.5.113
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
@@ -67,11 +67,11 @@ 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.111-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-53.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
70
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.113-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-55.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
72
72
 
73
- - 🤖 **LLM usage:** $7.5000 (154 commits)
74
- - 👤 **Human dev:** ~$5373 (53.7h @ $100/h, 30min dedup)
73
+ - 🤖 **LLM usage:** $7.5000 (162 commits)
74
+ - 👤 **Human dev:** ~$5505 (55.0h @ $100/h, 30min dedup)
75
75
 
76
76
  Generated on 2026-04-18 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
77
77
 
@@ -112,43 +112,6 @@ code2llm ./ -f all
112
112
  code2llm ./ -f context
113
113
  ```
114
114
 
115
- ### Generating Individual Files
116
-
117
- ```bash
118
- # Health diagnostics only
119
- code2llm ./ -f toon
120
-
121
- # Structural map only
122
- code2llm ./ -f map
123
-
124
- # Refactoring queue only
125
- code2llm ./ -f evolution
126
-
127
- # LLM narrative only
128
- code2llm ./ -f context
129
-
130
- # Call graph (mermaid + YAML)
131
- code2llm ./ -f mermaid
132
-
133
- # Call graph YAML only (no visualization)
134
- code2llm ./ -f calls
135
-
136
- # Flow diagrams only
137
- code2llm ./ -f flow
138
-
139
- # Standard YAML export
140
- code2llm ./ -f yaml
141
-
142
- # JSON export
143
- code2llm ./ -f json
144
-
145
- # Combined formats
146
- code2llm ./ -f toon,map,evolution,context
147
-
148
- # All core formats
149
- code2llm ./ -f all
150
- ```
151
-
152
115
  ### Performance Options
153
116
  ```bash
154
117
  # Fast analysis for large projects
@@ -300,7 +263,6 @@ cat context.md | xclip -sel clip # Linux
300
263
  - `flow.mmd` - Detailed control flow with complexity colors
301
264
  - `calls.mmd` - Simple call graph
302
265
  - `compact_flow.mmd` - High-level module view
303
- - `calls.yaml` - Structured call graph data (YAML format)
304
266
  - `*.png` - Pre-rendered images
305
267
 
306
268
  **Example usage**:
@@ -311,12 +273,6 @@ xdg-open flow.png # Linux
311
273
 
312
274
  # Edit in Mermaid Live Editor
313
275
  # Copy content of .mmd files to https://mermaid.live
314
-
315
- # Generate only call graph files (mermaid + YAML)
316
- code2llm ./ -f mermaid
317
-
318
- # Generate calls.yaml standalone (no visualization files)
319
- code2llm ./ -f calls
320
276
  ```
321
277
 
322
278
  ## 🔍 Common Analysis Patterns
@@ -446,9 +402,9 @@ code2llm ./ -f yaml --separate-orphans
446
402
 
447
403
  **Generated by**: `code2llm ./ -f all --readme`
448
404
  **Analysis Date**: 2026-04-18
449
- **Total Functions**: 1014
405
+ **Total Functions**: 1041
450
406
  **Total Classes**: 111
451
- **Modules**: 131
407
+ **Modules**: 135
452
408
 
453
409
  For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
454
410
 
@@ -3,11 +3,11 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.111-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-53.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.113-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-55.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $7.5000 (154 commits)
10
- - 👤 **Human dev:** ~$5373 (53.7h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $7.5000 (162 commits)
10
+ - 👤 **Human dev:** ~$5505 (55.0h @ $100/h, 30min dedup)
11
11
 
12
12
  Generated on 2026-04-18 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
@@ -48,43 +48,6 @@ code2llm ./ -f all
48
48
  code2llm ./ -f context
49
49
  ```
50
50
 
51
- ### Generating Individual Files
52
-
53
- ```bash
54
- # Health diagnostics only
55
- code2llm ./ -f toon
56
-
57
- # Structural map only
58
- code2llm ./ -f map
59
-
60
- # Refactoring queue only
61
- code2llm ./ -f evolution
62
-
63
- # LLM narrative only
64
- code2llm ./ -f context
65
-
66
- # Call graph (mermaid + YAML)
67
- code2llm ./ -f mermaid
68
-
69
- # Call graph YAML only (no visualization)
70
- code2llm ./ -f calls
71
-
72
- # Flow diagrams only
73
- code2llm ./ -f flow
74
-
75
- # Standard YAML export
76
- code2llm ./ -f yaml
77
-
78
- # JSON export
79
- code2llm ./ -f json
80
-
81
- # Combined formats
82
- code2llm ./ -f toon,map,evolution,context
83
-
84
- # All core formats
85
- code2llm ./ -f all
86
- ```
87
-
88
51
  ### Performance Options
89
52
  ```bash
90
53
  # Fast analysis for large projects
@@ -236,7 +199,6 @@ cat context.md | xclip -sel clip # Linux
236
199
  - `flow.mmd` - Detailed control flow with complexity colors
237
200
  - `calls.mmd` - Simple call graph
238
201
  - `compact_flow.mmd` - High-level module view
239
- - `calls.yaml` - Structured call graph data (YAML format)
240
202
  - `*.png` - Pre-rendered images
241
203
 
242
204
  **Example usage**:
@@ -247,12 +209,6 @@ xdg-open flow.png # Linux
247
209
 
248
210
  # Edit in Mermaid Live Editor
249
211
  # Copy content of .mmd files to https://mermaid.live
250
-
251
- # Generate only call graph files (mermaid + YAML)
252
- code2llm ./ -f mermaid
253
-
254
- # Generate calls.yaml standalone (no visualization files)
255
- code2llm ./ -f calls
256
212
  ```
257
213
 
258
214
  ## 🔍 Common Analysis Patterns
@@ -382,9 +338,9 @@ code2llm ./ -f yaml --separate-orphans
382
338
 
383
339
  **Generated by**: `code2llm ./ -f all --readme`
384
340
  **Analysis Date**: 2026-04-18
385
- **Total Functions**: 1014
341
+ **Total Functions**: 1041
386
342
  **Total Classes**: 111
387
- **Modules**: 131
343
+ **Modules**: 135
388
344
 
389
345
  For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
390
346
 
@@ -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.111"
11
+ __version__ = "0.5.113"
12
12
  __author__ = "STTS Project"
13
13
 
14
14
  # Core analysis components (lightweight, always needed)
@@ -71,8 +71,10 @@ def _export_project_yaml(args, result, output_dir: Path):
71
71
 
72
72
  def _export_project_toon(args, result, output_dir: Path):
73
73
  """Export project.toon.yaml directly from the current analysis result."""
74
+ from ..exporters.project_yaml.evolution import load_previous_evolution
75
+
74
76
  project_yaml_exporter = ProjectYAMLExporter()
75
- prev_evolution = project_yaml_exporter._load_previous_evolution(output_dir / 'project.yaml')
77
+ prev_evolution = load_previous_evolution(output_dir / 'project.yaml')
76
78
  data = project_yaml_exporter._build_project_yaml(result, prev_evolution)
77
79
 
78
80
  exporter = ToonViewGenerator()
@@ -214,21 +216,10 @@ def _export_mermaid_pngs(args, output_dir: Path) -> None:
214
216
 
215
217
 
216
218
  def _export_calls(args, result, output_dir: Path):
217
- """Export standalone calls.yaml (call graph as structured YAML).
218
-
219
- Generates calls.yaml without any Mermaid files — useful for programmatic
220
- analysis of call graphs without visualization overhead.
221
- """
222
- yaml_exporter = YAMLExporter()
223
- yaml_exporter.export_calls(result, str(output_dir / 'calls.yaml'))
224
- if args.verbose:
225
- print(f" - CALLS (call graph YAML): {output_dir / 'calls.yaml'}")
226
-
227
-
228
- def _export_calls_toon(args, result, output_dir: Path):
229
- """Export calls.toon.yaml (call graph in human-readable toon format).
219
+ """Export standalone calls.toon.yaml (call graph in toon format).
230
220
 
231
- Generates calls.toon.yaml with hubs, modules, and edges sections.
221
+ Generates calls.toon.yaml in human-readable toon format with hubs, modules,
222
+ and edges sections — useful for programmatic analysis of call graphs.
232
223
  """
233
224
  yaml_exporter = YAMLExporter()
234
225
  yaml_exporter.export_calls_toon(result, str(output_dir / 'calls.toon.yaml'))
@@ -256,9 +247,9 @@ def _export_mermaid(args, result, output_dir: Path):
256
247
  exporter.export_call_graph(result, str(output_dir / 'calls.mmd'))
257
248
  exporter.export_compact(result, str(output_dir / 'compact_flow.mmd'))
258
249
 
259
- # Export calls.yaml (structured call graph data)
250
+ # Export calls.toon.yaml (structured call graph data in toon format)
260
251
  yaml_exporter = YAMLExporter()
261
- yaml_exporter.export_calls(result, str(output_dir / 'calls.yaml'))
252
+ yaml_exporter.export_calls_toon(result, str(output_dir / 'calls.toon.yaml'))
262
253
 
263
254
  if args.verbose:
264
255
  files = ['flow.mmd']
@@ -266,7 +257,7 @@ def _export_mermaid(args, result, output_dir: Path):
266
257
  files.append('flow_detailed.mmd')
267
258
  if getattr(args, 'flow_full', False):
268
259
  files.append('flow_full.mmd')
269
- files.extend(['calls.mmd', 'compact_flow.mmd', 'calls.yaml'])
260
+ files.extend(['calls.mmd', 'compact_flow.mmd', 'calls.toon.yaml'])
270
261
  print(f" - Mermaid: {output_dir}/*.mmd ({', '.join(files)})")
271
262
 
272
263
  _export_mermaid_pngs(args, output_dir)
@@ -8,7 +8,6 @@ from .formats import (
8
8
  _export_simple_formats,
9
9
  _export_mermaid,
10
10
  _export_calls,
11
- _export_calls_toon,
12
11
  _export_evolution,
13
12
  _export_data_structures,
14
13
  _export_context_fallback,
@@ -0,0 +1,15 @@
1
+ """Project YAML Exporter — unified single source of truth for project diagnostics.
2
+
3
+ This package splits the original monolithic exporter into focused modules:
4
+ - health: CC metrics, alerts, duplicates
5
+ - modules: per-file metrics, exports
6
+ - hotspots: high fan-out detection
7
+ - evolution: append-only history log
8
+
9
+ Backward compatible: ProjectYAMLExporter can still be imported from here
10
+ or from the original location.
11
+ """
12
+
13
+ from .core import ProjectYAMLExporter
14
+
15
+ __all__ = ["ProjectYAMLExporter"]
@@ -0,0 +1,15 @@
1
+ """Constants and thresholds for project YAML export."""
2
+
3
+ # Cyclomatic Complexity thresholds
4
+ CC_CRITICAL = 10
5
+ CC_WARNING = 15
6
+ CC_ERROR = 20
7
+ CC_SEVERE = 25
8
+
9
+ # Fan-out thresholds
10
+ FAN_OUT_THRESHOLD = 10
11
+ FAN_OUT_ERROR = 15
12
+ FAN_OUT_SEVERE = 20
13
+
14
+ # Module size threshold
15
+ GOD_MODULE_LINES = 500
@@ -0,0 +1,118 @@
1
+ """Core ProjectYAMLExporter class — orchestrates all builders."""
2
+
3
+ from collections import defaultdict
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List
7
+
8
+ import yaml
9
+
10
+ from code2llm.core.models import AnalysisResult
11
+ from code2llm.core.config import LANGUAGE_EXTENSIONS
12
+ from code2llm.exporters.base import Exporter
13
+
14
+ from code2llm.exporters.toon.helpers import _is_excluded, _scan_line_counts, _rel_path
15
+ from .constants import GOD_MODULE_LINES
16
+ from .health import build_health
17
+ from .modules import build_modules
18
+ from .hotspots import build_hotspots, build_refactoring
19
+ from .evolution import build_evolution, load_previous_evolution
20
+
21
+
22
+ class ProjectYAMLExporter(Exporter):
23
+ """Export unified project.yaml — single source of truth for diagnostics.
24
+
25
+ Combines data from analysis.toon, project.toon.yaml, context.md, and evolution.toon.yaml
26
+ into one machine-parseable YAML file.
27
+ """
28
+
29
+ def export(self, result: AnalysisResult, output_path: str, **kwargs) -> None:
30
+ """Generate project.yaml from AnalysisResult.
31
+
32
+ If the file already exists, the evolution section is appended (not replaced).
33
+ """
34
+ output = Path(output_path)
35
+ output.parent.mkdir(parents=True, exist_ok=True)
36
+
37
+ # Load previous evolution entries if file exists
38
+ prev_evolution = load_previous_evolution(output)
39
+
40
+ data = self._build_project_yaml(result, prev_evolution)
41
+
42
+ with open(output, "w", encoding="utf-8") as f:
43
+ yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
44
+
45
+ def _build_project_yaml(
46
+ self, result: AnalysisResult, prev_evolution: List[Dict]
47
+ ) -> Dict[str, Any]:
48
+ """Build complete project.yaml structure."""
49
+ line_counts = _scan_line_counts(result.project_path)
50
+ # Filter out venv/site-packages/etc — only count lines of non-excluded files
51
+ filtered_lines = {
52
+ k: v for k, v in line_counts.items()
53
+ if not _is_excluded(k)
54
+ }
55
+ total_lines = sum(filtered_lines.values()) // 2 # keys stored twice (abs + rel)
56
+
57
+ modules = build_modules(result, line_counts)
58
+ health = build_health(result, modules)
59
+ hotspots = build_hotspots(result)
60
+ refactoring = build_refactoring(result, modules, hotspots)
61
+ evolution = build_evolution(health, total_lines, prev_evolution)
62
+
63
+ return {
64
+ "version": "1",
65
+ "project": {
66
+ "name": Path(result.project_path).name if result.project_path else "unknown",
67
+ "repo": result.project_path or "",
68
+ "language": self._detect_primary_language(result),
69
+ "analyzed_at": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
70
+ "tool": "code2llm",
71
+ "stats": {
72
+ "files": len(set(
73
+ fi.file for fi in result.functions.values()
74
+ if not _is_excluded(fi.file)
75
+ )) or len(result.modules),
76
+ "lines": total_lines,
77
+ "functions": len([
78
+ f for f in result.functions.values()
79
+ if not _is_excluded(f.file)
80
+ ]),
81
+ "classes": len([
82
+ c for c in result.classes.values()
83
+ if not _is_excluded(c.file)
84
+ ]),
85
+ },
86
+ },
87
+ "health": health,
88
+ "modules": modules,
89
+ "hotspots": hotspots,
90
+ "refactoring": refactoring,
91
+ "evolution": evolution,
92
+ }
93
+
94
+ def _detect_primary_language(self, result: AnalysisResult) -> str:
95
+ """Detect the primary language of the project based on file counts."""
96
+ lang_counts: Dict[str, int] = defaultdict(int)
97
+
98
+ # Count files by language
99
+ for mi in result.modules.values():
100
+ if _is_excluded(mi.file):
101
+ continue
102
+ detected = False
103
+ for lang, extensions in LANGUAGE_EXTENSIONS.items():
104
+ if any(mi.file.endswith(ext) for ext in extensions):
105
+ lang_counts[lang] += 1
106
+ detected = True
107
+ break
108
+ if not detected:
109
+ # Fallback to extension
110
+ ext = Path(mi.file).suffix.lstrip('.').lower()
111
+ if ext:
112
+ lang_counts[ext] += 1
113
+
114
+ if not lang_counts:
115
+ return "unknown"
116
+
117
+ # Return the most common language
118
+ return max(lang_counts.items(), key=lambda x: x[1])[0]
@@ -0,0 +1,46 @@
1
+ """Evolution history builder for project.yaml."""
2
+
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List
6
+
7
+ import yaml
8
+
9
+
10
+ def build_evolution(
11
+ health: Dict[str, Any],
12
+ total_lines: int,
13
+ prev_evolution: List[Dict],
14
+ ) -> List[Dict[str, Any]]:
15
+ """Build append-only evolution history."""
16
+ today = datetime.now().strftime("%Y-%m-%d")
17
+
18
+ new_entry = {
19
+ "date": today,
20
+ "cc_avg": health["cc_avg"],
21
+ "critical": health["critical_count"],
22
+ "lines": total_lines,
23
+ "note": "Automated analysis",
24
+ }
25
+
26
+ # Avoid duplicate entries for same date
27
+ evolution = [e for e in prev_evolution if e.get("date") != today]
28
+ evolution.append(new_entry)
29
+
30
+ return evolution
31
+
32
+
33
+ def load_previous_evolution(output_path: Path) -> List[Dict]:
34
+ """Load previous evolution entries from existing project.yaml."""
35
+ if not output_path.exists():
36
+ return []
37
+ try:
38
+ with open(output_path, "r", encoding="utf-8") as f:
39
+ data = yaml.safe_load(f)
40
+ if isinstance(data, dict) and "evolution" in data:
41
+ evo = data["evolution"]
42
+ if isinstance(evo, list):
43
+ return evo
44
+ except Exception:
45
+ pass
46
+ return []
@@ -0,0 +1,103 @@
1
+ """Health metrics builder for project.yaml."""
2
+
3
+ from collections import defaultdict
4
+ from typing import Any, Dict, List
5
+
6
+ from code2llm.core.models import AnalysisResult, ClassInfo, FunctionInfo
7
+ from code2llm.exporters.toon.helpers import _is_excluded
8
+ from .constants import CC_CRITICAL, CC_WARNING, CC_ERROR, CC_SEVERE, FAN_OUT_THRESHOLD, FAN_OUT_ERROR, FAN_OUT_SEVERE
9
+
10
+
11
+ def build_health(result: AnalysisResult, modules: List[Dict]) -> Dict[str, Any]:
12
+ """Build health section with CC metrics, alerts, and issues."""
13
+ all_cc = []
14
+ for fi in result.functions.values():
15
+ if _is_excluded(fi.file):
16
+ continue
17
+ all_cc.append(fi.complexity.get("cyclomatic_complexity", 0))
18
+
19
+ cc_avg = round(sum(all_cc) / len(all_cc), 1) if all_cc else 0.0
20
+ critical_count = sum(1 for c in all_cc if c >= CC_CRITICAL)
21
+
22
+ # Detect cycles
23
+ proj_metrics = result.metrics.get("project", {})
24
+ cycles = proj_metrics.get("circular_dependencies", [])
25
+
26
+ # Detect duplicates
27
+ dup_count = count_duplicates(result)
28
+
29
+ # Build alerts
30
+ alerts = build_alerts(result)
31
+
32
+ return {
33
+ "cc_avg": cc_avg,
34
+ "critical_count": critical_count,
35
+ "critical_limit": CC_CRITICAL,
36
+ "duplicates": dup_count,
37
+ "cycles": len(cycles),
38
+ "alerts": alerts if alerts else [],
39
+ }
40
+
41
+
42
+ def build_alerts(result: AnalysisResult) -> List[Dict[str, Any]]:
43
+ """Build list of health alerts for high CC and high fan-out."""
44
+ alerts = []
45
+ for qname, fi in result.functions.items():
46
+ if _is_excluded(fi.file):
47
+ continue
48
+ cc = fi.complexity.get("cyclomatic_complexity", 0)
49
+ if cc >= CC_WARNING:
50
+ display = fi.name
51
+ if fi.class_name:
52
+ display = f"{fi.class_name}.{fi.name}"
53
+ if cc >= CC_SEVERE:
54
+ severity = "critical"
55
+ elif cc >= CC_ERROR:
56
+ severity = "error"
57
+ else:
58
+ severity = "warning"
59
+ alerts.append({
60
+ "type": "cc_exceeded",
61
+ "target": display,
62
+ "value": cc,
63
+ "limit": CC_WARNING,
64
+ "severity": severity,
65
+ })
66
+
67
+ fan_alerts = []
68
+ for qname, fi in result.functions.items():
69
+ if _is_excluded(fi.file):
70
+ continue
71
+ fan_out = len(set(fi.calls))
72
+ if fan_out >= FAN_OUT_THRESHOLD:
73
+ display = fi.name
74
+ if fi.class_name:
75
+ display = f"{fi.class_name}.{fi.name}"
76
+ if fan_out >= FAN_OUT_SEVERE:
77
+ severity = "critical"
78
+ elif fan_out >= FAN_OUT_ERROR:
79
+ severity = "error"
80
+ else:
81
+ severity = "warning"
82
+ fan_alerts.append({
83
+ "type": "high_fan_out",
84
+ "target": display,
85
+ "value": fan_out,
86
+ "limit": FAN_OUT_THRESHOLD,
87
+ "severity": severity,
88
+ })
89
+
90
+ # Sort alerts by severity (critical first), then by value desc
91
+ sev_order = {"critical": 0, "error": 1, "warning": 2, "info": 3}
92
+ all_alerts = alerts + fan_alerts
93
+ all_alerts.sort(key=lambda a: (sev_order.get(a["severity"], 9), -a["value"]))
94
+ return all_alerts[:20]
95
+
96
+
97
+ def count_duplicates(result: AnalysisResult) -> int:
98
+ """Count duplicate class names in different files."""
99
+ name_files: Dict[str, List[str]] = defaultdict(list)
100
+ for qname, ci in result.classes.items():
101
+ if not _is_excluded(ci.file):
102
+ name_files[ci.name].append(ci.file)
103
+ return sum(1 for files in name_files.values() if len(set(files)) > 1)
@@ -0,0 +1,106 @@
1
+ """Hotspots and refactoring priorities builder for project.yaml."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List
5
+
6
+ from code2llm.core.models import AnalysisResult, FunctionInfo
7
+ from code2llm.exporters.toon.helpers import _is_excluded
8
+ from .constants import FAN_OUT_THRESHOLD, CC_WARNING, GOD_MODULE_LINES
9
+
10
+
11
+ def build_hotspots(result: AnalysisResult) -> List[Dict[str, Any]]:
12
+ """Build hotspots list (high fan-out functions)."""
13
+ spots = []
14
+ for qname, fi in result.functions.items():
15
+ if _is_excluded(fi.file):
16
+ continue
17
+ fan_out = len(set(fi.calls))
18
+ if fan_out >= FAN_OUT_THRESHOLD:
19
+ display = fi.name
20
+ if fi.class_name:
21
+ display = f"{fi.class_name}.{fi.name}"
22
+ note = hotspot_note(fi, fan_out)
23
+ spots.append({
24
+ "name": display,
25
+ "fan_out": fan_out,
26
+ "note": note,
27
+ })
28
+ spots.sort(key=lambda s: s["fan_out"], reverse=True)
29
+ return spots[:10]
30
+
31
+
32
+ def hotspot_note(fi: FunctionInfo, fan_out: int) -> str:
33
+ """Generate descriptive note for a hotspot."""
34
+ if "format" in fi.name.lower() or "dispatch" in fi.name.lower():
35
+ return f"{fan_out}-way dispatch"
36
+ if "export" in fi.name.lower():
37
+ return f"Export with {fan_out} outputs"
38
+ if "analyze" in fi.name.lower() or "process" in fi.name.lower():
39
+ return f"Analysis pipeline, {fan_out} stages"
40
+ if fi.docstring:
41
+ return fi.docstring[:80]
42
+ return f"Orchestrates {fan_out} calls"
43
+
44
+
45
+ def build_refactoring(
46
+ result: AnalysisResult,
47
+ modules: List[Dict],
48
+ hotspots: List[Dict],
49
+ ) -> Dict[str, Any]:
50
+ """Build prioritized refactoring actions."""
51
+ priorities = []
52
+
53
+ # High CC functions → split
54
+ for qname, fi in result.functions.items():
55
+ if _is_excluded(fi.file):
56
+ continue
57
+ cc = fi.complexity.get("cyclomatic_complexity", 0)
58
+ if cc >= CC_WARNING:
59
+ display = fi.name
60
+ if fi.class_name:
61
+ display = f"{fi.class_name}.{fi.name}"
62
+ rel = _rel_path(fi.file, result.project_path)
63
+ priorities.append({
64
+ "action": f"Split {display} (CC={cc})",
65
+ "impact": "high" if cc >= 25 else "medium",
66
+ "effort": "low",
67
+ "module": Path(rel).name,
68
+ })
69
+
70
+ # Cycles → break
71
+ proj_metrics = result.metrics.get("project", {})
72
+ cycles = proj_metrics.get("circular_dependencies", [])
73
+ for cycle in cycles[:3]:
74
+ priorities.append({
75
+ "action": f"Break circular dependency: {' → '.join(str(c) for c in cycle) if isinstance(cycle, list) else str(cycle)}",
76
+ "impact": "medium",
77
+ "effort": "low",
78
+ })
79
+
80
+ # High fan-out → reduce
81
+ for spot in hotspots[:3]:
82
+ if spot["fan_out"] >= 15:
83
+ priorities.append({
84
+ "action": f"Reduce {spot['name']} fan-out (currently {spot['fan_out']})",
85
+ "impact": "medium",
86
+ "effort": "medium",
87
+ })
88
+
89
+ # God modules → split
90
+ for mod in modules:
91
+ if mod["lines"] >= GOD_MODULE_LINES:
92
+ priorities.append({
93
+ "action": f"Split god module {mod['path']} ({mod['lines']}L, {mod['classes']} classes)",
94
+ "impact": "high",
95
+ "effort": "high",
96
+ })
97
+
98
+ # Sort: high impact first, then low effort first
99
+ impact_order = {"high": 0, "medium": 1, "low": 2}
100
+ effort_order = {"low": 0, "medium": 1, "high": 2}
101
+ priorities.sort(key=lambda p: (
102
+ impact_order.get(p.get("impact", "low"), 9),
103
+ effort_order.get(p.get("effort", "medium"), 9),
104
+ ))
105
+
106
+ return {"priorities": priorities[:15]}