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.
Files changed (89) hide show
  1. ai_coding_assistant-0.5.0.dist-info/METADATA +226 -0
  2. ai_coding_assistant-0.5.0.dist-info/RECORD +89 -0
  3. ai_coding_assistant-0.5.0.dist-info/WHEEL +4 -0
  4. ai_coding_assistant-0.5.0.dist-info/entry_points.txt +3 -0
  5. ai_coding_assistant-0.5.0.dist-info/licenses/LICENSE +21 -0
  6. coding_assistant/__init__.py +3 -0
  7. coding_assistant/__main__.py +19 -0
  8. coding_assistant/cli/__init__.py +1 -0
  9. coding_assistant/cli/app.py +158 -0
  10. coding_assistant/cli/commands/__init__.py +19 -0
  11. coding_assistant/cli/commands/ask.py +178 -0
  12. coding_assistant/cli/commands/config.py +438 -0
  13. coding_assistant/cli/commands/diagram.py +267 -0
  14. coding_assistant/cli/commands/document.py +410 -0
  15. coding_assistant/cli/commands/explain.py +192 -0
  16. coding_assistant/cli/commands/fix.py +249 -0
  17. coding_assistant/cli/commands/index.py +162 -0
  18. coding_assistant/cli/commands/refactor.py +245 -0
  19. coding_assistant/cli/commands/search.py +182 -0
  20. coding_assistant/cli/commands/serve_docs.py +128 -0
  21. coding_assistant/cli/repl.py +381 -0
  22. coding_assistant/cli/theme.py +90 -0
  23. coding_assistant/codebase/__init__.py +1 -0
  24. coding_assistant/codebase/crawler.py +93 -0
  25. coding_assistant/codebase/parser.py +266 -0
  26. coding_assistant/config/__init__.py +25 -0
  27. coding_assistant/config/config_manager.py +615 -0
  28. coding_assistant/config/settings.py +82 -0
  29. coding_assistant/context/__init__.py +19 -0
  30. coding_assistant/context/chunker.py +443 -0
  31. coding_assistant/context/enhanced_retriever.py +322 -0
  32. coding_assistant/context/hybrid_search.py +311 -0
  33. coding_assistant/context/ranker.py +355 -0
  34. coding_assistant/context/retriever.py +119 -0
  35. coding_assistant/context/window.py +362 -0
  36. coding_assistant/documentation/__init__.py +23 -0
  37. coding_assistant/documentation/agents/__init__.py +27 -0
  38. coding_assistant/documentation/agents/coordinator.py +510 -0
  39. coding_assistant/documentation/agents/module_documenter.py +111 -0
  40. coding_assistant/documentation/agents/synthesizer.py +139 -0
  41. coding_assistant/documentation/agents/task_delegator.py +100 -0
  42. coding_assistant/documentation/decomposition/__init__.py +21 -0
  43. coding_assistant/documentation/decomposition/context_preserver.py +477 -0
  44. coding_assistant/documentation/decomposition/module_detector.py +302 -0
  45. coding_assistant/documentation/decomposition/partitioner.py +621 -0
  46. coding_assistant/documentation/generators/__init__.py +14 -0
  47. coding_assistant/documentation/generators/dataflow_generator.py +440 -0
  48. coding_assistant/documentation/generators/diagram_generator.py +511 -0
  49. coding_assistant/documentation/graph/__init__.py +13 -0
  50. coding_assistant/documentation/graph/dependency_builder.py +468 -0
  51. coding_assistant/documentation/graph/module_analyzer.py +475 -0
  52. coding_assistant/documentation/writers/__init__.py +11 -0
  53. coding_assistant/documentation/writers/markdown_writer.py +322 -0
  54. coding_assistant/embeddings/__init__.py +0 -0
  55. coding_assistant/embeddings/generator.py +89 -0
  56. coding_assistant/embeddings/store.py +187 -0
  57. coding_assistant/exceptions/__init__.py +50 -0
  58. coding_assistant/exceptions/base.py +110 -0
  59. coding_assistant/exceptions/llm.py +249 -0
  60. coding_assistant/exceptions/recovery.py +263 -0
  61. coding_assistant/exceptions/storage.py +213 -0
  62. coding_assistant/exceptions/validation.py +230 -0
  63. coding_assistant/llm/__init__.py +1 -0
  64. coding_assistant/llm/client.py +277 -0
  65. coding_assistant/llm/gemini_client.py +181 -0
  66. coding_assistant/llm/groq_client.py +160 -0
  67. coding_assistant/llm/prompts.py +98 -0
  68. coding_assistant/llm/together_client.py +160 -0
  69. coding_assistant/operations/__init__.py +13 -0
  70. coding_assistant/operations/differ.py +369 -0
  71. coding_assistant/operations/generator.py +347 -0
  72. coding_assistant/operations/linter.py +430 -0
  73. coding_assistant/operations/validator.py +406 -0
  74. coding_assistant/storage/__init__.py +9 -0
  75. coding_assistant/storage/database.py +363 -0
  76. coding_assistant/storage/session.py +231 -0
  77. coding_assistant/utils/__init__.py +31 -0
  78. coding_assistant/utils/cache.py +477 -0
  79. coding_assistant/utils/hardware.py +132 -0
  80. coding_assistant/utils/keystore.py +206 -0
  81. coding_assistant/utils/logger.py +32 -0
  82. coding_assistant/utils/progress.py +311 -0
  83. coding_assistant/validation/__init__.py +13 -0
  84. coding_assistant/validation/files.py +305 -0
  85. coding_assistant/validation/inputs.py +335 -0
  86. coding_assistant/validation/params.py +280 -0
  87. coding_assistant/validation/sanitizers.py +243 -0
  88. coding_assistant/vcs/__init__.py +5 -0
  89. 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)