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,192 @@
|
|
|
1
|
+
"""Explain command - Explain how code works with enhanced context."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.syntax import Syntax
|
|
6
|
+
from rich.panel import Panel
|
|
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.llm.client import LLMClientFactory
|
|
13
|
+
from coding_assistant.llm.prompts import PromptBuilder
|
|
14
|
+
from coding_assistant.codebase.parser import CodeParser
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def explain_command(
|
|
20
|
+
target: str = typer.Argument(..., help="File path or file::function to explain"),
|
|
21
|
+
context_size: int = typer.Option(
|
|
22
|
+
5,
|
|
23
|
+
"--context",
|
|
24
|
+
"-c",
|
|
25
|
+
help="Number of related code chunks to include"
|
|
26
|
+
),
|
|
27
|
+
detailed: bool = typer.Option(
|
|
28
|
+
False,
|
|
29
|
+
"--detailed",
|
|
30
|
+
"-d",
|
|
31
|
+
help="Provide detailed explanation with examples"
|
|
32
|
+
),
|
|
33
|
+
):
|
|
34
|
+
"""Explain how code works with intelligent context.
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
assistant explain src/auth/login.py
|
|
38
|
+
assistant explain src/auth/login.py::authenticate_user
|
|
39
|
+
assistant explain src/utils.py --detailed
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
console.print("\nš [bold cyan]Code Explanation[/bold cyan]\n")
|
|
43
|
+
|
|
44
|
+
# Parse target (file or file::function)
|
|
45
|
+
if '::' in target:
|
|
46
|
+
file_path, function_name = target.split('::', 1)
|
|
47
|
+
else:
|
|
48
|
+
file_path = target
|
|
49
|
+
function_name = None
|
|
50
|
+
|
|
51
|
+
file_path = Path(file_path)
|
|
52
|
+
|
|
53
|
+
# Check if file exists
|
|
54
|
+
if not file_path.exists():
|
|
55
|
+
console.print(f"[red]ā File not found: {file_path}[/red]")
|
|
56
|
+
raise typer.Exit(1)
|
|
57
|
+
|
|
58
|
+
# Read the code
|
|
59
|
+
try:
|
|
60
|
+
code = file_path.read_text(encoding='utf-8')
|
|
61
|
+
except Exception as e:
|
|
62
|
+
console.print(f"[red]ā Error reading file: {e}[/red]")
|
|
63
|
+
raise typer.Exit(1)
|
|
64
|
+
|
|
65
|
+
# If function specified, try to extract it
|
|
66
|
+
target_code = code
|
|
67
|
+
if function_name:
|
|
68
|
+
parser = CodeParser()
|
|
69
|
+
try:
|
|
70
|
+
parsed = parser.parse_file(str(file_path), code)
|
|
71
|
+
|
|
72
|
+
# Find the function
|
|
73
|
+
found = False
|
|
74
|
+
for chunk in parsed.get('chunks', []):
|
|
75
|
+
if chunk.get('name') == function_name:
|
|
76
|
+
target_code = chunk['content']
|
|
77
|
+
found = True
|
|
78
|
+
console.print(f"[dim]Found function: {function_name}[/dim]\n")
|
|
79
|
+
break
|
|
80
|
+
|
|
81
|
+
if not found:
|
|
82
|
+
console.print(f"[yellow]ā ļø Function '{function_name}' not found, explaining entire file[/yellow]\n")
|
|
83
|
+
except:
|
|
84
|
+
console.print(f"[yellow]ā ļø Could not parse file, explaining entire content[/yellow]\n")
|
|
85
|
+
|
|
86
|
+
# Show the code we're explaining
|
|
87
|
+
console.print(Panel(
|
|
88
|
+
Syntax(target_code[:500] + ("..." if len(target_code) > 500 else ""),
|
|
89
|
+
"python" if file_path.suffix == '.py' else "javascript",
|
|
90
|
+
theme="monokai",
|
|
91
|
+
line_numbers=True),
|
|
92
|
+
title=f"Code to Explain: {file_path.name}" + (f"::{function_name}" if function_name else ""),
|
|
93
|
+
border_style="blue"
|
|
94
|
+
))
|
|
95
|
+
|
|
96
|
+
# Get relevant context using enhanced retrieval
|
|
97
|
+
context_chunks = []
|
|
98
|
+
try:
|
|
99
|
+
with console.status("[bold green]Finding relevant context..."):
|
|
100
|
+
retriever = EnhancedSemanticRetriever(settings.project_path)
|
|
101
|
+
|
|
102
|
+
# Check if indexed
|
|
103
|
+
stats = retriever.get_stats()
|
|
104
|
+
if stats['total_chunks'] > 0:
|
|
105
|
+
# Search for related code
|
|
106
|
+
query = f"code related to {file_path.name}" + (f" {function_name}" if function_name else "")
|
|
107
|
+
|
|
108
|
+
results = retriever.retrieve(
|
|
109
|
+
query=query,
|
|
110
|
+
k=context_size,
|
|
111
|
+
current_file=str(file_path),
|
|
112
|
+
language='python' if file_path.suffix == '.py' else 'javascript',
|
|
113
|
+
use_hybrid=True,
|
|
114
|
+
use_ranking=True
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if settings.verbose and results:
|
|
118
|
+
console.print("\n[dim]Related code found:[/dim]")
|
|
119
|
+
for r in results:
|
|
120
|
+
console.print(f"[dim] ⢠{r['path']} - {r['name']} (score: {r['rank_score']:.2f})[/dim]")
|
|
121
|
+
console.print()
|
|
122
|
+
|
|
123
|
+
context_chunks = results
|
|
124
|
+
else:
|
|
125
|
+
console.print("[yellow]ā ļø Codebase not indexed. Run 'assistant index' for better context.[/yellow]\n")
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
if settings.verbose:
|
|
129
|
+
console.print(f"[yellow]ā ļø Could not retrieve context: {e}[/yellow]\n")
|
|
130
|
+
|
|
131
|
+
# Build explanation prompt
|
|
132
|
+
prompt_builder = PromptBuilder()
|
|
133
|
+
|
|
134
|
+
system_prompt = """You are an expert code reviewer and educator. Your task is to explain code clearly and thoroughly.
|
|
135
|
+
|
|
136
|
+
When explaining code:
|
|
137
|
+
1. Start with a high-level overview of what the code does
|
|
138
|
+
2. Explain the main components and their purposes
|
|
139
|
+
3. Highlight important logic and design decisions
|
|
140
|
+
4. Point out any potential issues or improvements
|
|
141
|
+
5. Use clear, concise language suitable for developers
|
|
142
|
+
|
|
143
|
+
If the code uses specific patterns or frameworks, mention them."""
|
|
144
|
+
|
|
145
|
+
if detailed:
|
|
146
|
+
system_prompt += """
|
|
147
|
+
|
|
148
|
+
Provide a detailed explanation including:
|
|
149
|
+
- Step-by-step walkthrough of the logic
|
|
150
|
+
- Example inputs and outputs
|
|
151
|
+
- Edge cases handled
|
|
152
|
+
- Dependencies and integrations
|
|
153
|
+
- Best practices applied or missing"""
|
|
154
|
+
|
|
155
|
+
# Build context section
|
|
156
|
+
context_text = f"# Code to Explain\n\nFile: `{file_path}`\n"
|
|
157
|
+
if function_name:
|
|
158
|
+
context_text += f"Function: `{function_name}`\n"
|
|
159
|
+
context_text += f"\n```{file_path.suffix[1:] if file_path.suffix else 'text'}\n{target_code}\n```\n\n"
|
|
160
|
+
|
|
161
|
+
# Add related code context
|
|
162
|
+
if context_chunks:
|
|
163
|
+
context_text += "# Related Code (for context)\n\n"
|
|
164
|
+
for chunk in context_chunks[:3]: # Limit to top 3
|
|
165
|
+
context_text += f"## {chunk['path']}\n"
|
|
166
|
+
context_text += f"```{chunk.get('language', 'text')}\n{chunk['content'][:300]}...\n```\n\n"
|
|
167
|
+
|
|
168
|
+
messages = [
|
|
169
|
+
{"role": "system", "content": system_prompt},
|
|
170
|
+
{"role": "user", "content": context_text + "\nPlease explain this code."}
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
# Get LLM client
|
|
174
|
+
llm = LLMClientFactory.create_client(settings.llm_provider)
|
|
175
|
+
|
|
176
|
+
# Generate explanation
|
|
177
|
+
console.print("[bold blue]Explanation:[/bold blue]\n")
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
response_text = ""
|
|
181
|
+
for chunk in llm.generate(messages, stream=True):
|
|
182
|
+
console.print(chunk, end="")
|
|
183
|
+
response_text += chunk
|
|
184
|
+
console.print("\n")
|
|
185
|
+
|
|
186
|
+
# Show helpful tips
|
|
187
|
+
if not detailed:
|
|
188
|
+
console.print("[dim]š” Tip: Use --detailed for a more thorough explanation[/dim]")
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
console.print(f"\n[red]ā Error generating explanation: {e}[/red]")
|
|
192
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Fix command - Fix linting issues and bugs."""
|
|
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.operations.validator import SyntaxValidator
|
|
12
|
+
from coding_assistant.operations.linter import LinterIntegration
|
|
13
|
+
from coding_assistant.operations.generator import CodeGenerator
|
|
14
|
+
from coding_assistant.operations.differ import DiffGenerator
|
|
15
|
+
from coding_assistant.llm.client import LLMClientFactory
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def fix_command(
|
|
21
|
+
file_path: str = typer.Argument(..., help="File path to fix"),
|
|
22
|
+
error: Optional[str] = typer.Option(
|
|
23
|
+
None,
|
|
24
|
+
"--error",
|
|
25
|
+
"-e",
|
|
26
|
+
help="Specific error message to fix"
|
|
27
|
+
),
|
|
28
|
+
auto_apply: bool = typer.Option(
|
|
29
|
+
False,
|
|
30
|
+
"--auto-apply",
|
|
31
|
+
"-y",
|
|
32
|
+
help="Apply fixes without confirmation"
|
|
33
|
+
),
|
|
34
|
+
lint_only: bool = typer.Option(
|
|
35
|
+
False,
|
|
36
|
+
"--lint-only",
|
|
37
|
+
help="Only fix linting issues (no AI fixes)"
|
|
38
|
+
),
|
|
39
|
+
):
|
|
40
|
+
"""Fix linting issues and bugs in code.
|
|
41
|
+
|
|
42
|
+
The fix process:
|
|
43
|
+
1. Validate syntax
|
|
44
|
+
2. Run linter to find issues
|
|
45
|
+
3. Try auto-fix for linting issues
|
|
46
|
+
4. If error specified, get AI fix
|
|
47
|
+
5. Validate fixed code
|
|
48
|
+
6. Show diff
|
|
49
|
+
7. Apply with confirmation
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
assistant fix src/module.py
|
|
53
|
+
assistant fix src/auth.py --error "TypeError: expected str, got int"
|
|
54
|
+
assistant fix src/utils.py --lint-only --auto-apply
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
console.print("\nš§ [bold cyan]Fix Code Issues[/bold cyan]\n")
|
|
58
|
+
|
|
59
|
+
file_path_obj = Path(file_path)
|
|
60
|
+
|
|
61
|
+
# Check file exists
|
|
62
|
+
if not file_path_obj.exists():
|
|
63
|
+
console.print(f"[red]ā File not found: {file_path}[/red]")
|
|
64
|
+
raise typer.Exit(1)
|
|
65
|
+
|
|
66
|
+
# Detect language
|
|
67
|
+
language_map = {
|
|
68
|
+
'.py': 'python',
|
|
69
|
+
'.js': 'javascript',
|
|
70
|
+
'.ts': 'typescript',
|
|
71
|
+
'.jsx': 'jsx',
|
|
72
|
+
'.tsx': 'tsx'
|
|
73
|
+
}
|
|
74
|
+
language = language_map.get(file_path_obj.suffix, 'python')
|
|
75
|
+
|
|
76
|
+
# Read original code
|
|
77
|
+
try:
|
|
78
|
+
original_code = file_path_obj.read_text(encoding='utf-8')
|
|
79
|
+
except Exception as e:
|
|
80
|
+
console.print(f"[red]ā Error reading file: {e}[/red]")
|
|
81
|
+
raise typer.Exit(1)
|
|
82
|
+
|
|
83
|
+
console.print(f"[bold]File:[/bold] {file_path}")
|
|
84
|
+
console.print(f"[dim]Language: {language}[/dim]\n")
|
|
85
|
+
|
|
86
|
+
# Step 1: Validate syntax
|
|
87
|
+
console.print("[bold]1. Validating syntax...[/bold]")
|
|
88
|
+
validator = SyntaxValidator()
|
|
89
|
+
validation_result = validator.validate(original_code, language)
|
|
90
|
+
|
|
91
|
+
if not validation_result.is_valid:
|
|
92
|
+
console.print(f"[red]ā Syntax error at line {validation_result.line}:[/red]")
|
|
93
|
+
console.print(f"[red] {validation_result.error_message}[/red]\n")
|
|
94
|
+
|
|
95
|
+
if lint_only:
|
|
96
|
+
console.print("[yellow]Cannot auto-fix syntax errors with --lint-only.[/yellow]")
|
|
97
|
+
console.print("[yellow]Use without --lint-only to get AI-powered fix.[/yellow]\n")
|
|
98
|
+
raise typer.Exit(1)
|
|
99
|
+
|
|
100
|
+
# Will need AI fix
|
|
101
|
+
error = error or validation_result.error_message
|
|
102
|
+
else:
|
|
103
|
+
console.print("[green]ā Syntax valid[/green]\n")
|
|
104
|
+
|
|
105
|
+
# Step 2: Run linter
|
|
106
|
+
console.print("[bold]2. Running linter...[/bold]")
|
|
107
|
+
linter = LinterIntegration(project_root=str(settings.project_path))
|
|
108
|
+
lint_result = linter.lint(original_code, language, auto_fix=True)
|
|
109
|
+
|
|
110
|
+
if lint_result.error_count == 0 and lint_result.warning_count == 0:
|
|
111
|
+
console.print("[green]ā No linting issues found[/green]\n")
|
|
112
|
+
|
|
113
|
+
if not error:
|
|
114
|
+
console.print("[green]ā Code is clean! No fixes needed.[/green]\n")
|
|
115
|
+
return
|
|
116
|
+
else:
|
|
117
|
+
console.print(f"[yellow]Found {lint_result.error_count} errors, {lint_result.warning_count} warnings[/yellow]")
|
|
118
|
+
linter.display_issues(lint_result)
|
|
119
|
+
console.print()
|
|
120
|
+
|
|
121
|
+
# Step 3: Apply auto-fixes
|
|
122
|
+
fixed_code = original_code
|
|
123
|
+
|
|
124
|
+
if lint_result.fixed_code:
|
|
125
|
+
console.print("[bold]3. Applying linter auto-fixes...[/bold]")
|
|
126
|
+
fixed_code = lint_result.fixed_code
|
|
127
|
+
console.print("[green]ā Auto-fixed linting issues[/green]\n")
|
|
128
|
+
|
|
129
|
+
# If lint-only mode and we have fixes, show and apply
|
|
130
|
+
if lint_only:
|
|
131
|
+
differ = DiffGenerator()
|
|
132
|
+
diff = differ.generate_diff(original_code, fixed_code, file_path)
|
|
133
|
+
differ.display_diff(diff, title="Linting Fixes")
|
|
134
|
+
|
|
135
|
+
if not auto_apply:
|
|
136
|
+
if not typer.confirm("\nApply these fixes?"):
|
|
137
|
+
console.print("[yellow]Cancelled.[/yellow]\n")
|
|
138
|
+
raise typer.Exit(0)
|
|
139
|
+
|
|
140
|
+
file_path_obj.write_text(fixed_code, encoding='utf-8')
|
|
141
|
+
console.print(f"\n[green]ā Fixes applied to {file_path}[/green]\n")
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
# Step 4: AI-powered fix (if error specified or syntax invalid)
|
|
145
|
+
if error and not lint_only:
|
|
146
|
+
console.print(f"[bold]4. Getting AI fix for error...[/bold]")
|
|
147
|
+
console.print(f"[dim]Error: {error}[/dim]\n")
|
|
148
|
+
|
|
149
|
+
llm = LLMClientFactory.create_client(settings.llm_provider)
|
|
150
|
+
|
|
151
|
+
system_prompt = f"""You are an expert {language} debugger and code fixer.
|
|
152
|
+
|
|
153
|
+
Your task: Fix the code error while preserving functionality.
|
|
154
|
+
|
|
155
|
+
Guidelines:
|
|
156
|
+
- Fix ONLY the specific error mentioned
|
|
157
|
+
- Preserve all other functionality
|
|
158
|
+
- Follow {language} best practices
|
|
159
|
+
- Add comments explaining the fix if needed
|
|
160
|
+
- Return ONLY the fixed code in a code block
|
|
161
|
+
- Do not include explanations outside the code block"""
|
|
162
|
+
|
|
163
|
+
user_prompt = f"""Fix this {language} code error:
|
|
164
|
+
|
|
165
|
+
Error: {error}
|
|
166
|
+
|
|
167
|
+
Code:
|
|
168
|
+
```{language}
|
|
169
|
+
{fixed_code}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Please provide the fixed code."""
|
|
173
|
+
|
|
174
|
+
messages = [
|
|
175
|
+
{"role": "system", "content": system_prompt},
|
|
176
|
+
{"role": "user", "content": user_prompt}
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
# Get fix from LLM
|
|
180
|
+
with console.status("[bold green]Generating fix..."):
|
|
181
|
+
try:
|
|
182
|
+
response = ""
|
|
183
|
+
for chunk in llm.generate(messages, stream=False):
|
|
184
|
+
response += chunk
|
|
185
|
+
except Exception as e:
|
|
186
|
+
console.print(f"[red]ā Error generating fix: {e}[/red]")
|
|
187
|
+
raise typer.Exit(1)
|
|
188
|
+
|
|
189
|
+
# Extract fixed code
|
|
190
|
+
generator = CodeGenerator()
|
|
191
|
+
blocks = generator.extract_code_blocks(response, validate=True)
|
|
192
|
+
|
|
193
|
+
if not blocks:
|
|
194
|
+
console.print("[red]ā No code found in fix response[/red]")
|
|
195
|
+
raise typer.Exit(1)
|
|
196
|
+
|
|
197
|
+
ai_fixed_code = blocks[0].code
|
|
198
|
+
|
|
199
|
+
# Validate AI fix
|
|
200
|
+
validation_result = validator.validate(ai_fixed_code, language)
|
|
201
|
+
if not validation_result.is_valid:
|
|
202
|
+
console.print(f"[red]ā AI-generated fix has syntax errors:[/red]")
|
|
203
|
+
console.print(f"[red] {validation_result.error_message}[/red]")
|
|
204
|
+
raise typer.Exit(1)
|
|
205
|
+
|
|
206
|
+
fixed_code = ai_fixed_code
|
|
207
|
+
console.print("[green]ā AI fix generated and validated[/green]\n")
|
|
208
|
+
|
|
209
|
+
# Step 5: Show diff and apply
|
|
210
|
+
if fixed_code != original_code:
|
|
211
|
+
console.print("[bold]Changes Preview:[/bold]\n")
|
|
212
|
+
differ = DiffGenerator()
|
|
213
|
+
diff = differ.generate_diff(original_code, fixed_code, file_path)
|
|
214
|
+
differ.display_diff(diff, title=f"Fixes for {file_path_obj.name}")
|
|
215
|
+
|
|
216
|
+
stats = differ.get_change_stats(diff)
|
|
217
|
+
console.print(f"\n[dim]Lines changed: {stats['lines_added'] + stats['lines_removed']}[/dim]\n")
|
|
218
|
+
|
|
219
|
+
# Confirm
|
|
220
|
+
if not auto_apply:
|
|
221
|
+
if not typer.confirm("Apply these fixes?"):
|
|
222
|
+
console.print("[yellow]Cancelled. No changes made.[/yellow]\n")
|
|
223
|
+
raise typer.Exit(0)
|
|
224
|
+
|
|
225
|
+
# Apply
|
|
226
|
+
try:
|
|
227
|
+
file_path_obj.write_text(fixed_code, encoding='utf-8')
|
|
228
|
+
console.print(f"\n[bold green]ā Fixes applied to {file_path}[/bold green]\n")
|
|
229
|
+
|
|
230
|
+
# Verify fix worked
|
|
231
|
+
console.print("[bold]Verifying fix...[/bold]")
|
|
232
|
+
final_validation = validator.validate(fixed_code, language)
|
|
233
|
+
final_lint = linter.lint(fixed_code, language, auto_fix=False)
|
|
234
|
+
|
|
235
|
+
if final_validation.is_valid:
|
|
236
|
+
console.print("[green]ā Syntax valid[/green]")
|
|
237
|
+
|
|
238
|
+
if final_lint.error_count == 0:
|
|
239
|
+
console.print("[green]ā No linting errors[/green]")
|
|
240
|
+
else:
|
|
241
|
+
console.print(f"[yellow]ā ļø Still has {final_lint.error_count} linting issues[/yellow]")
|
|
242
|
+
|
|
243
|
+
console.print()
|
|
244
|
+
|
|
245
|
+
except Exception as e:
|
|
246
|
+
console.print(f"[red]ā Error writing file: {e}[/red]")
|
|
247
|
+
raise typer.Exit(1)
|
|
248
|
+
else:
|
|
249
|
+
console.print("[green]No changes needed![/green]\n")
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Index command - Index codebase for semantic search."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
from coding_assistant.config.settings import settings
|
|
9
|
+
from coding_assistant.context.enhanced_retriever import EnhancedSemanticRetriever
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def index_command(
|
|
15
|
+
max_files: int = typer.Option(
|
|
16
|
+
100,
|
|
17
|
+
"--max-files",
|
|
18
|
+
"-m",
|
|
19
|
+
help="Maximum number of files to index"
|
|
20
|
+
),
|
|
21
|
+
force: bool = typer.Option(
|
|
22
|
+
False,
|
|
23
|
+
"--force",
|
|
24
|
+
"-f",
|
|
25
|
+
help="Force re-index (clear existing index)"
|
|
26
|
+
),
|
|
27
|
+
stats: bool = typer.Option(
|
|
28
|
+
False,
|
|
29
|
+
"--stats",
|
|
30
|
+
"-s",
|
|
31
|
+
help="Show index statistics only (don't index)"
|
|
32
|
+
),
|
|
33
|
+
):
|
|
34
|
+
"""Index your codebase for semantic search with smart chunking.
|
|
35
|
+
|
|
36
|
+
This command:
|
|
37
|
+
- Crawls your codebase
|
|
38
|
+
- Chunks code at function/class level
|
|
39
|
+
- Generates embeddings
|
|
40
|
+
- Builds vector + BM25 search index
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
assistant index
|
|
44
|
+
assistant index --force --max-files 200
|
|
45
|
+
assistant index --stats
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
console.print("\nš [bold cyan]Codebase Indexing[/bold cyan]\n")
|
|
49
|
+
|
|
50
|
+
# Initialize retriever
|
|
51
|
+
try:
|
|
52
|
+
retriever = EnhancedSemanticRetriever(settings.project_path)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
console.print(f"[red]ā Error initializing retriever: {e}[/red]")
|
|
55
|
+
raise typer.Exit(1)
|
|
56
|
+
|
|
57
|
+
# If just showing stats
|
|
58
|
+
if stats:
|
|
59
|
+
try:
|
|
60
|
+
stats_data = retriever.get_stats()
|
|
61
|
+
|
|
62
|
+
# Create stats table
|
|
63
|
+
table = Table(title="Index Statistics", show_header=True, header_style="bold magenta")
|
|
64
|
+
table.add_column("Metric", style="cyan")
|
|
65
|
+
table.add_column("Value", justify="right", style="green")
|
|
66
|
+
|
|
67
|
+
table.add_row("Total Chunks", str(stats_data.get('total_chunks', 0)))
|
|
68
|
+
table.add_row("Indexed Chunks", str(stats_data.get('indexed_chunks', 0)))
|
|
69
|
+
table.add_row("Embedding Dimension", str(stats_data.get('embedding_dimension', 0)))
|
|
70
|
+
table.add_row("Hybrid Search", "ā" if stats_data.get('hybrid_search_enabled') else "ā")
|
|
71
|
+
table.add_row("Vector Store Size", str(stats_data.get('vector_store_count', 0)))
|
|
72
|
+
|
|
73
|
+
console.print(table)
|
|
74
|
+
console.print()
|
|
75
|
+
|
|
76
|
+
if stats_data.get('total_chunks', 0) == 0:
|
|
77
|
+
console.print("[yellow]ā ļø Codebase not indexed yet. Run 'assistant index' to index.[/yellow]\n")
|
|
78
|
+
else:
|
|
79
|
+
console.print(f"[green]ā Index is ready for search![/green]\n")
|
|
80
|
+
|
|
81
|
+
return
|
|
82
|
+
except Exception as e:
|
|
83
|
+
console.print(f"[red]ā Error getting stats: {e}[/red]")
|
|
84
|
+
raise typer.Exit(1)
|
|
85
|
+
|
|
86
|
+
# Clear if force flag
|
|
87
|
+
if force:
|
|
88
|
+
console.print("[yellow]Clearing existing index...[/yellow]")
|
|
89
|
+
try:
|
|
90
|
+
retriever.clear_index()
|
|
91
|
+
console.print("[green]ā Index cleared[/green]\n")
|
|
92
|
+
except Exception as e:
|
|
93
|
+
console.print(f"[yellow]ā ļø Could not clear index: {e}[/yellow]\n")
|
|
94
|
+
|
|
95
|
+
# Check if already indexed (unless force)
|
|
96
|
+
if not force:
|
|
97
|
+
try:
|
|
98
|
+
stats_data = retriever.get_stats()
|
|
99
|
+
if stats_data.get('total_chunks', 0) > 0:
|
|
100
|
+
console.print(f"[yellow]ā ļø Codebase already indexed ({stats_data['total_chunks']} chunks).[/yellow]")
|
|
101
|
+
console.print("[yellow] Use --force to re-index.[/yellow]\n")
|
|
102
|
+
|
|
103
|
+
if not typer.confirm("Continue anyway?"):
|
|
104
|
+
console.print("[dim]Cancelled.[/dim]")
|
|
105
|
+
raise typer.Exit(0)
|
|
106
|
+
console.print()
|
|
107
|
+
except:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
# Index with progress bar
|
|
111
|
+
console.print(f"[bold]Indexing up to {max_files} files...[/bold]")
|
|
112
|
+
console.print("[dim]This may take a minute for large codebases...[/dim]\n")
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
with Progress(
|
|
116
|
+
SpinnerColumn(),
|
|
117
|
+
TextColumn("[progress.description]{task.description}"),
|
|
118
|
+
BarColumn(),
|
|
119
|
+
TaskProgressColumn(),
|
|
120
|
+
console=console,
|
|
121
|
+
transient=False
|
|
122
|
+
) as progress:
|
|
123
|
+
|
|
124
|
+
task = progress.add_task("[cyan]Indexing codebase...", total=100)
|
|
125
|
+
|
|
126
|
+
# Index codebase
|
|
127
|
+
retriever.index_codebase(max_files=max_files, force_reindex=force)
|
|
128
|
+
|
|
129
|
+
progress.update(task, completed=100)
|
|
130
|
+
|
|
131
|
+
# Get final stats
|
|
132
|
+
stats_data = retriever.get_stats()
|
|
133
|
+
|
|
134
|
+
# Success message
|
|
135
|
+
console.print(f"\n[bold green]ā Indexing complete![/bold green]\n")
|
|
136
|
+
|
|
137
|
+
# Show stats table
|
|
138
|
+
table = Table(show_header=False, box=None)
|
|
139
|
+
table.add_column("Metric", style="cyan")
|
|
140
|
+
table.add_column("Value", justify="right", style="bold green")
|
|
141
|
+
|
|
142
|
+
table.add_row("Total chunks indexed", str(stats_data['total_chunks']))
|
|
143
|
+
table.add_row("Indexed chunks", str(stats_data.get('indexed_chunks', stats_data['total_chunks'])))
|
|
144
|
+
table.add_row("Embedding dimension", str(stats_data['embedding_dimension']))
|
|
145
|
+
table.add_row("Hybrid search", "ā Enabled" if stats_data.get('hybrid_search_enabled') else "ā Disabled")
|
|
146
|
+
|
|
147
|
+
console.print(table)
|
|
148
|
+
console.print()
|
|
149
|
+
|
|
150
|
+
# Next steps
|
|
151
|
+
console.print("[bold]Next steps:[/bold]")
|
|
152
|
+
console.print(" ⢠Search: [cyan]assistant search 'your query'[/cyan]")
|
|
153
|
+
console.print(" ⢠Ask: [cyan]assistant ask 'your question'[/cyan]")
|
|
154
|
+
console.print(" ⢠Explain: [cyan]assistant explain path/to/file.py[/cyan]")
|
|
155
|
+
console.print()
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
console.print(f"\n[red]ā Indexing failed: {e}[/red]")
|
|
159
|
+
if settings.verbose:
|
|
160
|
+
import traceback
|
|
161
|
+
traceback.print_exc()
|
|
162
|
+
raise typer.Exit(1)
|