code2llm 0.5.180__tar.gz → 0.5.185__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.180/code2llm.egg-info → code2llm-0.5.185}/PKG-INFO +7 -6
- {code2llm-0.5.180 → code2llm-0.5.185}/README.md +6 -5
- code2llm-0.5.185/VERSION +1 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/__init__.py +1 -1
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/smell_engine.py +11 -6
- code2llm-0.5.185/code2llm/autoupdate.py +178 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli.py +6 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_exports/code2logic.py +4 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_exports/formats.py +11 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_exports/orchestrator.py +2 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_exports/orchestrator_constants.py +3 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_parser.py +1 -1
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/analyzer.py +59 -8
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/config.py +1 -1
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/file_filter.py +66 -11
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/persistent_cache.py +14 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/streaming/prioritizer.py +22 -15
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/__init__.py +8 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/flow_constants.py +4 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/map/module_list.py +1 -1
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/map/utils.py +1 -1
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/map/yaml_export.py +1 -1
- code2llm-0.5.185/code2llm/exporters/projections_exporter.py +139 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/_render_section_helpers.py +20 -11
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/helpers.py +21 -2
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/mermaid/png.py +45 -26
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/nlp/__init__.py +1 -1
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/refactor/prompt_engine.py +1 -1
- {code2llm-0.5.180 → code2llm-0.5.185/code2llm.egg-info}/PKG-INFO +7 -6
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm.egg-info/SOURCES.txt +7 -1
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm.egg-info/top_level.txt +0 -1
- {code2llm-0.5.180 → code2llm-0.5.185}/pyproject.toml +8 -2
- {code2llm-0.5.180 → code2llm-0.5.185}/setup.py +1 -1
- code2llm-0.5.185/tests/test_analysis_env.py +46 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_analyzer.py +69 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_edge_cases.py +33 -0
- code2llm-0.5.185/tests/test_perf_exporters.py +74 -0
- code2llm-0.5.185/tests/test_projections_exporter.py +130 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_prompt_engine.py +2 -2
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_smells_data_clumps.py +51 -0
- code2llm-0.5.185/tests/test_version_parity.py +38 -0
- code2llm-0.5.180/VERSION +0 -1
- {code2llm-0.5.180 → code2llm-0.5.185}/LICENSE +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/MANIFEST.in +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/__main__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/_data_impl.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/call_graph_engine.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/complexity.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/native_bridge.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/pipeline_classifier.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/pipeline_detector.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/pipeline_resolver.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/type_inference.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/utils/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/analysis/utils/ast_helpers.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/api.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_analysis.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_commands.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_exports/orchestrator_chunked.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_exports/orchestrator_handlers.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/cli_exports/prompt.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/ast_registry.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/export_pipeline.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/file_analyzer.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/file_cache.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/gitignore.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/incremental.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/_c_parser.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/_calls.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/_complexity.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/base.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/cpp.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/csharp.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/generic.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/go_lang.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/java.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/js_analysis.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/php.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/ruby.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/rust.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/ts_extractors.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/ts_parser.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/lang/typescript.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/large_repo.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/models.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/refactoring.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/repo_files.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/source_classifier.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/dashboard_data.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/dashboard_renderer.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/evolution/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/evolution/computation.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/evolution/constants.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/evolution/exclusion.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/evolution/render.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/evolution/yaml_export.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/index_generator/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/index_generator/renderer.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/index_generator/scanner.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/index_generator.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/map/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/map/alerts.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/map/details.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/map/header.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/mermaid/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/mermaid/calls.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/mermaid/classic.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/mermaid/compact.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/mermaid/flow_compact.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/mermaid/flow_detailed.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/mermaid/flow_full.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/mermaid/utils.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/planfile_tickets.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/project_yaml/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/project_yaml/constants.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/project_yaml/core.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/project_yaml/evolution.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/project_yaml/health.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/project_yaml/hotspots.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/project_yaml/modules.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/project_yaml_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/readme/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/readme/content.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/readme/files.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/readme/insights.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/readme/sections.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/_render_coupling_helpers.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/constants.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/metrics_core.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/metrics_duplicates.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/metrics_health.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/_utils.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/llm_flow/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/llm_flow/analysis.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/llm_flow/cli.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/llm_flow/generator.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/llm_flow/nodes.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/llm_flow/parsing.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/llm_flow/utils.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/llm_task.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/mermaid/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/mermaid/fix.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/mermaid/validation.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/generators/mermaid.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/parsers/toon_parser.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/templates/extract_method.md +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm/templates/move_method.md +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/code2llm.egg-info/requires.txt +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/setup.cfg +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_analysis_cache_version.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_cache_invalidation_e2e.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_call_graph_engine.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_calls_toon_export.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_complexity.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_declarative_collection.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_dup_groups.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_export_cache_flags.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_export_cache_ownership.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_export_profile_contract.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_file_analyzer_tagging.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_js_complexity_boundaries.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_mermaid_png.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_native_rust_bridge.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_orchestrator_cache_mtime.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_persistent_cache.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_planfile_tickets_exporter.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_project_toon_export.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_prompt_txt.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_smell_engine.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/tests/test_smells_shotgun_surgery.py +0 -0
- {code2llm-0.5.180 → code2llm-0.5.185}/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.185
|
|
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: Tom Sapletta
|
|
@@ -77,13 +77,13 @@ Dynamic: requires-python
|
|
|
77
77
|
|
|
78
78
|
## AI Cost Tracking
|
|
79
79
|
|
|
80
|
-
    
