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.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- 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,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
|
+
"""
|