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,454 @@
1
+ """Interactive TUI — full-featured terminal user interface for code search.
2
+
3
+ Provides a split-pane search interface with:
4
+ - Live search input
5
+ - Scrollable results list
6
+ - Syntax-highlighted file preview
7
+ - Mode switching (semantic/keyword/regex/hybrid)
8
+
9
+ Uses ``textual`` when available; falls back to a simple REPL otherwise.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from semantic_code_intelligence.config.settings import AppConfig, load_config
19
+ from semantic_code_intelligence.services.search_service import SearchMode, search_codebase
20
+ from semantic_code_intelligence.utils.logging import get_logger
21
+
22
+ logger = get_logger("tui")
23
+
24
+
25
+ def _textual_available() -> bool:
26
+ """Check if the textual library is installed."""
27
+ try:
28
+ import textual # noqa: F401
29
+ return True
30
+ except ImportError:
31
+ return False
32
+
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Textual TUI (rich split-pane interface)
36
+ # ---------------------------------------------------------------------------
37
+
38
+ def _run_textual_tui(
39
+ project_root: Path,
40
+ mode: SearchMode = "hybrid",
41
+ top_k: int = 10,
42
+ ) -> None:
43
+ """Run the full Textual TUI."""
44
+ from textual.app import App, ComposeResult
45
+ from textual.binding import Binding
46
+ from textual.containers import Horizontal, Vertical
47
+ from textual.widgets import (
48
+ Footer,
49
+ Header,
50
+ Input,
51
+ Label,
52
+ ListItem,
53
+ ListView,
54
+ Static,
55
+ )
56
+
57
+ class ResultItem(ListItem):
58
+ """A single search result row."""
59
+
60
+ def __init__(self, result: Any, index: int) -> None:
61
+ self.result = result
62
+ self.result_index = index
63
+ path = Path(result.file_path).name
64
+ label = f"#{index} [{result.score:.3f}] {path}:L{result.start_line}-{result.end_line} ({result.language})"
65
+ super().__init__(Label(label), id=f"result-{index}")
66
+
67
+ class CodexaTUI(App):
68
+ """CodexA Interactive Search TUI."""
69
+
70
+ TITLE = "CodexA Search"
71
+ CSS = """
72
+ Screen {
73
+ layout: vertical;
74
+ }
75
+ #search-bar {
76
+ dock: top;
77
+ height: 3;
78
+ padding: 0 1;
79
+ border: tall $accent;
80
+ }
81
+ #mode-bar {
82
+ dock: top;
83
+ height: 1;
84
+ color: $text-muted;
85
+ padding: 0 2;
86
+ background: $surface;
87
+ }
88
+ #main-pane {
89
+ height: 1fr;
90
+ }
91
+ #results-pane {
92
+ width: 2fr;
93
+ min-width: 30;
94
+ border-right: solid $primary;
95
+ }
96
+ #preview-pane {
97
+ width: 3fr;
98
+ min-width: 40;
99
+ overflow-y: auto;
100
+ padding: 0 1;
101
+ }
102
+ #preview-content {
103
+ width: 1fr;
104
+ }
105
+ #status-bar {
106
+ dock: bottom;
107
+ height: 1;
108
+ color: $text-muted;
109
+ padding: 0 2;
110
+ background: $surface;
111
+ }
112
+ ResultItem {
113
+ height: auto;
114
+ padding: 0 1;
115
+ }
116
+ ResultItem:hover {
117
+ background: $boost;
118
+ }
119
+ ListView > ResultItem.-active {
120
+ background: $accent 30%;
121
+ }
122
+ """
123
+
124
+ BINDINGS = [
125
+ Binding("ctrl+q", "quit", "Quit", show=True),
126
+ Binding("ctrl+m", "cycle_mode", "Mode", show=True),
127
+ Binding("ctrl+k", "increase_topk", "K+", show=True),
128
+ Binding("ctrl+j", "decrease_topk", "K-", show=True),
129
+ Binding("escape", "clear_search", "Clear", show=True),
130
+ ]
131
+
132
+ def __init__(self) -> None:
133
+ super().__init__()
134
+ self.current_mode: SearchMode = mode
135
+ self.current_top_k: int = top_k
136
+ self.current_results: list[Any] = []
137
+ self.project_root = project_root
138
+
139
+ def compose(self) -> ComposeResult:
140
+ yield Header()
141
+ yield Input(placeholder="Type a search query...", id="search-bar")
142
+ yield Label(
143
+ f" Mode: {self.current_mode} | Top-K: {self.current_top_k} | Project: {project_root.name}",
144
+ id="mode-bar",
145
+ )
146
+ with Horizontal(id="main-pane"):
147
+ with Vertical(id="results-pane"):
148
+ yield ListView(id="results-list")
149
+ yield Static("Select a result to preview code...", id="preview-pane")
150
+ yield Label(" Ready", id="status-bar")
151
+ yield Footer()
152
+
153
+ async def on_input_submitted(self, event: Input.Submitted) -> None:
154
+ query = event.value.strip()
155
+ if not query:
156
+ return
157
+ status = self.query_one("#status-bar", Label)
158
+ status.update(f" Searching: {query!r} (mode={self.current_mode})...")
159
+ try:
160
+ results = search_codebase(
161
+ query=query,
162
+ project_root=self.project_root,
163
+ top_k=self.current_top_k,
164
+ mode=self.current_mode,
165
+ auto_index=True,
166
+ )
167
+ self.current_results = results
168
+ lv = self.query_one("#results-list", ListView)
169
+ await lv.clear()
170
+ for i, r in enumerate(results, 1):
171
+ await lv.append(ResultItem(r, i))
172
+ status.update(f" Found {len(results)} results for: {query!r}")
173
+ # Show preview of first result
174
+ if results:
175
+ self._show_preview(results[0])
176
+ except FileNotFoundError:
177
+ status.update(" Index not found. Run 'codexa index' first.")
178
+ except Exception as e:
179
+ status.update(f" Error: {e}")
180
+
181
+ def on_list_view_selected(self, event: ListView.Selected) -> None:
182
+ item = event.item
183
+ if isinstance(item, ResultItem):
184
+ self._show_preview(item.result)
185
+
186
+ def on_list_view_highlighted(self, event: ListView.Highlighted) -> None:
187
+ item = event.item
188
+ if isinstance(item, ResultItem):
189
+ self._show_preview(item.result)
190
+
191
+ def _show_preview(self, result: Any) -> None:
192
+ """Render syntax-highlighted code in the preview pane."""
193
+ lines = result.content.splitlines()
194
+ numbered = []
195
+ for i, line in enumerate(lines, start=result.start_line):
196
+ numbered.append(f"{i:>5} | {line}")
197
+ header = f" {result.file_path} L{result.start_line}-{result.end_line} ({result.language})\n"
198
+ separator = " " + "-" * 60 + "\n"
199
+ code_text = header + separator + "\n".join(numbered)
200
+ preview = self.query_one("#preview-pane", Static)
201
+ preview.update(code_text)
202
+
203
+ def action_cycle_mode(self) -> None:
204
+ modes: list[SearchMode] = ["semantic", "keyword", "regex", "hybrid"]
205
+ idx = modes.index(self.current_mode)
206
+ self.current_mode = modes[(idx + 1) % len(modes)]
207
+ self._update_mode_bar()
208
+ status = self.query_one("#status-bar", Label)
209
+ status.update(f" Mode changed to: {self.current_mode}")
210
+
211
+ def action_increase_topk(self) -> None:
212
+ self.current_top_k = min(self.current_top_k + 5, 50)
213
+ self._update_mode_bar()
214
+ status = self.query_one("#status-bar", Label)
215
+ status.update(f" Top-K set to: {self.current_top_k}")
216
+
217
+ def action_decrease_topk(self) -> None:
218
+ self.current_top_k = max(self.current_top_k - 5, 5)
219
+ self._update_mode_bar()
220
+ status = self.query_one("#status-bar", Label)
221
+ status.update(f" Top-K set to: {self.current_top_k}")
222
+
223
+ def _update_mode_bar(self) -> None:
224
+ mode_bar = self.query_one("#mode-bar", Label)
225
+ mode_bar.update(
226
+ f" Mode: {self.current_mode} | Top-K: {self.current_top_k} | Project: {project_root.name}"
227
+ )
228
+
229
+ def action_clear_search(self) -> None:
230
+ search_input = self.query_one("#search-bar", Input)
231
+ search_input.value = ""
232
+ search_input.focus()
233
+
234
+ app = CodexaTUI()
235
+ app.run()
236
+
237
+
238
+ # ---------------------------------------------------------------------------
239
+ # Fallback REPL (no textual dependency)
240
+ # ---------------------------------------------------------------------------
241
+
242
+
243
+ def _format_result_line(i: int, r: Any) -> str:
244
+ """Format a single result as a compact line for TUI display."""
245
+ path = r.file_path
246
+ try:
247
+ path = str(Path(r.file_path).name)
248
+ except Exception:
249
+ pass
250
+ return f" {i:>3}. [{r.score:.3f}] {path}:L{r.start_line}-{r.end_line} {r.language}"
251
+
252
+
253
+ def _print_results(results: list[Any], query: str) -> None:
254
+ """Print results in a compact format."""
255
+ try:
256
+ from rich.console import Console as RichConsole
257
+ from rich.table import Table
258
+ c = RichConsole()
259
+ if not results:
260
+ c.print(f"\n [dim]No results for:[/dim] {query!r}\n")
261
+ return
262
+ c.print(f"\n [bold green]{len(results)}[/bold green] results for: [cyan]{query!r}[/cyan]\n")
263
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 1))
264
+ table.add_column("#", style="dim", width=4)
265
+ table.add_column("Score", style="yellow", width=7)
266
+ table.add_column("File", style="cyan")
267
+ table.add_column("Lines", style="dim")
268
+ table.add_column("Lang", style="magenta")
269
+ for i, r in enumerate(results, 1):
270
+ path = r.file_path
271
+ try:
272
+ path = str(Path(r.file_path).name)
273
+ except Exception:
274
+ pass
275
+ table.add_row(str(i), f"{r.score:.3f}", path, f"L{r.start_line}-{r.end_line}", r.language)
276
+ c.print(table)
277
+ c.print()
278
+ except ImportError:
279
+ if not results:
280
+ print(f"\n No results for: {query!r}\n")
281
+ return
282
+ print(f"\n Found {len(results)} results for: {query!r}\n")
283
+ for i, r in enumerate(results, 1):
284
+ print(_format_result_line(i, r))
285
+ print()
286
+
287
+
288
+ def _show_detail(results: list[Any], index: int) -> None:
289
+ """Show full content of a result."""
290
+ if index < 1 or index > len(results):
291
+ print(f" Invalid selection. Enter 1-{len(results)}.")
292
+ return
293
+ r = results[index - 1]
294
+ try:
295
+ from rich.console import Console as RichConsole
296
+ from rich.syntax import Syntax
297
+ c = RichConsole()
298
+ c.print(f"\n [bold cyan]{r.file_path}[/bold cyan] L{r.start_line}-{r.end_line}\n")
299
+ c.rule(style="dim")
300
+ lang = getattr(r, "language", "python") or "text"
301
+ c.print(Syntax(r.content, lang, theme="monokai", line_numbers=True, start_line=r.start_line))
302
+ c.print()
303
+ except ImportError:
304
+ print(f"\n === {r.file_path} L{r.start_line}-{r.end_line} ===\n")
305
+ for i, line in enumerate(r.content.splitlines(), start=r.start_line):
306
+ print(f" {i:>5} | {line}")
307
+ print()
308
+
309
+
310
+ def _run_fallback_repl(
311
+ project_root: Path,
312
+ mode: SearchMode = "hybrid",
313
+ top_k: int = 10,
314
+ ) -> None:
315
+ """Run the simple fallback REPL (no textual)."""
316
+ project_root = project_root.resolve()
317
+
318
+ try:
319
+ from rich.console import Console as RichConsole
320
+ c = RichConsole()
321
+ try:
322
+ c.rule("[bold cyan]CodexA Interactive Search[/bold cyan]", style="cyan")
323
+ c.print(f" [dim]Mode:[/dim] {mode} [dim]Top-K:[/dim] {top_k} [dim]Project:[/dim] {project_root.name}")
324
+ c.print(" [dim]Commands:[/dim] /mode <m> /topk <n> /view <n> /explain <sym> /help /quit")
325
+ c.rule(style="dim")
326
+ c.print()
327
+ except (UnicodeEncodeError, OSError):
328
+ print(f"\n CodexA Interactive Search (mode={mode}, top_k={top_k})")
329
+ print(f" Project: {project_root}")
330
+ print(" Commands: /mode <m> /topk <n> /view <n> /explain <sym> /help /quit\n")
331
+ except ImportError:
332
+ print(f"\n CodexA Interactive Search (mode={mode}, top_k={top_k})")
333
+ print(f" Project: {project_root}")
334
+ print(" Commands: /mode <m> /topk <n> /view <n> /explain <sym> /help /quit\n")
335
+
336
+ current_mode: SearchMode = mode
337
+ current_top_k: int = top_k
338
+ last_results: list[Any] = []
339
+
340
+ while True:
341
+ try:
342
+ line = input(" search> ").strip()
343
+ except (EOFError, KeyboardInterrupt):
344
+ print("\n Bye!\n")
345
+ break
346
+
347
+ if not line:
348
+ continue
349
+
350
+ if line.lower() in ("/quit", "/exit", "/q"):
351
+ print(" Bye!\n")
352
+ break
353
+
354
+ if line.lower() == "/help":
355
+ print(" Commands:")
356
+ print(" /mode <semantic|keyword|regex|hybrid> — change search mode")
357
+ print(" /topk <n> — set number of results")
358
+ print(" /view <n> — view result details")
359
+ print(" /explain <symbol> — explain a symbol")
360
+ print(" /quit — exit")
361
+ print()
362
+ continue
363
+
364
+ if line.lower().startswith("/mode "):
365
+ new_mode = line.split(maxsplit=1)[1].lower()
366
+ if new_mode in ("semantic", "keyword", "regex", "hybrid"):
367
+ current_mode = new_mode # type: ignore[assignment]
368
+ print(f" Mode set to: {current_mode}")
369
+ else:
370
+ print(" Valid modes: semantic, keyword, regex, hybrid")
371
+ continue
372
+
373
+ if line.lower().startswith("/topk "):
374
+ try:
375
+ current_top_k = max(1, min(50, int(line.split()[1])))
376
+ print(f" Top-K set to: {current_top_k}")
377
+ except (ValueError, IndexError):
378
+ print(" Usage: /topk <number>")
379
+ continue
380
+
381
+ if line.lower().startswith("/view "):
382
+ try:
383
+ idx = int(line.split()[1])
384
+ _show_detail(last_results, idx)
385
+ except (ValueError, IndexError):
386
+ print(" Usage: /view <number>")
387
+ continue
388
+
389
+ if line.lower().startswith("/explain "):
390
+ symbol_name = line.split(maxsplit=1)[1].strip()
391
+ try:
392
+ from semantic_code_intelligence.tools.executor import ToolExecutor
393
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
394
+ executor = ToolExecutor(project_root)
395
+ inv = ToolInvocation(tool_name="explain_symbol", arguments={"symbol_name": symbol_name})
396
+ result = executor.execute(inv)
397
+ if result.success and result.result_payload:
398
+ payload = result.result_payload
399
+ explanation = payload.get("explanation", "No explanation available.")
400
+ try:
401
+ from rich.console import Console as RichConsole
402
+ from rich.panel import Panel
403
+ rc = RichConsole()
404
+ rc.print(Panel(explanation, title=symbol_name, border_style="cyan"))
405
+ except ImportError:
406
+ print(f"\n {symbol_name}: {explanation}\n")
407
+ else:
408
+ err = result.error.error_message if result.error else "Unknown error"
409
+ print(f" Error: {err}")
410
+ except Exception as e:
411
+ print(f" Explain failed: {e}")
412
+ continue
413
+
414
+ # Execute search
415
+ try:
416
+ results = search_codebase(
417
+ query=line,
418
+ project_root=project_root,
419
+ top_k=current_top_k,
420
+ mode=current_mode,
421
+ auto_index=True,
422
+ )
423
+ last_results = results
424
+ _print_results(results, line)
425
+ except FileNotFoundError:
426
+ print(" Index not found. Run 'codexa index' first.")
427
+ except Exception as e:
428
+ print(f" Search error: {e}")
429
+
430
+
431
+ # ---------------------------------------------------------------------------
432
+ # Public API
433
+ # ---------------------------------------------------------------------------
434
+
435
+
436
+ def run_tui(
437
+ project_root: Path,
438
+ mode: SearchMode = "hybrid",
439
+ top_k: int = 10,
440
+ ) -> None:
441
+ """Run the interactive TUI search interface.
442
+
443
+ Uses the full Textual split-pane TUI when ``textual`` is installed,
444
+ otherwise falls back to a simple input-loop REPL.
445
+
446
+ Args:
447
+ project_root: Project root directory.
448
+ mode: Default search mode.
449
+ top_k: Number of results per query.
450
+ """
451
+ if _textual_available():
452
+ _run_textual_tui(project_root, mode=mode, top_k=top_k)
453
+ else:
454
+ _run_fallback_repl(project_root, mode=mode, top_k=top_k)
File without changes
@@ -0,0 +1,112 @@
1
+ """Logging utilities for Semantic Code Intelligence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import sys
7
+ from typing import Optional
8
+
9
+ from rich.console import Console
10
+ from rich.logging import RichHandler
11
+ from rich.theme import Theme
12
+
13
+ # Detect whether the console supports Unicode safely
14
+ _UNICODE_SAFE = sys.stdout.encoding and sys.stdout.encoding.lower().startswith("utf")
15
+
16
+ _ICON_SUCCESS = "\u2713" if _UNICODE_SAFE else "OK"
17
+ _ICON_ERROR = "\u2717" if _UNICODE_SAFE else "!!"
18
+ _ICON_WARNING = "\u26a0" if _UNICODE_SAFE else "!!"
19
+ _ICON_INFO = "\u2139" if _UNICODE_SAFE else "--"
20
+
21
+ # Shared console instances
22
+ _theme = Theme(
23
+ {
24
+ "info": "cyan",
25
+ "warning": "yellow",
26
+ "error": "bold red",
27
+ "success": "bold green",
28
+ "highlight": "bold magenta",
29
+ }
30
+ )
31
+
32
+ console = Console(theme=_theme)
33
+ error_console = Console(stderr=True, theme=_theme)
34
+
35
+
36
+ def setup_logging(verbose: bool = False) -> logging.Logger:
37
+ """Configure and return the application logger with Rich formatting.
38
+
39
+ Args:
40
+ verbose: If True, set log level to DEBUG. Otherwise INFO.
41
+
42
+ Returns:
43
+ Configured logger instance.
44
+ """
45
+ level = logging.DEBUG if verbose else logging.INFO
46
+
47
+ logging.basicConfig(
48
+ level=level,
49
+ format="%(message)s",
50
+ datefmt="[%X]",
51
+ handlers=[
52
+ RichHandler(
53
+ console=error_console,
54
+ rich_tracebacks=True,
55
+ show_path=verbose,
56
+ markup=False,
57
+ )
58
+ ],
59
+ force=True,
60
+ )
61
+
62
+ logger = logging.getLogger("codexa")
63
+ logger.setLevel(level)
64
+ return logger
65
+
66
+
67
+ def get_logger(name: Optional[str] = None) -> logging.Logger:
68
+ """Get a child logger under the 'codexa' namespace.
69
+
70
+ Args:
71
+ name: Optional sub-logger name. If None, returns the root codexa logger.
72
+ """
73
+ base = "codexa"
74
+ if name:
75
+ return logging.getLogger(f"{base}.{name}")
76
+ return logging.getLogger(base)
77
+
78
+
79
+ def print_success(message: str) -> None:
80
+ """Print a success message to the console."""
81
+ console.print(f"[success]{_ICON_SUCCESS}[/success] {message}")
82
+
83
+
84
+ def print_error(message: str) -> None:
85
+ """Print an error message to stderr."""
86
+ error_console.print(f"[error]{_ICON_ERROR}[/error] {message}")
87
+
88
+
89
+ def print_warning(message: str) -> None:
90
+ """Print a warning message to the console."""
91
+ console.print(f"[warning]{_ICON_WARNING}[/warning] {message}")
92
+
93
+
94
+ def print_info(message: str) -> None:
95
+ """Print an info message to the console."""
96
+ console.print(f"[info]{_ICON_INFO}[/info] {message}")
97
+
98
+
99
+ def print_separator(title: str = "", style: str = "dim") -> None:
100
+ """Print a visual separator line, optionally with a centered title."""
101
+ if title:
102
+ console.rule(f"[bold]{title}[/bold]", style=style)
103
+ else:
104
+ console.rule(style=style)
105
+
106
+
107
+ def print_header(title: str, subtitle: str = "") -> None:
108
+ """Print a styled section header."""
109
+ console.print(f"\n[bold cyan]{title}[/bold cyan]")
110
+ if subtitle:
111
+ console.print(f" [dim]{subtitle}[/dim]")
112
+ console.print()
@@ -0,0 +1,3 @@
1
+ """Single source of truth for CodexA version."""
2
+
3
+ __version__ = "0.4.0"
@@ -0,0 +1,11 @@
1
+ """Web interface and REST API layer for CodexA.
2
+
3
+ Provides an optional lightweight web UI and developer-friendly REST API
4
+ that wraps existing CodexA services. Built on the Python standard library
5
+ (``http.server``) — no framework dependencies.
6
+
7
+ Components:
8
+ api — REST API endpoints (search, ask, analyze, health)
9
+ ui — Server-rendered HTML interface for browser access
10
+ visualize — Mermaid-compatible text graph generators
11
+ """