code2llm 0.5.145__tar.gz → 0.5.146__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.145/code2llm.egg-info → code2llm-0.5.146}/PKG-INFO +5 -7
- {code2llm-0.5.145 → code2llm-0.5.146}/README.md +4 -6
- code2llm-0.5.146/VERSION +1 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/__init__.py +1 -1
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_exports/formats.py +26 -6
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_exports/orchestrator.py +52 -1
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_exports/orchestrator_handlers.py +36 -11
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_exports/prompt.py +6 -1
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/project_yaml/core.py +1 -1
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/toon/helpers.py +40 -9
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/toon/metrics.py +1 -1
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/toon/metrics_core.py +49 -57
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/nlp/__init__.py +1 -1
- {code2llm-0.5.145 → code2llm-0.5.146/code2llm.egg-info}/PKG-INFO +5 -7
- {code2llm-0.5.145 → code2llm-0.5.146}/pyproject.toml +1 -1
- code2llm-0.5.145/VERSION +0 -1
- {code2llm-0.5.145 → code2llm-0.5.146}/LICENSE +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/MANIFEST.in +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/__main__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/pipeline_classifier.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/pipeline_detector.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/pipeline_resolver.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/type_inference.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/utils/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/analysis/utils/ast_helpers.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/api.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_analysis.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_commands.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_exports/code2logic.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_exports/orchestrator_chunked.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_exports/orchestrator_constants.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/cli_parser.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/analyzer.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/ast_registry.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/config.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/export_pipeline.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/file_analyzer.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/file_cache.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/file_filter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/gitignore.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/incremental.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/base.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/cpp.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/csharp.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/generic.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/go_lang.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/java.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/php.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/ruby.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/rust.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/ts_extractors.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/ts_parser.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/lang/typescript.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/large_repo.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/models.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/persistent_cache.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/refactoring.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/repo_files.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/dashboard_data.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/dashboard_renderer.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/evolution/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/evolution/computation.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/evolution/constants.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/evolution/exclusion.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/evolution/render.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/evolution/yaml_export.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/flow_constants.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/index_generator/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/index_generator/renderer.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/index_generator/scanner.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/index_generator.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/map/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/map/alerts.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/map/details.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/map/header.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/map/module_list.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/map/utils.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/map/yaml_export.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/mermaid/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/mermaid/calls.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/mermaid/classic.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/mermaid/compact.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/mermaid/flow_compact.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/mermaid/flow_detailed.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/mermaid/flow_full.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/mermaid/utils.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/project_yaml/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/project_yaml/constants.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/project_yaml/evolution.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/project_yaml/health.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/project_yaml/hotspots.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/project_yaml/modules.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/project_yaml_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/readme/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/readme/content.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/readme/files.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/readme/insights.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/readme/sections.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/toon/metrics_duplicates.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/toon/metrics_health.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/_utils.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/llm_flow/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/llm_flow/analysis.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/llm_flow/cli.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/llm_flow/generator.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/llm_flow/nodes.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/llm_flow/parsing.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/llm_flow/utils.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/llm_task.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/mermaid/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/mermaid/fix.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/mermaid/png.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/mermaid/validation.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/generators/mermaid.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/parsers/toon_parser.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm.egg-info/SOURCES.txt +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm.egg-info/requires.txt +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/code2llm.egg-info/top_level.txt +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/setup.cfg +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/setup.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_cache_invalidation_e2e.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_calls_toon_export.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_declarative_collection.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_file_analyzer_tagging.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_orchestrator_cache_mtime.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_persistent_cache.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_project_toon_export.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_prompt_txt.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.145 → code2llm-0.5.146}/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.146
|
|
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,18 +67,16 @@ 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 (198 commits)
|
|
74
|
+
- 👤 **Human dev:** ~$7224 (72.2h @ $100/h, 30min dedup)
|
|
75
75
|
|
|
76
76
|
Generated on 2026-05-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
77
77
|
|
|
78
78
|
---
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
80
|
This directory contains the complete analysis of your project generated by `code2llm`. Each file serves a specific purpose for understanding, refactoring, and documenting your codebase.
|
|
83
81
|
|
|
84
82
|
## 📁 Generated Files Overview
|
|
@@ -3,18 +3,16 @@
|
|
|
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 (198 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$7224 (72.2h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-05-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
16
|
This directory contains the complete analysis of your project generated by `code2llm`. Each file serves a specific purpose for understanding, refactoring, and documenting your codebase.
|
|
19
17
|
|
|
20
18
|
## 📁 Generated Files Overview
|
code2llm-0.5.146/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.5.146
|
|
@@ -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.146"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
|
+
import time
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
@@ -13,6 +14,7 @@ from code2llm.exporters import (
|
|
|
13
14
|
ArticleViewGenerator, HTMLDashboardGenerator,
|
|
14
15
|
load_project_yaml, IndexHTMLGenerator,
|
|
15
16
|
)
|
|
17
|
+
from .orchestrator import _inject_generation_time as _inject_time
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
def _export_evolution(args, result, output_dir: Path):
|
|
@@ -21,9 +23,12 @@ def _export_evolution(args, result, output_dir: Path):
|
|
|
21
23
|
return
|
|
22
24
|
exporter = EvolutionExporter()
|
|
23
25
|
filepath = output_dir / 'evolution.toon.yaml'
|
|
26
|
+
t0 = time.monotonic()
|
|
24
27
|
exporter.export(result, str(filepath))
|
|
28
|
+
elapsed = time.monotonic() - t0
|
|
29
|
+
_inject_time(filepath, elapsed)
|
|
25
30
|
if args.verbose:
|
|
26
|
-
print(f" - EVOLUTION (refactoring queue): {filepath}")
|
|
31
|
+
print(f" - EVOLUTION (refactoring queue): {filepath} ({elapsed:.2f}s)")
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
def _export_data_structures(args, result, output_dir: Path):
|
|
@@ -43,9 +48,12 @@ def _export_context_fallback(args, result, output_dir: Path, formats: list):
|
|
|
43
48
|
return
|
|
44
49
|
exporter = ContextExporter()
|
|
45
50
|
filepath = output_dir / 'context.md'
|
|
51
|
+
t0 = time.monotonic()
|
|
46
52
|
exporter.export(result, str(filepath))
|
|
53
|
+
elapsed = time.monotonic() - t0
|
|
54
|
+
_inject_time(filepath, elapsed)
|
|
47
55
|
if args.verbose:
|
|
48
|
-
print(f" - CONTEXT (LLM narrative): {filepath}")
|
|
56
|
+
print(f" - CONTEXT (LLM narrative): {filepath} ({elapsed:.2f}s)")
|
|
49
57
|
|
|
50
58
|
|
|
51
59
|
def _export_readme(args, result, output_dir: Path):
|
|
@@ -54,18 +62,24 @@ def _export_readme(args, result, output_dir: Path):
|
|
|
54
62
|
return
|
|
55
63
|
exporter = READMEExporter()
|
|
56
64
|
filepath = output_dir / 'README.md'
|
|
65
|
+
t0 = time.monotonic()
|
|
57
66
|
exporter.export(result, str(filepath))
|
|
67
|
+
elapsed = time.monotonic() - t0
|
|
68
|
+
_inject_time(filepath, elapsed)
|
|
58
69
|
if args.verbose:
|
|
59
|
-
print(f" - README (documentation): {filepath}")
|
|
70
|
+
print(f" - README (documentation): {filepath} ({elapsed:.2f}s)")
|
|
60
71
|
|
|
61
72
|
|
|
62
73
|
def _export_project_yaml(args, result, output_dir: Path):
|
|
63
74
|
"""Export unified project.yaml — single source of truth."""
|
|
64
75
|
exporter = ProjectYAMLExporter()
|
|
65
76
|
filepath = output_dir / 'project.yaml'
|
|
77
|
+
t0 = time.monotonic()
|
|
66
78
|
exporter.export(result, str(filepath))
|
|
79
|
+
elapsed = time.monotonic() - t0
|
|
80
|
+
_inject_time(filepath, elapsed)
|
|
67
81
|
if getattr(args, 'verbose', False):
|
|
68
|
-
print(f" - PROJECT-YAML (single source of truth): {filepath}")
|
|
82
|
+
print(f" - PROJECT-YAML (single source of truth): {filepath} ({elapsed:.2f}s)")
|
|
69
83
|
return filepath
|
|
70
84
|
|
|
71
85
|
|
|
@@ -79,10 +93,13 @@ def _export_project_toon(args, result, output_dir: Path):
|
|
|
79
93
|
|
|
80
94
|
exporter = ToonViewGenerator()
|
|
81
95
|
filepath = output_dir / 'project.toon.yaml'
|
|
96
|
+
t0 = time.monotonic()
|
|
82
97
|
exporter.generate(data, str(filepath))
|
|
98
|
+
elapsed = time.monotonic() - t0
|
|
99
|
+
_inject_time(filepath, elapsed)
|
|
83
100
|
|
|
84
101
|
if getattr(args, 'verbose', False):
|
|
85
|
-
print(f" - PROJECT-TOON (project overview): {filepath}")
|
|
102
|
+
print(f" - PROJECT-TOON (project overview): {filepath} ({elapsed:.2f}s)")
|
|
86
103
|
|
|
87
104
|
return filepath
|
|
88
105
|
|
|
@@ -129,9 +146,12 @@ def _export_simple_formats(args, result, output_dir: Path, formats):
|
|
|
129
146
|
exporter = exporter_cls()
|
|
130
147
|
# Export as plain text TOON format with .toon.yaml extension
|
|
131
148
|
filepath = output_dir / filename
|
|
149
|
+
t0 = time.monotonic()
|
|
132
150
|
exporter.export(result, str(filepath))
|
|
151
|
+
elapsed = time.monotonic() - t0
|
|
152
|
+
_inject_time(filepath, elapsed)
|
|
133
153
|
if args.verbose:
|
|
134
|
-
print(f" - {label}: {filepath}")
|
|
154
|
+
print(f" - {label}: {filepath} ({elapsed:.2f}s)")
|
|
135
155
|
|
|
136
156
|
# Unified project.yaml (single source of truth)
|
|
137
157
|
if 'project-yaml' in formats:
|
|
@@ -7,6 +7,7 @@ Maintains backward compatibility with all existing --format values.
|
|
|
7
7
|
import os
|
|
8
8
|
import shutil
|
|
9
9
|
import sys
|
|
10
|
+
import time
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import Optional, List, Dict, Any
|
|
12
13
|
|
|
@@ -280,9 +281,12 @@ def _export_registry_formats(args, result, output_dir: Path, formats: List[str])
|
|
|
280
281
|
kwargs = _get_format_kwargs(fmt, args)
|
|
281
282
|
|
|
282
283
|
try:
|
|
284
|
+
t0 = time.monotonic()
|
|
283
285
|
exporter.export(result, str(filepath), **kwargs)
|
|
286
|
+
elapsed = time.monotonic() - t0
|
|
287
|
+
_inject_generation_time(filepath, elapsed)
|
|
284
288
|
if args.verbose:
|
|
285
|
-
print(f" - {label}: {filepath}")
|
|
289
|
+
print(f" - {label}: {filepath} ({elapsed:.2f}s)")
|
|
286
290
|
except Exception as e:
|
|
287
291
|
if args.verbose:
|
|
288
292
|
print(f" - {label} export failed: {e}", file=sys.stderr)
|
|
@@ -306,6 +310,53 @@ def _export_chunked(
|
|
|
306
310
|
_chunked_impl(args, result, output_dir, source_path, formats, requested_formats)
|
|
307
311
|
|
|
308
312
|
|
|
313
|
+
def _inject_generation_time(filepath: Path, elapsed: float) -> None:
|
|
314
|
+
"""Inject generation time comment into the second line of an exported file."""
|
|
315
|
+
try:
|
|
316
|
+
path = Path(filepath)
|
|
317
|
+
if not path.exists():
|
|
318
|
+
return
|
|
319
|
+
suffix = path.suffix.lower()
|
|
320
|
+
name = path.name.lower()
|
|
321
|
+
|
|
322
|
+
# Only inject into text-based files
|
|
323
|
+
if suffix not in ('.yaml', '.yml', '.md', '.txt', '.mmd', '.html', '.json', '.export'):
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
content = path.read_text(encoding='utf-8')
|
|
327
|
+
if not content:
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
tag = f"generated in {elapsed:.2f}s"
|
|
331
|
+
|
|
332
|
+
if suffix in ('.yaml', '.yml', '.mmd', '.export', '.txt') or name.endswith('.toon.yaml'):
|
|
333
|
+
# YAML/Mermaid/text: insert '# generated in X.XXs' after first line
|
|
334
|
+
lines = content.split('\n', 1)
|
|
335
|
+
if len(lines) == 2:
|
|
336
|
+
content = f"{lines[0]}\n# {tag}\n{lines[1]}"
|
|
337
|
+
else:
|
|
338
|
+
content = f"{lines[0]}\n# {tag}\n"
|
|
339
|
+
elif suffix == '.md':
|
|
340
|
+
# Markdown: insert HTML comment after first line
|
|
341
|
+
lines = content.split('\n', 1)
|
|
342
|
+
if len(lines) == 2:
|
|
343
|
+
content = f"{lines[0]}\n<!-- {tag} -->\n{lines[1]}"
|
|
344
|
+
else:
|
|
345
|
+
content = f"{lines[0]}\n<!-- {tag} -->\n"
|
|
346
|
+
elif suffix == '.html':
|
|
347
|
+
# HTML: insert comment after <!DOCTYPE or <html>
|
|
348
|
+
content = content.replace('\n', f'\n<!-- {tag} -->\n', 1)
|
|
349
|
+
elif suffix == '.json':
|
|
350
|
+
# JSON doesn't support comments — skip
|
|
351
|
+
return
|
|
352
|
+
else:
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
path.write_text(content, encoding='utf-8')
|
|
356
|
+
except Exception:
|
|
357
|
+
pass # Never fail the export pipeline for a comment
|
|
358
|
+
|
|
359
|
+
|
|
309
360
|
# Backward-compatible aliases
|
|
310
361
|
_export_single_project = _export_single
|
|
311
362
|
_export_chunked_results = _export_chunked
|
|
@@ -4,6 +4,7 @@ This module contains all the individual export handler functions
|
|
|
4
4
|
that were extracted from orchestrator.py to reduce its size.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import time
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any, Dict, List
|
|
9
10
|
|
|
@@ -20,10 +21,14 @@ from .orchestrator_constants import FORMAT_LABELS
|
|
|
20
21
|
|
|
21
22
|
def _export_mermaid(args, result, output_dir: Path):
|
|
22
23
|
"""Export mermaid diagrams."""
|
|
24
|
+
from .orchestrator import _inject_generation_time
|
|
25
|
+
|
|
23
26
|
exporter = MermaidExporter()
|
|
24
27
|
include_examples = getattr(args, 'flow_include_examples', False)
|
|
25
28
|
|
|
26
29
|
# Core diagrams
|
|
30
|
+
mmd_files = ['flow.mmd', 'calls.mmd', 'compact_flow.mmd']
|
|
31
|
+
t0 = time.monotonic()
|
|
27
32
|
exporter.export_flow_compact(result, str(output_dir / 'flow.mmd'), include_examples)
|
|
28
33
|
exporter.export_call_graph(result, str(output_dir / 'calls.mmd'))
|
|
29
34
|
exporter.export_compact(result, str(output_dir / 'compact_flow.mmd'))
|
|
@@ -31,21 +36,27 @@ def _export_mermaid(args, result, output_dir: Path):
|
|
|
31
36
|
# Optional detailed diagrams
|
|
32
37
|
if getattr(args, 'flow_detail', False):
|
|
33
38
|
exporter.export_flow_detailed(result, str(output_dir / 'flow_detailed.mmd'), include_examples)
|
|
39
|
+
mmd_files.append('flow_detailed.mmd')
|
|
34
40
|
if getattr(args, 'flow_full', False):
|
|
35
41
|
exporter.export_flow_full(result, str(output_dir / 'flow_full.mmd'), include_examples)
|
|
42
|
+
mmd_files.append('flow_full.mmd')
|
|
43
|
+
elapsed_mmd = time.monotonic() - t0
|
|
44
|
+
|
|
45
|
+
# Inject timing into each .mmd file
|
|
46
|
+
for mf in mmd_files:
|
|
47
|
+
_inject_generation_time(output_dir / mf, elapsed_mmd)
|
|
36
48
|
|
|
37
49
|
# Also export calls.yaml/toon
|
|
38
50
|
yaml_exporter = YAMLExporter()
|
|
51
|
+
t0 = time.monotonic()
|
|
39
52
|
yaml_exporter.export_calls(result, str(output_dir / 'calls.yaml'))
|
|
40
53
|
yaml_exporter.export_calls_toon(result, str(output_dir / 'calls.toon.yaml'))
|
|
54
|
+
elapsed_calls = time.monotonic() - t0
|
|
55
|
+
_inject_generation_time(output_dir / 'calls.toon.yaml', elapsed_calls)
|
|
41
56
|
|
|
42
57
|
if args.verbose:
|
|
43
|
-
files =
|
|
44
|
-
|
|
45
|
-
files.append('flow_detailed.mmd')
|
|
46
|
-
if getattr(args, 'flow_full', False):
|
|
47
|
-
files.append('flow_full.mmd')
|
|
48
|
-
print(f" - Mermaid: {output_dir}/*.mmd ({', '.join(files)})")
|
|
58
|
+
files = mmd_files + ['calls.yaml']
|
|
59
|
+
print(f" - Mermaid: {output_dir}/*.mmd ({', '.join(files)}) ({elapsed_mmd:.2f}s)")
|
|
49
60
|
|
|
50
61
|
# PNG generation
|
|
51
62
|
_export_mermaid_pngs(args, output_dir)
|
|
@@ -99,28 +110,38 @@ def _export_data_structures(args, result, output_dir: Path):
|
|
|
99
110
|
def _export_project_toon(args, result, output_dir: Path):
|
|
100
111
|
"""Export project.toon.yaml from project.yaml data."""
|
|
101
112
|
from ..exporters.project_yaml_exporter import ProjectYAMLExporter
|
|
113
|
+
from .orchestrator import _inject_generation_time
|
|
102
114
|
|
|
103
115
|
project_yaml_exporter = ProjectYAMLExporter()
|
|
104
116
|
prev_evolution = load_previous_evolution(output_dir / 'project.yaml')
|
|
105
117
|
data = project_yaml_exporter._build_project_yaml(result, prev_evolution)
|
|
106
118
|
|
|
107
119
|
generator = ToonViewGenerator()
|
|
108
|
-
|
|
120
|
+
filepath = output_dir / 'project.toon.yaml'
|
|
121
|
+
t0 = time.monotonic()
|
|
122
|
+
generator.generate(data, str(filepath))
|
|
123
|
+
elapsed = time.monotonic() - t0
|
|
124
|
+
_inject_generation_time(filepath, elapsed)
|
|
109
125
|
|
|
110
126
|
if args.verbose:
|
|
111
|
-
print(f" - PROJECT-TOON (project overview): {
|
|
127
|
+
print(f" - PROJECT-TOON (project overview): {filepath} ({elapsed:.2f}s)")
|
|
112
128
|
|
|
113
129
|
|
|
114
130
|
def _export_readme(args, result, output_dir: Path):
|
|
115
131
|
"""Export README.md."""
|
|
116
132
|
if getattr(args, 'no_readme', False):
|
|
117
133
|
return
|
|
134
|
+
from .orchestrator import _inject_generation_time
|
|
118
135
|
exporter_cls = get_exporter('readme')
|
|
119
136
|
if exporter_cls:
|
|
120
137
|
exporter = exporter_cls()
|
|
121
|
-
|
|
138
|
+
filepath = output_dir / 'README.md'
|
|
139
|
+
t0 = time.monotonic()
|
|
140
|
+
exporter.export(result, str(filepath))
|
|
141
|
+
elapsed = time.monotonic() - t0
|
|
142
|
+
_inject_generation_time(filepath, elapsed)
|
|
122
143
|
if args.verbose:
|
|
123
|
-
print(f" - README (documentation): {
|
|
144
|
+
print(f" - README (documentation): {filepath} ({elapsed:.2f}s)")
|
|
124
145
|
|
|
125
146
|
|
|
126
147
|
def _export_index_html(args, output_dir: Path):
|
|
@@ -128,10 +149,14 @@ def _export_index_html(args, output_dir: Path):
|
|
|
128
149
|
if 'all' not in getattr(args, 'format', ''):
|
|
129
150
|
return
|
|
130
151
|
try:
|
|
152
|
+
from .orchestrator import _inject_generation_time
|
|
131
153
|
generator = IndexHTMLGenerator(output_dir)
|
|
154
|
+
t0 = time.monotonic()
|
|
132
155
|
index_path = generator.generate()
|
|
156
|
+
elapsed = time.monotonic() - t0
|
|
157
|
+
_inject_generation_time(index_path, elapsed)
|
|
133
158
|
if args.verbose:
|
|
134
|
-
print(f" - INDEX (file browser): {index_path}")
|
|
159
|
+
print(f" - INDEX (file browser): {index_path} ({elapsed:.2f}s)")
|
|
135
160
|
except Exception as e:
|
|
136
161
|
if args.verbose:
|
|
137
162
|
print(f" - INDEX generation failed: {e}", file=__import__('sys').stderr)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Prompt generation — prompt.txt for LLM consumption (regular and chunked)."""
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
+
import time
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import List, Optional, Tuple
|
|
6
7
|
|
|
@@ -30,9 +31,13 @@ def _export_prompt_txt(args, output_dir: Path, formats: list[str], source_path:
|
|
|
30
31
|
lines.extend(_build_prompt_footer(chunked=False, file_analysis=file_analysis))
|
|
31
32
|
|
|
32
33
|
prompt_path = output_dir / 'prompt.txt'
|
|
34
|
+
t0 = time.monotonic()
|
|
33
35
|
prompt_path.write_text("\n".join(lines) + "\n", encoding='utf-8')
|
|
36
|
+
elapsed = time.monotonic() - t0
|
|
37
|
+
from .orchestrator import _inject_generation_time
|
|
38
|
+
_inject_generation_time(prompt_path, elapsed)
|
|
34
39
|
if args.verbose:
|
|
35
|
-
print(f" - PROMPT: {prompt_path}")
|
|
40
|
+
print(f" - PROMPT: {prompt_path} ({elapsed:.2f}s)")
|
|
36
41
|
|
|
37
42
|
|
|
38
43
|
def _export_chunked_prompt_txt(args, output_dir: Path, formats: list[str], source_path: Optional[Path] = None, subprojects: list = None) -> None:
|
|
@@ -48,7 +48,7 @@ class ProjectYAMLExporter(BaseExporter):
|
|
|
48
48
|
self, result: AnalysisResult, prev_evolution: List[Dict]
|
|
49
49
|
) -> Dict[str, Any]:
|
|
50
50
|
"""Build complete project.yaml structure."""
|
|
51
|
-
line_counts = _scan_line_counts(result.project_path)
|
|
51
|
+
line_counts = _scan_line_counts(result.project_path, result=result)
|
|
52
52
|
# Filter out venv/site-packages/etc — only count lines of non-excluded files
|
|
53
53
|
filtered_lines = {
|
|
54
54
|
k: v for k, v in line_counts.items()
|
|
@@ -83,15 +83,12 @@ def _hotspot_description(fi: FunctionInfo, fan_out: int) -> str:
|
|
|
83
83
|
return f"calls {fan_out} functions"
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"""Scan project directory for all source file line counts.
|
|
86
|
+
def _scan_line_counts(project_path, result=None) -> Dict[str, int]:
|
|
87
|
+
"""Get line counts for project files.
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
Fast path: derive from AnalysisResult modules (already parsed, no extra I/O).
|
|
90
|
+
Slow fallback: single os.walk pass reading files from disk.
|
|
92
91
|
"""
|
|
93
|
-
from ...core.config import ALL_EXTENSIONS
|
|
94
|
-
|
|
95
92
|
line_counts: Dict[str, int] = {}
|
|
96
93
|
if not project_path:
|
|
97
94
|
return line_counts
|
|
@@ -99,8 +96,35 @@ def _scan_line_counts(project_path) -> Dict[str, int]:
|
|
|
99
96
|
if not pp.is_dir():
|
|
100
97
|
return line_counts
|
|
101
98
|
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
# Fast path: use already-analyzed file data when available
|
|
100
|
+
if result is not None:
|
|
101
|
+
for mname, mi in getattr(result, 'modules', {}).items():
|
|
102
|
+
fpath = mi.file
|
|
103
|
+
if not fpath:
|
|
104
|
+
continue
|
|
105
|
+
try:
|
|
106
|
+
lc = len(Path(fpath).read_text(encoding="utf-8", errors="ignore").splitlines())
|
|
107
|
+
rel = str(Path(fpath).relative_to(pp))
|
|
108
|
+
line_counts[str(fpath)] = lc
|
|
109
|
+
line_counts[rel] = lc
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
return line_counts
|
|
113
|
+
|
|
114
|
+
# Slow fallback: single walk instead of 73 rglob calls
|
|
115
|
+
from ...core.config import ALL_EXTENSIONS
|
|
116
|
+
ext_set = set(ALL_EXTENSIONS)
|
|
117
|
+
for root, dirs, files in Path(project_path).walk() if hasattr(Path, 'walk') else _walk_compat(pp):
|
|
118
|
+
# Prune excluded directories
|
|
119
|
+
dirs[:] = [d for d in dirs if d not in {
|
|
120
|
+
'venv', '.venv', 'node_modules', '__pycache__', '.git',
|
|
121
|
+
'dist', 'build', '.tox', '.mypy_cache', 'egg-info',
|
|
122
|
+
}]
|
|
123
|
+
for fname in files:
|
|
124
|
+
ext = Path(fname).suffix
|
|
125
|
+
if ext not in ext_set:
|
|
126
|
+
continue
|
|
127
|
+
src_file = root / fname
|
|
104
128
|
try:
|
|
105
129
|
lc = len(src_file.read_text(encoding="utf-8", errors="ignore").splitlines())
|
|
106
130
|
rel = str(src_file.relative_to(pp))
|
|
@@ -109,3 +133,10 @@ def _scan_line_counts(project_path) -> Dict[str, int]:
|
|
|
109
133
|
except Exception:
|
|
110
134
|
pass
|
|
111
135
|
return line_counts
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _walk_compat(path):
|
|
139
|
+
"""os.walk compatibility wrapper for Path (Python < 3.12)."""
|
|
140
|
+
import os
|
|
141
|
+
for root, dirs, files in os.walk(path):
|
|
142
|
+
yield Path(root), dirs, files
|
|
@@ -35,7 +35,7 @@ class MetricsComputer:
|
|
|
35
35
|
"""Compute all metrics and return context dict."""
|
|
36
36
|
self.result = result
|
|
37
37
|
self.project_path = result.project_path
|
|
38
|
-
self.line_counts = _scan_line_counts(self.project_path)
|
|
38
|
+
self.line_counts = _scan_line_counts(self.project_path, result=result)
|
|
39
39
|
|
|
40
40
|
# Initialize specialized computers
|
|
41
41
|
self._core = CoreMetricsComputer(self.line_counts, self.project_path)
|
|
@@ -80,55 +80,46 @@ class CoreMetricsComputer:
|
|
|
80
80
|
"fan_in": 0,
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _build_suffix_index(result: AnalysisResult) -> Dict[str, List[Tuple[str, FunctionInfo]]]:
|
|
85
|
+
"""Build reverse lookup: simple_name -> [(qualified_name, FunctionInfo)].
|
|
86
|
+
|
|
87
|
+
Replaces O(F) scans per call with O(1) dict lookups.
|
|
88
|
+
"""
|
|
89
|
+
idx: Dict[str, List[Tuple[str, FunctionInfo]]] = defaultdict(list)
|
|
90
|
+
for qn, fi in result.functions.items():
|
|
91
|
+
simple = qn.rsplit(".", 1)[-1]
|
|
92
|
+
idx[simple].append((qn, fi))
|
|
93
|
+
return idx
|
|
94
|
+
|
|
83
95
|
@staticmethod
|
|
84
96
|
def _compute_fan_in(files: Dict, result: AnalysisResult) -> None:
|
|
85
97
|
"""Compute fan-in per file (how many other files call into this file)."""
|
|
86
98
|
importers: Dict[str, set] = defaultdict(set)
|
|
99
|
+
suffix_idx = CoreMetricsComputer._build_suffix_index(result)
|
|
87
100
|
|
|
88
101
|
for fname, fi in result.functions.items():
|
|
89
|
-
|
|
102
|
+
src_file = fi.file
|
|
103
|
+
# Forward: who calls me? (called_by)
|
|
104
|
+
for callee in fi.called_by:
|
|
105
|
+
callee_info = result.functions.get(callee)
|
|
106
|
+
if callee_info and callee_info.file != src_file:
|
|
107
|
+
importers[src_file].add(callee_info.file)
|
|
108
|
+
# Reverse: who do I call? → target file gets fan-in
|
|
109
|
+
for callee in fi.calls:
|
|
110
|
+
callee_info = result.functions.get(callee)
|
|
111
|
+
if callee_info and callee_info.file != src_file:
|
|
112
|
+
importers[callee_info.file].add(src_file)
|
|
113
|
+
elif not callee_info:
|
|
114
|
+
# O(1) suffix lookup instead of O(F) scan
|
|
115
|
+
for qn, ci in suffix_idx.get(callee, []):
|
|
116
|
+
if ci.file != src_file:
|
|
117
|
+
importers[ci.file].add(src_file)
|
|
118
|
+
break
|
|
90
119
|
|
|
91
120
|
for fpath in files:
|
|
92
121
|
files[fpath]["fan_in"] = len(importers.get(fpath, set()))
|
|
93
122
|
|
|
94
|
-
@staticmethod
|
|
95
|
-
def _process_function_calls(fi: FunctionInfo, result: AnalysisResult, importers: Dict[str, set]) -> None:
|
|
96
|
-
"""Process calls for a single function to compute fan-in."""
|
|
97
|
-
src_file = fi.file
|
|
98
|
-
|
|
99
|
-
# Forward: who calls me? (called_by)
|
|
100
|
-
CoreMetricsComputer._process_called_by(fi, result, src_file, importers)
|
|
101
|
-
|
|
102
|
-
# Reverse: who do I call? → target file gets fan-in
|
|
103
|
-
CoreMetricsComputer._process_callee_calls(fi, result, src_file, importers)
|
|
104
|
-
|
|
105
|
-
@staticmethod
|
|
106
|
-
def _process_called_by(fi: FunctionInfo, result: AnalysisResult, src_file: str, importers: Dict[str, set]) -> None:
|
|
107
|
-
"""Process called_by relationships."""
|
|
108
|
-
for callee in fi.called_by:
|
|
109
|
-
callee_info = result.functions.get(callee)
|
|
110
|
-
if callee_info and callee_info.file != src_file:
|
|
111
|
-
importers[src_file].add(callee_info.file)
|
|
112
|
-
|
|
113
|
-
@staticmethod
|
|
114
|
-
def _process_callee_calls(fi: FunctionInfo, result: AnalysisResult, src_file: str, importers: Dict[str, set]) -> None:
|
|
115
|
-
"""Process callee relationships."""
|
|
116
|
-
for callee in fi.calls:
|
|
117
|
-
callee_info = result.functions.get(callee)
|
|
118
|
-
if callee_info and callee_info.file != src_file:
|
|
119
|
-
importers[callee_info.file].add(src_file)
|
|
120
|
-
else:
|
|
121
|
-
# Suffix match for unqualified names
|
|
122
|
-
CoreMetricsComputer._handle_suffix_match(callee, result, src_file, importers)
|
|
123
|
-
|
|
124
|
-
@staticmethod
|
|
125
|
-
def _handle_suffix_match(callee: str, result: AnalysisResult, src_file: str, importers: Dict[str, set]) -> None:
|
|
126
|
-
"""Handle suffix matching for unqualified names."""
|
|
127
|
-
for qn, ci in result.functions.items():
|
|
128
|
-
if qn.endswith(f".{callee}") and ci.file != src_file:
|
|
129
|
-
importers[ci.file].add(src_file)
|
|
130
|
-
break
|
|
131
|
-
|
|
132
123
|
def compute_package_metrics(
|
|
133
124
|
self, files: Dict[str, Dict], result: AnalysisResult
|
|
134
125
|
) -> Dict[str, Dict[str, Any]]:
|
|
@@ -248,6 +239,12 @@ class CoreMetricsComputer:
|
|
|
248
239
|
"""Build coupling matrix from cross-module calls."""
|
|
249
240
|
matrix: Dict[Tuple[str, str], int] = defaultdict(int)
|
|
250
241
|
|
|
242
|
+
# Build reverse index: simple_name -> [(qualified, module)] for O(1) lookup
|
|
243
|
+
suffix_idx: Dict[str, List[Tuple[str, str]]] = defaultdict(list)
|
|
244
|
+
for qn, mod in func_to_module.items():
|
|
245
|
+
simple = qn.rsplit(".", 1)[-1]
|
|
246
|
+
suffix_idx[simple].append((qn, mod))
|
|
247
|
+
|
|
251
248
|
for qname, fi in result.functions.items():
|
|
252
249
|
if _is_excluded(fi.file):
|
|
253
250
|
continue
|
|
@@ -255,7 +252,9 @@ class CoreMetricsComputer:
|
|
|
255
252
|
src_pkg = _package_of_module(src_mod)
|
|
256
253
|
|
|
257
254
|
for callee in fi.calls:
|
|
258
|
-
callee_mod = self._resolve_callee_module(
|
|
255
|
+
callee_mod = self._resolve_callee_module(
|
|
256
|
+
callee, func_to_module, src_pkg, suffix_idx
|
|
257
|
+
)
|
|
259
258
|
if callee_mod and callee_mod != src_mod:
|
|
260
259
|
dst_pkg = _package_of_module(callee_mod)
|
|
261
260
|
if dst_pkg and dst_pkg != src_pkg:
|
|
@@ -263,31 +262,24 @@ class CoreMetricsComputer:
|
|
|
263
262
|
|
|
264
263
|
return matrix
|
|
265
264
|
|
|
266
|
-
def _resolve_callee_module(
|
|
267
|
-
|
|
265
|
+
def _resolve_callee_module(
|
|
266
|
+
self, callee: str, func_to_module: Dict[str, str],
|
|
267
|
+
src_pkg: str, suffix_idx: Dict[str, List[Tuple[str, str]]],
|
|
268
|
+
) -> Optional[str]:
|
|
269
|
+
"""Resolve callee to a known function module (O(1) via suffix index)."""
|
|
268
270
|
callee_mod = func_to_module.get(callee)
|
|
269
271
|
if callee_mod:
|
|
270
272
|
return callee_mod
|
|
271
273
|
|
|
272
|
-
|
|
273
|
-
candidates = [
|
|
274
|
-
(qn, mod) for qn, mod in func_to_module.items()
|
|
275
|
-
if qn.endswith(f".{callee}")
|
|
276
|
-
]
|
|
277
|
-
|
|
274
|
+
candidates = suffix_idx.get(callee, [])
|
|
278
275
|
if len(candidates) == 1:
|
|
279
276
|
return candidates[0][1]
|
|
280
277
|
elif candidates:
|
|
281
278
|
# Prefer callee in same package as caller
|
|
282
|
-
|
|
283
|
-
(
|
|
284
|
-
|
|
285
|
-
]
|
|
286
|
-
if same_pkg:
|
|
287
|
-
return same_pkg[0][1]
|
|
288
|
-
else:
|
|
289
|
-
# Pick first cross-package candidate
|
|
290
|
-
return candidates[0][1]
|
|
279
|
+
for qn, mod in candidates:
|
|
280
|
+
if _package_of_module(mod) == src_pkg:
|
|
281
|
+
return mod
|
|
282
|
+
return candidates[0][1]
|
|
291
283
|
|
|
292
284
|
return None
|
|
293
285
|
|