code2llm 0.5.154__tar.gz → 0.5.156__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.154/code2llm.egg-info → code2llm-0.5.156}/PKG-INFO +6 -6
- {code2llm-0.5.154 → code2llm-0.5.156}/README.md +5 -5
- code2llm-0.5.156/VERSION +1 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/__init__.py +1 -1
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/pipeline_detector.py +3 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_analysis.py +22 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/export_pipeline.py +5 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/php.py +4 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/ts_extractors.py +2 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/large_repo.py +1 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/streaming/cache.py +2 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/article_view.py +8 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/context_exporter.py +4 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/context_view.py +6 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/html_dashboard.py +2 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/planfile_tickets.py +7 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/toon/helpers.py +5 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/toon_view.py +7 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/llm_flow/analysis.py +5 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/llm_flow/cli.py +2 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/llm_flow/generator.py +2 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/llm_task.py +5 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/nlp/__init__.py +1 -1
- {code2llm-0.5.154 → code2llm-0.5.156/code2llm.egg-info}/PKG-INFO +6 -6
- {code2llm-0.5.154 → code2llm-0.5.156}/pyproject.toml +1 -1
- code2llm-0.5.154/VERSION +0 -1
- {code2llm-0.5.154 → code2llm-0.5.156}/LICENSE +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/MANIFEST.in +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/__main__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/pipeline_classifier.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/pipeline_resolver.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/type_inference.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/utils/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/analysis/utils/ast_helpers.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/api.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_commands.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_exports/code2logic.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_exports/formats.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_exports/orchestrator.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_exports/orchestrator_chunked.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_exports/orchestrator_constants.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_exports/orchestrator_handlers.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_exports/prompt.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/cli_parser.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/analyzer.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/ast_registry.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/config.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/file_analyzer.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/file_cache.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/file_filter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/gitignore.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/incremental.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/_c_parser.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/_calls.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/_complexity.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/base.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/cpp.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/csharp.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/generic.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/go_lang.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/java.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/ruby.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/rust.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/ts_parser.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/lang/typescript.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/models.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/persistent_cache.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/refactoring.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/repo_files.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/source_classifier.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/dashboard_data.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/dashboard_renderer.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/evolution/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/evolution/computation.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/evolution/constants.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/evolution/exclusion.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/evolution/render.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/evolution/yaml_export.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/flow_constants.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/index_generator/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/index_generator/renderer.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/index_generator/scanner.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/index_generator.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/map/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/map/alerts.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/map/details.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/map/header.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/map/module_list.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/map/utils.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/map/yaml_export.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/mermaid/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/mermaid/calls.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/mermaid/classic.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/mermaid/compact.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/mermaid/flow_compact.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/mermaid/flow_detailed.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/mermaid/flow_full.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/mermaid/utils.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/project_yaml/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/project_yaml/constants.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/project_yaml/core.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/project_yaml/evolution.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/project_yaml/health.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/project_yaml/hotspots.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/project_yaml/modules.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/project_yaml_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/readme/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/readme/content.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/readme/files.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/readme/insights.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/readme/sections.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/toon/metrics_core.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/toon/metrics_duplicates.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/toon/metrics_health.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/_utils.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/llm_flow/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/llm_flow/nodes.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/llm_flow/parsing.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/llm_flow/utils.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/mermaid/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/mermaid/fix.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/mermaid/png.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/mermaid/validation.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/generators/mermaid.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/parsers/toon_parser.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm.egg-info/SOURCES.txt +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm.egg-info/requires.txt +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/code2llm.egg-info/top_level.txt +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/setup.cfg +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/setup.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_cache_invalidation_e2e.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_calls_toon_export.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_declarative_collection.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_export_cache_flags.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_file_analyzer_tagging.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_orchestrator_cache_mtime.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_persistent_cache.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_planfile_tickets_exporter.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_project_toon_export.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_prompt_txt.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.154 → code2llm-0.5.156}/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.156
|
|
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
|
|
@@ -66,13 +66,13 @@ Dynamic: requires-python
|
|
|
66
66
|
|
|
67
67
|
## AI Cost Tracking
|
|
68
68
|
|
|
69
|
-
     
|
|
70
|
+
  
|
|
71
71
|
|
|
72
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
73
|
-
- 👤 **Human dev:** ~$
|
|
72
|
+
- 🤖 **LLM usage:** $7.5000 (211 commits)
|
|
73
|
+
- 👤 **Human dev:** ~$7972 (79.7h @ $100/h, 30min dedup)
|
|
74
74
|
|
|
75
|
-
Generated on 2026-05-
|
|
75
|
+
Generated on 2026-05-25 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
76
76
|
|
|
77
77
|
---
|
|
78
78
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## AI Cost Tracking
|
|
4
4
|
|
|
5
|
-
     
|
|
6
|
+
  
