code2llm 0.5.113__tar.gz → 0.5.115__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 (142) hide show
  1. {code2llm-0.5.113 → code2llm-0.5.115}/PKG-INFO +16 -11
  2. {code2llm-0.5.113 → code2llm-0.5.115}/README.md +15 -10
  3. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/__init__.py +1 -1
  4. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/utils/__init__.py +2 -2
  5. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/utils/ast_helpers.py +19 -0
  6. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/cli_analysis.py +5 -1
  7. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/cli_commands.py +67 -1
  8. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/cli_exports/formats.py +21 -6
  9. code2llm-0.5.115/code2llm/cli_exports/orchestrator.py +337 -0
  10. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/cli_parser.py +21 -1
  11. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/analyzer.py +147 -69
  12. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/config.py +2 -1
  13. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/file_filter.py +48 -25
  14. code2llm-0.5.115/code2llm/core/gitignore.py +138 -0
  15. code2llm-0.5.115/code2llm/core/persistent_cache.py +322 -0
  16. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/repo_files.py +2 -1
  17. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/__init__.py +29 -2
  18. code2llm-0.5.115/code2llm/exporters/base.py +158 -0
  19. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/context_exporter.py +9 -7
  20. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/evolution_exporter.py +7 -5
  21. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/flow_exporter.py +7 -5
  22. code2llm-0.5.115/code2llm/exporters/json_exporter.py +27 -0
  23. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/map_exporter.py +8 -5
  24. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/mermaid_exporter.py +55 -43
  25. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/project_yaml/core.py +6 -4
  26. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/project_yaml/hotspots.py +1 -1
  27. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/readme_exporter.py +11 -8
  28. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/toon/__init__.py +8 -5
  29. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/toon/helpers.py +9 -4
  30. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/yaml_exporter.py +17 -8
  31. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/generators/mermaid.py +23 -24
  32. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/nlp/__init__.py +1 -1
  33. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm.egg-info/PKG-INFO +16 -11
  34. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm.egg-info/SOURCES.txt +2 -0
  35. {code2llm-0.5.113 → code2llm-0.5.115}/pyproject.toml +1 -1
  36. {code2llm-0.5.113 → code2llm-0.5.115}/setup.py +1 -1
  37. code2llm-0.5.115/tests/test_persistent_cache.py +182 -0
  38. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_project_toon_export.py +1 -5
  39. code2llm-0.5.113/code2llm/cli_exports/orchestrator.py +0 -156
  40. code2llm-0.5.113/code2llm/core/gitignore.py +0 -139
  41. code2llm-0.5.113/code2llm/exporters/base.py +0 -13
  42. code2llm-0.5.113/code2llm/exporters/json_exporter.py +0 -17
  43. {code2llm-0.5.113 → code2llm-0.5.115}/LICENSE +0 -0
  44. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/__main__.py +0 -0
  45. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/__init__.py +0 -0
  46. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/call_graph.py +0 -0
  47. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/cfg.py +0 -0
  48. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/coupling.py +0 -0
  49. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/data_analysis.py +0 -0
  50. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/dfg.py +0 -0
  51. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/pipeline_detector.py +0 -0
  52. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/side_effects.py +0 -0
  53. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/smells.py +0 -0
  54. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/analysis/type_inference.py +0 -0
  55. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/api.py +0 -0
  56. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/cli.py +0 -0
  57. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/cli_exports/__init__.py +0 -0
  58. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/cli_exports/code2logic.py +0 -0
  59. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/cli_exports/prompt.py +0 -0
  60. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/__init__.py +0 -0
  61. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/ast_registry.py +0 -0
  62. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/export_pipeline.py +0 -0
  63. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/file_analyzer.py +0 -0
  64. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/file_cache.py +0 -0
  65. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/incremental.py +0 -0
  66. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/__init__.py +0 -0
  67. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/base.py +0 -0
  68. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/cpp.py +0 -0
  69. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/csharp.py +0 -0
  70. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/generic.py +0 -0
  71. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/go_lang.py +0 -0
  72. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/java.py +0 -0
  73. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/php.py +0 -0
  74. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/ruby.py +0 -0
  75. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/rust.py +0 -0
  76. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/ts_extractors.py +0 -0
  77. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/ts_parser.py +0 -0
  78. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/lang/typescript.py +0 -0
  79. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/large_repo.py +0 -0
  80. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/models.py +0 -0
  81. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/refactoring.py +0 -0
  82. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/streaming/__init__.py +0 -0
  83. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/streaming/cache.py +0 -0
  84. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/streaming/incremental.py +0 -0
  85. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/streaming/prioritizer.py +0 -0
  86. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/streaming/scanner.py +0 -0
  87. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/streaming/strategies.py +0 -0
  88. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/streaming_analyzer.py +0 -0
  89. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/core/toon_size_manager.py +0 -0
  90. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/article_view.py +0 -0
  91. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/context_view.py +0 -0
  92. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/flow_constants.py +0 -0
  93. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/flow_renderer.py +0 -0
  94. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/html_dashboard.py +0 -0
  95. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/index_generator.py +0 -0
  96. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/llm_exporter.py +0 -0
  97. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
  98. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/project_yaml/__init__.py +0 -0
  99. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/project_yaml/constants.py +0 -0
  100. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/project_yaml/evolution.py +0 -0
  101. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/project_yaml/health.py +0 -0
  102. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/project_yaml/modules.py +0 -0
  103. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/project_yaml_exporter.py +0 -0
  104. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/report_generators.py +0 -0
  105. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/toon/metrics.py +0 -0
  106. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/toon/module_detail.py +0 -0
  107. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/toon/renderer.py +0 -0
  108. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/toon.py +0 -0
  109. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/toon_view.py +0 -0
  110. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/exporters/validate_project.py +0 -0
  111. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/generators/__init__.py +0 -0
  112. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/generators/llm_flow.py +0 -0
  113. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/generators/llm_task.py +0 -0
  114. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/nlp/config.py +0 -0
  115. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/nlp/entity_resolution.py +0 -0
  116. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/nlp/intent_matching.py +0 -0
  117. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/nlp/normalization.py +0 -0
  118. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/nlp/pipeline.py +0 -0
  119. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/patterns/__init__.py +0 -0
  120. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/patterns/detector.py +0 -0
  121. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/refactor/__init__.py +0 -0
  122. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm/refactor/prompt_engine.py +0 -0
  123. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm.egg-info/dependency_links.txt +0 -0
  124. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm.egg-info/entry_points.txt +0 -0
  125. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm.egg-info/requires.txt +0 -0
  126. {code2llm-0.5.113 → code2llm-0.5.115}/code2llm.egg-info/top_level.txt +0 -0
  127. {code2llm-0.5.113 → code2llm-0.5.115}/setup.cfg +0 -0
  128. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_advanced_analysis.py +0 -0
  129. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_analyzer.py +0 -0
  130. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_calls_toon_export.py +0 -0
  131. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_deep_analysis.py +0 -0
  132. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_edge_cases.py +0 -0
  133. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_flow_exporter.py +0 -0
  134. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_format_quality.py +0 -0
  135. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_multilanguage_e2e.py +0 -0
  136. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_nlp_pipeline.py +0 -0
  137. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_nonpython_cc_calls.py +0 -0
  138. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_pipeline_detector.py +0 -0
  139. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_prompt_engine.py +0 -0
  140. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_prompt_txt.py +0 -0
  141. {code2llm-0.5.113 → code2llm-0.5.115}/tests/test_refactoring_engine.py +0 -0
  142. {code2llm-0.5.113 → code2llm-0.5.115}/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.113
