codexa 0.4.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 (189) hide show
  1. codexa-0.4.0.dist-info/METADATA +650 -0
  2. codexa-0.4.0.dist-info/RECORD +189 -0
  3. codexa-0.4.0.dist-info/WHEEL +5 -0
  4. codexa-0.4.0.dist-info/entry_points.txt +2 -0
  5. codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
  6. codexa-0.4.0.dist-info/top_level.txt +1 -0
  7. semantic_code_intelligence/__init__.py +5 -0
  8. semantic_code_intelligence/analysis/__init__.py +21 -0
  9. semantic_code_intelligence/analysis/ai_features.py +351 -0
  10. semantic_code_intelligence/bridge/__init__.py +28 -0
  11. semantic_code_intelligence/bridge/context_provider.py +245 -0
  12. semantic_code_intelligence/bridge/protocol.py +167 -0
  13. semantic_code_intelligence/bridge/server.py +348 -0
  14. semantic_code_intelligence/bridge/vscode.py +271 -0
  15. semantic_code_intelligence/ci/__init__.py +13 -0
  16. semantic_code_intelligence/ci/hooks.py +98 -0
  17. semantic_code_intelligence/ci/hotspots.py +272 -0
  18. semantic_code_intelligence/ci/impact.py +246 -0
  19. semantic_code_intelligence/ci/metrics.py +591 -0
  20. semantic_code_intelligence/ci/pr.py +412 -0
  21. semantic_code_intelligence/ci/quality.py +557 -0
  22. semantic_code_intelligence/ci/templates.py +164 -0
  23. semantic_code_intelligence/ci/trace.py +224 -0
  24. semantic_code_intelligence/cli/__init__.py +0 -0
  25. semantic_code_intelligence/cli/commands/__init__.py +0 -0
  26. semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
  27. semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
  28. semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
  29. semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
  30. semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
  31. semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
  32. semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
  33. semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
  34. semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
  35. semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
  36. semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
  37. semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
  38. semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
  39. semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
  40. semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
  41. semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
  42. semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
  43. semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
  44. semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
  45. semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
  46. semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
  47. semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
  48. semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
  49. semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
  50. semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
  51. semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
  52. semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
  53. semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
  54. semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
  55. semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
  56. semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
  57. semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
  58. semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
  59. semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
  60. semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
  61. semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
  62. semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
  63. semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
  64. semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
  65. semantic_code_intelligence/cli/main.py +65 -0
  66. semantic_code_intelligence/cli/router.py +92 -0
  67. semantic_code_intelligence/config/__init__.py +0 -0
  68. semantic_code_intelligence/config/settings.py +260 -0
  69. semantic_code_intelligence/context/__init__.py +19 -0
  70. semantic_code_intelligence/context/engine.py +429 -0
  71. semantic_code_intelligence/context/memory.py +253 -0
  72. semantic_code_intelligence/daemon/__init__.py +1 -0
  73. semantic_code_intelligence/daemon/watcher.py +515 -0
  74. semantic_code_intelligence/docs/__init__.py +1080 -0
  75. semantic_code_intelligence/embeddings/__init__.py +0 -0
  76. semantic_code_intelligence/embeddings/enhanced.py +131 -0
  77. semantic_code_intelligence/embeddings/generator.py +149 -0
  78. semantic_code_intelligence/embeddings/model_registry.py +100 -0
  79. semantic_code_intelligence/evolution/__init__.py +1 -0
  80. semantic_code_intelligence/evolution/budget_guard.py +111 -0
  81. semantic_code_intelligence/evolution/commit_manager.py +88 -0
  82. semantic_code_intelligence/evolution/context_builder.py +131 -0
  83. semantic_code_intelligence/evolution/engine.py +249 -0
  84. semantic_code_intelligence/evolution/patch_generator.py +229 -0
  85. semantic_code_intelligence/evolution/task_selector.py +214 -0
  86. semantic_code_intelligence/evolution/test_runner.py +111 -0
  87. semantic_code_intelligence/indexing/__init__.py +0 -0
  88. semantic_code_intelligence/indexing/chunker.py +174 -0
  89. semantic_code_intelligence/indexing/parallel.py +86 -0
  90. semantic_code_intelligence/indexing/scanner.py +146 -0
  91. semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
  92. semantic_code_intelligence/llm/__init__.py +62 -0
  93. semantic_code_intelligence/llm/cache.py +219 -0
  94. semantic_code_intelligence/llm/cached_provider.py +145 -0
  95. semantic_code_intelligence/llm/conversation.py +190 -0
  96. semantic_code_intelligence/llm/cross_refactor.py +272 -0
  97. semantic_code_intelligence/llm/investigation.py +274 -0
  98. semantic_code_intelligence/llm/mock_provider.py +77 -0
  99. semantic_code_intelligence/llm/ollama_provider.py +122 -0
  100. semantic_code_intelligence/llm/openai_provider.py +100 -0
  101. semantic_code_intelligence/llm/provider.py +92 -0
  102. semantic_code_intelligence/llm/rate_limiter.py +164 -0
  103. semantic_code_intelligence/llm/reasoning.py +438 -0
  104. semantic_code_intelligence/llm/safety.py +110 -0
  105. semantic_code_intelligence/llm/streaming.py +251 -0
  106. semantic_code_intelligence/lsp/__init__.py +609 -0
  107. semantic_code_intelligence/mcp/__init__.py +393 -0
  108. semantic_code_intelligence/parsing/__init__.py +19 -0
  109. semantic_code_intelligence/parsing/parser.py +375 -0
  110. semantic_code_intelligence/plugins/__init__.py +255 -0
  111. semantic_code_intelligence/plugins/examples/__init__.py +1 -0
  112. semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
  113. semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
  114. semantic_code_intelligence/scalability/__init__.py +205 -0
  115. semantic_code_intelligence/search/__init__.py +0 -0
  116. semantic_code_intelligence/search/formatter.py +123 -0
  117. semantic_code_intelligence/search/grep.py +361 -0
  118. semantic_code_intelligence/search/hybrid_search.py +170 -0
  119. semantic_code_intelligence/search/keyword_search.py +311 -0
  120. semantic_code_intelligence/search/section_expander.py +103 -0
  121. semantic_code_intelligence/services/__init__.py +0 -0
  122. semantic_code_intelligence/services/indexing_service.py +630 -0
  123. semantic_code_intelligence/services/search_service.py +269 -0
  124. semantic_code_intelligence/storage/__init__.py +0 -0
  125. semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
  126. semantic_code_intelligence/storage/hash_store.py +66 -0
  127. semantic_code_intelligence/storage/index_manifest.py +85 -0
  128. semantic_code_intelligence/storage/index_stats.py +138 -0
  129. semantic_code_intelligence/storage/query_history.py +160 -0
  130. semantic_code_intelligence/storage/symbol_registry.py +209 -0
  131. semantic_code_intelligence/storage/vector_store.py +297 -0
  132. semantic_code_intelligence/tests/__init__.py +0 -0
  133. semantic_code_intelligence/tests/test_ai_features.py +351 -0
  134. semantic_code_intelligence/tests/test_chunker.py +119 -0
  135. semantic_code_intelligence/tests/test_cli.py +188 -0
  136. semantic_code_intelligence/tests/test_config.py +154 -0
  137. semantic_code_intelligence/tests/test_context.py +381 -0
  138. semantic_code_intelligence/tests/test_embeddings.py +73 -0
  139. semantic_code_intelligence/tests/test_endtoend.py +1142 -0
  140. semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
  141. semantic_code_intelligence/tests/test_hash_store.py +79 -0
  142. semantic_code_intelligence/tests/test_logging.py +55 -0
  143. semantic_code_intelligence/tests/test_new_cli.py +138 -0
  144. semantic_code_intelligence/tests/test_parser.py +495 -0
  145. semantic_code_intelligence/tests/test_phase10.py +355 -0
  146. semantic_code_intelligence/tests/test_phase11.py +593 -0
  147. semantic_code_intelligence/tests/test_phase12.py +375 -0
  148. semantic_code_intelligence/tests/test_phase13.py +663 -0
  149. semantic_code_intelligence/tests/test_phase14.py +568 -0
  150. semantic_code_intelligence/tests/test_phase15.py +814 -0
  151. semantic_code_intelligence/tests/test_phase16.py +792 -0
  152. semantic_code_intelligence/tests/test_phase17.py +815 -0
  153. semantic_code_intelligence/tests/test_phase18.py +934 -0
  154. semantic_code_intelligence/tests/test_phase19.py +986 -0
  155. semantic_code_intelligence/tests/test_phase20.py +2753 -0
  156. semantic_code_intelligence/tests/test_phase20b.py +2058 -0
  157. semantic_code_intelligence/tests/test_phase20c.py +962 -0
  158. semantic_code_intelligence/tests/test_phase21.py +428 -0
  159. semantic_code_intelligence/tests/test_phase22.py +799 -0
  160. semantic_code_intelligence/tests/test_phase23.py +783 -0
  161. semantic_code_intelligence/tests/test_phase24.py +715 -0
  162. semantic_code_intelligence/tests/test_phase25.py +496 -0
  163. semantic_code_intelligence/tests/test_phase26.py +251 -0
  164. semantic_code_intelligence/tests/test_phase27.py +531 -0
  165. semantic_code_intelligence/tests/test_phase8.py +592 -0
  166. semantic_code_intelligence/tests/test_phase9.py +643 -0
  167. semantic_code_intelligence/tests/test_plugins.py +293 -0
  168. semantic_code_intelligence/tests/test_priority_features.py +727 -0
  169. semantic_code_intelligence/tests/test_router.py +41 -0
  170. semantic_code_intelligence/tests/test_scalability.py +138 -0
  171. semantic_code_intelligence/tests/test_scanner.py +125 -0
  172. semantic_code_intelligence/tests/test_search.py +160 -0
  173. semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
  174. semantic_code_intelligence/tests/test_tools.py +182 -0
  175. semantic_code_intelligence/tests/test_vector_store.py +151 -0
  176. semantic_code_intelligence/tests/test_watcher.py +211 -0
  177. semantic_code_intelligence/tools/__init__.py +442 -0
  178. semantic_code_intelligence/tools/executor.py +232 -0
  179. semantic_code_intelligence/tools/protocol.py +200 -0
  180. semantic_code_intelligence/tui/__init__.py +454 -0
  181. semantic_code_intelligence/utils/__init__.py +0 -0
  182. semantic_code_intelligence/utils/logging.py +112 -0
  183. semantic_code_intelligence/version.py +3 -0
  184. semantic_code_intelligence/web/__init__.py +11 -0
  185. semantic_code_intelligence/web/api.py +289 -0
  186. semantic_code_intelligence/web/server.py +397 -0
  187. semantic_code_intelligence/web/ui.py +659 -0
  188. semantic_code_intelligence/web/visualize.py +226 -0
  189. semantic_code_intelligence/workspace/__init__.py +427 -0
