codeui-python 0.1.0__py3-none-any.whl → 0.1.1__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 (49) hide show
  1. codeui/__init__.py +1 -1
  2. codeui/agent/context.py +19 -3
  3. codeui/analysis/dead_code.py +13 -2
  4. codeui/analysis/undefined.py +70 -0
  5. codeui/analysis/unresolved_import.py +34 -4
  6. codeui/cli/main.py +101 -7
  7. codeui/core/cache.py +54 -0
  8. codeui/core/graph.py +233 -3
  9. codeui/core/override.py +1 -0
  10. codeui/core/resolver.py +51 -37
  11. codeui/errors.py +10 -0
  12. codeui/lang/base.py +5 -2
  13. codeui/lang/cpp.py +271 -0
  14. codeui/lang/csharp.py +262 -0
  15. codeui/lang/dart.py +278 -0
  16. codeui/lang/generic.py +6 -4
  17. codeui/lang/go.py +5 -2
  18. codeui/lang/java.py +320 -0
  19. codeui/lang/kotlin.py +303 -0
  20. codeui/lang/php.py +275 -0
  21. codeui/lang/python.py +49 -7
  22. codeui/lang/registry.py +22 -2
  23. codeui/lang/ruby.py +270 -0
  24. codeui/lang/rust.py +5 -2
  25. codeui/lang/scala.py +290 -0
  26. codeui/lang/swift.py +276 -0
  27. codeui/lang/ts.py +99 -8
  28. codeui/report/sarif_emitter.py +2 -1
  29. codeui/server/server.py +156 -85
  30. codeui/server/static/css/styles.css +1609 -0
  31. codeui/server/static/index.html +199 -0
  32. codeui/server/static/js/api.js +485 -0
  33. codeui/server/static/js/app.js +1044 -0
  34. codeui/server/static/js/graph_canvas.js +1485 -0
  35. codeui/server/static/js/minimap.js +383 -0
  36. codeui/server/static/js/sidebar.js +268 -0
  37. codeui/server/static/js/state.js +83 -0
  38. codeui/server/static/js/subgraph_window.js +1208 -0
  39. codeui/server/static/js/theme.js +332 -0
  40. codeui/tracer/__init__.py +4 -1
  41. codeui/tracer/tracer.py +109 -118
  42. codeui_python-0.1.1.dist-info/METADATA +171 -0
  43. codeui_python-0.1.1.dist-info/RECORD +71 -0
  44. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/licenses/LICENSE +1 -1
  45. codeui_python-0.1.0.dist-info/METADATA +0 -191
  46. codeui_python-0.1.0.dist-info/RECORD +0 -52
  47. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/WHEEL +0 -0
  48. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/entry_points.txt +0 -0
  49. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,332 @@
