claude-dev-cli 0.7.0__py3-none-any.whl → 0.8.1__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.
Potentially problematic release.
This version of claude-dev-cli might be problematic. Click here for more details.
- claude_dev_cli/__init__.py +1 -1
- claude_dev_cli/cli.py +150 -23
- claude_dev_cli/config.py +31 -0
- claude_dev_cli/context.py +559 -0
- {claude_dev_cli-0.7.0.dist-info → claude_dev_cli-0.8.1.dist-info}/METADATA +51 -5
- {claude_dev_cli-0.7.0.dist-info → claude_dev_cli-0.8.1.dist-info}/RECORD +10 -9
- {claude_dev_cli-0.7.0.dist-info → claude_dev_cli-0.8.1.dist-info}/WHEEL +0 -0
- {claude_dev_cli-0.7.0.dist-info → claude_dev_cli-0.8.1.dist-info}/entry_points.txt +0 -0
- {claude_dev_cli-0.7.0.dist-info → claude_dev_cli-0.8.1.dist-info}/licenses/LICENSE +0 -0
- {claude_dev_cli-0.7.0.dist-info → claude_dev_cli-0.8.1.dist-info}/top_level.txt +0 -0
claude_dev_cli/__init__.py
CHANGED
claude_dev_cli/cli.py
CHANGED
|
@@ -57,6 +57,7 @@ except Exception:
|
|
|
57
57
|
@click.option('-a', '--api', help='API config to use')
|
|
58
58
|
@click.option('-m', '--model', help='Claude model to use')
|
|
59
59
|
@click.option('--stream/--no-stream', default=True, help='Stream response')
|
|
60
|
+
@click.option('--auto-context', is_flag=True, help='Automatically include git, dependencies, and related files')
|
|
60
61
|
@click.pass_context
|
|
61
62
|
def ask(
|
|
62
63
|
ctx: click.Context,
|
|
@@ -65,7 +66,8 @@ def ask(
|
|
|
65
66
|
system: Optional[str],
|
|
66
67
|
api: Optional[str],
|
|
67
68
|
model: Optional[str],
|
|
68
|
-
stream: bool
|
|
69
|
+
stream: bool,
|
|
70
|
+
auto_context: bool
|
|
69
71
|
) -> None:
|
|
70
72
|
"""Ask Claude a question (single-shot mode)."""
|
|
71
73
|
console = ctx.obj['console']
|
|
@@ -73,7 +75,18 @@ def ask(
|
|
|
73
75
|
# Build prompt
|
|
74
76
|
prompt_parts = []
|
|
75
77
|
|
|
76
|
-
if
|
|
78
|
+
# Gather context if requested
|
|
79
|
+
if auto_context and file:
|
|
80
|
+
from claude_dev_cli.context import ContextGatherer
|
|
81
|
+
|
|
82
|
+
with console.status("[bold blue]Gathering context..."):
|
|
83
|
+
gatherer = ContextGatherer()
|
|
84
|
+
context = gatherer.gather_for_file(Path(file))
|
|
85
|
+
context_info = context.format_for_prompt()
|
|
86
|
+
|
|
87
|
+
console.print("[dim]✓ Context gathered[/dim]")
|
|
88
|
+
prompt_parts.append(context_info)
|
|
89
|
+
elif file:
|
|
77
90
|
with open(file, 'r') as f:
|
|
78
91
|
file_content = f.read()
|
|
79
92
|
prompt_parts.append(f"File: {file}\n\n{file_content}\n\n")
|
|
@@ -448,20 +461,37 @@ def generate() -> None:
|
|
|
448
461
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
449
462
|
@click.option('-a', '--api', help='API config to use')
|
|
450
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')
|
|
451
465
|
@click.pass_context
|
|
452
466
|
def gen_tests(
|
|
453
467
|
ctx: click.Context,
|
|
454
468
|
file_path: str,
|
|
455
469
|
output: Optional[str],
|
|
456
470
|
api: Optional[str],
|
|
457
|
-
interactive: bool
|
|
471
|
+
interactive: bool,
|
|
472
|
+
auto_context: bool
|
|
458
473
|
) -> None:
|
|
459
474
|
"""Generate pytest tests for a Python file."""
|
|
460
475
|
console = ctx.obj['console']
|
|
461
476
|
|
|
462
477
|
try:
|
|
463
|
-
|
|
464
|
-
|
|
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)
|
|
465
495
|
|
|
466
496
|
if interactive:
|
|
467
497
|
# Show initial result
|
|
@@ -517,20 +547,37 @@ def gen_tests(
|
|
|
517
547
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
518
548
|
@click.option('-a', '--api', help='API config to use')
|
|
519
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')
|
|
520
551
|
@click.pass_context
|
|
521
552
|
def gen_docs(
|
|
522
553
|
ctx: click.Context,
|
|
523
554
|
file_path: str,
|
|
524
555
|
output: Optional[str],
|
|
525
556
|
api: Optional[str],
|
|
526
|
-
interactive: bool
|
|
557
|
+
interactive: bool,
|
|
558
|
+
auto_context: bool
|
|
527
559
|
) -> None:
|
|
528
560
|
"""Generate documentation for a Python file."""
|
|
529
561
|
console = ctx.obj['console']
|
|
530
562
|
|
|
531
563
|
try:
|
|
532
|
-
|
|
533
|
-
|
|
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)
|
|
534
581
|
|
|
535
582
|
if interactive:
|
|
536
583
|
console.print("\n[bold]Initial Documentation:[/bold]\n")
|
|
@@ -584,19 +631,42 @@ def gen_docs(
|
|
|
584
631
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
585
632
|
@click.option('-a', '--api', help='API config to use')
|
|
586
633
|
@click.option('-i', '--interactive', is_flag=True, help='Interactive follow-up questions')
|
|
634
|
+
@click.option('--auto-context', is_flag=True, help='Automatically include git, dependencies, and related files')
|
|
587
635
|
@click.pass_context
|
|
588
636
|
def review(
|
|
589
637
|
ctx: click.Context,
|
|
590
638
|
file_path: str,
|
|
591
639
|
api: Optional[str],
|
|
592
|
-
interactive: bool
|
|
640
|
+
interactive: bool,
|
|
641
|
+
auto_context: bool
|
|
593
642
|
) -> None:
|
|
594
643
|
"""Review code for bugs and improvements."""
|
|
595
644
|
console = ctx.obj['console']
|
|
596
645
|
|
|
597
646
|
try:
|
|
647
|
+
# Gather context if requested
|
|
648
|
+
context_info = ""
|
|
649
|
+
if auto_context:
|
|
650
|
+
from claude_dev_cli.context import ContextGatherer
|
|
651
|
+
|
|
652
|
+
with console.status("[bold blue]Gathering context..."):
|
|
653
|
+
gatherer = ContextGatherer()
|
|
654
|
+
context = gatherer.gather_for_review(Path(file_path))
|
|
655
|
+
context_info = context.format_for_prompt()
|
|
656
|
+
|
|
657
|
+
console.print("[dim]✓ Context gathered (git, dependencies, tests)[/dim]")
|
|
658
|
+
|
|
598
659
|
with console.status("[bold blue]Reviewing code..."):
|
|
599
|
-
|
|
660
|
+
# If we have context, prepend it to the file analysis
|
|
661
|
+
if context_info:
|
|
662
|
+
# Read file separately for context-aware review
|
|
663
|
+
result = code_review(file_path, api_config_name=api)
|
|
664
|
+
# The context module already includes the file, so we use it differently
|
|
665
|
+
client = ClaudeClient(api_config_name=api)
|
|
666
|
+
enhanced_prompt = f"{context_info}\n\nPlease review this code for bugs and improvements."
|
|
667
|
+
result = client.call(enhanced_prompt)
|
|
668
|
+
else:
|
|
669
|
+
result = code_review(file_path, api_config_name=api)
|
|
600
670
|
|
|
601
671
|
md = Markdown(result)
|
|
602
672
|
console.print(md)
|
|
@@ -633,12 +703,14 @@ def review(
|
|
|
633
703
|
@click.option('-f', '--file', type=click.Path(exists=True), help='File to debug')
|
|
634
704
|
@click.option('-e', '--error', help='Error message to analyze')
|
|
635
705
|
@click.option('-a', '--api', help='API config to use')
|
|
706
|
+
@click.option('--auto-context', is_flag=True, help='Automatically include git context and parse error details')
|
|
636
707
|
@click.pass_context
|
|
637
708
|
def debug(
|
|
638
709
|
ctx: click.Context,
|
|
639
710
|
file: Optional[str],
|
|
640
711
|
error: Optional[str],
|
|
641
|
-
api: Optional[str]
|
|
712
|
+
api: Optional[str],
|
|
713
|
+
auto_context: bool
|
|
642
714
|
) -> None:
|
|
643
715
|
"""Debug code and analyze errors."""
|
|
644
716
|
console = ctx.obj['console']
|
|
@@ -648,13 +720,33 @@ def debug(
|
|
|
648
720
|
if not sys.stdin.isatty():
|
|
649
721
|
stdin_content = sys.stdin.read().strip()
|
|
650
722
|
|
|
723
|
+
error_text = error or stdin_content
|
|
724
|
+
|
|
651
725
|
try:
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
726
|
+
# Gather context if requested
|
|
727
|
+
if auto_context and error_text:
|
|
728
|
+
from claude_dev_cli.context import ContextGatherer
|
|
729
|
+
|
|
730
|
+
with console.status("[bold blue]Gathering context..."):
|
|
731
|
+
gatherer = ContextGatherer()
|
|
732
|
+
file_path = Path(file) if file else None
|
|
733
|
+
context = gatherer.gather_for_error(error_text, file_path=file_path)
|
|
734
|
+
context_info = context.format_for_prompt()
|
|
735
|
+
|
|
736
|
+
console.print("[dim]✓ Context gathered (error details, git context)[/dim]")
|
|
737
|
+
|
|
738
|
+
# Use context-aware analysis
|
|
739
|
+
client = ClaudeClient(api_config_name=api)
|
|
740
|
+
enhanced_prompt = f"{context_info}\n\nPlease analyze this error and suggest fixes."
|
|
741
|
+
result = client.call(enhanced_prompt)
|
|
742
|
+
else:
|
|
743
|
+
# Original behavior
|
|
744
|
+
with console.status("[bold blue]Analyzing error..."):
|
|
745
|
+
result = debug_code(
|
|
746
|
+
file_path=file,
|
|
747
|
+
error_message=error_text,
|
|
748
|
+
api_config_name=api
|
|
749
|
+
)
|
|
658
750
|
|
|
659
751
|
md = Markdown(result)
|
|
660
752
|
console.print(md)
|
|
@@ -669,20 +761,38 @@ def debug(
|
|
|
669
761
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
670
762
|
@click.option('-a', '--api', help='API config to use')
|
|
671
763
|
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
764
|
+
@click.option('--auto-context', is_flag=True, help='Automatically include git, dependencies, and related files')
|
|
672
765
|
@click.pass_context
|
|
673
766
|
def refactor(
|
|
674
767
|
ctx: click.Context,
|
|
675
768
|
file_path: str,
|
|
676
769
|
output: Optional[str],
|
|
677
770
|
api: Optional[str],
|
|
678
|
-
interactive: bool
|
|
771
|
+
interactive: bool,
|
|
772
|
+
auto_context: bool
|
|
679
773
|
) -> None:
|
|
680
774
|
"""Suggest refactoring improvements."""
|
|
681
775
|
console = ctx.obj['console']
|
|
682
776
|
|
|
683
777
|
try:
|
|
684
|
-
|
|
685
|
-
|
|
778
|
+
# Gather context if requested
|
|
779
|
+
if auto_context:
|
|
780
|
+
from claude_dev_cli.context import ContextGatherer
|
|
781
|
+
|
|
782
|
+
with console.status("[bold blue]Gathering context..."):
|
|
783
|
+
gatherer = ContextGatherer()
|
|
784
|
+
context = gatherer.gather_for_file(Path(file_path))
|
|
785
|
+
context_info = context.format_for_prompt()
|
|
786
|
+
|
|
787
|
+
console.print("[dim]✓ Context gathered[/dim]")
|
|
788
|
+
|
|
789
|
+
# Use context-aware refactoring
|
|
790
|
+
client = ClaudeClient(api_config_name=api)
|
|
791
|
+
enhanced_prompt = f"{context_info}\n\nPlease suggest refactoring improvements for the main file."
|
|
792
|
+
result = client.call(enhanced_prompt)
|
|
793
|
+
else:
|
|
794
|
+
with console.status("[bold blue]Analyzing code..."):
|
|
795
|
+
result = refactor_code(file_path, api_config_name=api)
|
|
686
796
|
|
|
687
797
|
if interactive:
|
|
688
798
|
console.print("\n[bold]Initial Refactoring:[/bold]\n")
|
|
@@ -740,14 +850,31 @@ def git() -> None:
|
|
|
740
850
|
|
|
741
851
|
@git.command('commit')
|
|
742
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')
|
|
743
854
|
@click.pass_context
|
|
744
|
-
def git_commit(ctx: click.Context, api: Optional[str]) -> None:
|
|
855
|
+
def git_commit(ctx: click.Context, api: Optional[str], auto_context: bool) -> None:
|
|
745
856
|
"""Generate commit message from staged changes."""
|
|
746
857
|
console = ctx.obj['console']
|
|
747
858
|
|
|
748
859
|
try:
|
|
749
|
-
|
|
750
|
-
|
|
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)
|
|
751
878
|
|
|
752
879
|
console.print("\n[bold green]Suggested commit message:[/bold green]")
|
|
753
880
|
console.print(Panel(result, border_style="green"))
|
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
|
|
|
@@ -25,6 +37,19 @@ class ProjectProfile(BaseModel):
|
|
|
25
37
|
api_config: str # Name of the API config to use
|
|
26
38
|
system_prompt: Optional[str] = None
|
|
27
39
|
allowed_commands: List[str] = Field(default_factory=lambda: ["all"])
|
|
40
|
+
|
|
41
|
+
# Project memory - preferences and patterns
|
|
42
|
+
auto_context: bool = False # Default value for --auto-context flag
|
|
43
|
+
coding_style: Optional[str] = None # Preferred coding style
|
|
44
|
+
test_framework: Optional[str] = None # Preferred test framework
|
|
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
|
|
28
53
|
|
|
29
54
|
|
|
30
55
|
class Config:
|
|
@@ -61,6 +86,7 @@ class Config:
|
|
|
61
86
|
"project_profiles": [],
|
|
62
87
|
"default_model": "claude-3-5-sonnet-20241022",
|
|
63
88
|
"max_tokens": 4096,
|
|
89
|
+
"context": ContextConfig().model_dump(),
|
|
64
90
|
}
|
|
65
91
|
self._save_config(default_config)
|
|
66
92
|
return default_config
|
|
@@ -232,3 +258,8 @@ class Config:
|
|
|
232
258
|
def get_max_tokens(self) -> int:
|
|
233
259
|
"""Get default max tokens."""
|
|
234
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()
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
"""Intelligent context gathering for AI operations."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional, Set
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ContextItem:
|
|
14
|
+
"""A single piece of context information."""
|
|
15
|
+
type: str # 'file', 'git', 'dependency', 'error'
|
|
16
|
+
content: str
|
|
17
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
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
|
+
|
|
37
|
+
def format_for_prompt(self) -> str:
|
|
38
|
+
"""Format this context item for inclusion in a prompt."""
|
|
39
|
+
if self.type == 'file':
|
|
40
|
+
path = self.metadata.get('path', 'unknown')
|
|
41
|
+
truncated_note = " (truncated)" if self.metadata.get('truncated') else ""
|
|
42
|
+
return f"# File: {path}{truncated_note}\n\n{self.content}\n"
|
|
43
|
+
elif self.type == 'git':
|
|
44
|
+
return f"# Git Context\n\n{self.content}\n"
|
|
45
|
+
elif self.type == 'dependency':
|
|
46
|
+
return f"# Dependencies\n\n{self.content}\n"
|
|
47
|
+
elif self.type == 'error':
|
|
48
|
+
return f"# Error Context\n\n{self.content}\n"
|
|
49
|
+
else:
|
|
50
|
+
return self.content
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class Context:
|
|
55
|
+
"""Collection of context items."""
|
|
56
|
+
items: List[ContextItem] = field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
def add(self, item: ContextItem) -> None:
|
|
59
|
+
"""Add a context item."""
|
|
60
|
+
self.items.append(item)
|
|
61
|
+
|
|
62
|
+
def format_for_prompt(self) -> str:
|
|
63
|
+
"""Format all context items for inclusion in a prompt."""
|
|
64
|
+
if not self.items:
|
|
65
|
+
return ""
|
|
66
|
+
|
|
67
|
+
parts = ["# Context Information\n"]
|
|
68
|
+
for item in self.items:
|
|
69
|
+
parts.append(item.format_for_prompt())
|
|
70
|
+
|
|
71
|
+
return "\n".join(parts)
|
|
72
|
+
|
|
73
|
+
def get_by_type(self, context_type: str) -> List[ContextItem]:
|
|
74
|
+
"""Get all context items of a specific type."""
|
|
75
|
+
return [item for item in self.items if item.type == context_type]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class GitContext:
|
|
79
|
+
"""Gather Git-related context."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, cwd: Optional[Path] = None):
|
|
82
|
+
self.cwd = cwd or Path.cwd()
|
|
83
|
+
|
|
84
|
+
def is_git_repo(self) -> bool:
|
|
85
|
+
"""Check if current directory is a git repository."""
|
|
86
|
+
try:
|
|
87
|
+
result = subprocess.run(
|
|
88
|
+
['git', 'rev-parse', '--git-dir'],
|
|
89
|
+
cwd=self.cwd,
|
|
90
|
+
capture_output=True,
|
|
91
|
+
text=True
|
|
92
|
+
)
|
|
93
|
+
return result.returncode == 0
|
|
94
|
+
except Exception:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def get_current_branch(self) -> Optional[str]:
|
|
98
|
+
"""Get the current git branch."""
|
|
99
|
+
try:
|
|
100
|
+
result = subprocess.run(
|
|
101
|
+
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
|
102
|
+
cwd=self.cwd,
|
|
103
|
+
capture_output=True,
|
|
104
|
+
text=True,
|
|
105
|
+
check=True
|
|
106
|
+
)
|
|
107
|
+
return result.stdout.strip()
|
|
108
|
+
except Exception:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
def get_recent_commits(self, count: int = 5) -> List[Dict[str, str]]:
|
|
112
|
+
"""Get recent commit messages."""
|
|
113
|
+
try:
|
|
114
|
+
result = subprocess.run(
|
|
115
|
+
['git', '--no-pager', 'log', f'-{count}', '--pretty=format:%h|%s|%an|%ar'],
|
|
116
|
+
cwd=self.cwd,
|
|
117
|
+
capture_output=True,
|
|
118
|
+
text=True,
|
|
119
|
+
check=True
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
commits = []
|
|
123
|
+
for line in result.stdout.strip().split('\n'):
|
|
124
|
+
if line:
|
|
125
|
+
parts = line.split('|', 3)
|
|
126
|
+
if len(parts) == 4:
|
|
127
|
+
commits.append({
|
|
128
|
+
'hash': parts[0],
|
|
129
|
+
'message': parts[1],
|
|
130
|
+
'author': parts[2],
|
|
131
|
+
'date': parts[3]
|
|
132
|
+
})
|
|
133
|
+
return commits
|
|
134
|
+
except Exception:
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
def get_staged_diff(self) -> Optional[str]:
|
|
138
|
+
"""Get diff of staged changes."""
|
|
139
|
+
try:
|
|
140
|
+
result = subprocess.run(
|
|
141
|
+
['git', '--no-pager', 'diff', '--cached'],
|
|
142
|
+
cwd=self.cwd,
|
|
143
|
+
capture_output=True,
|
|
144
|
+
text=True,
|
|
145
|
+
check=True
|
|
146
|
+
)
|
|
147
|
+
return result.stdout if result.stdout else None
|
|
148
|
+
except Exception:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
def get_unstaged_diff(self) -> Optional[str]:
|
|
152
|
+
"""Get diff of unstaged changes."""
|
|
153
|
+
try:
|
|
154
|
+
result = subprocess.run(
|
|
155
|
+
['git', '--no-pager', 'diff'],
|
|
156
|
+
cwd=self.cwd,
|
|
157
|
+
capture_output=True,
|
|
158
|
+
text=True,
|
|
159
|
+
check=True
|
|
160
|
+
)
|
|
161
|
+
return result.stdout if result.stdout else None
|
|
162
|
+
except Exception:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
def get_modified_files(self) -> List[str]:
|
|
166
|
+
"""Get list of modified files."""
|
|
167
|
+
try:
|
|
168
|
+
result = subprocess.run(
|
|
169
|
+
['git', 'status', '--porcelain'],
|
|
170
|
+
cwd=self.cwd,
|
|
171
|
+
capture_output=True,
|
|
172
|
+
text=True,
|
|
173
|
+
check=True
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
files = []
|
|
177
|
+
for line in result.stdout.strip().split('\n'):
|
|
178
|
+
if line:
|
|
179
|
+
# Format: "XY filename"
|
|
180
|
+
parts = line.strip().split(maxsplit=1)
|
|
181
|
+
if len(parts) == 2:
|
|
182
|
+
files.append(parts[1])
|
|
183
|
+
return files
|
|
184
|
+
except Exception:
|
|
185
|
+
return []
|
|
186
|
+
|
|
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
|
+
"""
|
|
194
|
+
parts = []
|
|
195
|
+
|
|
196
|
+
branch = self.get_current_branch()
|
|
197
|
+
if branch:
|
|
198
|
+
parts.append(f"Branch: {branch}")
|
|
199
|
+
|
|
200
|
+
commits = self.get_recent_commits(5)
|
|
201
|
+
if commits:
|
|
202
|
+
parts.append("\nRecent commits:")
|
|
203
|
+
for commit in commits:
|
|
204
|
+
parts.append(f" {commit['hash']} - {commit['message']} ({commit['date']})")
|
|
205
|
+
|
|
206
|
+
modified = self.get_modified_files()
|
|
207
|
+
if modified:
|
|
208
|
+
parts.append(f"\nModified files: {', '.join(modified[:10])}")
|
|
209
|
+
|
|
210
|
+
if include_diff:
|
|
211
|
+
staged = self.get_staged_diff()
|
|
212
|
+
if staged:
|
|
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}")
|
|
219
|
+
|
|
220
|
+
content = "\n".join(parts) if parts else "No git context available"
|
|
221
|
+
|
|
222
|
+
return ContextItem(
|
|
223
|
+
type='git',
|
|
224
|
+
content=content,
|
|
225
|
+
metadata={'branch': branch, 'modified_count': len(modified)}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class DependencyAnalyzer:
|
|
230
|
+
"""Analyze project dependencies and imports."""
|
|
231
|
+
|
|
232
|
+
def __init__(self, project_root: Path):
|
|
233
|
+
self.project_root = project_root
|
|
234
|
+
|
|
235
|
+
def find_python_imports(self, file_path: Path) -> Set[str]:
|
|
236
|
+
"""Extract imports from a Python file."""
|
|
237
|
+
imports = set()
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
with open(file_path, 'r') as f:
|
|
241
|
+
tree = ast.parse(f.read())
|
|
242
|
+
|
|
243
|
+
for node in ast.walk(tree):
|
|
244
|
+
if isinstance(node, ast.Import):
|
|
245
|
+
for name in node.names:
|
|
246
|
+
imports.add(name.name.split('.')[0])
|
|
247
|
+
elif isinstance(node, ast.ImportFrom):
|
|
248
|
+
if node.module:
|
|
249
|
+
imports.add(node.module.split('.')[0])
|
|
250
|
+
except Exception:
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
return imports
|
|
254
|
+
|
|
255
|
+
def find_related_files(self, file_path: Path, max_depth: int = 2) -> List[Path]:
|
|
256
|
+
"""Find files related to the given file through imports."""
|
|
257
|
+
if not file_path.suffix == '.py':
|
|
258
|
+
return []
|
|
259
|
+
|
|
260
|
+
related = []
|
|
261
|
+
imports = self.find_python_imports(file_path)
|
|
262
|
+
|
|
263
|
+
# Look for local modules
|
|
264
|
+
for imp in imports:
|
|
265
|
+
# Try as module file
|
|
266
|
+
module_file = self.project_root / f"{imp}.py"
|
|
267
|
+
if module_file.exists() and module_file != file_path:
|
|
268
|
+
related.append(module_file)
|
|
269
|
+
|
|
270
|
+
# Try as package
|
|
271
|
+
package_init = self.project_root / imp / "__init__.py"
|
|
272
|
+
if package_init.exists():
|
|
273
|
+
related.append(package_init)
|
|
274
|
+
|
|
275
|
+
return related[:5] # Limit to avoid too many files
|
|
276
|
+
|
|
277
|
+
def get_dependency_files(self) -> List[Path]:
|
|
278
|
+
"""Find dependency configuration files."""
|
|
279
|
+
files = []
|
|
280
|
+
|
|
281
|
+
# Python
|
|
282
|
+
for name in ['requirements.txt', 'setup.py', 'pyproject.toml', 'Pipfile']:
|
|
283
|
+
file = self.project_root / name
|
|
284
|
+
if file.exists():
|
|
285
|
+
files.append(file)
|
|
286
|
+
|
|
287
|
+
# Node.js
|
|
288
|
+
for name in ['package.json', 'package-lock.json']:
|
|
289
|
+
file = self.project_root / name
|
|
290
|
+
if file.exists():
|
|
291
|
+
files.append(file)
|
|
292
|
+
|
|
293
|
+
# Other
|
|
294
|
+
for name in ['Gemfile', 'go.mod', 'Cargo.toml']:
|
|
295
|
+
file = self.project_root / name
|
|
296
|
+
if file.exists():
|
|
297
|
+
files.append(file)
|
|
298
|
+
|
|
299
|
+
return files
|
|
300
|
+
|
|
301
|
+
def gather(self, target_file: Optional[Path] = None) -> ContextItem:
|
|
302
|
+
"""Gather dependency context."""
|
|
303
|
+
parts = []
|
|
304
|
+
|
|
305
|
+
# Include dependency files
|
|
306
|
+
dep_files = self.get_dependency_files()
|
|
307
|
+
if dep_files:
|
|
308
|
+
parts.append("Dependency files:")
|
|
309
|
+
for file in dep_files[:3]: # Limit
|
|
310
|
+
parts.append(f" - {file.name}")
|
|
311
|
+
try:
|
|
312
|
+
content = file.read_text()
|
|
313
|
+
# Include only relevant parts
|
|
314
|
+
if file.suffix == '.json':
|
|
315
|
+
data = json.loads(content)
|
|
316
|
+
if 'dependencies' in data:
|
|
317
|
+
parts.append(f" Dependencies: {', '.join(list(data['dependencies'].keys())[:10])}")
|
|
318
|
+
elif file.suffix == '.txt':
|
|
319
|
+
lines = content.split('\n')[:20]
|
|
320
|
+
parts.append(f" Requirements: {', '.join([l.split('==')[0] for l in lines if l and not l.startswith('#')])}")
|
|
321
|
+
except Exception:
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
# Related files if target specified
|
|
325
|
+
if target_file and target_file.exists():
|
|
326
|
+
related = self.find_related_files(target_file)
|
|
327
|
+
if related:
|
|
328
|
+
parts.append(f"\nRelated files for {target_file.name}:")
|
|
329
|
+
for file in related:
|
|
330
|
+
parts.append(f" - {file.relative_to(self.project_root)}")
|
|
331
|
+
|
|
332
|
+
content = "\n".join(parts) if parts else "No dependency context found"
|
|
333
|
+
|
|
334
|
+
return ContextItem(
|
|
335
|
+
type='dependency',
|
|
336
|
+
content=content,
|
|
337
|
+
metadata={'dependency_files': [str(f) for f in dep_files]}
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class ErrorContext:
|
|
342
|
+
"""Parse and format error context."""
|
|
343
|
+
|
|
344
|
+
@staticmethod
|
|
345
|
+
def parse_traceback(error_text: str) -> Dict[str, Any]:
|
|
346
|
+
"""Parse Python traceback into structured data."""
|
|
347
|
+
lines = error_text.split('\n')
|
|
348
|
+
|
|
349
|
+
# Find traceback start
|
|
350
|
+
traceback_start = -1
|
|
351
|
+
for i, line in enumerate(lines):
|
|
352
|
+
if 'Traceback' in line:
|
|
353
|
+
traceback_start = i
|
|
354
|
+
break
|
|
355
|
+
|
|
356
|
+
if traceback_start == -1:
|
|
357
|
+
return {'raw': error_text}
|
|
358
|
+
|
|
359
|
+
# Extract frames
|
|
360
|
+
frames = []
|
|
361
|
+
current_frame = {}
|
|
362
|
+
|
|
363
|
+
for line in lines[traceback_start + 1:]:
|
|
364
|
+
if line.startswith(' File '):
|
|
365
|
+
if current_frame:
|
|
366
|
+
frames.append(current_frame)
|
|
367
|
+
|
|
368
|
+
# Parse: File "path", line X, in function
|
|
369
|
+
match = re.match(r'\s*File "([^"]+)", line (\d+), in (.+)', line)
|
|
370
|
+
if match:
|
|
371
|
+
current_frame = {
|
|
372
|
+
'file': match.group(1),
|
|
373
|
+
'line': int(match.group(2)),
|
|
374
|
+
'function': match.group(3)
|
|
375
|
+
}
|
|
376
|
+
elif line.startswith(' ') and current_frame:
|
|
377
|
+
current_frame['code'] = line.strip()
|
|
378
|
+
elif line and not line.startswith(' '):
|
|
379
|
+
# Error message
|
|
380
|
+
if current_frame:
|
|
381
|
+
frames.append(current_frame)
|
|
382
|
+
current_frame = {}
|
|
383
|
+
|
|
384
|
+
error_type = line.split(':')[0] if ':' in line else line
|
|
385
|
+
error_message = line.split(':', 1)[1].strip() if ':' in line else ''
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
'frames': frames,
|
|
389
|
+
'error_type': error_type,
|
|
390
|
+
'error_message': error_message,
|
|
391
|
+
'raw': error_text
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return {'frames': frames, 'raw': error_text}
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
def format_for_ai(error_text: str) -> str:
|
|
398
|
+
"""Format error for AI consumption."""
|
|
399
|
+
parsed = ErrorContext.parse_traceback(error_text)
|
|
400
|
+
|
|
401
|
+
if 'error_type' not in parsed:
|
|
402
|
+
return error_text
|
|
403
|
+
|
|
404
|
+
parts = [
|
|
405
|
+
f"Error Type: {parsed['error_type']}",
|
|
406
|
+
f"Error Message: {parsed.get('error_message', 'N/A')}",
|
|
407
|
+
"\nStack Trace:"
|
|
408
|
+
]
|
|
409
|
+
|
|
410
|
+
for i, frame in enumerate(parsed.get('frames', []), 1):
|
|
411
|
+
parts.append(f" {i}. {frame.get('file', 'unknown')}:{frame.get('line', '?')} in {frame.get('function', 'unknown')}")
|
|
412
|
+
if 'code' in frame:
|
|
413
|
+
parts.append(f" > {frame['code']}")
|
|
414
|
+
|
|
415
|
+
return "\n".join(parts)
|
|
416
|
+
|
|
417
|
+
def gather(self, error_text: str) -> ContextItem:
|
|
418
|
+
"""Gather error context."""
|
|
419
|
+
formatted = self.format_for_ai(error_text)
|
|
420
|
+
parsed = self.parse_traceback(error_text)
|
|
421
|
+
|
|
422
|
+
return ContextItem(
|
|
423
|
+
type='error',
|
|
424
|
+
content=formatted,
|
|
425
|
+
metadata=parsed
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class ContextGatherer:
|
|
430
|
+
"""Main context gathering coordinator."""
|
|
431
|
+
|
|
432
|
+
def __init__(self, project_root: Optional[Path] = None, max_file_lines: int = 1000, max_related_files: int = 5):
|
|
433
|
+
self.project_root = project_root or Path.cwd()
|
|
434
|
+
self.git = GitContext(self.project_root)
|
|
435
|
+
self.dependencies = DependencyAnalyzer(self.project_root)
|
|
436
|
+
self.error_parser = ErrorContext()
|
|
437
|
+
self.max_file_lines = max_file_lines
|
|
438
|
+
self.max_related_files = max_related_files
|
|
439
|
+
|
|
440
|
+
def gather_for_file(
|
|
441
|
+
self,
|
|
442
|
+
file_path: Path,
|
|
443
|
+
include_git: bool = True,
|
|
444
|
+
include_dependencies: bool = True,
|
|
445
|
+
include_related: bool = True,
|
|
446
|
+
max_lines: Optional[int] = None
|
|
447
|
+
) -> Context:
|
|
448
|
+
"""Gather context for a specific file operation.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
file_path: Path to the file to gather context for
|
|
452
|
+
include_git: Include git context
|
|
453
|
+
include_dependencies: Include dependency information
|
|
454
|
+
include_related: Include related files
|
|
455
|
+
max_lines: Maximum lines per file (uses instance default if None)
|
|
456
|
+
"""
|
|
457
|
+
context = Context()
|
|
458
|
+
max_lines = max_lines or self.max_file_lines
|
|
459
|
+
|
|
460
|
+
# Add the file itself
|
|
461
|
+
if file_path.exists():
|
|
462
|
+
item = ContextItem(
|
|
463
|
+
type='file',
|
|
464
|
+
content=file_path.read_text(),
|
|
465
|
+
metadata={'path': str(file_path)}
|
|
466
|
+
)
|
|
467
|
+
context.add(item.truncate(max_lines))
|
|
468
|
+
|
|
469
|
+
# Add git context
|
|
470
|
+
if include_git and self.git.is_git_repo():
|
|
471
|
+
context.add(self.git.gather(include_diff=False))
|
|
472
|
+
|
|
473
|
+
# Add dependency context
|
|
474
|
+
if include_dependencies:
|
|
475
|
+
context.add(self.dependencies.gather(target_file=file_path if include_related else None))
|
|
476
|
+
|
|
477
|
+
return context
|
|
478
|
+
|
|
479
|
+
def gather_for_error(
|
|
480
|
+
self,
|
|
481
|
+
error_text: str,
|
|
482
|
+
file_path: Optional[Path] = None,
|
|
483
|
+
include_git: bool = True,
|
|
484
|
+
max_lines: Optional[int] = None
|
|
485
|
+
) -> Context:
|
|
486
|
+
"""Gather context for error debugging.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
error_text: The error message or traceback
|
|
490
|
+
file_path: Optional file path related to the error
|
|
491
|
+
include_git: Include git context
|
|
492
|
+
max_lines: Maximum lines per file (uses instance default if None)
|
|
493
|
+
"""
|
|
494
|
+
context = Context()
|
|
495
|
+
max_lines = max_lines or self.max_file_lines
|
|
496
|
+
|
|
497
|
+
# Add error context
|
|
498
|
+
context.add(self.error_parser.gather(error_text))
|
|
499
|
+
|
|
500
|
+
# Add file if provided
|
|
501
|
+
if file_path and file_path.exists():
|
|
502
|
+
item = ContextItem(
|
|
503
|
+
type='file',
|
|
504
|
+
content=file_path.read_text(),
|
|
505
|
+
metadata={'path': str(file_path)}
|
|
506
|
+
)
|
|
507
|
+
context.add(item.truncate(max_lines))
|
|
508
|
+
|
|
509
|
+
# Add git context
|
|
510
|
+
if include_git and self.git.is_git_repo():
|
|
511
|
+
context.add(self.git.gather(include_diff=False))
|
|
512
|
+
|
|
513
|
+
return context
|
|
514
|
+
|
|
515
|
+
def gather_for_review(
|
|
516
|
+
self,
|
|
517
|
+
file_path: Path,
|
|
518
|
+
include_git: bool = True,
|
|
519
|
+
include_tests: bool = True,
|
|
520
|
+
max_lines: Optional[int] = None
|
|
521
|
+
) -> Context:
|
|
522
|
+
"""Gather context for code review.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
file_path: Path to the file to review
|
|
526
|
+
include_git: Include git context
|
|
527
|
+
include_tests: Try to find and include test files
|
|
528
|
+
max_lines: Maximum lines per file (uses instance default if None)
|
|
529
|
+
"""
|
|
530
|
+
context = self.gather_for_file(
|
|
531
|
+
file_path,
|
|
532
|
+
include_git=include_git,
|
|
533
|
+
include_dependencies=True,
|
|
534
|
+
include_related=True,
|
|
535
|
+
max_lines=max_lines
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
max_lines = max_lines or self.max_file_lines
|
|
539
|
+
|
|
540
|
+
# Try to find test file
|
|
541
|
+
if include_tests:
|
|
542
|
+
test_patterns = [
|
|
543
|
+
self.project_root / "tests" / f"test_{file_path.name}",
|
|
544
|
+
self.project_root / f"test_{file_path.name}",
|
|
545
|
+
file_path.parent / f"test_{file_path.name}"
|
|
546
|
+
]
|
|
547
|
+
|
|
548
|
+
for test_file in test_patterns:
|
|
549
|
+
if test_file.exists():
|
|
550
|
+
item = ContextItem(
|
|
551
|
+
type='file',
|
|
552
|
+
content=test_file.read_text(),
|
|
553
|
+
metadata={'path': str(test_file), 'is_test': True}
|
|
554
|
+
)
|
|
555
|
+
context.add(item.truncate(max_lines))
|
|
556
|
+
break
|
|
557
|
+
|
|
558
|
+
return context
|
|
559
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-dev-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
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,6 +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 (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)
|
|
86
|
+
- **Git Integration**: Automatically include branch, commits, modified files
|
|
87
|
+
- **Dependency Analysis**: Parse imports and include related files
|
|
88
|
+
- **Error Parsing**: Structured Python traceback parsing
|
|
89
|
+
- **Smart Truncation**: Prevent token limits with configurable file size limits
|
|
90
|
+
- **Project Memory**: Remember preferences per project
|
|
91
|
+
- **Global Config**: Set context defaults in `~/.claude-dev-cli/config.json`
|
|
92
|
+
|
|
82
93
|
### 🎒 TOON Format Support (Optional)
|
|
83
94
|
- **Token Reduction**: 30-60% fewer tokens than JSON
|
|
84
95
|
- **Cost Savings**: Reduce API costs significantly
|
|
@@ -149,14 +160,20 @@ cdc generate tests mymodule.py -o tests/test_mymodule.py
|
|
|
149
160
|
# Generate tests with interactive refinement
|
|
150
161
|
cdc generate tests mymodule.py --interactive
|
|
151
162
|
|
|
163
|
+
# Generate tests with context (includes dependencies, related files) - NEW in v0.8.1
|
|
164
|
+
cdc generate tests mymodule.py --auto-context
|
|
165
|
+
|
|
152
166
|
# Code review
|
|
153
167
|
cdc review mymodule.py
|
|
154
168
|
|
|
169
|
+
# Code review with auto-context (includes git, dependencies, tests)
|
|
170
|
+
cdc review mymodule.py --auto-context
|
|
171
|
+
|
|
155
172
|
# Code review with interactive follow-up questions
|
|
156
173
|
cdc review mymodule.py --interactive
|
|
157
174
|
|
|
158
|
-
# Debug errors
|
|
159
|
-
python script.py 2>&1 | cdc debug
|
|
175
|
+
# Debug errors with intelligent error parsing
|
|
176
|
+
python script.py 2>&1 | cdc debug --auto-context
|
|
160
177
|
|
|
161
178
|
# Generate documentation
|
|
162
179
|
cdc generate docs mymodule.py
|
|
@@ -164,8 +181,11 @@ cdc generate docs mymodule.py
|
|
|
164
181
|
# Generate docs with interactive refinement
|
|
165
182
|
cdc generate docs mymodule.py --interactive
|
|
166
183
|
|
|
167
|
-
#
|
|
168
|
-
cdc
|
|
184
|
+
# Generate docs with context (includes dependencies) - NEW in v0.8.1
|
|
185
|
+
cdc generate docs mymodule.py --auto-context
|
|
186
|
+
|
|
187
|
+
# Refactor with context (includes related files)
|
|
188
|
+
cdc refactor legacy_code.py --auto-context
|
|
169
189
|
|
|
170
190
|
# Refactor with interactive refinement
|
|
171
191
|
cdc refactor legacy_code.py --interactive
|
|
@@ -173,6 +193,32 @@ cdc refactor legacy_code.py --interactive
|
|
|
173
193
|
# Git commit message
|
|
174
194
|
git add .
|
|
175
195
|
cdc git commit
|
|
196
|
+
|
|
197
|
+
# Git commit message with context (includes history, branch) - NEW in v0.8.1
|
|
198
|
+
git add .
|
|
199
|
+
cdc git commit --auto-context
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 4. Context-Aware Operations (NEW in v0.8.0)
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Auto-context includes: git info, dependencies, related files
|
|
206
|
+
|
|
207
|
+
# Review with full project context
|
|
208
|
+
cdc review mymodule.py --auto-context
|
|
209
|
+
# ✓ Context gathered (git, dependencies, tests)
|
|
210
|
+
|
|
211
|
+
# Debug with parsed error details
|
|
212
|
+
python broken.py 2>&1 | cdc debug -f broken.py --auto-context
|
|
213
|
+
# ✓ Context gathered (error details, git context)
|
|
214
|
+
|
|
215
|
+
# Ask questions with file context
|
|
216
|
+
cdc ask -f mycode.py --auto-context "how can I improve this?"
|
|
217
|
+
# ✓ Context gathered
|
|
218
|
+
|
|
219
|
+
# Refactor with related files
|
|
220
|
+
cdc refactor app.py --auto-context
|
|
221
|
+
# Automatically includes imported modules and dependencies
|
|
176
222
|
```
|
|
177
223
|
|
|
178
224
|
### 5. Custom Templates
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
claude_dev_cli/__init__.py,sha256=
|
|
2
|
-
claude_dev_cli/cli.py,sha256=
|
|
1
|
+
claude_dev_cli/__init__.py,sha256=0g1KP2ohJ45KCYW9EkcRcTA885E4Gqm1kDQbmIOdC-k,469
|
|
2
|
+
claude_dev_cli/cli.py,sha256=FjVq9QanwxM8WqSZPPM4-b50v9Ycy9ZDHEg9gBo-kCk,51304
|
|
3
3
|
claude_dev_cli/commands.py,sha256=RKGx2rv56PM6eErvA2uoQ20hY8babuI5jav8nCUyUOk,3964
|
|
4
|
-
claude_dev_cli/config.py,sha256=
|
|
4
|
+
claude_dev_cli/config.py,sha256=OLx0xWDf1RIK6RIxl5OKVS4aOSMZZOKxBDmzfQX-muk,9745
|
|
5
|
+
claude_dev_cli/context.py,sha256=Z3QYq4ZHAqpuv_xPZtXcBeWf0LCelzkybj8cBz2nBAo,19523
|
|
5
6
|
claude_dev_cli/core.py,sha256=yaLjEixDvPzvUy4fJ2UB7nMpPPLyKACjR-RuM-1OQBY,4780
|
|
6
7
|
claude_dev_cli/history.py,sha256=iQlqgTnXCsyCq5q-XaDl7V5MyPKQ3bx7o_k76-xWSAA,6863
|
|
7
8
|
claude_dev_cli/secure_storage.py,sha256=TK3WOaU7a0yTOtzdP_t_28fDRp2lovANNAC6MBdm4nQ,7096
|
|
@@ -16,9 +17,9 @@ claude_dev_cli/plugins/base.py,sha256=H4HQet1I-a3WLCfE9F06Lp8NuFvVoIlou7sIgyJFK-
|
|
|
16
17
|
claude_dev_cli/plugins/diff_editor/__init__.py,sha256=gqR5S2TyIVuq-sK107fegsutQ7Z-sgAIEbtc71FhXIM,101
|
|
17
18
|
claude_dev_cli/plugins/diff_editor/plugin.py,sha256=M1bUoqpasD3ZNQo36Fu_8g92uySPZyG_ujMbj5UplsU,3073
|
|
18
19
|
claude_dev_cli/plugins/diff_editor/viewer.py,sha256=1IOXIKw_01ppJx5C1dQt9Kr6U1TdAHT8_iUT5r_q0NM,17169
|
|
19
|
-
claude_dev_cli-0.
|
|
20
|
-
claude_dev_cli-0.
|
|
21
|
-
claude_dev_cli-0.
|
|
22
|
-
claude_dev_cli-0.
|
|
23
|
-
claude_dev_cli-0.
|
|
24
|
-
claude_dev_cli-0.
|
|
20
|
+
claude_dev_cli-0.8.1.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
|
|
21
|
+
claude_dev_cli-0.8.1.dist-info/METADATA,sha256=3QFSQUey2f4wMFgtrImtuPgQoD_-Bk2me891eCrCyhY,15223
|
|
22
|
+
claude_dev_cli-0.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
claude_dev_cli-0.8.1.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
|
|
24
|
+
claude_dev_cli-0.8.1.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
|
|
25
|
+
claude_dev_cli-0.8.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|