code2llm 0.5.100__tar.gz → 0.5.102__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.100 → code2llm-0.5.102}/PKG-INFO +2 -2
- {code2llm-0.5.100 → code2llm-0.5.102}/README.md +1 -1
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/__init__.py +1 -1
- code2llm-0.5.102/code2llm/core/export_pipeline.py +153 -0
- code2llm-0.5.102/code2llm/core/incremental.py +150 -0
- code2llm-0.5.102/code2llm/core/lang/csharp.py +42 -0
- code2llm-0.5.102/code2llm/core/lang/java.py +43 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/__init__.py +1 -1
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/PKG-INFO +2 -2
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/SOURCES.txt +2 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/pyproject.toml +1 -1
- {code2llm-0.5.100 → code2llm-0.5.102}/setup.py +1 -1
- code2llm-0.5.100/code2llm/core/lang/csharp.py +0 -49
- code2llm-0.5.100/code2llm/core/lang/java.py +0 -50
- {code2llm-0.5.100 → code2llm-0.5.102}/LICENSE +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/__main__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/call_graph.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/coupling.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/pipeline_detector.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/smells.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/type_inference.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/utils/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/utils/ast_helpers.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/api.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_analysis.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_commands.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_exports/code2logic.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_exports/formats.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_exports/orchestrator.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_exports/prompt.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_parser.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/analyzer.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/ast_registry.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/config.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/file_analyzer.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/file_cache.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/file_filter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/gitignore.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/base.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/cpp.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/generic.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/go_lang.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/php.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/ruby.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/rust.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/typescript.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/large_repo.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/models.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/refactoring.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/repo_files.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/flow_constants.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/index_generator.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/project_yaml_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon/helpers.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon/renderer.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/generators/llm_task.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/generators/mermaid.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/requires.txt +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/top_level.txt +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/setup.cfg +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_project_toon_export.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_prompt_txt.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.100 → code2llm-0.5.102}/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.102
|
|
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
|
|
@@ -56,7 +56,7 @@ Dynamic: requires-python
|
|
|
56
56
|
|
|
57
57
|
## AI Cost Tracking
|
|
58
58
|
|
|
59
|
-
    
|
|
60
60
|
  
|
|
61
61
|
|
|
62
62
|
- 🤖 **LLM usage:** $7.5000 (148 commits)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
7
|
  
