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.
Files changed (120) hide show
  1. {code2llm-0.5.51 → code2llm-0.5.52}/PKG-INFO +1 -1
  2. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/__init__.py +1 -1
  3. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/data_analysis.py +47 -31
  4. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/type_inference.py +26 -20
  5. code2llm-0.5.52/code2llm/cli.py +69 -0
  6. code2llm-0.5.52/code2llm/cli_commands.py +220 -0
  7. code2llm-0.5.52/code2llm/cli_parser.py +238 -0
  8. code2llm-0.5.52/code2llm/core/core/file_analyzer.py +398 -0
  9. code2llm-0.5.52/code2llm/core/core/lang/__init__.py +11 -0
  10. code2llm-0.5.52/code2llm/core/core/lang/base.py +118 -0
  11. code2llm-0.5.52/code2llm/core/core/lang/cpp.py +125 -0
  12. code2llm-0.5.52/code2llm/core/core/lang/csharp.py +153 -0
  13. code2llm-0.5.52/code2llm/core/core/lang/generic.py +71 -0
  14. code2llm-0.5.52/code2llm/core/core/lang/go_lang.py +87 -0
  15. code2llm-0.5.52/code2llm/core/core/lang/java.py +95 -0
  16. code2llm-0.5.52/code2llm/core/core/lang/php.py +185 -0
  17. code2llm-0.5.52/code2llm/core/core/lang/ruby.py +186 -0
  18. code2llm-0.5.52/code2llm/core/core/lang/rust.py +94 -0
  19. code2llm-0.5.52/code2llm/core/core/lang/typescript.py +234 -0
  20. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/large_repo.py +47 -159
  21. code2llm-0.5.52/code2llm/core/repo_files.py +145 -0
  22. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/__init__.py +1 -1
  23. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/PKG-INFO +1 -1
  24. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/SOURCES.txt +14 -0
  25. {code2llm-0.5.51 → code2llm-0.5.52}/pyproject.toml +1 -1
  26. code2llm-0.5.51/code2llm/cli.py +0 -511
  27. code2llm-0.5.51/code2llm/core/core/file_analyzer.py +0 -1681
  28. {code2llm-0.5.51 → code2llm-0.5.52}/LICENSE +0 -0
  29. {code2llm-0.5.51 → code2llm-0.5.52}/README.md +0 -0
  30. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/__main__.py +0 -0
  31. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/__init__.py +0 -0
  32. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/call_graph.py +0 -0
  33. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/cfg.py +0 -0
  34. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/coupling.py +0 -0
  35. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/dfg.py +0 -0
  36. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/pipeline_detector.py +0 -0
  37. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/side_effects.py +0 -0
  38. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/analysis/smells.py +0 -0
  39. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/api.py +0 -0
  40. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_analysis.py +0 -0
  41. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_exports/__init__.py +0 -0
  42. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_exports/code2logic.py +0 -0
  43. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_exports/formats.py +0 -0
  44. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_exports/orchestrator.py +0 -0
  45. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/cli_exports/prompt.py +0 -0
  46. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/__init__.py +0 -0
  47. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/analyzer.py +0 -0
  48. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/config.py +0 -0
  49. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/core/__init__.py +0 -0
  50. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/core/file_cache.py +0 -0
  51. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/core/file_filter.py +0 -0
  52. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/core/refactoring.py +0 -0
  53. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/models.py +0 -0
  54. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/__init__.py +0 -0
  55. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/cache.py +0 -0
  56. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/incremental.py +0 -0
  57. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/prioritizer.py +0 -0
  58. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/scanner.py +0 -0
  59. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming/strategies.py +0 -0
  60. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/streaming_analyzer.py +0 -0
  61. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/core/toon_size_manager.py +0 -0
  62. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/__init__.py +0 -0
  63. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/article_view.py +0 -0
  64. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/base.py +0 -0
  65. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/context_exporter.py +0 -0
  66. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/context_view.py +0 -0
  67. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/evolution_exporter.py +0 -0
  68. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/flow_constants.py +0 -0
  69. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/flow_exporter.py +0 -0
  70. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/flow_renderer.py +0 -0
  71. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/html_dashboard.py +0 -0
  72. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/json_exporter.py +0 -0
  73. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/llm_exporter.py +0 -0
  74. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/map_exporter.py +0 -0
  75. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/mermaid_exporter.py +0 -0
  76. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/project_yaml_exporter.py +0 -0
  77. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/readme_exporter.py +0 -0
  78. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/report_generators.py +0 -0
  79. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon/__init__.py +0 -0
  80. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon/helpers.py +0 -0
  81. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon/metrics.py +0 -0
  82. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon/module_detail.py +0 -0
  83. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon/renderer.py +0 -0
  84. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon.py +0 -0
  85. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/toon_view.py +0 -0
  86. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/validate_project.py +0 -0
  87. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/exporters/yaml_exporter.py +0 -0
  88. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/generators/__init__.py +0 -0
  89. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/generators/llm_flow.py +0 -0
  90. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/generators/llm_task.py +0 -0
  91. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/generators/mermaid.py +0 -0
  92. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/config.py +0 -0
  93. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/entity_resolution.py +0 -0
  94. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/intent_matching.py +0 -0
  95. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/normalization.py +0 -0
  96. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/nlp/pipeline.py +0 -0
  97. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/patterns/__init__.py +0 -0
  98. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/patterns/detector.py +0 -0
  99. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/refactor/__init__.py +0 -0
  100. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm/refactor/prompt_engine.py +0 -0
  101. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/dependency_links.txt +0 -0
  102. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/entry_points.txt +0 -0
  103. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/requires.txt +0 -0
  104. {code2llm-0.5.51 → code2llm-0.5.52}/code2llm.egg-info/top_level.txt +0 -0
  105. {code2llm-0.5.51 → code2llm-0.5.52}/setup.cfg +0 -0
  106. {code2llm-0.5.51 → code2llm-0.5.52}/setup.py +0 -0
  107. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_advanced_analysis.py +0 -0
  108. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_analyzer.py +0 -0
  109. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_deep_analysis.py +0 -0
  110. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_edge_cases.py +0 -0
  111. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_flow_exporter.py +0 -0
  112. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_format_quality.py +0 -0
  113. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_multilanguage_e2e.py +0 -0
  114. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_nlp_pipeline.py +0 -0
  115. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_nonpython_cc_calls.py +0 -0
  116. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_pipeline_detector.py +0 -0
  117. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_prompt_engine.py +0 -0
  118. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_prompt_txt.py +0 -0
  119. {code2llm-0.5.51 → code2llm-0.5.52}/tests/test_refactoring_engine.py +0 -0
  120. {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.51
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.51"
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
- for out_name, out_func in output_funcs[:20]:
57
- if out_name in t_func.calls:
58
- pipelines.append({
59
- 'pipeline_id': f"pipeline_{len(pipelines)+1}",
60
- 'stages': [
61
- {'stage': 'input', 'function': in_name, 'description': in_func.docstring[:100] if in_func.docstring else 'N/A'},
62
- {'stage': 'transform', 'function': t_name, 'description': t_func.docstring[:100] if t_func.docstring else 'N/A'},
63
- {'stage': 'output', 'function': out_name, 'description': out_func.docstring[:100] if out_func.docstring else 'N/A'},
64
- ],
65
- 'data_flow': f"{in_name} → {t_name} → {out_name}",
66
- })
67
- if len(pipelines) >= 15:
68
- return pipelines
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
- for arg_name in fi.args:
287
- inferred_type = None
288
- if arg_name == "self":
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