code2llm 0.5.110__tar.gz → 0.5.112__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.
- {code2llm-0.5.110 → code2llm-0.5.112}/PKG-INFO +7 -51
- {code2llm-0.5.110 → code2llm-0.5.112}/README.md +6 -50
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/__init__.py +1 -1
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/cli_exports/formats.py +11 -9
- code2llm-0.5.112/code2llm/exporters/project_yaml/__init__.py +15 -0
- code2llm-0.5.112/code2llm/exporters/project_yaml/constants.py +15 -0
- code2llm-0.5.112/code2llm/exporters/project_yaml/core.py +118 -0
- code2llm-0.5.112/code2llm/exporters/project_yaml/evolution.py +46 -0
- code2llm-0.5.112/code2llm/exporters/project_yaml/health.py +103 -0
- code2llm-0.5.112/code2llm/exporters/project_yaml/hotspots.py +106 -0
- code2llm-0.5.112/code2llm/exporters/project_yaml/modules.py +151 -0
- code2llm-0.5.112/code2llm/exporters/project_yaml_exporter.py +15 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/yaml_exporter.py +72 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/nlp/__init__.py +1 -1
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm.egg-info/PKG-INFO +7 -51
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm.egg-info/SOURCES.txt +7 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/pyproject.toml +1 -1
- {code2llm-0.5.110 → code2llm-0.5.112}/setup.py +1 -1
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_project_toon_export.py +1 -1
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_prompt_txt.py +2 -1
- code2llm-0.5.110/code2llm/exporters/project_yaml_exporter.py +0 -513
- {code2llm-0.5.110 → code2llm-0.5.112}/LICENSE +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/__main__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/pipeline_detector.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/type_inference.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/utils/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/analysis/utils/ast_helpers.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/api.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/cli.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/cli_analysis.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/cli_commands.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/cli_exports/code2logic.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/cli_exports/orchestrator.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/cli_exports/prompt.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/cli_parser.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/analyzer.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/ast_registry.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/config.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/export_pipeline.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/file_analyzer.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/file_cache.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/file_filter.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/gitignore.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/incremental.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/base.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/cpp.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/csharp.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/generic.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/go_lang.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/java.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/php.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/ruby.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/rust.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/ts_extractors.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/ts_parser.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/lang/typescript.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/large_repo.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/models.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/refactoring.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/repo_files.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/flow_constants.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/index_generator.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/toon/helpers.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/generators/llm_task.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/generators/mermaid.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm.egg-info/requires.txt +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/code2llm.egg-info/top_level.txt +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/setup.cfg +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.110 → code2llm-0.5.112}/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.
|
|
3
|
+
Version: 0.5.112
|
|
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
|
-
     
|
|
71
|
+
  
|
|
72
72
|
|
|
73
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
74
|
-
- 👤 **Human dev:** ~$
|
|
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**:
|
|
405
|
+
**Total Functions**: 1041
|
|
450
406
|
**Total Classes**: 111
|
|
451
|
-
**Modules**:
|
|
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
|
-
     
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
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**:
|
|
341
|
+
**Total Functions**: 1041
|
|
386
342
|
**Total Classes**: 111
|
|
387
|
-
**Modules**:
|
|
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.
|
|
11
|
+
__version__ = "0.5.112"
|
|
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 =
|
|
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,15 +216,15 @@ 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
|
|
219
|
+
"""Export standalone calls.toon.yaml (call graph in toon format).
|
|
218
220
|
|
|
219
|
-
Generates calls.yaml
|
|
220
|
-
analysis of call graphs
|
|
221
|
+
Generates calls.toon.yaml in human-readable toon format with hubs, modules,
|
|
222
|
+
and edges sections — useful for programmatic analysis of call graphs.
|
|
221
223
|
"""
|
|
222
224
|
yaml_exporter = YAMLExporter()
|
|
223
|
-
yaml_exporter.
|
|
225
|
+
yaml_exporter.export_calls_toon(result, str(output_dir / 'calls.toon.yaml'))
|
|
224
226
|
if args.verbose:
|
|
225
|
-
print(f" - CALLS (
|
|
227
|
+
print(f" - CALLS (toon format): {output_dir / 'calls.toon.yaml'}")
|
|
226
228
|
|
|
227
229
|
|
|
228
230
|
def _export_mermaid(args, result, output_dir: Path):
|
|
@@ -245,9 +247,9 @@ def _export_mermaid(args, result, output_dir: Path):
|
|
|
245
247
|
exporter.export_call_graph(result, str(output_dir / 'calls.mmd'))
|
|
246
248
|
exporter.export_compact(result, str(output_dir / 'compact_flow.mmd'))
|
|
247
249
|
|
|
248
|
-
# Export calls.yaml (structured call graph data)
|
|
250
|
+
# Export calls.toon.yaml (structured call graph data in toon format)
|
|
249
251
|
yaml_exporter = YAMLExporter()
|
|
250
|
-
yaml_exporter.
|
|
252
|
+
yaml_exporter.export_calls_toon(result, str(output_dir / 'calls.toon.yaml'))
|
|
251
253
|
|
|
252
254
|
if args.verbose:
|
|
253
255
|
files = ['flow.mmd']
|
|
@@ -255,7 +257,7 @@ def _export_mermaid(args, result, output_dir: Path):
|
|
|
255
257
|
files.append('flow_detailed.mmd')
|
|
256
258
|
if getattr(args, 'flow_full', False):
|
|
257
259
|
files.append('flow_full.mmd')
|
|
258
|
-
files.extend(['calls.mmd', 'compact_flow.mmd', 'calls.yaml'])
|
|
260
|
+
files.extend(['calls.mmd', 'compact_flow.mmd', 'calls.toon.yaml'])
|
|
259
261
|
print(f" - Mermaid: {output_dir}/*.mmd ({', '.join(files)})")
|
|
260
262
|
|
|
261
263
|
_export_mermaid_pngs(args, output_dir)
|
|
@@ -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]}
|