|
|
81
|
+
  
|
|
82
82
|
|
|
83
|
-
- 🤖 **LLM usage:** $
|
|
84
|
-
- 👤 **Human dev:** ~$
|
|
83
|
+
- 🤖 **LLM usage:** $0.5710 (336 commits)
|
|
84
|
+
- 👤 **Human dev:** ~$14034 (140.3h @ $100/h, 30min dedup)
|
|
85
85
|
|
|
86
|
-
Generated on 2026-
|
|
86
|
+
Generated on 2026-10-18 using [openrouter/deep/deep-v4-pro](https://openrouter.ai/deep/deep-v4-pro)
|
|
87
87
|
|
|
88
88
|
---
|
|
89
89
|
|
|
@@ -101,6 +101,7 @@ When you run `code2llm ./ -f all`, the following files are created:
|
|
|
101
101
|
|------|--------|---------|--------------|
|
|
102
102
|
| `evolution.toon.yaml` | **YAML** | **📋 Refactoring queue** - Prioritized improvements | 0 refactoring actions needed |
|
|
103
103
|
| `map.toon.yaml` | **YAML** | **🗺️ Structural map + project header** - Modules, imports, exports, signatures, stats, alerts, hotspots, trend | Project architecture overview |
|
|
104
|
+
| `projections.json` | **JSON** | **🔎 Bounded symbol catalog** (`code2llm.projection/v1`) for intent retrieval / external indexers — deterministic, digested, capped at 2048 records | Reuse contract without analyzer internals |
|
|
104
105
|
|
|
105
106
|
### 🤖 LLM-Ready Documentation
|
|
106
107
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## AI Cost Tracking
|
|
4
4
|
|
|
5
|
-
    
|
|
6
|
+
  
|
|
7
7
|
|
|
8
|
-
- 🤖 **LLM usage:** $
|
|
9
|
-
- 👤 **Human dev:** ~$
|
|
8
|
+
- 🤖 **LLM usage:** $0.5710 (336 commits)
|
|
9
|
+
- 👤 **Human dev:** ~$14034 (140.3h @ $100/h, 30min dedup)
|
|
10
10
|
|
|
11
|
-
Generated on 2026-
|
|
11
|
+
Generated on 2026-10-18 using [openrouter/deep/deep-v4-pro](https://openrouter.ai/deep/deep-v4-pro)
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -26,6 +26,7 @@ When you run `code2llm ./ -f all`, the following files are created:
|
|
|
26
26
|
|------|--------|---------|--------------|
|
|
27
27
|
| `evolution.toon.yaml` | **YAML** | **📋 Refactoring queue** - Prioritized improvements | 0 refactoring actions needed |
|
|
28
28
|
| `map.toon.yaml` | **YAML** | **🗺️ Structural map + project header** - Modules, imports, exports, signatures, stats, alerts, hotspots, trend | Project architecture overview |
|
|
29
|
+
| `projections.json` | **JSON** | **🔎 Bounded symbol catalog** (`code2llm.projection/v1`) for intent retrieval / external indexers — deterministic, digested, capped at 2048 records | Reuse contract without analyzer internals |
|
|
29
30
|
|
|
30
31
|
### 🤖 LLM-Ready Documentation
|
|
31
32
|
|
code2llm-0.5.185/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.5.185
|
|
@@ -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.185"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -212,22 +212,27 @@ def detect_data_clumps(
|
|
|
212
212
|
|
|
213
213
|
for (file, args), funcs in arg_sets.items():
|
|
214
214
|
if len(funcs) >= 2:
|
|
215
|
-
|
|
215
|
+
# Names feed exported ticket identities, so hash/discovery order
|
|
216
|
+
# must not turn the same finding into a new ticket after restart.
|
|
217
|
+
ordered_args = sorted(args)
|
|
218
|
+
ordered_funcs = sorted(funcs)
|
|
219
|
+
arg_names = ", ".join(ordered_args)
|
|
220
|
+
for func_name in ordered_funcs:
|
|
216
221
|
func_info = functions[func_name]
|
|
217
222
|
smells.append(
|
|
218
223
|
CodeSmell(
|
|
219
|
-
name=f"Data Clump: {
|
|
224
|
+
name=f"Data Clump: {arg_names}",
|
|
220
225
|
type="data_clump",
|
|
221
226
|
file=func_info.file,
|
|
222
227
|
line=func_info.line,
|
|
223
228
|
severity=0.6,
|
|
224
229
|
description=(
|
|
225
|
-
f"Arguments ({
|
|
226
|
-
f" in multiple functions: {', '.join(
|
|
230
|
+
f"Arguments ({arg_names}) are used together"
|
|
231
|
+
f" in multiple functions: {', '.join(ordered_funcs)}."
|
|
227
232
|
),
|
|
228
233
|
context={
|
|
229
|
-
"clump":
|
|
230
|
-
"related_functions":
|
|
234
|
+
"clump": ordered_args,
|
|
235
|
+
"related_functions": ordered_funcs,
|
|
231
236
|
},
|
|
232
237
|
)
|
|
233
238
|
)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Autonomous non-blocking background update checker and auto-upgrader for CLI tools.
|
|
2
|
+
|
|
3
|
+
Checks PyPI for package updates at most once every TTL interval (default: 24h).
|
|
4
|
+
Spawns a detached background process with zero network delay on the CLI invocation.
|
|
5
|
+
If an update is available:
|
|
6
|
+
- If AUTO_UPGRADE=1 or <PKG>_AUTO_UPGRADE=1: spawns a background pip install --upgrade.
|
|
7
|
+
- Otherwise, reports cleanly on stderr.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
from importlib import metadata
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_cache_dir(pkg_name: str) -> Path:
|
|
22
|
+
"""Return platform cache directory for package update tracking."""
|
|
23
|
+
xdg_cache = os.environ.get("XDG_CACHE_HOME")
|
|
24
|
+
if xdg_cache:
|
|
25
|
+
base = Path(xdg_cache)
|
|
26
|
+
else:
|
|
27
|
+
base = Path.home() / ".cache"
|
|
28
|
+
pkg_cache = base / pkg_name
|
|
29
|
+
pkg_cache.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
return pkg_cache
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _is_newer(latest: str, current: str) -> bool:
|
|
34
|
+
"""Check if latest is strictly newer than current, with zero hard dependency."""
|
|
35
|
+
try:
|
|
36
|
+
from packaging import version
|
|
37
|
+
return version.parse(latest) > version.parse(current)
|
|
38
|
+
except Exception:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def parse_simple(v: str) -> tuple:
|
|
42
|
+
nums = []
|
|
43
|
+
for part in v.split("."):
|
|
44
|
+
digits = ""
|
|
45
|
+
for ch in part:
|
|
46
|
+
if ch.isdigit():
|
|
47
|
+
digits += ch
|
|
48
|
+
else:
|
|
49
|
+
break
|
|
50
|
+
nums.append(int(digits) if digits else 0)
|
|
51
|
+
return tuple(nums)
|
|
52
|
+
|
|
53
|
+
return parse_simple(latest) > parse_simple(current)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _spawn_detached_check(pkg_name: str, current_version: str, cache_file: Path) -> None:
|
|
57
|
+
"""Launch completely detached background worker process."""
|
|
58
|
+
worker_code = f"""
|
|
59
|
+
import json, sys, time
|
|
60
|
+
from urllib import request
|
|
61
|
+
from pathlib import Path
|
|
62
|
+
|
|
63
|
+
pkg_name = {pkg_name!r}
|
|
64
|
+
current_version = {current_version!r}
|
|
65
|
+
cache_file = Path({str(cache_file)!r})
|
|
66
|
+
|
|
67
|
+
latest = None
|
|
68
|
+
try:
|
|
69
|
+
url = f"https://pypi.org/pypi/{{pkg_name}}/json"
|
|
70
|
+
req = request.Request(url, headers={{"User-Agent": f"{{pkg_name}}-autoupdate"}})
|
|
71
|
+
with request.urlopen(req, timeout=3.0) as resp:
|
|
72
|
+
if resp.status == 200:
|
|
73
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
74
|
+
latest = data.get("info", {{}}).get("version")
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
cache_data = {{
|
|
79
|
+
"last_check": time.time(),
|
|
80
|
+
"current_version": current_version,
|
|
81
|
+
"latest_version": latest or current_version,
|
|
82
|
+
}}
|
|
83
|
+
try:
|
|
84
|
+
cache_file.write_text(json.dumps(cache_data), encoding="utf-8")
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
subprocess.Popen(
|
|
90
|
+
[sys.executable, "-c", worker_code],
|
|
91
|
+
stdout=subprocess.DEVNULL,
|
|
92
|
+
stderr=subprocess.DEVNULL,
|
|
93
|
+
stdin=subprocess.DEVNULL,
|
|
94
|
+
start_new_session=True,
|
|
95
|
+
)
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _spawn_background_upgrade(pkg_name: str) -> None:
|
|
101
|
+
"""Spawn background pip install --upgrade if auto-upgrade is enabled."""
|
|
102
|
+
try:
|
|
103
|
+
subprocess.Popen(
|
|
104
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", "--quiet", "--disable-pip-version-check", pkg_name],
|
|
105
|
+
stdout=subprocess.DEVNULL,
|
|
106
|
+
stderr=subprocess.DEVNULL,
|
|
107
|
+
stdin=subprocess.DEVNULL,
|
|
108
|
+
start_new_session=True,
|
|
109
|
+
)
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def check_for_updates(
|
|
115
|
+
pkg_name: str,
|
|
116
|
+
ttl_seconds: int = 86400, # 24 hours
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Non-blocking check for package updates.
|
|
119
|
+
|
|
120
|
+
Reads previous check result from disk (fast, ~0.1ms). If an update was found
|
|
121
|
+
in a previous run:
|
|
122
|
+
- If AUTO_UPGRADE=1 or <PKG>_AUTO_UPGRADE=1, triggers a silent background pip upgrade.
|
|
123
|
+
- Otherwise, displays a friendly notice on stderr.
|
|
124
|
+
Spawns a detached process to query PyPI in the background only when the cache is expired.
|
|
125
|
+
"""
|
|
126
|
+
if os.environ.get("CI") or os.environ.get("NO_AUTOUPDATE"):
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
current_version = metadata.version(pkg_name)
|
|
131
|
+
except Exception:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
cache_dir = _get_cache_dir(pkg_name)
|
|
135
|
+
cache_file = cache_dir / "update_check.json"
|
|
136
|
+
|
|
137
|
+
should_query = True
|
|
138
|
+
cached_latest = None
|
|
139
|
+
|
|
140
|
+
if cache_file.exists():
|
|
141
|
+
try:
|
|
142
|
+
cached = json.loads(cache_file.read_text(encoding="utf-8"))
|
|
143
|
+
last_check = cached.get("last_check", 0)
|
|
144
|
+
cached_latest = cached.get("latest_version")
|
|
145
|
+
if time.time() - last_check < ttl_seconds:
|
|
146
|
+
should_query = False
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
if cached_latest and cached_latest != current_version:
|
|
151
|
+
try:
|
|
152
|
+
if _is_newer(cached_latest, current_version):
|
|
153
|
+
safe_pkg = pkg_name.upper().replace("-", "_")
|
|
154
|
+
env_pkg_key = safe_pkg + "_AUTO_UPGRADE"
|
|
155
|
+
auto_upgrade_enabled = (
|
|
156
|
+
os.environ.get("AUTO_UPGRADE") == "1" or
|
|
157
|
+
os.environ.get(env_pkg_key) == "1"
|
|
158
|
+
)
|
|
159
|
+
if auto_upgrade_enabled:
|
|
160
|
+
sys.stderr.write(
|
|
161
|
+
"\n⚡ [" + pkg_name + "] Automatyczna aktualizacja w tle: "
|
|
162
|
+
+ current_version + " → " + cached_latest + "...\n"
|
|
163
|
+
)
|
|
164
|
+
sys.stderr.flush()
|
|
165
|
+
_spawn_background_upgrade(pkg_name)
|
|
166
|
+
else:
|
|
167
|
+
sys.stderr.write(
|
|
168
|
+
"\n💡 [" + pkg_name + "] Nowa wersja dostępna: "
|
|
169
|
+
+ current_version + " → " + cached_latest + "\n"
|
|
170
|
+
+ " Aby zaktualizować, uruchom: pip install --upgrade " + pkg_name + "\n"
|
|
171
|
+
+ " (lub ustaw AUTO_UPGRADE=1)\n\n"
|
|
172
|
+
)
|
|
173
|
+
sys.stderr.flush()
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
if should_query:
|
|
178
|
+
_spawn_detached_check(pkg_name, current_version, cache_file)
|
|
@@ -29,6 +29,12 @@ _validate_chunked_output = validate_chunked_output
|
|
|
29
29
|
|
|
30
30
|
def main():
|
|
31
31
|
"""Main CLI entry point."""
|
|
32
|
+
try:
|
|
33
|
+
from .autoupdate import check_for_updates
|
|
34
|
+
check_for_updates("code2llm")
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
|
|
32
38
|
# Handle special sub-commands first
|
|
33
39
|
special_result = handle_special_commands()
|
|
34
40
|
if special_result is not None:
|
|
@@ -65,6 +65,10 @@ def _build_code2logic_cmd(args, source_path: Path, output_dir: Path) -> list[str
|
|
|
65
65
|
"-o",
|
|
66
66
|
str(output_dir),
|
|
67
67
|
]
|
|
68
|
+
excludes = getattr(args, "exclude", None)
|
|
69
|
+
if excludes:
|
|
70
|
+
cmd.append("--exclude")
|
|
71
|
+
cmd.extend(str(x) for x in excludes)
|
|
68
72
|
if not args.verbose:
|
|
69
73
|
cmd.append("-q")
|
|
70
74
|
return cmd
|
|
@@ -30,6 +30,8 @@ from code2llm.exporters import (
|
|
|
30
30
|
EvolutionExporter,
|
|
31
31
|
READMEExporter,
|
|
32
32
|
ProjectYAMLExporter,
|
|
33
|
+
ProjectionsExporter,
|
|
34
|
+
PROJECTION_FILENAME,
|
|
33
35
|
ToonViewGenerator,
|
|
34
36
|
ContextViewGenerator,
|
|
35
37
|
ArticleViewGenerator,
|
|
@@ -195,6 +197,15 @@ def _export_simple_formats(args, result, output_dir: Path, formats):
|
|
|
195
197
|
if args.verbose:
|
|
196
198
|
print(f" - JSON: {filepath}")
|
|
197
199
|
|
|
200
|
+
if "projections" in formats:
|
|
201
|
+
exporter = ProjectionsExporter()
|
|
202
|
+
filepath = output_dir / PROJECTION_FILENAME
|
|
203
|
+
t0 = time.monotonic()
|
|
204
|
+
exporter.export(result, str(filepath))
|
|
205
|
+
elapsed = time.monotonic() - t0
|
|
206
|
+
if args.verbose:
|
|
207
|
+
print(f" - PROJECTIONS: {filepath} ({elapsed:.2f}s)")
|
|
208
|
+
|
|
198
209
|
|
|
199
210
|
def _export_yaml(args, result, output_dir: Path):
|
|
200
211
|
"""Export YAML with optional split/separated modes."""
|
|
@@ -252,6 +252,8 @@ def _expand_all_formats(requested: List[str], include_png: bool = False) -> List
|
|
|
252
252
|
if "all" not in requested:
|
|
253
253
|
return requested[:]
|
|
254
254
|
formats = ["toon", "map", "context", "evolution", "mermaid", "planfile"]
|
|
255
|
+
if include_png and "mermaid" not in formats:
|
|
256
|
+
formats.append("mermaid")
|
|
255
257
|
return formats
|
|
256
258
|
|
|
257
259
|
|
|
@@ -20,6 +20,7 @@ FORMAT_FILENAMES: Dict[str, str] = {
|
|
|
20
20
|
"planfile": "planfile-tickets.yaml",
|
|
21
21
|
"readme": "README.md",
|
|
22
22
|
"project-yaml": "project.yaml",
|
|
23
|
+
"projections": "projections.json",
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
# Files produced per format in dry-run preview
|
|
@@ -33,6 +34,7 @@ FORMAT_DRY_RUN_FILES: Dict[str, List[str]] = {
|
|
|
33
34
|
"yaml": ["analysis.yaml"],
|
|
34
35
|
"json": ["analysis.json"],
|
|
35
36
|
"readme": ["README.md"],
|
|
37
|
+
"projections": ["projections.json"],
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
# Human-readable labels
|
|
@@ -47,6 +49,7 @@ FORMAT_LABELS: Dict[str, str] = {
|
|
|
47
49
|
"planfile": "PLANFILE (ticket suggestions)",
|
|
48
50
|
"readme": "README (documentation)",
|
|
49
51
|
"project-yaml": "PROJECT-YAML (single source of truth)",
|
|
52
|
+
"projections": "PROJECTIONS (intent retrieval)",
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
# ---------------------------------------------------------------------------
|
|
@@ -97,7 +97,7 @@ Strategy Options (--strategy):
|
|
|
97
97
|
default="toon",
|
|
98
98
|
help=(
|
|
99
99
|
"Output formats: toon,map,flow,context,code2logic,yaml,json,mermaid,"
|
|
100
|
-
"evolution,calls,png,project-yaml,planfile,all (default: toon)"
|
|
100
|
+
"evolution,calls,png,project-yaml,planfile,projections,all (default: toon)"
|
|
101
101
|
),
|
|
102
102
|
)
|
|
103
103
|
|
|
@@ -36,9 +36,12 @@
|
|
|
36
36
|
# Config is serialised to a plain dict before pickling (dataclasses are not picklable).
|
|
37
37
|
# Results from worker processes are plain dicts; _merge_results rebuilds dataclasses.
|
|
38
38
|
|
|
39
|
+
import hashlib
|
|
39
40
|
import logging
|
|
41
|
+
import multiprocessing as mp
|
|
40
42
|
import os
|
|
41
43
|
import time
|
|
44
|
+
from collections import Counter
|
|
42
45
|
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
43
46
|
from pathlib import Path
|
|
44
47
|
from typing import Dict, List, Optional, Tuple
|
|
@@ -171,7 +174,7 @@ class ProjectAnalyzer:
|
|
|
171
174
|
cached_results: List[Dict] = []
|
|
172
175
|
for fp in cached_paths:
|
|
173
176
|
r = pcache.get_file_result(fp)
|
|
174
|
-
if r
|
|
177
|
+
if isinstance(r, dict) and getattr(r.get("module"), "name", None) == path_to_module[fp]:
|
|
175
178
|
cached_results.append(r)
|
|
176
179
|
else:
|
|
177
180
|
changed_paths.append(fp)
|
|
@@ -317,12 +320,16 @@ class ProjectAnalyzer:
|
|
|
317
320
|
project_name = project_path.name
|
|
318
321
|
|
|
319
322
|
# Fast path: native Rust directory traversal
|
|
323
|
+
skip_dirs = list(
|
|
324
|
+
_SKIP_DIR_NAMES
|
|
325
|
+
| getattr(self.file_filter, "_simple_dir_excludes", set())
|
|
326
|
+
)
|
|
320
327
|
native_files = native_walk_project_files(
|
|
321
328
|
project_str,
|
|
322
329
|
extensions=list(ALL_EXTENSIONS),
|
|
323
330
|
filenames=list(ALL_FILENAMES),
|
|
324
331
|
filename_prefixes=list(LANGUAGE_FILENAME_PREFIXES),
|
|
325
|
-
skip_dirs=
|
|
332
|
+
skip_dirs=skip_dirs,
|
|
326
333
|
respect_gitignore=getattr(self.config.filters, "gitignore_enabled", True),
|
|
327
334
|
)
|
|
328
335
|
if native_files is not None:
|
|
@@ -331,7 +338,7 @@ class ProjectAnalyzer:
|
|
|
331
338
|
for f_path, mod_name in native_files
|
|
332
339
|
if self.file_filter.should_process(f_path)
|
|
333
340
|
]
|
|
334
|
-
return filtered
|
|
341
|
+
return self._source_module_names(filtered, project_path)
|
|
335
342
|
|
|
336
343
|
files = []
|
|
337
344
|
ext_set = set(ALL_EXTENSIONS)
|
|
@@ -362,7 +369,32 @@ class ProjectAnalyzer:
|
|
|
362
369
|
module_name = self._compute_module_name(rel, filename, project_name)
|
|
363
370
|
files.append((file_str, module_name))
|
|
364
371
|
|
|
365
|
-
return files
|
|
372
|
+
return self._source_module_names(files, project_path)
|
|
373
|
+
|
|
374
|
+
@staticmethod
|
|
375
|
+
def _source_module_names(files, project_path):
|
|
376
|
+
"""Give colliding logical names distinct, deterministic static identities.
|
|
377
|
+
|
|
378
|
+
File/package aliases can coexist on disk. Keeping either under their
|
|
379
|
+
shared import name would silently overwrite symbols and may bind calls
|
|
380
|
+
to the wrong file. Unique names retain compatibility; ambiguous names
|
|
381
|
+
receive source-path identities before analysis, on both file walkers.
|
|
382
|
+
"""
|
|
383
|
+
counts = Counter(name for _, name in files)
|
|
384
|
+
reserved = set(counts)
|
|
385
|
+
selected = {}
|
|
386
|
+
for file_path, name in sorted(files):
|
|
387
|
+
if counts[name] == 1:
|
|
388
|
+
selected[file_path] = name
|
|
389
|
+
continue
|
|
390
|
+
relative = Path(file_path).relative_to(project_path).as_posix()
|
|
391
|
+
digest = hashlib.sha256(relative.encode("utf-8")).hexdigest()
|
|
392
|
+
candidate = name + ".__source_" + digest
|
|
393
|
+
while candidate in reserved:
|
|
394
|
+
candidate += "_"
|
|
395
|
+
reserved.add(candidate)
|
|
396
|
+
selected[file_path] = candidate
|
|
397
|
+
return [(path, selected[path]) for path, _ in files]
|
|
366
398
|
|
|
367
399
|
def _wrap_tqdm(self, iterator, total: int, desc: str = "Analyzing"):
|
|
368
400
|
"""Wrap iterator with tqdm when verbose=False and tqdm is available."""
|
|
@@ -390,7 +422,14 @@ class ProjectAnalyzer:
|
|
|
390
422
|
"output_dir": self.config.output_dir,
|
|
391
423
|
}
|
|
392
424
|
|
|
393
|
-
|
|
425
|
+
# Use forkserver if available (Linux) to prevent deadlocks with native C/Rust extensions
|
|
426
|
+
# and multi-threaded runtimes when forking.
|
|
427
|
+
try:
|
|
428
|
+
mp_context = mp.get_context("forkserver")
|
|
429
|
+
except ValueError:
|
|
430
|
+
mp_context = None
|
|
431
|
+
|
|
432
|
+
with ProcessPoolExecutor(max_workers=workers, mp_context=mp_context) as executor:
|
|
394
433
|
# Submit all jobs
|
|
395
434
|
future_to_file = {
|
|
396
435
|
executor.submit(
|
|
@@ -401,7 +440,9 @@ class ProjectAnalyzer:
|
|
|
401
440
|
|
|
402
441
|
# Collect results as they complete (with optional progress bar)
|
|
403
442
|
completed = 0
|
|
404
|
-
iterator = self._wrap_tqdm(
|
|
443
|
+
iterator = self._wrap_tqdm(
|
|
444
|
+
as_completed(future_to_file), total=len(files), desc="Analyzing"
|
|
445
|
+
)
|
|
405
446
|
for future in iterator:
|
|
406
447
|
file_path, _ = future_to_file[future]
|
|
407
448
|
try:
|
|
@@ -422,11 +463,21 @@ class ProjectAnalyzer:
|
|
|
422
463
|
analyzer = FileAnalyzer(self.config, self.cache)
|
|
423
464
|
total = len(files)
|
|
424
465
|
|
|
425
|
-
file_iterator = self._wrap_tqdm(
|
|
466
|
+
file_iterator = self._wrap_tqdm(
|
|
467
|
+
list(enumerate(files, 1)), total=total, desc="Analyzing"
|
|
468
|
+
)
|
|
426
469
|
for i, (file_path, module_name) in file_iterator:
|
|
427
470
|
try:
|
|
428
|
-
|
|
471
|
+
cached = self.cache.get_fast(file_path) if self.cache else None
|
|
472
|
+
stale_identity = cached is not None and (
|
|
473
|
+
not isinstance(cached, dict)
|
|
474
|
+
or getattr(cached.get("module"), "name", None) != module_name
|
|
475
|
+
)
|
|
476
|
+
selected = FileAnalyzer(self.config, None) if stale_identity else analyzer
|
|
477
|
+
result = selected.analyze_file(file_path, module_name)
|
|
429
478
|
if result:
|
|
479
|
+
if stale_identity:
|
|
480
|
+
self.cache.put_fast(file_path, result)
|
|
430
481
|
results.append(result)
|
|
431
482
|
except Exception as e:
|
|
432
483
|
if self.config.verbose:
|
|
@@ -91,7 +91,7 @@ class PerformanceConfig:
|
|
|
91
91
|
def get_workers(self) -> int:
|
|
92
92
|
"""Get effective worker count (auto-detect if set to 0)."""
|
|
93
93
|
if self.parallel_workers <= 0:
|
|
94
|
-
return _get_optimal_workers(default=4)
|
|
94
|
+
return _get_optimal_workers(default=4, max_per_gb=2.0)
|
|
95
95
|
return self.parallel_workers
|
|
96
96
|
|
|
97
97
|
def apply_fast_mode(self) -> None:
|
|
@@ -66,6 +66,15 @@ _SKIP_DIR_NAMES = (
|
|
|
66
66
|
".serverless",
|
|
67
67
|
".netlify",
|
|
68
68
|
".vercel",
|
|
69
|
+
# Additional heavy directories and vendor bundles
|
|
70
|
+
"c2004",
|
|
71
|
+
"c2004-git-backup",
|
|
72
|
+
"workshop",
|
|
73
|
+
"managed_components",
|
|
74
|
+
"vendor",
|
|
75
|
+
".subactor",
|
|
76
|
+
".worktrees",
|
|
77
|
+
".planfile",
|
|
69
78
|
}
|
|
70
79
|
)
|
|
71
80
|
| ARCHIVE_DIR_NAMES
|
|
@@ -83,13 +92,31 @@ class FastFileFilter:
|
|
|
83
92
|
# Split patterns into fast substring checks vs pre-compiled regex
|
|
84
93
|
self._simple_excludes = []
|
|
85
94
|
self._regex_excludes = []
|
|
95
|
+
self._simple_dir_excludes: set[str] = set()
|
|
96
|
+
wildcard_patterns = []
|
|
86
97
|
for p in config.exclude_patterns:
|
|
87
98
|
p_lower = p.lower()
|
|
99
|
+
p_dir = p_lower.removesuffix("/**").removesuffix("/*").rstrip("/")
|
|
100
|
+
if p_dir and not any(c in p_dir for c in ("*", "?", "[")):
|
|
101
|
+
self._simple_dir_excludes.add(p_dir)
|
|
102
|
+
if "/" in p_dir:
|
|
103
|
+
self._simple_dir_excludes.add(p_dir.split("/")[-1])
|
|
88
104
|
if any(c in p_lower for c in ("*", "?", "[")):
|
|
89
105
|
self._regex_excludes.append(re.compile(fnmatch.translate(p_lower)))
|
|
106
|
+
wildcard_patterns.append(p_lower)
|
|
90
107
|
else:
|
|
91
108
|
self._simple_excludes.append(p_lower)
|
|
92
109
|
|
|
110
|
+
if wildcard_patterns:
|
|
111
|
+
try:
|
|
112
|
+
self._combined_regex_excludes = re.compile(
|
|
113
|
+
"|".join(f"(?:{fnmatch.translate(p)})" for p in wildcard_patterns)
|
|
114
|
+
)
|
|
115
|
+
except Exception:
|
|
116
|
+
self._combined_regex_excludes = None
|
|
117
|
+
else:
|
|
118
|
+
self._combined_regex_excludes = None
|
|
119
|
+
|
|
93
120
|
self._simple_includes = []
|
|
94
121
|
self._regex_includes = []
|
|
95
122
|
for p in config.include_patterns:
|
|
@@ -107,7 +134,11 @@ class FastFileFilter:
|
|
|
107
134
|
def should_skip_dir(self, dirname: str) -> bool:
|
|
108
135
|
"""Fast O(1) check: skip this directory entirely during tree walk?"""
|
|
109
136
|
lower = dirname.lower()
|
|
110
|
-
return
|
|
137
|
+
return (
|
|
138
|
+
lower.startswith(".")
|
|
139
|
+
or lower in _SKIP_DIR_NAMES
|
|
140
|
+
or lower in self._simple_dir_excludes
|
|
141
|
+
)
|
|
111
142
|
|
|
112
143
|
def _passes_gitignore(self, file_path: str) -> bool:
|
|
113
144
|
"""Check if file passes gitignore patterns (True = pass, False = excluded)."""
|
|
@@ -115,17 +146,32 @@ class FastFileFilter:
|
|
|
115
146
|
return True
|
|
116
147
|
return not self._gitignore_parser.is_ignored(Path(file_path), self.project_path)
|
|
117
148
|
|
|
118
|
-
def _passes_excludes(
|
|
149
|
+
def _passes_excludes(
|
|
150
|
+
self, path_lower: str, basename_lower: str, rel_lower: str | None = None
|
|
151
|
+
) -> bool:
|
|
119
152
|
"""Check if file passes exclude patterns (True = pass, False = excluded)."""
|
|
120
153
|
# Fast substring excludes
|
|
121
154
|
for pattern in self._simple_excludes:
|
|
122
|
-
if pattern in path_lower:
|
|
155
|
+
if pattern in path_lower or (rel_lower and pattern in rel_lower):
|
|
123
156
|
return False
|
|
124
157
|
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
if
|
|
158
|
+
# Fast combined regex check
|
|
159
|
+
if self._combined_regex_excludes is not None:
|
|
160
|
+
if (
|
|
161
|
+
self._combined_regex_excludes.match(path_lower)
|
|
162
|
+
or self._combined_regex_excludes.match(basename_lower)
|
|
163
|
+
or (rel_lower and self._combined_regex_excludes.match(rel_lower))
|
|
164
|
+
):
|
|
128
165
|
return False
|
|
166
|
+
else:
|
|
167
|
+
# Pre-compiled wildcard excludes
|
|
168
|
+
for regex in self._regex_excludes:
|
|
169
|
+
if (
|
|
170
|
+
regex.match(path_lower)
|
|
171
|
+
or regex.match(basename_lower)
|
|
172
|
+
or (rel_lower and regex.match(rel_lower))
|
|
173
|
+
):
|
|
174
|
+
return False
|
|
129
175
|
|
|
130
176
|
return True
|
|
131
177
|
|
|
@@ -146,11 +192,20 @@ class FastFileFilter:
|
|
|
146
192
|
if is_generated_artifact(file_path, self.project_path):
|
|
147
193
|
return False
|
|
148
194
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
195
|
+
if not self._passes_gitignore(file_path):
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
rel_lower = None
|
|
199
|
+
if self.project_path:
|
|
200
|
+
try:
|
|
201
|
+
rel_lower = str(Path(file_path).relative_to(self.project_path)).lower()
|
|
202
|
+
except ValueError:
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
if not self._passes_excludes(path_lower, basename_lower, rel_lower):
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
return self._passes_includes(path_lower)
|
|
154
209
|
|
|
155
210
|
def _passes_line_count(self, line_count: int) -> bool:
|
|
156
211
|
"""Check if function passes line count threshold."""
|