1
+ const THEMES = {
2
+ dark: {
3
+ id: "dark",
4
+ name: "Dark Slate",
5
+ cssVars: {
6
+ "--bg-base": "#0f172a",
7
+ "--bg-surface": "#1e293b",
8
+ "--bg-elevated": "#334155",
9
+ "--border-subtle": "#334155",
10
+ "--border-strong": "#475569",
11
+ "--text-main": "#f8fafc",
12
+ "--text-muted": "#94a3b8",
13
+ "--text-dim": "#64748b",
14
+ "--primary": "#2563eb",
15
+ "--primary-hover": "#1d4ed8",
16
+ "--accent": "#38bdf8",
17
+ "--danger": "#ef4444",
18
+ "--warning": "#f59e0b",
19
+ "--success": "#10b981",
20
+ "--purple": "#a855f7",
21
+ "--pink": "#ec4899",
22
+ "--panel-overlay-bg": "rgba(15, 23, 42, 0.88)",
23
+ "--panel-header-bg": "#1e293b",
24
+ "--minimap-bg": "rgba(15, 23, 42, 0.40)",
25
+ "--editor-bg": "#070a12",
26
+ "--editor-text": "#f8fafc",
27
+ "--editor-line-bg": "#0b0f19",
28
+ "--editor-line-text": "#475569",
29
+ "--subgraph-bg": "rgba(19, 27, 46, 0.98)",
30
+ "--subgraph-bar-bg": "rgba(15, 23, 42, 0.94)",
31
+ "--subgraph-bar-text": "#cbd5e1",
32
+ "--chip-bg": "rgba(30, 41, 59, 0.8)",
33
+ "--chip-text": "#cbd5e1",
34
+ "--card-hover-bg": "#1e293b"
35
+ },
36
+ canvas: {
37
+ nodeText: "#f1f5f9",
38
+ nodeTextDim: "#94a3b8",
39
+ nodeTextExternal: "#e9d5ff",
40
+ nodeTextExternalFile: "#c084fc",
41
+ nodeHoverRing: "#f8fafc",
42
+ nodeSelectedRing: "#38bdf8",
43
+ linkStroke: "rgba(71, 85, 105, 0.35)",
44
+ linkStrokeDim: "rgba(71, 85, 105, 0.05)",
45
+ minimapDots: "rgba(51, 65, 85, 0.35)",
46
+ minimapLinks: "rgba(71, 85, 105, 0.35)",
47
+ minimapViewfinder: "rgba(56, 189, 248, 0.15)",
48
+ minimapViewfinderBorder: "#38bdf8"
49
+ }
50
+ },
51
+ cyber: {
52
+ id: "cyber",
53
+ name: "Cyber Matrix",
54
+ cssVars: {
55
+ "--bg-base": "#030712",
56
+ "--bg-surface": "#0b132b",
57
+ "--bg-elevated": "#1c2541",
58
+ "--border-subtle": "#1f293d",
59
+ "--border-strong": "#00f0ff",
60
+ "--text-main": "#f0fdfa",
61
+ "--text-muted": "#5eead4",
62
+ "--text-dim": "#2dd4bf",
63
+ "--primary": "#00f0ff",
64
+ "--primary-hover": "#38bdf8",
65
+ "--accent": "#00f0ff",
66
+ "--danger": "#ef4444",
67
+ "--warning": "#f59e0b",
68
+ "--success": "#10b981",
69
+ "--purple": "#a855f7",
70
+ "--pink": "#ec4899",
71
+ "--panel-overlay-bg": "rgba(3, 7, 18, 0.90)",
72
+ "--panel-header-bg": "#0b132b",
73
+ "--minimap-bg": "rgba(3, 7, 18, 0.40)",
74
+ "--editor-bg": "#020617",
75
+ "--editor-text": "#f0fdfa",
76
+ "--editor-line-bg": "#090e1a",
77
+ "--editor-line-text": "#2dd4bf",
78
+ "--subgraph-bg": "rgba(11, 19, 43, 0.98)",
79
+ "--subgraph-bar-bg": "rgba(11, 19, 43, 0.94)",
80
+ "--subgraph-bar-text": "#5eead4",
81
+ "--chip-bg": "rgba(28, 37, 65, 0.8)",
82
+ "--chip-text": "#5eead4",
83
+ "--card-hover-bg": "#1c2541"
84
+ },
85
+ canvas: {
86
+ nodeText: "#f0fdfa",
87
+ nodeTextDim: "#2dd4bf",
88
+ nodeTextExternal: "#a5f3fc",
89
+ nodeTextExternalFile: "#00f0ff",
90
+ nodeHoverRing: "#00f0ff",
91
+ nodeSelectedRing: "#00f0ff",
92
+ linkStroke: "rgba(0, 240, 255, 0.35)",
93
+ linkStrokeDim: "rgba(0, 240, 255, 0.05)",
94
+ minimapDots: "rgba(0, 240, 255, 0.25)",
95
+ minimapLinks: "rgba(0, 240, 255, 0.35)",
96
+ minimapViewfinder: "rgba(0, 240, 255, 0.18)",
97
+ minimapViewfinderBorder: "#00f0ff"
98
+ }
99
+ },
100
+ light: {
101
+ id: "light",
102
+ name: "Light Studio",
103
+ cssVars: {
104
+ "--bg-base": "#f8fafc",
105
+ "--bg-surface": "#ffffff",
106
+ "--bg-elevated": "#f1f5f9",
107
+ "--border-subtle": "#e2e8f0",
108
+ "--border-strong": "#cbd5e1",
109
+ "--text-main": "#0f172a",
110
+ "--text-muted": "#475569",
111
+ "--text-dim": "#64748b",
112
+ "--primary": "#2563eb",
113
+ "--primary-hover": "#1d4ed8",
114
+ "--accent": "#0284c7",
115
+ "--danger": "#dc2626",
116
+ "--warning": "#d97706",
117
+ "--success": "#059669",
118
+ "--purple": "#9333ea",
119
+ "--pink": "#db2777",
120
+ "--panel-overlay-bg": "rgba(255, 255, 255, 0.92)",
121
+ "--panel-header-bg": "#ffffff",
122
+ "--minimap-bg": "rgba(255, 255, 255, 0.55)",
123
+ "--editor-bg": "#ffffff",
124
+ "--editor-text": "#0f172a",
125
+ "--editor-line-bg": "#f8fafc",
126
+ "--editor-line-text": "#64748b",
127
+ "--subgraph-bg": "rgba(255, 255, 255, 0.98)",
128
+ "--subgraph-bar-bg": "rgba(241, 245, 249, 0.94)",
129
+ "--subgraph-bar-text": "#0f172a",
130
+ "--chip-bg": "rgba(226, 232, 240, 0.8)",
131
+ "--chip-text": "#0f172a",
132
+ "--card-hover-bg": "#f1f5f9"
133
+ },
134
+ canvas: {
135
+ nodeText: "#0f172a",
136
+ nodeTextDim: "#64748b",
137
+ nodeTextExternal: "#7e22ce",
138
+ nodeTextExternalFile: "#6b21a8",
139
+ nodeHoverRing: "#0f172a",
140
+ nodeSelectedRing: "#0284c7",
141
+ linkStroke: "rgba(100, 116, 139, 0.45)",
142
+ linkStrokeDim: "rgba(148, 163, 184, 0.12)",
143
+ minimapDots: "rgba(148, 163, 184, 0.45)",
144
+ minimapLinks: "rgba(100, 116, 139, 0.40)",
145
+ minimapViewfinder: "rgba(2, 132, 199, 0.15)",
146
+ minimapViewfinderBorder: "#0284c7"
147
+ }
148
+ },
149
+ forest: {
150
+ id: "forest",
151
+ name: "Forest Matrix",
152
+ cssVars: {
153
+ "--bg-base": "#061a14",
154
+ "--bg-surface": "#0d2820",
155
+ "--bg-elevated": "#173d32",
156
+ "--border-subtle": "#173d32",
157
+ "--border-strong": "#10b981",
158
+ "--text-main": "#ecfdf5",
159
+ "--text-muted": "#6ee7b7",
160
+ "--text-dim": "#34d399",
161
+ "--primary": "#059669",
162
+ "--primary-hover": "#047857",
163
+ "--accent": "#10b981",
164
+ "--danger": "#ef4444",
165
+ "--warning": "#f59e0b",
166
+ "--success": "#10b981",
167
+ "--purple": "#a855f7",
168
+ "--pink": "#ec4899",
169
+ "--panel-overlay-bg": "rgba(6, 26, 20, 0.90)",
170
+ "--panel-header-bg": "#0d2820",
171
+ "--minimap-bg": "rgba(6, 26, 20, 0.45)",
172
+ "--editor-bg": "#03110d",
173
+ "--editor-text": "#ecfdf5",
174
+ "--editor-line-bg": "#061a14",
175
+ "--editor-line-text": "#34d399",
176
+ "--subgraph-bg": "rgba(13, 40, 32, 0.98)",
177
+ "--subgraph-bar-bg": "rgba(13, 40, 32, 0.94)",
178
+ "--subgraph-bar-text": "#6ee7b7",
179
+ "--chip-bg": "rgba(23, 61, 50, 0.8)",
180
+ "--chip-text": "#6ee7b7",
181
+ "--card-hover-bg": "#173d32"
182
+ },
183
+ canvas: {
184
+ nodeText: "#ecfdf5",
185
+ nodeTextDim: "#34d399",
186
+ nodeTextExternal: "#a7f3d0",
187
+ nodeTextExternalFile: "#10b981",
188
+ nodeHoverRing: "#10b981",
189
+ nodeSelectedRing: "#10b981",
190
+ linkStroke: "rgba(16, 185, 129, 0.35)",
191
+ linkStrokeDim: "rgba(16, 185, 129, 0.06)",
192
+ minimapDots: "rgba(16, 185, 129, 0.30)",
193
+ minimapLinks: "rgba(16, 185, 129, 0.35)",
194
+ minimapViewfinder: "rgba(16, 185, 129, 0.20)",
195
+ minimapViewfinderBorder: "#10b981"
196
+ }
197
+ },
198
+ monokai: {
199
+ id: "monokai",
200
+ name: "Monokai Pro",
201
+ cssVars: {
202
+ "--bg-base": "#272822",
203
+ "--bg-surface": "#383830",
204
+ "--bg-elevated": "#49483e",
205
+ "--border-subtle": "#49483e",
206
+ "--border-strong": "#f92672",
207
+ "--text-main": "#f8f8f2",
208
+ "--text-muted": "#a6e22e",
209
+ "--text-dim": "#75715e",
210
+ "--primary": "#f92672",
211
+ "--primary-hover": "#fd5c63",
212
+ "--accent": "#66d9ef",
213
+ "--danger": "#f92672",
214
+ "--warning": "#fd971f",
215
+ "--success": "#a6e22e",
216
+ "--purple": "#ae81ff",
217
+ "--pink": "#f92672",
218
+ "--panel-overlay-bg": "rgba(39, 40, 34, 0.90)",
219
+ "--panel-header-bg": "#383830",
220
+ "--minimap-bg": "rgba(39, 40, 34, 0.45)",
221
+ "--editor-bg": "#1e1f1c",
222
+ "--editor-text": "#f8f8f2",
223
+ "--editor-line-bg": "#272822",
224
+ "--editor-line-text": "#75715e",
225
+ "--subgraph-bg": "rgba(56, 56, 48, 0.98)",
226
+ "--subgraph-bar-bg": "rgba(56, 56, 48, 0.94)",
227
+ "--subgraph-bar-text": "#a6e22e",
228
+ "--chip-bg": "rgba(73, 72, 62, 0.8)",
229
+ "--chip-text": "#a6e22e",
230
+ "--card-hover-bg": "#49483e"
231
+ },
232
+ canvas: {
233
+ nodeText: "#f8f8f2",
234
+ nodeTextDim: "#75715e",
235
+ nodeTextExternal: "#ae81ff",
236
+ nodeTextExternalFile: "#66d9ef",
237
+ nodeHoverRing: "#66d9ef",
238
+ nodeSelectedRing: "#f92672",
239
+ linkStroke: "rgba(102, 217, 239, 0.35)",
240
+ linkStrokeDim: "rgba(102, 217, 239, 0.06)",
241
+ minimapDots: "rgba(117, 113, 94, 0.35)",
242
+ minimapLinks: "rgba(102, 217, 239, 0.35)",
243
+ minimapViewfinder: "rgba(249, 38, 114, 0.20)",
244
+ minimapViewfinderBorder: "#f92672"
245
+ }
246
+ }
247
+ };
248
+
249
+ class ThemeManager {
250
+ static themes = THEMES;
251
+
252
+ static getThemes() {
253
+ return Object.values(this.themes);
254
+ }
255
+
256
+ static getTheme(themeId) {
257
+ return this.themes[themeId] || this.themes.dark;
258
+ }
259
+
260
+ static getCanvasColors(themeId) {
261
+ const id = themeId || (window.appState ? window.appState.theme : "dark");
262
+ const theme = this.getTheme(id);
263
+ return theme.canvas;
264
+ }
265
+
266
+ static registerTheme(themeConfig) {
267
+ if (!themeConfig || !themeConfig.id) return;
268
+ this.themes[themeConfig.id] = themeConfig;
269
+ this.populateThemeSelector();
270
+ }
271
+
272
+ static populateThemeSelector() {
273
+ const select = document.getElementById("select-theme");
274
+ if (!select) return;
275
+ const current = window.appState ? window.appState.theme : "dark";
276
+ select.innerHTML = Object.values(this.themes).map(t =>
277
+ `<option value="${t.id}" ${t.id === current ? "selected" : ""}>Theme: ${t.name}</option>`
278
+ ).join("");
279
+ }
280
+
281
+ static applyTheme(themeId) {
282
+ const theme = this.getTheme(themeId);
283
+ if (window.appState) {
284
+ window.appState.theme = theme.id;
285
+ }
286
+ document.body.setAttribute("data-theme", theme.id);
287
+
288
+ if (theme.cssVars) {
289
+ for (const [key, val] of Object.entries(theme.cssVars)) {
290
+ document.documentElement.style.setProperty(key, val);
291
+ }
292
+ }
293
+
294
+ const select = document.getElementById("select-theme");
295
+ if (select && select.value !== theme.id) {
296
+ select.value = theme.id;
297
+ }
298
+
299
+ try {
300
+ localStorage.setItem("codeui_theme", theme.id);
301
+ } catch (_) {}
302
+
303
+ if (window.mainCanvas) {
304
+ window.mainCanvas.render();
305
+ if (window.mainCanvas.miniMap) {
306
+ window.mainCanvas.miniMap.update();
307
+ }
308
+ }
309
+
310
+ if (window.appState && window.appState.activeSubgraphs) {
311
+ window.appState.activeSubgraphs.forEach(win => {
312
+ if (win.canvasInstance) {
313
+ win.canvasInstance.render();
314
+ }
315
+ });
316
+ }
317
+ }
318
+
319
+ static init() {
320
+ this.populateThemeSelector();
321
+ let initialTheme = "dark";
322
+ try {
323
+ const saved = localStorage.getItem("codeui_theme");
324
+ if (saved && this.themes[saved]) {
325
+ initialTheme = saved;
326
+ }
327
+ } catch (_) {}
328
+ this.applyTheme(initialTheme);
329
+ }
330
+ }
331
+
332
+ window.ThemeManager = ThemeManager;
codeui/tracer/__init__.py CHANGED
@@ -1 +1,4 @@
1
- """Call-chain and logic tracing package for codeui."""
1
+ """Execution tracer and path analyzer for codeui."""
2
+ from codeui.tracer.tracer import Tracer
3
+
4
+ __all__ = ["Tracer"]
codeui/tracer/tracer.py CHANGED
@@ -1,147 +1,138 @@
1
- """Logic and call-chain tracer across files and languages."""
2
- import fnmatch
3
- from collections import deque
4
- from pathlib import Path
5
- from typing import Generator, Iterable, List, Set, Tuple
1
+ """Logic, dependency, and call-graph path tracing."""
2
+ from typing import Dict, Iterable, List, Set
6
3
  from codeui.core.graph import Graph
