claude-dev-cli 0.8.0__py3-none-any.whl → 0.8.2__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.
@@ -9,7 +9,7 @@ Features:
9
9
  - Interactive and single-shot modes
10
10
  """
11
11
 
12
- __version__ = "0.8.0"
12
+ __version__ = "0.8.2"
13
13
  __author__ = "Julio"
14
14
  __license__ = "MIT"
15
15
 
claude_dev_cli/cli.py CHANGED
@@ -461,20 +461,37 @@ def generate() -> None:
461
461
  @click.option('-o', '--output', type=click.Path(), help='Output file path')
462
462
  @click.option('-a', '--api', help='API config to use')
463
463
  @click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
464
+ @click.option('--auto-context', is_flag=True, help='Include dependencies and related files')
464
465
  @click.pass_context
465
466
  def gen_tests(
466
467
  ctx: click.Context,
467
468
  file_path: str,
468
469
  output: Optional[str],
469
470
  api: Optional[str],
470
- interactive: bool
471
+ interactive: bool,
472
+ auto_context: bool
471
473
  ) -> None:
472
474
  """Generate pytest tests for a Python file."""
473
475
  console = ctx.obj['console']
474
476
 
475
477
  try:
476
- with console.status("[bold blue]Generating tests..."):
477
- result = generate_tests(file_path, api_config_name=api)
478
+ if auto_context:
479
+ from claude_dev_cli.context import ContextGatherer
480
+
481
+ with console.status("[bold blue]Gathering context..."):
482
+ gatherer = ContextGatherer()
483
+ context = gatherer.gather_for_file(Path(file_path), include_git=False)
484
+ context_info = context.format_for_prompt()
485
+
486
+ console.print("[dim]✓ Context gathered (dependencies, related files)[/dim]")
487
+
488
+ # Use context-aware test generation
489
+ client = ClaudeClient(api_config_name=api)
490
+ enhanced_prompt = f"{context_info}\n\nPlease generate comprehensive pytest tests for the main file, including fixtures, edge cases, and proper mocking where needed."
491
+ result = client.call(enhanced_prompt)
492
+ else:
493
+ with console.status("[bold blue]Generating tests..."):
494
+ result = generate_tests(file_path, api_config_name=api)
478
495
 
479
496
  if interactive:
480
497
  # Show initial result
@@ -530,20 +547,37 @@ def gen_tests(
530
547
  @click.option('-o', '--output', type=click.Path(), help='Output file path')
531
548
  @click.option('-a', '--api', help='API config to use')
532
549
  @click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
550
+ @click.option('--auto-context', is_flag=True, help='Include dependencies and related files')
533
551
  @click.pass_context
534
552
  def gen_docs(
535
553
  ctx: click.Context,
536
554
  file_path: str,
537
555
  output: Optional[str],
538
556
  api: Optional[str],
539
- interactive: bool
557
+ interactive: bool,
558
+ auto_context: bool
540
559
  ) -> None:
541
560
  """Generate documentation for a Python file."""
542
561
  console = ctx.obj['console']
543
562
 
544
563
  try:
545
- with console.status("[bold blue]Generating documentation..."):
546
- result = generate_docs(file_path, api_config_name=api)
564
+ if auto_context:
565
+ from claude_dev_cli.context import ContextGatherer
566
+
567
+ with console.status("[bold blue]Gathering context..."):
568
+ gatherer = ContextGatherer()
569
+ context = gatherer.gather_for_file(Path(file_path), include_git=False)
570
+ context_info = context.format_for_prompt()
571
+
572
+ console.print("[dim]✓ Context gathered (dependencies, related files)[/dim]")
573
+
574
+ # Use context-aware documentation generation
575
+ client = ClaudeClient(api_config_name=api)
576
+ enhanced_prompt = f"{context_info}\n\nPlease generate comprehensive documentation for the main file, including API reference, usage examples, and integration notes."
577
+ result = client.call(enhanced_prompt)
578
+ else:
579
+ with console.status("[bold blue]Generating documentation..."):
580
+ result = generate_docs(file_path, api_config_name=api)
547
581
 
548
582
  if interactive:
549
583
  console.print("\n[bold]Initial Documentation:[/bold]\n")
@@ -816,14 +850,31 @@ def git() -> None:
816
850
 
817
851
  @git.command('commit')
818
852
  @click.option('-a', '--api', help='API config to use')
853
+ @click.option('--auto-context', is_flag=True, help='Include git history and branch context')
819
854
  @click.pass_context
820
- def git_commit(ctx: click.Context, api: Optional[str]) -> None:
855
+ def git_commit(ctx: click.Context, api: Optional[str], auto_context: bool) -> None:
821
856
  """Generate commit message from staged changes."""
822
857
  console = ctx.obj['console']
823
858
 
824
859
  try:
825
- with console.status("[bold blue]Analyzing changes..."):
826
- result = git_commit_message(api_config_name=api)
860
+ if auto_context:
861
+ from claude_dev_cli.context import ContextGatherer
862
+
863
+ with console.status("[bold blue]Gathering context..."):
864
+ gatherer = ContextGatherer()
865
+ # Get git context with recent commits and branch info
866
+ git_context = gatherer.git.gather(include_diff=True)
867
+ context_info = git_context.format_for_prompt()
868
+
869
+ console.print("[dim]✓ Context gathered (branch, commits, diff)[/dim]")
870
+
871
+ # Use context-aware commit message generation
872
+ client = ClaudeClient(api_config_name=api)
873
+ enhanced_prompt = f"{context_info}\n\nPlease generate a concise, conventional commit message for the staged changes. Follow best practices: imperative mood, clear scope, explain what and why."
874
+ result = client.call(enhanced_prompt)
875
+ else:
876
+ with console.status("[bold blue]Analyzing changes..."):
877
+ result = git_commit_message(api_config_name=api)
827
878
 
828
879
  console.print("\n[bold green]Suggested commit message:[/bold green]")
829
880
  console.print(Panel(result, border_style="green"))
@@ -833,6 +884,107 @@ def git_commit(ctx: click.Context, api: Optional[str]) -> None:
833
884
  sys.exit(1)
834
885
 
835
886
 
887
+ @main.group()
888
+ def context() -> None:
889
+ """Context gathering tools and information."""
890
+ pass
891
+
892
+
893
+ @context.command('summary')
894
+ @click.argument('file_path', type=click.Path(exists=True))
895
+ @click.option('--include-git/--no-git', default=True, help='Include git context')
896
+ @click.option('--include-deps/--no-deps', default=True, help='Include dependencies')
897
+ @click.option('--include-tests/--no-tests', default=True, help='Include test files')
898
+ @click.pass_context
899
+ def context_summary(
900
+ ctx: click.Context,
901
+ file_path: str,
902
+ include_git: bool,
903
+ include_deps: bool,
904
+ include_tests: bool
905
+ ) -> None:
906
+ """Show what context would be gathered for a file."""
907
+ from claude_dev_cli.context import ContextGatherer
908
+ from rich.table import Table
909
+
910
+ console = ctx.obj['console']
911
+
912
+ try:
913
+ file_path_obj = Path(file_path)
914
+ gatherer = ContextGatherer()
915
+
916
+ # Gather context
917
+ with console.status("[bold blue]Analyzing context..."):
918
+ context = gatherer.gather_for_review(
919
+ file_path_obj,
920
+ include_git=include_git,
921
+ include_tests=include_tests
922
+ )
923
+
924
+ # Display summary
925
+ console.print(f"\n[bold cyan]Context Summary for:[/bold cyan] {file_path}\n")
926
+
927
+ table = Table(show_header=True, header_style="bold magenta")
928
+ table.add_column("Type", style="cyan")
929
+ table.add_column("Content", style="white")
930
+ table.add_column("Size", justify="right", style="yellow")
931
+ table.add_column("Lines", justify="right", style="green")
932
+ table.add_column("Truncated", justify="center", style="red")
933
+
934
+ total_chars = 0
935
+ total_lines = 0
936
+
937
+ for item in context.items:
938
+ lines = len(item.content.split('\n'))
939
+ chars = len(item.content)
940
+ truncated = "✓" if item.metadata.get('truncated') else ""
941
+
942
+ # Format content preview
943
+ if item.type == 'file':
944
+ content = item.metadata.get('path', 'unknown')
945
+ if item.metadata.get('is_test'):
946
+ content += " [TEST]"
947
+ elif item.type == 'git':
948
+ branch = item.metadata.get('branch', 'unknown')
949
+ modified = item.metadata.get('modified_count', 0)
950
+ content = f"Branch: {branch}, {modified} modified files"
951
+ elif item.type == 'dependency':
952
+ dep_files = item.metadata.get('dependency_files', [])
953
+ content = f"{len(dep_files)} dependency files"
954
+ else:
955
+ content = item.type
956
+
957
+ table.add_row(
958
+ item.type.title(),
959
+ content[:60] + "..." if len(content) > 60 else content,
960
+ f"{chars:,}",
961
+ f"{lines:,}",
962
+ truncated
963
+ )
964
+
965
+ total_chars += chars
966
+ total_lines += lines
967
+
968
+ console.print(table)
969
+
970
+ # Show totals
971
+ console.print(f"\n[bold]Total:[/bold]")
972
+ console.print(f" Characters: [yellow]{total_chars:,}[/yellow]")
973
+ console.print(f" Lines: [green]{total_lines:,}[/green]")
974
+ console.print(f" Estimated tokens: [cyan]~{total_chars // 4:,}[/cyan] (rough estimate)")
975
+
976
+ # Show any truncation warnings
977
+ truncated_items = [item for item in context.items if item.metadata.get('truncated')]
978
+ if truncated_items:
979
+ console.print(f"\n[yellow]⚠ {len(truncated_items)} item(s) truncated to fit size limits[/yellow]")
980
+
981
+ console.print(f"\n[dim]Use --auto-context with commands to include this context[/dim]")
982
+
983
+ except Exception as e:
984
+ console.print(f"[red]Error: {e}[/red]")
985
+ sys.exit(1)
986
+
987
+
836
988
  @main.command('usage')
837
989
  @click.option('--days', type=int, help='Filter by days')
838
990
  @click.option('--api', help='Filter by API config')
claude_dev_cli/config.py CHANGED
@@ -9,6 +9,18 @@ from pydantic import BaseModel, Field
9
9
  from claude_dev_cli.secure_storage import SecureStorage
10
10
 
11
11
 
12
+ class ContextConfig(BaseModel):
13
+ """Global context gathering configuration."""
14
+
15
+ auto_context_default: bool = False # Default for --auto-context flag
16
+ max_file_lines: int = 1000 # Maximum lines per file in context
17
+ max_related_files: int = 5 # Maximum related files to include
18
+ max_diff_lines: int = 200 # Maximum lines of diff to include
19
+ include_git: bool = True # Include git context by default
20
+ include_dependencies: bool = True # Include dependencies by default
21
+ include_tests: bool = True # Include test files by default
22
+
23
+
12
24
  class APIConfig(BaseModel):
13
25
  """Configuration for a Claude API key."""
14
26
 
@@ -31,6 +43,13 @@ class ProjectProfile(BaseModel):
31
43
  coding_style: Optional[str] = None # Preferred coding style
32
44
  test_framework: Optional[str] = None # Preferred test framework
33
45
  preferences: Dict[str, str] = Field(default_factory=dict) # Custom preferences
46
+
47
+ # Context gathering configuration
48
+ max_context_files: int = 5 # Maximum number of related files to include
49
+ max_diff_lines: int = 200 # Maximum lines of diff to include
50
+ max_file_lines: int = 1000 # Maximum lines per file in context
51
+ include_tests_by_default: bool = True # Include test files in review context
52
+ context_depth: int = 2 # How deep to search for related modules
34
53
 
35
54
 
36
55
  class Config:
@@ -67,6 +86,7 @@ class Config:
67
86
  "project_profiles": [],
68
87
  "default_model": "claude-3-5-sonnet-20241022",
69
88
  "max_tokens": 4096,
89
+ "context": ContextConfig().model_dump(),
70
90
  }
71
91
  self._save_config(default_config)
72
92
  return default_config
@@ -238,3 +258,8 @@ class Config:
238
258
  def get_max_tokens(self) -> int:
239
259
  """Get default max tokens."""
240
260
  return self._data.get("max_tokens", 4096)
261
+
262
+ def get_context_config(self) -> ContextConfig:
263
+ """Get context gathering configuration."""
264
+ context_data = self._data.get("context", {})
265
+ return ContextConfig(**context_data) if context_data else ContextConfig()
claude_dev_cli/context.py CHANGED
@@ -16,11 +16,30 @@ class ContextItem:
16
16
  content: str
17
17
  metadata: Dict[str, Any] = field(default_factory=dict)
18
18
 
19
+ def truncate(self, max_lines: Optional[int] = None) -> 'ContextItem':
20
+ """Truncate content to specified number of lines."""
21
+ if max_lines is None:
22
+ return self
23
+
24
+ lines = self.content.split('\n')
25
+ if len(lines) <= max_lines:
26
+ return self
27
+
28
+ truncated_lines = lines[:max_lines]
29
+ truncated_lines.append(f"\n... (truncated {len(lines) - max_lines} more lines)")
30
+
31
+ return ContextItem(
32
+ type=self.type,
33
+ content='\n'.join(truncated_lines),
34
+ metadata={**self.metadata, 'truncated': True, 'original_lines': len(lines)}
35
+ )
36
+
19
37
  def format_for_prompt(self) -> str:
20
38
  """Format this context item for inclusion in a prompt."""
21
39
  if self.type == 'file':
22
40
  path = self.metadata.get('path', 'unknown')
23
- return f"# File: {path}\n\n{self.content}\n"
41
+ truncated_note = " (truncated)" if self.metadata.get('truncated') else ""
42
+ return f"# File: {path}{truncated_note}\n\n{self.content}\n"
24
43
  elif self.type == 'git':
25
44
  return f"# Git Context\n\n{self.content}\n"
26
45
  elif self.type == 'dependency':
@@ -165,8 +184,13 @@ class GitContext:
165
184
  except Exception:
166
185
  return []
167
186
 
168
- def gather(self, include_diff: bool = False) -> ContextItem:
169
- """Gather all git context."""
187
+ def gather(self, include_diff: bool = False, max_diff_lines: int = 200) -> ContextItem:
188
+ """Gather all git context.
189
+
190
+ Args:
191
+ include_diff: Include staged diff in context
192
+ max_diff_lines: Maximum lines of diff to include
193
+ """
170
194
  parts = []
171
195
 
172
196
  branch = self.get_current_branch()
@@ -186,7 +210,12 @@ class GitContext:
186
210
  if include_diff:
187
211
  staged = self.get_staged_diff()
188
212
  if staged:
189
- parts.append(f"\nStaged changes:\n{staged[:1000]}...") # Limit size
213
+ diff_lines = staged.split('\n')
214
+ if len(diff_lines) > max_diff_lines:
215
+ truncated_diff = '\n'.join(diff_lines[:max_diff_lines])
216
+ parts.append(f"\nStaged changes (truncated {len(diff_lines) - max_diff_lines} lines):\n{truncated_diff}\n... (diff truncated)")
217
+ else:
218
+ parts.append(f"\nStaged changes:\n{staged}")
190
219
 
191
220
  content = "\n".join(parts) if parts else "No git context available"
192
221
 
@@ -310,10 +339,25 @@ class DependencyAnalyzer:
310
339
 
311
340
 
312
341
  class ErrorContext:
313
- """Parse and format error context."""
342
+ """Parse and format error context for multiple languages."""
314
343
 
315
344
  @staticmethod
316
- def parse_traceback(error_text: str) -> Dict[str, Any]:
345
+ def detect_language(error_text: str) -> str:
346
+ """Detect programming language from error format."""
347
+ if 'Traceback (most recent call last):' in error_text or 'File "' in error_text:
348
+ return 'python'
349
+ elif 'at ' in error_text and ('.js:' in error_text or '.ts:' in error_text):
350
+ return 'javascript'
351
+ elif 'panic:' in error_text and '.go:' in error_text:
352
+ return 'go'
353
+ elif 'thread' in error_text and 'panicked at' in error_text and '.rs:' in error_text:
354
+ return 'rust'
355
+ elif 'at ' in error_text and '.java:' in error_text:
356
+ return 'java'
357
+ return 'unknown'
358
+
359
+ @staticmethod
360
+ def parse_python_traceback(error_text: str) -> Dict[str, Any]:
317
361
  """Parse Python traceback into structured data."""
318
362
  lines = error_text.split('\n')
319
363
 
@@ -362,24 +406,183 @@ class ErrorContext:
362
406
  'raw': error_text
363
407
  }
364
408
 
365
- return {'frames': frames, 'raw': error_text}
409
+ return {'frames': frames, 'raw': error_text, 'language': 'python'}
410
+
411
+ @staticmethod
412
+ def parse_javascript_stack(error_text: str) -> Dict[str, Any]:
413
+ """Parse JavaScript/TypeScript stack trace."""
414
+ lines = error_text.split('\n')
415
+ frames = []
416
+ error_type = None
417
+ error_message = None
418
+
419
+ for line in lines:
420
+ # Error message usually first: "Error: message" or "TypeError: message"
421
+ if not error_type and (':' in line and not line.strip().startswith('at')):
422
+ parts = line.split(':', 1)
423
+ error_type = parts[0].strip()
424
+ error_message = parts[1].strip() if len(parts) > 1 else ''
425
+ # Stack frame: "at functionName (file.js:line:col)" or "at file.js:line:col"
426
+ elif line.strip().startswith('at '):
427
+ match = re.search(r'at\s+(?:(.+?)\s+)?\(([^)]+)\)|(\S+)$', line)
428
+ if match:
429
+ function = match.group(1) or 'anonymous'
430
+ location = match.group(2) or match.group(3)
431
+ if location and ':' in location:
432
+ parts = location.rsplit(':', 2)
433
+ frames.append({
434
+ 'file': parts[0],
435
+ 'line': int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else None,
436
+ 'column': int(parts[2]) if len(parts) > 2 and parts[2].isdigit() else None,
437
+ 'function': function
438
+ })
439
+
440
+ return {
441
+ 'frames': frames,
442
+ 'error_type': error_type,
443
+ 'error_message': error_message,
444
+ 'raw': error_text,
445
+ 'language': 'javascript'
446
+ }
447
+
448
+ @staticmethod
449
+ def parse_go_panic(error_text: str) -> Dict[str, Any]:
450
+ """Parse Go panic trace."""
451
+ lines = error_text.split('\n')
452
+ frames = []
453
+ error_message = None
454
+
455
+ for line in lines:
456
+ # Panic message: "panic: message"
457
+ if line.startswith('panic:'):
458
+ error_message = line.replace('panic:', '').strip()
459
+ # Stack frame: "function(args)" followed by "\tfile.go:line +0xhex"
460
+ elif '\t' in line and '.go:' in line:
461
+ match = re.search(r'([^/\s]+\.go):(\d+)', line)
462
+ if match:
463
+ frames.append({
464
+ 'file': match.group(1),
465
+ 'line': int(match.group(2)),
466
+ 'function': 'goroutine'
467
+ })
468
+
469
+ return {
470
+ 'frames': frames,
471
+ 'error_type': 'panic',
472
+ 'error_message': error_message,
473
+ 'raw': error_text,
474
+ 'language': 'go'
475
+ }
476
+
477
+ @staticmethod
478
+ def parse_rust_panic(error_text: str) -> Dict[str, Any]:
479
+ """Parse Rust panic message."""
480
+ lines = error_text.split('\n')
481
+ frames = []
482
+ error_message = None
483
+
484
+ for line in lines:
485
+ # Panic message: "thread 'main' panicked at 'message', file.rs:line:col"
486
+ if 'panicked at' in line:
487
+ match = re.search(r"panicked at '([^']+)', ([^:]+):(\d+):(\d+)", line)
488
+ if match:
489
+ error_message = match.group(1)
490
+ frames.append({
491
+ 'file': match.group(2),
492
+ 'line': int(match.group(3)),
493
+ 'column': int(match.group(4)),
494
+ 'function': 'panic'
495
+ })
496
+ # Stack backtrace frames
497
+ elif '.rs:' in line:
498
+ match = re.search(r'([^/\s]+\.rs):(\d+):(\d+)', line)
499
+ if match:
500
+ frames.append({
501
+ 'file': match.group(1),
502
+ 'line': int(match.group(2)),
503
+ 'column': int(match.group(3)),
504
+ 'function': 'unknown'
505
+ })
506
+
507
+ return {
508
+ 'frames': frames,
509
+ 'error_type': 'panic',
510
+ 'error_message': error_message,
511
+ 'raw': error_text,
512
+ 'language': 'rust'
513
+ }
514
+
515
+ @staticmethod
516
+ def parse_java_stack(error_text: str) -> Dict[str, Any]:
517
+ """Parse Java stack trace."""
518
+ lines = error_text.split('\n')
519
+ frames = []
520
+ error_type = None
521
+ error_message = None
522
+
523
+ for line in lines:
524
+ # Exception message: "java.lang.NullPointerException: message"
525
+ if not error_type and 'Exception' in line or 'Error' in line:
526
+ parts = line.split(':', 1)
527
+ error_type = parts[0].strip().split('.')[-1] # Get last part of package
528
+ error_message = parts[1].strip() if len(parts) > 1 else ''
529
+ # Stack frame: "at package.Class.method(File.java:line)"
530
+ elif line.strip().startswith('at '):
531
+ match = re.search(r'at\s+([^(]+)\(([^:]+\.java):(\d+)\)', line)
532
+ if match:
533
+ frames.append({
534
+ 'function': match.group(1),
535
+ 'file': match.group(2),
536
+ 'line': int(match.group(3))
537
+ })
538
+
539
+ return {
540
+ 'frames': frames,
541
+ 'error_type': error_type,
542
+ 'error_message': error_message,
543
+ 'raw': error_text,
544
+ 'language': 'java'
545
+ }
546
+
547
+ @staticmethod
548
+ def parse_traceback(error_text: str) -> Dict[str, Any]:
549
+ """Parse error/traceback with language auto-detection."""
550
+ language = ErrorContext.detect_language(error_text)
551
+
552
+ if language == 'python':
553
+ return ErrorContext.parse_python_traceback(error_text)
554
+ elif language == 'javascript':
555
+ return ErrorContext.parse_javascript_stack(error_text)
556
+ elif language == 'go':
557
+ return ErrorContext.parse_go_panic(error_text)
558
+ elif language == 'rust':
559
+ return ErrorContext.parse_rust_panic(error_text)
560
+ elif language == 'java':
561
+ return ErrorContext.parse_java_stack(error_text)
562
+ else:
563
+ return {'raw': error_text, 'language': 'unknown'}
366
564
 
367
565
  @staticmethod
368
566
  def format_for_ai(error_text: str) -> str:
369
- """Format error for AI consumption."""
567
+ """Format error for AI consumption with language detection."""
370
568
  parsed = ErrorContext.parse_traceback(error_text)
371
569
 
372
570
  if 'error_type' not in parsed:
373
571
  return error_text
374
572
 
573
+ language = parsed.get('language', 'unknown')
375
574
  parts = [
575
+ f"Language: {language.title()}",
376
576
  f"Error Type: {parsed['error_type']}",
377
577
  f"Error Message: {parsed.get('error_message', 'N/A')}",
378
578
  "\nStack Trace:"
379
579
  ]
380
580
 
381
581
  for i, frame in enumerate(parsed.get('frames', []), 1):
382
- parts.append(f" {i}. {frame.get('file', 'unknown')}:{frame.get('line', '?')} in {frame.get('function', 'unknown')}")
582
+ file_loc = f"{frame.get('file', 'unknown')}:{frame.get('line', '?')}"
583
+ if 'column' in frame:
584
+ file_loc += f":{frame.get('column', '?')}"
585
+ parts.append(f" {i}. {file_loc} in {frame.get('function', 'unknown')}")
383
586
  if 'code' in frame:
384
587
  parts.append(f" > {frame['code']}")
385
588
 
@@ -400,29 +603,42 @@ class ErrorContext:
400
603
  class ContextGatherer:
401
604
  """Main context gathering coordinator."""
402
605
 
403
- def __init__(self, project_root: Optional[Path] = None):
606
+ def __init__(self, project_root: Optional[Path] = None, max_file_lines: int = 1000, max_related_files: int = 5):
404
607
  self.project_root = project_root or Path.cwd()
405
608
  self.git = GitContext(self.project_root)
406
609
  self.dependencies = DependencyAnalyzer(self.project_root)
407
610
  self.error_parser = ErrorContext()
611
+ self.max_file_lines = max_file_lines
612
+ self.max_related_files = max_related_files
408
613
 
409
614
  def gather_for_file(
410
615
  self,
411
616
  file_path: Path,
412
617
  include_git: bool = True,
413
618
  include_dependencies: bool = True,
414
- include_related: bool = True
619
+ include_related: bool = True,
620
+ max_lines: Optional[int] = None
415
621
  ) -> Context:
416
- """Gather context for a specific file operation."""
622
+ """Gather context for a specific file operation.
623
+
624
+ Args:
625
+ file_path: Path to the file to gather context for
626
+ include_git: Include git context
627
+ include_dependencies: Include dependency information
628
+ include_related: Include related files
629
+ max_lines: Maximum lines per file (uses instance default if None)
630
+ """
417
631
  context = Context()
632
+ max_lines = max_lines or self.max_file_lines
418
633
 
419
634
  # Add the file itself
420
635
  if file_path.exists():
421
- context.add(ContextItem(
636
+ item = ContextItem(
422
637
  type='file',
423
638
  content=file_path.read_text(),
424
639
  metadata={'path': str(file_path)}
425
- ))
640
+ )
641
+ context.add(item.truncate(max_lines))
426
642
 
427
643
  # Add git context
428
644
  if include_git and self.git.is_git_repo():
@@ -438,21 +654,31 @@ class ContextGatherer:
438
654
  self,
439
655
  error_text: str,
440
656
  file_path: Optional[Path] = None,
441
- include_git: bool = True
657
+ include_git: bool = True,
658
+ max_lines: Optional[int] = None
442
659
  ) -> Context:
443
- """Gather context for error debugging."""
660
+ """Gather context for error debugging.
661
+
662
+ Args:
663
+ error_text: The error message or traceback
664
+ file_path: Optional file path related to the error
665
+ include_git: Include git context
666
+ max_lines: Maximum lines per file (uses instance default if None)
667
+ """
444
668
  context = Context()
669
+ max_lines = max_lines or self.max_file_lines
445
670
 
446
671
  # Add error context
447
672
  context.add(self.error_parser.gather(error_text))
448
673
 
449
674
  # Add file if provided
450
675
  if file_path and file_path.exists():
451
- context.add(ContextItem(
676
+ item = ContextItem(
452
677
  type='file',
453
678
  content=file_path.read_text(),
454
679
  metadata={'path': str(file_path)}
455
- ))
680
+ )
681
+ context.add(item.truncate(max_lines))
456
682
 
457
683
  # Add git context
458
684
  if include_git and self.git.is_git_repo():
@@ -464,16 +690,27 @@ class ContextGatherer:
464
690
  self,
465
691
  file_path: Path,
466
692
  include_git: bool = True,
467
- include_tests: bool = True
693
+ include_tests: bool = True,
694
+ max_lines: Optional[int] = None
468
695
  ) -> Context:
469
- """Gather context for code review."""
696
+ """Gather context for code review.
697
+
698
+ Args:
699
+ file_path: Path to the file to review
700
+ include_git: Include git context
701
+ include_tests: Try to find and include test files
702
+ max_lines: Maximum lines per file (uses instance default if None)
703
+ """
470
704
  context = self.gather_for_file(
471
705
  file_path,
472
706
  include_git=include_git,
473
707
  include_dependencies=True,
474
- include_related=True
708
+ include_related=True,
709
+ max_lines=max_lines
475
710
  )
476
711
 
712
+ max_lines = max_lines or self.max_file_lines
713
+
477
714
  # Try to find test file
478
715
  if include_tests:
479
716
  test_patterns = [
@@ -484,11 +721,13 @@ class ContextGatherer:
484
721
 
485
722
  for test_file in test_patterns:
486
723
  if test_file.exists():
487
- context.add(ContextItem(
724
+ item = ContextItem(
488
725
  type='file',
489
726
  content=test_file.read_text(),
490
727
  metadata={'path': str(test_file), 'is_test': True}
491
- ))
728
+ )
729
+ context.add(item.truncate(max_lines))
492
730
  break
493
731
 
494
732
  return context
733
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-dev-cli
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: A powerful CLI tool for developers using Claude AI with multi-API routing, test generation, code review, and usage tracking
5
5
  Author-email: Julio <thinmanj@users.noreply.github.com>
6
6
  License: MIT
@@ -79,12 +79,17 @@ A powerful command-line tool for developers using Claude AI with multi-API routi
79
79
  - **Variable Substitution**: Use {{variable}} placeholders for dynamic content
80
80
  - **Categories**: Organize templates by category (review, testing, debugging, etc.)
81
81
 
82
- ### 🧠 Context Intelligence (NEW in v0.8.0)
83
- - **Auto-Context**: `--auto-context` flag for intelligent context gathering
82
+ ### 🧠 Context Intelligence (v0.8.0+)
83
+ - **Auto-Context**: `--auto-context` flag on 7 commands for intelligent context gathering
84
+ - `ask`, `review`, `debug`, `refactor` (v0.8.0)
85
+ - `git commit`, `generate tests`, `generate docs` (v0.8.1)
84
86
  - **Git Integration**: Automatically include branch, commits, modified files
85
87
  - **Dependency Analysis**: Parse imports and include related files
86
- - **Error Parsing**: Structured Python traceback parsing
88
+ - **Multi-Language Error Parsing** (v0.8.2): Python, JavaScript/TypeScript, Go, Rust, Java
89
+ - **Context Summary** (v0.8.2): Preview context before API calls with `cdc context summary`
90
+ - **Smart Truncation**: Prevent token limits with configurable file size limits
87
91
  - **Project Memory**: Remember preferences per project
92
+ - **Global Config**: Set context defaults in `~/.claude-dev-cli/config.json`
88
93
 
89
94
  ### 🎒 TOON Format Support (Optional)
90
95
  - **Token Reduction**: 30-60% fewer tokens than JSON
@@ -156,6 +161,9 @@ cdc generate tests mymodule.py -o tests/test_mymodule.py
156
161
  # Generate tests with interactive refinement
157
162
  cdc generate tests mymodule.py --interactive
158
163
 
164
+ # Generate tests with context (includes dependencies, related files) - NEW in v0.8.1
165
+ cdc generate tests mymodule.py --auto-context
166
+
159
167
  # Code review
160
168
  cdc review mymodule.py
161
169
 
@@ -174,6 +182,9 @@ cdc generate docs mymodule.py
174
182
  # Generate docs with interactive refinement
175
183
  cdc generate docs mymodule.py --interactive
176
184
 
185
+ # Generate docs with context (includes dependencies) - NEW in v0.8.1
186
+ cdc generate docs mymodule.py --auto-context
187
+
177
188
  # Refactor with context (includes related files)
178
189
  cdc refactor legacy_code.py --auto-context
179
190
 
@@ -183,9 +194,13 @@ cdc refactor legacy_code.py --interactive
183
194
  # Git commit message
184
195
  git add .
185
196
  cdc git commit
197
+
198
+ # Git commit message with context (includes history, branch) - NEW in v0.8.1
199
+ git add .
200
+ cdc git commit --auto-context
186
201
  ```
187
202
 
188
- ### 4. Context-Aware Operations (NEW in v0.8.0)
203
+ ### 4. Context-Aware Operations (v0.8.0+)
189
204
 
190
205
  ```bash
191
206
  # Auto-context includes: git info, dependencies, related files
@@ -194,9 +209,16 @@ cdc git commit
194
209
  cdc review mymodule.py --auto-context
195
210
  # ✓ Context gathered (git, dependencies, tests)
196
211
 
197
- # Debug with parsed error details
212
+ # Debug with parsed error details (multi-language support)
198
213
  python broken.py 2>&1 | cdc debug -f broken.py --auto-context
214
+ node app.js 2>&1 | cdc debug --auto-context # JavaScript/TypeScript
215
+ go run main.go 2>&1 | cdc debug --auto-context # Go
199
216
  # ✓ Context gathered (error details, git context)
217
+ # Supports: Python, JavaScript, TypeScript, Go, Rust, Java
218
+
219
+ # Preview context before making API calls - NEW in v0.8.2
220
+ cdc context summary mymodule.py
221
+ # Shows: files, sizes, lines, estimated tokens, truncation warnings
200
222
 
201
223
  # Ask questions with file context
202
224
  cdc ask -f mycode.py --auto-context "how can I improve this?"
@@ -1,8 +1,8 @@
1
- claude_dev_cli/__init__.py,sha256=02RgI91qpbUhOce0qyLICGF_7ipeIKMVnPg23VMtzQU,469
2
- claude_dev_cli/cli.py,sha256=iTL4yIOuRo-e3hFmWIZ78p0WdRIaU-XvmE2ZVUprrHA,48371
1
+ claude_dev_cli/__init__.py,sha256=zz-bOjjR01C_VWxFEsIWbdJui0kBuDLpDfJV7yAFFRc,469
2
+ claude_dev_cli/cli.py,sha256=BjTST8JSd8quldFxs09ZzlAPGt-rFIYhKUCWfj1pIn4,55100
3
3
  claude_dev_cli/commands.py,sha256=RKGx2rv56PM6eErvA2uoQ20hY8babuI5jav8nCUyUOk,3964
4
- claude_dev_cli/config.py,sha256=nsfRa6R0hnHc_15OTVZFmizf2Hl-MQXnJf3GBh13D8o,8477
5
- claude_dev_cli/context.py,sha256=eKJGvnHpbd_Xr748DRpZ4uN9mp89ZQ3M80E-hWUovME,16766
4
+ claude_dev_cli/config.py,sha256=OLx0xWDf1RIK6RIxl5OKVS4aOSMZZOKxBDmzfQX-muk,9745
5
+ claude_dev_cli/context.py,sha256=1TlLzpREFZDEIuU7RAtlkjxARKWZpnxHHvK283sUAZE,26714
6
6
  claude_dev_cli/core.py,sha256=yaLjEixDvPzvUy4fJ2UB7nMpPPLyKACjR-RuM-1OQBY,4780
7
7
  claude_dev_cli/history.py,sha256=iQlqgTnXCsyCq5q-XaDl7V5MyPKQ3bx7o_k76-xWSAA,6863
8
8
  claude_dev_cli/secure_storage.py,sha256=TK3WOaU7a0yTOtzdP_t_28fDRp2lovANNAC6MBdm4nQ,7096
@@ -17,9 +17,9 @@ claude_dev_cli/plugins/base.py,sha256=H4HQet1I-a3WLCfE9F06Lp8NuFvVoIlou7sIgyJFK-
17
17
  claude_dev_cli/plugins/diff_editor/__init__.py,sha256=gqR5S2TyIVuq-sK107fegsutQ7Z-sgAIEbtc71FhXIM,101
18
18
  claude_dev_cli/plugins/diff_editor/plugin.py,sha256=M1bUoqpasD3ZNQo36Fu_8g92uySPZyG_ujMbj5UplsU,3073
19
19
  claude_dev_cli/plugins/diff_editor/viewer.py,sha256=1IOXIKw_01ppJx5C1dQt9Kr6U1TdAHT8_iUT5r_q0NM,17169
20
- claude_dev_cli-0.8.0.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
21
- claude_dev_cli-0.8.0.dist-info/METADATA,sha256=9VF6tdI1W1mKdyq1YeeBE-I4NMzPMxmx5RXzhOIFTrI,14580
22
- claude_dev_cli-0.8.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- claude_dev_cli-0.8.0.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
24
- claude_dev_cli-0.8.0.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
25
- claude_dev_cli-0.8.0.dist-info/RECORD,,
20
+ claude_dev_cli-0.8.2.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
21
+ claude_dev_cli-0.8.2.dist-info/METADATA,sha256=4Pyai7Is7NJezxG5ypTm5rML1wEqIlOwHI5GZDlFFs0,15708
22
+ claude_dev_cli-0.8.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ claude_dev_cli-0.8.2.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
24
+ claude_dev_cli-0.8.2.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
25
+ claude_dev_cli-0.8.2.dist-info/RECORD,,