janito 0.7.0__py3-none-any.whl → 0.8.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 (112) hide show
  1. janito/__main__.py +127 -141
  2. janito/agents/__init__.py +22 -22
  3. janito/agents/agent.py +24 -27
  4. janito/agents/claudeai.py +41 -45
  5. janito/agents/deepseekai.py +47 -0
  6. janito/change/applied_blocks.py +34 -0
  7. janito/change/applier.py +167 -0
  8. janito/change/edit_blocks.py +148 -0
  9. janito/change/finder.py +72 -0
  10. janito/change/request.py +144 -0
  11. janito/change/validator.py +87 -269
  12. janito/change/view/content.py +63 -0
  13. janito/change/{viewer → view}/diff.py +44 -43
  14. janito/change/view/panels.py +201 -0
  15. janito/change/view/sections.py +69 -0
  16. janito/change/view/styling.py +140 -0
  17. janito/change/view/summary.py +37 -0
  18. janito/change/{viewer → view}/themes.py +62 -55
  19. janito/change/view/viewer.py +59 -0
  20. janito/cli/__init__.py +1 -1
  21. janito/cli/commands.py +68 -88
  22. janito/cli/functions.py +66 -111
  23. janito/common.py +132 -79
  24. janito/config.py +99 -101
  25. janito/data/change_prompt.txt +81 -0
  26. janito/data/system_prompt.txt +3 -0
  27. janito/qa.py +56 -57
  28. janito/version.py +22 -22
  29. janito/workspace/__init__.py +8 -6
  30. janito/workspace/analysis.py +120 -120
  31. janito/workspace/{types.py → models.py} +97 -98
  32. janito/workspace/show.py +115 -141
  33. janito/workspace/stats.py +42 -43
  34. janito/workspace/workset.py +135 -108
  35. janito/workspace/workspace.py +335 -114
  36. janito-0.8.0.dist-info/METADATA +106 -0
  37. janito-0.8.0.dist-info/RECORD +40 -0
  38. {janito-0.7.0.dist-info → janito-0.8.0.dist-info}/licenses/LICENSE +20 -20
  39. janito/__init__.py +0 -2
  40. janito/agents/openai.py +0 -57
  41. janito/agents/test.py +0 -34
  42. janito/change/__init__.py +0 -32
  43. janito/change/__main__.py +0 -0
  44. janito/change/analysis/__init__.py +0 -23
  45. janito/change/analysis/__main__.py +0 -7
  46. janito/change/analysis/analyze.py +0 -62
  47. janito/change/analysis/formatting.py +0 -78
  48. janito/change/analysis/options.py +0 -81
  49. janito/change/analysis/prompts.py +0 -90
  50. janito/change/analysis/view/__init__.py +0 -9
  51. janito/change/analysis/view/terminal.py +0 -181
  52. janito/change/applier/__init__.py +0 -5
  53. janito/change/applier/file.py +0 -58
  54. janito/change/applier/main.py +0 -156
  55. janito/change/applier/text.py +0 -247
  56. janito/change/applier/workspace_dir.py +0 -58
  57. janito/change/core.py +0 -124
  58. janito/change/history.py +0 -44
  59. janito/change/operations.py +0 -7
  60. janito/change/parser.py +0 -287
  61. janito/change/play.py +0 -54
  62. janito/change/preview.py +0 -82
  63. janito/change/prompts.py +0 -121
  64. janito/change/test.py +0 -0
  65. janito/change/viewer/__init__.py +0 -11
  66. janito/change/viewer/content.py +0 -66
  67. janito/change/viewer/panels.py +0 -533
  68. janito/change/viewer/styling.py +0 -114
  69. janito/clear_statement_parser/clear_statement_format.txt +0 -328
  70. janito/clear_statement_parser/examples.txt +0 -326
  71. janito/clear_statement_parser/models.py +0 -104
  72. janito/clear_statement_parser/parser.py +0 -496
  73. janito/cli/base.py +0 -30
  74. janito/cli/history.py +0 -61
  75. janito/cli/registry.py +0 -26
  76. janito/demo/__init__.py +0 -4
  77. janito/demo/data.py +0 -13
  78. janito/demo/mock_data.py +0 -20
  79. janito/demo/operations.py +0 -45
  80. janito/demo/runner.py +0 -59
  81. janito/demo/scenarios.py +0 -32
  82. janito/prompt.py +0 -36
  83. janito/review.py +0 -13
  84. janito/search_replace/README.md +0 -192
  85. janito/search_replace/__init__.py +0 -7
  86. janito/search_replace/__main__.py +0 -21
  87. janito/search_replace/core.py +0 -120
  88. janito/search_replace/logger.py +0 -35
  89. janito/search_replace/parser.py +0 -52
  90. janito/search_replace/play.py +0 -61
  91. janito/search_replace/replacer.py +0 -36
  92. janito/search_replace/searcher.py +0 -411
  93. janito/search_replace/strategy_result.py +0 -10
  94. janito/shell/__init__.py +0 -38
  95. janito/shell/bus.py +0 -31
  96. janito/shell/commands.py +0 -136
  97. janito/shell/history.py +0 -20
  98. janito/shell/processor.py +0 -32
  99. janito/shell/prompt.py +0 -48
  100. janito/shell/registry.py +0 -60
  101. janito/tui/__init__.py +0 -21
  102. janito/tui/base.py +0 -22
  103. janito/tui/flows/__init__.py +0 -5
  104. janito/tui/flows/changes.py +0 -65
  105. janito/tui/flows/content.py +0 -128
  106. janito/tui/flows/selection.py +0 -117
  107. janito/tui/screens/__init__.py +0 -3
  108. janito/tui/screens/app.py +0 -1
  109. janito-0.7.0.dist-info/METADATA +0 -167
  110. janito-0.7.0.dist-info/RECORD +0 -96
  111. {janito-0.7.0.dist-info → janito-0.8.0.dist-info}/WHEEL +0 -0
  112. {janito-0.7.0.dist-info → janito-0.8.0.dist-info}/entry_points.txt +0 -0
