code2llm 0.5.126__tar.gz → 0.5.128__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.128/MANIFEST.in +4 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/PKG-INFO +2 -2
- {code2llm-0.5.126 → code2llm-0.5.128}/README.md +1 -1
- code2llm-0.5.128/VERSION +1 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/__init__.py +1 -1
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_exports/orchestrator.py +1 -3
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/file_filter.py +6 -0
- code2llm-0.5.128/code2llm/exporters/evolution/__init__.py +78 -0
- code2llm-0.5.128/code2llm/exporters/evolution/computation.py +167 -0
- code2llm-0.5.128/code2llm/exporters/evolution/constants.py +25 -0
- code2llm-0.5.128/code2llm/exporters/evolution/exclusion.py +17 -0
- code2llm-0.5.128/code2llm/exporters/evolution/render.py +195 -0
- code2llm-0.5.128/code2llm/exporters/evolution/yaml_export.py +103 -0
- code2llm-0.5.128/code2llm/exporters/evolution_exporter.py +74 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/flow_constants.py +4 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/nlp/__init__.py +1 -1
- code2llm-0.5.128/code2llm/parsers/toon_parser.py +147 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm.egg-info/PKG-INFO +2 -2
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm.egg-info/SOURCES.txt +9 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/pyproject.toml +1 -1
- code2llm-0.5.126/code2llm/exporters/evolution_exporter.py +0 -473
- {code2llm-0.5.126 → code2llm-0.5.128}/LICENSE +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/__main__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/pipeline_classifier.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/pipeline_detector.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/pipeline_resolver.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/type_inference.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/utils/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/analysis/utils/ast_helpers.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/api.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_analysis.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_commands.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_exports/code2logic.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_exports/formats.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_exports/orchestrator_chunked.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_exports/orchestrator_constants.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_exports/orchestrator_handlers.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_exports/prompt.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/cli_parser.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/analyzer.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/ast_registry.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/config.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/export_pipeline.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/file_analyzer.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/file_cache.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/gitignore.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/incremental.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/base.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/cpp.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/csharp.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/generic.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/go_lang.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/java.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/php.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/ruby.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/rust.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/ts_extractors.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/ts_parser.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/lang/typescript.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/large_repo.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/models.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/persistent_cache.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/refactoring.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/repo_files.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/dashboard_data.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/dashboard_renderer.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/index_generator/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/index_generator/renderer.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/index_generator/scanner.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/index_generator.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/map/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/map/alerts.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/map/details.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/map/header.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/map/module_list.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/map/utils.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/map/yaml_export.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/mermaid/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/mermaid/calls.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/mermaid/classic.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/mermaid/compact.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/mermaid/flow_compact.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/mermaid/flow_detailed.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/mermaid/flow_full.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/mermaid/utils.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/project_yaml/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/project_yaml/constants.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/project_yaml/core.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/project_yaml/evolution.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/project_yaml/health.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/project_yaml/hotspots.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/project_yaml/modules.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/project_yaml_exporter.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/readme/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/readme/content.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/readme/files.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/readme/insights.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/readme/sections.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/toon/helpers.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/toon/metrics_core.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/toon/metrics_duplicates.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/toon/metrics_health.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/_utils.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/llm_flow/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/llm_flow/analysis.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/llm_flow/cli.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/llm_flow/generator.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/llm_flow/nodes.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/llm_flow/parsing.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/llm_flow/utils.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/llm_task.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/mermaid/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/mermaid/fix.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/mermaid/png.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/mermaid/validation.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/generators/mermaid.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm.egg-info/requires.txt +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/code2llm.egg-info/top_level.txt +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/setup.cfg +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/setup.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_calls_toon_export.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_persistent_cache.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_project_toon_export.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_prompt_txt.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.126 → code2llm-0.5.128}/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.128
|
|
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,7 +67,7 @@ Dynamic: requires-python
|
|
|
67
67
|
|
|
68
68
|
## AI Cost Tracking
|
|
69
69
|
|
|
70
|
-
    
|
|
71
71
|
  
|
|
72
72
|
|
|
73
73
|
- 🤖 **LLM usage:** $7.5000 (166 commits)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
7
|
  
