code-review-graph-codeblackwell 2.3.6.post1__py3-none-any.whl

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 (74) hide show
  1. code_review_graph/__init__.py +20 -0
  2. code_review_graph/__main__.py +4 -0
  3. code_review_graph/analysis.py +410 -0
  4. code_review_graph/changes.py +409 -0
  5. code_review_graph/cli.py +1255 -0
  6. code_review_graph/communities.py +874 -0
  7. code_review_graph/constants.py +23 -0
  8. code_review_graph/context_savings.py +317 -0
  9. code_review_graph/custom_languages.py +322 -0
  10. code_review_graph/daemon.py +1009 -0
  11. code_review_graph/daemon_cli.py +320 -0
  12. code_review_graph/docs/LLM-OPTIMIZED-REFERENCE.md +71 -0
  13. code_review_graph/embeddings.py +1006 -0
  14. code_review_graph/enrich.py +303 -0
  15. code_review_graph/eval/__init__.py +33 -0
  16. code_review_graph/eval/benchmarks/__init__.py +1 -0
  17. code_review_graph/eval/benchmarks/agent_baseline.py +193 -0
  18. code_review_graph/eval/benchmarks/build_performance.py +60 -0
  19. code_review_graph/eval/benchmarks/flow_completeness.py +36 -0
  20. code_review_graph/eval/benchmarks/impact_accuracy.py +220 -0
  21. code_review_graph/eval/benchmarks/multi_hop_retrieval.py +125 -0
  22. code_review_graph/eval/benchmarks/search_quality.py +59 -0
  23. code_review_graph/eval/benchmarks/token_efficiency.py +143 -0
  24. code_review_graph/eval/configs/code-review-graph.yaml +50 -0
  25. code_review_graph/eval/configs/express.yaml +45 -0
  26. code_review_graph/eval/configs/fastapi.yaml +48 -0
  27. code_review_graph/eval/configs/flask.yaml +50 -0
  28. code_review_graph/eval/configs/gin.yaml +51 -0
  29. code_review_graph/eval/configs/httpx.yaml +48 -0
  30. code_review_graph/eval/reporter.py +301 -0
  31. code_review_graph/eval/runner.py +211 -0
  32. code_review_graph/eval/scorer.py +85 -0
  33. code_review_graph/eval/token_benchmark.py +182 -0
  34. code_review_graph/exports.py +409 -0
  35. code_review_graph/flows.py +698 -0
  36. code_review_graph/graph.py +1427 -0
  37. code_review_graph/graph_diff.py +122 -0
  38. code_review_graph/hints.py +384 -0
  39. code_review_graph/incremental.py +1245 -0
  40. code_review_graph/jedi_resolver.py +303 -0
  41. code_review_graph/main.py +1079 -0
  42. code_review_graph/memory.py +142 -0
  43. code_review_graph/migrations.py +284 -0
  44. code_review_graph/parser.py +6957 -0
  45. code_review_graph/postprocessing.py +134 -0
  46. code_review_graph/prompts.py +159 -0
  47. code_review_graph/refactor.py +852 -0
  48. code_review_graph/registry.py +319 -0
  49. code_review_graph/rescript_resolver.py +206 -0
  50. code_review_graph/search.py +447 -0
  51. code_review_graph/skills.py +1481 -0
  52. code_review_graph/spring_resolver.py +200 -0
  53. code_review_graph/temporal_resolver.py +199 -0
  54. code_review_graph/token_benchmark.py +125 -0
  55. code_review_graph/tools/__init__.py +156 -0
  56. code_review_graph/tools/_common.py +176 -0
  57. code_review_graph/tools/analysis_tools.py +184 -0
  58. code_review_graph/tools/build.py +541 -0
  59. code_review_graph/tools/community_tools.py +246 -0
  60. code_review_graph/tools/context.py +152 -0
  61. code_review_graph/tools/docs.py +274 -0
  62. code_review_graph/tools/flows_tools.py +176 -0
  63. code_review_graph/tools/query.py +692 -0
  64. code_review_graph/tools/refactor_tools.py +168 -0
  65. code_review_graph/tools/registry_tools.py +125 -0
  66. code_review_graph/tools/review.py +477 -0
  67. code_review_graph/tsconfig_resolver.py +257 -0
  68. code_review_graph/visualization.py +2184 -0
  69. code_review_graph/wiki.py +305 -0
  70. code_review_graph_codeblackwell-2.3.6.post1.dist-info/METADATA +718 -0
  71. code_review_graph_codeblackwell-2.3.6.post1.dist-info/RECORD +74 -0
  72. code_review_graph_codeblackwell-2.3.6.post1.dist-info/WHEEL +4 -0
  73. code_review_graph_codeblackwell-2.3.6.post1.dist-info/entry_points.txt +3 -0
  74. code_review_graph_codeblackwell-2.3.6.post1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,257 @@
