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.
- janito/__main__.py +127 -141
- janito/agents/__init__.py +22 -22
- janito/agents/agent.py +24 -27
- janito/agents/claudeai.py +41 -45
- janito/agents/deepseekai.py +47 -0
- janito/change/applied_blocks.py +34 -0
- janito/change/applier.py +167 -0
- janito/change/edit_blocks.py +148 -0
- janito/change/finder.py +72 -0
- janito/change/request.py +144 -0
- janito/change/validator.py +87 -269
- janito/change/view/content.py +63 -0
- janito/change/{viewer → view}/diff.py +44 -43
- janito/change/view/panels.py +201 -0
- janito/change/view/sections.py +69 -0
- janito/change/view/styling.py +140 -0
- janito/change/view/summary.py +37 -0
- janito/change/{viewer → view}/themes.py +62 -55
- janito/change/view/viewer.py +59 -0
- janito/cli/__init__.py +1 -1
- janito/cli/commands.py +68 -88
- janito/cli/functions.py +66 -111
- janito/common.py +132 -79
- janito/config.py +99 -101
- janito/data/change_prompt.txt +81 -0
- janito/data/system_prompt.txt +3 -0
- janito/qa.py +56 -57
- janito/version.py +22 -22
- janito/workspace/__init__.py +8 -6
- janito/workspace/analysis.py +120 -120
- janito/workspace/{types.py → models.py} +97 -98
- janito/workspace/show.py +115 -141
- janito/workspace/stats.py +42 -43
- janito/workspace/workset.py +135 -108
- janito/workspace/workspace.py +335 -114
- janito-0.8.0.dist-info/METADATA +106 -0
- janito-0.8.0.dist-info/RECORD +40 -0
- {janito-0.7.0.dist-info → janito-0.8.0.dist-info}/licenses/LICENSE +20 -20
- janito/__init__.py +0 -2
- janito/agents/openai.py +0 -57
- janito/agents/test.py +0 -34
- janito/change/__init__.py +0 -32
- janito/change/__main__.py +0 -0
- janito/change/analysis/__init__.py +0 -23
- janito/change/analysis/__main__.py +0 -7
- janito/change/analysis/analyze.py +0 -62
- janito/change/analysis/formatting.py +0 -78
- janito/change/analysis/options.py +0 -81
- janito/change/analysis/prompts.py +0 -90
- janito/change/analysis/view/__init__.py +0 -9
- janito/change/analysis/view/terminal.py +0 -181
- janito/change/applier/__init__.py +0 -5
- janito/change/applier/file.py +0 -58
- janito/change/applier/main.py +0 -156
- janito/change/applier/text.py +0 -247
- janito/change/applier/workspace_dir.py +0 -58
- janito/change/core.py +0 -124
- janito/change/history.py +0 -44
- janito/change/operations.py +0 -7
- janito/change/parser.py +0 -287
- janito/change/play.py +0 -54
- janito/change/preview.py +0 -82
- janito/change/prompts.py +0 -121
- janito/change/test.py +0 -0
- janito/change/viewer/__init__.py +0 -11
- janito/change/viewer/content.py +0 -66
- janito/change/viewer/panels.py +0 -533
- janito/change/viewer/styling.py +0 -114
- janito/clear_statement_parser/clear_statement_format.txt +0 -328
- janito/clear_statement_parser/examples.txt +0 -326
- janito/clear_statement_parser/models.py +0 -104
- janito/clear_statement_parser/parser.py +0 -496
- janito/cli/base.py +0 -30
- janito/cli/history.py +0 -61
- janito/cli/registry.py +0 -26
- janito/demo/__init__.py +0 -4
- janito/demo/data.py +0 -13
- janito/demo/mock_data.py +0 -20
- janito/demo/operations.py +0 -45
- janito/demo/runner.py +0 -59
- janito/demo/scenarios.py +0 -32
- janito/prompt.py +0 -36
- janito/review.py +0 -13
- janito/search_replace/README.md +0 -192
- janito/search_replace/__init__.py +0 -7
- janito/search_replace/__main__.py +0 -21
- janito/search_replace/core.py +0 -120
- janito/search_replace/logger.py +0 -35
- janito/search_replace/parser.py +0 -52
- janito/search_replace/play.py +0 -61
- janito/search_replace/replacer.py +0 -36
- janito/search_replace/searcher.py +0 -411
- janito/search_replace/strategy_result.py +0 -10
- janito/shell/__init__.py +0 -38
- janito/shell/bus.py +0 -31
- janito/shell/commands.py +0 -136
- janito/shell/history.py +0 -20
- janito/shell/processor.py +0 -32
- janito/shell/prompt.py +0 -48
- janito/shell/registry.py +0 -60
- janito/tui/__init__.py +0 -21
- janito/tui/base.py +0 -22
- janito/tui/flows/__init__.py +0 -5
- janito/tui/flows/changes.py +0 -65
- janito/tui/flows/content.py +0 -128
- janito/tui/flows/selection.py +0 -117
- janito/tui/screens/__init__.py +0 -3
- janito/tui/screens/app.py +0 -1
- janito-0.7.0.dist-info/METADATA +0 -167
- janito-0.7.0.dist-info/RECORD +0 -96
- {janito-0.7.0.dist-info → janito-0.8.0.dist-info}/WHEEL +0 -0
- {janito-0.7.0.dist-info → janito-0.8.0.dist-info}/entry_points.txt +0 -0
janito/workspace/analysis.py
CHANGED
@@ -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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
class
|
32
|
-
"""
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
self.
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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)
|