3
+ Version: 0.5.115
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,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.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)
70
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.115-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-57.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
72
72
 
73
- - 🤖 **LLM usage:** $7.5000 (162 commits)
74
- - 👤 **Human dev:** ~$5505 (55.0h @ $100/h, 30min dedup)
73
+ - 🤖 **LLM usage:** $7.5000 (166 commits)
74
+ - 👤 **Human dev:** ~$5731 (57.3h @ $100/h, 30min dedup)
75
75
 
76
- Generated on 2026-04-18 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
76
+ Generated on 2026-04-19 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
77
77
 
78
78
  ---
79
79
 
@@ -89,7 +89,7 @@ When you run `code2llm ./ -f all`, the following files are created:
89
89
 
90
90
  | File | Format | Purpose | Key Insights |
91
91
  |------|--------|---------|--------------|
92
- | `analysis.toon` | **TOON** | **🔥 Health diagnostics** - Health, LAYERS, COUPLING | 69 critical functions, 0 god modules |
92
+ | `evolution.toon.yaml` | **YAML** | **📋 Refactoring queue** - Prioritized improvements | 0 refactoring actions needed |
93
93
 
94
94
  ### 🤖 LLM-Ready Documentation
95
95
 
@@ -97,6 +97,11 @@ When you run `code2llm ./ -f all`, the following files are created:
97
97
  |------|--------|---------|----------|
98
98
  | `context.md` | **Markdown** | **📖 LLM narrative** - Architecture summary | Paste into ChatGPT/Claude for code analysis |
99
99
 
