code2llm 0.5.99__tar.gz → 0.5.100__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.99 → code2llm-0.5.100}/PKG-INFO +22 -7
- {code2llm-0.5.99 → code2llm-0.5.100}/README.md +18 -6
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/__init__.py +1 -1
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/analysis/side_effects.py +6 -36
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/analysis/type_inference.py +6 -37
- code2llm-0.5.100/code2llm/analysis/utils/__init__.py +5 -0
- code2llm-0.5.100/code2llm/analysis/utils/ast_helpers.py +54 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/cli_exports/formats.py +31 -33
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/cli_exports/prompt.py +57 -46
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/analyzer.py +40 -18
- code2llm-0.5.100/code2llm/core/ast_registry.py +102 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/file_analyzer.py +42 -44
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/file_cache.py +45 -4
- code2llm-0.5.100/code2llm/core/file_filter.py +98 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/base.py +159 -94
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/ruby.py +26 -18
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/repo_files.py +3 -1
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/map_exporter.py +60 -56
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/mermaid_exporter.py +86 -234
- code2llm-0.5.100/code2llm/exporters/mermaid_flow_helpers.py +262 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/nlp/__init__.py +1 -1
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm.egg-info/PKG-INFO +22 -7
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm.egg-info/SOURCES.txt +4 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm.egg-info/requires.txt +3 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/pyproject.toml +33 -1
- {code2llm-0.5.99 → code2llm-0.5.100}/setup.py +1 -1
- code2llm-0.5.99/code2llm/core/file_filter.py +0 -58
- {code2llm-0.5.99 → code2llm-0.5.100}/LICENSE +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/__main__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/analysis/pipeline_detector.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/api.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/cli.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/cli_analysis.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/cli_commands.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/cli_exports/code2logic.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/cli_exports/orchestrator.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/cli_parser.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/config.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/gitignore.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/__init__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/cpp.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/csharp.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/generic.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/go_lang.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/java.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/php.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/rust.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/lang/typescript.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/large_repo.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/models.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/refactoring.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/flow_constants.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/index_generator.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/project_yaml_exporter.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/toon/helpers.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/generators/llm_task.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/generators/mermaid.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/code2llm.egg-info/top_level.txt +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/setup.cfg +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_project_toon_export.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_prompt_txt.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.99 → code2llm-0.5.100}/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.100
|
|
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
|
|
@@ -43,6 +43,9 @@ Requires-Dist: pytest-cov>=2.12; extra == "dev"
|
|
|
43
43
|
Requires-Dist: black>=21.0; extra == "dev"
|
|
44
44
|
Requires-Dist: flake8>=3.9; extra == "dev"
|
|
45
45
|
Requires-Dist: mypy>=0.910; extra == "dev"
|
|
46
|
+
Requires-Dist: goal>=2.1.0; extra == "dev"
|
|
47
|
+
Requires-Dist: costs>=0.1.20; extra == "dev"
|
|
48
|
+
Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
46
49
|
Dynamic: author
|
|
47
50
|
Dynamic: home-page
|
|
48
51
|
Dynamic: license-file
|
|
@@ -50,6 +53,21 @@ Dynamic: requires-python
|
|
|
50
53
|
|
|
51
54
|
# code2llm - Generated Analysis Files
|
|
52
55
|
|
|
56
|
+
|
|
57
|
+
## AI Cost Tracking
|
|
58
|
+
|
|
59
|
+
   
|
|
60
|
+
  
