code2llm 0.5.51__tar.gz → 0.5.52__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.51 → code2llm-0.5.52}/PKG-INFO +1 -1
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/__init__.py +1 -1
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/data_analysis.py +47 -31
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/type_inference.py +26 -20
- code2llm-0.5.52/code2llm/cli.py +69 -0
- code2llm-0.5.52/code2llm/cli_commands.py +220 -0
- code2llm-0.5.52/code2llm/cli_parser.py +238 -0
- code2llm-0.5.52/code2llm/core/core/file_analyzer.py +398 -0
- code2llm-0.5.52/code2llm/core/core/lang/__init__.py +11 -0
- code2llm-0.5.52/code2llm/core/core/lang/base.py +118 -0
- code2llm-0.5.52/code2llm/core/core/lang/cpp.py +125 -0
- code2llm-0.5.52/code2llm/core/core/lang/csharp.py +153 -0
- code2llm-0.5.52/code2llm/core/core/lang/generic.py +71 -0
- code2llm-0.5.52/code2llm/core/core/lang/go_lang.py +87 -0
- code2llm-0.5.52/code2llm/core/core/lang/java.py +95 -0
- code2llm-0.5.52/code2llm/core/core/lang/php.py +185 -0
- code2llm-0.5.52/code2llm/core/core/lang/ruby.py +186 -0
- code2llm-0.5.52/code2llm/core/core/lang/rust.py +94 -0
- code2llm-0.5.52/code2llm/core/core/lang/typescript.py +234 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/large_repo.py +47 -159
- code2llm-0.5.52/code2llm/core/repo_files.py +145 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/__init__.py +1 -1
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/PKG-INFO +1 -1
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/SOURCES.txt +14 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/pyproject.toml +1 -1
- code2llm-0.5.51/code2llm/cli.py +0 -511
- code2llm-0.5.51/code2llm/core/core/file_analyzer.py +0 -1681
- {code2llm-0.5.51 → code2llm-0.5.52}/LICENSE +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/README.md +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/__main__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/pipeline_detector.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/api.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_analysis.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_exports/code2logic.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_exports/formats.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_exports/orchestrator.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_exports/prompt.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/analyzer.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/config.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/core/__init__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/core/file_cache.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/core/file_filter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/core/refactoring.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/models.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/flow_constants.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/project_yaml_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon/helpers.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/generators/llm_task.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/generators/mermaid.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/requires.txt +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/top_level.txt +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/setup.cfg +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/setup.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_prompt_txt.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.51 → code2llm-0.5.52}/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.52
|
|
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
|
|
@@ -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.52"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -4,6 +4,35 @@ from typing import Any, Dict, List
|
|
|
4
4
|
from ..core.models import AnalysisResult
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
_INPUT_INDICATORS = ['parse', 'load', 'read', 'fetch', 'get', 'input', 'receive', 'extract']
|
|
8
|
+
_TRANSFORM_INDICATORS = ['transform', 'convert', 'process', 'validate', 'filter', 'map', 'reduce', 'compute']
|
|
9
|
+
_OUTPUT_INDICATORS = ['serialize', 'format', 'write', 'save', 'send', 'output', 'render', 'encode']
|
|
10
|
+
_MAX_PIPELINES = 15
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _categorize_functions(result: 'AnalysisResult'):
|
|
14
|
+
"""Categorize functions into input/transform/output based on name patterns."""
|
|
15
|
+
input_funcs, transform_funcs, output_funcs = [], [], []
|
|
16
|
+
for func_name, func in result.functions.items():
|
|
17
|
+
name_lower = func.name.lower()
|
|
18
|
+
if any(ind in name_lower for ind in _INPUT_INDICATORS):
|
|
19
|
+
input_funcs.append((func_name, func))
|
|
20
|
+
elif any(ind in name_lower for ind in _TRANSFORM_INDICATORS):
|
|
21
|
+
transform_funcs.append((func_name, func))
|
|
22
|
+
elif any(ind in name_lower for ind in _OUTPUT_INDICATORS):
|
|
23
|
+
output_funcs.append((func_name, func))
|
|
24
|
+
return input_funcs, transform_funcs, output_funcs
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _make_stage(label: str, func_name: str, func) -> Dict[str, str]:
|
|
28
|
+
"""Build a single pipeline stage dict."""
|
|
29
|
+
return {
|
|
30
|
+
'stage': label,
|
|
31
|
+
'function': func_name,
|
|
32
|
+
'description': func.docstring[:100] if func.docstring else 'N/A',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
7
36
|
class DataAnalyzer:
|
|
8
37
|
"""Analyze data flows, structures, and optimization opportunities."""
|
|
9
38
|
|
|
@@ -32,40 +61,27 @@ class DataAnalyzer:
|
|
|
32
61
|
|
|
33
62
|
def _find_data_pipelines(self, result: AnalysisResult) -> list:
|
|
34
63
|
"""Find data transformation pipelines in the codebase."""
|
|
64
|
+
input_funcs, transform_funcs, output_funcs = _categorize_functions(result)
|
|
65
|
+
|
|
35
66
|
pipelines = []
|
|
36
|
-
input_indicators = ['parse', 'load', 'read', 'fetch', 'get', 'input', 'receive', 'extract']
|
|
37
|
-
transform_indicators = ['transform', 'convert', 'process', 'validate', 'filter', 'map', 'reduce', 'compute']
|
|
38
|
-
output_indicators = ['serialize', 'format', 'write', 'save', 'send', 'output', 'render', 'encode']
|
|
39
|
-
|
|
40
|
-
input_funcs = []
|
|
41
|
-
transform_funcs = []
|
|
42
|
-
output_funcs = []
|
|
43
|
-
|
|
44
|
-
for func_name, func in result.functions.items():
|
|
45
|
-
name_lower = func.name.lower()
|
|
46
|
-
if any(ind in name_lower for ind in input_indicators):
|
|
47
|
-
input_funcs.append((func_name, func))
|
|
48
|
-
elif any(ind in name_lower for ind in transform_indicators):
|
|
49
|
-
transform_funcs.append((func_name, func))
|
|
50
|
-
elif any(ind in name_lower for ind in output_indicators):
|
|
51
|
-
output_funcs.append((func_name, func))
|
|
52
|
-
|
|
53
67
|
for in_name, in_func in input_funcs[:20]:
|
|
54
68
|
for t_name, t_func in transform_funcs[:30]:
|
|
55
|
-
if t_name in in_func.calls:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
if t_name not in in_func.calls:
|
|
70
|
+
continue
|
|
71
|
+
for out_name, out_func in output_funcs[:20]:
|
|
72
|
+
if out_name not in t_func.calls:
|
|
73
|
+
continue
|
|
74
|
+
pipelines.append({
|
|
75
|
+
'pipeline_id': f"pipeline_{len(pipelines)+1}",
|
|
76
|
+
'stages': [
|
|
77
|
+
_make_stage('input', in_name, in_func),
|
|
78
|
+
_make_stage('transform', t_name, t_func),
|
|
79
|
+
_make_stage('output', out_name, out_func),
|
|
80
|
+
],
|
|
81
|
+
'data_flow': f"{in_name} → {t_name} → {out_name}",
|
|
82
|
+
})
|
|
83
|
+
if len(pipelines) >= _MAX_PIPELINES:
|
|
84
|
+
return pipelines
|
|
69
85
|
return pipelines
|
|
70
86
|
|
|
71
87
|
def _find_state_patterns(self, result: AnalysisResult) -> list:
|
|
@@ -17,6 +17,15 @@ from ..core.models import FunctionInfo
|
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
|
+
# Arg name substring -> inferred type (checked in order)
|
|
21
|
+
ARG_NAME_TYPE_MAP = [
|
|
22
|
+
("path", "Path"),
|
|
23
|
+
("name", "str"),
|
|
24
|
+
("text", "str"),
|
|
25
|
+
("config", "Config"),
|
|
26
|
+
("result", "AnalysisResult"),
|
|
27
|
+
]
|
|
28
|
+
|
|
20
29
|
# Name pattern -> (consumed types, produced types)
|
|
21
30
|
NAME_PATTERNS: List[Tuple[List[str], List[str], List[str]]] = [
|
|
22
31
|
# (name_contains, consumed, produced)
|
|
@@ -282,26 +291,10 @@ class TypeInferenceEngine:
|
|
|
282
291
|
break
|
|
283
292
|
|
|
284
293
|
# Build arg list with inferred types
|
|
285
|
-
args = [
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
inferred_type = None
|
|
290
|
-
elif consumed:
|
|
291
|
-
inferred_type = consumed[0] if consumed else None
|
|
292
|
-
elif "path" in arg_name.lower():
|
|
293
|
-
inferred_type = "Path"
|
|
294
|
-
elif "name" in arg_name.lower() or "text" in arg_name.lower():
|
|
295
|
-
inferred_type = "str"
|
|
296
|
-
elif "config" in arg_name.lower():
|
|
297
|
-
inferred_type = "Config"
|
|
298
|
-
elif "result" in arg_name.lower():
|
|
299
|
-
inferred_type = "AnalysisResult"
|
|
300
|
-
args.append({
|
|
301
|
-
"name": arg_name,
|
|
302
|
-
"type": inferred_type,
|
|
303
|
-
"has_default": False,
|
|
304
|
-
})
|
|
294
|
+
args = [
|
|
295
|
+
{"name": a, "type": self._infer_arg_type(a, consumed), "has_default": False}
|
|
296
|
+
for a in fi.args
|
|
297
|
+
]
|
|
305
298
|
|
|
306
299
|
ret = produced[0] if produced else None
|
|
307
300
|
has_any = ret is not None or any(a["type"] for a in args)
|
|
@@ -313,3 +306,16 @@ class TypeInferenceEngine:
|
|
|
313
306
|
"name": fi.name,
|
|
314
307
|
"qualified_name": fi.qualified_name,
|
|
315
308
|
}
|
|
309
|
+
|
|
310
|
+
@staticmethod
|
|
311
|
+
def _infer_arg_type(arg_name: str, consumed: List[str]) -> Optional[str]:
|
|
312
|
+
"""Infer type for a single argument from consumed types or name patterns."""
|
|
313
|
+
if arg_name == "self":
|
|
314
|
+
return None
|
|
315
|
+
if consumed:
|
|
316
|
+
return consumed[0]
|
|
317
|
+
arg_lower = arg_name.lower()
|
|
318
|
+
for pattern, typ in ARG_NAME_TYPE_MAP:
|
|
319
|
+
if pattern in arg_lower:
|
|
320
|
+
return typ
|
|
321
|
+
return None
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
code2llm - CLI for Python code flow analysis
|
|
4
|
+
|
|
5
|
+
Analyze control flow, data flow, and call graphs of Python codebases.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from .cli_parser import create_parser
|
|
11
|
+
from .cli_commands import (
|
|
12
|
+
handle_special_commands, validate_and_setup, print_start_info,
|
|
13
|
+
validate_chunked_output,
|
|
14
|
+
# Backward compatibility aliases
|
|
15
|
+
handle_report_command as _handle_report_command,
|
|
16
|
+
generate_llm_context,
|
|
17
|
+
)
|
|
18
|
+
from .cli_exports import (
|
|
19
|
+
_export_evolution, _export_data_structures, _export_context_fallback,
|
|
20
|
+
_export_readme, _export_code2logic, _export_prompt_txt, _run_exports,
|
|
21
|
+
_export_simple_formats, _export_yaml, _export_mermaid, _export_refactor_prompts,
|
|
22
|
+
_export_project_yaml, _run_report,
|
|
23
|
+
)
|
|
24
|
+
from .cli_analysis import _run_analysis
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Backward compatibility aliases
|
|
28
|
+
_handle_special_commands = handle_special_commands
|
|
29
|
+
_validate_and_setup = validate_and_setup
|
|
30
|
+
_print_start_info = print_start_info
|
|
31
|
+
_validate_chunked_output = validate_chunked_output
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main():
|
|
35
|
+
"""Main CLI entry point."""
|
|
36
|
+
# Handle special sub-commands first
|
|
37
|
+
special_result = handle_special_commands()
|
|
38
|
+
if special_result is not None:
|
|
39
|
+
return special_result
|
|
40
|
+
|
|
41
|
+
# Parse arguments
|
|
42
|
+
parser = create_parser()
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
|
|
45
|
+
source_path, output_dir = validate_and_setup(args)
|
|
46
|
+
print_start_info(args, source_path, output_dir)
|
|
47
|
+
|
|
48
|
+
# Validate mode - only check existing output
|
|
49
|
+
if args.validate:
|
|
50
|
+
is_valid = validate_chunked_output(output_dir, args)
|
|
51
|
+
return 0 if is_valid else 1
|
|
52
|
+
|
|
53
|
+
# Analyze → Export
|
|
54
|
+
result = _run_analysis(args, source_path, output_dir)
|
|
55
|
+
_run_exports(args, result, output_dir, source_path=source_path)
|
|
56
|
+
|
|
57
|
+
# Auto-validate after chunked analysis
|
|
58
|
+
if args.chunk and args.verbose:
|
|
59
|
+
print(f"\n🔍 Auto-validating chunked output...")
|
|
60
|
+
validate_chunked_output(output_dir, args)
|
|
61
|
+
|
|
62
|
+
if args.verbose:
|
|
63
|
+
print(f"\nAll outputs saved to: {output_dir}")
|
|
64
|
+
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == '__main__':
|
|
69
|
+
sys.exit(main())
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""CLI subcommands and validation for code2llm."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from .cli_exports import _run_report
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def handle_special_commands() -> Optional[int]:
|
|
12
|
+
"""Handle special sub-commands (llm-flow, llm-context, report)."""
|
|
13
|
+
if len(sys.argv) > 1 and sys.argv[1] == 'llm-flow':
|
|
14
|
+
from .generators.llm_flow import main as llm_flow_main
|
|
15
|
+
return llm_flow_main(sys.argv[2:])
|
|
16
|
+
if len(sys.argv) > 1 and sys.argv[1] == 'llm-context':
|
|
17
|
+
return generate_llm_context(sys.argv[2:])
|
|
18
|
+
if len(sys.argv) > 1 and sys.argv[1] == 'report':
|
|
19
|
+
return handle_report_command(sys.argv[2:])
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def handle_report_command(args_list) -> int:
|
|
24
|
+
"""Generate views from an existing project.yaml.
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
code2llm report --format toon # → project.toon
|
|
28
|
+
code2llm report --format context # → context.md
|
|
29
|
+
code2llm report --format article # → status.md
|
|
30
|
+
code2llm report --format html # → dashboard.html
|
|
31
|
+
code2llm report --format all # → all views
|
|
32
|
+
"""
|
|
33
|
+
import argparse
|
|
34
|
+
|
|
35
|
+
parser = argparse.ArgumentParser(
|
|
36
|
+
prog='code2llm report',
|
|
37
|
+
description='Generate views from project.yaml (single source of truth)',
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
'--input', '-i',
|
|
41
|
+
default='./project.yaml',
|
|
42
|
+
help='Path to project.yaml (default: ./project.yaml)',
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
'--format', '-f',
|
|
46
|
+
dest='report_format',
|
|
47
|
+
default='all',
|
|
48
|
+
help='Output format: toon, context, article, html, all (default: all)',
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
'-o', '--output',
|
|
52
|
+
default='.',
|
|
53
|
+
help='Output directory (default: current directory)',
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
'-v', '--verbose',
|
|
57
|
+
action='store_true',
|
|
58
|
+
help='Verbose output',
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
args = parser.parse_args(args_list)
|
|
62
|
+
|
|
63
|
+
input_path = Path(args.input)
|
|
64
|
+
if not input_path.exists():
|
|
65
|
+
print(f"Error: project.yaml not found: {input_path}", file=sys.stderr)
|
|
66
|
+
print("Run 'code2llm <source> -f project-yaml' first to generate it.", file=sys.stderr)
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
output_dir = Path(args.output)
|
|
70
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
if args.verbose:
|
|
73
|
+
print(f"Generating views from: {input_path}")
|
|
74
|
+
print(f"Output directory: {output_dir}")
|
|
75
|
+
|
|
76
|
+
_run_report(args, str(input_path), output_dir)
|
|
77
|
+
|
|
78
|
+
if args.verbose:
|
|
79
|
+
print(f"\nAll views saved to: {output_dir}")
|
|
80
|
+
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def validate_and_setup(args) -> tuple[Path, Path]:
|
|
85
|
+
"""Validate source path and setup output directory."""
|
|
86
|
+
if not args.source:
|
|
87
|
+
print("Error: missing required argument: source", file=sys.stderr)
|
|
88
|
+
print("Usage: code2llm <source> [options]", file=sys.stderr)
|
|
89
|
+
print(" or: code2llm llm-flow [options]", file=sys.stderr)
|
|
90
|
+
sys.exit(2)
|
|
91
|
+
|
|
92
|
+
source_path = Path(args.source)
|
|
93
|
+
if not source_path.exists():
|
|
94
|
+
print(f"Error: Source path not found: {source_path}", file=sys.stderr)
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
|
|
97
|
+
output_dir = Path(args.output)
|
|
98
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
return source_path, output_dir
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def print_start_info(args, source_path: Path, output_dir: Path) -> None:
|
|
103
|
+
"""Print analysis start information if verbose."""
|
|
104
|
+
if args.verbose:
|
|
105
|
+
print(f"Analyzing: {source_path}")
|
|
106
|
+
print(f"Mode: {args.mode}")
|
|
107
|
+
print(f"Output: {output_dir}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def validate_chunked_output(output_dir: Path, args) -> bool:
|
|
111
|
+
"""Validate generated chunked output.
|
|
112
|
+
|
|
113
|
+
Checks:
|
|
114
|
+
1. All chunks have required files (analysis.toon, context.md, evolution.toon)
|
|
115
|
+
2. Files are not empty
|
|
116
|
+
3. Report summary
|
|
117
|
+
|
|
118
|
+
Returns True if valid, False otherwise.
|
|
119
|
+
"""
|
|
120
|
+
if not output_dir.exists():
|
|
121
|
+
print(f"✗ Output directory does not exist: {output_dir}", file=sys.stderr)
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
# Find all chunk directories
|
|
125
|
+
chunk_dirs = [d for d in output_dir.iterdir() if d.is_dir()]
|
|
126
|
+
|
|
127
|
+
if not chunk_dirs:
|
|
128
|
+
print(f"✗ No chunk directories found in: {output_dir}", file=sys.stderr)
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
required_files = ['analysis.toon', 'context.md', 'evolution.toon']
|
|
132
|
+
issues = []
|
|
133
|
+
valid_chunks = []
|
|
134
|
+
|
|
135
|
+
print(f"\n🔍 Validating {len(chunk_dirs)} chunks in: {output_dir}")
|
|
136
|
+
print("-" * 50)
|
|
137
|
+
|
|
138
|
+
for chunk_dir in sorted(chunk_dirs):
|
|
139
|
+
chunk_name = chunk_dir.name
|
|
140
|
+
chunk_issues = []
|
|
141
|
+
|
|
142
|
+
for req_file in required_files:
|
|
143
|
+
file_path = chunk_dir / req_file
|
|
144
|
+
if not file_path.exists():
|
|
145
|
+
chunk_issues.append(f" missing {req_file}")
|
|
146
|
+
elif file_path.stat().st_size == 0:
|
|
147
|
+
chunk_issues.append(f" empty {req_file}")
|
|
148
|
+
|
|
149
|
+
if chunk_issues:
|
|
150
|
+
issues.append((chunk_name, chunk_issues))
|
|
151
|
+
print(f"✗ {chunk_name}")
|
|
152
|
+
for issue in chunk_issues:
|
|
153
|
+
print(f" {issue}")
|
|
154
|
+
else:
|
|
155
|
+
# Get file sizes
|
|
156
|
+
sizes = []
|
|
157
|
+
for req_file in required_files:
|
|
158
|
+
size = (chunk_dir / req_file).stat().st_size
|
|
159
|
+
sizes.append(f"{req_file}:{size//1024}KB" if size > 1024 else f"{req_file}:{size}B")
|
|
160
|
+
valid_chunks.append(chunk_name)
|
|
161
|
+
print(f"✓ {chunk_name} ({', '.join(sizes)})")
|
|
162
|
+
|
|
163
|
+
print("-" * 50)
|
|
164
|
+
print(f"\n📊 Validation Summary:")
|
|
165
|
+
print(f" Total chunks: {len(chunk_dirs)}")
|
|
166
|
+
print(f" Valid: {len(valid_chunks)}")
|
|
167
|
+
print(f" Issues: {len(issues)}")
|
|
168
|
+
|
|
169
|
+
if issues:
|
|
170
|
+
print(f"\n⚠️ {len(issues)} chunk(s) have issues:")
|
|
171
|
+
for chunk_name, chunk_issues in issues:
|
|
172
|
+
print(f" - {chunk_name}")
|
|
173
|
+
return False
|
|
174
|
+
else:
|
|
175
|
+
print(f"\n✅ All {len(valid_chunks)} chunks are valid!")
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def generate_llm_context(args_list):
|
|
180
|
+
"""Quick command to generate LLM context only."""
|
|
181
|
+
import argparse
|
|
182
|
+
|
|
183
|
+
parser = argparse.ArgumentParser(
|
|
184
|
+
prog='code2llm llm-context',
|
|
185
|
+
description='Generate LLM-friendly context for a project'
|
|
186
|
+
)
|
|
187
|
+
parser.add_argument('source', help='Path to Python project')
|
|
188
|
+
parser.add_argument('-o', '--output', default='./llm_context.md', help='Output file path')
|
|
189
|
+
parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
|
|
190
|
+
|
|
191
|
+
args = parser.parse_args(args_list)
|
|
192
|
+
|
|
193
|
+
from pathlib import Path
|
|
194
|
+
from . import ProjectAnalyzer, FAST_CONFIG
|
|
195
|
+
from .exporters import ContextExporter
|
|
196
|
+
|
|
197
|
+
source_path = Path(args.source)
|
|
198
|
+
if not source_path.exists():
|
|
199
|
+
print(f"Error: Source path not found: {source_path}", file=sys.stderr)
|
|
200
|
+
return 1
|
|
201
|
+
|
|
202
|
+
if args.verbose:
|
|
203
|
+
print(f"Generating LLM context for: {source_path}")
|
|
204
|
+
|
|
205
|
+
# Use fast config with parallel disabled for stability
|
|
206
|
+
FAST_CONFIG.performance.parallel_enabled = False
|
|
207
|
+
|
|
208
|
+
analyzer = ProjectAnalyzer(FAST_CONFIG)
|
|
209
|
+
result = analyzer.analyze_project(str(source_path))
|
|
210
|
+
|
|
211
|
+
exporter = ContextExporter()
|
|
212
|
+
exporter.export(result, args.output)
|
|
213
|
+
|
|
214
|
+
# Print summary
|
|
215
|
+
print(f"\n✓ LLM context generated: {args.output}")
|
|
216
|
+
print(f" Functions: {len(result.functions)}")
|
|
217
|
+
print(f" Classes: {len(result.classes)}")
|
|
218
|
+
print(f" Modules: {len(result.modules)}")
|
|
219
|
+
|
|
220
|
+
return 0
|