|
|
7
7
|
|
|
8
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
9
|
-
- 👤 **Human dev:** ~$
|
|
8
|
+
- 🤖 **LLM usage:** $7.5000 (211 commits)
|
|
9
|
+
- 👤 **Human dev:** ~$7972 (79.7h @ $100/h, 30min dedup)
|
|
10
10
|
|
|
11
|
-
Generated on 2026-05-
|
|
11
|
+
Generated on 2026-05-25 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
code2llm-0.5.156/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.5.156
|
|
@@ -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.156"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -63,9 +63,11 @@ class Pipeline:
|
|
|
63
63
|
|
|
64
64
|
@property
|
|
65
65
|
def purity_ratio(self) -> float:
|
|
66
|
+
"""Fraction of stages that are free of side effects."""
|
|
66
67
|
return self.pure_count / self.total_stages if self.total_stages else 0.0
|
|
67
68
|
|
|
68
69
|
def to_dict(self) -> Dict[str, Any]:
|
|
70
|
+
"""Serialise the Pipeline to a plain dict."""
|
|
69
71
|
return {
|
|
70
72
|
"name": self.name,
|
|
71
73
|
"domain": self.domain,
|
|
@@ -125,6 +127,7 @@ class PipelineDetector:
|
|
|
125
127
|
type_engine: Optional[TypeInferenceEngine] = None,
|
|
126
128
|
side_effect_detector: Optional[SideEffectDetector] = None,
|
|
127
129
|
):
|
|
130
|
+
"""Initialise with optional shared type engine and side-effect detector."""
|
|
128
131
|
self._type_engine = type_engine or TypeInferenceEngine()
|
|
129
132
|
self._se_detector = side_effect_detector or SideEffectDetector()
|
|
130
133
|
self._resolver = PipelineResolver()
|
|
@@ -283,6 +283,15 @@ def _merge_chunked_results(all_results, source_path: Path):
|
|
|
283
283
|
|
|
284
284
|
merged = AnalysisResult(project_path=str(source_path))
|
|
285
285
|
|
|
286
|
+
# Track seen (file, simple_name) pairs to deduplicate overlapping chunk scopes.
|
|
287
|
+
# When the splitter assigns the same directory to multiple chunks (e.g. root and
|
|
288
|
+
# batch_1 both point at the project root), the same class/function would otherwise
|
|
289
|
+
# appear multiple times in the merged result and trigger false-positive duplicate
|
|
290
|
+
# detection in the TOON exporter.
|
|
291
|
+
_seen_funcs: set = set()
|
|
292
|
+
_seen_classes: set = set()
|
|
293
|
+
_seen_modules: set = set()
|
|
294
|
+
|
|
286
295
|
for name, result, output_dir in all_results:
|
|
287
296
|
if not result:
|
|
288
297
|
continue
|
|
@@ -290,14 +299,26 @@ def _merge_chunked_results(all_results, source_path: Path):
|
|
|
290
299
|
prefix = f"{name}."
|
|
291
300
|
|
|
292
301
|
for func_name, func_info in result.functions.items():
|
|
302
|
+
dedup_key = (func_info.file, func_info.name)
|
|
303
|
+
if dedup_key in _seen_funcs:
|
|
304
|
+
continue
|
|
305
|
+
_seen_funcs.add(dedup_key)
|
|
293
306
|
new_name = f"{prefix}{func_name}" if "." not in func_name else func_name
|
|
294
307
|
merged.functions[new_name] = func_info
|
|
295
308
|
|
|
296
309
|
for class_name, class_info in result.classes.items():
|
|
310
|
+
dedup_key = (class_info.file, class_info.name)
|
|
311
|
+
if dedup_key in _seen_classes:
|
|
312
|
+
continue
|
|
313
|
+
_seen_classes.add(dedup_key)
|
|
297
314
|
new_name = f"{prefix}{class_name}" if "." not in class_name else class_name
|
|
298
315
|
merged.classes[new_name] = class_info
|
|
299
316
|
|
|
300
317
|
for mod_name, mod_info in result.modules.items():
|
|
318
|
+
dedup_key = (mod_info.file, mod_info.name)
|
|
319
|
+
if dedup_key in _seen_modules:
|
|
320
|
+
continue
|
|
321
|
+
_seen_modules.add(dedup_key)
|
|
301
322
|
new_name = f"{prefix}{mod_name}" if "." not in mod_name else mod_name
|
|
302
323
|
merged.modules[new_name] = mod_info
|
|
303
324
|
|
|
@@ -336,6 +357,7 @@ def _run_streaming_analysis(args, config, source_path: Path):
|
|
|
336
357
|
if args.verbose:
|
|
337
358
|
|
|
338
359
|
def on_progress(update):
|
|
360
|
+
"""Print a progress percentage line in-place."""
|
|
339
361
|
pct = update.get("percentage", 0)
|
|
340
362
|
print(f"\r[{pct:.0f}%] {update.get('message', '')}", end="", flush=True)
|
|
341
363
|
|
|
@@ -19,23 +19,28 @@ class SharedExportContext:
|
|
|
19
19
|
__slots__ = ("_result", "_computed")
|
|
20
20
|
|
|
21
21
|
def __init__(self, result: AnalysisResult):
|
|
22
|
+
"""Wrap an AnalysisResult for lazy computed-metric access."""
|
|
22
23
|
self._result = result
|
|
23
24
|
self._computed: Dict[str, Any] = {}
|
|
24
25
|
|
|
25
26
|
@property
|
|
26
27
|
def result(self) -> AnalysisResult:
|
|
28
|
+
"""Return the underlying AnalysisResult."""
|
|
27
29
|
return self._result
|
|
28
30
|
|
|
29
31
|
@property
|
|
30
32
|
def functions(self) -> Dict:
|
|
33
|
+
"""Return the functions dict from the underlying result."""
|
|
31
34
|
return self._result.functions
|
|
32
35
|
|
|
33
36
|
@property
|
|
34
37
|
def classes(self) -> Dict:
|
|
38
|
+
"""Return the classes dict from the underlying result."""
|
|
35
39
|
return self._result.classes
|
|
36
40
|
|
|
37
41
|
@property
|
|
38
42
|
def modules(self) -> Dict:
|
|
43
|
+
"""Return the modules dict from the underlying result."""
|
|
39
44
|
return self._result.modules
|
|
40
45
|
|
|
41
46
|
@property
|
|
@@ -11,6 +11,7 @@ from code2llm.core.lang.base import (
|
|
|
11
11
|
def _parse_php_metadata(
|
|
12
12
|
content: str, module_name: str, result: Dict
|
|
13
13
|
) -> Tuple[Optional[str], bool]:
|
|
14
|
+
"""Extract PHP namespace and import declarations from source content."""
|
|
14
15
|
lines = content.split("\n")
|
|
15
16
|
current_namespace = None
|
|
16
17
|
in_php = False
|
|
@@ -35,6 +36,7 @@ def _parse_php_metadata(
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
def _adjust_qualified_names(result: Dict, module_name: str, namespace: str) -> None:
|
|
39
|
+
"""Prefix all class/function qualified names with the PHP namespace."""
|
|
38
40
|
ns_prefix = f".{namespace}"
|
|
39
41
|
for key in ["classes", "functions"]:
|
|
40
42
|
new_items = {}
|
|
@@ -56,6 +58,7 @@ def _extract_php_traits(
|
|
|
56
58
|
result: Dict,
|
|
57
59
|
stats: Dict,
|
|
58
60
|
) -> None:
|
|
61
|
+
"""Detect PHP trait declarations and add them as ClassInfo entries."""
|
|
59
62
|
trait_pattern = re.compile(r"^\s*trait\s+(\w+)")
|
|
60
63
|
for line_no, line in enumerate(content.split("\n"), 1):
|
|
61
64
|
tm = trait_pattern.match(line.strip())
|
|
@@ -79,6 +82,7 @@ def _extract_php_traits(
|
|
|
79
82
|
def analyze_php(
|
|
80
83
|
content: str, file_path: str, module_name: str, ext: str, stats: Dict
|
|
81
84
|
) -> Dict:
|
|
85
|
+
"""Analyze a PHP source file and return declarations, complexity, and call info."""
|
|
82
86
|
patterns = {
|
|
83
87
|
"import": re.compile(
|
|
84
88
|
r'^(?:include|require|include_once|require_once)\s*["\']([^"\']+)["\']'
|
|
@@ -108,6 +108,7 @@ def _extract_functions_ts(
|
|
|
108
108
|
func_types = FUNCTION_TYPES.get(lang, ())
|
|
109
109
|
|
|
110
110
|
def visit(node, class_context: Optional[str] = None):
|
|
111
|
+
"""Recursively visit a tree-sitter node, extracting function declarations."""
|
|
111
112
|
if node.type in func_types:
|
|
112
113
|
name_node = _find_name_node(node)
|
|
113
114
|
if name_node:
|
|
@@ -156,6 +157,7 @@ def _extract_classes_ts(
|
|
|
156
157
|
class_types = CLASS_TYPES.get(lang, ())
|
|
157
158
|
|
|
158
159
|
def visit(node):
|
|
160
|
+
"""Recursively visit a tree-sitter node, extracting class declarations."""
|
|
159
161
|
if node.type in class_types:
|
|
160
162
|
name_node = _find_name_node(node)
|
|
161
163
|
if name_node:
|
|
@@ -11,6 +11,7 @@ class StreamingFileCache:
|
|
|
11
11
|
"""Memory-efficient cache with LRU eviction."""
|
|
12
12
|
|
|
13
13
|
def __init__(self, max_size: int = 100, cache_dir: str = ".code2llm_cache"):
|
|
14
|
+
"""Initialise the hybrid (memory + disk) LRU cache."""
|
|
14
15
|
self.max_size = max_size
|
|
15
16
|
self.cache_dir = Path(cache_dir)
|
|
16
17
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -18,6 +19,7 @@ class StreamingFileCache:
|
|
|
18
19
|
self._access_order: List[str] = []
|
|
19
20
|
|
|
20
21
|
def _get_cache_key(self, file_path: str, content: str) -> str:
|
|
22
|
+
"""Derive the cache key for a given file path and content."""
|
|
21
23
|
return make_cache_key(file_path, content)
|
|
22
24
|
|
|
23
25
|
def _evict_if_needed(self) -> None:
|
|
@@ -13,6 +13,7 @@ class ArticleViewGenerator(ViewGeneratorMixin):
|
|
|
13
13
|
"""Generate status.md — publishable project health article."""
|
|
14
14
|
|
|
15
15
|
def _render(self, data: Dict[str, Any]) -> List[str]:
|
|
16
|
+
"""Render the full article from project.yaml data, returning Markdown lines."""
|
|
16
17
|
proj = data.get("project", {})
|
|
17
18
|
health = data.get("health", {})
|
|
18
19
|
hotspots = data.get("hotspots", [])
|
|
@@ -31,6 +32,7 @@ class ArticleViewGenerator(ViewGeneratorMixin):
|
|
|
31
32
|
|
|
32
33
|
@staticmethod
|
|
33
34
|
def _render_frontmatter(proj: Dict) -> List[str]:
|
|
35
|
+
"""Render YAML frontmatter and H1 heading."""
|
|
34
36
|
stats = proj.get("stats", {})
|
|
35
37
|
return [
|
|
36
38
|
"---",
|
|
@@ -49,6 +51,7 @@ class ArticleViewGenerator(ViewGeneratorMixin):
|
|
|
49
51
|
|
|
50
52
|
@staticmethod
|
|
51
53
|
def _render_health_summary(proj: Dict, health: Dict) -> List[str]:
|
|
54
|
+
"""Render the health summary table with overall verdict."""
|
|
52
55
|
stats = proj.get("stats", {})
|
|
53
56
|
cc_avg = health.get("cc_avg", 0)
|
|
54
57
|
crit = health.get("critical_count", 0)
|
|
@@ -78,6 +81,7 @@ class ArticleViewGenerator(ViewGeneratorMixin):
|
|
|
78
81
|
|
|
79
82
|
@staticmethod
|
|
80
83
|
def _render_alerts(health: Dict) -> List[str]:
|
|
84
|
+
"""Render the alert list (health warnings/errors)."""
|
|
81
85
|
alerts = health.get("alerts", [])
|
|
82
86
|
if not alerts:
|
|
83
87
|
return []
|
|
@@ -95,6 +99,7 @@ class ArticleViewGenerator(ViewGeneratorMixin):
|
|
|
95
99
|
|
|
96
100
|
@staticmethod
|
|
97
101
|
def _render_hotspots(hotspots: List[Dict]) -> List[str]:
|
|
102
|
+
"""Render the top hotspot functions by fan-out."""
|
|
98
103
|
if not hotspots:
|
|
99
104
|
return []
|
|
100
105
|
lines = [
|
|
@@ -112,6 +117,7 @@ class ArticleViewGenerator(ViewGeneratorMixin):
|
|
|
112
117
|
|
|
113
118
|
@staticmethod
|
|
114
119
|
def _render_roadmap(refactoring: Dict) -> List[str]:
|
|
120
|
+
"""Render the refactoring roadmap priority list."""
|
|
115
121
|
priorities = refactoring.get("priorities", [])
|
|
116
122
|
if not priorities:
|
|
117
123
|
return []
|
|
@@ -128,6 +134,7 @@ class ArticleViewGenerator(ViewGeneratorMixin):
|
|
|
128
134
|
|
|
129
135
|
@staticmethod
|
|
130
136
|
def _render_evolution(evolution: List[Dict]) -> List[str]:
|
|
137
|
+
"""Render the Evolution table with CC trend delta."""
|
|
131
138
|
if len(evolution) <= 1:
|
|
132
139
|
return []
|
|
133
140
|
lines = [
|
|
@@ -161,6 +168,7 @@ class ArticleViewGenerator(ViewGeneratorMixin):
|
|
|
161
168
|
|
|
162
169
|
@staticmethod
|
|
163
170
|
def _render_footer() -> List[str]:
|
|
171
|
+
"""Render the article footer with generation timestamp."""
|
|
164
172
|
return [
|
|
165
173
|
"---",
|
|
166
174
|
f"*Generated by code2llm on {datetime.now().strftime('%Y-%m-%d %H:%M')}*",
|
|
@@ -64,6 +64,7 @@ class ContextExporter(BaseExporter):
|
|
|
64
64
|
return path
|
|
65
65
|
|
|
66
66
|
def _get_overview(self, result: AnalysisResult) -> List[str]:
|
|
67
|
+
"""Build the Overview section listing language, file counts, and top classes."""
|
|
67
68
|
lang_info = self._detect_languages(result)
|
|
68
69
|
primary = lang_info[0][0] if lang_info else "unknown"
|
|
69
70
|
lang_summary = ", ".join(f"{l}: {c}" for l, c in lang_info[:5])
|
|
@@ -99,6 +100,7 @@ class ContextExporter(BaseExporter):
|
|
|
99
100
|
return sorted(lang_counts.items(), key=lambda x: -x[1])
|
|
100
101
|
|
|
101
102
|
def _get_architecture_by_module(self, result: AnalysisResult) -> List[str]:
|
|
103
|
+
"""Build the Architecture section grouped by module with stats."""
|
|
102
104
|
lines = ["## Architecture by Module", ""]
|
|
103
105
|
module_stats = []
|
|
104
106
|
for mod_name, mod in result.modules.items():
|
|
@@ -121,6 +123,7 @@ class ContextExporter(BaseExporter):
|
|
|
121
123
|
def _get_important_entries(
|
|
122
124
|
self, result: AnalysisResult
|
|
123
125
|
) -> List[Tuple[str, int, Any]]:
|
|
126
|
+
"""Return (name, fan_out, func_info) triples for notable entry-point functions."""
|
|
124
127
|
entries = []
|
|
125
128
|
for ep in result.entry_points:
|
|
126
129
|
func = result.functions.get(ep)
|
|
@@ -133,6 +136,7 @@ class ContextExporter(BaseExporter):
|
|
|
133
136
|
def _get_key_entry_points(
|
|
134
137
|
self, important_entries: List[Tuple[str, int, Any]]
|
|
135
138
|
) -> List[str]:
|
|
139
|
+
"""Build the Key Entry Points section from pre-ranked entry list."""
|
|
136
140
|
lines = ["## Key Entry Points", "", "Main execution flows into the system:", ""]
|
|
137
141
|
for ep, _, func in important_entries[:30]:
|
|
138
142
|
lines.append(f"### {ep}")
|
|
@@ -13,6 +13,7 @@ class ContextViewGenerator(ViewGeneratorMixin):
|
|
|
13
13
|
"""Generate context.md from project.yaml data."""
|
|
14
14
|
|
|
15
15
|
def _render(self, data: Dict[str, Any]) -> List[str]:
|
|
16
|
+
"""Render the full context narrative from project.yaml data."""
|
|
16
17
|
proj = data.get("project", {})
|
|
17
18
|
health = data.get("health", {})
|
|
18
19
|
modules = data.get("modules", [])
|
|
@@ -30,6 +31,7 @@ class ContextViewGenerator(ViewGeneratorMixin):
|
|
|
30
31
|
|
|
31
32
|
@staticmethod
|
|
32
33
|
def _render_overview(proj: Dict, health: Dict) -> List[str]:
|
|
34
|
+
"""Render the Overview section with key project stats."""
|
|
33
35
|
stats = proj.get("stats", {})
|
|
34
36
|
return [
|
|
35
37
|
"# System Architecture Analysis",
|
|
@@ -49,6 +51,7 @@ class ContextViewGenerator(ViewGeneratorMixin):
|
|
|
49
51
|
|
|
50
52
|
@staticmethod
|
|
51
53
|
def _render_architecture(modules: List[Dict]) -> List[str]:
|
|
54
|
+
"""Render the Architecture section grouped by directory."""
|
|
52
55
|
lines = ["## Architecture", ""]
|
|
53
56
|
dir_groups: Dict[str, List[Dict]] = {}
|
|
54
57
|
for m in modules:
|
|
@@ -81,6 +84,7 @@ class ContextViewGenerator(ViewGeneratorMixin):
|
|
|
81
84
|
|
|
82
85
|
@staticmethod
|
|
83
86
|
def _render_exports(modules: List[Dict]) -> List[str]:
|
|
87
|
+
"""Render the Key Exports section (flagged classes and functions)."""
|
|
84
88
|
lines = ["## Key Exports", ""]
|
|
85
89
|
for m in modules:
|
|
86
90
|
for exp in m.get("exports", []):
|
|
@@ -104,6 +108,7 @@ class ContextViewGenerator(ViewGeneratorMixin):
|
|
|
104
108
|
|
|
105
109
|
@staticmethod
|
|
106
110
|
def _render_hotspots(hotspots: List[Dict]) -> List[str]:
|
|
111
|
+
"""Render the Hotspots section (top 7 by fan-out)."""
|
|
107
112
|
if not hotspots:
|
|
108
113
|
return []
|
|
109
114
|
lines = ["## Hotspots (High Fan-Out)", ""]
|
|
@@ -116,6 +121,7 @@ class ContextViewGenerator(ViewGeneratorMixin):
|
|
|
116
121
|
|
|
117
122
|
@staticmethod
|
|
118
123
|
def _render_refactoring(refactoring: Dict) -> List[str]:
|
|
124
|
+
"""Render the Refactoring Priorities table."""
|
|
119
125
|
priorities = refactoring.get("priorities", [])
|
|
120
126
|
if not priorities:
|
|
121
127
|
return []
|
|
@@ -23,10 +23,12 @@ class HTMLDashboardGenerator:
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
def __init__(self):
|
|
26
|
+
"""Initialise with a DashboardDataBuilder and DashboardRenderer."""
|
|
26
27
|
self._data_builder = DashboardDataBuilder()
|
|
27
28
|
self._renderer = DashboardRenderer()
|
|
28
29
|
|
|
29
30
|
def generate(self, data: Dict[str, Any], output_path: str) -> None:
|
|
31
|
+
"""Render dashboard HTML from data and write it to output_path."""
|
|
30
32
|
html = self._render(data)
|
|
31
33
|
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
|
|
32
34
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
@@ -159,6 +159,7 @@ class PlanfileTicketsExporter(BaseExporter):
|
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
def _high_cc_tickets(result: AnalysisResult) -> list[PlanfileTicketSuggestion]:
|
|
162
|
+
"""Yield refactor tickets for functions whose cyclomatic complexity exceeds the limit."""
|
|
162
163
|
tickets: list[PlanfileTicketSuggestion] = []
|
|
163
164
|
for func in result.functions.values():
|
|
164
165
|
cc = _function_cc(func)
|
|
@@ -187,6 +188,7 @@ def _high_cc_tickets(result: AnalysisResult) -> list[PlanfileTicketSuggestion]:
|
|
|
187
188
|
|
|
188
189
|
|
|
189
190
|
def _god_module_tickets(result: AnalysisResult) -> list[PlanfileTicketSuggestion]:
|
|
191
|
+
"""Yield split tickets for modules that are too large (god-module smell)."""
|
|
190
192
|
tickets: list[PlanfileTicketSuggestion] = []
|
|
191
193
|
for module in result.modules.values():
|
|
192
194
|
class_count = len(module.classes)
|
|
@@ -213,6 +215,7 @@ def _god_module_tickets(result: AnalysisResult) -> list[PlanfileTicketSuggestion
|
|
|
213
215
|
|
|
214
216
|
|
|
215
217
|
def _duplicate_class_tickets(result: AnalysisResult) -> list[PlanfileTicketSuggestion]:
|
|
218
|
+
"""Yield deduplication tickets for class pairs with ≥60% method-name overlap."""
|
|
216
219
|
tickets: list[PlanfileTicketSuggestion] = []
|
|
217
220
|
classes = list(result.classes.values())
|
|
218
221
|
for index, left in enumerate(classes):
|
|
@@ -251,6 +254,7 @@ def _duplicate_class_tickets(result: AnalysisResult) -> list[PlanfileTicketSugge
|
|
|
251
254
|
|
|
252
255
|
|
|
253
256
|
def _smell_tickets(result: AnalysisResult) -> list[PlanfileTicketSuggestion]:
|
|
257
|
+
"""Yield tickets for structural smells not already covered by other ticket generators."""
|
|
254
258
|
tickets: list[PlanfileTicketSuggestion] = []
|
|
255
259
|
for smell in result.smells:
|
|
256
260
|
if smell.type == "god_function" and smell.context.get("complexity", 0) >= _CC_LIMIT:
|
|
@@ -277,6 +281,7 @@ def _smell_tickets(result: AnalysisResult) -> list[PlanfileTicketSuggestion]:
|
|
|
277
281
|
|
|
278
282
|
|
|
279
283
|
def _function_cc(func: FunctionInfo) -> int:
|
|
284
|
+
"""Extract the cyclomatic complexity integer from a FunctionInfo, defaulting to 0."""
|
|
280
285
|
raw = func.complexity.get("cyclomatic_complexity", func.complexity.get("cc", 0))
|
|
281
286
|
try:
|
|
282
287
|
return int(raw)
|
|
@@ -285,10 +290,12 @@ def _function_cc(func: FunctionInfo) -> int:
|
|
|
285
290
|
|
|
286
291
|
|
|
287
292
|
def _method_names(cls: ClassInfo) -> set[str]:
|
|
293
|
+
"""Return the set of unqualified method names for a class."""
|
|
288
294
|
return {method.split(".")[-1] for method in cls.methods}
|
|
289
295
|
|
|
290
296
|
|
|
291
297
|
def _rel_path(path: str, project_path: str) -> str:
|
|
298
|
+
"""Return path relative to project_path, falling back to the original if resolution fails."""
|
|
292
299
|
if not path:
|
|
293
300
|
return ""
|
|
294
301
|
raw = Path(path)
|
|
@@ -11,11 +11,13 @@ from code2llm.exporters.flow_constants import is_excluded_path
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def _is_excluded(path: str) -> bool:
|
|
14
|
+
"""Return True if the given file path should be excluded from duplicate analysis."""
|
|
14
15
|
return is_excluded_path(path)
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@lru_cache(maxsize=4096)
|
|
18
19
|
def _rel_path(fpath: str, project_path: str) -> str:
|
|
20
|
+
"""Return fpath relative to project_path, or fpath unchanged if resolution fails."""
|
|
19
21
|
if not project_path or not fpath:
|
|
20
22
|
return fpath or ""
|
|
21
23
|
try:
|
|
@@ -51,6 +53,7 @@ def _package_of_module(module_name: str) -> str:
|
|
|
51
53
|
|
|
52
54
|
|
|
53
55
|
def _traits_from_cfg(fi: FunctionInfo, result: AnalysisResult) -> list:
|
|
56
|
+
"""Derive structural trait labels (loops/cond/ret) from a function's CFG nodes."""
|
|
54
57
|
traits = []
|
|
55
58
|
node_types = set()
|
|
56
59
|
for nid in fi.cfg_nodes or []:
|
|
@@ -67,6 +70,7 @@ def _traits_from_cfg(fi: FunctionInfo, result: AnalysisResult) -> list:
|
|
|
67
70
|
|
|
68
71
|
|
|
69
72
|
def _dup_file_set(ctx: Dict[str, Any]) -> Set[str]:
|
|
73
|
+
"""Return the set of file paths that appear in at least one duplicate pair."""
|
|
70
74
|
s: Set[str] = set()
|
|
71
75
|
for d in ctx["duplicates"]:
|
|
72
76
|
s.add(d["fileA"])
|
|
@@ -75,6 +79,7 @@ def _dup_file_set(ctx: Dict[str, Any]) -> Set[str]:
|
|
|
75
79
|
|
|
76
80
|
|
|
77
81
|
def _hotspot_description(fi: FunctionInfo, fan_out: int) -> str:
|
|
82
|
+
"""Generate a short human-readable description for a hotspot function."""
|
|
78
83
|
if fi.name == "to_dict":
|
|
79
84
|
return f"{fan_out} conditional field serializations"
|
|
80
85
|
if "format" in fi.name.lower() or "dispatch" in fi.name.lower():
|
|
@@ -52,6 +52,7 @@ class ToonViewGenerator(ViewGeneratorMixin):
|
|
|
52
52
|
"""Generate project.toon.yaml from project.yaml data."""
|
|
53
53
|
|
|
54
54
|
def _render(self, data: Dict[str, Any]) -> List[str]:
|
|
55
|
+
"""Render the full TOON view from project.yaml data."""
|
|
55
56
|
proj = data.get("project", {})
|
|
56
57
|
health = data.get("health", {})
|
|
57
58
|
modules = data.get("modules", [])
|
|
@@ -71,6 +72,7 @@ class ToonViewGenerator(ViewGeneratorMixin):
|
|
|
71
72
|
|
|
72
73
|
@staticmethod
|
|
73
74
|
def _render_header(proj: Dict) -> List[str]:
|
|
75
|
+
"""Render single-line project header with key stats."""
|
|
74
76
|
stats = proj.get("stats", {})
|
|
75
77
|
lang = proj.get("language", "unknown")
|
|
76
78
|
return [
|
|
@@ -85,6 +87,7 @@ class ToonViewGenerator(ViewGeneratorMixin):
|
|
|
85
87
|
|
|
86
88
|
@staticmethod
|
|
87
89
|
def _render_health(health: Dict) -> List[str]:
|
|
90
|
+
"""Render one-line HEALTH summary."""
|
|
88
91
|
return [
|
|
89
92
|
"HEALTH:",
|
|
90
93
|
f" CC̄={health.get('cc_avg', 0)} "
|
|
@@ -95,6 +98,7 @@ class ToonViewGenerator(ViewGeneratorMixin):
|
|
|
95
98
|
|
|
96
99
|
@staticmethod
|
|
97
100
|
def _render_alerts(health: Dict) -> List[str]:
|
|
101
|
+
"""Render ALERTS block (returns empty list if no alerts)."""
|
|
98
102
|
alerts = health.get("alerts", [])
|
|
99
103
|
if not alerts:
|
|
100
104
|
return []
|
|
@@ -116,6 +120,7 @@ class ToonViewGenerator(ViewGeneratorMixin):
|
|
|
116
120
|
|
|
117
121
|
@staticmethod
|
|
118
122
|
def _render_modules(modules: List[Dict]) -> List[str]:
|
|
123
|
+
"""Render MODULES block with top-15 by size and language breakdown."""
|
|
119
124
|
# Show top modules by size (lines) - works for all languages
|
|
120
125
|
top_by_lines = sorted(modules, key=lambda m: m.get("lines", 0), reverse=True)[
|
|
121
126
|
:15
|
|
@@ -149,6 +154,7 @@ class ToonViewGenerator(ViewGeneratorMixin):
|
|
|
149
154
|
|
|
150
155
|
@staticmethod
|
|
151
156
|
def _render_hotspots(hotspots: List[Dict]) -> List[str]:
|
|
157
|
+
"""Render HOTSPOTS block (top 5 by fan-out)."""
|
|
152
158
|
if not hotspots:
|
|
153
159
|
return []
|
|
154
160
|
lines = ["", f"HOTSPOTS[{len(hotspots)}]:"]
|
|
@@ -161,6 +167,7 @@ class ToonViewGenerator(ViewGeneratorMixin):
|
|
|
161
167
|
|
|
162
168
|
@staticmethod
|
|
163
169
|
def _render_refactoring(refactoring: Dict) -> List[str]:
|
|
170
|
+
"""Render REFACTOR block with top 5 refactoring actions."""
|
|
164
171
|
priorities = refactoring.get("priorities", [])
|
|
165
172
|
if not priorities:
|
|
166
173
|
return []
|
|
@@ -11,6 +11,8 @@ from .parsing import _parse_call_label
|
|
|
11
11
|
|
|
12
12
|
@dataclass(frozen=True)
|
|
13
13
|
class FuncSummary:
|
|
14
|
+
"""Immutable summary of a single function's decisions and outgoing calls."""
|
|
15
|
+
|
|
14
16
|
name: str
|
|
15
17
|
file: Optional[str]
|
|
16
18
|
line: Optional[int]
|
|
@@ -67,6 +69,7 @@ def _pick_relevant_functions(
|
|
|
67
69
|
]
|
|
68
70
|
|
|
69
71
|
def score(fn: str) -> int:
|
|
72
|
+
"""Rank function by node count, calls, and entry-point status for flow selection."""
|
|
70
73
|
s = 0
|
|
71
74
|
s += min(500, counts.get(fn, 0)) # node count baseline
|
|
72
75
|
for needle, boost in keyword_boosts:
|
|
@@ -147,6 +150,7 @@ def _summarize_functions(
|
|
|
147
150
|
def _build_call_graph(
|
|
148
151
|
func_summaries: Dict[str, FuncSummary], known_functions: Set[str]
|
|
149
152
|
) -> Dict[str, Set[str]]:
|
|
153
|
+
"""Build adjacency list of internal calls between known functions."""
|
|
150
154
|
g: Dict[str, Set[str]] = defaultdict(set)
|
|
151
155
|
for fn, s in func_summaries.items():
|
|
152
156
|
for callee in s.calls:
|
|
@@ -158,6 +162,7 @@ def _build_call_graph(
|
|
|
158
162
|
def _reachable(
|
|
159
163
|
g: Dict[str, Set[str]], roots: Iterable[str], max_nodes: int
|
|
160
164
|
) -> List[str]:
|
|
165
|
+
"""BFS from roots through call graph; return up to max_nodes reachable functions."""
|
|
161
166
|
seen: Set[str] = set()
|
|
162
167
|
q: deque[str] = deque([r for r in roots if r])
|
|
163
168
|
|
|
@@ -11,6 +11,7 @@ from .generator import generate_llm_flow, render_llm_flow_md
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def create_parser() -> argparse.ArgumentParser:
|
|
14
|
+
"""Build and return the argument parser for the llm-flow-generator CLI."""
|
|
14
15
|
p = argparse.ArgumentParser(
|
|
15
16
|
prog="llm-flow-generator",
|
|
16
17
|
description="Generate compact LLM-friendly app flow summary from code2llm analysis.yaml",
|
|
@@ -39,6 +40,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def main(argv: Optional[List[str]] = None) -> int:
|
|
43
|
+
"""Entry point: parse args, run flow generation, write output files."""
|
|
42
44
|
args = create_parser().parse_args(argv)
|
|
43
45
|
|
|
44
46
|
input_path = Path(args.input)
|
|
@@ -16,6 +16,7 @@ def generate_llm_flow(
|
|
|
16
16
|
limit_decisions: int,
|
|
17
17
|
limit_calls: int,
|
|
18
18
|
) -> Dict[str, Any]:
|
|
19
|
+
"""Build a compact LLM-friendly flow dict from a code2llm analysis result."""
|
|
19
20
|
nodes = _collect_nodes(analysis)
|
|
20
21
|
entrypoints = _collect_entrypoints(nodes)
|
|
21
22
|
|
|
@@ -62,6 +63,7 @@ def generate_llm_flow(
|
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
def render_llm_flow_md(flow: Dict[str, Any]) -> str:
|
|
66
|
+
"""Render a flow dict (from generate_llm_flow) as a Markdown summary string."""
|
|
65
67
|
app = _as_dict(flow.get("app"))
|
|
66
68
|
entrypoints = _as_list(app.get("entrypoints"))
|
|
67
69
|
selected = _as_list(_as_dict(flow.get("flow")).get("selected_functions"))
|
|
@@ -9,10 +9,12 @@ from ._utils import dump_yaml
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def _strip_bom(text: str) -> str:
|
|
12
|
+
"""Strip UTF-8 BOM from the start of text if present."""
|
|
12
13
|
return text[1:] if text.startswith("\ufeff") else text
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def _ensure_list(value: Any) -> List[Any]:
|
|
17
|
+
"""Wrap scalar value in a list; return value unchanged if already a list."""
|
|
16
18
|
if value is None:
|
|
17
19
|
return []
|
|
18
20
|
if isinstance(value, list):
|
|
@@ -30,6 +32,7 @@ def _deep_get(d: Dict[str, Any], path: Tuple[str, ...]) -> Any:
|
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
def normalize_llm_task(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
35
|
+
"""Normalise a raw LLM task dict into a canonical structure with all required keys."""
|
|
33
36
|
task = data.get("task") or {}
|
|
34
37
|
context = data.get("context") or {}
|
|
35
38
|
deliverables = data.get("deliverables") or {}
|
|
@@ -308,6 +311,7 @@ def load_input(path: Path) -> Dict[str, Any]:
|
|
|
308
311
|
|
|
309
312
|
|
|
310
313
|
def create_parser() -> argparse.ArgumentParser:
|
|
314
|
+
"""Build and return the CLI argument parser for the LLM task generator."""
|
|
311
315
|
p = argparse.ArgumentParser(
|
|
312
316
|
prog="llm-task-generator",
|
|
313
317
|
description="Generate normalized llm_task.yaml from simplified task spec (text/YAML/JSON).",
|
|
@@ -325,6 +329,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
325
329
|
|
|
326
330
|
|
|
327
331
|
def main(argv: Optional[List[str]] = None) -> int:
|
|
332
|
+
"""Entry point for the LLM task generator CLI."""
|
|
328
333
|
args = create_parser().parse_args(argv)
|
|
329
334
|
|
|
330
335
|
input_path = Path(args.input)
|