|
|
61
|
+
|
|
62
|
+
- 🤖 **LLM usage:** $7.5000 (148 commits)
|
|
63
|
+
- 👤 **Human dev:** ~$5123 (51.2h @ $100/h, 30min dedup)
|
|
64
|
+
|
|
65
|
+
Generated on 2026-03-31 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
53
71
|
This directory contains the complete analysis of your project generated by `code2llm`. Each file serves a specific purpose for understanding, refactoring, and documenting your codebase.
|
|
54
72
|
|
|
55
73
|
## 📁 Generated Files Overview
|
|
@@ -372,17 +390,14 @@ code2llm ./ -f yaml --separate-orphans
|
|
|
372
390
|
---
|
|
373
391
|
|
|
374
392
|
**Generated by**: `code2llm ./ -f all --readme`
|
|
375
|
-
**Analysis Date**: 2026-03-
|
|
393
|
+
**Analysis Date**: 2026-03-31
|
|
376
394
|
**Total Functions**: 934
|
|
377
395
|
**Total Classes**: 106
|
|
378
396
|
**Modules**: 122
|
|
379
397
|
|
|
380
398
|
For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
|
|
381
399
|
|
|
382
|
-
## License
|
|
383
|
-
|
|
384
|
-
Apache License 2.0 - see [LICENSE](LICENSE) for details.
|
|
385
400
|
|
|
386
|
-
##
|
|
401
|
+
## License
|
|
387
402
|
|
|
388
|
-
|
|
403
|
+
Licensed under Apache-2.0.
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# code2llm - Generated Analysis Files
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## AI Cost Tracking
|
|
5
|
+
|
|
6
|
+
   
|
|
7
|
+
  