@@ -1,121 +1,121 @@
1
- from collections import defaultdict
2
- from pathlib import Path
3
- from typing import Dict, List
4
-
5
- from rich.columns import Columns
6
- from rich.console import Console, Group
7
- from rich.panel import Panel
8
- from rich.rule import Rule
9
- from janito.config import config
10
-
11
- def analyze_workspace_content(content: str) -> None:
12
- """Show statistics about the scanned content"""
13
- if not content:
14
- return
15
-
16
- # Collect include paths
17
- paths = []
18
- if config.include:
19
- for path in config.include:
20
- is_recursive = path in config.recursive
21
- path_str = str(path.relative_to(config.workspace_dir))
22
- paths.append(f"{path_str}/*" if is_recursive else f"{path_str}/")
23
- else:
24
- # Use workspace_dir as fallback when no include paths specified
25
- paths.append("./")
26
-
27
- console = Console()
28
-
29
- dir_counts: Dict[str, int] = defaultdict(int)
30
- dir_sizes: Dict[str, int] = defaultdict(int)
31
- file_types: Dict[str, int] = defaultdict(int)
32
- current_path = None
33
- current_content = []
34
-
35
- for line in content.split('\n'):
36
- if line.startswith('<path>'):
37
- path = Path(line.replace('<path>', '').replace('</path>', '').strip())
38
- current_path = str(path.parent)
39
- dir_counts[current_path] += 1
40
- file_types[path.suffix.lower() or 'no_ext'] += 1
41
- elif line.startswith('<content>'):
42
- current_content = []
43
- elif line.startswith('</content>'):
44
- content_size = sum(len(line.encode('utf-8')) for line in current_content)
45
- if current_path:
46
- dir_sizes[current_path] += content_size
47
- current_content = []
48
- elif current_content is not None:
49
- current_content.append(line)
50
-
51
- console = Console()
52
-
53
- # Directory statistics
54
- dir_stats = [
55
- f"📁 {directory}/ [{count} file(s), {_format_size(size)}]"
56
- for directory, (count, size) in (
57
- (d, (dir_counts[d], dir_sizes[d]))
58
- for d in sorted(dir_counts.keys())
59
- )
60
- ]
61
-
62
- # File type statistics
63
- type_stats = [
64
- f"📄 .{ext.lstrip('.')} [{count} file(s)]" if ext != 'no_ext' else f"📄 {ext} [{count} file(s)]"
65
- for ext, count in sorted(file_types.items())
66
- ]
67
-
68
- # Create grouped content with styled separators
69
- content_sections = []
70
-
71
- if paths:
72
- # Group paths with their stats
73
- path_stats = []
74
- for path in sorted(set(paths)):
75
- base_path = Path(path.rstrip("/*"))
76
- total_files = sum(1 for d, count in dir_counts.items()
77
- if Path(d).is_relative_to(base_path))
78
- total_size = sum(size for d, size in dir_sizes.items()
79
- if Path(d).is_relative_to(base_path))
80
- path_stats.append(f"{path} [{total_files} file(s), {_format_size(total_size)}]")
81
-
82
- content_sections.extend([
83
- "[bold yellow]📌 Included Paths[/bold yellow]",
84
- Rule(style="yellow"),
85
- Columns(path_stats, equal=True, expand=True),
86
- "\n"
87
- ])
88
-
89
- # Add directory structure section only in verbose mode
90
- if config.verbose:
91
- content_sections.extend([
92
- "[bold magenta]📂 Directory Structure[/bold magenta]",
93
- Rule(style="magenta"),
94
- Columns(dir_stats, equal=True, expand=True),
95
- "\n"
96
- ])
97
-
98
- # Always show file types section
99
- content_sections.extend([
100
- "[bold cyan]📑 File Types[/bold cyan]",
101
- Rule(style="cyan"),
102
- Columns(type_stats, equal=True, expand=True)
103
- ])
104
-
105
- content = Group(*content_sections)
106
-
107
- # Display workset analysis in panel
108
- console.print("\n")
109
- console.print(Panel(
110
- content,
111
- title="[bold blue]Workset Analysis[/bold blue]",
112
- title_align="center"
113
- ))
114
-
115
- def _format_size(size_bytes: int) -> str:
116
- """Format size in bytes to human readable format"""
117
- for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
118
- if size_bytes < 1024.0:
119
- break
120
- size_bytes /= 1024.0
1
+ from collections import defaultdict
2
+ from pathlib import Path
3
+ from typing import Dict, List
4
+
5
+ from rich.columns import Columns
6
+ from rich.console import Console, Group
7
+ from rich.panel import Panel
8
+ from rich.rule import Rule
9
+ from janito.config import config
10
+
11
+ def analyze_workspace_content(content: str) -> None:
12
+ """Show statistics about the scanned content"""
13
+ if not content:
14
+ return
15
+
16
+ # Collect include paths
17
+ paths = []
18
+ if config.include:
19
+ for path in config.include:
20
+ is_recursive = path in config.recursive
21
+ path_str = str(path.relative_to(config.workspace_dir))
22
+ paths.append(f"{path_str}/*" if is_recursive else f"{path_str}/")
23
+ else:
24
+ # Use workspace_dir as fallback when no include paths specified
25
+ paths.append("./")
26
+
27
+ console = Console()
28
+
29
+ dir_counts: Dict[str, int] = defaultdict(int)
30
+ dir_sizes: Dict[str, int] = defaultdict(int)
31
+ file_types: Dict[str, int] = defaultdict(int)
32
+ current_path = None
33
+ current_content = []
34
+
35
+ for line in content.split('\n'):
36
+ if line.startswith('<path>'):
37
+ path = Path(line.replace('<path>', '').replace('</path>', '').strip())
38
+ current_path = str(path.parent)
39
+ dir_counts[current_path] += 1
40
+ file_types[path.suffix.lower() or 'no_ext'] += 1
41
+ elif line.startswith('<content>'):
42
+ current_content = []
43
+ elif line.startswith('</content>'):
44
+ content_size = sum(len(line.encode('utf-8')) for line in current_content)
45
+ if current_path:
46
+ dir_sizes[current_path] += content_size
47
+ current_content = []
48
+ elif current_content is not None:
49
+ current_content.append(line)
50
+
51
+ console = Console()
52
+
53
+ # Directory statistics
54
+ dir_stats = [
55
+ f"📁 {directory}/ [{count} file(s), {_format_size(size)}]"
56
+ for directory, (count, size) in (
57
+ (d, (dir_counts[d], dir_sizes[d]))
58
+ for d in sorted(dir_counts.keys())
59
+ )
60
+ ]
61
+
62
+ # File type statistics
63
+ type_stats = [
64
+ f"📄 .{ext.lstrip('.')} [{count} file(s)]" if ext != 'no_ext' else f"📄 {ext} [{count} file(s)]"
65
+ for ext, count in sorted(file_types.items())
66
+ ]
67
+
68
+ # Create grouped content with styled separators
69
+ content_sections = []
70
+
71
+ if paths:
72
+ # Group paths with their stats
73
+ path_stats = []
74
+ for path in sorted(set(paths)):
75
+ base_path = Path(path.rstrip("/*"))
76
+ total_files = sum(1 for d, count in dir_counts.items()
77
+ if Path(d).is_relative_to(base_path))
78
+ total_size = sum(size for d, size in dir_sizes.items()
79
+ if Path(d).is_relative_to(base_path))
80
+ path_stats.append(f"{path} [{total_files} file(s), {_format_size(total_size)}]")
81
+
82
+ content_sections.extend([
83
+ "[bold yellow]📌 Included Paths[/bold yellow]",
84
+ Rule(style="yellow"),
85
+ Columns(path_stats, equal=True, expand=True),
86
+ "\n"
87
+ ])
88
+
89
+ # Add directory structure section only in verbose mode
90
+ if config.verbose:
91
+ content_sections.extend([
92
+ "[bold magenta]📂 Directory Structure[/bold magenta]",
93
+ Rule(style="magenta"),
94
+ Columns(dir_stats, equal=True, expand=True),
95
+ "\n"
96
+ ])
97
+
98
+ # Always show file types section
99
+ content_sections.extend([
100
+ "[bold cyan]📑 File Types[/bold cyan]",
101
+ Rule(style="cyan"),
102
+ Columns(type_stats, equal=True, expand=True)
103
+ ])
104
+
105
+ content = Group(*content_sections)
106
+
107
+ # Display workset analysis in panel
108
+ console.print("\n")
109
+ console.print(Panel(
110
+ content,
111
+ title="[bold blue]Workset Analysis[/bold blue]",
112
+ title_align="center"
113
+ ))
114
+
115
+ def _format_size(size_bytes: int) -> str:
116
+ """Format size in bytes to human readable format"""
117
+ for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
118
+ if size_bytes < 1024.0:
119
+ break
120
+ size_bytes /= 1024.0
121
121
  return f"{size_bytes:.1f} {unit}"