100
+ ### 📊 Visualizations
101
+
102
+ | File | Format | Purpose | Description |
103
+ |------|--------|---------|-------------|
104
+ | `calls.mmd` | **Mermaid** | **📞 Call graph** | Function dependencies (edges only) |
100
105
 
101
106
  ## 🚀 Quick Start Commands
102
107
 
@@ -401,10 +406,10 @@ code2llm ./ -f yaml --separate-orphans
401
406
  ---
402
407
 
403
408
  **Generated by**: `code2llm ./ -f all --readme`
404
- **Analysis Date**: 2026-04-18
405
- **Total Functions**: 1041
406
- **Total Classes**: 111
407
- **Modules**: 135
409
+ **Analysis Date**: 2026-04-19
410
+ **Total Functions**: 1115
411
+ **Total Classes**: 121
412
+ **Modules**: 152
408
413
 
409
414
  For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
410
415
 
@@ -3,13 +3,13 @@
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.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)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.115-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-57.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $7.5000 (162 commits)
10
- - 👤 **Human dev:** ~$5505 (55.0h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $7.5000 (166 commits)
10
+ - 👤 **Human dev:** ~$5731 (57.3h @ $100/h, 30min dedup)
11
11
 
12
- Generated on 2026-04-18 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
12
+ Generated on 2026-04-19 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
14
14
  ---
15
15
 
@@ -25,7 +25,7 @@ When you run `code2llm ./ -f all`, the following files are created:
25
25
 
26
26
  | File | Format | Purpose | Key Insights |
27
27
  |------|--------|---------|--------------|
28
- | `analysis.toon` | **TOON** | **🔥 Health diagnostics** - Health, LAYERS, COUPLING | 69 critical functions, 0 god modules |
28
+ | `evolution.toon.yaml` | **YAML** | **📋 Refactoring queue** - Prioritized improvements | 0 refactoring actions needed |
29
29
 
30
30
  ### 🤖 LLM-Ready Documentation
31
31
 
@@ -33,6 +33,11 @@ When you run `code2llm ./ -f all`, the following files are created:
33
33
  |------|--------|---------|----------|
34
34
  | `context.md` | **Markdown** | **📖 LLM narrative** - Architecture summary | Paste into ChatGPT/Claude for code analysis |
35
35
 
36
+ ### 📊 Visualizations
37
+
38
+ | File | Format | Purpose | Description |
39
+ |------|--------|---------|-------------|
40
+ | `calls.mmd` | **Mermaid** | **📞 Call graph** | Function dependencies (edges only) |
36
41
 
37
42
  ## 🚀 Quick Start Commands
38
43
 
@@ -337,10 +342,10 @@ code2llm ./ -f yaml --separate-orphans
337
342
  ---
338
343
 
339
344
  **Generated by**: `code2llm ./ -f all --readme`
340
- **Analysis Date**: 2026-04-18
341
- **Total Functions**: 1041
342
- **Total Classes**: 111
343
- **Modules**: 135
345
+ **Analysis Date**: 2026-04-19
346
+ **Total Functions**: 1115
347
+ **Total Classes**: 121
348
+ **Modules**: 152
344
349
 
345
350
  For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
346
351
 
@@ -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.113"
11
+ __version__ = "0.5.115"
12
12
  __author__ = "STTS Project"
13
13
 
14
14
  # Core analysis components (lightweight, always needed)
@@ -1,5 +1,5 @@
1
1
  """Shared AST utilities for analysis modules."""
2
2
 
3
- from .ast_helpers import get_ast, find_function_node, expr_to_str
3
+ from .ast_helpers import get_ast, find_function_node, expr_to_str, ast_unparse
4
4
 
5
- __all__ = ["get_ast", "find_function_node", "expr_to_str"]
5
+ __all__ = ["get_ast", "find_function_node", "expr_to_str", "ast_unparse"]
@@ -38,6 +38,25 @@ def find_function_node(
38
38
  return None
39
39
 
40
40
 
41
+ def ast_unparse(node: Optional[ast.AST], default_none: str = "None") -> str:
42
+ """Convert an AST node to its source string via ast.unparse (Python 3.9+).
43
+
44
+ Used as a shared replacement for the duplicated *_expr_to_str* methods
45
+ in ``cfg.py``, ``dfg.py``, and ``call_graph.py``.
46
+
47
+ Args:
48
+ node: AST node to convert, or None.
49
+ default_none: value returned when *node* is None (``"None"`` for most
50
+ callers; ``""`` for call_graph which uses empty-string sentinel).
51
+ """
52
+ if node is None:
53
+ return default_none
54
+ try:
55
+ return ast.unparse(node) if hasattr(ast, "unparse") else str(node)
56
+ except Exception:
57
+ return str(node)
58
+
59
+
41
60
  def expr_to_str(node: ast.expr) -> Optional[str]:
42
61
  """Convert an AST expression to a dotted string (for call-name extraction).
43
62
 
@@ -73,7 +73,7 @@ def _build_config(args, output_dir: Path):
73
73
  if hasattr(args, 'no_gitignore') and args.no_gitignore:
74
74
  filter_config.gitignore_enabled = False
75
75
 
76
- return Config(
76
+ config = Config(
77
77
  mode=args.mode,
78
78
  max_depth_enumeration=args.max_depth,
79
79
  detect_state_machines=not args.no_patterns,
@@ -81,6 +81,10 @@ def _build_config(args, output_dir: Path):
81
81
  output_dir=str(output_dir),
82
82
  filters=filter_config
83
83
  )
84
+ # Persistent cache flags (read via getattr with defaults in analyzer.py)
85
+ no_cache = getattr(args, 'no_cache', False) or getattr(args, 'force', False)
86
+ config.no_cache = no_cache
87
+ return config
84
88
 
85
89
 
86
90
  def _print_analysis_summary(result) -> None:
@@ -9,7 +9,7 @@ from .cli_exports import _run_report
9
9
 
10
10
 
11
11
  def handle_special_commands() -> Optional[int]:
12
- """Handle special sub-commands (llm-flow, llm-context, report)."""
12
+ """Handle special sub-commands (llm-flow, llm-context, report, cache)."""
13
13
  if len(sys.argv) > 1 and sys.argv[1] == 'llm-flow':
14
14
  from .generators.llm_flow import main as llm_flow_main
15
15
  return llm_flow_main(sys.argv[2:])
@@ -17,9 +17,75 @@ def handle_special_commands() -> Optional[int]:
17
17
  return generate_llm_context(sys.argv[2:])
18
18
  if len(sys.argv) > 1 and sys.argv[1] == 'report':
19
19
  return handle_report_command(sys.argv[2:])
20
+ if len(sys.argv) > 1 and sys.argv[1] == 'cache':
21
+ return handle_cache_command(sys.argv[2:])
20
22
  return None
21
23
 
22
24
 
25
+ def handle_cache_command(args_list) -> int:
26
+ """Manage persistent cache (~/.code2llm/).
27
+
28
+ Usage:
29
+ code2llm cache status # show size, projects, last used
30
+ code2llm cache clear # clear cache for current directory
31
+ code2llm cache clear --all # clear entire ~/.code2llm/
32
+ code2llm cache gc # manual garbage collection
33
+ """
34
+ import os
35
+ import time
36
+ from .core.persistent_cache import PersistentCache, get_all_projects, clear_all, _DEFAULT_ROOT
37
+
38
+ parser = argparse.ArgumentParser(prog='code2llm cache')
39
+ parser.add_argument('action', choices=['status', 'clear', 'gc'], help='Cache action')
40
+ parser.add_argument('--all', action='store_true', dest='all_projects',
41
+ help='Apply to all cached projects (clear only)')
42
+ parser.add_argument('--max-age', type=int, default=30, metavar='DAYS',
43
+ help='Max age in days for gc (default: 30)')
44
+ args = parser.parse_args(args_list)
45
+
46
+ if args.action == 'status':
47
+ projects = get_all_projects()
48
+ root = _DEFAULT_ROOT
49
+ total_mb = sum(p.get('cache_size_bytes', 0) for p in projects) / (1024 * 1024)
50
+ print(f"Cache: {root}")
51
+ print(f" Projects: {len(projects)} Total: {total_mb:.1f} MB")
52
+ for p in projects:
53
+ size_mb = p.get('cache_size_bytes', 0) / (1024 * 1024)
54
+ updated = p.get('updated_at', 0)
55
+ age_min = int((time.time() - updated) / 60) if updated else 0
56
+ age_str = f"{age_min}m ago" if age_min < 120 else f"{age_min//60}h ago"
57
+ exports = p.get('exports', 0)
58
+ files = p.get('files_cached', 0)
59
+ print(f"\n {p.get('project', '?')}")
60
+ print(f" Files: {files} Exports: {exports} Size: {size_mb:.1f} MB Last: {age_str}")
61
+ return 0
62
+
63
+ if args.action == 'clear':
64
+ if args.all_projects:
65
+ clear_all()
66
+ print("Cleared entire cache.")
67
+ else:
68
+ project_dir = os.path.realpath('.')
69
+ c = PersistentCache(project_dir)
70
+ c.clear()
71
+ print(f"Cleared cache for {project_dir}")
72
+ return 0
73
+
74
+ if args.action == 'gc':
75
+ projects = get_all_projects()
76
+ total_removed = 0
77
+ for p in projects:
78
+ project_dir = p.get('project')
79
+ if project_dir and Path(project_dir).exists():
80
+ c = PersistentCache(project_dir)
81
+ removed = c.gc(max_age_days=args.max_age)
82
+ total_removed += removed
83
+ print(f"GC complete: {total_removed} stale entries removed.")
84
+ return 0
85
+
86
+ return 0
87
+
88
+
23
89
  def handle_report_command(args_list) -> int:
24
90
  """Generate views from an existing project.yaml (legacy).
25
91
 
@@ -216,10 +216,24 @@ def _export_mermaid_pngs(args, output_dir: Path) -> None:
216
216
 
217
217
 
218
218
  def _export_calls(args, result, output_dir: Path):
219
- """Export standalone calls.toon.yaml (call graph in toon format).
220
-
221
- Generates calls.toon.yaml in human-readable toon format with hubs, modules,
222
- and edges sections useful for programmatic analysis of call graphs.
219
+ """Export standalone calls.yaml (structured call graph YAML).
220
+
221
+ Generates calls.yaml with structured call graph data:
222
+ - nodes: functions with metadata (CC, calls_in/out)
223
+ - edges: caller -> callee relationships
224
+ - modules: grouping by module
225
+ - stats: summary statistics
226
+ """
227
+ yaml_exporter = YAMLExporter()
228
+ yaml_exporter.export_calls(result, str(output_dir / 'calls.yaml'))
229
+ if args.verbose:
230
+ print(f" - CALLS (call graph YAML): {output_dir / 'calls.yaml'}")
231
+
232
+
233
+ def _export_calls_toon(args, result, output_dir: Path):
234
+ """Export calls.toon.yaml (call graph in human-readable toon format).
235
+
236
+ Generates calls.toon.yaml with hubs, modules, and edges sections.
223
237
  """
224
238
  yaml_exporter = YAMLExporter()
225
239
  yaml_exporter.export_calls_toon(result, str(output_dir / 'calls.toon.yaml'))
@@ -247,8 +261,9 @@ def _export_mermaid(args, result, output_dir: Path):
247
261
  exporter.export_call_graph(result, str(output_dir / 'calls.mmd'))
248
262
  exporter.export_compact(result, str(output_dir / 'compact_flow.mmd'))
249
263
 
250
- # Export calls.toon.yaml (structured call graph data in toon format)
264
+ # Export calls.yaml (structured call graph data) and calls.toon.yaml (human-readable)
251
265
  yaml_exporter = YAMLExporter()
266
+ yaml_exporter.export_calls(result, str(output_dir / 'calls.yaml'))
252
267
  yaml_exporter.export_calls_toon(result, str(output_dir / 'calls.toon.yaml'))
253
268
 
254
269
  if args.verbose:
@@ -257,7 +272,7 @@ def _export_mermaid(args, result, output_dir: Path):
257
272
  files.append('flow_detailed.mmd')
258
273
  if getattr(args, 'flow_full', False):
259
274
  files.append('flow_full.mmd')
260
- files.extend(['calls.mmd', 'compact_flow.mmd', 'calls.toon.yaml'])
275
+ files.extend(['calls.mmd', 'compact_flow.mmd', 'calls.yaml'])
261
276
  print(f" - Mermaid: {output_dir}/*.mmd ({', '.join(files)})")
262
277
 
263
278
  _export_mermaid_pngs(args, output_dir)
@@ -0,0 +1,337 @@
1
+ """Export orchestration — registry-based dispatch for cleaner code.
2
+
3
+ Refactored to use EXPORT_REGISTRY for core format dispatch.
4
+ Maintains backward compatibility with all existing --format values.
5
+ """
6
+
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Optional, List, Dict, Any
10
+
11
+ from code2llm.exporters import (
12
+ get_exporter,
13
+ EXPORT_REGISTRY,
14
+ YAMLExporter,
15
+ MermaidExporter,
16
+ ToonViewGenerator,
17
+ IndexHTMLGenerator,
18
+ )
19
+ from code2llm.exporters.project_yaml.evolution import load_previous_evolution
20
+
21
+
22
+ # Format output filenames
23
+ FORMAT_FILENAMES: Dict[str, str] = {
24
+ 'toon': 'analysis.toon.yaml',
25
+ 'map': 'map.toon.yaml',
26
+ 'flow': 'flow.toon.yaml',
27
+ 'context': 'context.md',
28
+ 'yaml': 'analysis.yaml',
29
+ 'json': 'analysis.json',
30
+ 'evolution': 'evolution.toon.yaml',
31
+ 'readme': 'README.md',
32
+ 'project-yaml': 'project.yaml',
33
+ }
34
+
35
+ # Human-readable labels
36
+ FORMAT_LABELS: Dict[str, str] = {
37
+ 'toon': 'TOON (diagnostics)',
38
+ 'map': 'MAP (structure)',
39
+ 'flow': 'FLOW (data-flow)',
40
+ 'context': 'CONTEXT (LLM narrative)',
41
+ 'yaml': 'YAML',
42
+ 'json': 'JSON',
43
+ 'evolution': 'EVOLUTION (refactoring queue)',
44
+ 'readme': 'README (documentation)',
45
+ 'project-yaml': 'PROJECT-YAML (single source of truth)',
46
+ }
47
+
48
+
49
+ def _run_exports(args, result, output_dir: Path, source_path: Optional[Path] = None):
50
+ """Export analysis results in requested formats.
51
+
52
+ Uses EXPORT_REGISTRY for core format dispatch.
53
+ For chunked analysis, exports to subproject subdirectories.
54
+ """
55
+ requested_formats = [f.strip() for f in args.format.split(',')]
56
+ formats = _expand_all_formats(requested_formats, getattr(args, 'png', False))
57
+ is_chunked = getattr(args, 'chunk', False)
58
+
59
+ try:
60
+ if is_chunked and source_path:
61
+ _export_chunked(args, result, output_dir, source_path, formats, requested_formats)
62
+ else:
63
+ _export_single(args, result, output_dir, formats, requested_formats, source_path)
64
+ except Exception as e:
65
+ print(f"Error during export: {e}", file=sys.stderr)
66
+ sys.exit(1)
67
+
68
+
69
+ def _expand_all_formats(requested: List[str], include_png: bool = False) -> List[str]:
70
+ """Expand 'all' to concrete format list."""
71
+ if 'all' not in requested:
72
+ return requested[:]
73
+ formats = ['toon', 'map', 'context', 'evolution']
74
+ if include_png:
75
+ formats.append('mermaid')
76
+ return formats
77
+
78
+
79
+ def _export_single(
80
+ args, result, output_dir: Path,
81
+ formats: List[str], requested_formats: List[str],
82
+ source_path: Optional[Path] = None
83
+ ):
84
+ """Export single project results."""
85
+ # Core formats via registry
86
+ _export_registry_formats(args, result, output_dir, formats)
87
+
88
+ # Special/conditional formats
89
+ if 'mermaid' in formats:
90
+ _export_mermaid(args, result, output_dir)
91
+ if 'calls' in formats or 'calls_toon' in formats:
92
+ _export_calls(args, result, output_dir, formats)
93
+
94
+ # Evolution always exported for 'all' or 'evolution' (handled by registry)
95
+ # Context fallback only if not explicitly requested
96
+ if 'context' not in formats and 'all' not in requested_formats:
97
+ _export_context_fallback(args, result, output_dir)
98
+
99
+ # project.toon.yaml for 'all' mode
100
+ if 'all' in requested_formats:
101
+ _export_project_toon(args, result, output_dir)
102
+
103
+ # Optional exports
104
+ if source_path is not None:
105
+ from .code2logic import _export_code2logic
106
+ from .prompt import _export_prompt_txt
107
+ _export_code2logic(args, source_path, output_dir, formats)
108
+ _export_prompt_txt(args, output_dir, requested_formats, source_path)
109
+
110
+ if getattr(args, 'refactor', False):
111
+ from .formats import _export_refactor_prompts
112
+ _export_refactor_prompts(args, result, output_dir)
113
+
114
+ if getattr(args, 'data_structures', False):
115
+ _export_data_structures(args, result, output_dir)
116
+
117
+ # Always export README and index
118
+ _export_readme(args, result, output_dir)
119
+ _export_index_html(args, output_dir)
120
+
121
+
122
+ def _export_registry_formats(args, result, output_dir: Path, formats: List[str]):
123
+ """Export core formats via EXPORT_REGISTRY lookup."""
124
+ for fmt in formats:
125
+ exporter_cls = get_exporter(fmt)
126
+ if exporter_cls is None:
127
+ continue
128
+
129
+ filename = FORMAT_FILENAMES.get(fmt, f'{fmt}.export')
130
+ label = FORMAT_LABELS.get(fmt, fmt.upper())
131
+ filepath = output_dir / filename
132
+
133
+ exporter = exporter_cls()
134
+ kwargs = _get_format_kwargs(fmt, args)
135
+
136
+ try:
137
+ exporter.export(result, str(filepath), **kwargs)
138
+ if args.verbose:
139
+ print(f" - {label}: {filepath}")
140
+ except Exception as e:
141
+ if args.verbose:
142
+ print(f" - {label} export failed: {e}", file=sys.stderr)
143
+
144
+
145
+ def _get_format_kwargs(fmt: str, args) -> Dict[str, Any]:
146
+ """Get format-specific kwargs for export."""
147
+ kwargs: Dict[str, Any] = {}
148
+ if fmt in ('yaml', 'json'):
149
+ kwargs['compact'] = not args.full
150
+ kwargs['include_defaults'] = args.full
151
+ return kwargs
152
+
153
+
154
+ def _export_mermaid(args, result, output_dir: Path):
155
+ """Export mermaid diagrams."""
156
+ exporter = MermaidExporter()
157
+ include_examples = getattr(args, 'flow_include_examples', False)
158
+
159
+ # Core diagrams
160
+ exporter.export_flow_compact(result, str(output_dir / 'flow.mmd'), include_examples)
161
+ exporter.export_call_graph(result, str(output_dir / 'calls.mmd'))
162
+ exporter.export_compact(result, str(output_dir / 'compact_flow.mmd'))
163
+
164
+ # Optional detailed diagrams
165
+ if getattr(args, 'flow_detail', False):
166
+ exporter.export_flow_detailed(result, str(output_dir / 'flow_detailed.mmd'), include_examples)
167
+ if getattr(args, 'flow_full', False):
168
+ exporter.export_flow_full(result, str(output_dir / 'flow_full.mmd'), include_examples)
169
+
170
+ # Also export calls.yaml/toon
171
+ yaml_exporter = YAMLExporter()
172
+ yaml_exporter.export_calls(result, str(output_dir / 'calls.yaml'))
173
+ yaml_exporter.export_calls_toon(result, str(output_dir / 'calls.toon.yaml'))
174
+
175
+ if args.verbose:
176
+ files = ['flow.mmd', 'calls.mmd', 'compact_flow.mmd', 'calls.yaml']
177
+ if getattr(args, 'flow_detail', False):
178
+ files.append('flow_detailed.mmd')
179
+ if getattr(args, 'flow_full', False):
180
+ files.append('flow_full.mmd')
181
+ print(f" - Mermaid: {output_dir}/*.mmd ({', '.join(files)})")
182
+
183
+ # PNG generation
184
+ _export_mermaid_pngs(args, output_dir)
185
+
186
+
187
+ def _export_mermaid_pngs(args, output_dir: Path):
188
+ """Generate PNGs from mermaid files."""
189
+ if getattr(args, 'no_png', False):
190
+ return
191
+ try:
192
+ from ..generators.mermaid import generate_pngs
193
+ png_count = generate_pngs(output_dir, output_dir)
194
+ if args.verbose and png_count > 0:
195
+ print(f" - PNG: {png_count} files generated")
196
+ except ImportError:
197
+ if args.verbose:
198
+ print(f" - PNG: Skipped (install with: make install-mermaid)")
199
+
200
+
201
+ def _export_calls(args, result, output_dir: Path, formats: List[str]):
202
+ """Export calls.yaml and calls.toon.yaml."""
203
+ yaml_exporter = YAMLExporter()
204
+ if 'calls' in formats:
205
+ yaml_exporter.export_calls(result, str(output_dir / 'calls.yaml'))
206
+ if args.verbose:
207
+ print(f" - CALLS (call graph YAML): {output_dir / 'calls.yaml'}")
208
+ if 'calls_toon' in formats:
209
+ yaml_exporter.export_calls_toon(result, str(output_dir / 'calls.toon.yaml'))
210
+ if args.verbose:
211
+ print(f" - CALLS (toon format): {output_dir / 'calls.toon.yaml'}")
212
+
213
+
214
+ def _export_context_fallback(args, result, output_dir: Path):
215
+ """Export context.md as fallback."""
216
+ exporter_cls = get_exporter('context')
217
+ if exporter_cls:
218
+ exporter = exporter_cls()
219
+ exporter.export(result, str(output_dir / 'context.md'))
220
+ if args.verbose:
221
+ print(f" - CONTEXT (LLM narrative): {output_dir / 'context.md'}")
222
+
223
+
224
+ def _export_data_structures(args, result, output_dir: Path):
225
+ """Export data_structures.yaml."""
226
+ yaml_exporter = YAMLExporter()
227
+ yaml_exporter.export_data_structures(result, str(output_dir / 'data_structures.yaml'), compact=True)
228
+ if args.verbose:
229
+ print(f" - Data structures: {output_dir / 'data_structures.yaml'}")
230
+
231
+
232
+ def _export_project_toon(args, result, output_dir: Path):
233
+ """Export project.toon.yaml from project.yaml data."""
234
+ from ..exporters.project_yaml_exporter import ProjectYAMLExporter
235
+
236
+ project_yaml_exporter = ProjectYAMLExporter()
237
+ prev_evolution = load_previous_evolution(output_dir / 'project.yaml')
238
+ data = project_yaml_exporter._build_project_yaml(result, prev_evolution)
239
+
240
+ generator = ToonViewGenerator()
241
+ generator.generate(data, str(output_dir / 'project.toon.yaml'))
242
+
243
+ if args.verbose:
244
+ print(f" - PROJECT-TOON (project overview): {output_dir / 'project.toon.yaml'}")
245
+
246
+
247
+ def _export_readme(args, result, output_dir: Path):
248
+ """Export README.md."""
249
+ if getattr(args, 'no_readme', False):
250
+ return
251
+ exporter_cls = get_exporter('readme')
252
+ if exporter_cls:
253
+ exporter = exporter_cls()
254
+ exporter.export(result, str(output_dir / 'README.md'))
255
+ if args.verbose:
256
+ print(f" - README (documentation): {output_dir / 'README.md'}")
257
+
258
+
259
+ def _export_index_html(args, output_dir: Path):
260
+ """Generate index.html for browsing files."""
261
+ if 'all' not in getattr(args, 'format', ''):
262
+ return
263
+ try:
264
+ generator = IndexHTMLGenerator(output_dir)
265
+ index_path = generator.generate()
266
+ if args.verbose:
267
+ print(f" - INDEX (file browser): {index_path}")
268
+ except Exception as e:
269
+ if args.verbose:
270
+ print(f" - INDEX generation failed: {e}", file=sys.stderr)
271
+
272
+
273
+ def _export_chunked(
274
+ args, result, output_dir: Path, source_path: Path,
275
+ formats: List[str], requested_formats: List[str]
276
+ ):
277
+ """Export chunked analysis results."""
278
+ subprojects = _get_filtered_subprojects(args, source_path)
279
+
280
+ for sp in subprojects:
281
+ _process_subproject(args, sp, output_dir)
282
+
283
+ # Merged summary
284
+ _export_registry_formats(args, result, output_dir, ['toon', 'context', 'evolution'])
285
+
286
+ if 'calls' in formats or 'calls_toon' in formats:
287
+ _export_calls(args, result, output_dir, formats)
288
+ if 'all' in requested_formats:
289
+ _export_project_toon(args, result, output_dir)
290
+
291
+ if source_path is not None:
292
+ from .code2logic import _export_code2logic
293
+ from .prompt import _export_chunked_prompt_txt
294
+ _export_code2logic(args, source_path, output_dir, formats)
295
+ _export_chunked_prompt_txt(args, output_dir, requested_formats, source_path, subprojects)
296
+
297
+ _export_readme(args, result, output_dir)
298
+ _export_index_html(args, output_dir)
299
+
300
+
301
+ def _get_filtered_subprojects(args, source_path: Path):
302
+ """Get filtered subprojects list."""
303
+ from ..core.large_repo import HierarchicalRepoSplitter
304
+
305
+ splitter = HierarchicalRepoSplitter(size_limit_kb=args.chunk_size)
306
+ subprojects = splitter.get_analysis_plan(source_path)
307
+
308
+ if getattr(args, 'only_subproject', None):
309
+ subprojects = [
310
+ sp for sp in subprojects
311
+ if sp.name == args.only_subproject or sp.name.startswith(args.only_subproject + '.')
312
+ ]
313
+ if getattr(args, 'skip_subprojects', None):
314
+ subprojects = [
315
+ sp for sp in subprojects
316
+ if not any(sp.name.startswith(skip) for skip in args.skip_subprojects)
317
+ ]
318
+ return subprojects
319
+
320
+
321
+ def _process_subproject(args, sp, output_dir: Path):
322
+ """Process a single subproject result."""
323
+ sp_output_dir = output_dir / sp.name.replace('.', '_')
324
+ if not sp_output_dir.exists():
325
+ return
326
+ for ext in ['.toon', '.yaml', '.json']:
327
+ result_file = sp_output_dir / f'analysis{ext}'
328
+ if result_file.exists():
329
+ if args.verbose:
330
+ level_name = {0: 'root', 1: 'L1', 2: 'L2'}.get(sp.level, f'L{sp.level}')
331
+ print(f" - Exported [{level_name}] {sp.name}")
332
+ break
333
+
334
+
335
+ # Backward-compatible aliases
336
+ _export_single_project = _export_single
337
+ _export_chunked_results = _export_chunked