7
- from codeui.core.ir import Edge, EdgeKind, Symbol, SymbolKind
8
- from codeui.errors import SymbolNotFoundError
4
+ from codeui.core.ir import Edge, EdgeKind, LayerKind, Location, Symbol, SymbolKind, Visibility
9
5
 
10
6
  class Tracer:
11
- """Graph logic tracer yielding call chains, data flow, and entry points.
7
+ """Traces forward and backward execution paths and symbol call chains.
12
8
  Example:
13
- >>> g = Graph()
14
- >>> tracer = Tracer(g)
15
- >>> list(tracer.entry_points())
9
+ >>> t = Tracer(Graph())
10
+ >>> list(t.forward("missing"))
16
11
  []
17
12
  """
13
+ TRACER_EDGE_KINDS = (
14
+ EdgeKind.CALLS,
15
+ EdgeKind.READS,
16
+ EdgeKind.WRITES,
17
+ EdgeKind.REFERENCES,
18
+ EdgeKind.INSTANTIATES,
19
+ EdgeKind.INHERITS,
20
+ EdgeKind.IMPLEMENTS,
21
+ EdgeKind.OVERRIDES,
22
+ EdgeKind.DEPENDS_ON,
23
+ )
24
+
18
25
  def __init__(self, graph: Graph) -> None:
19
26
  self.graph = graph
20
27
 
21
- def forward(self, symbol_id: str, depth: int | None = None) -> Generator[Symbol, None, None]:
22
- """Follow symbol usage forward (who uses or calls this symbol?).
28
+ def forward(self, symbol_id: str, depth: int = 5) -> Iterable[Symbol]:
29
+ """Trace forward callers or dependents targeting the specified symbol.
23
30
  Example:
24
- >>> g = Graph()
25
- >>> tracer = Tracer(g)
26
- >>> list(tracer.forward("nonexistent", 1))
31
+ >>> t = Tracer(Graph())
32
+ >>> list(t.forward("s"))
27
33
  []
28
34
  """
