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.
Files changed (128) hide show
  1. {code2llm-0.5.100 → code2llm-0.5.102}/PKG-INFO +2 -2
  2. {code2llm-0.5.100 → code2llm-0.5.102}/README.md +1 -1
  3. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/__init__.py +1 -1
  4. code2llm-0.5.102/code2llm/core/export_pipeline.py +153 -0
  5. code2llm-0.5.102/code2llm/core/incremental.py +150 -0
  6. code2llm-0.5.102/code2llm/core/lang/csharp.py +42 -0
  7. code2llm-0.5.102/code2llm/core/lang/java.py +43 -0
  8. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/__init__.py +1 -1
  9. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/PKG-INFO +2 -2
  10. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/SOURCES.txt +2 -0
  11. {code2llm-0.5.100 → code2llm-0.5.102}/pyproject.toml +1 -1
  12. {code2llm-0.5.100 → code2llm-0.5.102}/setup.py +1 -1
  13. code2llm-0.5.100/code2llm/core/lang/csharp.py +0 -49
  14. code2llm-0.5.100/code2llm/core/lang/java.py +0 -50
  15. {code2llm-0.5.100 → code2llm-0.5.102}/LICENSE +0 -0
  16. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/__main__.py +0 -0
  17. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/__init__.py +0 -0
  18. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/call_graph.py +0 -0
  19. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/cfg.py +0 -0
  20. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/coupling.py +0 -0
  21. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/data_analysis.py +0 -0
  22. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/dfg.py +0 -0
  23. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/pipeline_detector.py +0 -0
  24. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/side_effects.py +0 -0
  25. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/smells.py +0 -0
  26. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/type_inference.py +0 -0
  27. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/utils/__init__.py +0 -0
  28. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/analysis/utils/ast_helpers.py +0 -0
  29. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/api.py +0 -0
  30. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli.py +0 -0
  31. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_analysis.py +0 -0
  32. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_commands.py +0 -0
  33. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_exports/__init__.py +0 -0
  34. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_exports/code2logic.py +0 -0
  35. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_exports/formats.py +0 -0
  36. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_exports/orchestrator.py +0 -0
  37. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_exports/prompt.py +0 -0
  38. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/cli_parser.py +0 -0
  39. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/__init__.py +0 -0
  40. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/analyzer.py +0 -0
  41. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/ast_registry.py +0 -0
  42. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/config.py +0 -0
  43. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/file_analyzer.py +0 -0
  44. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/file_cache.py +0 -0
  45. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/file_filter.py +0 -0
  46. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/gitignore.py +0 -0
  47. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/__init__.py +0 -0
  48. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/base.py +0 -0
  49. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/cpp.py +0 -0
  50. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/generic.py +0 -0
  51. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/go_lang.py +0 -0
  52. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/php.py +0 -0
  53. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/ruby.py +0 -0
  54. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/rust.py +0 -0
  55. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/lang/typescript.py +0 -0
  56. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/large_repo.py +0 -0
  57. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/models.py +0 -0
  58. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/refactoring.py +0 -0
  59. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/repo_files.py +0 -0
  60. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/__init__.py +0 -0
  61. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/cache.py +0 -0
  62. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/incremental.py +0 -0
  63. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/prioritizer.py +0 -0
  64. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/scanner.py +0 -0
  65. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming/strategies.py +0 -0
  66. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/streaming_analyzer.py +0 -0
  67. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/core/toon_size_manager.py +0 -0
  68. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/__init__.py +0 -0
  69. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/article_view.py +0 -0
  70. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/base.py +0 -0
  71. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/context_exporter.py +0 -0
  72. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/context_view.py +0 -0
  73. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/evolution_exporter.py +0 -0
  74. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/flow_constants.py +0 -0
  75. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/flow_exporter.py +0 -0
  76. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/flow_renderer.py +0 -0
  77. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/html_dashboard.py +0 -0
  78. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/index_generator.py +0 -0
  79. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/json_exporter.py +0 -0
  80. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/llm_exporter.py +0 -0
  81. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/map_exporter.py +0 -0
  82. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/mermaid_exporter.py +0 -0
  83. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
  84. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/project_yaml_exporter.py +0 -0
  85. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/readme_exporter.py +0 -0
  86. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/report_generators.py +0 -0
  87. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon/__init__.py +0 -0
  88. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon/helpers.py +0 -0
  89. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon/metrics.py +0 -0
  90. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon/module_detail.py +0 -0
  91. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon/renderer.py +0 -0
  92. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon.py +0 -0
  93. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/toon_view.py +0 -0
  94. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/validate_project.py +0 -0
  95. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/exporters/yaml_exporter.py +0 -0
  96. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/generators/__init__.py +0 -0
  97. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/generators/llm_flow.py +0 -0
  98. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/generators/llm_task.py +0 -0
  99. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/generators/mermaid.py +0 -0
  100. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/config.py +0 -0
  101. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/entity_resolution.py +0 -0
  102. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/intent_matching.py +0 -0
  103. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/normalization.py +0 -0
  104. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/nlp/pipeline.py +0 -0
  105. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/patterns/__init__.py +0 -0
  106. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/patterns/detector.py +0 -0
  107. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/refactor/__init__.py +0 -0
  108. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm/refactor/prompt_engine.py +0 -0
  109. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/dependency_links.txt +0 -0
  110. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/entry_points.txt +0 -0
  111. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/requires.txt +0 -0
  112. {code2llm-0.5.100 → code2llm-0.5.102}/code2llm.egg-info/top_level.txt +0 -0
  113. {code2llm-0.5.100 → code2llm-0.5.102}/setup.cfg +0 -0
  114. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_advanced_analysis.py +0 -0
  115. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_analyzer.py +0 -0
  116. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_deep_analysis.py +0 -0
  117. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_edge_cases.py +0 -0
  118. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_flow_exporter.py +0 -0
  119. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_format_quality.py +0 -0
  120. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_multilanguage_e2e.py +0 -0
  121. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_nlp_pipeline.py +0 -0
  122. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_nonpython_cc_calls.py +0 -0
  123. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_pipeline_detector.py +0 -0
  124. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_project_toon_export.py +0 -0
  125. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_prompt_engine.py +0 -0
  126. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_prompt_txt.py +0 -0
  127. {code2llm-0.5.100 → code2llm-0.5.102}/tests/test_refactoring_engine.py +0 -0
  128. {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.100
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.100-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
59
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.102-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
60
60
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-51.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
61
61
 
62
62
  - 🤖 **LLM usage:** $7.5000 (148 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.100-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.102-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-51.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
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.100"
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
+ )
@@ -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.100"
7
+ __version__ = "0.5.102"
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.100
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.100-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
59
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.102-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
60
60
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-51.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
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.100"
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"
@@ -4,7 +4,7 @@ from setuptools import setup, find_packages
4
4
  import os
5
5
 
6
6
  # Read version
7
- version = "0.5.99"
7
+ version = "0.5.101"
8
8
 
9
9
  # Read long description
10
10
  def read_readme():
@@ -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