code2llm 0.5.121__tar.gz → 0.5.122__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.121 → code2llm-0.5.122}/PKG-INFO +2 -2
- {code2llm-0.5.121 → code2llm-0.5.122}/README.md +1 -1
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/__init__.py +1 -1
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/cli_exports/orchestrator.py +99 -1
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/analyzer.py +49 -50
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/config.py +45 -7
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/file_analyzer.py +2 -6
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/gitignore.py +1 -1
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/__init__.py +1 -3
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/models.py +0 -1
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/refactoring.py +0 -1
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/generators/mermaid.py +26 -2
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/nlp/__init__.py +1 -1
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm.egg-info/PKG-INFO +2 -2
- {code2llm-0.5.121 → code2llm-0.5.122}/pyproject.toml +1 -1
- {code2llm-0.5.121 → code2llm-0.5.122}/setup.py +1 -1
- {code2llm-0.5.121 → code2llm-0.5.122}/LICENSE +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/__main__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/pipeline_classifier.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/pipeline_detector.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/pipeline_resolver.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/type_inference.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/utils/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/analysis/utils/ast_helpers.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/api.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/cli.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/cli_analysis.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/cli_commands.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/cli_exports/code2logic.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/cli_exports/formats.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/cli_exports/prompt.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/cli_parser.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/ast_registry.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/export_pipeline.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/file_cache.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/file_filter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/incremental.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/base.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/cpp.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/csharp.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/generic.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/go_lang.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/java.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/php.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/ruby.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/rust.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/ts_extractors.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/ts_parser.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/lang/typescript.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/large_repo.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/persistent_cache.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/repo_files.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/dashboard_data.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/dashboard_renderer.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/flow_constants.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/index_generator/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/index_generator/renderer.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/index_generator/scanner.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/index_generator.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/project_yaml/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/project_yaml/constants.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/project_yaml/core.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/project_yaml/evolution.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/project_yaml/health.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/project_yaml/hotspots.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/project_yaml/modules.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/project_yaml_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/toon/helpers.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/toon/metrics_core.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/toon/metrics_duplicates.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/toon/metrics_health.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/generators/_utils.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/generators/llm_task.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm.egg-info/SOURCES.txt +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm.egg-info/requires.txt +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/code2llm.egg-info/top_level.txt +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/setup.cfg +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_calls_toon_export.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_persistent_cache.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_project_toon_export.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_prompt_txt.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.121 → code2llm-0.5.122}/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.122
|
|
4
4
|
Summary: High-performance Python code flow analysis with optimized TOON format - CFG, DFG, call graphs, and intelligent code queries
|
|
5
5
|
Home-page: https://github.com/wronai/stts
|
|
6
6
|
Author: STTS Project
|
|
@@ -67,7 +67,7 @@ Dynamic: requires-python
|
|
|
67
67
|
|
|
68
68
|
## AI Cost Tracking
|
|
69
69
|
|
|
70
|
-
    
|
|
71
71
|
  
|
|
72
72
|
|
|
73
73
|
- 🤖 **LLM usage:** $7.5000 (166 commits)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
7
|
  