29
- if not self.graph.has_symbol(symbol_id):
30
- return
31
35
  visited: Set[str] = {symbol_id}
32
- queue: deque[Tuple[str, int]] = deque([(symbol_id, 0)])
33
- while queue:
34
- curr_id, curr_depth = queue.popleft()
35
- if depth is not None and curr_depth >= depth:
36
- continue
37
- incoming = self.graph.get_incoming_edges(curr_id)
38
- for edge in incoming:
39
- src_id = edge.source_id
40
- if src_id not in visited and self.graph.has_symbol(src_id):
41
- visited.add(src_id)
42
- queue.append((src_id, curr_depth + 1))
43
- yield self.graph.get_symbol(src_id)
36
+ queue: List[str] = [symbol_id]
37
+ current_depth = 0
38
+
39
+ while queue and current_depth < depth:
40
+ next_queue: List[str] = []
41
+ for curr in queue:
42
+ for edge in self.graph.get_incoming_edges(curr):
43
+ if edge.kind in self.TRACER_EDGE_KINDS:
44
+ src = edge.source_id
45
+ if src not in visited:
46
+ visited.add(src)
47
+ if self.graph.has_symbol(src):
48
+ sym = self.graph.get_symbol(src)
49
+ yield sym
50
+ next_queue.append(src)
51
+ queue = next_queue
52
+ current_depth += 1
44
53
 