@@ -1,98 +1,97 @@
1
- from dataclasses import dataclass, field
2
- from pathlib import Path
3
- from typing import List, Dict, Set, Tuple
4
- from sys import maxsize
5
- from janito.config import config
6
- from enum import auto, Enum
7
-
8
- @dataclass
9
- class FileInfo:
10
- """Represents a file's basic information"""
11
- name: str # Relative path from workspace root
12
- content: str
13
- seconds_ago: int = 0 # Seconds since last modification
14
-
15
- class ScanType(Enum):
16
- """Type of path scanning"""
17
- PLAIN = auto()
18
- RECURSIVE = auto()
19
-
20
- @dataclass
21
- class ScanPath:
22
- """Represents a path to be scanned"""
23
- path: Path
24
- scan_type: ScanType = ScanType.PLAIN
25
-
26
- @property
27
- def is_recursive(self) -> bool:
28
- return self.scan_type == ScanType.RECURSIVE
29
-
30
- @dataclass
31
- class WorksetContent:
32
- """Represents workset content and statistics."""
33
- files: List[FileInfo] = field(default_factory=list)
34
- scanned_paths: Set[Path] = field(default_factory=set)
35
- dir_counts: Dict[str, int] = field(default_factory=dict)
36
- dir_sizes: Dict[str, int] = field(default_factory=dict)
37
- file_types: Dict[str, int] = field(default_factory=dict)
38
- scan_completed: bool = False
39
- analyzed: bool = False
40
-
41
- def clear(self) -> None:
42
- """Reset all content"""
43
- self.files = []
44
- self.scanned_paths = set()
45
- self.dir_counts = {}
46
- self.dir_sizes = {}
47
- self.file_types = {}
48
- self.scan_completed = False
49
- self.analyzed = False
50
-
51
- def add_file(self, file_info: FileInfo) -> None:
52
- """Add a file to the content and update statistics"""
53
- self.files.append(file_info)
54
-
55
- # Update file type stats
56
- suffix = Path(file_info.name).suffix.lower() or 'no_ext'
57
- self.file_types[suffix] = self.file_types.get(suffix, 0) + 1
58
-
59
- # Update directory stats
60
- dir_path = str(Path(file_info.name).parent)
61
- self.dir_counts[dir_path] = self.dir_counts.get(dir_path, 0) + 1
62
- self.dir_sizes[dir_path] = self.dir_sizes.get(dir_path, 0) + len(file_info.content.encode('utf-8'))
63
-
64
- def get_file_info(self, time_ranges: List[int] = None) -> Tuple[List[FileInfo], List[FileInfo], List[FileInfo], List[FileInfo]]:
65
- """Get file information grouped into 4 blocks based on modification time ranges."""
66
- if not time_ranges:
67
- time_ranges = [300, 3600, 86400, maxsize] # 5min, 1h, 24h, rest
68
- else:
69
- time_ranges = [int(x) for x in time_ranges[:3]] + [maxsize]
70
- if len(time_ranges) < 4:
71
- time_ranges.extend([maxsize] * (4 - len(time_ranges)))
72
- time_ranges.sort()
73
-
74
- blocks = [[] for _ in range(4)]
75
-
76
- def get_range_index(seconds: int) -> int:
77
- for i, threshold in enumerate(time_ranges):
78
- if seconds <= threshold:
79
- return i
80
- return len(time_ranges) - 1
81
-
82
- # Sort and group files by modification time
83
- sorted_files = sorted(self.files, key=lambda f: f.seconds_ago)
84
- for file_info in sorted_files:
85
- block_idx = get_range_index(file_info.seconds_ago)
86
- blocks[block_idx].append(file_info)
87
-
88
- return tuple(blocks)
89
-
90
- @property
91
- def content_size(self) -> int:
92
- """Get total content size in bytes"""
93
- return sum(len(f.content.encode('utf-8')) for f in self.files)
94
-
95
- @property
96
- def file_count(self) -> int:
97
- """Get total number of files"""
98
- return len(self.files)
1
+ from dataclasses import dataclass, field
2
+ from pathlib import Path
3
+ from typing import List, Dict, Set, Tuple
4
+ from sys import maxsize
5
+ from janito.config import config
6
+ from enum import auto, Enum
7
+
8
+ @dataclass
9
+ class FileInfo:
10
+ """Represents a file's basic information"""
11
+ name: str # Relative path from workspace root
12
+ content: str
13
+ seconds_ago: int = 0 # Seconds since last modification
14
+
15
+ def __lt__(self, other: 'FileInfo') -> bool:
16
+ """Enable sorting by filepath."""
17
+ if not isinstance(other, FileInfo):
18
+ return NotImplemented
19
+ return self.name < other.name
20
+
21
+ def __eq__(self, other: object) -> bool:
22
+ """Enable equality comparison by filepath."""
23
+ if not isinstance(other, FileInfo):
24
+ return NotImplemented
25
+ return self.name == other.name
26
+
27
+ def __hash__(self) -> int:
28
+ """Enable using FileInfo in sets by using filepath as hash."""
29
+ return hash(self.name)
30
+
31
+ class ScanType(Enum):
32
+ """Type of path scanning"""
33
+ PLAIN = auto()
34
+ RECURSIVE = auto()
35
+
36
+ @dataclass
37
+ class ScanPath:
38
+ """Represents a path to be scanned"""
39
+ path: Path
40
+ scan_type: ScanType = ScanType.PLAIN
41
+
42
+ @property
43
+ def is_recursive(self) -> bool:
44
+ return self.scan_type == ScanType.RECURSIVE
45
+
46
+ @classmethod
47
+ def validate(cls, path: Path) -> None:
48
+ """Validate path is relative and exists"""
49
+ if path.is_absolute():
50
+ raise ValueError(f"Path must be relative: {path}")
51
+ if not path.exists():
52
+ raise ValueError(f"Path does not exist: {path}")
53
+
54
+ @dataclass
55
+ class WorksetContent:
56
+ """Represents workset content and statistics."""
57
+ files: List[FileInfo] = field(default_factory=list)
58
+ scanned_paths: Set[Path] = field(default_factory=set)
59
+ dir_counts: Dict[str, int] = field(default_factory=dict)
60
+ dir_sizes: Dict[str, int] = field(default_factory=dict)
61
+ file_types: Dict[str, int] = field(default_factory=dict)
62
+ scan_completed: bool = False
63
+ analyzed: bool = False
64
+
65
+ def clear(self) -> None:
66
+ """Reset all content"""
67
+ self.files = []
68
+ self.scanned_paths = set()
69
+ self.dir_counts = {}
70
+ self.dir_sizes = {}
71
+ self.file_types = {}
72
+ self.scan_completed = False
73
+ self.analyzed = False
74
+
75
+ def add_file(self, file_info: FileInfo) -> None:
76
+ """Add a file to the content and update statistics"""
77
+ self.files.append(file_info)
78
+
79
+ # Update file type stats
80
+ suffix = Path(file_info.name).suffix.lower() or 'no_ext'
81
+ self.file_types[suffix] = self.file_types.get(suffix, 0) + 1
82
+
83
+ # Update directory stats
84
+ dir_path = str(Path(file_info.name).parent)
85
+ self.dir_counts[dir_path] = self.dir_counts.get(dir_path, 0) + 1
86
+ self.dir_sizes[dir_path] = self.dir_sizes.get(dir_path, 0) + len(file_info.content.encode('utf-8'))
87
+
88
+
89
+ @property
90
+ def content_size(self) -> int:
91
+ """Get total content size in bytes"""
92
+ return sum(len(f.content.encode('utf-8')) for f in self.files)
93
+
94
+ @property
95
+ def file_count(self) -> int:
96
+ """Get total number of files"""
97
+ return len(self.files)