aja-codeintel 0.1.6__tar.gz → 0.1.8__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 (108) hide show
  1. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/PKG-INFO +1 -1
  2. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/PKG-INFO +1 -1
  3. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/SOURCES.txt +13 -1
  4. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/__init__.py +36 -35
  5. aja_codeintel-0.1.8/codeintel_cli/commands/project/debug_cmd.py +51 -0
  6. aja_codeintel-0.1.8/codeintel_cli/commands/project/errors_cmd.py +222 -0
  7. aja_codeintel-0.1.8/codeintel_cli/commands/project/models_cmd.py +192 -0
  8. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/modeltree_cmd.py +213 -213
  9. aja_codeintel-0.1.8/codeintel_cli/commands/project/overview_cmd.py +689 -0
  10. aja_codeintel-0.1.8/codeintel_cli/commands/project/scan_cmd.py +95 -0
  11. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/servicemap_cmd.py +54 -16
  12. aja_codeintel-0.1.8/codeintel_cli/commands/project/types_cmd.py +134 -0
  13. aja_codeintel-0.1.8/codeintel_cli/context/java_service.py +471 -0
  14. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/resolve_model_target.py +133 -133
  15. aja_codeintel-0.1.8/codeintel_cli/endpoints/java_spring.py +472 -0
  16. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/frontend/server.py +37 -64
  17. aja_codeintel-0.1.8/codeintel_cli/lang/java/ast_engine.py +442 -0
  18. aja_codeintel-0.1.8/codeintel_cli/lang/java/auth.py +50 -0
  19. aja_codeintel-0.1.8/codeintel_cli/lang/java/base_path.py +79 -0
  20. aja_codeintel-0.1.8/codeintel_cli/lang/java/call_graph.py +25 -0
  21. aja_codeintel-0.1.8/codeintel_cli/lang/java/enums.py +54 -0
  22. aja_codeintel-0.1.8/codeintel_cli/lang/java/method_index.py +82 -0
  23. aja_codeintel-0.1.8/codeintel_cli/lang/java/models.py +114 -0
  24. aja_codeintel-0.1.8/codeintel_cli/lang/java/pagination.py +60 -0
  25. aja_codeintel-0.1.8/codeintel_cli/lang/java/types.py +294 -0
  26. aja_codeintel-0.1.8/codeintel_cli/lang/java/validation.py +78 -0
  27. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/scanner/scanner.py +49 -49
  28. aja_codeintel-0.1.8/codeintel_cli/terminal/__init__.py +0 -0
  29. aja_codeintel-0.1.8/codeintel_cli/terminal/error_parser.py +39 -0
  30. aja_codeintel-0.1.8/codeintel_cli/terminal/printer.py +46 -0
  31. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/pyproject.toml +1 -1
  32. aja_codeintel-0.1.6/codeintel_cli/commands/project/errors_cmd.py +0 -238
  33. aja_codeintel-0.1.6/codeintel_cli/commands/project/models_cmd.py +0 -151
  34. aja_codeintel-0.1.6/codeintel_cli/commands/project/overview_cmd.py +0 -252
  35. aja_codeintel-0.1.6/codeintel_cli/commands/project/scan_cmd.py +0 -46
  36. aja_codeintel-0.1.6/codeintel_cli/commands/project/types_cmd.py +0 -424
  37. aja_codeintel-0.1.6/codeintel_cli/context/java_service.py +0 -226
  38. aja_codeintel-0.1.6/codeintel_cli/endpoints/java_spring.py +0 -221
  39. aja_codeintel-0.1.6/codeintel_cli/lang/java/ast_engine.py +0 -98
  40. aja_codeintel-0.1.6/codeintel_cli/lang/java/models.py +0 -258
  41. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/LICENSE +0 -0
  42. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/README.md +0 -0
  43. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/dependency_links.txt +0 -0
  44. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/entry_points.txt +0 -0
  45. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/requires.txt +0 -0
  46. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/top_level.txt +0 -0
  47. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/__init__.py +0 -0
  48. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/__main__.py +0 -0
  49. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/cli.py +0 -0
  50. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/__init__.py +0 -0
  51. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/graph/__init__.py +0 -0
  52. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/graph/deps_cmd.py +0 -0
  53. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/graph/related_cmd.py +0 -0
  54. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/graph/relsymbols_cmd.py +0 -0
  55. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/graph/reverse_related_cmd.py +0 -0
  56. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/nav/__init__.py +0 -0
  57. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/nav/copy_cmd.py +0 -0
  58. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/nav/open_cmd.py +0 -0
  59. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/nav/where_cmd.py +0 -0
  60. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/context_cmd.py +0 -0
  61. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/endpoints_cmd.py +0 -0
  62. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/folder_cmd.py +0 -0
  63. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/frontend_cmd.py +0 -0
  64. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/imports_cmd.py +0 -0
  65. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/new.py +0 -0
  66. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/resolve_cmd.py +0 -0
  67. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/tree_cmd.py +0 -0
  68. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/version_cmd.py +0 -0
  69. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/context/java_context.py +0 -0
  70. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/context/java_rel.py +0 -0
  71. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/context/python_context.py +0 -0
  72. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/context/python_rel.py +0 -0
  73. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/context/python_service.py +0 -0
  74. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/fuzzy.py +0 -0
  75. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/opener.py +0 -0
  76. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/project.py +0 -0
  77. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/resolve_folder.py +0 -0
  78. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/resolve_target.py +0 -0
  79. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/timing.py +0 -0
  80. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/where.py +0 -0
  81. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/db/__init__.py +0 -0
  82. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/db/cache.py +0 -0
  83. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/db/operations.py +0 -0
  84. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/db/schema.py +0 -0
  85. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/endpoints/__init__.py +0 -0
  86. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/endpoints/models.py +0 -0
  87. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/endpoints/openapi_spec.py +0 -0
  88. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/endpoints/python_web.py +0 -0
  89. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/endpoints/scan.py +0 -0
  90. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/errors.py +0 -0
  91. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/frontend/__init__.py +0 -0
  92. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/graph/__init__.py +0 -0
  93. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/graph/builder.py +0 -0
  94. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/graph/query.py +0 -0
  95. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/graph/traverse.py +0 -0
  96. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/__init__.py +0 -0
  97. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/java/__init__.py +0 -0
  98. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/java/engine.py +0 -0
  99. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/java/resolve.py +0 -0
  100. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/python/__init__.py +0 -0
  101. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/python/engine.py +0 -0
  102. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/python/models.py +0 -0
  103. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/router.py +0 -0
  104. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/parser/imports.py +0 -0
  105. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/parser/resolve.py +0 -0
  106. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/parser/symbols.py +0 -0
  107. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/scanner/__init__.py +0 -0
  108. {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aja-codeintel
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: CodeIntel CLI tool
5
5
  Author: Shiva Areti
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aja-codeintel
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: CodeIntel CLI tool
5
5
  Author: Shiva Areti
6
6
  License: MIT License
@@ -23,6 +23,7 @@ codeintel_cli/commands/nav/open_cmd.py
23
23
  codeintel_cli/commands/nav/where_cmd.py
24
24
  codeintel_cli/commands/project/__init__.py
25
25
  codeintel_cli/commands/project/context_cmd.py
26
+ codeintel_cli/commands/project/debug_cmd.py
26
27
  codeintel_cli/commands/project/endpoints_cmd.py
27
28
  codeintel_cli/commands/project/errors_cmd.py
28
29
  codeintel_cli/commands/project/folder_cmd.py
@@ -72,9 +73,17 @@ codeintel_cli/lang/__init__.py
72
73
  codeintel_cli/lang/router.py
73
74
  codeintel_cli/lang/java/__init__.py
74
75
  codeintel_cli/lang/java/ast_engine.py
76
+ codeintel_cli/lang/java/auth.py
77
+ codeintel_cli/lang/java/base_path.py
78
+ codeintel_cli/lang/java/call_graph.py
75
79
  codeintel_cli/lang/java/engine.py
80
+ codeintel_cli/lang/java/enums.py
81
+ codeintel_cli/lang/java/method_index.py
76
82
  codeintel_cli/lang/java/models.py
83
+ codeintel_cli/lang/java/pagination.py
77
84
  codeintel_cli/lang/java/resolve.py
85
+ codeintel_cli/lang/java/types.py
86
+ codeintel_cli/lang/java/validation.py
78
87
  codeintel_cli/lang/python/__init__.py
79
88
  codeintel_cli/lang/python/engine.py
80
89
  codeintel_cli/lang/python/models.py
@@ -82,4 +91,7 @@ codeintel_cli/parser/imports.py
82
91
  codeintel_cli/parser/resolve.py
83
92
  codeintel_cli/parser/symbols.py
84
93
  codeintel_cli/scanner/__init__.py
85
- codeintel_cli/scanner/scanner.py
94
+ codeintel_cli/scanner/scanner.py
95
+ codeintel_cli/terminal/__init__.py
96
+ codeintel_cli/terminal/error_parser.py
97
+ codeintel_cli/terminal/printer.py
@@ -1,37 +1,38 @@
1
- from __future__ import annotations
2
-
3
- import typer
4
-
5
-
6
- def register_project_commands(app: typer.Typer) -> None:
7
- from .overview_cmd import register_overview
8
- from .models_cmd import register_models
9
- from .endpoints_cmd import register_endpoints
10
- from .types_cmd import register_types_command
11
- from .scan_cmd import register_scan
12
- from .context_cmd import register_context
13
- from .tree_cmd import register_tree
14
- from .imports_cmd import register_imports
15
- from .folder_cmd import register_folder
16
- from .resolve_cmd import register_resolve
17
- from .servicemap_cmd import register_servicemap
18
- from .modeltree_cmd import register_modeltree
19
- from .version_cmd import register_version
1
+ from __future__ import annotations
2
+ import typer
3
+
4
+
5
+ def register_project_commands(app: typer.Typer) -> None:
6
+ from .overview_cmd import register_overview
7
+ from .models_cmd import register_models
8
+ from .endpoints_cmd import register_endpoints
9
+ from .types_cmd import register_types_command
10
+ from .scan_cmd import register_scan
11
+ from .context_cmd import register_context
12
+ from .tree_cmd import register_tree
13
+ from .imports_cmd import register_imports
14
+ from .folder_cmd import register_folder
15
+ from .resolve_cmd import register_resolve
16
+ from .servicemap_cmd import register_servicemap
17
+ from .modeltree_cmd import register_modeltree
18
+ from .version_cmd import register_version
20
19
  from .frontend_cmd import register_frontend
21
- from .errors_cmd import register_errors
22
-
23
- register_overview(app)
24
- register_models(app)
25
- register_endpoints(app)
26
- register_types_command(app)
27
- register_scan(app)
28
- register_context(app)
29
- register_tree(app)
30
- register_imports(app)
31
- register_folder(app)
32
- register_resolve(app)
33
- register_servicemap(app)
34
- register_modeltree(app)
35
- register_version(app)
20
+ from .errors_cmd import register_errors
21
+ from .debug_cmd import register_debug
22
+
23
+ register_overview(app)
24
+ register_models(app)
25
+ register_endpoints(app)
26
+ register_types_command(app)
27
+ register_scan(app)
28
+ register_context(app)
29
+ register_tree(app)
30
+ register_imports(app)
31
+ register_folder(app)
32
+ register_resolve(app)
33
+ register_servicemap(app)
34
+ register_modeltree(app)
35
+ register_version(app)
36
36
  register_frontend(app)
37
- register_errors(app)
37
+ register_errors(app)
38
+ register_debug(app)
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+ import time
3
+ from pathlib import Path
4
+ import typer
5
+ from ...core.project import find_project_root
6
+
7
+
8
+ def register_debug(app: typer.Typer) -> None:
9
+ @app.command("debug", help="Show exact error code blocks + call graph (callers/callees).")
10
+ def debug(
11
+ path: str = typer.Argument(".", help="Project root"),
12
+ skip_compile: bool = typer.Option(False, "--skip-compile"),
13
+ verbose: bool = typer.Option(False, "--verbose", "-v"),
14
+ ) -> None:
15
+ t0 = time.perf_counter()
16
+ project_root = find_project_root(Path(path).resolve())
17
+
18
+ from ...commands.project.errors_cmd import _run_mvn_compile
19
+ from ...lang.java.method_index import load_index, build_index, save_index, find_method_at_line
20
+ from ...lang.java.call_graph import build_call_graph, get_related
21
+ from ...terminal.printer import print_block
22
+ from ...terminal.error_parser import JavaError
23
+
24
+ compile_errors = []
25
+ if not skip_compile:
26
+ typer.echo("Running mvn compile...")
27
+ compile_errors, _, _ = _run_mvn_compile(project_root)
28
+
29
+ if not compile_errors:
30
+ typer.echo(typer.style("? No compile errors found.", fg=typer.colors.GREEN))
31
+ raise typer.Exit()
32
+
33
+ typer.echo(f"Found {len(compile_errors)} error(s). Building index + call graph...")
34
+
35
+ index = load_index(project_root)
36
+ if not index:
37
+ index = build_index(project_root)
38
+ save_index(project_root, index)
39
+
40
+ G = build_call_graph(index)
41
+
42
+ for e in compile_errors:
43
+ err = JavaError(file=e["file"], line=int(e["line"]), message=e["msg"])
44
+ method = find_method_at_line(index, e["file"], int(e["line"]))
45
+ if not method:
46
+ typer.echo(typer.style(f"\n[!] {e['file']}:{e['line']} — method not in index", fg=typer.colors.YELLOW))
47
+ continue
48
+ related = get_related(G, method["class"], method["method"])
49
+ print_block(err, method, project_root, related["callers"], related["callees"])
50
+
51
+ typer.echo(f"\nFinished in {time.perf_counter() - t0:.3f}s")
@@ -0,0 +1,222 @@
1
+ from __future__ import annotations
2
+ import re
3
+ import subprocess
4
+ import time
5
+ import threading
6
+ from pathlib import Path
7
+ import typer
8
+ from ...core.project import find_project_root
9
+ from ...scanner.scanner import find_all_supported_files
10
+ from ...db.cache import CacheManager
11
+
12
+
13
+ def _find_maven_cmd(root: Path) -> str:
14
+ for name in ["mvnw.cmd", "mvnw"]:
15
+ if (root / name).exists():
16
+ return str(root / name)
17
+ return "mvn"
18
+
19
+
20
+ def _changed_files(project_root: Path) -> list[Path]:
21
+ try:
22
+ cache = CacheManager(project_root)
23
+ cached = {Path(p) for p in cache.get_cached_files()}
24
+ changed = []
25
+ for p in cached:
26
+ if not p.exists():
27
+ continue
28
+ db_mtime = cache.get_file_mtime(p)
29
+ if db_mtime is None or p.stat().st_mtime > db_mtime:
30
+ changed.append(p)
31
+ return changed
32
+ except Exception:
33
+ return []
34
+
35
+
36
+ _MVN_ERROR_RE = re.compile(r'^\[ERROR\]\s+(.+?\.java):\[(\d+),\d+\]\s+(.+)$')
37
+ _MVN_WARNING_RE = re.compile(r'^\[WARNING\]\s+(.+?\.java):\[(\d+),\d+\]\s+(.+)$')
38
+
39
+
40
+ def _run_mvn_compile(project_root: Path) -> tuple[list[dict], list[dict], str]:
41
+ mvn = _find_maven_cmd(project_root)
42
+ done = threading.Event()
43
+
44
+ def spinner():
45
+ chars = ["|", "/", "-", "\\"]
46
+ i = 0
47
+ start = time.perf_counter()
48
+ while not done.is_set():
49
+ elapsed = time.perf_counter() - start
50
+ typer.echo(f"\r {chars[i % 4]} mvn compile running... {elapsed:.0f}s", nl=False)
51
+ i += 1
52
+ time.sleep(0.2)
53
+ typer.echo("\r" + " " * 40 + "\r", nl=False)
54
+
55
+ t = threading.Thread(target=spinner, daemon=True)
56
+ t.start()
57
+
58
+ try:
59
+ result = subprocess.run(
60
+ f'"{mvn}" compile -q --batch-mode',
61
+ cwd=str(project_root),
62
+ capture_output=True, text=True, shell=True, timeout=300,
63
+ )
64
+ output = result.stdout + result.stderr
65
+ except subprocess.TimeoutExpired:
66
+ done.set()
67
+ return [], [{"file": "?", "line": "?", "msg": "mvn compile timed out"}], ""
68
+ except Exception as e:
69
+ done.set()
70
+ return [], [{"file": "?", "line": "?", "msg": str(e)}], ""
71
+ finally:
72
+ done.set()
73
+ t.join()
74
+
75
+ errors, warnings = [], []
76
+ seen_e, seen_w = set(), set()
77
+ for line in output.splitlines():
78
+ m = _MVN_ERROR_RE.match(line.strip())
79
+ if m:
80
+ k = (Path(m.group(1)).name, m.group(2))
81
+ if k not in seen_e:
82
+ seen_e.add(k)
83
+ errors.append({"file": Path(m.group(1)).name, "line": m.group(2), "msg": m.group(3).strip()})
84
+ continue
85
+ m = _MVN_WARNING_RE.match(line.strip())
86
+ if m:
87
+ k = (Path(m.group(1)).name, m.group(2))
88
+ if k not in seen_w:
89
+ seen_w.add(k)
90
+ warnings.append({"file": Path(m.group(1)).name, "line": m.group(2), "msg": m.group(3).strip()})
91
+ return errors, warnings, output
92
+
93
+
94
+ _STATIC_CHECKS = [
95
+ (re.compile(r'catch\s*\([^)]+\)\s*\{\s*\}'), "Empty catch block", "warning"),
96
+ (re.compile(r'\.get\(\)\s*\.'), "Possible null dereference after .get()", "warning"),
97
+ (re.compile(r'System\.out\.print'), "Debug System.out.print left in code", "warning"),
98
+ (re.compile(r'//\s*(TODO|FIXME|HACK|XXX)\b'), "Unresolved TODO/FIXME", "info"),
99
+ (re.compile(r'catch\s*\(\s*Exception\s+\w+\s*\)'), "Catching raw Exception", "warning"),
100
+ (re.compile(r'==\s*"[^"]*"'), 'String compared with == instead of .equals()', "error"),
101
+ (re.compile(r'static\s+(?!final)\w+\s+\w+\s*='), "Mutable static field", "warning"),
102
+ (re.compile(r'throw\s+new\s+NullPointerException\s*\(\s*\)'), "NullPointerException without message", "warning"),
103
+ ]
104
+ _SKIP_DIRS = {"target", "build", ".git", "test", "tests"}
105
+
106
+
107
+ def _static_analyze(files: list[Path]) -> list[dict]:
108
+ issues = []
109
+ for f in files:
110
+ if any(part in _SKIP_DIRS for part in f.parts):
111
+ continue
112
+ try:
113
+ lines = f.read_text(encoding="utf-8", errors="ignore").splitlines()
114
+ except Exception:
115
+ continue
116
+ for lineno, line in enumerate(lines, 1):
117
+ for pattern, msg, severity in _STATIC_CHECKS:
118
+ if pattern.search(line):
119
+ issues.append({"file": f.name, "line": str(lineno), "msg": msg, "severity": severity})
120
+ break
121
+ return issues
122
+
123
+
124
+ _SEV_ORDER = {"error": 0, "warning": 1, "info": 2}
125
+
126
+
127
+ def _render(compile_errors, compile_warnings, static_issues) -> list[str]:
128
+ out = []
129
+ if not compile_errors and not compile_warnings and not static_issues:
130
+ return ["No issues found."]
131
+ if compile_errors:
132
+ out.append(f"COMPILE ERRORS ({len(compile_errors)})")
133
+ out.append("-" * 60)
134
+ for e in compile_errors:
135
+ out.append(f" {e['file']:<30} line {e['line']:<6} {e['msg']}")
136
+ out.append("")
137
+ if compile_warnings:
138
+ out.append(f"COMPILE WARNINGS ({len(compile_warnings)})")
139
+ out.append("-" * 60)
140
+ for w in compile_warnings:
141
+ out.append(f" {w['file']:<30} line {w['line']:<6} {w['msg']}")
142
+ out.append("")
143
+ if static_issues:
144
+ static_issues.sort(key=lambda x: (_SEV_ORDER.get(x["severity"], 9), x["file"], int(x["line"])))
145
+ by_sev: dict[str, list] = {}
146
+ for i in static_issues:
147
+ by_sev.setdefault(i["severity"], []).append(i)
148
+ labels = {"error": "STATIC ERRORS", "warning": "STATIC WARNINGS", "info": "STATIC INFO"}
149
+ for sev in ("error", "warning", "info"):
150
+ group = by_sev.get(sev, [])
151
+ if group:
152
+ out.append(f"{labels[sev]} ({len(group)})")
153
+ out.append("-" * 60)
154
+ for i in group:
155
+ out.append(f" {i['file']:<30} line {i['line']:<6} {i['msg']}")
156
+ out.append("")
157
+ out.append("-" * 60)
158
+ out.append(f" Total: {len(compile_errors)} compile error(s), {len(compile_warnings)} warning(s), {len(static_issues)} static issue(s)")
159
+ return out
160
+
161
+
162
+ def register_errors(app: typer.Typer) -> None:
163
+ @app.command("errors", help="Static analysis (instant). Add --compile to also run mvn compile.")
164
+ def errors(
165
+ path: str = typer.Argument(".", help="Project root"),
166
+ changed_only: bool = typer.Option(False, "--changed-only", "-c"),
167
+ compile: bool = typer.Option(False, "--compile", help="Also run mvn compile"),
168
+ skip_static: bool = typer.Option(False, "--skip-static"),
169
+ verbose: bool = typer.Option(False, "--verbose", "-v"),
170
+ block: bool = typer.Option(False, "--block", "-b", help="Show exact method code block for each compile error"),
171
+ ) -> None:
172
+ t0 = time.perf_counter()
173
+ project_root = find_project_root(Path(path).resolve())
174
+
175
+ if changed_only:
176
+ java_files = _changed_files(project_root)
177
+ typer.echo(f"Scanning {len(java_files)} changed file(s)...")
178
+ else:
179
+ java_files = [f for f in find_all_supported_files(project_root) if f.suffix.lower() == ".java"]
180
+ typer.echo(f"Scanning {len(java_files)} Java file(s)...")
181
+ typer.echo("")
182
+
183
+ compile_errors, compile_warnings, raw_output = [], [], ""
184
+
185
+ if compile or block:
186
+ compile_errors, compile_warnings, raw_output = _run_mvn_compile(project_root)
187
+
188
+ static_issues = []
189
+ if not skip_static:
190
+ static_issues = _static_analyze(java_files)
191
+ if not verbose:
192
+ static_issues = [i for i in static_issues if i["severity"] != "info"]
193
+
194
+ for line in _render(compile_errors, compile_warnings, static_issues):
195
+ typer.echo(line)
196
+
197
+ if block and compile_errors:
198
+ _show_blocks(project_root, compile_errors)
199
+ elif block and not compile_errors:
200
+ typer.echo("No compile errors to show blocks for.")
201
+
202
+ typer.echo(f"\nFinished in {time.perf_counter() - t0:.3f}s")
203
+
204
+
205
+ def _show_blocks(project_root: Path, compile_errors: list[dict]) -> None:
206
+ from ...lang.java.method_index import load_index, build_index, save_index, find_method_at_line
207
+ from ...terminal.printer import print_block
208
+ from ...terminal.error_parser import JavaError
209
+
210
+ index = load_index(project_root)
211
+ if not index:
212
+ typer.echo("\nBuilding method index...")
213
+ index = build_index(project_root)
214
+ save_index(project_root, index)
215
+
216
+ for e in compile_errors:
217
+ err = JavaError(file=e["file"], line=int(e["line"]), message=e["msg"])
218
+ method = find_method_at_line(index, e["file"], int(e["line"]))
219
+ if method:
220
+ print_block(err, method, project_root)
221
+ else:
222
+ typer.echo(f"\n[!] {e['file']}:{e['line']} - method not in index")
@@ -0,0 +1,192 @@
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
+ MODEL_DIRS = {
11
+ "model", "models",
12
+ "entity", "entities",
13
+ "domain", "domains",
14
+ "core",
15
+ "aggregate", "aggregates",
16
+ "persistence",
17
+ "db", "data", "schema",
18
+ "pojo", "bean", "beans",
19
+ "orm", "schemas",
20
+ "datamodel", "datamodels",
21
+ "table", "tables",
22
+ "record", "records",
23
+ "document", "documents",
24
+ "dao",
25
+ # petclinic-style: models live in feature packages
26
+ "owner", "vet", "pet", "visit",
27
+ }
28
+
29
+ SKIP_DIRS = {
30
+ "test", "tests",
31
+ "controller", "controllers",
32
+ "service", "services",
33
+ "repository", "repositories",
34
+ "config", "configuration",
35
+ "util", "utils", "helper", "helpers",
36
+ "exception", "exceptions",
37
+ "security",
38
+ "mapper", "mappers",
39
+ "migration", "migrations",
40
+ "dto",
41
+ }
42
+
43
+ SKIP_SUFFIXES = (
44
+ "Repository", "Mapper", "Dao",
45
+ "Service", "Controller",
46
+ "Config", "Configuration",
47
+ "Util", "Utils", "Helper",
48
+ "Exception", "Handler",
49
+ "Interceptor", "Filter",
50
+ "Listener", "Scheduler",
51
+ "Validator", "Converter",
52
+ "Serializer", "Deserializer",
53
+ "Factory", "Builder",
54
+ "Specification",
55
+ )
56
+
57
+ # Annotations that definitively mark a class as a domain model
58
+ _ENTITY_ANNOTATIONS = {
59
+ "Entity", "MappedSuperclass", "Embeddable",
60
+ "Table", "Document",
61
+ }
62
+
63
+
64
+ def _import_extractors():
65
+ py = None
66
+ jv = None
67
+ try:
68
+ from ...lang.python.models import extract_python_models as _py
69
+ py = _py
70
+ except Exception:
71
+ pass
72
+ try:
73
+ from ...context.python_context import extract_python_models as _py
74
+ py = _py
75
+ except Exception:
76
+ pass
77
+ try:
78
+ from ...lang.java.models import extract_java_models as _jv
79
+ jv = _jv
80
+ except Exception:
81
+ pass
82
+ try:
83
+ from ...context.java_context import extract_java_models as _jv
84
+ jv = _jv
85
+ except Exception:
86
+ pass
87
+ return py, jv
88
+
89
+
90
+ def _is_model_path(rel: Path) -> bool:
91
+ parts = [p.lower() for p in rel.parts]
92
+ if "src" in parts and "test" in parts:
93
+ return False
94
+ if any(part in SKIP_DIRS for part in parts):
95
+ return False
96
+ stem = rel.stem
97
+ if any(stem.endswith(s) for s in SKIP_SUFFIXES):
98
+ return False
99
+ return any(part in MODEL_DIRS for part in parts)
100
+
101
+
102
+ def _has_entity_annotation(path: Path) -> bool:
103
+ """Fast check: does this .java file contain a JPA/document annotation?"""
104
+ try:
105
+ text = path.read_text(encoding="utf-8", errors="ignore")
106
+ for ann in _ENTITY_ANNOTATIONS:
107
+ if f"@{ann}" in text:
108
+ return True
109
+ except Exception:
110
+ pass
111
+ return False
112
+
113
+
114
+ def register_models(app: typer.Typer) -> None:
115
+ @app.command(help="List domain models.")
116
+ def models(
117
+ root: str = typer.Argument(".", help="Project root folder"),
118
+ all_files: bool = typer.Option(False, "--all", help="Scan all Java files (not just model dirs)"),
119
+ with_inherited: bool = typer.Option(True, "--inherited/--no-inherited", help="Merge inherited fields"),
120
+ ):
121
+ root_path = Path(root).resolve()
122
+ project_root = find_project_root(root_path)
123
+ extract_python_models, extract_java_models = _import_extractors()
124
+ files = find_all_supported_files(project_root)
125
+
126
+ pr = project_root.resolve()
127
+ candidates: list[Path] = []
128
+
129
+ for f in files:
130
+ fr = f.resolve()
131
+ try:
132
+ rel = fr.relative_to(pr)
133
+ except Exception:
134
+ continue
135
+
136
+ ext = f.suffix.lower()
137
+
138
+ if ext == ".py":
139
+ if _is_model_path(rel):
140
+ candidates.append(fr)
141
+ elif ext == ".java":
142
+ # 1. Directory-based heuristic
143
+ if all_files or _is_model_path(rel):
144
+ candidates.append(fr)
145
+ # 2. Annotation-based fallback — catches petclinic-style feature packages
146
+ elif _has_entity_annotation(fr):
147
+ candidates.append(fr)
148
+
149
+ # --- collect all java models first for inheritance resolution ---
150
+ from ...lang.java.models import resolve_inherited_fields, ModelDef
151
+
152
+ java_models_all: list[ModelDef] = []
153
+ java_file_map: dict[str, list[ModelDef]] = {}
154
+
155
+ if extract_java_models:
156
+ for f in sorted(candidates, key=lambda x: str(x).lower()):
157
+ if f.suffix.lower() == ".java":
158
+ defs = extract_java_models(f, pr)
159
+ java_file_map[str(f)] = defs
160
+ java_models_all.extend(defs)
161
+
162
+ if with_inherited:
163
+ java_models_all = resolve_inherited_fields(java_models_all)
164
+ # rebuild map with resolved models
165
+ resolved_by_name = {m.name: m for m in java_models_all}
166
+ for k in java_file_map:
167
+ java_file_map[k] = [
168
+ resolved_by_name.get(m.name, m)
169
+ for m in java_file_map[k]
170
+ ]
171
+
172
+ out: list[str] = []
173
+ for f in sorted(candidates, key=lambda x: str(x).lower()):
174
+ ext = f.suffix.lower()
175
+ if ext == ".py" and extract_python_models is not None:
176
+ defs = extract_python_models(f, project_root)
177
+ elif ext == ".java":
178
+ defs = java_file_map.get(str(f), [])
179
+ else:
180
+ continue
181
+ for d in defs:
182
+ inherited_marker = f" [extends: {', '.join(d.bases)}]" if d.bases else ""
183
+ out.append(f"{d.name} ({d.kind}){inherited_marker} {d.file}")
184
+ for fld in d.fields:
185
+ out.append(f" - {fld.name}: {fld.type}")
186
+
187
+ if not out:
188
+ typer.echo("No domain models found.")
189
+ raise typer.Exit(0)
190
+
191
+ for line in out:
192
+ typer.echo(line)