45
- def backward(self, symbol_id: str, depth: int | None = None) -> Generator[Symbol, None, None]:
46
- """Follow symbol dependencies backward (what does this symbol depend on?).
54
+ def backward(self, symbol_id: str, depth: int = 5) -> Iterable[Symbol]:
55
+ """Trace backward downstream dependencies and callees invoked by symbol.
47
56
  Example:
48
- >>> g = Graph()
49
- >>> tracer = Tracer(g)
50
- >>> list(tracer.backward("nonexistent", 1))
57
+ >>> t = Tracer(Graph())
58
+ >>> list(t.backward("s"))
51
59
  []
52
60
  """
53
- if not self.graph.has_symbol(symbol_id):
54
- return
55
61
  visited: Set[str] = {symbol_id}
56
- queue: deque[Tuple[str, int]] = deque([(symbol_id, 0)])
57
- while queue:
58
- curr_id, curr_depth = queue.popleft()
59
- if depth is not None and curr_depth >= depth:
60
- continue
61
- outgoing = self.graph.get_outgoing_edges(curr_id)
62
- for edge in outgoing:
63
- tgt_id = edge.target_id
64
- if tgt_id not in visited and self.graph.has_symbol(tgt_id):
65
- visited.add(tgt_id)
66
- queue.append((tgt_id, curr_depth + 1))
67
- yield self.graph.get_symbol(tgt_id)
62
+ queue: List[str] = [symbol_id]
63
+ current_depth = 0
68
64
 
69
- def paths(self, from_id: str, to_id: str, max_paths: int = 5) -> Generator[List[Symbol], None, None]:
70
- """Find shortest call or reference paths between two symbols.
71
- Example:
72
- >>> g = Graph()
73
- >>> tracer = Tracer(g)
74
- >>> list(tracer.paths("a", "b", 5))
75
- []
76
- """
77
- if not self.graph.has_symbol(from_id) or not self.graph.has_symbol(to_id):
78
- return
79
- paths_found = 0
80
- queue: deque[List[str]] = deque([[from_id]])
81
- visited: Set[Tuple[str, ...]] = set()
82
- while queue and paths_found < max_paths:
83
- path = queue.popleft()
84
- last_node = path[-1]
85
- if last_node == to_id:
86
- paths_found += 1
87
- yield [self.graph.get_symbol(nid) for nid in path]
88
- continue
89
- for edge in self.graph.get_outgoing_edges(last_node):
90
- nxt = edge.target_id
91
- if nxt not in path and self.graph.has_symbol(nxt):
92
- new_path = path + [nxt]
93
- path_tuple = tuple(new_path)
94
- if path_tuple not in visited:
95
- visited.add(path_tuple)
96
- queue.append(new_path)
65
+ while queue and current_depth < depth:
66
+ next_queue: List[str] = []
67
+ for curr in queue:
68
+ for edge in self.graph.get_outgoing_edges(curr):
69
+ if edge.kind in self.TRACER_EDGE_KINDS:
70
+ tgt = edge.target_id
71
+ if tgt not in visited:
72
+ visited.add(tgt)
73
+ if self.graph.has_symbol(tgt):
74
+ sym = self.graph.get_symbol(tgt)
75
+ yield sym
76
+ next_queue.append(tgt)
77
+ queue = next_queue
78
+ current_depth += 1
97
79
 
98
- def data_flow(self, symbol_id: str) -> Generator[Tuple[Symbol, str], None, None]:
99
- """Trace definition-use data flow chains for a variable or parameter.
80
+ def entry_points(self) -> Iterable[Symbol]:
81
+ """Yield symbols that act as project entry points based on file classification or explicit entries.
100
82
  Example:
101
- >>> g = Graph()
102
- >>> tracer = Tracer(g)
103
- >>> list(tracer.data_flow("s1"))
83
+ >>> t = Tracer(Graph())
84
+ >>> list(t.entry_points())
104
85
  []
105
86
  """
