code2llm 0.5.108__tar.gz → 0.5.110__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 (128) hide show
  1. {code2llm-0.5.108 → code2llm-0.5.110}/PKG-INFO +3 -2
  2. {code2llm-0.5.108 → code2llm-0.5.110}/README.md +1 -1
  3. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/__init__.py +1 -1
  4. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/cli_commands.py +66 -36
  5. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/yaml_exporter.py +30 -14
  6. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/nlp/__init__.py +1 -1
  7. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm.egg-info/PKG-INFO +3 -2
  8. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm.egg-info/requires.txt +1 -0
  9. {code2llm-0.5.108 → code2llm-0.5.110}/pyproject.toml +6 -1
  10. {code2llm-0.5.108 → code2llm-0.5.110}/setup.py +1 -1
  11. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_edge_cases.py +6 -5
  12. {code2llm-0.5.108 → code2llm-0.5.110}/LICENSE +0 -0
  13. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/__main__.py +0 -0
  14. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/__init__.py +0 -0
  15. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/call_graph.py +0 -0
  16. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/cfg.py +0 -0
  17. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/coupling.py +0 -0
  18. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/data_analysis.py +0 -0
  19. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/dfg.py +0 -0
  20. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/pipeline_detector.py +0 -0
  21. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/side_effects.py +0 -0
  22. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/smells.py +0 -0
  23. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/type_inference.py +0 -0
  24. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/utils/__init__.py +0 -0
  25. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/analysis/utils/ast_helpers.py +0 -0
  26. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/api.py +0 -0
  27. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/cli.py +0 -0
  28. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/cli_analysis.py +0 -0
  29. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/cli_exports/__init__.py +0 -0
  30. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/cli_exports/code2logic.py +0 -0
  31. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/cli_exports/formats.py +0 -0
  32. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/cli_exports/orchestrator.py +0 -0
  33. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/cli_exports/prompt.py +0 -0
  34. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/cli_parser.py +0 -0
  35. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/__init__.py +0 -0
  36. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/analyzer.py +0 -0
  37. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/ast_registry.py +0 -0
  38. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/config.py +0 -0
  39. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/export_pipeline.py +0 -0
  40. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/file_analyzer.py +0 -0
  41. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/file_cache.py +0 -0
  42. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/file_filter.py +0 -0
  43. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/gitignore.py +0 -0
  44. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/incremental.py +0 -0
  45. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/__init__.py +0 -0
  46. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/base.py +0 -0
  47. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/cpp.py +0 -0
  48. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/csharp.py +0 -0
  49. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/generic.py +0 -0
  50. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/go_lang.py +0 -0
  51. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/java.py +0 -0
  52. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/php.py +0 -0
  53. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/ruby.py +0 -0
  54. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/rust.py +0 -0
  55. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/ts_extractors.py +0 -0
  56. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/ts_parser.py +0 -0
  57. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/lang/typescript.py +0 -0
  58. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/large_repo.py +0 -0
  59. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/models.py +0 -0
  60. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/refactoring.py +0 -0
  61. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/repo_files.py +0 -0
  62. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/streaming/__init__.py +0 -0
  63. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/streaming/cache.py +0 -0
  64. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/streaming/incremental.py +0 -0
  65. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/streaming/prioritizer.py +0 -0
  66. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/streaming/scanner.py +0 -0
  67. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/streaming/strategies.py +0 -0
  68. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/streaming_analyzer.py +0 -0
  69. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/core/toon_size_manager.py +0 -0
  70. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/__init__.py +0 -0
  71. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/article_view.py +0 -0
  72. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/base.py +0 -0
  73. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/context_exporter.py +0 -0
  74. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/context_view.py +0 -0
  75. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/evolution_exporter.py +0 -0
  76. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/flow_constants.py +0 -0
  77. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/flow_exporter.py +0 -0
  78. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/flow_renderer.py +0 -0
  79. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/html_dashboard.py +0 -0
  80. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/index_generator.py +0 -0
  81. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/json_exporter.py +0 -0
  82. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/llm_exporter.py +0 -0
  83. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/map_exporter.py +0 -0
  84. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/mermaid_exporter.py +0 -0
  85. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
  86. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/project_yaml_exporter.py +0 -0
  87. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/readme_exporter.py +0 -0
  88. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/report_generators.py +0 -0
  89. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/toon/__init__.py +0 -0
  90. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/toon/helpers.py +0 -0
  91. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/toon/metrics.py +0 -0
  92. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/toon/module_detail.py +0 -0
  93. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/toon/renderer.py +0 -0
  94. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/toon.py +0 -0
  95. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/toon_view.py +0 -0
  96. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/exporters/validate_project.py +0 -0
  97. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/generators/__init__.py +0 -0
  98. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/generators/llm_flow.py +0 -0
  99. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/generators/llm_task.py +0 -0
  100. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/generators/mermaid.py +0 -0
  101. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/nlp/config.py +0 -0
  102. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/nlp/entity_resolution.py +0 -0
  103. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/nlp/intent_matching.py +0 -0
  104. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/nlp/normalization.py +0 -0
  105. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/nlp/pipeline.py +0 -0
  106. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/patterns/__init__.py +0 -0
  107. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/patterns/detector.py +0 -0
  108. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/refactor/__init__.py +0 -0
  109. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm/refactor/prompt_engine.py +0 -0
  110. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm.egg-info/SOURCES.txt +0 -0
  111. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm.egg-info/dependency_links.txt +0 -0
  112. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm.egg-info/entry_points.txt +0 -0
  113. {code2llm-0.5.108 → code2llm-0.5.110}/code2llm.egg-info/top_level.txt +0 -0
  114. {code2llm-0.5.108 → code2llm-0.5.110}/setup.cfg +0 -0
  115. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_advanced_analysis.py +0 -0
  116. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_analyzer.py +0 -0
  117. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_deep_analysis.py +0 -0
  118. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_flow_exporter.py +0 -0
  119. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_format_quality.py +0 -0
  120. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_multilanguage_e2e.py +0 -0
  121. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_nlp_pipeline.py +0 -0
  122. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_nonpython_cc_calls.py +0 -0
  123. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_pipeline_detector.py +0 -0
  124. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_project_toon_export.py +0 -0
  125. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_prompt_engine.py +0 -0
  126. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_prompt_txt.py +0 -0
  127. {code2llm-0.5.108 → code2llm-0.5.110}/tests/test_refactoring_engine.py +0 -0
  128. {code2llm-0.5.108 → code2llm-0.5.110}/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.108