|
|
8
8
|
|
|
9
9
|
- 🤖 **LLM usage:** $7.5000 (148 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.
|
|
11
|
+
__version__ = "0.5.102"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Export pipeline — shared context computed once for all exporters.
|
|
2
|
+
|
|
3
|
+
Instead of each exporter building its own context from scratch, the pipeline
|
|
4
|
+
pre-computes shared data (metrics aggregations, call graph, etc.) and passes
|
|
5
|
+
it to all exporters in sequence.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from code2llm.core.models import AnalysisResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SharedExportContext:
|
|
14
|
+
"""Pre-computed context shared across all exporters.
|
|
15
|
+
|
|
16
|
+
Lazy-computes expensive aggregations on first access.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
__slots__ = ("_result", "_computed")
|
|
20
|
+
|
|
21
|
+
def __init__(self, result: AnalysisResult):
|
|
22
|
+
self._result = result
|
|
23
|
+
self._computed: Dict[str, Any] = {}
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def result(self) -> AnalysisResult:
|
|
27
|
+
return self._result
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def functions(self) -> Dict:
|
|
31
|
+
return self._result.functions
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def classes(self) -> Dict:
|
|
35
|
+
return self._result.classes
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def modules(self) -> Dict:
|
|
39
|
+
return self._result.modules
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def entry_points(self) -> List[str]:
|
|
43
|
+
return self._result.entry_points
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def metrics_summary(self) -> Dict[str, Any]:
|
|
47
|
+
"""Aggregate metrics — computed once, cached."""
|
|
48
|
+
if "metrics_summary" not in self._computed:
|
|
49
|
+
self._computed["metrics_summary"] = self._compute_metrics_summary()
|
|
50
|
+
return self._computed["metrics_summary"]
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def complexity_distribution(self) -> Dict[str, int]:
|
|
54
|
+
"""CC rank distribution — computed once, cached."""
|
|
55
|
+
if "cc_dist" not in self._computed:
|
|
56
|
+
self._computed["cc_dist"] = self._compute_cc_distribution()
|
|
57
|
+
return self._computed["cc_dist"]
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def call_graph_edges(self) -> List[tuple]:
|
|
61
|
+
"""Flattened call graph as (caller, callee) tuples."""
|
|
62
|
+
if "cg_edges" not in self._computed:
|
|
63
|
+
edges = []
|
|
64
|
+
for fname, finfo in self._result.functions.items():
|
|
65
|
+
for callee in finfo.calls:
|
|
66
|
+
edges.append((fname, callee))
|
|
67
|
+
self._computed["cg_edges"] = edges
|
|
68
|
+
return self._computed["cg_edges"]
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def high_complexity_functions(self) -> List[str]:
|
|
72
|
+
"""Functions with CC >= 10 (ranks C/D)."""
|
|
73
|
+
if "high_cc" not in self._computed:
|
|
74
|
+
high = []
|
|
75
|
+
for fname, finfo in self._result.functions.items():
|
|
76
|
+
cc = getattr(finfo, "complexity", None)
|
|
77
|
+
if cc and cc.get("cyclomatic_complexity", 0) >= 10:
|
|
78
|
+
high.append(fname)
|
|
79
|
+
self._computed["high_cc"] = high
|
|
80
|
+
return self._computed["high_cc"]
|
|
81
|
+
|
|
82
|
+
# ------------------------------------------------------------------
|
|
83
|
+
# Internal computation
|
|
84
|
+
# ------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
def _compute_metrics_summary(self) -> Dict[str, Any]:
|
|
87
|
+
funcs = self._result.functions
|
|
88
|
+
classes = self._result.classes
|
|
89
|
+
|
|
90
|
+
total_cc = 0
|
|
91
|
+
cc_count = 0
|
|
92
|
+
for f in funcs.values():
|
|
93
|
+
cc_data = getattr(f, "complexity", None)
|
|
94
|
+
if cc_data:
|
|
95
|
+
total_cc += cc_data.get("cyclomatic_complexity", 1)
|
|
96
|
+
cc_count += 1
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
"total_functions": len(funcs),
|
|
100
|
+
"total_classes": len(classes),
|
|
101
|
+
"total_modules": len(self._result.modules),
|
|
102
|
+
"average_cc": round(total_cc / cc_count, 2) if cc_count else 0,
|
|
103
|
+
"entry_points": len(self._result.entry_points),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def _compute_cc_distribution(self) -> Dict[str, int]:
|
|
107
|
+
dist = {"A": 0, "B": 0, "C": 0, "D": 0, "unknown": 0}
|
|
108
|
+
for f in self._result.functions.values():
|
|
109
|
+
cc_data = getattr(f, "complexity", None)
|
|
110
|
+
if cc_data:
|
|
111
|
+
rank = cc_data.get("cc_rank", "unknown")
|
|
112
|
+
dist[rank] = dist.get(rank, 0) + 1
|
|
113
|
+
else:
|
|
114
|
+
dist["unknown"] += 1
|
|
115
|
+
return dist
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class ExportPipeline:
|
|
119
|
+
"""Run multiple exporters with a single shared context.
|
|
120
|
+
|
|
121
|
+
Usage::
|
|
122
|
+
|
|
123
|
+
pipeline = ExportPipeline(analysis_result)
|
|
124
|
+
pipeline.run([
|
|
125
|
+
ToonExporter(config),
|
|
126
|
+
MermaidExporter(config),
|
|
127
|
+
ContextExporter(config),
|
|
128
|
+
], output_dir="/path/to/output")
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def __init__(self, result: AnalysisResult):
|
|
132
|
+
self._ctx = SharedExportContext(result)
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def context(self) -> SharedExportContext:
|
|
136
|
+
return self._ctx
|
|
137
|
+
|
|
138
|
+
def run(self, exporters: List, output_dir: str) -> Dict[str, bool]:
|
|
139
|
+
"""Run all exporters in sequence, returning success status per exporter."""
|
|
140
|
+
results = {}
|
|
141
|
+
for exporter in exporters:
|
|
142
|
+
name = type(exporter).__name__
|
|
143
|
+
try:
|
|
144
|
+
# Exporters that support shared context
|
|
145
|
+
if hasattr(exporter, "export_with_context"):
|
|
146
|
+
exporter.export_with_context(self._ctx, output_dir)
|
|
147
|
+
else:
|
|
148
|
+
# Fallback: pass raw AnalysisResult
|
|
149
|
+
exporter.export(self._ctx.result, output_dir)
|
|
150
|
+
results[name] = True
|
|
151
|
+
except Exception as e:
|
|
152
|
+
results[name] = False
|
|
153
|
+
return results
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Incremental analysis — hash-based skip for unchanged files.
|
|
2
|
+
|
|
3
|
+
On repeated runs, files whose mtime+size match the cached state are skipped,
|
|
4
|
+
and their previous analysis results are reused. This dramatically speeds up
|
|
5
|
+
CI/CD and iterative development workflows.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
CACHE_FILE = ".code2llm_incremental.json"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _file_signature(filepath: str) -> Tuple[int, int]:
|
|
20
|
+
"""Return (mtime_ns, size) for a file — fast freshness check."""
|
|
21
|
+
try:
|
|
22
|
+
s = os.stat(filepath)
|
|
23
|
+
return (s.st_mtime_ns, s.st_size)
|
|
24
|
+
except OSError:
|
|
25
|
+
return (0, 0)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class IncrementalAnalyzer:
|
|
29
|
+
"""Track file signatures to skip unchanged files on subsequent runs.
|
|
30
|
+
|
|
31
|
+
Usage::
|
|
32
|
+
|
|
33
|
+
inc = IncrementalAnalyzer(project_dir)
|
|
34
|
+
|
|
35
|
+
for filepath in files:
|
|
36
|
+
if inc.needs_analysis(filepath):
|
|
37
|
+
result = analyze(filepath)
|
|
38
|
+
inc.update(filepath, result)
|
|
39
|
+
else:
|
|
40
|
+
result = inc.get_cached_result(filepath)
|
|
41
|
+
|
|
42
|
+
inc.save() # persist state for next run
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, project_dir: str):
|
|
46
|
+
self._project_dir = Path(project_dir).resolve()
|
|
47
|
+
self._cache_path = self._project_dir / CACHE_FILE
|
|
48
|
+
self._state: Dict[str, Dict[str, Any]] = self._load_cache()
|
|
49
|
+
self._dirty = False
|
|
50
|
+
|
|
51
|
+
# ------------------------------------------------------------------
|
|
52
|
+
# Public API
|
|
53
|
+
# ------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
def needs_analysis(self, filepath: str) -> bool:
|
|
56
|
+
"""Return True if file has changed since last cached analysis."""
|
|
57
|
+
key = self._normalize_key(filepath)
|
|
58
|
+
sig = _file_signature(filepath)
|
|
59
|
+
|
|
60
|
+
cached = self._state.get(key)
|
|
61
|
+
if not cached:
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
cached_sig = (cached.get("mtime_ns", 0), cached.get("size", 0))
|
|
65
|
+
return sig != cached_sig
|
|
66
|
+
|
|
67
|
+
def get_cached_result(self, filepath: str) -> Optional[Dict]:
|
|
68
|
+
"""Return previously cached analysis result, or None."""
|
|
69
|
+
key = self._normalize_key(filepath)
|
|
70
|
+
cached = self._state.get(key)
|
|
71
|
+
if cached:
|
|
72
|
+
return cached.get("result")
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
def update(self, filepath: str, result: Dict) -> None:
|
|
76
|
+
"""Store analysis result and file signature for future runs."""
|
|
77
|
+
key = self._normalize_key(filepath)
|
|
78
|
+
sig = _file_signature(filepath)
|
|
79
|
+
|
|
80
|
+
self._state[key] = {
|
|
81
|
+
"mtime_ns": sig[0],
|
|
82
|
+
"size": sig[1],
|
|
83
|
+
"result": result,
|
|
84
|
+
}
|
|
85
|
+
self._dirty = True
|
|
86
|
+
|
|
87
|
+
def invalidate(self, filepath: str) -> None:
|
|
88
|
+
"""Remove cached state for a file (e.g. after deletion)."""
|
|
89
|
+
key = self._normalize_key(filepath)
|
|
90
|
+
if key in self._state:
|
|
91
|
+
del self._state[key]
|
|
92
|
+
self._dirty = True
|
|
93
|
+
|
|
94
|
+
def save(self) -> None:
|
|
95
|
+
"""Persist incremental state to disk."""
|
|
96
|
+
if not self._dirty:
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Write atomically via temp file
|
|
101
|
+
tmp_path = self._cache_path.with_suffix(".tmp")
|
|
102
|
+
with open(tmp_path, "w", encoding="utf-8") as f:
|
|
103
|
+
json.dump(self._state, f, indent=2, default=str)
|
|
104
|
+
tmp_path.replace(self._cache_path)
|
|
105
|
+
self._dirty = False
|
|
106
|
+
logger.debug("Saved incremental cache: %d files", len(self._state))
|
|
107
|
+
except OSError as e:
|
|
108
|
+
logger.warning("Cannot save incremental cache: %s", e)
|
|
109
|
+
|
|
110
|
+
def clear(self) -> None:
|
|
111
|
+
"""Clear all cached state (force full re-analysis)."""
|
|
112
|
+
self._state.clear()
|
|
113
|
+
self._dirty = True
|
|
114
|
+
if self._cache_path.exists():
|
|
115
|
+
try:
|
|
116
|
+
self._cache_path.unlink()
|
|
117
|
+
except OSError:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def cached_count(self) -> int:
|
|
122
|
+
"""Number of files in cache."""
|
|
123
|
+
return len(self._state)
|
|
124
|
+
|
|
125
|
+
# ------------------------------------------------------------------
|
|
126
|
+
# Internal
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
def _load_cache(self) -> Dict[str, Dict[str, Any]]:
|
|
130
|
+
"""Load previous cache from disk, or return empty dict."""
|
|
131
|
+
if not self._cache_path.exists():
|
|
132
|
+
return {}
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
with open(self._cache_path, "r", encoding="utf-8") as f:
|
|
136
|
+
data = json.load(f)
|
|
137
|
+
if isinstance(data, dict):
|
|
138
|
+
return data
|
|
139
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
140
|
+
logger.debug("Cannot load incremental cache: %s", e)
|
|
141
|
+
|
|
142
|
+
return {}
|
|
143
|
+
|
|
144
|
+
def _normalize_key(self, filepath: str) -> str:
|
|
145
|
+
"""Normalize filepath to relative path from project root."""
|
|
146
|
+
try:
|
|
147
|
+
return str(Path(filepath).resolve().relative_to(self._project_dir))
|
|
148
|
+
except ValueError:
|
|
149
|
+
# File outside project — use absolute path
|
|
150
|
+
return str(Path(filepath).resolve())
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""C# analyzer (regex-based)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
from code2llm.core.lang.base import analyze_c_family
|
|
7
|
+
|
|
8
|
+
# C#-specific patterns
|
|
9
|
+
_CSHARP_PATTERNS = {
|
|
10
|
+
'import': re.compile(r'^\s*using\s+([\w.]+)\s*;'),
|
|
11
|
+
'class': re.compile(
|
|
12
|
+
r'^\s*(?:public\s+|private\s+|internal\s+|protected\s+)?'
|
|
13
|
+
r'(?:abstract\s+|sealed\s+)?'
|
|
14
|
+
r'class\s+(\w+)'
|
|
15
|
+
r'(?:\s*:\s*(\w+))?'
|
|
16
|
+
),
|
|
17
|
+
'interface': re.compile(
|
|
18
|
+
r'^\s*(?:public\s+|private\s+|internal\s+)?interface\s+(\w+)'
|
|
19
|
+
),
|
|
20
|
+
'function': re.compile(
|
|
21
|
+
r'^\s*(?:public\s+|private\s+|protected\s+|internal\s+)?'
|
|
22
|
+
r'(?:static\s+|virtual\s+|override\s+|abstract\s+)?'
|
|
23
|
+
r'(?:async\s+)?'
|
|
24
|
+
r'(?:[\w<>,\[\]]+\s+)?'
|
|
25
|
+
r'(\w+)\s*\([^)]*\)'
|
|
26
|
+
),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_CSHARP_CONFIG = {
|
|
30
|
+
'index_files': (),
|
|
31
|
+
'brace_track': True,
|
|
32
|
+
'reserved': {'if', 'for', 'while', 'switch', 'return', 'catch', 'class', 'namespace'},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def analyze_csharp(content: str, file_path: str, module_name: str,
|
|
37
|
+
ext: str, stats: Dict) -> Dict:
|
|
38
|
+
"""Analyze C# files using shared C-family extraction."""
|
|
39
|
+
return analyze_c_family(
|
|
40
|
+
content, file_path, module_name, stats,
|
|
41
|
+
_CSHARP_PATTERNS, _CSHARP_CONFIG,
|
|
42
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Java analyzer (regex-based)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
from code2llm.core.lang.base import analyze_c_family
|
|
7
|
+
|
|
8
|
+
# Java-specific patterns
|
|
9
|
+
_JAVA_PATTERNS = {
|
|
10
|
+
'import': re.compile(r'^\s*import\s+([\w.]+)\s*;'),
|
|
11
|
+
'class': re.compile(
|
|
12
|
+
r'^\s*(?:public\s+|private\s+|protected\s+)?'
|
|
13
|
+
r'(?:abstract\s+|final\s+)?'
|
|
14
|
+
r'class\s+(\w+)'
|
|
15
|
+
r'(?:\s+extends\s+(\w+))?'
|
|
16
|
+
r'(?:\s+implements\s+([\w,\s]+))?'
|
|
17
|
+
),
|
|
18
|
+
'interface': re.compile(
|
|
19
|
+
r'^\s*(?:public\s+|private\s+|protected\s+)?'
|
|
20
|
+
r'interface\s+(\w+)'
|
|
21
|
+
),
|
|
22
|
+
'function': re.compile(
|
|
23
|
+
r'^\s*(?:public\s+|private\s+|protected\s+)?'
|
|
24
|
+
r'(?:static\s+|final\s+|abstract\s+|synchronized\s+)?'
|
|
25
|
+
r'(?:[\w<>,\[\]]+\s+)?'
|
|
26
|
+
r'(\w+)\s*\([^)]*\)'
|
|
27
|
+
),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_JAVA_CONFIG = {
|
|
31
|
+
'index_files': (),
|
|
32
|
+
'brace_track': True,
|
|
33
|
+
'reserved': {'if', 'for', 'while', 'switch', 'return', 'catch', 'class', 'interface'},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def analyze_java(content: str, file_path: str, module_name: str,
|
|
38
|
+
ext: str, stats: Dict) -> Dict:
|
|
39
|
+
"""Analyze Java files using shared C-family extraction."""
|
|
40
|
+
return analyze_c_family(
|
|
41
|
+
content, file_path, module_name, stats,
|
|
42
|
+
_JAVA_PATTERNS, _JAVA_CONFIG,
|
|
43
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code2llm
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.102
|
|
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
|
|
@@ -56,7 +56,7 @@ Dynamic: requires-python
|
|
|
56
56
|
|
|
57
57
|
## AI Cost Tracking
|
|
58
58
|
|
|
59
|
-
    
|
|
60
60
|
  
|
|
61
61
|
|
|
62
62
|
- 🤖 **LLM usage:** $7.5000 (148 commits)
|
|
@@ -36,10 +36,12 @@ code2llm/core/__init__.py
|
|
|
36
36
|
code2llm/core/analyzer.py
|
|
37
37
|
code2llm/core/ast_registry.py
|
|
38
38
|
code2llm/core/config.py
|
|
39
|
+
code2llm/core/export_pipeline.py
|
|
39
40
|
code2llm/core/file_analyzer.py
|
|
40
41
|
code2llm/core/file_cache.py
|
|
41
42
|
code2llm/core/file_filter.py
|
|
42
43
|
code2llm/core/gitignore.py
|
|
44
|
+
code2llm/core/incremental.py
|
|
43
45
|
code2llm/core/large_repo.py
|
|
44
46
|
code2llm/core/models.py
|
|
45
47
|
code2llm/core/refactoring.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code2llm"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.102"
|
|
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"
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"""C# analyzer (regex-based)."""
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from typing import Dict
|
|
5
|
-
|
|
6
|
-
from code2llm.core.models import ClassInfo, FunctionInfo, ModuleInfo
|
|
7
|
-
from code2llm.core.lang.base import calculate_complexity_regex, extract_calls_regex, _extract_declarations
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def analyze_csharp(content: str, file_path: str, module_name: str,
|
|
11
|
-
ext: str, stats: Dict) -> Dict:
|
|
12
|
-
"""Analyze C# files using shared extraction."""
|
|
13
|
-
|
|
14
|
-
patterns = {
|
|
15
|
-
'import': re.compile(r'^\s*using\s+([\w.]+)\s*;'),
|
|
16
|
-
'class': re.compile(
|
|
17
|
-
r'^\s*(?:public\s+|private\s+|internal\s+|protected\s+)?'
|
|
18
|
-
r'(?:abstract\s+|sealed\s+)?'
|
|
19
|
-
r'class\s+(\w+)'
|
|
20
|
-
r'(?:\s*:\s*(\w+))?'
|
|
21
|
-
),
|
|
22
|
-
'interface': re.compile(
|
|
23
|
-
r'^\s*(?:public\s+|private\s+|internal\s+)?interface\s+(\w+)'
|
|
24
|
-
),
|
|
25
|
-
'function': re.compile(
|
|
26
|
-
r'^\s*(?:public\s+|private\s+|protected\s+|internal\s+)?'
|
|
27
|
-
r'(?:static\s+|virtual\s+|override\s+|abstract\s+)?'
|
|
28
|
-
r'(?:async\s+)?'
|
|
29
|
-
r'(?:[\w<>,\[\]]+\s+)?' # return type with generics
|
|
30
|
-
r'(\w+)\s*\([^)]*\)' # name and params
|
|
31
|
-
),
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
lang_config = {
|
|
35
|
-
'index_files': (),
|
|
36
|
-
'brace_track': True,
|
|
37
|
-
'reserved': {'if', 'for', 'while', 'switch', 'return', 'catch', 'class', 'namespace'},
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
result = _extract_declarations(
|
|
41
|
-
content, file_path, module_name,
|
|
42
|
-
patterns, stats, lang_config
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
calculate_complexity_regex(content, result, lang='c_family')
|
|
46
|
-
extract_calls_regex(content, module_name, result)
|
|
47
|
-
|
|
48
|
-
stats['files_processed'] += 1
|
|
49
|
-
return result
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"""Java analyzer (regex-based)."""
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from typing import Dict
|
|
5
|
-
|
|
6
|
-
from code2llm.core.models import ClassInfo, FunctionInfo, ModuleInfo
|
|
7
|
-
from code2llm.core.lang.base import calculate_complexity_regex, extract_calls_regex, _extract_declarations
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def analyze_java(content: str, file_path: str, module_name: str,
|
|
11
|
-
ext: str, stats: Dict) -> Dict:
|
|
12
|
-
"""Analyze Java files using shared extraction."""
|
|
13
|
-
|
|
14
|
-
patterns = {
|
|
15
|
-
'import': re.compile(r'^\s*import\s+([\w.]+)\s*;'),
|
|
16
|
-
'class': re.compile(
|
|
17
|
-
r'^\s*(?:public\s+|private\s+|protected\s+)?'
|
|
18
|
-
r'(?:abstract\s+|final\s+)?'
|
|
19
|
-
r'class\s+(\w+)'
|
|
20
|
-
r'(?:\s+extends\s+(\w+))?'
|
|
21
|
-
r'(?:\s+implements\s+([\w,\s]+))?'
|
|
22
|
-
),
|
|
23
|
-
'interface': re.compile(
|
|
24
|
-
r'^\s*(?:public\s+|private\s+|protected\s+)?'
|
|
25
|
-
r'interface\s+(\w+)'
|
|
26
|
-
),
|
|
27
|
-
'function': re.compile(
|
|
28
|
-
r'^\s*(?:public\s+|private\s+|protected\s+)?'
|
|
29
|
-
r'(?:static\s+|final\s+|abstract\s+|synchronized\s+)?'
|
|
30
|
-
r'(?:[\w<>,\[\]]+\s+)?' # return type with generics
|
|
31
|
-
r'(\w+)\s*\([^)]*\)' # name and params
|
|
32
|
-
),
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
lang_config = {
|
|
36
|
-
'index_files': (),
|
|
37
|
-
'brace_track': True,
|
|
38
|
-
'reserved': {'if', 'for', 'while', 'switch', 'return', 'catch', 'class', 'interface'},
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
result = _extract_declarations(
|
|
42
|
-
content, file_path, module_name,
|
|
43
|
-
patterns, stats, lang_config
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
calculate_complexity_regex(content, result, lang='c_family')
|
|
47
|
-
extract_calls_regex(content, module_name, result)
|
|
48
|
-
|
|
49
|
-
stats['files_processed'] += 1
|
|
50
|
-
return result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|