aja-codeintel 0.1.0__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 (68) hide show
  1. aja_codeintel-0.1.0.dist-info/METADATA +436 -0
  2. aja_codeintel-0.1.0.dist-info/RECORD +68 -0
  3. aja_codeintel-0.1.0.dist-info/WHEEL +5 -0
  4. aja_codeintel-0.1.0.dist-info/entry_points.txt +3 -0
  5. aja_codeintel-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. aja_codeintel-0.1.0.dist-info/top_level.txt +1 -0
  7. codeintel_cli/__init__.py +1 -0
  8. codeintel_cli/__main__.py +4 -0
  9. codeintel_cli/cli.py +41 -0
  10. codeintel_cli/commands/__init__.py +1 -0
  11. codeintel_cli/commands/graph/__init__.py +18 -0
  12. codeintel_cli/commands/graph/deps_cmd.py +35 -0
  13. codeintel_cli/commands/graph/related_cmd.py +121 -0
  14. codeintel_cli/commands/graph/relsymbols_cmd.py +347 -0
  15. codeintel_cli/commands/graph/reverse_related_cmd.py +54 -0
  16. codeintel_cli/commands/nav/__init__.py +12 -0
  17. codeintel_cli/commands/nav/copy_cmd.py +101 -0
  18. codeintel_cli/commands/nav/open_cmd.py +18 -0
  19. codeintel_cli/commands/nav/where_cmd.py +21 -0
  20. codeintel_cli/commands/project/__init__.py +26 -0
  21. codeintel_cli/commands/project/context_cmd.py +326 -0
  22. codeintel_cli/commands/project/folder_cmd.py +51 -0
  23. codeintel_cli/commands/project/imports_cmd.py +90 -0
  24. codeintel_cli/commands/project/models_cmd.py +98 -0
  25. codeintel_cli/commands/project/modeltree_cmd.py +476 -0
  26. codeintel_cli/commands/project/new.py +0 -0
  27. codeintel_cli/commands/project/resolve_cmd.py +29 -0
  28. codeintel_cli/commands/project/scan_cmd.py +51 -0
  29. codeintel_cli/commands/project/servicemap_cmd.py +180 -0
  30. codeintel_cli/commands/project/tree_cmd.py +203 -0
  31. codeintel_cli/commands/project/version_cmd.py +14 -0
  32. codeintel_cli/context/java_context.py +180 -0
  33. codeintel_cli/context/java_rel.py +299 -0
  34. codeintel_cli/context/java_service.py +291 -0
  35. codeintel_cli/context/python_context.py +91 -0
  36. codeintel_cli/context/python_rel.py +251 -0
  37. codeintel_cli/context/python_service.py +205 -0
  38. codeintel_cli/core/fuzzy.py +72 -0
  39. codeintel_cli/core/opener.py +37 -0
  40. codeintel_cli/core/project.py +34 -0
  41. codeintel_cli/core/resolve_folder.py +68 -0
  42. codeintel_cli/core/resolve_model_target.py +92 -0
  43. codeintel_cli/core/resolve_target.py +53 -0
  44. codeintel_cli/core/timing.py +13 -0
  45. codeintel_cli/core/where.py +77 -0
  46. codeintel_cli/db/__init__.py +7 -0
  47. codeintel_cli/db/cache.py +224 -0
  48. codeintel_cli/db/operations.py +333 -0
  49. codeintel_cli/db/schema.py +102 -0
  50. codeintel_cli/errors.py +78 -0
  51. codeintel_cli/graph/__init__.py +1 -0
  52. codeintel_cli/graph/builder.py +149 -0
  53. codeintel_cli/graph/query.py +30 -0
  54. codeintel_cli/graph/traverse.py +49 -0
  55. codeintel_cli/lang/__init__.py +0 -0
  56. codeintel_cli/lang/java/__init__.py +0 -0
  57. codeintel_cli/lang/java/engine.py +18 -0
  58. codeintel_cli/lang/java/models.py +105 -0
  59. codeintel_cli/lang/java/resolve.py +49 -0
  60. codeintel_cli/lang/python/__init__.py +0 -0
  61. codeintel_cli/lang/python/engine.py +8 -0
  62. codeintel_cli/lang/python/models.py +86 -0
  63. codeintel_cli/lang/router.py +24 -0
  64. codeintel_cli/parser/imports.py +26 -0
  65. codeintel_cli/parser/resolve.py +49 -0
  66. codeintel_cli/parser/symbols.py +92 -0
  67. codeintel_cli/scanner/__init__.py +0 -0
  68. codeintel_cli/scanner/scanner.py +41 -0
