code2llm 0.5.37__tar.gz → 0.5.38__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.37 → code2llm-0.5.38}/PKG-INFO +4 -4
- {code2llm-0.5.37 → code2llm-0.5.38}/README.md +3 -3
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/__init__.py +1 -1
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/cli.py +2 -241
- code2llm-0.5.38/code2llm/cli_analysis.py +282 -0
- code2llm-0.5.38/code2llm/cli_exports/__init__.py +48 -0
- code2llm-0.5.38/code2llm/cli_exports/code2logic.py +127 -0
- code2llm-0.5.38/code2llm/cli_exports/formats.py +225 -0
- code2llm-0.5.38/code2llm/cli_exports/orchestrator.py +96 -0
- code2llm-0.5.38/code2llm/cli_exports/prompt.py +162 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/project_yaml_exporter.py +99 -83
- code2llm-0.5.38/code2llm/exporters/validate_project.py +118 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/nlp/__init__.py +1 -1
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm.egg-info/PKG-INFO +4 -4
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm.egg-info/SOURCES.txt +7 -1
- {code2llm-0.5.37 → code2llm-0.5.38}/pyproject.toml +1 -1
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_prompt_txt.py +6 -4
- code2llm-0.5.37/code2llm/cli_exports.py +0 -633
- {code2llm-0.5.37 → code2llm-0.5.38}/LICENSE +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/__main__.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/analysis/pipeline_detector.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/analysis/type_inference.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/analyzer.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/config.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/core/__init__.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/core/file_analyzer.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/core/file_cache.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/core/file_filter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/core/refactoring.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/large_repo.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/models.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/flow_constants.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/toon/helpers.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/generators/llm_task.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/generators/mermaid.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm.egg-info/requires.txt +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/code2llm.egg-info/top_level.txt +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/setup.cfg +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/setup.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.37 → code2llm-0.5.38}/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.38
|
|
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
|
|
@@ -60,7 +60,7 @@ When you run `code2llm ./ -f all`, the following files are created:
|
|
|
60
60
|
|
|
61
61
|
| File | Format | Purpose | Key Insights |
|
|
62
62
|
|------|--------|---------|--------------|
|
|
63
|
-
| `analysis.toon` | **TOON** | **🔥 Health diagnostics** - Complexity, god modules, coupling |
|
|
63
|
+
| `analysis.toon` | **TOON** | **🔥 Health diagnostics** - Complexity, god modules, coupling | 52 critical functions, 0 god modules |
|
|
64
64
|
| `project.toon` | **TOON** | **🧠 Project logic** - Compact module view from code2logic | Generated via code2logic integration |
|
|
65
65
|
|
|
66
66
|
### 🤖 LLM-Ready Documentation
|
|
@@ -375,9 +375,9 @@ code2llm ./ -f yaml --separate-orphans
|
|
|
375
375
|
|
|
376
376
|
**Generated by**: `code2llm ./ -f all --readme`
|
|
377
377
|
**Analysis Date**: 2026-03-05
|
|
378
|
-
**Total Functions**:
|
|
378
|
+
**Total Functions**: 820
|
|
379
379
|
**Total Classes**: 104
|
|
380
|
-
**Modules**:
|
|
380
|
+
**Modules**: 102
|
|
381
381
|
|
|
382
382
|
For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
|
|
383
383
|
|
|
@@ -10,7 +10,7 @@ When you run `code2llm ./ -f all`, the following files are created:
|
|
|
10
10
|
|
|
11
11
|
| File | Format | Purpose | Key Insights |
|
|
12
12
|
|------|--------|---------|--------------|
|
|
13
|
-
| `analysis.toon` | **TOON** | **🔥 Health diagnostics** - Complexity, god modules, coupling |
|
|
13
|
+
| `analysis.toon` | **TOON** | **🔥 Health diagnostics** - Complexity, god modules, coupling | 52 critical functions, 0 god modules |
|
|
14
14
|
| `project.toon` | **TOON** | **🧠 Project logic** - Compact module view from code2logic | Generated via code2logic integration |
|
|
15
15
|
|
|
16
16
|
### 🤖 LLM-Ready Documentation
|
|
@@ -325,9 +325,9 @@ code2llm ./ -f yaml --separate-orphans
|
|
|
325
325
|
|
|
326
326
|
**Generated by**: `code2llm ./ -f all --readme`
|
|
327
327
|
**Analysis Date**: 2026-03-05
|
|
328
|
-
**Total Functions**:
|
|
328
|
+
**Total Functions**: 820
|
|
329
329
|
**Total Classes**: 104
|
|
330
|
-
**Modules**:
|
|
330
|
+
**Modules**: 102
|
|
331
331
|
|
|
332
332
|
For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
|
|
333
333
|
|
|
@@ -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.38"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -18,6 +18,7 @@ from .cli_exports import (
|
|
|
18
18
|
_export_simple_formats, _export_yaml, _export_mermaid, _export_refactor_prompts,
|
|
19
19
|
_export_project_yaml, _run_report,
|
|
20
20
|
)
|
|
21
|
+
from .cli_analysis import _run_analysis
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
|
|
@@ -459,247 +460,7 @@ def main():
|
|
|
459
460
|
return 0
|
|
460
461
|
|
|
461
462
|
|
|
462
|
-
|
|
463
|
-
"""Run code analysis with configured strategy.
|
|
464
|
-
|
|
465
|
-
Returns AnalysisResult or exits on error.
|
|
466
|
-
For large repos, may analyze in chunks and merge results.
|
|
467
|
-
"""
|
|
468
|
-
from .core.large_repo import (
|
|
469
|
-
HierarchicalRepoSplitter, should_use_chunking, get_analysis_plan
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
# Check if we should use chunked analysis
|
|
473
|
-
# Auto-chunk when estimated output > chunk_size (default 256KB = ~85 files)
|
|
474
|
-
# --no-chunk explicitly disables chunking
|
|
475
|
-
use_chunking = (
|
|
476
|
-
not args.no_chunk and
|
|
477
|
-
(args.chunk or should_use_chunking(source_path, args.chunk_size))
|
|
478
|
-
)
|
|
479
|
-
|
|
480
|
-
if use_chunking:
|
|
481
|
-
if args.verbose:
|
|
482
|
-
print(f"Large repository detected - using chunked analysis")
|
|
483
|
-
# Set chunk flag so export knows to use chunked mode
|
|
484
|
-
args.chunk = True
|
|
485
|
-
return _run_chunked_analysis(args, source_path, output_dir)
|
|
486
|
-
|
|
487
|
-
# Standard single-analysis flow
|
|
488
|
-
config = Config(
|
|
489
|
-
mode=args.mode,
|
|
490
|
-
max_depth_enumeration=args.max_depth,
|
|
491
|
-
detect_state_machines=not args.no_patterns,
|
|
492
|
-
detect_recursion=not args.no_patterns,
|
|
493
|
-
output_dir=str(output_dir)
|
|
494
|
-
)
|
|
495
|
-
|
|
496
|
-
try:
|
|
497
|
-
if args.streaming or args.strategy in ['quick', 'deep']:
|
|
498
|
-
result = _run_streaming_analysis(args, config, source_path)
|
|
499
|
-
else:
|
|
500
|
-
analyzer = ProjectAnalyzer(config)
|
|
501
|
-
result = analyzer.analyze_project(str(source_path))
|
|
502
|
-
|
|
503
|
-
if args.verbose:
|
|
504
|
-
print(f"\nAnalysis complete:")
|
|
505
|
-
print(f" - Functions: {len(result.functions)}")
|
|
506
|
-
print(f" - Classes: {len(result.classes)}")
|
|
507
|
-
print(f" - CFG nodes: {len(result.nodes)}")
|
|
508
|
-
print(f" - CFG edges: {len(result.edges)}")
|
|
509
|
-
|
|
510
|
-
return result
|
|
511
|
-
|
|
512
|
-
except Exception as e:
|
|
513
|
-
print(f"Error during analysis: {e}", file=sys.stderr)
|
|
514
|
-
sys.exit(1)
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
def _run_chunked_analysis(args, source_path: Path, output_dir: Path):
|
|
518
|
-
"""Analyze large repository using hierarchical chunking.
|
|
519
|
-
|
|
520
|
-
Strategy:
|
|
521
|
-
1. Level 1 folders first
|
|
522
|
-
2. If >256KB, split to level 2 subfolders
|
|
523
|
-
3. If still too big, use file chunking
|
|
524
|
-
"""
|
|
525
|
-
from .core.large_repo import HierarchicalRepoSplitter
|
|
526
|
-
|
|
527
|
-
splitter = HierarchicalRepoSplitter(
|
|
528
|
-
size_limit_kb=args.chunk_size,
|
|
529
|
-
max_files_per_chunk=args.max_files_per_chunk
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
# Get hierarchical analysis plan
|
|
533
|
-
subprojects = splitter.get_analysis_plan(source_path)
|
|
534
|
-
|
|
535
|
-
if args.verbose:
|
|
536
|
-
print(f"Hierarchical analysis plan ({len(subprojects)} chunks):")
|
|
537
|
-
level_counts = {}
|
|
538
|
-
for sp in subprojects:
|
|
539
|
-
level_counts[sp.level] = level_counts.get(sp.level, 0) + 1
|
|
540
|
-
|
|
541
|
-
for level in sorted(level_counts.keys()):
|
|
542
|
-
level_name = {0: 'root', 1: 'level-1', 2: 'level-2', 3: 'file-chunks'}.get(level, f'level-{level}')
|
|
543
|
-
print(f" {level_name}: {level_counts[level]} chunks")
|
|
544
|
-
|
|
545
|
-
print("\nChunks:")
|
|
546
|
-
for sp in subprojects:
|
|
547
|
-
level_indicator = " " * sp.level
|
|
548
|
-
size_info = f"~{sp.estimated_size_kb}KB"
|
|
549
|
-
print(f"{level_indicator}{sp.name}: {sp.file_count} files ({size_info})")
|
|
550
|
-
|
|
551
|
-
# Filter subprojects if requested
|
|
552
|
-
if args.only_subproject:
|
|
553
|
-
subprojects = [sp for sp in subprojects if sp.name == args.only_subproject or sp.name.startswith(args.only_subproject + '.')]
|
|
554
|
-
if not subprojects:
|
|
555
|
-
print(f"Error: Subproject '{args.only_subproject}' not found", file=sys.stderr)
|
|
556
|
-
sys.exit(1)
|
|
557
|
-
|
|
558
|
-
if args.skip_subprojects:
|
|
559
|
-
subprojects = [sp for sp in subprojects if not any(sp.name.startswith(skip) for skip in args.skip_subprojects)]
|
|
560
|
-
|
|
561
|
-
# Analyze each subproject
|
|
562
|
-
all_results = []
|
|
563
|
-
for i, subproject in enumerate(subprojects, 1):
|
|
564
|
-
if args.verbose:
|
|
565
|
-
level_name = {0: 'root', 1: 'L1', 2: 'L2', 3: 'chunk'}.get(subproject.level, f'L{subproject.level}')
|
|
566
|
-
print(f"\n[{i}/{len(subprojects)}] Analyzing [{level_name}]: {subproject.name}")
|
|
567
|
-
|
|
568
|
-
sp_output_dir = output_dir / subproject.name.replace('.', '_')
|
|
569
|
-
sp_output_dir.mkdir(parents=True, exist_ok=True)
|
|
570
|
-
|
|
571
|
-
result = _analyze_subproject(args, subproject, sp_output_dir)
|
|
572
|
-
if result:
|
|
573
|
-
all_results.append((subproject.name, result, sp_output_dir))
|
|
574
|
-
|
|
575
|
-
# Create merged summary
|
|
576
|
-
merged_result = _merge_chunked_results(all_results, source_path)
|
|
577
|
-
|
|
578
|
-
if args.verbose:
|
|
579
|
-
print(f"\nChunked analysis complete:")
|
|
580
|
-
print(f" - Chunks analyzed: {len(all_results)}")
|
|
581
|
-
print(f" - Total functions: {len(merged_result.functions)}")
|
|
582
|
-
print(f" - Total classes: {len(merged_result.classes)}")
|
|
583
|
-
|
|
584
|
-
return merged_result
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
def _analyze_subproject(args, subproject, output_dir: Path):
|
|
588
|
-
"""Analyze and export a single subproject."""
|
|
589
|
-
from .core.analyzer import ProjectAnalyzer
|
|
590
|
-
from .core.config import Config
|
|
591
|
-
from .cli_exports import _export_simple_formats, _export_evolution, _export_chunked_prompt_txt, _export_code2logic
|
|
592
|
-
|
|
593
|
-
config = Config(
|
|
594
|
-
mode=args.mode,
|
|
595
|
-
max_depth_enumeration=args.max_depth,
|
|
596
|
-
detect_state_machines=not args.no_patterns,
|
|
597
|
-
detect_recursion=not args.no_patterns,
|
|
598
|
-
output_dir=str(output_dir),
|
|
599
|
-
verbose=args.verbose
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
analyzer = ProjectAnalyzer(config)
|
|
603
|
-
|
|
604
|
-
try:
|
|
605
|
-
# Analyze subproject
|
|
606
|
-
result = analyzer.analyze_project(str(subproject.path))
|
|
607
|
-
|
|
608
|
-
# Export results for this subproject
|
|
609
|
-
formats = [f.strip() for f in args.format.split(',')]
|
|
610
|
-
if 'all' in formats:
|
|
611
|
-
formats = ['toon', 'context', 'evolution', 'code2logic']
|
|
612
|
-
|
|
613
|
-
# Export simple formats (toon, context)
|
|
614
|
-
_export_simple_formats(args, result, output_dir, formats)
|
|
615
|
-
|
|
616
|
-
# Export evolution
|
|
617
|
-
if 'evolution' in formats or 'all' in formats:
|
|
618
|
-
_export_evolution(args, result, output_dir)
|
|
619
|
-
|
|
620
|
-
if args.verbose:
|
|
621
|
-
print(f" ✓ Exported {subproject.name}: {len(result.functions)} functions")
|
|
622
|
-
|
|
623
|
-
return result
|
|
624
|
-
except Exception as e:
|
|
625
|
-
print(f"Warning: Failed to analyze {subproject.name}: {e}", file=sys.stderr)
|
|
626
|
-
return None
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
def _merge_chunked_results(all_results, source_path: Path):
|
|
630
|
-
"""Merge results from multiple subproject analyses."""
|
|
631
|
-
from .core.models import AnalysisResult
|
|
632
|
-
|
|
633
|
-
merged = AnalysisResult(project_path=str(source_path))
|
|
634
|
-
|
|
635
|
-
for name, result, output_dir in all_results:
|
|
636
|
-
if not result:
|
|
637
|
-
continue
|
|
638
|
-
|
|
639
|
-
# Prefix with subproject name to avoid collisions
|
|
640
|
-
prefix = f"{name}."
|
|
641
|
-
|
|
642
|
-
# Merge functions
|
|
643
|
-
for func_name, func_info in result.functions.items():
|
|
644
|
-
new_name = f"{prefix}{func_name}" if '.' not in func_name else func_name
|
|
645
|
-
merged.functions[new_name] = func_info
|
|
646
|
-
|
|
647
|
-
# Merge classes
|
|
648
|
-
for class_name, class_info in result.classes.items():
|
|
649
|
-
new_name = f"{prefix}{class_name}" if '.' not in class_name else class_name
|
|
650
|
-
merged.classes[new_name] = class_info
|
|
651
|
-
|
|
652
|
-
# Merge modules
|
|
653
|
-
for mod_name, mod_info in result.modules.items():
|
|
654
|
-
new_name = f"{prefix}{mod_name}" if '.' not in mod_name else mod_name
|
|
655
|
-
merged.modules[new_name] = mod_info
|
|
656
|
-
|
|
657
|
-
# Merge nodes and edges (simplified - just count)
|
|
658
|
-
merged.nodes.update(result.nodes)
|
|
659
|
-
merged.edges.extend(result.edges)
|
|
660
|
-
|
|
661
|
-
return merged
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
def _run_streaming_analysis(args, config, source_path: Path):
|
|
665
|
-
"""Run streaming analysis with progress reporting."""
|
|
666
|
-
from .core.streaming_analyzer import (
|
|
667
|
-
StreamingAnalyzer, STRATEGY_QUICK,
|
|
668
|
-
STRATEGY_STANDARD, STRATEGY_DEEP
|
|
669
|
-
)
|
|
670
|
-
|
|
671
|
-
strategy_map = {
|
|
672
|
-
'quick': STRATEGY_QUICK,
|
|
673
|
-
'standard': STRATEGY_STANDARD,
|
|
674
|
-
'deep': STRATEGY_DEEP
|
|
675
|
-
}
|
|
676
|
-
strategy = strategy_map.get(args.strategy, STRATEGY_STANDARD)
|
|
677
|
-
|
|
678
|
-
# Adjust strategy for memory limit
|
|
679
|
-
strategy.max_files_in_memory = min(
|
|
680
|
-
strategy.max_files_in_memory,
|
|
681
|
-
args.max_memory // 10
|
|
682
|
-
)
|
|
683
|
-
|
|
684
|
-
analyzer = StreamingAnalyzer(config, strategy)
|
|
685
|
-
|
|
686
|
-
if args.verbose:
|
|
687
|
-
def on_progress(update):
|
|
688
|
-
pct = update.get('percentage', 0)
|
|
689
|
-
print(f"\r[{pct:.0f}%] {update.get('message', '')}", end='', flush=True)
|
|
690
|
-
analyzer.set_progress_callback(on_progress)
|
|
691
|
-
|
|
692
|
-
print(f"Analyzing with {args.strategy} strategy...")
|
|
693
|
-
for update in analyzer.analyze_streaming(str(source_path)):
|
|
694
|
-
if update['type'] == 'complete':
|
|
695
|
-
if args.verbose:
|
|
696
|
-
print()
|
|
697
|
-
print(f"Completed in {update.get('elapsed_seconds', 0):.1f}s")
|
|
698
|
-
|
|
699
|
-
# Re-run standard analyzer for full results
|
|
700
|
-
# TODO: Modify streaming to accumulate results properly
|
|
701
|
-
analyzer = ProjectAnalyzer(config)
|
|
702
|
-
return analyzer.analyze_project(str(source_path))
|
|
463
|
+
# Analysis functions are in cli_analysis.py — imported at top of file
|
|
703
464
|
|
|
704
465
|
|
|
705
466
|
def generate_llm_context(args_list):
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""Analysis execution — chunked, streaming, and standard analysis flows.
|
|
2
|
+
|
|
3
|
+
Extracted from cli.py to isolate analysis orchestration from CLI parsing.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _run_analysis(args, source_path: Path, output_dir: Path):
|
|
12
|
+
"""Run code analysis with configured strategy.
|
|
13
|
+
|
|
14
|
+
Returns AnalysisResult or exits on error.
|
|
15
|
+
For large repos, may analyze in chunks and merge results.
|
|
16
|
+
"""
|
|
17
|
+
from .core.large_repo import should_use_chunking
|
|
18
|
+
|
|
19
|
+
# --no-chunk explicitly disables chunking
|
|
20
|
+
use_chunking = (
|
|
21
|
+
not args.no_chunk and
|
|
22
|
+
(args.chunk or should_use_chunking(source_path, args.chunk_size))
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if use_chunking:
|
|
26
|
+
if args.verbose:
|
|
27
|
+
print(f"Large repository detected - using chunked analysis")
|
|
28
|
+
args.chunk = True
|
|
29
|
+
return _run_chunked_analysis(args, source_path, output_dir)
|
|
30
|
+
|
|
31
|
+
return _run_standard_analysis(args, source_path, output_dir)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _run_standard_analysis(args, source_path: Path, output_dir: Path):
|
|
35
|
+
"""Standard single-project analysis flow."""
|
|
36
|
+
from .core.config import Config
|
|
37
|
+
from .core.analyzer import ProjectAnalyzer
|
|
38
|
+
|
|
39
|
+
config = _build_config(args, output_dir)
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
if args.streaming or args.strategy in ['quick', 'deep']:
|
|
43
|
+
result = _run_streaming_analysis(args, config, source_path)
|
|
44
|
+
else:
|
|
45
|
+
analyzer = ProjectAnalyzer(config)
|
|
46
|
+
result = analyzer.analyze_project(str(source_path))
|
|
47
|
+
|
|
48
|
+
if args.verbose:
|
|
49
|
+
_print_analysis_summary(result)
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(f"Error during analysis: {e}", file=sys.stderr)
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _build_config(args, output_dir: Path):
|
|
59
|
+
"""Build analysis Config from CLI args."""
|
|
60
|
+
from .core.config import Config
|
|
61
|
+
return Config(
|
|
62
|
+
mode=args.mode,
|
|
63
|
+
max_depth_enumeration=args.max_depth,
|
|
64
|
+
detect_state_machines=not args.no_patterns,
|
|
65
|
+
detect_recursion=not args.no_patterns,
|
|
66
|
+
output_dir=str(output_dir)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _print_analysis_summary(result) -> None:
|
|
71
|
+
"""Print analysis completion summary."""
|
|
72
|
+
print(f"\nAnalysis complete:")
|
|
73
|
+
print(f" - Functions: {len(result.functions)}")
|
|
74
|
+
print(f" - Classes: {len(result.classes)}")
|
|
75
|
+
print(f" - CFG nodes: {len(result.nodes)}")
|
|
76
|
+
print(f" - CFG edges: {len(result.edges)}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ------------------------------------------------------------------
|
|
80
|
+
# Chunked analysis
|
|
81
|
+
# ------------------------------------------------------------------
|
|
82
|
+
def _run_chunked_analysis(args, source_path: Path, output_dir: Path):
|
|
83
|
+
"""Analyze large repository using hierarchical chunking.
|
|
84
|
+
|
|
85
|
+
Strategy:
|
|
86
|
+
1. Level 1 folders first
|
|
87
|
+
2. If >256KB, split to level 2 subfolders
|
|
88
|
+
3. If still too big, use file chunking
|
|
89
|
+
"""
|
|
90
|
+
from .core.large_repo import HierarchicalRepoSplitter
|
|
91
|
+
|
|
92
|
+
splitter = HierarchicalRepoSplitter(
|
|
93
|
+
size_limit_kb=args.chunk_size,
|
|
94
|
+
max_files_per_chunk=args.max_files_per_chunk
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
subprojects = splitter.get_analysis_plan(source_path)
|
|
98
|
+
|
|
99
|
+
if args.verbose:
|
|
100
|
+
_print_chunked_plan(subprojects)
|
|
101
|
+
|
|
102
|
+
subprojects = _filter_subprojects(args, subprojects)
|
|
103
|
+
|
|
104
|
+
all_results = _analyze_all_subprojects(args, subprojects, output_dir)
|
|
105
|
+
|
|
106
|
+
merged_result = _merge_chunked_results(all_results, source_path)
|
|
107
|
+
|
|
108
|
+
if args.verbose:
|
|
109
|
+
print(f"\nChunked analysis complete:")
|
|
110
|
+
print(f" - Chunks analyzed: {len(all_results)}")
|
|
111
|
+
print(f" - Total functions: {len(merged_result.functions)}")
|
|
112
|
+
print(f" - Total classes: {len(merged_result.classes)}")
|
|
113
|
+
|
|
114
|
+
return merged_result
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _print_chunked_plan(subprojects) -> None:
|
|
118
|
+
"""Print hierarchical analysis plan summary."""
|
|
119
|
+
print(f"Hierarchical analysis plan ({len(subprojects)} chunks):")
|
|
120
|
+
level_counts = {}
|
|
121
|
+
for sp in subprojects:
|
|
122
|
+
level_counts[sp.level] = level_counts.get(sp.level, 0) + 1
|
|
123
|
+
|
|
124
|
+
for level in sorted(level_counts.keys()):
|
|
125
|
+
level_name = {0: 'root', 1: 'level-1', 2: 'level-2', 3: 'file-chunks'}.get(level, f'level-{level}')
|
|
126
|
+
print(f" {level_name}: {level_counts[level]} chunks")
|
|
127
|
+
|
|
128
|
+
print("\nChunks:")
|
|
129
|
+
for sp in subprojects:
|
|
130
|
+
level_indicator = " " * sp.level
|
|
131
|
+
size_info = f"~{sp.estimated_size_kb}KB"
|
|
132
|
+
print(f"{level_indicator}{sp.name}: {sp.file_count} files ({size_info})")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _filter_subprojects(args, subprojects) -> list:
|
|
136
|
+
"""Apply --only-subproject and --skip-subprojects filters."""
|
|
137
|
+
if args.only_subproject:
|
|
138
|
+
subprojects = [
|
|
139
|
+
sp for sp in subprojects
|
|
140
|
+
if sp.name == args.only_subproject
|
|
141
|
+
or sp.name.startswith(args.only_subproject + '.')
|
|
142
|
+
]
|
|
143
|
+
if not subprojects:
|
|
144
|
+
print(f"Error: Subproject '{args.only_subproject}' not found", file=sys.stderr)
|
|
145
|
+
sys.exit(1)
|
|
146
|
+
|
|
147
|
+
if args.skip_subprojects:
|
|
148
|
+
subprojects = [
|
|
149
|
+
sp for sp in subprojects
|
|
150
|
+
if not any(sp.name.startswith(skip) for skip in args.skip_subprojects)
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
return subprojects
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _analyze_all_subprojects(args, subprojects, output_dir: Path) -> list:
|
|
157
|
+
"""Analyze each subproject and collect results."""
|
|
158
|
+
all_results = []
|
|
159
|
+
for i, subproject in enumerate(subprojects, 1):
|
|
160
|
+
if args.verbose:
|
|
161
|
+
level_name = {0: 'root', 1: 'L1', 2: 'L2', 3: 'chunk'}.get(subproject.level, f'L{subproject.level}')
|
|
162
|
+
print(f"\n[{i}/{len(subprojects)}] Analyzing [{level_name}]: {subproject.name}")
|
|
163
|
+
|
|
164
|
+
sp_output_dir = output_dir / subproject.name.replace('.', '_')
|
|
165
|
+
sp_output_dir.mkdir(parents=True, exist_ok=True)
|
|
166
|
+
|
|
167
|
+
result = _analyze_subproject(args, subproject, sp_output_dir)
|
|
168
|
+
if result:
|
|
169
|
+
all_results.append((subproject.name, result, sp_output_dir))
|
|
170
|
+
return all_results
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _analyze_subproject(args, subproject, output_dir: Path):
|
|
174
|
+
"""Analyze and export a single subproject."""
|
|
175
|
+
from .core.analyzer import ProjectAnalyzer
|
|
176
|
+
from .core.config import Config
|
|
177
|
+
from .cli_exports import _export_simple_formats, _export_evolution
|
|
178
|
+
|
|
179
|
+
config = Config(
|
|
180
|
+
mode=args.mode,
|
|
181
|
+
max_depth_enumeration=args.max_depth,
|
|
182
|
+
detect_state_machines=not args.no_patterns,
|
|
183
|
+
detect_recursion=not args.no_patterns,
|
|
184
|
+
output_dir=str(output_dir),
|
|
185
|
+
verbose=args.verbose
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
analyzer = ProjectAnalyzer(config)
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
result = analyzer.analyze_project(str(subproject.path))
|
|
192
|
+
|
|
193
|
+
formats = [f.strip() for f in args.format.split(',')]
|
|
194
|
+
if 'all' in formats:
|
|
195
|
+
formats = ['toon', 'context', 'evolution', 'code2logic']
|
|
196
|
+
|
|
197
|
+
_export_simple_formats(args, result, output_dir, formats)
|
|
198
|
+
|
|
199
|
+
if 'evolution' in formats or 'all' in formats:
|
|
200
|
+
_export_evolution(args, result, output_dir)
|
|
201
|
+
|
|
202
|
+
if args.verbose:
|
|
203
|
+
print(f" ✓ Exported {subproject.name}: {len(result.functions)} functions")
|
|
204
|
+
|
|
205
|
+
return result
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print(f"Warning: Failed to analyze {subproject.name}: {e}", file=sys.stderr)
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _merge_chunked_results(all_results, source_path: Path):
|
|
212
|
+
"""Merge results from multiple subproject analyses."""
|
|
213
|
+
from .core.models import AnalysisResult
|
|
214
|
+
|
|
215
|
+
merged = AnalysisResult(project_path=str(source_path))
|
|
216
|
+
|
|
217
|
+
for name, result, output_dir in all_results:
|
|
218
|
+
if not result:
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
prefix = f"{name}."
|
|
222
|
+
|
|
223
|
+
for func_name, func_info in result.functions.items():
|
|
224
|
+
new_name = f"{prefix}{func_name}" if '.' not in func_name else func_name
|
|
225
|
+
merged.functions[new_name] = func_info
|
|
226
|
+
|
|
227
|
+
for class_name, class_info in result.classes.items():
|
|
228
|
+
new_name = f"{prefix}{class_name}" if '.' not in class_name else class_name
|
|
229
|
+
merged.classes[new_name] = class_info
|
|
230
|
+
|
|
231
|
+
for mod_name, mod_info in result.modules.items():
|
|
232
|
+
new_name = f"{prefix}{mod_name}" if '.' not in mod_name else mod_name
|
|
233
|
+
merged.modules[new_name] = mod_info
|
|
234
|
+
|
|
235
|
+
merged.nodes.update(result.nodes)
|
|
236
|
+
merged.edges.extend(result.edges)
|
|
237
|
+
|
|
238
|
+
return merged
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ------------------------------------------------------------------
|
|
242
|
+
# Streaming analysis
|
|
243
|
+
# ------------------------------------------------------------------
|
|
244
|
+
def _run_streaming_analysis(args, config, source_path: Path):
|
|
245
|
+
"""Run streaming analysis with progress reporting."""
|
|
246
|
+
from .core.analyzer import ProjectAnalyzer
|
|
247
|
+
from .core.streaming_analyzer import (
|
|
248
|
+
StreamingAnalyzer, STRATEGY_QUICK,
|
|
249
|
+
STRATEGY_STANDARD, STRATEGY_DEEP
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
strategy_map = {
|
|
253
|
+
'quick': STRATEGY_QUICK,
|
|
254
|
+
'standard': STRATEGY_STANDARD,
|
|
255
|
+
'deep': STRATEGY_DEEP
|
|
256
|
+
}
|
|
257
|
+
strategy = strategy_map.get(args.strategy, STRATEGY_STANDARD)
|
|
258
|
+
|
|
259
|
+
strategy.max_files_in_memory = min(
|
|
260
|
+
strategy.max_files_in_memory,
|
|
261
|
+
args.max_memory // 10
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
analyzer = StreamingAnalyzer(config, strategy)
|
|
265
|
+
|
|
266
|
+
if args.verbose:
|
|
267
|
+
def on_progress(update):
|
|
268
|
+
pct = update.get('percentage', 0)
|
|
269
|
+
print(f"\r[{pct:.0f}%] {update.get('message', '')}", end='', flush=True)
|
|
270
|
+
analyzer.set_progress_callback(on_progress)
|
|
271
|
+
|
|
272
|
+
print(f"Analyzing with {args.strategy} strategy...")
|
|
273
|
+
for update in analyzer.analyze_streaming(str(source_path)):
|
|
274
|
+
if update['type'] == 'complete':
|
|
275
|
+
if args.verbose:
|
|
276
|
+
print()
|
|
277
|
+
print(f"Completed in {update.get('elapsed_seconds', 0):.1f}s")
|
|
278
|
+
|
|
279
|
+
# Re-run standard analyzer for full results
|
|
280
|
+
# TODO: Modify streaming to accumulate results properly
|
|
281
|
+
analyzer = ProjectAnalyzer(config)
|
|
282
|
+
return analyzer.analyze_project(str(source_path))
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""CLI export functions — split into thematic modules.
|
|
2
|
+
|
|
3
|
+
Thin re-export so existing ``from .cli_exports import ...`` continues to work.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .formats import (
|
|
7
|
+
_export_simple_formats,
|
|
8
|
+
_export_yaml,
|
|
9
|
+
_export_mermaid,
|
|
10
|
+
_export_evolution,
|
|
11
|
+
_export_data_structures,
|
|
12
|
+
_export_context_fallback,
|
|
13
|
+
_export_readme,
|
|
14
|
+
_export_refactor_prompts,
|
|
15
|
+
_export_project_yaml,
|
|
16
|
+
_run_report,
|
|
17
|
+
)
|
|
18
|
+
from .prompt import (
|
|
19
|
+
_export_prompt_txt,
|
|
20
|
+
_export_chunked_prompt_txt,
|
|
21
|
+
)
|
|
22
|
+
from .code2logic import (
|
|
23
|
+
_export_code2logic,
|
|
24
|
+
)
|
|
25
|
+
from .orchestrator import (
|
|
26
|
+
_run_exports,
|
|
27
|
+
_export_single_project,
|
|
28
|
+
_export_chunked_results,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"_export_simple_formats",
|
|
33
|
+
"_export_yaml",
|
|
34
|
+
"_export_mermaid",
|
|
35
|
+
"_export_evolution",
|
|
36
|
+
"_export_data_structures",
|
|
37
|
+
"_export_context_fallback",
|
|
38
|
+
"_export_readme",
|
|
39
|
+
"_export_refactor_prompts",
|
|
40
|
+
"_export_project_yaml",
|
|
41
|
+
"_run_report",
|
|
42
|
+
"_export_prompt_txt",
|
|
43
|
+
"_export_chunked_prompt_txt",
|
|
44
|
+
"_export_code2logic",
|
|
45
|
+
"_run_exports",
|
|
46
|
+
"_export_single_project",
|
|
47
|
+
"_export_chunked_results",
|
|
48
|
+
]
|