106
- if not self.graph.has_symbol(symbol_id):
87
+ explicit = getattr(self.graph, "_entry_points", [])
88
+ if explicit:
89
+ clean_entries = [e.replace("\\", "/").strip().lstrip("./") for e in explicit]
90
+ for s in self.graph.get_all_symbols():
91
+ if s.location and s.location.file_id:
92
+ clean_f = s.location.file_id.replace("\\", "/").strip().lstrip("./")
93
+ if clean_f in clean_entries:
94
+ yield s
107
95
  return
108
- for edge in self.graph.get_outgoing_edges(symbol_id):
109
- if edge.kind in (EdgeKind.READS, EdgeKind.WRITES, EdgeKind.RETURNS):
110
- if self.graph.has_symbol(edge.target_id):
111
- yield (self.graph.get_symbol(edge.target_id), str(edge.kind))
112
96
 
113
- def entry_points(self) -> Generator[Symbol, None, None]:
114
- """Discover project entry point symbols across languages with user entry precedence.
97
+ for s in self.graph.get_all_symbols():
98
+ if s.location and s.location.file_id:
99
+ cls = self.graph.get_file_classification(s.location.file_id)
100
+ if cls.layer == LayerKind.ENTRY:
101
+ yield s
102
+
103
+ def find_path(self, source_id: str, target_id: str, max_depth: int = 15) -> List[str]:
104
+ """Find the shortest directed dependency or call path between two nodes.
115
105
  Example:
