ai-coding-assistant 0.5.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.
- ai_coding_assistant-0.5.0.dist-info/METADATA +226 -0
- ai_coding_assistant-0.5.0.dist-info/RECORD +89 -0
- ai_coding_assistant-0.5.0.dist-info/WHEEL +4 -0
- ai_coding_assistant-0.5.0.dist-info/entry_points.txt +3 -0
- ai_coding_assistant-0.5.0.dist-info/licenses/LICENSE +21 -0
- coding_assistant/__init__.py +3 -0
- coding_assistant/__main__.py +19 -0
- coding_assistant/cli/__init__.py +1 -0
- coding_assistant/cli/app.py +158 -0
- coding_assistant/cli/commands/__init__.py +19 -0
- coding_assistant/cli/commands/ask.py +178 -0
- coding_assistant/cli/commands/config.py +438 -0
- coding_assistant/cli/commands/diagram.py +267 -0
- coding_assistant/cli/commands/document.py +410 -0
- coding_assistant/cli/commands/explain.py +192 -0
- coding_assistant/cli/commands/fix.py +249 -0
- coding_assistant/cli/commands/index.py +162 -0
- coding_assistant/cli/commands/refactor.py +245 -0
- coding_assistant/cli/commands/search.py +182 -0
- coding_assistant/cli/commands/serve_docs.py +128 -0
- coding_assistant/cli/repl.py +381 -0
- coding_assistant/cli/theme.py +90 -0
- coding_assistant/codebase/__init__.py +1 -0
- coding_assistant/codebase/crawler.py +93 -0
- coding_assistant/codebase/parser.py +266 -0
- coding_assistant/config/__init__.py +25 -0
- coding_assistant/config/config_manager.py +615 -0
- coding_assistant/config/settings.py +82 -0
- coding_assistant/context/__init__.py +19 -0
- coding_assistant/context/chunker.py +443 -0
- coding_assistant/context/enhanced_retriever.py +322 -0
- coding_assistant/context/hybrid_search.py +311 -0
- coding_assistant/context/ranker.py +355 -0
- coding_assistant/context/retriever.py +119 -0
- coding_assistant/context/window.py +362 -0
- coding_assistant/documentation/__init__.py +23 -0
- coding_assistant/documentation/agents/__init__.py +27 -0
- coding_assistant/documentation/agents/coordinator.py +510 -0
- coding_assistant/documentation/agents/module_documenter.py +111 -0
- coding_assistant/documentation/agents/synthesizer.py +139 -0
- coding_assistant/documentation/agents/task_delegator.py +100 -0
- coding_assistant/documentation/decomposition/__init__.py +21 -0
- coding_assistant/documentation/decomposition/context_preserver.py +477 -0
- coding_assistant/documentation/decomposition/module_detector.py +302 -0
- coding_assistant/documentation/decomposition/partitioner.py +621 -0
- coding_assistant/documentation/generators/__init__.py +14 -0
- coding_assistant/documentation/generators/dataflow_generator.py +440 -0
- coding_assistant/documentation/generators/diagram_generator.py +511 -0
- coding_assistant/documentation/graph/__init__.py +13 -0
- coding_assistant/documentation/graph/dependency_builder.py +468 -0
- coding_assistant/documentation/graph/module_analyzer.py +475 -0
- coding_assistant/documentation/writers/__init__.py +11 -0
- coding_assistant/documentation/writers/markdown_writer.py +322 -0
- coding_assistant/embeddings/__init__.py +0 -0
- coding_assistant/embeddings/generator.py +89 -0
- coding_assistant/embeddings/store.py +187 -0
- coding_assistant/exceptions/__init__.py +50 -0
- coding_assistant/exceptions/base.py +110 -0
- coding_assistant/exceptions/llm.py +249 -0
- coding_assistant/exceptions/recovery.py +263 -0
- coding_assistant/exceptions/storage.py +213 -0
- coding_assistant/exceptions/validation.py +230 -0
- coding_assistant/llm/__init__.py +1 -0
- coding_assistant/llm/client.py +277 -0
- coding_assistant/llm/gemini_client.py +181 -0
- coding_assistant/llm/groq_client.py +160 -0
- coding_assistant/llm/prompts.py +98 -0
- coding_assistant/llm/together_client.py +160 -0
- coding_assistant/operations/__init__.py +13 -0
- coding_assistant/operations/differ.py +369 -0
- coding_assistant/operations/generator.py +347 -0
- coding_assistant/operations/linter.py +430 -0
- coding_assistant/operations/validator.py +406 -0
- coding_assistant/storage/__init__.py +9 -0
- coding_assistant/storage/database.py +363 -0
- coding_assistant/storage/session.py +231 -0
- coding_assistant/utils/__init__.py +31 -0
- coding_assistant/utils/cache.py +477 -0
- coding_assistant/utils/hardware.py +132 -0
- coding_assistant/utils/keystore.py +206 -0
- coding_assistant/utils/logger.py +32 -0
- coding_assistant/utils/progress.py +311 -0
- coding_assistant/validation/__init__.py +13 -0
- coding_assistant/validation/files.py +305 -0
- coding_assistant/validation/inputs.py +335 -0
- coding_assistant/validation/params.py +280 -0
- coding_assistant/validation/sanitizers.py +243 -0
- coding_assistant/vcs/__init__.py +5 -0
- coding_assistant/vcs/git.py +269 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Refactor command - Refactor code with validation and preview."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
from rich.syntax import Syntax
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from coding_assistant.config.settings import settings
|
|
11
|
+
from coding_assistant.context.enhanced_retriever import EnhancedSemanticRetriever
|
|
12
|
+
from coding_assistant.operations.generator import CodeGenerator
|
|
13
|
+
from coding_assistant.operations.validator import SyntaxValidator
|
|
14
|
+
from coding_assistant.operations.linter import LinterIntegration
|
|
15
|
+
from coding_assistant.operations.differ import DiffGenerator
|
|
16
|
+
from coding_assistant.llm.client import LLMClientFactory
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def refactor_command(
|
|
22
|
+
target: str = typer.Argument(..., help="File path or file::function to refactor"),
|
|
23
|
+
strategy: str = typer.Option(
|
|
24
|
+
"improve readability and efficiency",
|
|
25
|
+
"--strategy",
|
|
26
|
+
"-s",
|
|
27
|
+
help="Refactoring strategy"
|
|
28
|
+
),
|
|
29
|
+
auto_apply: bool = typer.Option(
|
|
30
|
+
False,
|
|
31
|
+
"--auto-apply",
|
|
32
|
+
"-y",
|
|
33
|
+
help="Apply changes without confirmation"
|
|
34
|
+
),
|
|
35
|
+
run_linter: bool = typer.Option(
|
|
36
|
+
True,
|
|
37
|
+
"--lint/--no-lint",
|
|
38
|
+
help="Run linter on refactored code"
|
|
39
|
+
),
|
|
40
|
+
):
|
|
41
|
+
"""Refactor code with AI assistance, validation, and preview.
|
|
42
|
+
|
|
43
|
+
The refactoring process:
|
|
44
|
+
1. Read original code
|
|
45
|
+
2. Get LLM refactoring suggestion
|
|
46
|
+
3. Extract and validate refactored code
|
|
47
|
+
4. Run linter (optional)
|
|
48
|
+
5. Show diff preview
|
|
49
|
+
6. Ask for confirmation (unless --auto-apply)
|
|
50
|
+
7. Apply changes
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
assistant refactor src/utils.py
|
|
54
|
+
assistant refactor src/auth.py --strategy "extract helper functions"
|
|
55
|
+
assistant refactor src/module.py::process_data --auto-apply
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
console.print("\n🔧 [bold cyan]Code Refactoring[/bold cyan]\n")
|
|
59
|
+
|
|
60
|
+
# Parse target
|
|
61
|
+
if '::' in target:
|
|
62
|
+
file_path_str, function_name = target.split('::', 1)
|
|
63
|
+
else:
|
|
64
|
+
file_path_str = target
|
|
65
|
+
function_name = None
|
|
66
|
+
|
|
67
|
+
file_path = Path(file_path_str)
|
|
68
|
+
|
|
69
|
+
# Check file exists
|
|
70
|
+
if not file_path.exists():
|
|
71
|
+
console.print(f"[red]✗ File not found: {file_path}[/red]")
|
|
72
|
+
raise typer.Exit(1)
|
|
73
|
+
|
|
74
|
+
# Detect language
|
|
75
|
+
language_map = {
|
|
76
|
+
'.py': 'python',
|
|
77
|
+
'.js': 'javascript',
|
|
78
|
+
'.ts': 'typescript',
|
|
79
|
+
'.jsx': 'jsx',
|
|
80
|
+
'.tsx': 'tsx'
|
|
81
|
+
}
|
|
82
|
+
language = language_map.get(file_path.suffix, 'python')
|
|
83
|
+
|
|
84
|
+
# Read original code
|
|
85
|
+
try:
|
|
86
|
+
original_code = file_path.read_text(encoding='utf-8')
|
|
87
|
+
except Exception as e:
|
|
88
|
+
console.print(f"[red]✗ Error reading file: {e}[/red]")
|
|
89
|
+
raise typer.Exit(1)
|
|
90
|
+
|
|
91
|
+
# If function specified, extract it
|
|
92
|
+
target_code = original_code
|
|
93
|
+
if function_name:
|
|
94
|
+
# TODO: Extract specific function
|
|
95
|
+
console.print(f"[dim]Targeting function: {function_name}[/dim]")
|
|
96
|
+
|
|
97
|
+
# Show original code preview
|
|
98
|
+
console.print("[bold]Original Code:[/bold]")
|
|
99
|
+
console.print(Panel(
|
|
100
|
+
Syntax(original_code[:300] + ("..." if len(original_code) > 300 else ""),
|
|
101
|
+
language, theme="monokai", line_numbers=True),
|
|
102
|
+
title=str(file_path),
|
|
103
|
+
border_style="dim"
|
|
104
|
+
))
|
|
105
|
+
console.print()
|
|
106
|
+
|
|
107
|
+
# Get context for better refactoring
|
|
108
|
+
context_chunks = []
|
|
109
|
+
try:
|
|
110
|
+
retriever = EnhancedSemanticRetriever(settings.project_path)
|
|
111
|
+
stats = retriever.get_stats()
|
|
112
|
+
|
|
113
|
+
if stats['total_chunks'] > 0:
|
|
114
|
+
with console.status("[bold green]Finding related code for context..."):
|
|
115
|
+
results = retriever.retrieve(
|
|
116
|
+
query=f"code related to {file_path.name}",
|
|
117
|
+
k=3,
|
|
118
|
+
current_file=str(file_path),
|
|
119
|
+
language=language
|
|
120
|
+
)
|
|
121
|
+
context_chunks = results
|
|
122
|
+
except:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
# Build refactoring prompt
|
|
126
|
+
with console.status("[bold green]Generating refactoring suggestions..."):
|
|
127
|
+
llm = LLMClientFactory.create_client(settings.llm_provider)
|
|
128
|
+
|
|
129
|
+
system_prompt = f"""You are an expert code refactoring assistant.
|
|
130
|
+
|
|
131
|
+
Your task: Refactor the provided {language} code according to the strategy: "{strategy}"
|
|
132
|
+
|
|
133
|
+
Guidelines:
|
|
134
|
+
- Preserve functionality (same inputs → same outputs)
|
|
135
|
+
- Improve code quality, readability, and efficiency
|
|
136
|
+
- Follow {language} best practices
|
|
137
|
+
- Add helpful comments where appropriate
|
|
138
|
+
- Return ONLY the refactored code in a code block
|
|
139
|
+
- Do not include explanations outside the code block
|
|
140
|
+
|
|
141
|
+
Strategy: {strategy}"""
|
|
142
|
+
|
|
143
|
+
user_prompt = f"""Refactor this {language} code:
|
|
144
|
+
|
|
145
|
+
```{language}
|
|
146
|
+
{target_code}
|
|
147
|
+
```
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
if context_chunks:
|
|
151
|
+
user_prompt += "\n\nRelated code for context:\n"
|
|
152
|
+
for chunk in context_chunks[:2]:
|
|
153
|
+
user_prompt += f"\n```{chunk.get('language', language)}\n{chunk['content'][:200]}...\n```\n"
|
|
154
|
+
|
|
155
|
+
messages = [
|
|
156
|
+
{"role": "system", "content": system_prompt},
|
|
157
|
+
{"role": "user", "content": user_prompt}
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
# Get refactored code
|
|
161
|
+
try:
|
|
162
|
+
response = ""
|
|
163
|
+
for chunk in llm.generate(messages, stream=False):
|
|
164
|
+
response += chunk
|
|
165
|
+
except Exception as e:
|
|
166
|
+
console.print(f"[red]✗ Error generating refactoring: {e}[/red]")
|
|
167
|
+
raise typer.Exit(1)
|
|
168
|
+
|
|
169
|
+
# Extract code blocks
|
|
170
|
+
console.print("[bold green]✓[/bold green] Refactoring generated\n")
|
|
171
|
+
|
|
172
|
+
generator = CodeGenerator()
|
|
173
|
+
blocks = generator.extract_code_blocks(response, validate=True)
|
|
174
|
+
|
|
175
|
+
if not blocks:
|
|
176
|
+
console.print("[red]✗ No code found in refactoring response[/red]")
|
|
177
|
+
console.print("[dim]Response was:[/dim]")
|
|
178
|
+
console.print(response[:500])
|
|
179
|
+
raise typer.Exit(1)
|
|
180
|
+
|
|
181
|
+
refactored_code = blocks[0].code
|
|
182
|
+
|
|
183
|
+
# Validate syntax
|
|
184
|
+
console.print("[bold]Validating refactored code...[/bold]")
|
|
185
|
+
validator = SyntaxValidator()
|
|
186
|
+
validation_result = validator.validate(refactored_code, language)
|
|
187
|
+
|
|
188
|
+
if not validation_result.is_valid:
|
|
189
|
+
console.print(f"[red]✗ Refactored code has syntax errors:[/red]")
|
|
190
|
+
console.print(f"[red] Line {validation_result.line}: {validation_result.error_message}[/red]")
|
|
191
|
+
console.print("\n[yellow]Refactoring failed. Original code unchanged.[/yellow]\n")
|
|
192
|
+
raise typer.Exit(1)
|
|
193
|
+
|
|
194
|
+
console.print("[green]✓ Syntax valid[/green]\n")
|
|
195
|
+
|
|
196
|
+
# Run linter if requested
|
|
197
|
+
if run_linter:
|
|
198
|
+
console.print("[bold]Running linter...[/bold]")
|
|
199
|
+
linter = LinterIntegration(project_root=str(settings.project_path))
|
|
200
|
+
lint_result = linter.lint(refactored_code, language, auto_fix=True)
|
|
201
|
+
|
|
202
|
+
if lint_result.error_count > 0:
|
|
203
|
+
console.print(f"[yellow]⚠️ Found {lint_result.error_count} linting issues[/yellow]")
|
|
204
|
+
linter.display_issues(lint_result)
|
|
205
|
+
|
|
206
|
+
if lint_result.fixed_code:
|
|
207
|
+
console.print("[green]✓ Auto-fixed linting issues[/green]")
|
|
208
|
+
refactored_code = lint_result.fixed_code
|
|
209
|
+
else:
|
|
210
|
+
console.print("[green]✓ No linting issues[/green]")
|
|
211
|
+
console.print()
|
|
212
|
+
|
|
213
|
+
# Generate and show diff
|
|
214
|
+
console.print("[bold]Changes Preview:[/bold]\n")
|
|
215
|
+
differ = DiffGenerator()
|
|
216
|
+
diff = differ.generate_diff(original_code, refactored_code, str(file_path))
|
|
217
|
+
differ.display_diff(diff, title=f"Refactoring: {file_path}")
|
|
218
|
+
|
|
219
|
+
stats = differ.get_change_stats(diff)
|
|
220
|
+
console.print(f"\n[bold]Change Summary:[/bold]")
|
|
221
|
+
console.print(f" Lines added: [green]{stats['lines_added']}[/green]")
|
|
222
|
+
console.print(f" Lines removed: [red]{stats['lines_removed']}[/red]")
|
|
223
|
+
console.print(f" Net change: {stats['net_change']:+d}")
|
|
224
|
+
console.print()
|
|
225
|
+
|
|
226
|
+
# Ask for confirmation
|
|
227
|
+
if not auto_apply:
|
|
228
|
+
if not typer.confirm("Apply these changes?"):
|
|
229
|
+
console.print("[yellow]Refactoring cancelled. No changes made.[/yellow]\n")
|
|
230
|
+
raise typer.Exit(0)
|
|
231
|
+
|
|
232
|
+
# Apply changes
|
|
233
|
+
try:
|
|
234
|
+
file_path.write_text(refactored_code, encoding='utf-8')
|
|
235
|
+
console.print(f"\n[bold green]✓ Refactoring applied to {file_path}[/bold green]\n")
|
|
236
|
+
|
|
237
|
+
# Show next steps
|
|
238
|
+
console.print("[dim]Next steps:")
|
|
239
|
+
console.print(f" • Review: git diff {file_path}")
|
|
240
|
+
console.print(f" • Test: Run your tests")
|
|
241
|
+
console.print(f" • Commit: git commit -m 'Refactor {file_path.name}'[/dim]\n")
|
|
242
|
+
|
|
243
|
+
except Exception as e:
|
|
244
|
+
console.print(f"[red]✗ Error writing file: {e}[/red]")
|
|
245
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Search command - Search codebase with hybrid search."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.syntax import Syntax
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from coding_assistant.config.settings import settings
|
|
12
|
+
from coding_assistant.context.enhanced_retriever import EnhancedSemanticRetriever
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def search_command(
|
|
18
|
+
query: str = typer.Argument(..., help="Search query (natural language)"),
|
|
19
|
+
limit: int = typer.Option(
|
|
20
|
+
5,
|
|
21
|
+
"--limit",
|
|
22
|
+
"-l",
|
|
23
|
+
help="Number of results to return"
|
|
24
|
+
),
|
|
25
|
+
file_type: Optional[str] = typer.Option(
|
|
26
|
+
None,
|
|
27
|
+
"--type",
|
|
28
|
+
"-t",
|
|
29
|
+
help="Filter by file type (e.g., python, javascript)"
|
|
30
|
+
),
|
|
31
|
+
directory: Optional[str] = typer.Option(
|
|
32
|
+
None,
|
|
33
|
+
"--dir",
|
|
34
|
+
"-d",
|
|
35
|
+
help="Filter by directory"
|
|
36
|
+
),
|
|
37
|
+
show_content: bool = typer.Option(
|
|
38
|
+
True,
|
|
39
|
+
"--content/--no-content",
|
|
40
|
+
help="Show code content in results"
|
|
41
|
+
),
|
|
42
|
+
):
|
|
43
|
+
"""Search your codebase with hybrid semantic + keyword search.
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
assistant search "authentication implementation"
|
|
47
|
+
assistant search "JWT validation" --type python
|
|
48
|
+
assistant search "database queries" --dir src/db --limit 10
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
console.print("\n🔍 [bold cyan]Searching Codebase[/bold cyan]\n")
|
|
52
|
+
console.print(f"Query: [bold]{query}[/bold]\n")
|
|
53
|
+
|
|
54
|
+
# Initialize retriever
|
|
55
|
+
try:
|
|
56
|
+
retriever = EnhancedSemanticRetriever(settings.project_path)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
console.print(f"[red]✗ Error initializing retriever: {e}[/red]")
|
|
59
|
+
raise typer.Exit(1)
|
|
60
|
+
|
|
61
|
+
# Check if indexed
|
|
62
|
+
stats = retriever.get_stats()
|
|
63
|
+
if stats['total_chunks'] == 0:
|
|
64
|
+
console.print("[yellow]⚠️ Codebase not indexed.[/yellow]")
|
|
65
|
+
console.print("[yellow] Run 'assistant index' first to enable search.[/yellow]\n")
|
|
66
|
+
raise typer.Exit(1)
|
|
67
|
+
|
|
68
|
+
# Show search stats
|
|
69
|
+
if settings.verbose:
|
|
70
|
+
console.print(f"[dim]Index stats:[/dim]")
|
|
71
|
+
console.print(f"[dim] • Total chunks: {stats['total_chunks']}[/dim]")
|
|
72
|
+
console.print(f"[dim] • Hybrid search: {stats['hybrid_search_enabled']}[/dim]")
|
|
73
|
+
console.print()
|
|
74
|
+
|
|
75
|
+
# Perform search
|
|
76
|
+
try:
|
|
77
|
+
with console.status("[bold green]Searching..."):
|
|
78
|
+
results = retriever.retrieve(
|
|
79
|
+
query=query,
|
|
80
|
+
k=limit * 2, # Get more, then filter
|
|
81
|
+
language=file_type,
|
|
82
|
+
use_hybrid=True,
|
|
83
|
+
use_ranking=True
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Apply filters
|
|
87
|
+
if directory:
|
|
88
|
+
dir_path = Path(directory)
|
|
89
|
+
results = [r for r in results if str(dir_path) in r['path']]
|
|
90
|
+
|
|
91
|
+
if file_type:
|
|
92
|
+
results = [r for r in results if r.get('language') == file_type]
|
|
93
|
+
|
|
94
|
+
# Limit results
|
|
95
|
+
results = results[:limit]
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
console.print(f"[red]✗ Search error: {e}[/red]")
|
|
99
|
+
if settings.verbose:
|
|
100
|
+
import traceback
|
|
101
|
+
traceback.print_exc()
|
|
102
|
+
raise typer.Exit(1)
|
|
103
|
+
|
|
104
|
+
# Display results
|
|
105
|
+
if not results:
|
|
106
|
+
console.print("[yellow]No results found.[/yellow]\n")
|
|
107
|
+
console.print("[dim]Try:")
|
|
108
|
+
console.print(" • Broadening your search query")
|
|
109
|
+
console.print(" • Removing filters")
|
|
110
|
+
console.print(" • Re-indexing with 'assistant index --force'[/dim]\n")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
console.print(f"[bold green]✓ Found {len(results)} results[/bold green]\n")
|
|
114
|
+
|
|
115
|
+
# Create results table
|
|
116
|
+
table = Table(
|
|
117
|
+
title=f"Search Results for: '{query}'",
|
|
118
|
+
show_header=True,
|
|
119
|
+
header_style="bold magenta",
|
|
120
|
+
border_style="blue",
|
|
121
|
+
show_lines=True
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
table.add_column("#", width=3, style="dim")
|
|
125
|
+
table.add_column("File", style="cyan")
|
|
126
|
+
table.add_column("Type", width=10)
|
|
127
|
+
table.add_column("Name", style="bold")
|
|
128
|
+
table.add_column("Score", width=8, justify="right")
|
|
129
|
+
table.add_column("Lines", width=10, justify="center")
|
|
130
|
+
|
|
131
|
+
for i, result in enumerate(results, 1):
|
|
132
|
+
# Determine score color
|
|
133
|
+
score = result.get('rank_score', result.get('similarity', 0.0))
|
|
134
|
+
if score > 0.8:
|
|
135
|
+
score_style = "bold green"
|
|
136
|
+
elif score > 0.6:
|
|
137
|
+
score_style = "yellow"
|
|
138
|
+
else:
|
|
139
|
+
score_style = "dim"
|
|
140
|
+
|
|
141
|
+
table.add_row(
|
|
142
|
+
str(i),
|
|
143
|
+
result['path'],
|
|
144
|
+
result.get('type', 'file'),
|
|
145
|
+
result.get('name', '-'),
|
|
146
|
+
f"[{score_style}]{score:.2f}[/{score_style}]",
|
|
147
|
+
f"{result.get('start_line', 0)}-{result.get('end_line', 0)}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
console.print(table)
|
|
151
|
+
console.print()
|
|
152
|
+
|
|
153
|
+
# Show detailed content if requested
|
|
154
|
+
if show_content:
|
|
155
|
+
for i, result in enumerate(results[:3], 1): # Show top 3 in detail
|
|
156
|
+
console.print(f"[bold]Result {i}: {result['path']}[/bold]")
|
|
157
|
+
|
|
158
|
+
# Show code with syntax highlighting
|
|
159
|
+
code = result.get('content', '')
|
|
160
|
+
if code:
|
|
161
|
+
lang = result.get('language', 'text')
|
|
162
|
+
syntax = Syntax(
|
|
163
|
+
code[:300] + ("..." if len(code) > 300 else ""),
|
|
164
|
+
lang if lang in ('python', 'javascript', 'typescript') else 'text',
|
|
165
|
+
theme="monokai",
|
|
166
|
+
line_numbers=True,
|
|
167
|
+
start_line=result.get('start_line', 1)
|
|
168
|
+
)
|
|
169
|
+
console.print(Panel(syntax, border_style="dim"))
|
|
170
|
+
console.print()
|
|
171
|
+
|
|
172
|
+
if len(results) > 3:
|
|
173
|
+
console.print(f"[dim]... and {len(results) - 3} more results[/dim]")
|
|
174
|
+
console.print(f"[dim]Use --no-content to see all results in table only[/dim]\n")
|
|
175
|
+
|
|
176
|
+
# Show search tips
|
|
177
|
+
console.print("[dim]💡 Search Tips:[/dim]")
|
|
178
|
+
console.print("[dim] • Use natural language: 'how to authenticate users'[/dim]")
|
|
179
|
+
console.print("[dim] • Or keywords: 'JWT token validation'[/dim]")
|
|
180
|
+
console.print("[dim] • Filter by type: --type python[/dim]")
|
|
181
|
+
console.print("[dim] • Filter by directory: --dir src/auth[/dim]")
|
|
182
|
+
console.print("[dim] • Increase results: --limit 20[/dim]\n")
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Documentation server command.
|
|
2
|
+
|
|
3
|
+
This module provides a simple HTTP server for viewing generated
|
|
4
|
+
documentation locally.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import http.server
|
|
9
|
+
import socketserver
|
|
10
|
+
import webbrowser
|
|
11
|
+
import typer
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
# Create Typer app
|
|
18
|
+
serve_docs_app = typer.Typer(
|
|
19
|
+
name="serve-docs",
|
|
20
|
+
help="Serve documentation locally with HTTP server",
|
|
21
|
+
no_args_is_help=True
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@serve_docs_app.command()
|
|
26
|
+
def serve_command(
|
|
27
|
+
docs_dir: Path = typer.Option(
|
|
28
|
+
"./docs/generated",
|
|
29
|
+
"--dir",
|
|
30
|
+
"-d",
|
|
31
|
+
help="Documentation directory to serve"
|
|
32
|
+
),
|
|
33
|
+
port: int = typer.Option(
|
|
34
|
+
8000,
|
|
35
|
+
"--port",
|
|
36
|
+
"-p",
|
|
37
|
+
help="Server port"
|
|
38
|
+
),
|
|
39
|
+
open_browser: bool = typer.Option(
|
|
40
|
+
True,
|
|
41
|
+
"--open/--no-open",
|
|
42
|
+
help="Open browser automatically"
|
|
43
|
+
),
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Serve documentation locally via HTTP.
|
|
47
|
+
|
|
48
|
+
This command starts a simple HTTP server to view the generated
|
|
49
|
+
documentation in a web browser. Markdown files will be served
|
|
50
|
+
as plain text (for rendering, use a static site generator).
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
assistant serve-docs
|
|
54
|
+
assistant serve-docs --port 3000
|
|
55
|
+
assistant serve-docs --dir ./my-docs --no-open
|
|
56
|
+
"""
|
|
57
|
+
if not docs_dir.exists():
|
|
58
|
+
console.print(f"[red]Error:[/red] Documentation directory not found: {docs_dir}")
|
|
59
|
+
console.print(f"[dim]Run:[/dim] [cyan]assistant document generate[/cyan] first")
|
|
60
|
+
raise typer.Exit(1)
|
|
61
|
+
|
|
62
|
+
# Change to docs directory
|
|
63
|
+
import os
|
|
64
|
+
original_dir = os.getcwd()
|
|
65
|
+
os.chdir(docs_dir)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
# Create HTTP server
|
|
69
|
+
handler = http.server.SimpleHTTPRequestHandler
|
|
70
|
+
|
|
71
|
+
# Custom handler to add basic routing
|
|
72
|
+
class DocHandler(handler):
|
|
73
|
+
def do_GET(self):
|
|
74
|
+
# Redirect root to README.md
|
|
75
|
+
if self.path == '/':
|
|
76
|
+
self.path = '/README.md'
|
|
77
|
+
return super().do_GET()
|
|
78
|
+
|
|
79
|
+
def log_message(self, format, *args):
|
|
80
|
+
# Custom logging to console
|
|
81
|
+
console.print(f"[dim]{args[0]}[/dim] - {args[1]} - {args[2]}")
|
|
82
|
+
|
|
83
|
+
with socketserver.TCPServer(("", port), DocHandler) as httpd:
|
|
84
|
+
url = f"http://localhost:{port}"
|
|
85
|
+
|
|
86
|
+
console.print(Panel.fit(
|
|
87
|
+
f"[bold cyan]Documentation Server Running[/bold cyan]\\n"
|
|
88
|
+
f"URL: {url}\\n"
|
|
89
|
+
f"Directory: {docs_dir.resolve()}",
|
|
90
|
+
border_style="cyan"
|
|
91
|
+
))
|
|
92
|
+
|
|
93
|
+
console.print("")
|
|
94
|
+
console.print("[green]✓[/green] Server started successfully")
|
|
95
|
+
console.print(f"[cyan]→[/cyan] Visit {url} in your browser")
|
|
96
|
+
console.print("")
|
|
97
|
+
console.print("[dim]Press Ctrl+C to stop the server[/dim]")
|
|
98
|
+
console.print("")
|
|
99
|
+
|
|
100
|
+
# Open browser if requested
|
|
101
|
+
if open_browser:
|
|
102
|
+
try:
|
|
103
|
+
webbrowser.open(url)
|
|
104
|
+
console.print("[green]✓[/green] Browser opened")
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
# Serve forever
|
|
109
|
+
httpd.serve_forever()
|
|
110
|
+
|
|
111
|
+
except KeyboardInterrupt:
|
|
112
|
+
console.print("\\n\\n[yellow]Server stopped[/yellow]")
|
|
113
|
+
|
|
114
|
+
except OSError as e:
|
|
115
|
+
if "Address already in use" in str(e):
|
|
116
|
+
console.print(f"[red]Error:[/red] Port {port} is already in use")
|
|
117
|
+
console.print(f"[dim]Try a different port:[/dim] [cyan]--port {port + 1}[/cyan]")
|
|
118
|
+
else:
|
|
119
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
120
|
+
raise typer.Exit(1)
|
|
121
|
+
|
|
122
|
+
finally:
|
|
123
|
+
# Change back to original directory
|
|
124
|
+
os.chdir(original_dir)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# Export the Typer app
|
|
128
|
+
__all__ = ['serve_docs_app']
|