|
|
8
8
|
|
|
9
9
|
- 🤖 **LLM usage:** $7.5000 (166 commits)
|
code2llm-0.5.128/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.5.128
|
|
@@ -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.128"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -189,9 +189,7 @@ def _expand_all_formats(requested: List[str], include_png: bool = False) -> List
|
|
|
189
189
|
"""Expand 'all' to concrete format list."""
|
|
190
190
|
if 'all' not in requested:
|
|
191
191
|
return requested[:]
|
|
192
|
-
formats = ['toon', 'map', 'context', 'evolution']
|
|
193
|
-
if include_png:
|
|
194
|
-
formats.append('mermaid')
|
|
192
|
+
formats = ['toon', 'map', 'context', 'evolution', 'mermaid']
|
|
195
193
|
return formats
|
|
196
194
|
|
|
197
195
|
|
|
@@ -19,6 +19,12 @@ _SKIP_DIR_NAMES = frozenset({
|
|
|
19
19
|
'lib', 'lib64', 'site-packages', 'include', 'bin', 'share',
|
|
20
20
|
'.code2llm_cache',
|
|
21
21
|
'tests', 'test',
|
|
22
|
+
# Backup and auto-generated directories that often contain venvs
|
|
23
|
+
'.algitex', '.backup', 'backups', '.bak', 'bak',
|
|
24
|
+
# Additional venv patterns
|
|
25
|
+
'virtualenv', '.virtualenv', 'envs', '.envs', 'venv-', '.venv-',
|
|
26
|
+
# CI/CD and deployment artifacts
|
|
27
|
+
'.terraform', '.serverless', '.netlify', '.vercel',
|
|
22
28
|
})
|
|
23
29
|
|
|
24
30
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Evolution exporter package — prioritized refactoring queue for iterative improvement.
|
|
2
|
+
|
|
3
|
+
This package provides:
|
|
4
|
+
- constants: Thresholds and exclusion patterns
|
|
5
|
+
- exclusion: Path filtering logic
|
|
6
|
+
- computation: Metrics calculation (god modules, hub types, etc.)
|
|
7
|
+
- render: Text output generation for evolution.toon
|
|
8
|
+
- yaml_export: Structured YAML output for evolution.toon.yaml
|
|
9
|
+
|
|
10
|
+
All public names are re-exported here for backward compatibility
|
|
11
|
+
with the original evolution_exporter.py module structure.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Constants
|
|
15
|
+
from .constants import (
|
|
16
|
+
CC_SPLIT_THRESHOLD,
|
|
17
|
+
FAN_OUT_THRESHOLD,
|
|
18
|
+
GOD_MODULE_LINES,
|
|
19
|
+
HUB_TYPE_THRESHOLD,
|
|
20
|
+
EXCLUDE_PATTERNS,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Exclusion
|
|
24
|
+
from .exclusion import is_excluded
|
|
25
|
+
|
|
26
|
+
# Computation
|
|
27
|
+
from .computation import (
|
|
28
|
+
compute_func_data,
|
|
29
|
+
scan_file_sizes,
|
|
30
|
+
aggregate_file_stats,
|
|
31
|
+
make_relative_path,
|
|
32
|
+
filter_god_modules,
|
|
33
|
+
compute_god_modules,
|
|
34
|
+
compute_hub_types,
|
|
35
|
+
build_context,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Render
|
|
39
|
+
from .render import (
|
|
40
|
+
render_header,
|
|
41
|
+
render_next,
|
|
42
|
+
render_risks,
|
|
43
|
+
render_metrics_target,
|
|
44
|
+
render_patterns,
|
|
45
|
+
render_history,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# YAML Export
|
|
49
|
+
from .yaml_export import export_to_yaml
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
# Constants
|
|
53
|
+
'CC_SPLIT_THRESHOLD',
|
|
54
|
+
'FAN_OUT_THRESHOLD',
|
|
55
|
+
'GOD_MODULE_LINES',
|
|
56
|
+
'HUB_TYPE_THRESHOLD',
|
|
57
|
+
'EXCLUDE_PATTERNS',
|
|
58
|
+
# Exclusion
|
|
59
|
+
'is_excluded',
|
|
60
|
+
# Computation
|
|
61
|
+
'compute_func_data',
|
|
62
|
+
'scan_file_sizes',
|
|
63
|
+
'aggregate_file_stats',
|
|
64
|
+
'make_relative_path',
|
|
65
|
+
'filter_god_modules',
|
|
66
|
+
'compute_god_modules',
|
|
67
|
+
'compute_hub_types',
|
|
68
|
+
'build_context',
|
|
69
|
+
# Render
|
|
70
|
+
'render_header',
|
|
71
|
+
'render_next',
|
|
72
|
+
'render_risks',
|
|
73
|
+
'render_metrics_target',
|
|
74
|
+
'render_patterns',
|
|
75
|
+
'render_history',
|
|
76
|
+
# YAML Export
|
|
77
|
+
'export_to_yaml',
|
|
78
|
+
]
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Evolution exporter computation — metrics calculation for god modules, hub types, etc."""
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from code2llm.core.models import AnalysisResult, FunctionInfo
|
|
8
|
+
|
|
9
|
+
from .constants import GOD_MODULE_LINES, HUB_TYPE_THRESHOLD, CC_SPLIT_THRESHOLD
|
|
10
|
+
from .exclusion import is_excluded
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def compute_func_data(result: AnalysisResult) -> List[Dict]:
|
|
14
|
+
"""Compute per-function metrics, excluding venv."""
|
|
15
|
+
func_data = []
|
|
16
|
+
for qname, fi in result.functions.items():
|
|
17
|
+
if is_excluded(fi.file):
|
|
18
|
+
continue
|
|
19
|
+
cc = fi.complexity.get("cyclomatic_complexity", 0)
|
|
20
|
+
fan_out = len(set(fi.calls))
|
|
21
|
+
fan_in = len(set(fi.called_by))
|
|
22
|
+
func_data.append({
|
|
23
|
+
"qname": qname, "name": fi.name,
|
|
24
|
+
"class_name": fi.class_name, "cc": cc,
|
|
25
|
+
"fan_out": fan_out, "fan_in": fan_in,
|
|
26
|
+
"impact": cc * max(fan_out, 1),
|
|
27
|
+
"file": fi.file, "module": fi.module,
|
|
28
|
+
})
|
|
29
|
+
return sorted(func_data, key=lambda x: x["impact"], reverse=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def scan_file_sizes(project_path: Optional[Path]) -> Dict[str, int]:
|
|
33
|
+
"""Scan Python files and return line counts."""
|
|
34
|
+
file_lines: Dict[str, int] = {}
|
|
35
|
+
if not project_path or not project_path.is_dir():
|
|
36
|
+
return file_lines
|
|
37
|
+
|
|
38
|
+
for py in project_path.rglob("*.py"):
|
|
39
|
+
fpath = str(py)
|
|
40
|
+
if is_excluded(fpath):
|
|
41
|
+
continue
|
|
42
|
+
try:
|
|
43
|
+
lc = len(py.read_text(encoding="utf-8", errors="ignore").splitlines())
|
|
44
|
+
file_lines[fpath] = lc
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
return file_lines
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def aggregate_file_stats(
|
|
51
|
+
result: AnalysisResult,
|
|
52
|
+
file_lines: Dict[str, int]
|
|
53
|
+
) -> Dict[str, Dict]:
|
|
54
|
+
"""Aggregate function and class data per file."""
|
|
55
|
+
file_stats: Dict[str, Dict] = defaultdict(
|
|
56
|
+
lambda: {"lines": 0, "funcs": 0, "classes": set(), "max_cc": 0}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Initialize with line counts
|
|
60
|
+
for fpath, lc in file_lines.items():
|
|
61
|
+
file_stats[fpath]["lines"] = lc
|
|
62
|
+
|
|
63
|
+
# Aggregate function data
|
|
64
|
+
for qname, fi in result.functions.items():
|
|
65
|
+
if is_excluded(fi.file):
|
|
66
|
+
continue
|
|
67
|
+
fs = file_stats[fi.file]
|
|
68
|
+
fs["funcs"] += 1
|
|
69
|
+
fs["max_cc"] = max(fs["max_cc"], fi.complexity.get("cyclomatic_complexity", 0))
|
|
70
|
+
if fi.class_name:
|
|
71
|
+
fs["classes"].add(fi.class_name)
|
|
72
|
+
|
|
73
|
+
# Aggregate class data
|
|
74
|
+
for qname, ci in result.classes.items():
|
|
75
|
+
if not is_excluded(ci.file):
|
|
76
|
+
file_stats[ci.file]["classes"].add(ci.name)
|
|
77
|
+
|
|
78
|
+
return file_stats
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def make_relative_path(fpath: str, project_path: Optional[Path]) -> str:
|
|
82
|
+
"""Convert absolute path to relative path."""
|
|
83
|
+
if not project_path:
|
|
84
|
+
return fpath
|
|
85
|
+
try:
|
|
86
|
+
return str(Path(fpath).relative_to(project_path))
|
|
87
|
+
except ValueError:
|
|
88
|
+
return fpath
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def filter_god_modules(file_stats: Dict[str, Dict], project_path: Optional[Path]) -> List[Dict]:
|
|
92
|
+
"""Filter files to god modules (≥500 lines)."""
|
|
93
|
+
god_modules = []
|
|
94
|
+
for fpath, stats in file_stats.items():
|
|
95
|
+
if stats["lines"] >= GOD_MODULE_LINES:
|
|
96
|
+
rel = make_relative_path(fpath, project_path)
|
|
97
|
+
god_modules.append({
|
|
98
|
+
"file": rel,
|
|
99
|
+
"lines": stats["lines"],
|
|
100
|
+
"funcs": stats["funcs"],
|
|
101
|
+
"classes": len(stats["classes"]),
|
|
102
|
+
"max_cc": stats["max_cc"],
|
|
103
|
+
})
|
|
104
|
+
god_modules.sort(key=lambda x: x["lines"], reverse=True)
|
|
105
|
+
return god_modules
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def compute_god_modules(result: AnalysisResult) -> List[Dict]:
|
|
109
|
+
"""Identify god modules (≥500 lines) from project files."""
|
|
110
|
+
pp = Path(result.project_path) if result.project_path else None
|
|
111
|
+
|
|
112
|
+
file_lines = scan_file_sizes(pp)
|
|
113
|
+
file_stats = aggregate_file_stats(result, file_lines)
|
|
114
|
+
return filter_god_modules(file_stats, pp)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def compute_hub_types(result: AnalysisResult) -> List[Dict]:
|
|
118
|
+
"""Identify hub types consumed by many functions."""
|
|
119
|
+
type_consumers: Dict[str, int] = defaultdict(int)
|
|
120
|
+
type_producers: Dict[str, int] = defaultdict(int)
|
|
121
|
+
for qname, fi in result.functions.items():
|
|
122
|
+
ret = fi.complexity.get("return_type", "")
|
|
123
|
+
if ret:
|
|
124
|
+
type_producers[ret] += 1
|
|
125
|
+
for arg_type in fi.complexity.get("arg_types", []):
|
|
126
|
+
if arg_type:
|
|
127
|
+
type_consumers[arg_type] += 1
|
|
128
|
+
hub_types = [
|
|
129
|
+
{"type": t, "consumers": c, "producers": type_producers.get(t, 0)}
|
|
130
|
+
for t, c in type_consumers.items()
|
|
131
|
+
if c >= HUB_TYPE_THRESHOLD
|
|
132
|
+
]
|
|
133
|
+
hub_types.sort(key=lambda x: x["consumers"], reverse=True)
|
|
134
|
+
return hub_types
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def build_context(result: AnalysisResult) -> Dict[str, Any]:
|
|
138
|
+
"""Build context dict with all computed metrics."""
|
|
139
|
+
ctx = {
|
|
140
|
+
"result": result,
|
|
141
|
+
}
|
|
142
|
+
ctx["funcs"] = compute_func_data(result)
|
|
143
|
+
ctx["god_modules"] = compute_god_modules(result)
|
|
144
|
+
ctx["hub_types"] = compute_hub_types(result)
|
|
145
|
+
|
|
146
|
+
# Overall metrics
|
|
147
|
+
all_cc = [f["cc"] for f in ctx["funcs"]]
|
|
148
|
+
ctx["avg_cc"] = round(sum(all_cc) / len(all_cc), 1) if all_cc else 0.0
|
|
149
|
+
ctx["max_cc"] = max(all_cc) if all_cc else 0
|
|
150
|
+
ctx["total_funcs"] = len(all_cc)
|
|
151
|
+
ctx["total_files"] = len(set(f["file"] for f in ctx["funcs"])) or 1
|
|
152
|
+
ctx["high_cc_count"] = len([c for c in all_cc if c >= CC_SPLIT_THRESHOLD])
|
|
153
|
+
ctx["critical_count"] = len([c for c in all_cc if c >= 10])
|
|
154
|
+
|
|
155
|
+
return ctx
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
__all__ = [
|
|
159
|
+
'compute_func_data',
|
|
160
|
+
'scan_file_sizes',
|
|
161
|
+
'aggregate_file_stats',
|
|
162
|
+
'make_relative_path',
|
|
163
|
+
'filter_god_modules',
|
|
164
|
+
'compute_god_modules',
|
|
165
|
+
'compute_hub_types',
|
|
166
|
+
'build_context',
|
|
167
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Evolution exporter constants — thresholds and configuration."""
|
|
2
|
+
|
|
3
|
+
# Thresholds
|
|
4
|
+
CC_SPLIT_THRESHOLD = 15
|
|
5
|
+
FAN_OUT_THRESHOLD = 10
|
|
6
|
+
GOD_MODULE_LINES = 500
|
|
7
|
+
HUB_TYPE_THRESHOLD = 10
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Exclude patterns (mirrors ToonExporter)
|
|
11
|
+
EXCLUDE_PATTERNS = {
|
|
12
|
+
'venv', '.venv', 'env', '.env', 'publish-env', 'test-env',
|
|
13
|
+
'site-packages', 'node_modules', '__pycache__', '.git',
|
|
14
|
+
'dist', 'build', 'egg-info', '.tox', '.mypy_cache',
|
|
15
|
+
'examples', 'benchmarks', 'tests', 'scripts', 'demo_langs',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
'CC_SPLIT_THRESHOLD',
|
|
21
|
+
'FAN_OUT_THRESHOLD',
|
|
22
|
+
'GOD_MODULE_LINES',
|
|
23
|
+
'HUB_TYPE_THRESHOLD',
|
|
24
|
+
'EXCLUDE_PATTERNS',
|
|
25
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Evolution exporter exclusion logic — path filtering."""
|
|
2
|
+
|
|
3
|
+
from .constants import EXCLUDE_PATTERNS
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_excluded(path: str) -> bool:
|
|
7
|
+
"""Check if path should be excluded (venv, site-packages, etc.)."""
|
|
8
|
+
path_lower = path.lower().replace('\\', '/')
|
|
9
|
+
for pattern in EXCLUDE_PATTERNS:
|
|
10
|
+
if f'/{pattern}/' in path_lower or path_lower.startswith(f'{pattern}/'):
|
|
11
|
+
return True
|
|
12
|
+
if pattern in path_lower.split('/'):
|
|
13
|
+
return True
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ['is_excluded']
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Evolution exporter render — text output generation for evolution.toon."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List
|
|
6
|
+
|
|
7
|
+
from .constants import CC_SPLIT_THRESHOLD
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def render_header(ctx: Dict[str, Any]) -> List[str]:
|
|
11
|
+
"""Render header line."""
|
|
12
|
+
return [
|
|
13
|
+
f"# code2llm/evolution | {ctx['total_funcs']} func"
|
|
14
|
+
f" | {ctx['total_files']}f | {datetime.now().strftime('%Y-%m-%d')}",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def render_next(ctx: Dict[str, Any]) -> List[str]:
|
|
19
|
+
"""Render NEXT — ranked refactoring queue."""
|
|
20
|
+
actions: List[Dict[str, Any]] = []
|
|
21
|
+
|
|
22
|
+
# 1. God modules → split
|
|
23
|
+
for gm in ctx["god_modules"][:3]:
|
|
24
|
+
actions.append({
|
|
25
|
+
"priority": "!!",
|
|
26
|
+
"action": "SPLIT",
|
|
27
|
+
"target": gm["file"],
|
|
28
|
+
"why": f"{gm['lines']}L, {gm['classes']} classes, max CC={gm['max_cc']}",
|
|
29
|
+
"effort": "~4h",
|
|
30
|
+
"impact_score": gm["lines"] * gm["max_cc"],
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
# 2. High CC functions → split
|
|
34
|
+
for f in ctx["funcs"][:20]:
|
|
35
|
+
if f["cc"] >= CC_SPLIT_THRESHOLD:
|
|
36
|
+
display = f["name"]
|
|
37
|
+
if f["class_name"]:
|
|
38
|
+
display = f"{f['class_name']}.{f['name']}"
|
|
39
|
+
actions.append({
|
|
40
|
+
"priority": "!!" if f["cc"] >= 25 else "!",
|
|
41
|
+
"action": "SPLIT-FUNC",
|
|
42
|
+
"target": f"{display} CC={f['cc']} fan={f['fan_out']}",
|
|
43
|
+
"why": f"CC={f['cc']} exceeds {CC_SPLIT_THRESHOLD}",
|
|
44
|
+
"effort": "~1h",
|
|
45
|
+
"impact_score": f["impact"],
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
# 3. Hub types → interface segregation
|
|
49
|
+
for ht in ctx["hub_types"][:3]:
|
|
50
|
+
if ht["consumers"] >= 20:
|
|
51
|
+
actions.append({
|
|
52
|
+
"priority": "!",
|
|
53
|
+
"action": "INTERFACE-SPLIT",
|
|
54
|
+
"target": f"{ht['type']} consumed:{ht['consumers']}",
|
|
55
|
+
"why": f"Hub type with {ht['consumers']} consumers → split interface",
|
|
56
|
+
"effort": "~6h",
|
|
57
|
+
"impact_score": ht["consumers"] * 10,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
# Sort by impact and limit
|
|
61
|
+
actions.sort(key=lambda x: x["impact_score"], reverse=True)
|
|
62
|
+
actions = actions[:10]
|
|
63
|
+
|
|
64
|
+
if not actions:
|
|
65
|
+
return ["NEXT[0]: no refactoring needed"]
|
|
66
|
+
|
|
67
|
+
lines = [f"NEXT[{len(actions)}] (ranked by impact):"]
|
|
68
|
+
for i, a in enumerate(actions, 1):
|
|
69
|
+
lines.append(
|
|
70
|
+
f" [{i}] {a['priority']:2s} {a['action']:15s} {a['target']}"
|
|
71
|
+
)
|
|
72
|
+
lines.append(
|
|
73
|
+
f" WHY: {a['why']}"
|
|
74
|
+
)
|
|
75
|
+
lines.append(
|
|
76
|
+
f" EFFORT: {a['effort']} IMPACT: {a['impact_score']}"
|
|
77
|
+
)
|
|
78
|
+
lines.append("")
|
|
79
|
+
|
|
80
|
+
return lines
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def render_risks(ctx: Dict[str, Any]) -> List[str]:
|
|
84
|
+
"""Render RISKS — potential breaking changes."""
|
|
85
|
+
risks: List[str] = []
|
|
86
|
+
|
|
87
|
+
# God module splits may break imports
|
|
88
|
+
for gm in ctx["god_modules"][:3]:
|
|
89
|
+
risks.append(
|
|
90
|
+
f"⚠ Splitting {gm['file']} may break {gm['funcs']} import paths"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Hub type splits change public API
|
|
94
|
+
for ht in ctx["hub_types"][:2]:
|
|
95
|
+
if ht["consumers"] >= 20:
|
|
96
|
+
risks.append(
|
|
97
|
+
f"⚠ Splitting {ht['type']} changes API for {ht['consumers']} consumers"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if not risks:
|
|
101
|
+
return ["RISKS[0]: none"]
|
|
102
|
+
|
|
103
|
+
lines = [f"RISKS[{len(risks)}]:"]
|
|
104
|
+
for r in risks:
|
|
105
|
+
lines.append(f" {r}")
|
|
106
|
+
return lines
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def render_metrics_target(ctx: Dict[str, Any]) -> List[str]:
|
|
110
|
+
"""Render METRICS-TARGET — baseline vs goals."""
|
|
111
|
+
avg = ctx["avg_cc"]
|
|
112
|
+
max_cc = ctx["max_cc"]
|
|
113
|
+
gods = len(ctx["god_modules"])
|
|
114
|
+
hubs = len(ctx["hub_types"])
|
|
115
|
+
high = ctx["high_cc_count"]
|
|
116
|
+
|
|
117
|
+
# Compute targets (halve the worst metrics)
|
|
118
|
+
target_avg = round(min(avg * 0.7, 5.0), 1)
|
|
119
|
+
target_max = min(max_cc // 2, 20)
|
|
120
|
+
target_gods = 0
|
|
121
|
+
target_high = max(high // 2, 0)
|
|
122
|
+
|
|
123
|
+
lines = [
|
|
124
|
+
"METRICS-TARGET:",
|
|
125
|
+
f" CC̄: {avg} → ≤{target_avg}",
|
|
126
|
+
f" max-CC: {max_cc} → ≤{target_max}",
|
|
127
|
+
f" god-modules: {gods} → {target_gods}",
|
|
128
|
+
f" high-CC(≥{CC_SPLIT_THRESHOLD}): {high} → ≤{target_high}",
|
|
129
|
+
f" hub-types: {hubs} → ≤{max(hubs - 2, 0)}",
|
|
130
|
+
]
|
|
131
|
+
return lines
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def render_patterns(ctx: Dict[str, Any]) -> List[str]:
|
|
135
|
+
"""Render PATTERNS — shared language parser extraction patterns."""
|
|
136
|
+
lines = [
|
|
137
|
+
"PATTERNS (language parser shared logic):",
|
|
138
|
+
" _extract_declarations() in base.py — unified extraction for:",
|
|
139
|
+
" - TypeScript: interfaces, types, classes, functions, arrow funcs",
|
|
140
|
+
" - PHP: namespaces, traits, classes, functions, includes",
|
|
141
|
+
" - Ruby: modules, classes, methods, requires",
|
|
142
|
+
" - C++: classes, structs, functions, #includes",
|
|
143
|
+
" - C#: classes, interfaces, methods, usings",
|
|
144
|
+
" - Java: classes, interfaces, methods, imports",
|
|
145
|
+
" - Go: packages, functions, structs",
|
|
146
|
+
" - Rust: modules, functions, traits, use statements",
|
|
147
|
+
"",
|
|
148
|
+
" Shared regex patterns per language:",
|
|
149
|
+
" - import: language-specific import/require/using patterns",
|
|
150
|
+
" - class: class/struct/trait declarations with inheritance",
|
|
151
|
+
" - function: function/method signatures with visibility",
|
|
152
|
+
" - brace_tracking: for C-family languages ({ })",
|
|
153
|
+
" - end_keyword_tracking: for Ruby (module/class/def...end)",
|
|
154
|
+
"",
|
|
155
|
+
" Benefits:",
|
|
156
|
+
" - Consistent extraction logic across all languages",
|
|
157
|
+
" - Reduced code duplication (~70% reduction in parser LOC)",
|
|
158
|
+
" - Easier maintenance: fix once, apply everywhere",
|
|
159
|
+
" - Standardized FunctionInfo/ClassInfo models",
|
|
160
|
+
]
|
|
161
|
+
return lines
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def render_history(ctx: Dict[str, Any], output_path: str) -> List[str]:
|
|
165
|
+
"""Render HISTORY — load previous evolution.toon.yaml if exists."""
|
|
166
|
+
lines = ["HISTORY:"]
|
|
167
|
+
|
|
168
|
+
prev_path = Path(output_path)
|
|
169
|
+
if prev_path.exists():
|
|
170
|
+
try:
|
|
171
|
+
prev_content = prev_path.read_text(encoding="utf-8")
|
|
172
|
+
# Extract previous metrics line
|
|
173
|
+
for line in prev_content.splitlines():
|
|
174
|
+
if line.strip().startswith("CC̄:"):
|
|
175
|
+
prev_avg = line.split("→")[0].strip().split()[-1]
|
|
176
|
+
lines.append(f" prev CC̄={prev_avg} → now CC̄={ctx['avg_cc']}")
|
|
177
|
+
break
|
|
178
|
+
else:
|
|
179
|
+
lines.append(f" previous evolution.toon.yaml found but no metrics parsed")
|
|
180
|
+
except Exception:
|
|
181
|
+
lines.append(f" (could not read previous evolution.toon.yaml)")
|
|
182
|
+
else:
|
|
183
|
+
lines.append(f" (first run — no previous data)")
|
|
184
|
+
|
|
185
|
+
return lines
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
__all__ = [
|
|
189
|
+
'render_header',
|
|
190
|
+
'render_next',
|
|
191
|
+
'render_risks',
|
|
192
|
+
'render_metrics_target',
|
|
193
|
+
'render_patterns',
|
|
194
|
+
'render_history',
|
|
195
|
+
]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Evolution exporter YAML output — evolution.toon.yaml structured format."""
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List
|
|
6
|
+
|
|
7
|
+
from code2llm.core.models import AnalysisResult
|
|
8
|
+
|
|
9
|
+
from .constants import CC_SPLIT_THRESHOLD
|
|
10
|
+
from .computation import build_context
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def export_to_yaml(result: AnalysisResult, output_path: str) -> None:
|
|
14
|
+
"""Generate evolution.toon.yaml (structured YAML)."""
|
|
15
|
+
ctx = build_context(result)
|
|
16
|
+
|
|
17
|
+
# Build refactoring actions
|
|
18
|
+
actions = []
|
|
19
|
+
for gm in ctx["god_modules"][:3]:
|
|
20
|
+
actions.append({
|
|
21
|
+
"priority": "high",
|
|
22
|
+
"action": "SPLIT",
|
|
23
|
+
"target": gm["file"],
|
|
24
|
+
"reason": f"{gm['lines']}L, {gm['classes']} classes, max CC={gm['max_cc']}",
|
|
25
|
+
"effort": "~4h",
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
for f in ctx["funcs"][:20]:
|
|
29
|
+
if f["cc"] >= CC_SPLIT_THRESHOLD:
|
|
30
|
+
actions.append({
|
|
31
|
+
"priority": "critical" if f["cc"] >= 25 else "high",
|
|
32
|
+
"action": "SPLIT-FUNC",
|
|
33
|
+
"target": f"{f['class_name']}.{f['name']}" if f["class_name"] else f["name"],
|
|
34
|
+
"cc": f["cc"],
|
|
35
|
+
"fan_out": f["fan_out"],
|
|
36
|
+
"reason": f"CC={f['cc']} exceeds {CC_SPLIT_THRESHOLD}",
|
|
37
|
+
"effort": "~1h",
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
for ht in ctx["hub_types"][:3]:
|
|
41
|
+
if ht["consumers"] >= 20:
|
|
42
|
+
actions.append({
|
|
43
|
+
"priority": "medium",
|
|
44
|
+
"action": "INTERFACE-SPLIT",
|
|
45
|
+
"target": ht["type"],
|
|
46
|
+
"consumers": ht["consumers"],
|
|
47
|
+
"reason": f"Hub type with {ht['consumers']} consumers",
|
|
48
|
+
"effort": "~6h",
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
actions.sort(key=lambda x: x.get("priority", "") == "critical", reverse=True)
|
|
52
|
+
|
|
53
|
+
# Build risks
|
|
54
|
+
risks = []
|
|
55
|
+
for gm in ctx["god_modules"][:3]:
|
|
56
|
+
risks.append({
|
|
57
|
+
"type": "breaking_imports",
|
|
58
|
+
"target": gm["file"],
|
|
59
|
+
"impact": f"may break {gm['funcs']} import paths",
|
|
60
|
+
})
|
|
61
|
+
for ht in ctx["hub_types"][:2]:
|
|
62
|
+
if ht["consumers"] >= 20:
|
|
63
|
+
risks.append({
|
|
64
|
+
"type": "api_change",
|
|
65
|
+
"target": ht["type"],
|
|
66
|
+
"impact": f"changes API for {ht['consumers']} consumers",
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
from datetime import datetime
|
|
70
|
+
data = {
|
|
71
|
+
"format": "evolution-toon-yaml",
|
|
72
|
+
"timestamp": datetime.now().strftime("%Y-%m-%d"),
|
|
73
|
+
"stats": {
|
|
74
|
+
"total_funcs": ctx["total_funcs"],
|
|
75
|
+
"total_files": ctx["total_files"],
|
|
76
|
+
"avg_cc": ctx["avg_cc"],
|
|
77
|
+
"max_cc": ctx["max_cc"],
|
|
78
|
+
"high_cc_count": ctx["high_cc_count"],
|
|
79
|
+
"critical_count": ctx["critical_count"],
|
|
80
|
+
},
|
|
81
|
+
"refactoring": {
|
|
82
|
+
"action_count": len(actions),
|
|
83
|
+
"actions": actions[:10],
|
|
84
|
+
},
|
|
85
|
+
"risks": {
|
|
86
|
+
"count": len(risks),
|
|
87
|
+
"items": risks,
|
|
88
|
+
},
|
|
89
|
+
"metrics_target": {
|
|
90
|
+
"avg_cc": {"current": ctx["avg_cc"], "target": round(min(ctx["avg_cc"] * 0.7, 5.0), 1)},
|
|
91
|
+
"max_cc": {"current": ctx["max_cc"], "target": min(ctx["max_cc"] // 2, 20)},
|
|
92
|
+
"god_modules": {"current": len(ctx["god_modules"]), "target": 0},
|
|
93
|
+
"high_cc": {"current": ctx["high_cc_count"], "target": max(ctx["high_cc_count"] // 2, 0)},
|
|
94
|
+
"hub_types": {"current": len(ctx["hub_types"]), "target": max(len(ctx["hub_types"]) - 2, 0)},
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
100
|
+
yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
__all__ = ['export_to_yaml']
|