3
+ Version: 0.5.110
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
@@ -50,6 +50,7 @@ Requires-Dist: tree-sitter-ruby>=0.21
50
50
  Provides-Extra: dev
51
51
  Requires-Dist: pytest>=6.2; extra == "dev"
52
52
  Requires-Dist: pytest-cov>=2.12; extra == "dev"
53
+ Requires-Dist: pytest-xdist>=3.0; extra == "dev"
53
54
  Requires-Dist: black>=21.0; extra == "dev"
54
55
  Requires-Dist: flake8>=3.9; extra == "dev"
55
56
  Requires-Dist: mypy>=0.910; extra == "dev"
@@ -66,7 +67,7 @@ Dynamic: requires-python
66
67
 
67
68
  ## AI Cost Tracking
68
69
 
69
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.108-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
70
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.110-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
70
71
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-53.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
71
72
 
72
73
  - 🤖 **LLM usage:** $7.5000 (154 commits)
@@ -3,7 +3,7 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.108-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.110-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
7
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-53.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
9
  - 🤖 **LLM usage:** $7.5000 (154 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.108"
11
+ __version__ = "0.5.110"
12
12
  __author__ = "STTS Project"
13
13
 
14
14
  # Core analysis components (lightweight, always needed)
@@ -109,71 +109,101 @@ def print_start_info(args, source_path: Path, output_dir: Path) -> None:
109
109
 
110
110
  def validate_chunked_output(output_dir: Path, args) -> bool:
111
111
  """Validate generated chunked output.
112
-
112
+
113
113
  Checks:
114
114
  1. All chunks have required files (analysis.toon, context.md, evolution.toon.yaml)
115
115
  2. Files are not empty
116
116
  3. Report summary
117
-
117
+
118
118
  Returns True if valid, False otherwise.
119
119
  """
120
120
  if not output_dir.exists():
121
121
  print(f"✗ Output directory does not exist: {output_dir}", file=sys.stderr)
122
122
  return False
123
-
124
- # Find all chunk directories
125
- chunk_dirs = [d for d in output_dir.iterdir() if d.is_dir()]
126
-
123
+
124
+ chunk_dirs = _get_chunk_dirs(output_dir)
127
125
  if not chunk_dirs:
128
126
  print(f"✗ No chunk directories found in: {output_dir}", file=sys.stderr)
129
127
  return False
130
-
128
+
131
129
  required_files = ['analysis.toon.yaml', 'context.md', 'evolution.toon.yaml']
130
+ issues, valid_chunks = _validate_chunks(chunk_dirs, required_files)
131
+
132
+ _print_validation_summary(chunk_dirs, valid_chunks, issues)
133
+ return len(issues) == 0
134
+
135
+
136
+ def _get_chunk_dirs(output_dir: Path) -> list[Path]:
137
+ """Find all chunk directories in output directory."""
138
+ return [d for d in output_dir.iterdir() if d.is_dir()]
139
+
140
+
141
+ def _validate_chunks(chunk_dirs: list[Path], required_files: list[str]) -> tuple:
142
+ """Validate all chunks and return issues and valid chunks."""
132
143
  issues = []
133
144
  valid_chunks = []
134
-
135
- print(f"\n🔍 Validating {len(chunk_dirs)} chunks in: {output_dir}")
145
+
146
+ print(f"\n🔍 Validating {len(chunk_dirs)} chunks")
136
147
  print("-" * 50)
137
-
148
+
138
149
  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
-
150
+ chunk_issues = _validate_single_chunk(chunk_dir, required_files)
151
+
149
152
  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}")