@@ -0,0 +1,326 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import deque
4
+ from pathlib import Path
5
+ import re
6
+ import typer
7
+
8
+ from ...core.resolve_target import resolve_target_file
9
+ from ...graph.builder import build_graph_with_counts, get_hub_files_by_ratio
10
+ from ...lang.router import extract_models_for_file
11
+ from ...lang.java.resolve import resolve_java_import_to_file
12
+ from ...parser.imports import extract_imports_from_file
13
+ from ...parser.resolve import resolve_import_to_file
14
+ from ...parser.symbols import extract_classes_from_file, extract_funcs_from_file
15
+ from ...db.cache import CacheManager
16
+
17
+
18
+ TYPE_RE = re.compile(r"\b(class|interface|enum|record)\s+([A-Za-z_][A-Za-z0-9_]*)\b")
19
+
20
+
21
+ def _rel(p: Path, root: Path) -> str:
22
+ try:
23
+ return p.resolve().relative_to(root.resolve()).as_posix()
24
+ except Exception:
25
+ return p.resolve().as_posix()
26
+
27
+
28
+ def _print_block(title: str, lines: list[str]) -> None:
29
+ if not lines:
30
+ return
31
+ typer.echo(title)
32
+ for x in lines:
33
+ typer.echo(f" {x}")
34
+ typer.echo("")
35
+
36
+
37
+ def _layered_related(
38
+ graph: dict[Path, set[Path]],
39
+ start: Path,
40
+ depth: int,
41
+ include_reverse: bool,
42
+ hubs: set[Path],
43
+ ) -> list[list[Path]]:
44
+ adj: dict[Path, set[Path]] = {k: set(v) for k, v in graph.items()}
45
+
46
+ if include_reverse:
47
+ rev: dict[Path, set[Path]] = {}
48
+ for src, deps in adj.items():
49
+ for dst in deps:
50
+ rev.setdefault(dst, set()).add(src)
51
+ for node, incoming in rev.items():
52
+ adj.setdefault(node, set()).update(incoming)
53
+
54
+ visited: set[Path] = {start}
55
+ q: deque[tuple[Path, int]] = deque([(start, 0)])
56
+ by_depth: dict[int, list[Path]] = {}
57
+
58
+ while q:
59
+ node, d = q.popleft()
60
+ if d == depth:
61
+ continue
62
+ for nxt in adj.get(node, set()):
63
+ if nxt in visited:
64
+ continue
65
+ if nxt in hubs:
66
+ continue
67
+ visited.add(nxt)
68
+ nd = d + 1
69
+ by_depth.setdefault(nd, []).append(nxt)
70
+ q.append((nxt, nd))
71
+
72
+ layers: list[list[Path]] = []
73
+ for d in range(1, depth + 1):
74
+ layers.append(sorted(by_depth.get(d, [])))
75
+ return layers
76
+
77
+
78
+ def _is_domain_model_path(p: Path) -> bool:
79
+ parts = {x.lower() for x in p.parts}
80
+ return bool(parts & {"model", "models", "entity", "entities"})
81
+
82
+
83
+ def _models_lines(scope: list[Path], root: Path) -> list[str]:
84
+ out: list[str] = []
85
+ for f in scope:
86
+ if not _is_domain_model_path(f):
87
+ continue
88
+ defs = extract_models_for_file(f, root)
89
+ if not defs:
90
+ continue
91
+ out.append(f"• {_rel(f, root)}")
92
+ for m in defs:
93
+ out.append(f" {m.name}")
94
+ for field in getattr(m, "fields", []):
95
+ out.append(f" {field.name}: {field.type}")
96
+ out.append("")
97
+ while out and out[-1] == "":
98
+ out.pop()
99
+ return out
100
+
101
+
102
+ def _py_target_imports(target: Path, root: Path) -> tuple[list[str], list[str]]:
103
+ related: set[str] = set()
104
+ non_related: set[str] = set()
105
+
106
+ for mod, level in extract_imports_from_file(target):
107
+ mod = mod or ""
108
+ s = ("." * level + mod) if level else mod
109
+ if not s:
110
+ continue
111
+ resolved = resolve_import_to_file(mod, level, target, root)
112
+ if resolved is None:
113
+ non_related.add(s)
114
+ else:
115
+ related.add(s)
116
+
117
+ return sorted(related), sorted(non_related)
118
+
119
+
120
+ def _py_related_imports(files: list[Path]) -> list[str]:
121
+ out: set[str] = set()
122
+ for f in files:
123
+ if f.suffix.lower() != ".py":
124
+ continue
125
+ for mod, level in extract_imports_from_file(f):
126
+ mod = mod or ""
127
+ s = ("." * level + mod) if level else mod
128
+ if s:
129
+ out.add(s)
130
+ return sorted(out)
131
+
132
+
133
+ def _py_types(path: Path) -> list[str]:
134
+ out: list[str] = []
135
+ for c in extract_classes_from_file(path):
136
+ if not c.name.startswith("_"):
137
+ out.append(f"{c.name} (class)")
138
+ for fn in extract_funcs_from_file(path):
139
+ if fn.name.startswith("_") or fn.name.startswith("register_"):
140
+ continue
141
+ out.append(f"{fn.name} (func)")
142
+ return out
143
+
144
+
145
+ def _java_read(path: Path) -> str:
146
+ try:
147
+ return path.read_text(encoding="utf-8", errors="ignore")
148
+ except Exception:
149
+ return ""
150
+
151
+
152
+ def _java_imports(path: Path) -> list[str]:
153
+ text = _java_read(path)
154
+ out: list[str] = []
155
+ for line in text.splitlines():
156
+ s = line.strip()
157
+ if not s.startswith("import "):
158
+ continue
159
+ if s.startswith("import static "):
160
+ continue
161
+ s = s.removeprefix("import ").rstrip(";").strip()
162
+ if not s or s.endswith(".*"):
163
+ continue
164
+ out.append(s)
165
+ return out
166
+
167
+
168
+ def _java_split_imports(imports: list[str], root: Path) -> tuple[list[str], list[str]]:
169
+ proj: set[str] = set()
170
+ ext: set[str] = set()
171
+ for imp in imports:
172
+ if resolve_java_import_to_file(imp, root) is None:
173
+ ext.add(imp)
174
+ else:
175
+ proj.add(imp)
176
+ return sorted(proj), sorted(ext)
177
+
178
+
179
+ def _java_target_imports(target: Path, root: Path) -> tuple[list[str], list[str]]:
180
+ return _java_split_imports(_java_imports(target), root)
181
+
182
+
183
+ def _java_related_imports(files: list[Path]) -> list[str]:
184
+ out: set[str] = set()
185
+ for f in files:
186
+ if f.suffix.lower() != ".java":
187
+ continue
188
+ for imp in _java_imports(f):
189
+ out.add(imp)
190
+ return sorted(out)
191
+
192
+
193
+ def _java_types(path: Path) -> list[str]:
194
+ text = _java_read(path)
195
+ if not text:
196
+ return []
197
+ text = re.sub(r"/\*.*?\*/", "", text, flags=re.S)
198
+ text = re.sub(r"//.*?$", "", text, flags=re.M)
199
+ m = TYPE_RE.search(text)
200
+ if not m:
201
+ return []
202
+ kind, name = m.group(1), m.group(2)
203
+ return [f"{name} ({kind})"]
204
+
205
+
206
+ def _types_section(title: str, files: list[Path], root: Path, lang: str) -> None:
207
+ lines: list[str] = []
208
+ for f in files:
209
+ ts = _py_types(f) if lang == "python" else _java_types(f)
210
+ if not ts:
211
+ continue
212
+ lines.append(f"• {_rel(f, root)}")
213
+ for t in ts:
214
+ lines.append(f" {t}")
215
+ if lines:
216
+ typer.echo(title)
217
+ for x in lines:
218
+ typer.echo(x)
219
+ typer.echo("")
220
+
221
+
222
+ def register_context(app: typer.Typer) -> None:
223
+ @app.command(help="Context bundle (layered). Default depth=1.")
224
+ def context(
225
+ new_file: str = typer.Argument(...),
226
+ root: str = typer.Option(".", "--root"),
227
+ depth: int = typer.Option(1, "--depth", "-d"),
228
+ forward_only: bool = typer.Option(False, "--forward-only"),
229
+ include_hubs: bool = typer.Option(False, "--include-hubs"),
230
+ use_sqlite_cache: bool = typer.Option(True, "--use-sqlite-cache/--no-sqlite-cache"),
231
+ ):
232
+ target, root_path = resolve_target_file(new_file, root=root)
233
+
234
+ if use_sqlite_cache:
235
+ with CacheManager(root_path) as cache:
236
+ if cache.needs_rescan():
237
+ cache.scan_project(verbose=False)
238
+ files = cache.get_cached_files()
239
+ else:
240
+ files = []
241
+ for p in root_path.rglob("*"):
242
+ if p.is_file() and p.suffix.lower() in {".py", ".java"}:
243
+ files.append(p.resolve())
244
+ files = sorted(set(files))
245
+
246
+ graph, dependents_count = build_graph_with_counts(
247
+ files,
248
+ root_path,
249
+ use_sqlite_cache=use_sqlite_cache,
250
+ )
251
+
252
+ hubs: set[Path] = set()
253
+ if not include_hubs:
254
+ hubs = get_hub_files_by_ratio(dependents_count, len(files), 0.5)
255
+
256
+ layers = _layered_related(
257
+ graph=graph,
258
+ start=target.resolve(),
259
+ depth=depth,
260
+ include_reverse=not forward_only,
261
+ hubs=hubs,
262
+ )
263
+
264
+ same_folder = sorted(
265
+ f.resolve()
266
+ for f in files
267
+ if f.parent.resolve() == target.parent.resolve()
268
+ and f.resolve() != target.resolve()
269
+ and f.name != "__init__.py"
270
+ )
271
+
272
+ banned: set[Path] = {target.resolve(), *[p.resolve() for p in same_folder]}
273
+ new_layers: list[list[Path]] = []
274
+ for layer in layers:
275
+ clean: list[Path] = []
276
+ for p in layer:
277
+ pr = p.resolve()
278
+ if pr in banned:
279
+ continue
280
+ clean.append(pr)
281
+ banned.add(pr)
282
+ new_layers.append(clean)
283
+ layers = new_layers
284
+
285
+ scope_all: list[Path] = [*same_folder]
286
+ for layer in layers:
287
+ scope_all.extend([p.resolve() for p in layer])
288
+
289
+ lang = "java" if target.suffix.lower() == ".java" else "python"
290
+
291
+ typer.echo("")
292
+ typer.echo("CONTEXT")
293
+ typer.echo(f"Target: {_rel(target, root_path)}")
294
+ typer.echo(f"Depth: {depth} Mode: {'forward-only' if forward_only else 'forward+reverse'}")
295
+ typer.echo("")
296
+
297
+ model_lines = _models_lines(scope_all, root_path)
298
+ if model_lines:
299
+ typer.echo("MODELS")
300
+ for x in model_lines:
301
+ typer.echo(x)
302
+ typer.echo("")
303
+
304
+ if lang == "java":
305
+ t_proj, t_ext = _java_target_imports(target, root_path)
306
+ _print_block("TARGET IMPORTS", t_proj)
307
+ _print_block("TARGET NON-RELATED IMPORTS", t_ext)
308
+ _types_section("TARGET TYPES", [target], root_path, lang)
309
+ else:
310
+ t_rel, t_non = _py_target_imports(target, root_path)
311
+ _print_block("TARGET IMPORTS", t_rel)
312
+ _print_block("TARGET NON-RELATED IMPORTS", t_non)
313
+
314
+ _types_section("SAME FOLDER TYPES", same_folder, root_path, lang)
315
+
316
+ for i, layer in enumerate(layers, 1):
317
+ if not layer:
318
+ continue
319
+ _types_section(f"RELATED LEVEL {i} TYPES", layer, root_path, lang)
320
+ if lang == "java":
321
+ all_imps = _java_related_imports(layer)
322
+ proj, ext = _java_split_imports(all_imps, root_path)
323
+ _print_block(f"RELATED LEVEL {i} PROJECT IMPORTS", proj)
324
+ _print_block(f"RELATED LEVEL {i} EXTERNAL IMPORTS", ext)
325
+ else:
326
+ _print_block(f"RELATED LEVEL {i} IMPORTS", _py_related_imports(layer))
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import typer
5
+
6
+ from ...core.project import is_skipped_path
7
+ from ...core.resolve_folder import resolve_target_folder
8
+ from ...scanner.scanner import find_source_files
9
+
10
+
11
+ def register_folder(app: typer.Typer) -> None:
12
+ @app.command(help="List source files under a folder (recursive).")
13
+ def folder(
14
+ folder: str = typer.Argument(..., help="Folder path or name (supports fuzzy)"),
15
+ recursive: bool = typer.Option(True, "--recursive/--no-recursive"),
16
+ relative: bool = typer.Option(True, "--relative/--absolute"),
17
+ lang: str = typer.Option("all", "--lang", help="all | python | java"),
18
+ ):
19
+ folder_path, root = resolve_target_folder(folder)
20
+
21
+ lang = lang.lower().strip()
22
+ if lang not in {"all", "python", "java"}:
23
+ raise typer.BadParameter("Invalid --lang. Use: all | python | java")
24
+
25
+ exts = (".py", ".java") if lang == "all" else (".py",) if lang == "python" else (".java",)
26
+
27
+ if recursive:
28
+ files = find_source_files(folder_path, exts)
29
+ else:
30
+ files = []
31
+ for ext in exts:
32
+ for p in folder_path.glob(f"*{ext}"):
33
+ if not p.is_file():
34
+ continue
35
+ if is_skipped_path(p):
36
+ continue
37
+ files.append(p.resolve())
38
+ files = sorted(set(files))
39
+
40
+ if not files:
41
+ typer.echo("(No matching files found.)")
42
+ raise typer.Exit(0)
43
+
44
+ for f in files:
45
+ if relative:
46
+ try:
47
+ typer.echo(str(f.relative_to(root)))
48
+ except Exception:
49
+ typer.echo(str(f))
50
+ else:
51
+ typer.echo(str(f))
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import typer
5
+
6
+ from ...errors import InvalidPathError
7
+ from ...parser.imports import extract_imports_from_file
8
+ from ...core.resolve_target import resolve_target_file
9
+
10
+
11
+ def _java_imports(path: Path) -> list[str]:
12
+ try:
13
+ text = path.read_text(encoding="utf-8", errors="ignore")
14
+ except Exception:
15
+ return []
16
+
17
+ out: list[str] = []
18
+ for line in text.splitlines():
19
+ s = line.strip()
20
+ if not s.startswith("import "):
21
+ continue
22
+ if s.startswith("import static "):
23
+ continue
24
+ s = s.removeprefix("import ").rstrip(";").strip()
25
+ if not s or s.endswith(".*"):
26
+ continue
27
+ out.append(s)
28
+ return out
29
+
30
+
31
+ def register_imports(app: typer.Typer) -> None:
32
+ @app.command(help="Print local imports found in a file (python/java).")
33
+ def imports(
34
+ file: str = typer.Argument(..., help="File or fuzzy name (.py or .java)."),
35
+ java_stdlib: bool = typer.Option(
36
+ False,
37
+ "--java-stdlib",
38
+ help="Include Java/JDK imports (java.*, javax.*, jakarta.*, etc.).",
39
+ ),
40
+ ):
41
+ q = (file or "").strip()
42
+ if not q:
43
+ raise InvalidPathError(message="File not found", path=Path(file))
44
+
45
+ target, _ = resolve_target_file(q)
46
+
47
+ if not target.exists() or not target.is_file():
48
+ raise InvalidPathError(message="File not found", path=target)
49
+
50
+ ext = target.suffix.lower()
51
+
52
+ if ext == ".py":
53
+ mods = extract_imports_from_file(target)
54
+ rels = [(m, lvl) for (m, lvl) in mods if lvl > 0]
55
+
56
+ if not rels:
57
+ typer.echo("No local relative imports found.")
58
+ raise typer.Exit(0)
59
+
60
+ for module, level in rels:
61
+ dots = "." * level
62
+ typer.echo(f"{dots}{module}" if module else dots)
63
+ return
64
+
65
+ if ext == ".java":
66
+ imps = _java_imports(target)
67
+
68
+ if not java_stdlib:
69
+ imps = [
70
+ x
71
+ for x in imps
72
+ if not (
73
+ x.startswith("java.")
74
+ or x.startswith("javax.")
75
+ or x.startswith("jakarta.")
76
+ or x.startswith("org.junit.")
77
+ or x.startswith("org.springframework.")
78
+ )
79
+ ]
80
+
81
+ if not imps:
82
+ typer.echo("No Java imports found.")
83
+ raise typer.Exit(0)
84
+
85
+ for x in imps:
86
+ typer.echo(x)
87
+ return
88
+
89
+ typer.echo("Unsupported file type. Use .py or .java.")
90
+ raise typer.Exit(2)
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import typer
5
+
6
+ from ...core.project import find_project_root
7
+ from ...scanner.scanner import find_all_supported_files
8
+
9
+
10
+ def _import_extractors():
11
+ py = None
12
+ jv = None
13
+
14
+ try:
15
+ from ...lang.python.models import extract_python_models as _py # type: ignore
16
+
17
+ py = _py
18
+ except Exception:
19
+ pass
20
+
21
+ try:
22
+ from ...context.python_context import extract_python_models as _py # type: ignore
23
+
24
+ py = _py
25
+ except Exception:
26
+ pass
27
+
28
+ try:
29
+ from ...lang.java.models import extract_java_models as _jv # type: ignore
30
+
31
+ jv = _jv
32
+ except Exception:
33
+ pass
34
+
35
+ try:
36
+ from ...context.java_context import extract_java_models as _jv # type: ignore
37
+
38
+ jv = _jv
39
+ except Exception:
40
+ pass
41
+
42
+ return py, jv
43
+
44
+
45
+ def _is_model_path(rel: Path) -> bool:
46
+ keys = {"model", "models", "entity", "entities", "domain", "domains"}
47
+ parts = [p.lower() for p in rel.parts]
48
+ if "src" in parts and "test" in parts:
49
+ return False
50
+ return any(part in keys for part in parts)
51
+
52
+
53
+ def register_models(app: typer.Typer) -> None:
54
+ @app.command(help="List domain models (from model/entity folders only).")
55
+ def models(
56
+ root: str = typer.Argument(".", help="Project root folder"),
57
+ ):
58
+ root_path = Path(root).resolve()
59
+ project_root = find_project_root(root_path)
60
+
61
+ extract_python_models, extract_java_models = _import_extractors()
62
+
63
+ files = find_all_supported_files(project_root)
64
+
65
+ candidates: list[Path] = []
66
+ pr = project_root.resolve()
67
+ for f in files:
68
+ fr = f.resolve()
69
+ try:
70
+ rel = fr.relative_to(pr)
71
+ except Exception:
72
+ continue
73
+ if _is_model_path(rel):
74
+ candidates.append(fr)
75
+
76
+ out: list[str] = []
77
+
78
+ for f in sorted(candidates, key=lambda x: str(x).lower()):
79
+ ext = f.suffix.lower()
80
+
81
+ if ext == ".py" and extract_python_models is not None:
82
+ defs = extract_python_models(f, project_root)
83
+ elif ext == ".java" and extract_java_models is not None:
84
+ defs = extract_java_models(f, project_root)
85
+ else:
86
+ continue
87
+
88
+ for d in defs:
89
+ out.append(f"{d.name} ({d.kind}) {d.file}")
90
+ for fld in d.fields:
91
+ out.append(f" - {fld.name}: {fld.type}")
92
+
93
+ if not out:
94
+ typer.echo("No domain models found.")
95
+ raise typer.Exit(0)
96
+
97
+ for line in out:
98
+ typer.echo(line)