|
|
8
|
+
|
|
9
|
+
- 🤖 **LLM usage:** $7.5000 (148 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$5123 (51.2h @ $100/h, 30min dedup)
|
|
11
|
+
|
|
12
|
+
Generated on 2026-03-31 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
3
18
|
This directory contains the complete analysis of your project generated by `code2llm`. Each file serves a specific purpose for understanding, refactoring, and documenting your codebase.
|
|
4
19
|
|
|
5
20
|
## 📁 Generated Files Overview
|
|
@@ -322,17 +337,14 @@ code2llm ./ -f yaml --separate-orphans
|
|
|
322
337
|
---
|
|
323
338
|
|
|
324
339
|
**Generated by**: `code2llm ./ -f all --readme`
|
|
325
|
-
**Analysis Date**: 2026-03-
|
|
340
|
+
**Analysis Date**: 2026-03-31
|
|
326
341
|
**Total Functions**: 934
|
|
327
342
|
**Total Classes**: 106
|
|
328
343
|
**Modules**: 122
|
|
329
344
|
|
|
330
345
|
For more information about code2llm, visit: https://github.com/tom-sapletta/code2llm
|
|
331
346
|
|
|
332
|
-
## License
|
|
333
|
-
|
|
334
|
-
Apache License 2.0 - see [LICENSE](LICENSE) for details.
|
|
335
347
|
|
|
336
|
-
##
|
|
348
|
+
## License
|
|
337
349
|
|
|
338
|
-
|
|
350
|
+
Licensed under Apache-2.0.
|
|
@@ -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.100"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -11,10 +11,11 @@ Used by FlowExporter to enrich CONTRACTS and SIDE_EFFECTS sections.
|
|
|
11
11
|
|
|
12
12
|
import ast
|
|
13
13
|
import logging
|
|
14
|
-
from pathlib import Path
|
|
15
14
|
from typing import Any, Dict, List, Optional, Set
|
|
16
15
|
|
|
17
16
|
from code2llm.core.models import FunctionInfo
|
|
17
|
+
from code2llm.core.ast_registry import ASTRegistry
|
|
18
|
+
from code2llm.analysis.utils.ast_helpers import find_function_node
|
|
18
19
|
|
|
19
20
|
logger = logging.getLogger(__name__)
|
|
20
21
|
|
|
@@ -121,16 +122,16 @@ class SideEffectDetector:
|
|
|
121
122
|
global references, and self-attribute mutations.
|
|
122
123
|
"""
|
|
123
124
|
|
|
124
|
-
def __init__(self):
|
|
125
|
-
self.
|
|
125
|
+
def __init__(self, registry: Optional[ASTRegistry] = None):
|
|
126
|
+
self._registry = registry or ASTRegistry.get_global()
|
|
126
127
|
|
|
127
128
|
def analyze_function(self, fi: FunctionInfo) -> SideEffectInfo:
|
|
128
129
|
"""Analyze a single function for side effects."""
|
|
129
130
|
info = SideEffectInfo(fi.name, fi.qualified_name)
|
|
130
131
|
|
|
131
|
-
tree = self.
|
|
132
|
+
tree = self._registry.get_ast(fi.file)
|
|
132
133
|
if tree:
|
|
133
|
-
node =
|
|
134
|
+
node = find_function_node(tree, fi.name, fi.line)
|
|
134
135
|
if node:
|
|
135
136
|
self._scan_node(node, info)
|
|
136
137
|
self._classify(info)
|
|
@@ -281,37 +282,6 @@ class SideEffectDetector:
|
|
|
281
282
|
# ------------------------------------------------------------------
|
|
282
283
|
# AST helpers
|
|
283
284
|
# ------------------------------------------------------------------
|
|
284
|
-
def _get_ast(self, file_path: str) -> Optional[ast.Module]:
|
|
285
|
-
"""Parse and cache AST for a source file."""
|
|
286
|
-
if not file_path:
|
|
287
|
-
return None
|
|
288
|
-
if file_path in self._ast_cache:
|
|
289
|
-
return self._ast_cache[file_path]
|
|
290
|
-
|
|
291
|
-
try:
|
|
292
|
-
source = Path(file_path).read_text(encoding="utf-8", errors="replace")
|
|
293
|
-
tree = ast.parse(source, filename=file_path)
|
|
294
|
-
self._ast_cache[file_path] = tree
|
|
295
|
-
except (OSError, SyntaxError) as e:
|
|
296
|
-
logger.debug("Cannot parse %s: %s", file_path, e)
|
|
297
|
-
self._ast_cache[file_path] = None
|
|
298
|
-
tree = None
|
|
299
|
-
return tree
|
|
300
|
-
|
|
301
|
-
def _find_function_node(
|
|
302
|
-
self, tree: ast.Module, name: str, line: int
|
|
303
|
-
) -> Optional[ast.FunctionDef]:
|
|
304
|
-
"""Find function node by name and line number."""
|
|
305
|
-
for node in ast.walk(tree):
|
|
306
|
-
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
307
|
-
if node.name == name and node.lineno == line:
|
|
308
|
-
return node
|
|
309
|
-
for node in ast.walk(tree):
|
|
310
|
-
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
311
|
-
if node.name == name:
|
|
312
|
-
return node
|
|
313
|
-
return None
|
|
314
|
-
|
|
315
285
|
def _get_call_name(self, node: ast.expr) -> Optional[str]:
|
|
316
286
|
"""Extract call name from AST node."""
|
|
317
287
|
if isinstance(node, ast.Name):
|
|
@@ -10,10 +10,11 @@ Used by FlowExporter to enrich CONTRACTS and DATA_TYPES sections.
|
|
|
10
10
|
|
|
11
11
|
import ast
|
|
12
12
|
import logging
|
|
13
|
-
from pathlib import Path
|
|
14
13
|
from typing import Any, Dict, List, Optional, Tuple
|
|
15
14
|
|
|
16
15
|
from code2llm.core.models import FunctionInfo
|
|
16
|
+
from code2llm.core.ast_registry import ASTRegistry
|
|
17
|
+
from code2llm.analysis.utils.ast_helpers import find_function_node
|
|
17
18
|
|
|
18
19
|
logger = logging.getLogger(__name__)
|
|
19
20
|
|
|
@@ -61,8 +62,8 @@ class TypeInferenceEngine:
|
|
|
61
62
|
type annotations that the core analyzer doesn't capture.
|
|
62
63
|
"""
|
|
63
64
|
|
|
64
|
-
def __init__(self):
|
|
65
|
-
self.
|
|
65
|
+
def __init__(self, registry: Optional[ASTRegistry] = None):
|
|
66
|
+
self._registry = registry or ASTRegistry.get_global()
|
|
66
67
|
|
|
67
68
|
def enrich_function(self, fi: FunctionInfo) -> Dict[str, Any]:
|
|
68
69
|
"""Extract full type info for a function.
|
|
@@ -72,9 +73,9 @@ class TypeInferenceEngine:
|
|
|
72
73
|
returns: str or None
|
|
73
74
|
source: 'annotation' | 'inferred' | 'none'
|
|
74
75
|
"""
|
|
75
|
-
tree = self.
|
|
76
|
+
tree = self._registry.get_ast(fi.file)
|
|
76
77
|
if tree:
|
|
77
|
-
node =
|
|
78
|
+
node = find_function_node(tree, fi.name, fi.line)
|
|
78
79
|
if node:
|
|
79
80
|
return self._extract_from_node(node, fi)
|
|
80
81
|
|
|
@@ -126,38 +127,6 @@ class TypeInferenceEngine:
|
|
|
126
127
|
# ------------------------------------------------------------------
|
|
127
128
|
# AST extraction
|
|
128
129
|
# ------------------------------------------------------------------
|
|
129
|
-
def _get_ast(self, file_path: str) -> Optional[ast.Module]:
|
|
130
|
-
"""Parse and cache AST for a source file."""
|
|
131
|
-
if not file_path:
|
|
132
|
-
return None
|
|
133
|
-
if file_path in self._ast_cache:
|
|
134
|
-
return self._ast_cache[file_path]
|
|
135
|
-
|
|
136
|
-
try:
|
|
137
|
-
source = Path(file_path).read_text(encoding="utf-8", errors="replace")
|
|
138
|
-
tree = ast.parse(source, filename=file_path)
|
|
139
|
-
self._ast_cache[file_path] = tree
|
|
140
|
-
except (OSError, SyntaxError) as e:
|
|
141
|
-
logger.debug("Cannot parse %s: %s", file_path, e)
|
|
142
|
-
self._ast_cache[file_path] = None
|
|
143
|
-
tree = None
|
|
144
|
-
return tree
|
|
145
|
-
|
|
146
|
-
def _find_function_node(
|
|
147
|
-
self, tree: ast.Module, name: str, line: int
|
|
148
|
-
) -> Optional[ast.FunctionDef]:
|
|
149
|
-
"""Find function node by name and line number."""
|
|
150
|
-
for node in ast.walk(tree):
|
|
151
|
-
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
152
|
-
if node.name == name and node.lineno == line:
|
|
153
|
-
return node
|
|
154
|
-
# Fallback: match by name only (first match)
|
|
155
|
-
for node in ast.walk(tree):
|
|
156
|
-
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
157
|
-
if node.name == name:
|
|
158
|
-
return node
|
|
159
|
-
return None
|
|
160
|
-
|
|
161
130
|
def _extract_from_node(
|
|
162
131
|
self, node: ast.FunctionDef, fi: FunctionInfo
|
|
163
132
|
) -> Dict[str, Any]:
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Shared AST utility functions — eliminate duplicated _get_ast / _find_function_node
|
|
2
|
+
across side_effects.py, type_inference.py, call_graph.py, cfg.py, dfg.py."""
|
|
3
|
+
|
|
4
|
+
import ast
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from code2llm.core.ast_registry import ASTRegistry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_ast(filepath: str,
|
|
11
|
+
registry: Optional[ASTRegistry] = None) -> Optional[ast.Module]:
|
|
12
|
+
"""Return parsed AST for *filepath* using the shared registry.
|
|
13
|
+
|
|
14
|
+
Falls back to process-wide singleton when no registry is supplied.
|
|
15
|
+
"""
|
|
16
|
+
reg = registry or ASTRegistry.get_global()
|
|
17
|
+
return reg.get_ast(filepath)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def find_function_node(
|
|
21
|
+
tree: ast.Module,
|
|
22
|
+
name: str,
|
|
23
|
+
line: int,
|
|
24
|
+
) -> Optional[ast.FunctionDef]:
|
|
25
|
+
"""Locate a function/async-function node by name and line number.
|
|
26
|
+
|
|
27
|
+
First pass: exact name + line match.
|
|
28
|
+
Second pass: first node whose name matches (fallback for out-of-sync lines).
|
|
29
|
+
"""
|
|
30
|
+
for node in ast.walk(tree):
|
|
31
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
32
|
+
if node.name == name and node.lineno == line:
|
|
33
|
+
return node
|
|
34
|
+
for node in ast.walk(tree):
|
|
35
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
36
|
+
if node.name == name:
|
|
37
|
+
return node
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def expr_to_str(node: ast.expr) -> Optional[str]:
|
|
42
|
+
"""Convert an AST expression to a dotted string (for call-name extraction).
|
|
43
|
+
|
|
44
|
+
Handles ``ast.Name`` (``foo``) and ``ast.Attribute`` (``obj.method``).
|
|
45
|
+
Returns *None* for unsupported node types.
|
|
46
|
+
"""
|
|
47
|
+
if isinstance(node, ast.Name):
|
|
48
|
+
return node.id
|
|
49
|
+
if isinstance(node, ast.Attribute):
|
|
50
|
+
value = expr_to_str(node.value)
|
|
51
|
+
if value:
|
|
52
|
+
return f"{value}.{node.attr}"
|
|
53
|
+
return node.attr
|
|
54
|
+
return None
|
|
@@ -186,6 +186,33 @@ def _export_yaml(args, result, output_dir: Path):
|
|
|
186
186
|
print(f" - YAML: {filepath}")
|
|
187
187
|
|
|
188
188
|
|
|
189
|
+
def _export_mermaid_pngs(args, output_dir: Path) -> None:
|
|
190
|
+
"""Attempt PNG generation from .mmd files, with graceful fallback."""
|
|
191
|
+
if args.no_png:
|
|
192
|
+
if args.verbose:
|
|
193
|
+
print(f" - PNG: Skipped (--no-png)")
|
|
194
|
+
return
|
|
195
|
+
try:
|
|
196
|
+
from ..generators.mermaid import generate_pngs
|
|
197
|
+
png_count = generate_pngs(output_dir, output_dir)
|
|
198
|
+
if args.verbose and png_count > 0:
|
|
199
|
+
print(f" - PNG: {png_count} files generated")
|
|
200
|
+
except ImportError:
|
|
201
|
+
try:
|
|
202
|
+
import subprocess
|
|
203
|
+
script_path = Path(__file__).parent.parent.parent / 'mermaid_to_png.py'
|
|
204
|
+
if script_path.exists():
|
|
205
|
+
png_result = subprocess.run(
|
|
206
|
+
['python', str(script_path), '--batch', str(output_dir), str(output_dir)],
|
|
207
|
+
capture_output=True, text=True, timeout=60,
|
|
208
|
+
)
|
|
209
|
+
if png_result.returncode == 0 and args.verbose:
|
|
210
|
+
print(f" - PNG: {output_dir / '*.png'}")
|
|
211
|
+
except Exception:
|
|
212
|
+
if args.verbose:
|
|
213
|
+
print(f" - PNG: Skipped (install with: make install-mermaid)")
|
|
214
|
+
|
|
215
|
+
|
|
189
216
|
def _export_mermaid(args, result, output_dir: Path):
|
|
190
217
|
"""Export Mermaid diagrams + optional PNG generation.
|
|
191
218
|
|
|
@@ -195,25 +222,17 @@ def _export_mermaid(args, result, output_dir: Path):
|
|
|
195
222
|
- flow_full.mmd (all nodes) - debug view [with --flow-full]
|
|
196
223
|
"""
|
|
197
224
|
exporter = MermaidExporter()
|
|
198
|
-
|
|
199
|
-
# Get include_examples flag
|
|
200
225
|
include_examples = getattr(args, 'flow_include_examples', False)
|
|
201
|
-
|
|
202
|
-
# Default: export compact flow (architectural view, ~50 nodes)
|
|
226
|
+
|
|
203
227
|
exporter.export_flow_compact(result, str(output_dir / 'flow.mmd'), include_examples)
|
|
204
|
-
|
|
205
|
-
# Optional: detailed flow (per-module, ~150 nodes)
|
|
206
228
|
if getattr(args, 'flow_detail', False):
|
|
207
229
|
exporter.export_flow_detailed(result, str(output_dir / 'flow_detailed.mmd'), include_examples)
|
|
208
|
-
|
|
209
|
-
# Optional: full flow (all nodes, debug view)
|
|
210
230
|
if getattr(args, 'flow_full', False):
|
|
211
231
|
exporter.export_flow_full(result, str(output_dir / 'flow_full.mmd'), include_examples)
|
|
212
|
-
|
|
213
|
-
# Legacy exports (for backward compatibility)
|
|
232
|
+
|
|
214
233
|
exporter.export_call_graph(result, str(output_dir / 'calls.mmd'))
|
|
215
234
|
exporter.export_compact(result, str(output_dir / 'compact_flow.mmd'))
|
|
216
|
-
|
|
235
|
+
|
|
217
236
|
if args.verbose:
|
|
218
237
|
files = ['flow.mmd']
|
|
219
238
|
if getattr(args, 'flow_detail', False):
|
|
@@ -223,28 +242,7 @@ def _export_mermaid(args, result, output_dir: Path):
|
|
|
223
242
|
files.extend(['calls.mmd', 'compact_flow.mmd'])
|
|
224
243
|
print(f" - Mermaid: {output_dir}/*.mmd ({', '.join(files)})")
|
|
225
244
|
|
|
226
|
-
|
|
227
|
-
try:
|
|
228
|
-
from ..generators.mermaid import generate_pngs
|
|
229
|
-
png_count = generate_pngs(output_dir, output_dir)
|
|
230
|
-
if args.verbose and png_count > 0:
|
|
231
|
-
print(f" - PNG: {png_count} files generated")
|
|
232
|
-
except ImportError:
|
|
233
|
-
try:
|
|
234
|
-
import subprocess
|
|
235
|
-
script_path = Path(__file__).parent.parent.parent / 'mermaid_to_png.py'
|
|
236
|
-
if script_path.exists():
|
|
237
|
-
png_result = subprocess.run([
|
|
238
|
-
'python', str(script_path),
|
|
239
|
-
'--batch', str(output_dir), str(output_dir)
|
|
240
|
-
], capture_output=True, text=True, timeout=60)
|
|
241
|
-
if png_result.returncode == 0 and args.verbose:
|
|
242
|
-
print(f" - PNG: {output_dir / '*.png'}")
|
|
243
|
-
except Exception:
|
|
244
|
-
if args.verbose:
|
|
245
|
-
print(f" - PNG: Skipped (install with: make install-mermaid)")
|
|
246
|
-
elif args.verbose:
|
|
247
|
-
print(f" - PNG: Skipped (--no-png)")
|
|
245
|
+
_export_mermaid_pngs(args, output_dir)
|
|
248
246
|
|
|
249
247
|
|
|
250
248
|
def _export_refactor_prompts(args, result, output_dir: Path):
|
|
@@ -399,60 +399,71 @@ def _build_priority_order(file_analysis: dict) -> List[str]:
|
|
|
399
399
|
return priorities
|
|
400
400
|
|
|
401
401
|
|
|
402
|
+
def _build_strategy_section(file_analysis: dict) -> List[str]:
|
|
403
|
+
"""Build the 'Analysis Strategy' block for the prompt footer."""
|
|
404
|
+
if not file_analysis.get('file_count', 0):
|
|
405
|
+
return []
|
|
406
|
+
lines = ["", "Analysis Strategy:"]
|
|
407
|
+
if file_analysis.get('has_analysis_toon') and file_analysis.get('has_map_toon'):
|
|
408
|
+
lines.append(
|
|
409
|
+
f"- Start with {file_analysis.get('analysis_file', 'analysis.toon')} for health"
|
|
410
|
+
f" metrics, then {file_analysis.get('map_file', 'map.toon.yaml')} for structure and signatures"
|
|
411
|
+
)
|
|
412
|
+
if file_analysis.get('has_evolution_toon'):
|
|
413
|
+
lines.append(
|
|
414
|
+
f"- Review {file_analysis.get('evolution_file', 'evolution.toon.yaml')}"
|
|
415
|
+
" for action priorities and next steps"
|
|
416
|
+
)
|
|
417
|
+
if file_analysis.get('has_project_toon_yaml'):
|
|
418
|
+
lines.append(
|
|
419
|
+
f"- Compare the compact project overview in"
|
|
420
|
+
f" {file_analysis.get('project_toon_file', 'project.toon.yaml')} with the main analysis files"
|
|
421
|
+
)
|
|
422
|
+
if file_analysis.get('has_project_logic'):
|
|
423
|
+
lines.append(
|
|
424
|
+
f"- Compare the compact project overview in"
|
|
425
|
+
f" {file_analysis.get('project_logic_file', 'project.toon')} with the main analysis files"
|
|
426
|
+
)
|
|
427
|
+
if file_analysis.get('has_validation_toon'):
|
|
428
|
+
lines.append(
|
|
429
|
+
f"- Check {file_analysis.get('validation_file', 'project/validation.toon.yaml')}"
|
|
430
|
+
" for validation issues (vallm tool output)"
|
|
431
|
+
)
|
|
432
|
+
if file_analysis.get('has_duplication_toon'):
|
|
433
|
+
lines.append(
|
|
434
|
+
f"- Examine {file_analysis.get('duplication_file', 'project/duplication.toon.yaml')}"
|
|
435
|
+
" for duplicate code patterns (redup tool output)"
|
|
436
|
+
)
|
|
437
|
+
return lines
|
|
438
|
+
|
|
439
|
+
|
|
402
440
|
def _build_prompt_footer(chunked: bool = False, file_analysis: dict = None) -> List[str]:
|
|
403
441
|
"""Build dynamic footer section of prompt based on generated files."""
|
|
404
442
|
if file_analysis is None:
|
|
405
443
|
file_analysis = {}
|
|
406
|
-
|
|
407
|
-
lines = [""]
|
|
408
|
-
|
|
409
|
-
# Dynamic tasks
|
|
410
|
-
lines.append("Task:")
|
|
411
|
-
tasks = _build_dynamic_tasks(file_analysis)
|
|
412
|
-
for task in tasks:
|
|
413
|
-
lines.append(task)
|
|
414
444
|
|
|
415
|
-
|
|
445
|
+
lines = ["", "Task:"]
|
|
446
|
+
lines.extend(_build_dynamic_tasks(file_analysis))
|
|
447
|
+
|
|
416
448
|
priorities = _build_priority_order(file_analysis)
|
|
417
449
|
if priorities:
|
|
418
|
-
lines
|
|
419
|
-
|
|
420
|
-
for priority in priorities:
|
|
421
|
-
lines.append(priority)
|
|
422
|
-
|
|
423
|
-
# Dynamic focus areas
|
|
450
|
+
lines += ["", "Priority Order:"] + priorities
|
|
451
|
+
|
|
424
452
|
focus_areas = _build_dynamic_focus_areas(file_analysis)
|
|
425
453
|
if focus_areas:
|
|
426
|
-
lines
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if file_analysis.get('has_project_toon_yaml'):
|
|
440
|
-
lines.append(f"- Compare the compact project overview in {file_analysis.get('project_toon_file', 'project.toon.yaml')} with the main analysis files")
|
|
441
|
-
if file_analysis.get('has_project_logic'):
|
|
442
|
-
lines.append(f"- Compare the compact project overview in {file_analysis.get('project_logic_file', 'project.toon')} with the main analysis files")
|
|
443
|
-
if file_analysis['has_validation_toon']:
|
|
444
|
-
lines.append(f"- Check {file_analysis.get('validation_file', 'project/validation.toon.yaml')} for validation issues (vallm tool output)")
|
|
445
|
-
if file_analysis['has_duplication_toon']:
|
|
446
|
-
lines.append(f"- Examine {file_analysis.get('duplication_file', 'project/duplication.toon.yaml')} for duplicate code patterns (redup tool output)")
|
|
447
|
-
|
|
448
|
-
# Constraints
|
|
449
|
-
lines.append("")
|
|
450
|
-
lines.append("Constraints:")
|
|
451
|
-
lines.append("- Prefer minimal, incremental changes.")
|
|
452
|
-
lines.append("- Maintain full backward compatibility.")
|
|
453
|
-
lines.append("- Base recommendations on concrete metrics from the provided files.")
|
|
454
|
-
lines.append("- If uncertain, ask clarifying questions.")
|
|
455
|
-
|
|
454
|
+
lines += ["", "Focus Areas for Analysis:"] + focus_areas
|
|
455
|
+
|
|
456
|
+
lines.extend(_build_strategy_section(file_analysis))
|
|
457
|
+
|
|
458
|
+
lines += [
|
|
459
|
+
"",
|
|
460
|
+
"Constraints:",
|
|
461
|
+
"- Prefer minimal, incremental changes.",
|
|
462
|
+
"- Maintain full backward compatibility.",
|
|
463
|
+
"- Base recommendations on concrete metrics from the provided files.",
|
|
464
|
+
"- If uncertain, ask clarifying questions.",
|
|
465
|
+
]
|
|
466
|
+
|
|
456
467
|
if chunked:
|
|
457
468
|
lines.extend([
|
|
458
469
|
"",
|
|
@@ -460,5 +471,5 @@ def _build_prompt_footer(chunked: bool = False, file_analysis: dict = None) -> L
|
|
|
460
471
|
" Start with the main files (analysis.toon, context.md) for overview,",
|
|
461
472
|
" then examine specific subproject directories as needed.",
|
|
462
473
|
])
|
|
463
|
-
|
|
474
|
+
|
|
464
475
|
return lines
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Optimized project analyzer with caching and parallel processing."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import time
|
|
4
5
|
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
5
6
|
from pathlib import Path
|
|
@@ -93,29 +94,50 @@ class ProjectAnalyzer:
|
|
|
93
94
|
return merged
|
|
94
95
|
|
|
95
96
|
def _collect_files(self, project_path: Path) -> List[Tuple[str, str]]:
|
|
96
|
-
"""Collect all source files with their module names for all supported languages.
|
|
97
|
-
files = []
|
|
97
|
+
"""Collect all source files with their module names for all supported languages.
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
Uses a single os.walk traversal with early directory pruning instead of
|
|
100
|
+
separate rglob calls per extension (~40x speedup on large repos).
|
|
101
|
+
"""
|
|
102
|
+
files = []
|
|
103
|
+
ext_set = set(ALL_EXTENSIONS) # O(1) lookup
|
|
104
|
+
init_names = frozenset({'__init__.py', 'index.js', 'index.ts', 'mod.rs', 'lib.rs'})
|
|
105
|
+
seen = set() # guard against duplicate paths (e.g. .h in both c and cpp lists)
|
|
106
|
+
project_str = str(project_path)
|
|
107
|
+
|
|
108
|
+
for dirpath, dirnames, filenames in os.walk(project_str, topdown=True):
|
|
109
|
+
# Prune skipped directories in-place so os.walk won't descend into them
|
|
110
|
+
dirnames[:] = [
|
|
111
|
+
d for d in dirnames
|
|
112
|
+
if not self.file_filter.should_skip_dir(d)
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
for filename in filenames:
|
|
116
|
+
suffix = os.path.splitext(filename)[1].lower()
|
|
117
|
+
if suffix not in ext_set:
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
file_str = os.path.join(dirpath, filename)
|
|
121
|
+
if file_str in seen:
|
|
122
|
+
continue
|
|
123
|
+
seen.add(file_str)
|
|
124
|
+
|
|
103
125
|
if not self.file_filter.should_process(file_str):
|
|
104
126
|
continue
|
|
105
|
-
|
|
106
|
-
# Calculate module name
|
|
107
|
-
|
|
108
|
-
parts =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
module_name = '.'.join(parts) if parts else project_path.name
|
|
127
|
+
|
|
128
|
+
# Calculate module name from relative path
|
|
129
|
+
rel = os.path.relpath(file_str, project_str)
|
|
130
|
+
parts = rel.replace('\\', '/').split('/')
|
|
131
|
+
dir_parts = parts[:-1] # everything before filename
|
|
132
|
+
|
|
133
|
+
if filename in init_names:
|
|
134
|
+
module_name = '.'.join(dir_parts) if dir_parts else project_path.name
|
|
114
135
|
else:
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
stem = os.path.splitext(filename)[0]
|
|
137
|
+
module_name = '.'.join(dir_parts + [stem]) if dir_parts else stem
|
|
138
|
+
|
|
117
139
|
files.append((file_str, module_name))
|
|
118
|
-
|
|
140
|
+
|
|
119
141
|
return files
|
|
120
142
|
|
|
121
143
|
def _analyze_parallel(self, files: List[Tuple[str, str]]) -> List[Dict]:
|