153
+ issues.append((chunk_dir.name, chunk_issues))
154
+ _print_chunk_errors(chunk_dir.name, chunk_issues)
154
155
  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
-
156
+ sizes = _get_file_sizes(chunk_dir, required_files)
157
+ valid_chunks.append(chunk_dir.name)
158
+ print(f"✓ {chunk_dir.name} ({sizes})")
159
+
163
160
  print("-" * 50)
161
+ return issues, valid_chunks
162
+
163
+
164
+ def _validate_single_chunk(chunk_dir: Path, required_files: list[str]) -> list[str]:
165
+ """Validate a single chunk directory. Returns list of issues."""
166
+ chunk_issues = []
167
+
168
+ for req_file in required_files:
169
+ file_path = chunk_dir / req_file
170
+ if not file_path.exists():
171
+ chunk_issues.append(f" missing {req_file}")
172
+ elif file_path.stat().st_size == 0:
173
+ chunk_issues.append(f" empty {req_file}")
174
+
175
+ return chunk_issues
176
+
177
+
178
+ def _get_file_sizes(chunk_dir: Path, required_files: list[str]) -> str:
179
+ """Get formatted file sizes for a chunk."""
180
+ sizes = []
181
+ for req_file in required_files:
182
+ size = (chunk_dir / req_file).stat().st_size
183
+ sizes.append(f"{req_file}:{size//1024}KB" if size > 1024 else f"{req_file}:{size}B")
184
+ return ", ".join(sizes)
185
+
186
+
187
+ def _print_chunk_errors(chunk_name: str, chunk_issues: list[str]) -> None:
188
+ """Print errors for a chunk."""
189
+ print(f"✗ {chunk_name}")
190
+ for issue in chunk_issues:
191
+ print(f" {issue}")
192
+
193
+
194
+ def _print_validation_summary(chunk_dirs: list, valid_chunks: list, issues: list) -> None:
195
+ """Print validation summary report."""
164
196
  print(f"\n📊 Validation Summary:")