@@ -0,0 +1,275 @@
1
+ """CLI command: plugin — manage and scaffold plugins."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json as json_mod
6
+ import textwrap
7
+ from pathlib import Path
8
+
9
+ import click
10
+
11
+ from semantic_code_intelligence.utils.logging import (
12
+ console,
13
+ get_logger,
14
+ print_error,
15
+ print_info,
16
+ print_success,
17
+ )
18
+
19
+ logger = get_logger("cli.plugin")
20
+
21
+
22
+ PLUGIN_TEMPLATE = textwrap.dedent('''\
23
+ """CodexA plugin: {name}
24
+
25
+ {description}
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from typing import Any
31
+
32
+ from semantic_code_intelligence.plugins import PluginBase, PluginHook, PluginMetadata
33
+
34
+
35
+ class {class_name}(PluginBase):
36
+ """Plugin implementation for {name}."""
37
+
38
+ def metadata(self) -> PluginMetadata:
39
+ return PluginMetadata(
40
+ name="{name}",
41
+ version="0.1.0",
42
+ description="{description}",
43
+ author="{author}",
44
+ hooks=[{hooks}],
45
+ )
46
+
47
+ def activate(self, context: dict[str, Any]) -> None:
48
+ """Called when the plugin is activated."""
49
+
50
+ def deactivate(self) -> None:
51
+ """Called when the plugin is deactivated."""
52
+
53
+ def on_hook(self, hook: PluginHook, data: dict[str, Any]) -> dict[str, Any]:
54
+ """Process hook events.
55
+
56
+ Args:
57
+ hook: The hook that fired.
58
+ data: Hook-specific data. Modify and return.
59
+ """
60
+ # Add your logic here
61
+ return data
62
+
63
+
64
+ def create_plugin() -> {class_name}:
65
+ """Factory function for plugin discovery."""
66
+ return {class_name}()
67
+ ''')
68
+
69
+
70
+ @click.group("plugin")
71
+ def plugin_cmd() -> None:
72
+ """Manage CodexA plugins."""
73
+
74
+
75
+ @plugin_cmd.command("new")
76
+ @click.argument("name")
77
+ @click.option(
78
+ "--description",
79
+ "-d",
80
+ default="A CodexA plugin",
81
+ help="Plugin description.",
82
+ )
83
+ @click.option(
84
+ "--author",
85
+ "-a",
86
+ default="",
87
+ help="Plugin author name.",
88
+ )
89
+ @click.option(
90
+ "--hooks",
91
+ "-H",
92
+ default="POST_SEARCH",
93
+ help="Comma-separated hook names (e.g. POST_SEARCH,POST_AI).",
94
+ )
95
+ @click.option(
96
+ "--output",
97
+ "-o",
98
+ default=None,
99
+ type=click.Path(file_okay=False),
100
+ help="Output directory (default: .codexa/plugins/).",
101
+ )
102
+ def plugin_new(name: str, description: str, author: str, hooks: str, output: str | None) -> None:
103
+ """Scaffold a new plugin from template.
104
+
105
+ Creates a ready-to-use plugin file with the correct structure.
106
+
107
+ Examples:
108
+
109
+ codexa plugin new my-formatter
110
+
111
+ codexa plugin new lint-checker --hooks CUSTOM_VALIDATION,POST_AI
112
+
113
+ codexa plugin new metrics -o ./plugins/ -a "Your Name"
114
+ """
115
+ # Validate hook names
116
+ from semantic_code_intelligence.plugins import PluginHook
117
+
118
+ hook_names = [h.strip().upper() for h in hooks.split(",") if h.strip()]
119
+ valid_hooks = {h.name for h in PluginHook}
120
+ for h in hook_names:
121
+ if h not in valid_hooks:
122
+ print_error(f"Unknown hook: {h}. Valid hooks: {', '.join(sorted(valid_hooks))}")
123
+ return
124
+
125
+ hook_refs = ", ".join(f"PluginHook.{h}" for h in hook_names)
126
+
127
+ # Build class name from plugin name
128
+ class_name = "".join(part.capitalize() for part in name.replace("-", "_").split("_")) + "Plugin"
129
+
130
+ content = PLUGIN_TEMPLATE.format(
131
+ name=name,
132
+ description=description,
133
+ author=author,
134
+ class_name=class_name,
135
+ hooks=hook_refs,
136
+ )
137
+
138
+ # Determine output path
139
+ if output:
140
+ out_dir = Path(output).resolve()
141
+ else:
142
+ out_dir = Path(".codexa/plugins").resolve()
143
+ out_dir.mkdir(parents=True, exist_ok=True)
144
+
145
+ filename = name.replace("-", "_") + ".py"
146
+ filepath = out_dir / filename
147
+
148
+ if filepath.exists():
149
+ print_error(f"File already exists: {filepath}")
150
+ return
151
+
152
+ filepath.write_text(content, encoding="utf-8")
153
+ print_success(f"Created plugin: {filepath}")
154
+ print_info(f"Register it by placing it in .codexa/plugins/ or calling PluginManager.register()")
155
+
156
+
157
+ @plugin_cmd.command("list")
158
+ @click.option(
159
+ "--path",
160
+ "-p",
161
+ default=".",
162
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
163
+ help="Project root path.",
164
+ )
165
+ @click.option(
166
+ "--json-output",
167
+ "--json",
168
+ "json_mode",
169
+ is_flag=True,
170
+ default=False,
171
+ help="Output results in JSON format.",
172
+ )
173
+ def plugin_list(path: str, json_mode: bool) -> None:
174
+ """List available plugins in the project.
175
+
176
+ Scans .codexa/plugins/ for discoverable plugin files.
177
+
178
+ Examples:
179
+
180
+ codexa plugin list
181
+
182
+ codexa plugin list --json
183
+ """
184
+ from semantic_code_intelligence.plugins import PluginManager
185
+
186
+ plugin_dir = Path(path) / ".codexa" / "plugins"
187
+ mgr = PluginManager()
188
+
189
+ if plugin_dir.is_dir():
190
+ count = mgr.discover_from_directory(plugin_dir)
191
+ else:
192
+ count = 0
193
+
194
+ plugins = []
195
+ for name in mgr.registered_plugins:
196
+ info = mgr.get_plugin_info(name)
197
+ if info:
198
+ plugins.append(info)
199
+
200
+ if json_mode:
201
+ click.echo(json_mod.dumps({"plugins": plugins, "count": len(plugins)}, indent=2))
202
+ return
203
+
204
+ if not plugins:
205
+ print_info(f"No plugins found in {plugin_dir}/")
206
+ print_info("Create one with: codexa plugin new <name>")
207
+ return
208
+
209
+ from rich.table import Table
210
+
211
+ table = Table(title="Discovered Plugins")
212
+ table.add_column("Name", style="bold")
213
+ table.add_column("Version")
214
+ table.add_column("Description")
215
+ table.add_column("Hooks")
216
+
217
+ for p in plugins:
218
+ table.add_row(
219
+ p["name"],
220
+ p["version"],
221
+ p["description"],
222
+ ", ".join(p.get("hooks", [])),
223
+ )
224
+
225
+ console.print(table)
226
+
227
+
228
+ @plugin_cmd.command("info")
229
+ @click.argument("name")
230
+ @click.option(
231
+ "--path",
232
+ "-p",
233
+ default=".",
234
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
235
+ help="Project root path.",
236
+ )
237
+ @click.option(
238
+ "--json-output",
239
+ "--json",
240
+ "json_mode",
241
+ is_flag=True,
242
+ default=False,
243
+ help="Output in JSON format.",
244
+ )
245
+ def plugin_info(name: str, path: str, json_mode: bool) -> None:
246
+ """Show details about a specific plugin.
247
+
248
+ Examples:
249
+
250
+ codexa plugin info my-formatter
251
+ """
252
+ from semantic_code_intelligence.plugins import PluginManager
253
+
254
+ plugin_dir = Path(path) / ".codexa" / "plugins"
255
+ mgr = PluginManager()
256
+
257
+ if plugin_dir.is_dir():
258
+ mgr.discover_from_directory(plugin_dir)
259
+
260
+ info = mgr.get_plugin_info(name)
261
+ if info is None:
262
+ print_error(f"Plugin '{name}' not found.")
263
+ return
264
+
265
+ if json_mode:
266
+ click.echo(json_mod.dumps(info, indent=2))
267
+ return
268
+
269
+ console.print(f"[bold]{info['name']}[/bold] v{info['version']}")
270
+ if info.get("description"):
271
+ console.print(f" {info['description']}")
272
+ if info.get("author"):
273
+ console.print(f" Author: {info['author']}")
274
+ console.print(f" Hooks: {', '.join(info.get('hooks', []))}")
275
+ console.print(f" Active: {info.get('active', False)}")
@@ -0,0 +1,178 @@
1
+ """CLI command: pr-summary — generate PR intelligence report."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json as json_mod
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from semantic_code_intelligence.utils.logging import (
11
+ console,
12
+ get_logger,
13
+ print_error,
14
+ print_info,
15
+ )
16
+
17
+ logger = get_logger("cli.pr_summary")
18
+
19
+
20
+ def _find_changed_files(root: Path) -> list[str]:
21
+ """Discover changed files using simple heuristics.
22
+
23
+ Checks for:
24
+ 1. Git diff (if inside a git repo).
25
+ 2. Falls back to all supported source files.
26
+ """
27
+ import subprocess
28
+
29
+ try:
30
+ result = subprocess.run(
31
+ ["git", "diff", "--name-only", "HEAD~1", "HEAD"],
32
+ capture_output=True,
33
+ text=True,
34
+ cwd=str(root),
35
+ timeout=10,
36
+ )
37
+ if result.returncode == 0 and result.stdout.strip():
38
+ files = []
39
+ for line in result.stdout.strip().splitlines():
40
+ fpath = root / line.strip()
41
+ if fpath.exists():
42
+ files.append(str(fpath))
43
+ if files:
44
+ return files
45
+ except Exception:
46
+ logger.debug("git diff-tree failed; falling back to full file scan")
47
+
48
+ # Fallback: list all supported source files
49
+ from semantic_code_intelligence.parsing.parser import EXTENSION_TO_LANGUAGE
50
+ files = []
51
+ for f in root.rglob("*"):
52
+ if f.is_file() and f.suffix in EXTENSION_TO_LANGUAGE:
53
+ parts = f.relative_to(root).parts
54
+ if any(p.startswith(".") or p in ("__pycache__", "node_modules") for p in parts):
55
+ continue
56
+ files.append(str(f))
57
+ return files
58
+
59
+
60
+ @click.command("pr-summary")
61
+ @click.option(
62
+ "--path",
63
+ "-p",
64
+ default=".",
65
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
66
+ help="Project root path.",
67
+ )
68
+ @click.option(
69
+ "--json-output",
70
+ "--json",
71
+ "json_mode",
72
+ is_flag=True,
73
+ default=False,
74
+ help="Output in JSON format.",
75
+ )
76
+ @click.option(
77
+ "--files",
78
+ "-f",
79
+ multiple=True,
80
+ help="Specific files to analyze (can be repeated).",
81
+ )
82
+ @click.option(
83
+ "--pipe",
84
+ is_flag=True,
85
+ default=False,
86
+ help="Plain text output for piping / CI.",
87
+ )
88
+ @click.pass_context
89
+ def pr_summary_cmd(
90
+ ctx: click.Context,
91
+ path: str,
92
+ json_mode: bool,
93
+ files: tuple[str, ...],
94
+ pipe: bool,
95
+ ) -> None:
96
+ """Generate a Pull Request intelligence report.
97
+
98
+ Analyzes changed files to produce a change summary, semantic impact
99
+ analysis, suggested reviewer domains, and risk scoring.
100
+
101
+ All output is advisory — CodexA never modifies repository code.
102
+
103
+ Examples:
104
+
105
+ codexa pr-summary
106
+
107
+ codexa pr-summary --json
108
+
109
+ codexa pr-summary -f src/main.py -f src/utils.py
110
+ """
111
+ from semantic_code_intelligence.ci.pr import generate_pr_report
112
+
113
+ root = Path(path).resolve()
114
+ changed = list(files) if files else _find_changed_files(root)
115
+
116
+ if not changed:
117
+ if json_mode:
118
+ click.echo(json_mod.dumps({"error": "No changed files found"}, indent=2))
119
+ else:
120
+ print_info("No changed files found")
121
+ return
122
+
123
+ report = generate_pr_report(changed, root)
124
+
125
+ if json_mode:
126
+ click.echo(json_mod.dumps(report.to_dict(), indent=2))
127
+ return
128
+
129
+ cs = report.change_summary
130
+ if pipe:
131
+ click.echo(f"Changed: {cs.files_changed} files Languages: {', '.join(cs.languages) or 'none'}")
132
+ click.echo(f"Symbols: +{cs.total_symbols_added} -{cs.total_symbols_removed} ~{cs.total_symbols_modified}")
133
+ if report.risk:
134
+ click.echo(f"Risk: {report.risk.level} ({report.risk.score}/100)")
135
+ for f in report.risk.factors:
136
+ click.echo(f" - {f}")
137
+ return
138
+
139
+ # Rich output
140
+ console.print(f"\n[bold cyan]PR Summary[/bold cyan]\n")
141
+ console.print(f" Files changed: {cs.files_changed}")
142
+ console.print(f" Languages: {', '.join(cs.languages) or 'none'}")
143
+ console.print(f" Symbols: [green]+{cs.total_symbols_added}[/green] [red]-{cs.total_symbols_removed}[/red] [yellow]~{cs.total_symbols_modified}[/yellow]\n")
144
+
145
+ if cs.file_details:
146
+ console.print("[bold]File Details:[/bold]")
147
+ for fd in cs.file_details:
148
+ lang_tag = f" [{fd.language}]" if fd.language else ""
149
+ console.print(f" {fd.path}{lang_tag}")
150
+ if fd.symbols_added:
151
+ console.print(f" [green]+ {', '.join(fd.symbols_added)}[/green]")
152
+ if fd.symbols_removed:
153
+ console.print(f" [red]- {', '.join(fd.symbols_removed)}[/red]")
154
+ if fd.symbols_modified:
155
+ console.print(f" [yellow]~ {', '.join(fd.symbols_modified)}[/yellow]")
156
+ console.print()
157
+
158
+ if report.impact:
159
+ imp = report.impact
160
+ if imp.affected_symbols:
161
+ console.print("[bold]Impact — Affected Symbols:[/bold]")
162
+ for s in imp.affected_symbols[:20]:
163
+ console.print(f" {s}")
164
+ console.print()
165
+
166
+ if report.reviewers:
167
+ console.print("[bold]Suggested Reviewer Domains:[/bold]")
168
+ for r in report.reviewers[:10]:
169
+ console.print(f" {r['domain']} ({r['file_count']} file(s))")
170
+ console.print()
171
+
172
+ if report.risk:
173
+ color = {"low": "green", "medium": "yellow", "high": "red", "critical": "bold red"}
174
+ c = color.get(report.risk.level, "white")
175
+ console.print(f"[{c}]Risk: {report.risk.level.upper()} ({report.risk.score}/100)[/{c}]")
176
+ for f in report.risk.factors:
177
+ console.print(f" - {f}")
178
+ console.print()
@@ -0,0 +1,208 @@
1
+ """CLI command: quality — run code quality analysis."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json as json_mod
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING
8
+
9
+ import click
10
+
11
+ from semantic_code_intelligence.utils.logging import (
12
+ console,
13
+ get_logger,
14
+ print_error,
15
+ print_success,
16
+ )
17
+
18
+ if TYPE_CHECKING:
19
+ from semantic_code_intelligence.ci.quality import QualityReport
20
+ from semantic_code_intelligence.llm.safety import SafetyResult
21
+
22
+ logger = get_logger("cli.quality")
23
+
24
+
25
+ # ------------------------------------------------------------------
26
+ # Output helpers — each handles one output format
27
+ # ------------------------------------------------------------------
28
+
29
+
30
+ def _output_safety(safety: "SafetyResult", count: int, *, json_mode: bool, pipe: bool) -> None:
31
+ """Emit safety-only results in the requested format."""
32
+ if json_mode:
33
+ click.echo(json_mod.dumps({
34
+ "files_analyzed": count,
35
+ "safety": safety.to_dict(),
36
+ }, indent=2))
37
+ elif pipe:
38
+ if safety.safe:
39
+ click.echo(f"PASS {count} files scanned, no safety issues")
40
+ else:
41
+ click.echo(f"FAIL {count} files scanned, {len(safety.issues)} safety issue(s)")
42
+ for issue in safety.issues:
43
+ click.echo(f" L{issue.line_number}: {issue.description}")
44
+ else:
45
+ if safety.safe:
46
+ print_success(f"Safety check passed — {count} files scanned")
47
+ else:
48
+ print_error(f"{len(safety.issues)} safety issue(s) found in {count} files")
49
+ for issue in safety.issues:
50
+ console.print(f" [yellow]L{issue.line_number}[/yellow]: {issue.description}")
51
+
52
+
53
+ def _output_report_pipe(report: "QualityReport") -> None:
54
+ """Emit full quality report in pipe-friendly plain text."""
55
+ mi_str = f" MI: {report.maintainability_index:.1f}" if report.maintainability_index is not None else ""
56
+ click.echo(f"Files: {report.files_analyzed} Symbols: {report.symbol_count} Issues: {report.issue_count}{mi_str}")
57
+ for c in report.complexity_issues:
58
+ click.echo(f" COMPLEXITY {c.symbol_name} ({c.file_path}:{c.start_line}) score={c.complexity} [{c.rating}]")
59
+ for d in report.dead_code:
60
+ click.echo(f" DEAD_CODE {d.symbol_name} ({d.file_path}:{d.start_line}) kind={d.kind}")
61
+ for dup in report.duplicates:
62
+ click.echo(f" DUPLICATE {dup.symbol_a} ↔ {dup.symbol_b} sim={dup.similarity:.2f}")
63
+ for b in report.bandit_issues:
64
+ click.echo(f" SECURITY [{b.test_id}] {b.text} ({b.file_path}:{b.line}) sev={b.severity}")
65
+ if report.safety and not report.safety.safe:
66
+ for i in report.safety.issues:
67
+ click.echo(f" SAFETY L{i.line_number}: {i.description}")
68
+
69
+
70
+ def _output_report_rich(report: "QualityReport", root: Path) -> None:
71
+ """Emit full quality report with Rich formatting."""
72
+ console.print(f"\n[bold cyan]Quality Report[/bold cyan] — {root}\n")
73
+ console.print(f" Files analyzed: {report.files_analyzed}")
74
+ console.print(f" Symbols: {report.symbol_count}")
75
+ console.print(f" Issues: {report.issue_count}")
76
+ if report.maintainability_index is not None:
77
+ console.print(f" Maintainability Index: {report.maintainability_index:.1f}")
78
+ console.print()
79
+
80
+ if report.complexity_issues:
81
+ console.print("[bold yellow]High Complexity Functions:[/bold yellow]")
82
+ for c in report.complexity_issues:
83
+ console.print(f" {c.symbol_name} ({c.file_path}:{c.start_line}) — score {c.complexity} [{c.rating}]")
84
+ console.print()
85
+
86
+ if report.dead_code:
87
+ console.print("[bold yellow]Potentially Dead Code:[/bold yellow]")
88
+ for d in report.dead_code:
89
+ console.print(f" {d.symbol_name} ({d.file_path}:{d.start_line}) — {d.kind}")
90
+ console.print()
91
+
92
+ if report.duplicates:
93
+ console.print("[bold yellow]Duplicate Logic:[/bold yellow]")
94
+ for dup in report.duplicates:
95
+ console.print(f" {dup.symbol_a} ↔ {dup.symbol_b} — {dup.similarity:.0%} similar")
96
+ console.print()
97
+
98
+ if report.bandit_issues:
99
+ console.print("[bold red]Security Issues (Bandit):[/bold red]")
100
+ for b in report.bandit_issues:
101
+ console.print(f" [{b.test_id}] {b.text} ({b.file_path}:{b.line}) — {b.severity}")
102
+ console.print()
103
+
104
+ if report.safety and not report.safety.safe:
105
+ console.print("[bold red]Safety Issues:[/bold red]")
106
+ for i in report.safety.issues:
107
+ console.print(f" L{i.line_number}: {i.description}")
108
+ console.print()
109
+
110
+ if report.issue_count == 0:
111
+ print_success("No quality issues found")
112
+
113
+
114
+ @click.command("quality")
115
+ @click.option(
116
+ "--path",
117
+ "-p",
118
+ default=".",
119
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
120
+ help="Project root path.",
121
+ )
122
+ @click.option(
123
+ "--json-output",
124
+ "--json",
125
+ "json_mode",
126
+ is_flag=True,
127
+ default=False,
128
+ help="Output in JSON format.",
129
+ )
130
+ @click.option(
131
+ "--complexity-threshold",
132
+ type=int,
133
+ default=10,
134
+ help="Minimum cyclomatic complexity to report (default: 10).",
135
+ )
136
+ @click.option(
137
+ "--safety-only",
138
+ is_flag=True,
139
+ default=False,
140
+ help="Run only the safety validator (fast mode).",
141
+ )
142
+ @click.option(
143
+ "--pipe",
144
+ is_flag=True,
145
+ default=False,
146
+ help="Plain text output for piping / CI.",
147
+ )
148
+ @click.pass_context
149
+ def quality_cmd(
150
+ ctx: click.Context,
151
+ path: str,
152
+ json_mode: bool,
153
+ complexity_threshold: int,
154
+ safety_only: bool,
155
+ pipe: bool,
156
+ ) -> None:
157
+ """Analyze code quality — complexity, dead code, duplicates, security.
158
+
159
+ Scans the project for quality issues and produces a human-readable or
160
+ JSON report. Useful for CI pipelines and local development.
161
+
162
+ Examples:
163
+
164
+ codexa quality
165
+
166
+ codexa quality --json
167
+
168
+ codexa quality --safety-only --pipe
169
+
170
+ codexa quality --complexity-threshold 15
171
+ """
172
+ from semantic_code_intelligence.ci.quality import analyze_project, QualityReport
173
+ from semantic_code_intelligence.llm.safety import SafetyValidator
174
+
175
+ root = Path(path).resolve()
176
+
177
+ if safety_only:
178
+ # Fast path: only safety scan
179
+ from semantic_code_intelligence.parsing.parser import EXTENSION_TO_LANGUAGE
180
+ code = ""
181
+ count = 0
182
+ for f in root.rglob("*"):
183
+ if f.is_file() and f.suffix in EXTENSION_TO_LANGUAGE:
184
+ parts = f.relative_to(root).parts
185
+ if any(p.startswith(".") or p in ("__pycache__", "node_modules") for p in parts):
186
+ continue
187
+ try:
188
+ code += f.read_text(encoding="utf-8", errors="replace") + "\n"
189
+ count += 1
190
+ except Exception:
191
+ logger.debug("Skipping unreadable file: %s", f)
192
+ validator = SafetyValidator()
193
+ safety = validator.validate(code)
194
+ _output_safety(safety, count, json_mode=json_mode, pipe=pipe)
195
+ return
196
+
197
+ # Full quality analysis
198
+ report = analyze_project(
199
+ root,
200
+ complexity_threshold=complexity_threshold,
201
+ )
202
+
203
+ if json_mode:
204
+ click.echo(json_mod.dumps(report.to_dict(), indent=2))
205
+ elif pipe:
206
+ _output_report_pipe(report)
207
+ else:
208
+ _output_report_rich(report, root)