|
|
8
8
|
|
|
9
9
|
- 🤖 **LLM usage:** $7.5000 (166 commits)
|
|
@@ -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.122"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -4,10 +4,18 @@ Refactored to use EXPORT_REGISTRY for core format dispatch.
|
|
|
4
4
|
Maintains backward compatibility with all existing --format values.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import shutil
|
|
7
8
|
import sys
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Optional, List, Dict, Any
|
|
10
11
|
|
|
12
|
+
# Optional progress bar support
|
|
13
|
+
try:
|
|
14
|
+
from tqdm import tqdm
|
|
15
|
+
_HAS_TQDM = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
_HAS_TQDM = False
|
|
18
|
+
|
|
11
19
|
from code2llm.exporters import (
|
|
12
20
|
get_exporter,
|
|
13
21
|
EXPORT_REGISTRY,
|
|
@@ -17,6 +25,8 @@ from code2llm.exporters import (
|
|
|
17
25
|
IndexHTMLGenerator,
|
|
18
26
|
)
|
|
19
27
|
from code2llm.exporters.project_yaml.evolution import load_previous_evolution
|
|
28
|
+
from code2llm.core.persistent_cache import PersistentCache
|
|
29
|
+
from code2llm.core.config import DEFAULT_PROGRESS_BAR_THRESHOLD
|
|
20
30
|
|
|
21
31
|
|
|
22
32
|
# Format output filenames
|
|
@@ -46,26 +56,103 @@ FORMAT_LABELS: Dict[str, str] = {
|
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
|
|
59
|
+
def _build_export_config(args, formats: List[str]) -> Dict[str, Any]:
|
|
60
|
+
"""Build config dict for export caching."""
|
|
61
|
+
return {
|
|
62
|
+
'formats': sorted(formats),
|
|
63
|
+
'png': getattr(args, 'png', False),
|
|
64
|
+
'no_png': getattr(args, 'no_png', False),
|
|
65
|
+
'flow_include_examples': getattr(args, 'flow_include_examples', False),
|
|
66
|
+
'full': getattr(args, 'full', False),
|
|
67
|
+
'refactor': getattr(args, 'refactor', False),
|
|
68
|
+
'data_structures': getattr(args, 'data_structures', False),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
49
72
|
def _run_exports(args, result, output_dir: Path, source_path: Optional[Path] = None):
|
|
50
73
|
"""Export analysis results in requested formats.
|
|
51
74
|
|
|
52
75
|
Uses EXPORT_REGISTRY for core format dispatch.
|
|
53
76
|
For chunked analysis, exports to subproject subdirectories.
|
|
77
|
+
Supports export-level caching for repeated runs.
|
|
54
78
|
"""
|
|
55
79
|
requested_formats = [f.strip() for f in args.format.split(',')]
|
|
56
80
|
formats = _expand_all_formats(requested_formats, getattr(args, 'png', False))
|
|
57
81
|
is_chunked = getattr(args, 'chunk', False)
|
|
58
82
|
|
|
83
|
+
# Skip cache for chunked or when explicitly disabled
|
|
84
|
+
skip_cache = is_chunked or getattr(args, 'no_cache', False)
|
|
85
|
+
|
|
86
|
+
if not skip_cache and source_path:
|
|
87
|
+
cache = PersistentCache(str(source_path))
|
|
88
|
+
config_dict = _build_export_config(args, formats)
|
|
89
|
+
cached_export_dir = cache.get_export_cache_dir(config_dict)
|
|
90
|
+
|
|
91
|
+
if cached_export_dir:
|
|
92
|
+
if args.verbose:
|
|
93
|
+
print(f" Using cached export from: {cached_export_dir}")
|
|
94
|
+
# Copy cached files to output_dir
|
|
95
|
+
_copy_cached_export(cached_export_dir, output_dir, verbose=args.verbose)
|
|
96
|
+
return
|
|
97
|
+
|
|
59
98
|
try:
|
|
60
99
|
if is_chunked and source_path:
|
|
61
100
|
_export_chunked(args, result, output_dir, source_path, formats, requested_formats)
|
|
62
101
|
else:
|
|
63
102
|
_export_single(args, result, output_dir, formats, requested_formats, source_path)
|
|
103
|
+
|
|
104
|
+
# Mark export as complete in cache
|
|
105
|
+
if not skip_cache and source_path:
|
|
106
|
+
cache = PersistentCache(str(source_path))
|
|
107
|
+
config_dict = _build_export_config(args, formats)
|
|
108
|
+
export_cache_dir = cache.create_export_cache_dir(config_dict)
|
|
109
|
+
_copy_to_cache(output_dir, export_cache_dir, verbose=args.verbose)
|
|
110
|
+
cache.mark_export_complete(export_cache_dir)
|
|
111
|
+
cache.save()
|
|
112
|
+
if args.verbose:
|
|
113
|
+
print(f" Export cached at: {export_cache_dir}")
|
|
114
|
+
|
|
64
115
|
except Exception as e:
|
|
65
116
|
print(f"Error during export: {e}", file=sys.stderr)
|
|
66
117
|
sys.exit(1)
|
|
67
118
|
|
|
68
119
|
|
|
120
|
+
def _copy_cached_export(cached_dir: Path, output_dir: Path, verbose: bool = False) -> None:
|
|
121
|
+
"""Copy files from cached export to output directory."""
|
|
122
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
items = [item for item in cached_dir.iterdir() if item.name != '_complete']
|
|
124
|
+
|
|
125
|
+
# Progress bar for large cache restores
|
|
126
|
+
use_tqdm = _HAS_TQDM and not verbose and len(items) > DEFAULT_PROGRESS_BAR_THRESHOLD
|
|
127
|
+
item_iterator = tqdm(items, desc="Restoring from cache") if use_tqdm else items
|
|
128
|
+
|
|
129
|
+
for item in item_iterator:
|
|
130
|
+
dest = output_dir / item.name
|
|
131
|
+
if item.is_dir():
|
|
132
|
+
shutil.copytree(item, dest, dirs_exist_ok=True)
|
|
133
|
+
else:
|
|
134
|
+
shutil.copy2(item, dest)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _copy_to_cache(output_dir: Path, cache_dir: Path, verbose: bool = False) -> None:
|
|
138
|
+
"""Copy export files to cache directory."""
|
|
139
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
if not output_dir.exists():
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
items = list(output_dir.iterdir())
|
|
144
|
+
# Progress bar for large cache saves
|
|
145
|
+
use_tqdm = _HAS_TQDM and not verbose and len(items) > DEFAULT_PROGRESS_BAR_THRESHOLD
|
|
146
|
+
item_iterator = tqdm(items, desc="Saving to cache") if use_tqdm else items
|
|
147
|
+
|
|
148
|
+
for item in item_iterator:
|
|
149
|
+
dest = cache_dir / item.name
|
|
150
|
+
if item.is_dir():
|
|
151
|
+
shutil.copytree(item, dest, dirs_exist_ok=True)
|
|
152
|
+
else:
|
|
153
|
+
shutil.copy2(item, dest)
|
|
154
|
+
|
|
155
|
+
|
|
69
156
|
def _expand_all_formats(requested: List[str], include_png: bool = False) -> List[str]:
|
|
70
157
|
"""Expand 'all' to concrete format list."""
|
|
71
158
|
if 'all' not in requested:
|
|
@@ -121,7 +208,18 @@ def _export_single(
|
|
|
121
208
|
|
|
122
209
|
def _export_registry_formats(args, result, output_dir: Path, formats: List[str]):
|
|
123
210
|
"""Export core formats via EXPORT_REGISTRY lookup."""
|
|
124
|
-
|
|
211
|
+
# Use progress bar when many formats and not in verbose mode
|
|
212
|
+
use_tqdm = (
|
|
213
|
+
_HAS_TQDM and
|
|
214
|
+
not args.verbose and
|
|
215
|
+
len(formats) > DEFAULT_PROGRESS_BAR_THRESHOLD
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
format_iterator = formats
|
|
219
|
+
if use_tqdm:
|
|
220
|
+
format_iterator = tqdm(formats, desc="Exporting formats")
|
|
221
|
+
|
|
222
|
+
for fmt in format_iterator:
|
|
125
223
|
exporter_cls = get_exporter(fmt)
|
|
126
224
|
if exporter_cls is None:
|
|
127
225
|
continue
|
|
@@ -7,9 +7,16 @@ from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Dict, List, Optional, Tuple
|
|
9
9
|
|
|
10
|
+
# Optional tqdm for progress bars
|
|
11
|
+
try:
|
|
12
|
+
from tqdm import tqdm
|
|
13
|
+
_HAS_TQDM = True
|
|
14
|
+
except ImportError:
|
|
15
|
+
_HAS_TQDM = False
|
|
16
|
+
|
|
10
17
|
logger = logging.getLogger(__name__)
|
|
11
18
|
|
|
12
|
-
from .config import Config, FAST_CONFIG, ALL_EXTENSIONS, LANGUAGE_EXTENSIONS
|
|
19
|
+
from .config import Config, FAST_CONFIG, ALL_EXTENSIONS, LANGUAGE_EXTENSIONS, DEFAULT_PROGRESS_BAR_THRESHOLD
|
|
13
20
|
from .models import AnalysisResult, FlowEdge, FlowNode, Pattern
|
|
14
21
|
from code2llm.analysis.call_graph import CallGraphExtractor
|
|
15
22
|
|
|
@@ -41,24 +48,15 @@ class ProjectAnalyzer:
|
|
|
41
48
|
|
|
42
49
|
if self.config.verbose:
|
|
43
50
|
print(f"Found {len(files)} files to analyze")
|
|
44
|
-
|
|
51
|
+
workers = self.config.performance.get_workers()
|
|
52
|
+
print(f" - Parallel: {self.config.performance.parallel_enabled}, Workers: {workers}")
|
|
45
53
|
|
|
46
54
|
pcache, cached_results, files_to_analyze = self._load_from_persistent_cache(files, project_path)
|
|
47
55
|
fresh_results = self._run_analysis(files_to_analyze)
|
|
48
56
|
self._store_to_persistent_cache(pcache, files_to_analyze, fresh_results)
|
|
49
57
|
|
|
50
58
|
merged = self._merge_results(cached_results + fresh_results, str(project_path))
|
|
51
|
-
self.
|
|
52
|
-
if not self.config.performance.skip_pattern_detection:
|
|
53
|
-
self._detect_patterns(merged)
|
|
54
|
-
if self.config.verbose:
|
|
55
|
-
print(f" - Running refactoring analysis...", flush=True)
|
|
56
|
-
self.refactoring_analyzer.perform_refactoring_analysis(merged)
|
|
57
|
-
if self.config.verbose:
|
|
58
|
-
print(f" - Refactoring analysis complete", flush=True)
|
|
59
|
-
merged.stats = self._build_stats(files, cached_results + fresh_results, merged, start_time)
|
|
60
|
-
if self.config.verbose:
|
|
61
|
-
self._print_summary(merged)
|
|
59
|
+
self._post_process(merged, files, cached_results + fresh_results, start_time)
|
|
62
60
|
return merged
|
|
63
61
|
|
|
64
62
|
def _resolve_project_path(self, project_path: str) -> Path:
|
|
@@ -146,6 +144,26 @@ class ProjectAnalyzer:
|
|
|
146
144
|
print(f" Classes: {len(merged.classes)}")
|
|
147
145
|
print(f" CFG Nodes: {len(merged.nodes)}")
|
|
148
146
|
print(f" Patterns: {len(merged.patterns)}")
|
|
147
|
+
|
|
148
|
+
def _post_process(
|
|
149
|
+
self,
|
|
150
|
+
merged: AnalysisResult,
|
|
151
|
+
files: List,
|
|
152
|
+
results: List[Dict],
|
|
153
|
+
start_time: float,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Run post-processing: call graph, patterns, refactoring, stats."""
|
|
156
|
+
self._build_call_graph(merged)
|
|
157
|
+
if not self.config.performance.skip_pattern_detection:
|
|
158
|
+
self._detect_patterns(merged)
|
|
159
|
+
if self.config.verbose:
|
|
160
|
+
print(" - Running refactoring analysis...", flush=True)
|
|
161
|
+
self.refactoring_analyzer.perform_refactoring_analysis(merged)
|
|
162
|
+
if self.config.verbose:
|
|
163
|
+
print(" - Refactoring analysis complete", flush=True)
|
|
164
|
+
merged.stats = self._build_stats(files, results, merged, start_time)
|
|
165
|
+
if self.config.verbose:
|
|
166
|
+
self._print_summary(merged)
|
|
149
167
|
|
|
150
168
|
def _collect_files(self, project_path: Path) -> List[Tuple[str, str]]:
|
|
151
169
|
"""Collect all source files with their module names for all supported languages.
|
|
@@ -197,7 +215,7 @@ class ProjectAnalyzer:
|
|
|
197
215
|
def _analyze_parallel(self, files: List[Tuple[str, str]]) -> List[Dict]:
|
|
198
216
|
"""Analyze files in parallel."""
|
|
199
217
|
results = []
|
|
200
|
-
workers = min(self.config.performance.
|
|
218
|
+
workers = min(self.config.performance.get_workers(), len(files))
|
|
201
219
|
|
|
202
220
|
# Convert config to dict for pickle compatibility
|
|
203
221
|
config_dict = {
|
|
@@ -215,9 +233,13 @@ class ProjectAnalyzer:
|
|
|
215
233
|
for file_path, module_name in files
|
|
216
234
|
}
|
|
217
235
|
|
|
218
|
-
# Collect results as they complete
|
|
236
|
+
# Collect results as they complete (with optional progress bar)
|
|
219
237
|
completed = 0
|
|
220
|
-
|
|
238
|
+
iterator = as_completed(future_to_file)
|
|
239
|
+
if not self.config.verbose and len(files) > DEFAULT_PROGRESS_BAR_THRESHOLD and _HAS_TQDM:
|
|
240
|
+
iterator = tqdm(iterator, total=len(files), desc="Analyzing")
|
|
241
|
+
|
|
242
|
+
for future in iterator:
|
|
221
243
|
file_path, module_name = future_to_file[future]
|
|
222
244
|
try:
|
|
223
245
|
result = future.result()
|
|
@@ -238,7 +260,12 @@ class ProjectAnalyzer:
|
|
|
238
260
|
analyzer = FileAnalyzer(self.config, self.cache)
|
|
239
261
|
total = len(files)
|
|
240
262
|
|
|
241
|
-
for
|
|
263
|
+
# Use tqdm for large projects in non-verbose mode
|
|
264
|
+
file_iterator = enumerate(files, 1)
|
|
265
|
+
if not self.config.verbose and total > DEFAULT_PROGRESS_BAR_THRESHOLD and _HAS_TQDM:
|
|
266
|
+
file_iterator = tqdm(list(file_iterator), desc="Analyzing", total=total)
|
|
267
|
+
|
|
268
|
+
for i, (file_path, module_name) in file_iterator:
|
|
242
269
|
try:
|
|
243
270
|
result = analyzer.analyze_file(file_path, module_name)
|
|
244
271
|
if result:
|
|
@@ -355,47 +382,19 @@ class ProjectAnalyzer:
|
|
|
355
382
|
|
|
356
383
|
def analyze_files(self, files: List[Tuple[str, str]], project_path: str) -> AnalysisResult:
|
|
357
384
|
"""Analyze specific list of files (for chunked analysis).
|
|
358
|
-
|
|
385
|
+
|
|
359
386
|
Args:
|
|
360
387
|
files: List of (file_path, module_name) tuples
|
|
361
388
|
project_path: Base project path for the result
|
|
362
389
|
"""
|
|
363
390
|
start_time = time.time()
|
|
364
|
-
|
|
391
|
+
|
|
365
392
|
if self.config.verbose:
|
|
366
393
|
print(f"Analyzing {len(files)} specific files")
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if self.config.performance.parallel_enabled and len(files) > 1:
|
|
370
|
-
results = self._analyze_parallel(files)
|
|
371
|
-
else:
|
|
372
|
-
results = self._analyze_sequential(files)
|
|
373
|
-
|
|
374
|
-
# Merge results
|
|
394
|
+
|
|
395
|
+
results = self._run_analysis(files)
|
|
375
396
|
merged = self._merge_results(results, project_path)
|
|
376
|
-
|
|
377
|
-
# Build call graph
|
|
378
|
-
self._build_call_graph(merged)
|
|
379
|
-
|
|
380
|
-
if not self.config.performance.skip_pattern_detection:
|
|
381
|
-
self._detect_patterns(merged)
|
|
382
|
-
|
|
383
|
-
# Refactoring analysis
|
|
384
|
-
self.refactoring_analyzer.perform_refactoring_analysis(merged)
|
|
385
|
-
|
|
386
|
-
# Calculate stats
|
|
387
|
-
elapsed = time.time() - start_time
|
|
388
|
-
merged.stats = {
|
|
389
|
-
'files_processed': len(files),
|
|
390
|
-
'functions_found': len(merged.functions),
|
|
391
|
-
'classes_found': len(merged.classes),
|
|
392
|
-
'nodes_created': len(merged.nodes),
|
|
393
|
-
'edges_created': len(merged.edges),
|
|
394
|
-
'patterns_detected': len(merged.patterns),
|
|
395
|
-
'analysis_time_seconds': round(elapsed, 2),
|
|
396
|
-
'cache_hits': sum(r.get('cache_hits', 0) for r in results),
|
|
397
|
-
}
|
|
398
|
-
|
|
397
|
+
self._post_process(merged, files, results, start_time)
|
|
399
398
|
return merged
|
|
400
399
|
|
|
401
400
|
def _detect_patterns(self, result: AnalysisResult) -> None:
|
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
"""Configuration and constants for code2llm."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
import psutil
|
|
3
5
|
from dataclasses import dataclass, field
|
|
4
6
|
from typing import List, Set
|
|
5
7
|
from enum import Enum
|
|
6
8
|
|
|
7
9
|
|
|
10
|
+
def _get_optimal_workers(default: int = 4, max_per_gb: float = 2.0) -> int:
|
|
11
|
+
"""Calculate optimal parallel workers based on CPU and available RAM.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
default: Default workers if detection fails
|
|
15
|
+
max_per_gb: Max workers per GB of RAM
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Optimal worker count (at least 1)
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
cpu_count = os.cpu_count() or default
|
|
22
|
+
available_ram_gb = psutil.virtual_memory().available / (1024 ** 3)
|
|
23
|
+
# Limit workers by RAM (assume each worker needs ~500MB)
|
|
24
|
+
ram_limited = int(available_ram_gb * max_per_gb)
|
|
25
|
+
# Take minimum of CPU and RAM limits, but at least 1
|
|
26
|
+
return max(1, min(cpu_count, ram_limited))
|
|
27
|
+
except Exception:
|
|
28
|
+
return default
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Performance limits (named constants for magic numbers)
|
|
32
|
+
DEFAULT_MAX_NODES_PER_FILE = 1000
|
|
33
|
+
DEFAULT_MAX_TOTAL_NODES = 10000
|
|
34
|
+
DEFAULT_MAX_EDGES = 50000
|
|
35
|
+
DEFAULT_CACHE_TTL_HOURS = 24
|
|
36
|
+
DEFAULT_MAX_MEMORY_MB = 2048
|
|
37
|
+
DEFAULT_PROGRESS_BAR_THRESHOLD = 50 # File count threshold for progress bar
|
|
38
|
+
|
|
39
|
+
|
|
8
40
|
class AnalysisMode(str, Enum):
|
|
9
41
|
"""Available analysis modes."""
|
|
10
42
|
STATIC = "static"
|
|
@@ -19,17 +51,23 @@ class PerformanceConfig:
|
|
|
19
51
|
"""Performance optimization settings."""
|
|
20
52
|
enable_cache: bool = True
|
|
21
53
|
cache_dir: str = ".code2llm_cache"
|
|
22
|
-
cache_ttl_hours: int =
|
|
23
|
-
parallel_workers: int =
|
|
54
|
+
cache_ttl_hours: int = DEFAULT_CACHE_TTL_HOURS
|
|
55
|
+
parallel_workers: int = 0 # 0 = auto-detect based on CPU/RAM
|
|
24
56
|
parallel_enabled: bool = True
|
|
25
|
-
max_memory_mb: int =
|
|
26
|
-
max_nodes_per_file: int =
|
|
27
|
-
max_total_nodes: int =
|
|
28
|
-
max_edges: int =
|
|
57
|
+
max_memory_mb: int = DEFAULT_MAX_MEMORY_MB
|
|
58
|
+
max_nodes_per_file: int = DEFAULT_MAX_NODES_PER_FILE
|
|
59
|
+
max_total_nodes: int = DEFAULT_MAX_TOTAL_NODES
|
|
60
|
+
max_edges: int = DEFAULT_MAX_EDGES
|
|
29
61
|
fast_mode: bool = False
|
|
30
62
|
skip_data_flow: bool = False
|
|
31
63
|
skip_pattern_detection: bool = False
|
|
32
64
|
|
|
65
|
+
def get_workers(self) -> int:
|
|
66
|
+
"""Get effective worker count (auto-detect if set to 0)."""
|
|
67
|
+
if self.parallel_workers <= 0:
|
|
68
|
+
return _get_optimal_workers(default=4)
|
|
69
|
+
return self.parallel_workers
|
|
70
|
+
|
|
33
71
|
|
|
34
72
|
@dataclass
|
|
35
73
|
class FilterConfig:
|
|
@@ -120,7 +158,7 @@ FAST_CONFIG = Config(
|
|
|
120
158
|
skip_data_flow=True,
|
|
121
159
|
skip_pattern_detection=True,
|
|
122
160
|
parallel_enabled=True,
|
|
123
|
-
parallel_workers=
|
|
161
|
+
parallel_workers=0, # auto-detect
|
|
124
162
|
max_nodes_per_file=500,
|
|
125
163
|
max_total_nodes=5000,
|
|
126
164
|
),
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
"""File analyzer for analyzing individual source files across multiple languages."""
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
|
-
import re
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
from typing import Dict, List, Optional
|
|
7
6
|
|
|
8
7
|
from radon.complexity import cc_visit, cc_rank
|
|
9
8
|
|
|
10
|
-
from .config import Config
|
|
11
|
-
from .models import
|
|
12
|
-
AnalysisResult, ClassInfo, FlowEdge, FlowNode,
|
|
13
|
-
FunctionInfo, ModuleInfo
|
|
14
|
-
)
|
|
9
|
+
from .config import Config
|
|
10
|
+
from .models import ClassInfo, FlowEdge, FlowNode, FunctionInfo, ModuleInfo
|
|
15
11
|
from code2llm.analysis.dfg import DFGExtractor
|
|
16
12
|
from code2llm.analysis.call_graph import CallGraphExtractor
|
|
17
13
|
from .file_filter import FastFileFilter
|
|
@@ -8,11 +8,9 @@ Provides:
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from abc import ABC, abstractmethod
|
|
11
|
-
from typing import Dict, Any, Callable,
|
|
11
|
+
from typing import Dict, Any, Callable, Optional
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
from code2llm.core.models import ModuleInfo, FunctionInfo, ClassInfo
|
|
15
|
-
|
|
16
14
|
|
|
17
15
|
# Type alias for parser results
|
|
18
16
|
ParserResult = Dict[str, Any]
|
|
@@ -254,9 +254,23 @@ def _fix_class_line(line: str):
|
|
|
254
254
|
return None
|
|
255
255
|
|
|
256
256
|
|
|
257
|
+
def _is_png_fresh(mmd_file: Path, output_dir: Path) -> bool:
|
|
258
|
+
"""Check if PNG exists and is newer than MMD source."""
|
|
259
|
+
png_file = output_dir / f"{mmd_file.stem}.png"
|
|
260
|
+
if not png_file.exists():
|
|
261
|
+
return False
|
|
262
|
+
# PNG is fresh if newer than MMD
|
|
263
|
+
return png_file.stat().st_mtime >= mmd_file.stat().st_mtime
|
|
264
|
+
|
|
265
|
+
|
|
257
266
|
def _prepare_and_render(mmd_file: Path, output_dir: Path, timeout: int) -> bool:
|
|
258
267
|
"""Validate, optionally fix, then render a single .mmd file to PNG."""
|
|
259
268
|
output_file = output_dir / f"{mmd_file.stem}.png"
|
|
269
|
+
|
|
270
|
+
# Skip if PNG is already fresh
|
|
271
|
+
if _is_png_fresh(mmd_file, output_dir):
|
|
272
|
+
return True
|
|
273
|
+
|
|
260
274
|
errors = validate_mermaid_file(mmd_file)
|
|
261
275
|
if errors:
|
|
262
276
|
print(f" Fixing {mmd_file.name}: {len(errors)} issues")
|
|
@@ -268,12 +282,22 @@ def _prepare_and_render(mmd_file: Path, output_dir: Path, timeout: int) -> bool:
|
|
|
268
282
|
return generate_single_png(mmd_file, output_file, timeout)
|
|
269
283
|
|
|
270
284
|
|
|
271
|
-
def generate_pngs(
|
|
272
|
-
|
|
285
|
+
def generate_pngs(
|
|
286
|
+
input_dir: Path, output_dir: Path, timeout: int = 60, max_workers: int = 0
|
|
287
|
+
) -> int:
|
|
288
|
+
"""Generate PNG files from all .mmd files in input_dir (parallel).
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
max_workers: Number of parallel workers (0 = auto-detect from CPU count)
|
|
292
|
+
"""
|
|
273
293
|
mmd_files = list(input_dir.glob('*.mmd'))
|
|
274
294
|
if not mmd_files:
|
|
275
295
|
return 0
|
|
276
296
|
|
|
297
|
+
# Auto-detect workers if not specified
|
|
298
|
+
if max_workers <= 0:
|
|
299
|
+
max_workers = os.cpu_count() or 4
|
|
300
|
+
|
|
277
301
|
success_count = 0
|
|
278
302
|
with ThreadPoolExecutor(max_workers=min(max_workers, len(mmd_files))) as pool:
|
|
279
303
|
futures = {pool.submit(_prepare_and_render, f, output_dir, timeout): f for f in mmd_files}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code2llm
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.122
|
|
4
4
|
Summary: High-performance Python code flow analysis with optimized TOON format - CFG, DFG, call graphs, and intelligent code queries
|
|
5
5
|
Home-page: https://github.com/wronai/stts
|
|
6
6
|
Author: STTS Project
|
|
@@ -67,7 +67,7 @@ Dynamic: requires-python
|
|
|
67
67
|
|
|
68
68
|
## AI Cost Tracking
|
|
69
69
|
|
|
70
|
-
    
|
|
71
71
|
  
|
|
72
72
|
|
|
73
73
|
- 🤖 **LLM usage:** $7.5000 (166 commits)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code2llm"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.122"
|
|
8
8
|
description = "High-performance Python code flow analysis with optimized TOON format - CFG, DFG, call graphs, and intelligent code queries"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|