code2llm 0.5.122__tar.gz → 0.5.124__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.122 → code2llm-0.5.124}/PKG-INFO +2 -2
- {code2llm-0.5.122 → code2llm-0.5.124}/README.md +1 -1
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/__init__.py +1 -1
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/cli_analysis.py +4 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/cli_commands.py +6 -5
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/cli_exports/orchestrator.py +53 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/cli_parser.py +12 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/analyzer.py +10 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/config.py +20 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/base.py +11 -1
- code2llm-0.5.124/code2llm/exporters/report_generators.py +76 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/generators/llm_flow.py +35 -4
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/generators/llm_task.py +35 -3
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/generators/mermaid.py +15 -9
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/nlp/__init__.py +1 -1
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm.egg-info/PKG-INFO +2 -2
- {code2llm-0.5.122 → code2llm-0.5.124}/pyproject.toml +1 -1
- {code2llm-0.5.122 → code2llm-0.5.124}/setup.py +1 -1
- code2llm-0.5.122/code2llm/exporters/report_generators.py +0 -34
- {code2llm-0.5.122 → code2llm-0.5.124}/LICENSE +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/__main__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/pipeline_classifier.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/pipeline_detector.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/pipeline_resolver.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/type_inference.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/utils/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/analysis/utils/ast_helpers.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/api.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/cli.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/cli_exports/code2logic.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/cli_exports/formats.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/cli_exports/prompt.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/ast_registry.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/export_pipeline.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/file_analyzer.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/file_cache.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/file_filter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/gitignore.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/incremental.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/cpp.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/csharp.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/generic.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/go_lang.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/java.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/php.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/ruby.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/rust.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/ts_extractors.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/ts_parser.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/lang/typescript.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/large_repo.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/models.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/persistent_cache.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/refactoring.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/repo_files.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/dashboard_data.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/dashboard_renderer.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/flow_constants.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/index_generator/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/index_generator/renderer.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/index_generator/scanner.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/index_generator.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/project_yaml/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/project_yaml/constants.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/project_yaml/core.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/project_yaml/evolution.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/project_yaml/health.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/project_yaml/hotspots.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/project_yaml/modules.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/project_yaml_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/toon/helpers.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/toon/metrics_core.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/toon/metrics_duplicates.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/toon/metrics_health.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/generators/_utils.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm.egg-info/SOURCES.txt +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm.egg-info/requires.txt +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/code2llm.egg-info/top_level.txt +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/setup.cfg +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_calls_toon_export.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_persistent_cache.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_project_toon_export.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_prompt_txt.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.122 → code2llm-0.5.124}/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.124
|
|
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.124"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -84,6 +84,10 @@ def _build_config(args, output_dir: Path):
|
|
|
84
84
|
# Persistent cache flags (read via getattr with defaults in analyzer.py)
|
|
85
85
|
no_cache = getattr(args, 'no_cache', False) or getattr(args, 'force', False)
|
|
86
86
|
config.no_cache = no_cache
|
|
87
|
+
# Watch mode for auto-detecting changed files
|
|
88
|
+
config.watch = getattr(args, 'watch', False)
|
|
89
|
+
# Dry-run mode (handled in orchestrator, but stored for reference)
|
|
90
|
+
config.dry_run = getattr(args, 'dry_run', False)
|
|
87
91
|
return config
|
|
88
92
|
|
|
89
93
|
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
8
|
from .cli_exports import _run_report
|
|
9
|
+
from .core.config import DEFAULT_CACHE_MAX_AGE_DAYS, KB
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def handle_special_commands() -> Optional[int]:
|
|
@@ -39,18 +40,18 @@ def handle_cache_command(args_list) -> int:
|
|
|
39
40
|
parser.add_argument('action', choices=['status', 'clear', 'gc'], help='Cache action')
|
|
40
41
|
parser.add_argument('--all', action='store_true', dest='all_projects',
|
|
41
42
|
help='Apply to all cached projects (clear only)')
|
|
42
|
-
parser.add_argument('--max-age', type=int, default=
|
|
43
|
-
help='Max age in days for gc (default:
|
|
43
|
+
parser.add_argument('--max-age', type=int, default=DEFAULT_CACHE_MAX_AGE_DAYS, metavar='DAYS',
|
|
44
|
+
help=f'Max age in days for gc (default: {DEFAULT_CACHE_MAX_AGE_DAYS})')
|
|
44
45
|
args = parser.parse_args(args_list)
|
|
45
46
|
|
|
46
47
|
if args.action == 'status':
|
|
47
48
|
projects = get_all_projects()
|
|
48
49
|
root = _DEFAULT_ROOT
|
|
49
|
-
total_mb = sum(p.get('cache_size_bytes', 0) for p in projects) / (
|
|
50
|
+
total_mb = sum(p.get('cache_size_bytes', 0) for p in projects) / (KB * KB)
|
|
50
51
|
print(f"Cache: {root}")
|
|
51
52
|
print(f" Projects: {len(projects)} Total: {total_mb:.1f} MB")
|
|
52
53
|
for p in projects:
|
|
53
|
-
size_mb = p.get('cache_size_bytes', 0) / (
|
|
54
|
+
size_mb = p.get('cache_size_bytes', 0) / (KB * KB)
|
|
54
55
|
updated = p.get('updated_at', 0)
|
|
55
56
|
age_min = int((time.time() - updated) / 60) if updated else 0
|
|
56
57
|
age_str = f"{age_min}m ago" if age_min < 120 else f"{age_min//60}h ago"
|
|
@@ -246,7 +247,7 @@ def _get_file_sizes(chunk_dir: Path, required_files: list[str]) -> str:
|
|
|
246
247
|
sizes = []
|
|
247
248
|
for req_file in required_files:
|
|
248
249
|
size = (chunk_dir / req_file).stat().st_size
|
|
249
|
-
sizes.append(f"{req_file}:{size//
|
|
250
|
+
sizes.append(f"{req_file}:{size//KB}KB" if size > KB else f"{req_file}:{size}B")
|
|
250
251
|
return ", ".join(sizes)
|
|
251
252
|
|
|
252
253
|
|
|
@@ -42,6 +42,18 @@ FORMAT_FILENAMES: Dict[str, str] = {
|
|
|
42
42
|
'project-yaml': 'project.yaml',
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
# Files produced per format in dry-run preview
|
|
46
|
+
FORMAT_DRY_RUN_FILES: Dict[str, List[str]] = {
|
|
47
|
+
'toon': ['analysis.toon'],
|
|
48
|
+
'map': ['map.toon.yaml'],
|
|
49
|
+
'evolution': ['evolution.toon.yaml'],
|
|
50
|
+
'context': ['context.md'],
|
|
51
|
+
'mermaid': ['calls.mmd', 'calls.png'],
|
|
52
|
+
'yaml': ['analysis.yaml'],
|
|
53
|
+
'json': ['analysis.json'],
|
|
54
|
+
'readme': ['README.md'],
|
|
55
|
+
}
|
|
56
|
+
|
|
45
57
|
# Human-readable labels
|
|
46
58
|
FORMAT_LABELS: Dict[str, str] = {
|
|
47
59
|
'toon': 'TOON (diagnostics)',
|
|
@@ -69,6 +81,41 @@ def _build_export_config(args, formats: List[str]) -> Dict[str, Any]:
|
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
|
|
84
|
+
def _collect_dry_run_files(formats: List[str], output_dir: Path) -> List[Path]:
|
|
85
|
+
"""Return the list of files that would be written for the given formats."""
|
|
86
|
+
output_files: List[Path] = []
|
|
87
|
+
for fmt in formats:
|
|
88
|
+
for name in FORMAT_DRY_RUN_FILES.get(fmt, []):
|
|
89
|
+
output_files.append(output_dir / name)
|
|
90
|
+
output_files.append(output_dir / 'project.toon.yaml')
|
|
91
|
+
output_files.append(output_dir / 'prompt.txt')
|
|
92
|
+
return output_files
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _show_dry_run_plan(formats: List[str], output_dir: Path, is_chunked: bool, result) -> None:
|
|
96
|
+
"""Display what would be exported in dry-run mode."""
|
|
97
|
+
print("\n📋 DRY-RUN: Would export the following:\n")
|
|
98
|
+
|
|
99
|
+
output_files = _collect_dry_run_files(formats, output_dir)
|
|
100
|
+
|
|
101
|
+
size_hint = ""
|
|
102
|
+
func_count = len(getattr(result, 'functions', []))
|
|
103
|
+
if func_count > 0:
|
|
104
|
+
size_hint = f" (~{func_count * 50 // 1024}KB est.)"
|
|
105
|
+
|
|
106
|
+
for f in sorted(set(output_files)):
|
|
107
|
+
print(f" 📄 {f}{size_hint}")
|
|
108
|
+
|
|
109
|
+
stats = getattr(result, 'stats', {})
|
|
110
|
+
if stats:
|
|
111
|
+
print(f"\n📊 Based on analysis:")
|
|
112
|
+
print(f" - Functions: {stats.get('functions_found', 'N/A')}")
|
|
113
|
+
print(f" - Classes: {stats.get('classes_found', 'N/A')}")
|
|
114
|
+
print(f" - Files: {stats.get('files_processed', 'N/A')}")
|
|
115
|
+
|
|
116
|
+
print(f"\n✅ Dry-run complete. Use without --dry-run to export.\n")
|
|
117
|
+
|
|
118
|
+
|
|
72
119
|
def _run_exports(args, result, output_dir: Path, source_path: Optional[Path] = None):
|
|
73
120
|
"""Export analysis results in requested formats.
|
|
74
121
|
|
|
@@ -79,6 +126,12 @@ def _run_exports(args, result, output_dir: Path, source_path: Optional[Path] = N
|
|
|
79
126
|
requested_formats = [f.strip() for f in args.format.split(',')]
|
|
80
127
|
formats = _expand_all_formats(requested_formats, getattr(args, 'png', False))
|
|
81
128
|
is_chunked = getattr(args, 'chunk', False)
|
|
129
|
+
dry_run = getattr(args, 'dry_run', False)
|
|
130
|
+
|
|
131
|
+
# Dry-run: show what would be exported without writing
|
|
132
|
+
if dry_run:
|
|
133
|
+
_show_dry_run_plan(formats, output_dir, is_chunked, result)
|
|
134
|
+
return
|
|
82
135
|
|
|
83
136
|
# Skip cache for chunked or when explicitly disabled
|
|
84
137
|
skip_cache = is_chunked or getattr(args, 'no_cache', False)
|
|
@@ -147,6 +147,18 @@ Strategy Options (--strategy):
|
|
|
147
147
|
help='Force re-analysis and re-export even when cache is valid (alias for --no-cache)'
|
|
148
148
|
)
|
|
149
149
|
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
'--dry-run',
|
|
152
|
+
action='store_true',
|
|
153
|
+
help='Show what would be exported without writing files'
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
parser.add_argument(
|
|
157
|
+
'--watch',
|
|
158
|
+
action='store_true',
|
|
159
|
+
help='Auto-detect changed files and only re-analyze those (faster subsequent runs)'
|
|
160
|
+
)
|
|
161
|
+
|
|
150
162
|
parser.add_argument(
|
|
151
163
|
'--strategy',
|
|
152
164
|
choices=['quick', 'standard', 'deep'],
|
|
@@ -52,6 +52,16 @@ class ProjectAnalyzer:
|
|
|
52
52
|
print(f" - Parallel: {self.config.performance.parallel_enabled}, Workers: {workers}")
|
|
53
53
|
|
|
54
54
|
pcache, cached_results, files_to_analyze = self._load_from_persistent_cache(files, project_path)
|
|
55
|
+
|
|
56
|
+
# Watch mode: show what changed
|
|
57
|
+
if getattr(self.config, 'watch', False) and files_to_analyze:
|
|
58
|
+
print(f"\n👁️ Watch mode: {len(files_to_analyze)} files changed since last run:")
|
|
59
|
+
for fp, _ in files_to_analyze[:10]:
|
|
60
|
+
print(f" • {Path(fp).name}")
|
|
61
|
+
if len(files_to_analyze) > 10:
|
|
62
|
+
print(f" ... and {len(files_to_analyze) - 10} more")
|
|
63
|
+
print()
|
|
64
|
+
|
|
55
65
|
fresh_results = self._run_analysis(files_to_analyze)
|
|
56
66
|
self._store_to_persistent_cache(pcache, files_to_analyze, fresh_results)
|
|
57
67
|
|
|
@@ -36,6 +36,26 @@ DEFAULT_CACHE_TTL_HOURS = 24
|
|
|
36
36
|
DEFAULT_MAX_MEMORY_MB = 2048
|
|
37
37
|
DEFAULT_PROGRESS_BAR_THRESHOLD = 50 # File count threshold for progress bar
|
|
38
38
|
|
|
39
|
+
# Complexity thresholds
|
|
40
|
+
CC_LOW_THRESHOLD = 5 # Rank A
|
|
41
|
+
CC_MEDIUM_THRESHOLD = 10 # Rank B
|
|
42
|
+
CC_HIGH_THRESHOLD = 20 # Rank C
|
|
43
|
+
CC_CRITICAL_THRESHOLD = 50 # For warnings
|
|
44
|
+
|
|
45
|
+
# Size limits
|
|
46
|
+
KB = 1024
|
|
47
|
+
MB = 1024 * 1024
|
|
48
|
+
MAX_FILE_SIZE_KB = 256
|
|
49
|
+
CHUNK_SIZE_KB = 256
|
|
50
|
+
|
|
51
|
+
# Timeouts
|
|
52
|
+
DEFAULT_PNG_TIMEOUT = 60
|
|
53
|
+
DEFAULT_MERMAID_MAX_TEXT_SIZE = 2_000_000
|
|
54
|
+
DEFAULT_MERMAID_MAX_EDGES = 20_000
|
|
55
|
+
|
|
56
|
+
# Cache settings
|
|
57
|
+
DEFAULT_CACHE_MAX_AGE_DAYS = 30
|
|
58
|
+
|
|
39
59
|
|
|
40
60
|
class AnalysisMode(str, Enum):
|
|
41
61
|
"""Available analysis modes."""
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
import re
|
|
4
4
|
from typing import Dict, List
|
|
5
5
|
|
|
6
|
+
from code2llm.core.config import (
|
|
7
|
+
CC_LOW_THRESHOLD,
|
|
8
|
+
CC_MEDIUM_THRESHOLD,
|
|
9
|
+
CC_HIGH_THRESHOLD,
|
|
10
|
+
)
|
|
11
|
+
|
|
6
12
|
|
|
7
13
|
# Branching keywords per language family
|
|
8
14
|
CC_PATTERNS = {
|
|
@@ -64,7 +70,11 @@ def calculate_complexity_regex(content: str, result: Dict,
|
|
|
64
70
|
cc = 1
|
|
65
71
|
else:
|
|
66
72
|
cc = 1 + len(pattern.findall(body))
|
|
67
|
-
rank =
|
|
73
|
+
rank = (
|
|
74
|
+
'A' if cc <= CC_LOW_THRESHOLD
|
|
75
|
+
else ('B' if cc <= CC_MEDIUM_THRESHOLD
|
|
76
|
+
else ('C' if cc <= CC_HIGH_THRESHOLD else 'D'))
|
|
77
|
+
)
|
|
68
78
|
func_info.complexity = {
|
|
69
79
|
'cyclomatic_complexity': cc,
|
|
70
80
|
'cc_rank': rank,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Report generators — produce views from project.yaml (single source of truth).
|
|
2
|
+
|
|
3
|
+
Thin re-export module. Actual generators live in separate files:
|
|
4
|
+
toon_view.py → ToonViewGenerator → project.toon.yaml
|
|
5
|
+
context_view.py → ContextViewGenerator → context.md
|
|
6
|
+
article_view.py → ArticleViewGenerator → status.md
|
|
7
|
+
html_dashboard.py → HTMLDashboardGenerator → dashboard.html
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
from typing import Any, Dict
|
|
12
|
+
|
|
13
|
+
from .toon_view import ToonViewGenerator
|
|
14
|
+
from .context_view import ContextViewGenerator
|
|
15
|
+
from .article_view import ArticleViewGenerator
|
|
16
|
+
from .html_dashboard import HTMLDashboardGenerator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_project_yaml(path: str) -> Dict[str, Any]:
|
|
20
|
+
"""Load and validate project.yaml with detailed error reporting."""
|
|
21
|
+
import yaml
|
|
22
|
+
from yaml.scanner import ScannerError
|
|
23
|
+
from yaml.parser import ParserError
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
27
|
+
content = f.read()
|
|
28
|
+
except FileNotFoundError:
|
|
29
|
+
raise ValueError(f"project.yaml not found: {path}")
|
|
30
|
+
except Exception as e:
|
|
31
|
+
raise ValueError(f"Cannot read project.yaml ({path}): {e}")
|
|
32
|
+
|
|
33
|
+
# Check for empty file
|
|
34
|
+
if not content.strip():
|
|
35
|
+
raise ValueError(f"project.yaml is empty: {path}")
|
|
36
|
+
|
|
37
|
+
# Try to parse YAML with detailed error reporting
|
|
38
|
+
try:
|
|
39
|
+
data = yaml.safe_load(content)
|
|
40
|
+
except ScannerError as e:
|
|
41
|
+
line = e.problem_mark.line + 1 if e.problem_mark else "?"
|
|
42
|
+
col = e.problem_mark.column if e.problem_mark else "?"
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"YAML syntax error in {path} at line {line}, column {col}: {e.problem}\n"
|
|
45
|
+
f"Hint: Check indentation and special characters (:, -, #)")
|
|
46
|
+
except ParserError as e:
|
|
47
|
+
line = e.problem_mark.line + 1 if e.problem_mark else "?"
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"YAML parse error in {path} at line {line}: {e.problem}\n"
|
|
50
|
+
f"Hint: Verify YAML structure (mapping vs list)")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
raise ValueError(f"YAML error in {path}: {e}")
|
|
53
|
+
|
|
54
|
+
# Validate structure
|
|
55
|
+
if data is None:
|
|
56
|
+
raise ValueError(f"project.yaml is null/empty: {path}")
|
|
57
|
+
if not isinstance(data, dict):
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"Invalid project.yaml: expected dict/object, got {type(data).__name__} in {path}\n"
|
|
60
|
+
f"Hint: YAML must start with key-value pairs, not a list")
|
|
61
|
+
if "version" not in data:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"Invalid project.yaml: missing required 'version' key in {path}\n"
|
|
64
|
+
f"Required keys: version, project, analysis\n"
|
|
65
|
+
f"Found keys: {list(data.keys())[:10]}")
|
|
66
|
+
|
|
67
|
+
return data
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
"load_project_yaml",
|
|
72
|
+
"ToonViewGenerator",
|
|
73
|
+
"ContextViewGenerator",
|
|
74
|
+
"ArticleViewGenerator",
|
|
75
|
+
"HTMLDashboardGenerator",
|
|
76
|
+
]
|
|
@@ -21,11 +21,42 @@ def _strip_bom(text: str) -> str:
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _safe_read_yaml(path: Path) -> Dict[str, Any]:
|
|
24
|
-
"""Read YAML file safely, handling BOM and type validation."""
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
"""Read YAML file safely, handling BOM and type validation with detailed errors."""
|
|
25
|
+
from yaml.scanner import ScannerError
|
|
26
|
+
from yaml.parser import ParserError
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
raw = _strip_bom(path.read_text(encoding="utf-8"))
|
|
30
|
+
except FileNotFoundError:
|
|
31
|
+
raise ValueError(f"File not found: {path}")
|
|
32
|
+
except Exception as e:
|
|
33
|
+
raise ValueError(f"Cannot read file {path}: {e}")
|
|
34
|
+
|
|
35
|
+
if not raw.strip():
|
|
36
|
+
raise ValueError(f"File is empty: {path}")
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
loaded = yaml.safe_load(raw)
|
|
40
|
+
except ScannerError as e:
|
|
41
|
+
line = e.problem_mark.line + 1 if e.problem_mark else "?"
|
|
42
|
+
col = e.problem_mark.column if e.problem_mark else "?"
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"YAML syntax error in {path} at line {line}, column {col}: {e.problem}\n"
|
|
45
|
+
f"Hint: Check indentation, avoid tabs, watch special characters (:, -, #)")
|
|
46
|
+
except ParserError as e:
|
|
47
|
+
line = e.problem_mark.line + 1 if e.problem_mark else "?"
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"YAML parse error in {path} at line {line}: {e.problem}\n"
|
|
50
|
+
f"Hint: Verify structure - are you using list where dict expected?")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
raise ValueError(f"YAML error in {path}: {e}")
|
|
53
|
+
|
|
54
|
+
if loaded is None:
|
|
55
|
+
raise ValueError(f"File is null/empty YAML: {path}")
|
|
27
56
|
if not isinstance(loaded, dict):
|
|
28
|
-
raise ValueError(
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Expected YAML mapping (dict), got {type(loaded).__name__} in {path}\n"
|
|
59
|
+
f"Hint: File must start with 'key: value' pairs, not a list")
|
|
29
60
|
return loaded
|
|
30
61
|
|
|
31
62
|
|
|
@@ -212,13 +212,45 @@ def parse_llm_task_text(text: str) -> Dict[str, Any]:
|
|
|
212
212
|
|
|
213
213
|
|
|
214
214
|
def load_input(path: Path) -> Dict[str, Any]:
|
|
215
|
-
|
|
215
|
+
"""Load input file with detailed YAML/JSON error reporting."""
|
|
216
|
+
from yaml.scanner import ScannerError
|
|
217
|
+
from yaml.parser import ParserError
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
raw = path.read_text(encoding="utf-8")
|
|
221
|
+
except FileNotFoundError:
|
|
222
|
+
raise ValueError(f"Input file not found: {path}")
|
|
223
|
+
except Exception as e:
|
|
224
|
+
raise ValueError(f"Cannot read input file {path}: {e}")
|
|
225
|
+
|
|
216
226
|
raw = _strip_bom(raw)
|
|
217
227
|
|
|
228
|
+
if not raw.strip():
|
|
229
|
+
raise ValueError(f"Input file is empty: {path}")
|
|
230
|
+
|
|
218
231
|
if path.suffix.lower() in {".yaml", ".yml"}:
|
|
219
|
-
|
|
232
|
+
try:
|
|
233
|
+
loaded = yaml.safe_load(raw)
|
|
234
|
+
except ScannerError as e:
|
|
235
|
+
line = e.problem_mark.line + 1 if e.problem_mark else "?"
|
|
236
|
+
col = e.problem_mark.column if e.problem_mark else "?"
|
|
237
|
+
raise ValueError(
|
|
238
|
+
f"YAML syntax error at line {line}, column {col}: {e.problem}\n"
|
|
239
|
+
f"Hint: Check indentation in {path}")
|
|
240
|
+
except ParserError as e:
|
|
241
|
+
line = e.problem_mark.line + 1 if e.problem_mark else "?"
|
|
242
|
+
raise ValueError(
|
|
243
|
+
f"YAML parse error at line {line}: {e.problem}\n"
|
|
244
|
+
f"Hint: Verify YAML structure in {path}")
|
|
245
|
+
except Exception as e:
|
|
246
|
+
raise ValueError(f"YAML error in {path}: {e}")
|
|
247
|
+
|
|
248
|
+
if loaded is None:
|
|
249
|
+
raise ValueError(f"YAML file is null/empty: {path}")
|
|
220
250
|
if not isinstance(loaded, dict):
|
|
221
|
-
raise ValueError(
|
|
251
|
+
raise ValueError(
|
|
252
|
+
f"YAML must be a mapping/object, got {type(loaded).__name__} in {path}\n"
|
|
253
|
+
f"Hint: File should start with 'key: value' pairs")
|
|
222
254
|
return loaded
|
|
223
255
|
|
|
224
256
|
if path.suffix.lower() == ".json":
|
|
@@ -12,6 +12,12 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import List, Optional, Tuple
|
|
14
14
|
|
|
15
|
+
from code2llm.core.config import (
|
|
16
|
+
DEFAULT_PNG_TIMEOUT,
|
|
17
|
+
DEFAULT_MERMAID_MAX_TEXT_SIZE,
|
|
18
|
+
DEFAULT_MERMAID_MAX_EDGES,
|
|
19
|
+
)
|
|
20
|
+
|
|
15
21
|
|
|
16
22
|
def validate_mermaid_file(mmd_path: Path) -> List[str]:
|
|
17
23
|
"""Validate Mermaid file and return list of errors."""
|
|
@@ -283,7 +289,7 @@ def _prepare_and_render(mmd_file: Path, output_dir: Path, timeout: int) -> bool:
|
|
|
283
289
|
|
|
284
290
|
|
|
285
291
|
def generate_pngs(
|
|
286
|
-
input_dir: Path, output_dir: Path, timeout: int =
|
|
292
|
+
input_dir: Path, output_dir: Path, timeout: int = DEFAULT_PNG_TIMEOUT, max_workers: int = 0
|
|
287
293
|
) -> int:
|
|
288
294
|
"""Generate PNG files from all .mmd files in input_dir (parallel).
|
|
289
295
|
|
|
@@ -310,14 +316,14 @@ def generate_pngs(
|
|
|
310
316
|
def _setup_puppeteer_config() -> tuple[int, int, Optional[str]]:
|
|
311
317
|
"""Setup puppeteer config file and return (max_text_size, max_edges, cfg_path)."""
|
|
312
318
|
try:
|
|
313
|
-
max_text_size = int(os.getenv('CODE2FLOW_MERMAID_MAX_TEXT_SIZE',
|
|
319
|
+
max_text_size = int(os.getenv('CODE2FLOW_MERMAID_MAX_TEXT_SIZE', str(DEFAULT_MERMAID_MAX_TEXT_SIZE)))
|
|
314
320
|
except Exception:
|
|
315
|
-
max_text_size =
|
|
321
|
+
max_text_size = DEFAULT_MERMAID_MAX_TEXT_SIZE
|
|
316
322
|
|
|
317
323
|
try:
|
|
318
|
-
max_edges = int(os.getenv('CODE2FLOW_MERMAID_MAX_EDGES',
|
|
324
|
+
max_edges = int(os.getenv('CODE2FLOW_MERMAID_MAX_EDGES', str(DEFAULT_MERMAID_MAX_EDGES)))
|
|
319
325
|
except Exception:
|
|
320
|
-
max_edges =
|
|
326
|
+
max_edges = DEFAULT_MERMAID_MAX_EDGES
|
|
321
327
|
|
|
322
328
|
cfg_path: Optional[str] = None
|
|
323
329
|
try:
|
|
@@ -406,7 +412,7 @@ def _run_mmdc_subprocess(
|
|
|
406
412
|
return False
|
|
407
413
|
|
|
408
414
|
|
|
409
|
-
def generate_single_png(mmd_file: Path, output_file: Path, timeout: int =
|
|
415
|
+
def generate_single_png(mmd_file: Path, output_file: Path, timeout: int = DEFAULT_PNG_TIMEOUT) -> bool:
|
|
410
416
|
"""Generate PNG from single Mermaid file using available renderers."""
|
|
411
417
|
# Create output directory
|
|
412
418
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -431,9 +437,9 @@ def generate_single_png(mmd_file: Path, output_file: Path, timeout: int = 60) ->
|
|
|
431
437
|
def generate_with_puppeteer(
|
|
432
438
|
mmd_file: Path,
|
|
433
439
|
output_file: Path,
|
|
434
|
-
timeout: int =
|
|
435
|
-
max_text_size: int =
|
|
436
|
-
max_edges: int =
|
|
440
|
+
timeout: int = DEFAULT_PNG_TIMEOUT,
|
|
441
|
+
max_text_size: int = DEFAULT_MERMAID_MAX_TEXT_SIZE,
|
|
442
|
+
max_edges: int = DEFAULT_MERMAID_MAX_EDGES,
|
|
437
443
|
) -> bool:
|
|
438
444
|
"""Generate PNG using Puppeteer with HTML template."""
|
|
439
445
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code2llm
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.124
|
|
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.124"
|
|
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"
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"""Report generators — produce views from project.yaml (single source of truth).
|
|
2
|
-
|
|
3
|
-
Thin re-export module. Actual generators live in separate files:
|
|
4
|
-
toon_view.py → ToonViewGenerator → project.toon.yaml
|
|
5
|
-
context_view.py → ContextViewGenerator → context.md
|
|
6
|
-
article_view.py → ArticleViewGenerator → status.md
|
|
7
|
-
html_dashboard.py → HTMLDashboardGenerator → dashboard.html
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import yaml
|
|
11
|
-
from typing import Any, Dict
|
|
12
|
-
|
|
13
|
-
from .toon_view import ToonViewGenerator
|
|
14
|
-
from .context_view import ContextViewGenerator
|
|
15
|
-
from .article_view import ArticleViewGenerator
|
|
16
|
-
from .html_dashboard import HTMLDashboardGenerator
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def load_project_yaml(path: str) -> Dict[str, Any]:
|
|
20
|
-
"""Load and validate project.yaml."""
|
|
21
|
-
with open(path, "r", encoding="utf-8") as f:
|
|
22
|
-
data = yaml.safe_load(f)
|
|
23
|
-
if not isinstance(data, dict) or "version" not in data:
|
|
24
|
-
raise ValueError(f"Invalid project.yaml: missing 'version' key in {path}")
|
|
25
|
-
return data
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
__all__ = [
|
|
29
|
-
"load_project_yaml",
|
|
30
|
-
"ToonViewGenerator",
|
|
31
|
-
"ContextViewGenerator",
|
|
32
|
-
"ArticleViewGenerator",
|
|
33
|
-
"HTMLDashboardGenerator",
|
|
34
|
-
]
|
|
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
|