165
197
  print(f" Total chunks: {len(chunk_dirs)}")
166
198
  print(f" Valid: {len(valid_chunks)}")
167
199
  print(f" Issues: {len(issues)}")
168
-
200
+
169
201
  if issues:
170
202
  print(f"\n⚠️ {len(issues)} chunk(s) have issues:")
171
- for chunk_name, chunk_issues in issues:
203
+ for chunk_name, _ in issues:
172
204
  print(f" - {chunk_name}")
173
- return False
174
205
  else:
175
206
  print(f"\n✅ All {len(valid_chunks)} chunks are valid!")
176
- return True
177
207
 
178
208
 
179
209
  def generate_llm_context(args_list):
@@ -132,24 +132,40 @@ class YAMLExporter(Exporter):
132
132
  connected: Set[str] = set()
133
133
  edges: List[Dict] = []
134
134
  seen_pairs: Set[Tuple[str, str]] = set()
135
-
135
+
136
136
  for func_name, fi in result.functions.items():
137
- for callee in fi.calls[:max_calls]:
138
- resolved = self._resolve_callee(callee, result.functions)
139
- if resolved and resolved != func_name:
140
- connected.add(func_name)
141
- connected.add(resolved)
142
- pair = (func_name, resolved)
143
- if pair not in seen_pairs:
144
- seen_pairs.add(pair)
145
- edges.append(self._create_edge(func_name, resolved, callee))
146
- if len(edges) >= max_edges:
147
- return connected, edges
148
137
  if len(edges) >= max_edges:
149
- return connected, edges
150
-
138
+ break
139
+ self._process_function_calls(
140
+ func_name, fi, result.functions, max_calls, max_edges,
141
+ connected, edges, seen_pairs
142
+ )
143
+
151
144
  return connected, edges
152
145
 
146
+ def _process_function_calls(
147
+ self, func_name: str, fi: FunctionInfo, functions: Dict[str, FunctionInfo],
148
+ max_calls: int, max_edges: int, connected: Set[str], edges: List[Dict],
149
+ seen_pairs: Set[Tuple[str, str]]
150
+ ) -> None:
151
+ """Process calls for a single function."""
152
+ for callee in fi.calls[:max_calls]:
153
+ if len(edges) >= max_edges:
154
+ break
155
+ resolved = self._resolve_callee(callee, functions)
156
+ if self._should_add_edge(func_name, resolved, seen_pairs):
157
+ connected.add(func_name)
158
+ connected.add(resolved)
159
+ seen_pairs.add((func_name, resolved))
160
+ edges.append(self._create_edge(func_name, resolved, callee))
161
+
162
+ @staticmethod
163
+ def _should_add_edge(func_name: str, resolved: Optional[str], seen_pairs: Set[Tuple[str, str]]) -> bool:
164
+ """Check if edge should be added (valid callee and not duplicate)."""
165
+ if not resolved or resolved == func_name:
166
+ return False
167
+ return (func_name, resolved) not in seen_pairs
168
+
153
169
  @staticmethod
154
170
  def _create_edge(caller: str, resolved: str, callee: str) -> Dict:
155
171
  """Create edge dict with call type classification."""
@@ -4,7 +4,7 @@ Provides query normalization, intent matching, and entity resolution
4
4
  with multilingual support and fuzzy matching.