1
+ """TypeScript tsconfig.json path alias resolver.
2
+
3
+ Resolves TypeScript path aliases (e.g., ``@/ -> src/``) declared in
4
+ ``compilerOptions.paths`` so that ``IMPORTS_FROM`` edges can point to
5
+ real file paths instead of raw alias strings.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ import re
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # Extensions probed when resolving an alias target
19
+ _PROBE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".vue"]
20
+
21
+ # Tsconfig filenames to look for when walking up the directory tree
22
+ _TSCONFIG_NAMES = ["tsconfig.json", "tsconfig.app.json"]
23
+
24
+
25
+ class TsconfigResolver:
26
+ """Resolves TypeScript path aliases (e.g., @/ -> src/) using tsconfig.json."""
27
+
28
+ def __init__(self) -> None:
29
+ self._cache: dict[str, Optional[dict]] = {}
30
+
31
+ # ------------------------------------------------------------------
32
+ # Public API
33
+ # ------------------------------------------------------------------
34
+
35
+ def resolve_alias(self, import_str: str, file_path: str) -> Optional[str]:
36
+ """Resolve a TS path alias to an absolute file path, or None."""
37
+ try:
38
+ config = self._load_tsconfig_for_file(file_path)
39
+ if config is None:
40
+ return None
41
+
42
+ base_url: Optional[str] = config.get("baseUrl")
43
+ paths: dict[str, list[str]] = config.get("paths", {})
44
+ tsconfig_dir: str = config.get("_tsconfig_dir", "")
45
+
46
+ if not paths:
47
+ return None
48
+
49
+ if base_url:
50
+ base_dir = (Path(tsconfig_dir) / base_url).resolve()
51
+ else:
52
+ base_dir = Path(tsconfig_dir).resolve()
53
+
54
+ return self._match_and_probe(import_str, paths, base_dir)
55
+ except (OSError, ValueError, TypeError):
56
+ logger.debug(
57
+ "TsconfigResolver: unexpected error for %s", file_path, exc_info=True,
58
+ )
59
+ return None
60
+
61
+ # ------------------------------------------------------------------
62
+ # Internal helpers
63
+ # ------------------------------------------------------------------
64
+
65
+ def _load_tsconfig_for_file(self, file_path: str) -> Optional[dict]:
66
+ """Find and load tsconfig.json for the given file."""
67
+ start_dir = Path(file_path).parent.resolve()
68
+ current = start_dir
69
+ visited: list[str] = []
70
+
71
+ while True:
72
+ dir_str = str(current)
73
+ if dir_str in self._cache:
74
+ result = self._cache[dir_str]
75
+ for visited_dir in visited:
76
+ self._cache[visited_dir] = result
77
+ return result
78
+
79
+ visited.append(dir_str)
80
+
81
+ for name in _TSCONFIG_NAMES:
82
+ candidate = current / name
83
+ if candidate.is_file():
84
+ config = self._parse_tsconfig(candidate)
85
+ config["_tsconfig_dir"] = dir_str
86
+ for visited_dir in visited:
87
+ self._cache[visited_dir] = config
88
+ return config
89
+
90
+ parent = current.parent
91
+ if parent == current:
92
+ for visited_dir in visited:
93
+ self._cache[visited_dir] = None
94
+ return None
95
+ current = parent
96
+
97
+ def _parse_tsconfig(self, tsconfig_path: Path) -> dict:
98
+ """Parse a tsconfig.json file (supports JSONC comments)."""
99
+ seen: set[str] = set()
100
+ return self._resolve_extends(tsconfig_path, seen)
101
+
102
+ def _resolve_extends(self, tsconfig_path: Path, seen: set[str]) -> dict:
103
+ """Recursively resolve the tsconfig extends chain."""
104
+ canonical = str(tsconfig_path.resolve())
105
+ if canonical in seen:
106
+ logger.debug("TsconfigResolver: cycle detected at %s", canonical)
107
+ return {}
108
+ seen = seen | {canonical}
109
+
110
+ try:
111
+ raw = tsconfig_path.read_text(encoding="utf-8", errors="replace")
112
+ except OSError:
113
+ logger.debug("TsconfigResolver: cannot read %s", tsconfig_path)
114
+ return {}
115
+
116
+ stripped = self._strip_jsonc_comments(raw)
117
+ try:
118
+ data: dict = json.loads(stripped)
119
+ except json.JSONDecodeError:
120
+ logger.debug("TsconfigResolver: invalid JSON in %s", tsconfig_path)
121
+ return {}
122
+
123
+ result: dict = {}
124
+
125
+ extends: Optional[str] = data.get("extends")
126
+ if extends and isinstance(extends, str) and extends.startswith("."):
127
+ parent_path = (tsconfig_path.parent / extends).resolve()
128
+ if not parent_path.suffix:
129
+ parent_path = parent_path.with_suffix(".json")
130
+ if parent_path.is_file():
131
+ parent_config = self._resolve_extends(parent_path, seen)
132
+ parent_opts = parent_config.get("compilerOptions", {})
133
+ result.setdefault("compilerOptions", {}).update(parent_opts)
134
+
135
+ child_opts: dict = data.get("compilerOptions", {})
136
+ result.setdefault("compilerOptions", {}).update(child_opts)
137
+
138
+ compiler_options = result.get("compilerOptions", {})
139
+ if "baseUrl" in compiler_options:
140
+ result["baseUrl"] = compiler_options["baseUrl"]
141
+ if "paths" in compiler_options:
142
+ result["paths"] = compiler_options["paths"]
143
+
144
+ return result
145
+
146
+ def _strip_jsonc_comments(self, text: str) -> str:
147
+ """Remove // and /* */ comments and trailing commas from JSONC."""
148
+ result: list[str] = []
149
+ i = 0
150
+ n = len(text)
151
+
152
+ while i < n:
153
+ ch = text[i]
154
+
155
+ if ch == '"':
156
+ result.append(ch)
157
+ i += 1
158
+ while i < n:
159
+ c = text[i]
160
+ result.append(c)
161
+ if c == "\\" and i + 1 < n:
162
+ i += 1
163
+ result.append(text[i])
164
+ elif c == '"':
165
+ break
166
+ i += 1
167
+ i += 1
168
+ continue
169
+
170
+ if ch == "/" and i + 1 < n and text[i + 1] == "*":
171
+ i += 2
172
+ while i < n - 1:
173
+ if text[i] == "*" and text[i + 1] == "/":
174
+ i += 2
175
+ break
176
+ i += 1
177
+ else:
178
+ i = n
179
+ continue
180
+
181
+ if ch == "/" and i + 1 < n and text[i + 1] == "/":
182
+ i += 2
183
+ while i < n and text[i] != "\n":
184
+ i += 1
185
+ continue
186
+
187
+ result.append(ch)
188
+ i += 1
189
+
190
+ stripped = "".join(result)
191
+ stripped = re.sub(r",\s*([\]}])", r"\1", stripped)
192
+ return stripped
193
+
194
+ def _match_and_probe(
195
+ self,
196
+ import_str: str,
197
+ paths: dict[str, list[str]],
198
+ base_dir: Path,
199
+ ) -> Optional[str]:
200
+ """Match import_str against alias patterns and probe the filesystem."""
201
+ def _pattern_specificity(item: tuple[str, list[str]]) -> int:
202
+ pat = item[0]
203
+ return len(pat.partition("*")[0])
204
+
205
+ sorted_paths = sorted(paths.items(), key=_pattern_specificity, reverse=True)
206
+
207
+ for pattern, replacements in sorted_paths:
208
+ suffix = _match_pattern(pattern, import_str)
209
+ if suffix is None:
210
+ continue
211
+
212
+ for replacement in replacements:
213
+ if "*" in replacement:
214
+ mapped = replacement.replace("*", suffix, 1)
215
+ else:
216
+ mapped = replacement
217
+
218
+ candidate_base = (base_dir / mapped).resolve()
219
+ found = _probe_path(candidate_base)
220
+ if found:
221
+ return str(found)
222
+
223
+ return None
224
+
225
+
226
+ # ---------------------------------------------------------------------------
227
+ # Module-level helpers
228
+ # ---------------------------------------------------------------------------
229
+
230
+
231
+ def _match_pattern(pattern: str, import_str: str) -> Optional[str]:
232
+ """Return the wildcard-matched suffix if pattern matches import_str."""
233
+ if "*" not in pattern:
234
+ return "" if import_str == pattern else None
235
+
236
+ prefix, _, suffix_pat = pattern.partition("*")
237
+ if not (import_str.startswith(prefix) and import_str.endswith(suffix_pat)):
238
+ return None
239
+
240
+ end = len(import_str) - len(suffix_pat) if suffix_pat else len(import_str)
241
+ return import_str[len(prefix):end]
242
+
243
+
244
+ def _probe_path(base: Path) -> Optional[Path]:
245
+ """Probe base and base + extensions for an existing file."""
246
+ if base.is_file():
247
+ return base
248
+ for ext in _PROBE_EXTENSIONS:
249
+ candidate = base.with_suffix(ext) if not base.suffix else Path(str(base) + ext)
250
+ if candidate.is_file():
251
+ return candidate
252
+ if base.is_dir():
253
+ for ext in _PROBE_EXTENSIONS:
254
+ candidate = base / f"index{ext}"
255
+ if candidate.is_file():
256
+ return candidate
257
+ return None