116
- >>> from codeui.core.ir import Location, Symbol, SymbolKind, Visibility
117
106
  >>> g = Graph()
118
- >>> g.set_entry_points(["custom_script.py"])
119
- >>> loc = Location("custom_script.py", 1, 0, 2, 0)
120
- >>> g.add_symbol(Symbol("custom_script.py::run", "run", "custom_script.run", SymbolKind.FUNCTION, "python", loc, None, "def run():", Visibility.PUBLIC, (), "h"))
121
- >>> tracer = Tracer(g)
122
- >>> [s.name for s in tracer.entry_points()]
123
- ['run']
107
+ >>> loc = Location("a.py", 1, 0, 1, 0)
108
+ >>> g.add_symbol(Symbol("s1", "f1", "a.f1", SymbolKind.FUNCTION, "py", loc, None, None, Visibility.PUBLIC, (), "h1"))
109
+ >>> g.add_symbol(Symbol("s2", "f2", "a.f2", SymbolKind.FUNCTION, "py", loc, None, None, Visibility.PUBLIC, (), "h2"))
110
+ >>> g.add_edge(Edge("s1", "s2", EdgeKind.CALLS, 1.0, 1.0, loc))
111
+ >>> t = Tracer(g)
112
+ >>> t.find_path("s1", "s2")
113
+ ['s1', 's2']
124
114
  """
125
- if getattr(self.graph, "_entry_points", None):
126
- user_entries = [str(ep).replace("\\", "/").lstrip("./") for ep in self.graph._entry_points]
127
- yielded_ids = set()
128
- for sym in self.graph.get_all_symbols():
129
- f_id = (sym.location.file_id if sym.location else sym.id).replace("\\", "/").lstrip("./")
130
- is_match = False
131
- for ep in user_entries:
132
- ep_sub = ep.split("/", 1)[1] if "/" in ep else ep
133
- if f_id == ep or f_id == ep_sub or (any(c in ep for c in "*?[") and fnmatch.fnmatch(f_id, ep)):
134
- is_match = True
135
- break
136
- if is_match and sym.id not in yielded_ids:
137
- yielded_ids.add(sym.id)
138
- yield sym
139
- if yielded_ids:
140
- return
115
+ if not source_id or not target_id:
116
+ return []
117
+ if source_id == target_id:
118
+ return [source_id]
119
+
120
+ queue: List[List[str]] = [[source_id]]
121
+ visited: Set[str] = {source_id}
122
+
123
+ while queue:
124
+ path = queue.pop(0)
125
+ if len(path) > max_depth:
126
+ continue
127
+ curr = path[-1]
128
+ for edge in self.graph.get_outgoing_edges(curr):
129
+ if edge.kind == EdgeKind.CONTAINS:
130
+ continue
131
+ nxt = edge.target_id
132
+ if nxt == target_id:
133
+ return path + [nxt]
134
+ if nxt not in visited:
135
+ visited.add(nxt)
136
+ queue.append(path + [nxt])
141
137
 
142
- entry_names = {"main", "index", "App", "app", "run", "handler", "cli", "start", "init", "__main__"}
143
- for sym in self.graph.get_all_symbols():
144
- if sym.kind in (SymbolKind.FUNCTION, SymbolKind.FILE) and not sym.name.startswith("test_") and (
145
- sym.name in entry_names or any(sym.name.startswith(p) for p in ("start_", "run_", "main_")) or sym.id.endswith("main.py") or sym.id.endswith("main.go") or sym.id.endswith("main.rs") or sym.id.endswith("index.ts") or sym.id.endswith("index.js")
146
- ):
147
- yield sym
138
+ return []