5
5
  """
6
6
 
7
- __version__ = "0.5.108"
7
+ __version__ = "0.5.110"
8
8
 
9
9
  from .pipeline import NLPPipeline
10
10
  from .normalization import QueryNormalizer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2llm
3
- Version: 0.5.108
3
+ Version: 0.5.110
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
@@ -50,6 +50,7 @@ Requires-Dist: tree-sitter-ruby>=0.21
50
50
  Provides-Extra: dev
51
51
  Requires-Dist: pytest>=6.2; extra == "dev"
52
52
  Requires-Dist: pytest-cov>=2.12; extra == "dev"
53
+ Requires-Dist: pytest-xdist>=3.0; extra == "dev"
53
54
  Requires-Dist: black>=21.0; extra == "dev"
54
55
  Requires-Dist: flake8>=3.9; extra == "dev"
55
56
  Requires-Dist: mypy>=0.910; extra == "dev"
@@ -66,7 +67,7 @@ Dynamic: requires-python
66
67
 
67
68
  ## AI Cost Tracking
68
69
 
69
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.108-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
70
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.110-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
70
71
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-53.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
71
72
 
72
73
  - 🤖 **LLM usage:** $7.5000 (154 commits)
@@ -24,6 +24,7 @@ tree-sitter-ruby>=0.21
24
24
  [dev]
25
25
  pytest>=6.2
26
26
  pytest-cov>=2.12
27
+ pytest-xdist>=3.0
27
28
  black>=21.0
28
29
  flake8>=3.9
29
30
  mypy>=0.910
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code2llm"
7
- version = "0.5.108"
7
+ version = "0.5.110"
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"
@@ -67,6 +67,7 @@ dependencies = [
67
67
  dev = [
68
68
  "pytest>=6.2",
69
69
  "pytest-cov>=2.12",
70
+ "pytest-xdist>=3.0",
70
71
  "black>=21.0",
71
72
  "flake8>=3.9",
72
73
  "mypy>=0.910",
@@ -94,6 +95,10 @@ ignore_missing_imports = true
94
95
  [tool.pytest.ini_options]
95
96
  testpaths = ["tests"]
96
97
  python_files = "test_*.py"
98
+ addopts = "-n auto" # Run tests in parallel using pytest-xdist
99
+ markers = [
100
+ "slow: marks tests as slow (deselect with '-m \"not slow\"')"
101
+ ]
97
102
 
98
103
  [tool.pfix]
99
104
  # Self-healing Python configuration
@@ -4,7 +4,7 @@ from setuptools import setup, find_packages
4
4
  import os
5
5
 
6
6
  # Read version
7
- version = "0.5.107"
7
+ version = "0.5.109"
8
8
 
9
9
  # Read long description
10
10
  def read_readme():
@@ -50,22 +50,23 @@ class TestEdgeCases:
50
50
  finally:
51
51
  shutil.rmtree(tmp_dir)
52
52
 
53
+ @pytest.mark.slow
53
54
  def test_very_large_file(self):
54
55
  """Handle very large Python file."""
55
56
  tmp_dir = Path(tempfile.mkdtemp())
56
57
  try:
57
- # Create large file (1000 functions)
58
- lines = [f"def func_{i}(): pass" for i in range(1000)]
58
+ # Create large file (200 functions - reduced from 1000 for faster tests)
59
+ lines = [f"def func_{i}(): pass" for i in range(200)]
59
60
  (tmp_dir / "large.py").write_text('\n'.join(lines))
60
-
61
+
61
62
  config = Config(
62
63
  mode="static",
63
64
  filters=FilterConfig(exclude_patterns=[], min_function_lines=1)
64
65
  )
65
66
  analyzer = ProjectAnalyzer(config)
66
67
  result = analyzer.analyze_project(str(tmp_dir))
67
-
68
- assert result.get_function_count() >= 1000
68
+
69
+ assert result.get_function_count() >= 200
69
70
  finally:
70
71
  shutil.rmtree(tmp_dir)
71
72
 
File without changes
File without changes
File without changes
File without changes