janito 0.3.0__py3-none-any.whl → 0.4.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/fileparser.py ADDED
@@ -0,0 +1,125 @@
1
+ from pathlib import Path
2
+ from typing import Dict, Tuple, List, Optional
3
+ from dataclasses import dataclass
4
+ import re
5
+ import ast
6
+ import sys # Add this import
7
+ from rich.console import Console
8
+ from rich.panel import Panel # Add this import
9
+ from janito.config import config # Add this import
10
+
11
+ @dataclass
12
+ class FileChange:
13
+ """Represents a file change with search/replace, search/delete or create instructions"""
14
+ description: str
15
+ is_new_file: bool
16
+ content: str = "" # For new files
17
+ search_blocks: List[Tuple[str, Optional[str], Optional[str]]] = None # (search, replace, description)
18
+
19
+ def add_search_block(self, search: str, replace: Optional[str], description: Optional[str] = None) -> None:
20
+ """Add a search/replace or search/delete block with optional description"""
21
+ if self.search_blocks is None:
22
+ self.search_blocks = []
23
+ self.search_blocks.append((search, replace, description))
24
+
25
+ def validate_python_syntax(content: str, filepath: Path) -> Tuple[bool, str]:
26
+ """Validate Python syntax and return (is_valid, error_message)"""
27
+ try:
28
+ ast.parse(content)
29
+ console = Console()
30
+ console.print(f"[green]✓ Python syntax validation passed:[/green] {filepath.absolute()}")
31
+ return True, ""
32
+ except SyntaxError as e:
33
+ error_msg = f"Line {e.lineno}: {e.msg}"
34
+ console = Console()
35
+ console.print(f"[red]✗ Python syntax validation failed:[/red] {filepath.absolute()}")
36
+ console.print(f"[red] {error_msg}[/red]")
37
+ return False, error_msg
38
+
39
+
40
+ def parse_block_changes(response_text: str) -> Dict[Path, FileChange]:
41
+ """Parse file changes from response blocks"""
42
+ changes = {}
43
+ console = Console()
44
+ # Match file blocks with UUID
45
+ file_pattern = r'## ([a-f0-9]{8}) file (.*?) (modify|create) "(.*?)" ##\n?(.*?)## \1 file end ##'
46
+
47
+ for match in re.finditer(file_pattern, response_text, re.DOTALL):
48
+ uuid, filepath, action, description, content = match.groups()
49
+ path = Path(filepath.strip())
50
+
51
+ if action == 'create':
52
+ changes[path] = FileChange(
53
+ description=description,
54
+ is_new_file=True,
55
+ content=content[1:] if content.startswith('\n') else content,
56
+ search_blocks=[]
57
+ )
58
+ continue
59
+
60
+ # For modifications, find all search/replace and search/delete blocks
61
+ search_blocks = []
62
+ block_patterns = [
63
+ # Match search/replace blocks with description - updated pattern
64
+ (r'## ' + re.escape(uuid) + r' search/replace "(.*?)" ##\n?(.*?)## ' +
65
+ re.escape(uuid) + r' replace with ##\n?(.*?)(?=## ' + re.escape(uuid) + r'|$)', False),
66
+ # Match search/delete blocks with description
67
+ (r'## ' + re.escape(uuid) + r' search/delete "(.*?)" ##\n?(.*?)(?=## ' + re.escape(uuid) + r'|$)', True)
68
+ ]
69
+
70
+ if config.debug:
71
+ console.print("\n[blue]Updated regex patterns:[/blue]")
72
+ for pattern, is_delete in block_patterns:
73
+ console.print(Panel(pattern, title="Search/Replace Pattern" if not is_delete else "Search/Delete Pattern", border_style="blue"))
74
+
75
+ for pattern, is_delete in block_patterns:
76
+ if config.debug:
77
+ console.print(f"\n[blue]Looking for pattern:[/blue]")
78
+ console.print(Panel(pattern, title="Pattern", border_style="blue"))
79
+ console.print(f"\n[blue]In content:[/blue]")
80
+ console.print(Panel(content, title="Content", border_style="blue"))
81
+
82
+ for block_match in re.finditer(pattern, content, re.DOTALL):
83
+ if is_delete:
84
+ description, search = block_match.groups()
85
+ search = search.rstrip('\n') + '\n' # Ensure single trailing newline
86
+ replace = None
87
+ else:
88
+ description, search, replace = block_match.groups()
89
+ search = search.rstrip('\n') + '\n' # Ensure single trailing newline
90
+ replace = (replace.rstrip('\n') + '\n') if replace else None
91
+
92
+ # Abort parsing if replace content is empty
93
+ if not is_delete and (replace is None or replace.strip() == ''):
94
+ console.print(f"\n[red]Error: Empty replace content found![/red]")
95
+ console.print(f"[red]File:[/red] {filepath}")
96
+ console.print(f"[red]Description:[/red] {description}")
97
+ console.print("[yellow]Search block:[/yellow]")
98
+ console.print(Panel(search, title="Search Content", border_style="yellow"))
99
+ console.print("[red]Replace block is empty or contains only whitespace![/red]")
100
+ console.print("[red]Aborting due to empty replace content.[/red]")
101
+ sys.exit(1)
102
+
103
+ # Enhanced debug info
104
+ if config.debug or (not is_delete and (replace is None or replace.strip() == '')):
105
+ console.print(f"\n[yellow]Search/Replace block analysis:[/yellow]")
106
+ console.print(f"[yellow]File:[/yellow] {filepath}")
107
+ console.print(f"[yellow]Description:[/yellow] {description}")
108
+ console.print("[yellow]Search block:[/yellow]")
109
+ console.print(Panel(search, title="Search Content", border_style="yellow"))
110
+ console.print("[yellow]Replace block:[/yellow]")
111
+ console.print(Panel(replace if replace else "<empty>", title="Replace Content", border_style="yellow"))
112
+ console.print("\n[blue]Match groups:[/blue]")
113
+ for i, group in enumerate(block_match.groups()):
114
+ console.print(Panel(str(group), title=f"Group {i}", border_style="blue"))
115
+
116
+ search_blocks.append((search, replace, description))
117
+
118
+ # Add debug info if no blocks were found
119
+ if config.debug and not search_blocks:
120
+ console.print(f"\n[red]No search/replace blocks found for file:[/red] {filepath}")
121
+ console.print("[red]Check if the content format matches the expected patterns[/red]")
122
+
123
+ changes[path] = FileChange(description=description, is_new_file=False, search_blocks=search_blocks)
124
+
125
+ return changes
janito/prompts.py CHANGED
@@ -1,25 +1,12 @@
1
1
  import re
2
+ import uuid
3
+ from typing import List, Union
4
+ from dataclasses import dataclass
5
+ from .analysis import parse_analysis_options, AnalysisOption
2
6
 
3
7
  # Core system prompt focused on role and purpose
4
- SYSTEM_PROMPT = """You are Janito, an AI assistant for software development tasks. Be concise.
5
- """
6
-
8
+ SYSTEM_PROMPT = """I am Janito, your friendly software development buddy. I help you with coding tasks while being clear and concise in my responses."""
7
9
 
8
- CHANGE_ANALISYS_PROMPT = """
9
- Current files:
10
- <files>
11
- {files_content}
12
- </files>
13
-
14
- Considering the current files content, provide a table of options for the requested change.
15
- Always provide options using a header label "=== **Option 1** : ...", "=== **Option 2**: ...", etc.
16
- Provide the header with a short description followed by the file changes on the next line
17
- What files should be modified and what should they contain? (one line description)
18
- Do not provide the content of any of the file suggested to be created or modified.
19
-
20
- Request:
21
- {request}
22
- """
23
10
 
24
11
  SELECTED_OPTION_PROMPT = """
25
12
  Original request: {request}
@@ -32,66 +19,48 @@ Current files:
32
19
  {files_content}
33
20
  </files>
34
21
 
35
- After checking the above files and the provided implementation, please provide the following:
36
-
37
- ## <uuid4> filename begin "short description of the change" ##
38
- <entire file content>
39
- ## <uuid4> filename end ##
40
-
41
- ALWAYS provide the entire file content, not just the changes.
42
- If no changes are needed answer to any worksppace just reply <
22
+ RULES:
23
+ - When revmoing constants, ensure they are not used elsewhere
24
+ - When adding new features to python files, add the necessary imports
25
+ - Python imports should be inserted at the top of the file
26
+
27
+ Please provide the changes in this format:
28
+
29
+ ## {uuid} file <filepath> modify "short file change description" ##
30
+ ## {uuid} search/replace "short change description" ##
31
+ <search_content>
32
+ ## {uuid} replace with ##
33
+ <replace_content>
34
+ ## {uuid} file end ##
35
+
36
+ Or to delete content:
37
+ ## {uuid} file <filepath> modify ##
38
+ ## {uuid} search/delete "short change description" ##
39
+ <content_to_delete>
40
+ ## {uuid} file end ##
41
+
42
+ For new files:
43
+ ## {uuid} file <filepath> create "short description" ##
44
+ <full_file_content>
45
+ ## {uuid} file end ##
46
+
47
+ RULES:
48
+ 1. search_content MUST preserve the original identation/whitespace
43
49
  """
44
50
 
45
- def build_selected_option_prompt(option_number: int, request: str, initial_response: str, files_content: str = "") -> str:
46
- """Build prompt for selected option details"""
47
- options = parse_options(initial_response)
48
- if option_number not in options:
49
- raise ValueError(f"Option {option_number} not found in response")
51
+ def build_selected_option_prompt(option_text: str, request: str, files_content: str = "") -> str:
52
+ """Build prompt for selected option details
53
+
54
+ Args:
55
+ option_text: Formatted text describing the selected option
56
+ request: The original user request
57
+ files_content: Content of relevant files
58
+ """
59
+ short_uuid = str(uuid.uuid4())[:8]
50
60
 
51
61
  return SELECTED_OPTION_PROMPT.format(
52
- option_text=options[option_number],
62
+ option_text=option_text,
53
63
  request=request,
54
- files_content=files_content
55
- )
56
-
57
- def parse_options(response: str) -> dict[int, str]:
58
- """Parse options from the response text, including any list items after the option label"""
59
- options = {}
60
- pattern = r"===\s*\*\*Option (\d+)\*\*\s*:\s*(.+?)(?====\s*\*\*Option|\Z)"
61
- matches = re.finditer(pattern, response, re.DOTALL)
62
-
63
- for match in matches:
64
- option_num = int(match.group(1))
65
- option_text = match.group(2).strip()
66
-
67
- # Split into description and list items
68
- lines = option_text.splitlines()
69
- description = lines[0]
70
- list_items = []
71
-
72
- # Collect list items that follow
73
- for line in lines[1:]:
74
- line = line.strip()
75
- if line.startswith(('- ', '* ', '• ')):
76
- list_items.append(line)
77
- elif not line:
78
- continue
79
- else:
80
- break
81
-
82
- # Combine description with list items if any exist
83
- if list_items:
84
- option_text = description + '\n' + '\n'.join(list_items)
85
-
86
- options[option_num] = option_text
87
-
88
- return options
89
-
90
-
91
- def build_request_analisys_prompt(files_content: str, request: str) -> str:
92
- """Build prompt for information requests"""
93
-
94
- return CHANGE_ANALISYS_PROMPT.format(
95
64
  files_content=files_content,
96
- request=request
65
+ uuid=short_uuid
97
66
  )
janito/qa.py CHANGED
@@ -1,6 +1,13 @@
1
1
  from rich.console import Console
2
2
  from rich.markdown import Markdown
3
+ from rich.panel import Panel
4
+ from rich.syntax import Syntax
5
+ from rich.table import Table
6
+ from rich.rule import Rule
3
7
  from janito.claude import ClaudeAPIAgent
8
+ from janito.common import progress_send_message
9
+ from typing import Dict, List
10
+ import re
4
11
 
5
12
  QA_PROMPT = """Please provide a clear and concise answer to the following question about the codebase:
6
13
 
@@ -12,6 +19,7 @@ Current files:
12
19
  </files>
13
20
 
14
21
  Focus on providing factual information and explanations. Do not suggest code changes.
22
+ Format your response using markdown with appropriate headers and code blocks.
15
23
  """
16
24
 
17
25
  def ask_question(question: str, files_content: str, claude: ClaudeAPIAgent) -> str:
@@ -20,13 +28,36 @@ def ask_question(question: str, files_content: str, claude: ClaudeAPIAgent) -> s
20
28
  question=question,
21
29
  files_content=files_content
22
30
  )
23
- return claude.send_message(prompt)
31
+ return progress_send_message(claude, prompt)
32
+
24
33
 
25
34
  def display_answer(answer: str, raw: bool = False) -> None:
26
- """Display the answer in markdown or raw format"""
35
+ """Display the answer as markdown with consistent colors"""
27
36
  console = Console()
37
+
38
+ # Define consistent colors
39
+ COLORS = {
40
+ 'primary': '#729FCF', # Soft blue for primary elements
41
+ 'secondary': '#8AE234', # Bright green for actions/success
42
+ 'accent': '#AD7FA8', # Purple for accents
43
+ 'muted': '#7F9F7F', # Muted green for less important text
44
+ }
45
+
28
46
  if raw:
29
47
  console.print(answer)
30
- else:
31
- md = Markdown(answer)
32
- console.print(md)
48
+ return
49
+
50
+ # Display markdown answer in a panel with consistent styling
51
+ answer_panel = Panel(
52
+ Markdown(answer),
53
+ title="[bold]Answer[/bold]",
54
+ title_align="center",
55
+ border_style=COLORS['primary'],
56
+ padding=(1, 2)
57
+ )
58
+
59
+ console.print("\n")
60
+ console.print(Rule(style=COLORS['accent']))
61
+ console.print(answer_panel)
62
+ console.print(Rule(style=COLORS['accent']))
63
+ console.print("\n")
janito/scan.py CHANGED
@@ -19,7 +19,7 @@ def _scan_paths(paths: List[Path], workdir: Path = None) -> Tuple[List[str], Lis
19
19
  # Load gitignore if it exists
20
20
  gitignore_path = workdir / '.gitignore' if workdir else None
21
21
  gitignore_spec = None
22
- if gitignore_path and gitignore_path.exists():
22
+ if (gitignore_path and gitignore_path.exists()):
23
23
  with open(gitignore_path) as f:
24
24
  gitignore = f.read()
25
25
  gitignore_spec = PathSpec.from_lines(GitWildMatchPattern, gitignore.splitlines())
@@ -100,21 +100,36 @@ def collect_files_content(paths: List[Path], workdir: Path = None) -> str:
100
100
 
101
101
  return "\n".join(content_parts)
102
102
 
103
+
103
104
  def preview_scan(paths: List[Path], workdir: Path = None) -> None:
104
105
  """Preview what files and directories would be scanned"""
105
106
  console = Console()
106
107
  _, file_items = _scan_paths(paths, workdir)
107
108
 
108
- # Change message based on whether we're scanning included paths or workdir
109
- if len(paths) == 1 and paths[0] == workdir:
110
- console.print(f"\n[bold blue]Scanning working directory:[/bold blue] {workdir.absolute()}")
109
+ # Display working directory status
110
+ console.print("\n[bold blue]Analysis Paths:[/bold blue]")
111
+ console.print(f"[cyan]Working Directory:[/cyan] {workdir.absolute()}")
112
+
113
+ # Show if working directory is being scanned
114
+ is_workdir_scanned = any(p.resolve() == workdir.resolve() for p in paths)
115
+ if is_workdir_scanned:
116
+ console.print("[green]✓ Working directory will be scanned[/green]")
111
117
  else:
112
- console.print(f"\n[bold blue]Working directory:[/bold blue] {workdir.absolute()}")
113
- console.print("\n[bold blue]Scanning included paths:[/bold blue]")
118
+ console.print("[yellow]! Working directory will not be scanned[/yellow]")
119
+
120
+ # Show included paths relative to working directory
121
+ if len(paths) > (1 if is_workdir_scanned else 0):
122
+ console.print("\n[cyan]Additional Included Paths:[/cyan]")
114
123
  for path in paths:
115
- console.print(f" • {path.absolute()}")
116
-
117
- console.print("\n[bold blue]Files that would be analyzed:[/bold blue]")
124
+ if path.resolve() != workdir.resolve():
125
+ try:
126
+ rel_path = path.relative_to(workdir)
127
+ console.print(f" • ./{rel_path}")
128
+ except ValueError:
129
+ # Path is outside working directory
130
+ console.print(f" • {path.absolute()}")
131
+
132
+ console.print("\n[bold blue]Files that will be analyzed:[/bold blue]")
118
133
  console.print(Columns(file_items, padding=(0, 4), expand=True))
119
134
 
120
135
  def is_dir_empty(path: Path) -> bool:
janito/version.py ADDED
@@ -0,0 +1,23 @@
1
+ """Version management module for Janito."""
2
+ from pathlib import Path
3
+ from typing import Optional
4
+ import tomli
5
+ from importlib.metadata import version as pkg_version
6
+
7
+ def get_version() -> str:
8
+ """
9
+ Get Janito version from package metadata or pyproject.toml.
10
+
11
+ Returns:
12
+ str: The version string
13
+ """
14
+ try:
15
+ return pkg_version("janito")
16
+ except Exception:
17
+ # Fallback to pyproject.toml
18
+ pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
19
+ if pyproject_path.exists():
20
+ with open(pyproject_path, "rb") as f:
21
+ pyproject_data = tomli.load(f)
22
+ return pyproject_data.get("project", {}).get("version", "unknown")
23
+ return "unknown"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: janito
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: A CLI tool for software development tasks powered by AI
5
5
  Project-URL: Homepage, https://github.com/joaompinto/janito
6
6
  Project-URL: Repository, https://github.com/joaompinto/janito.git
@@ -18,6 +18,7 @@ Requires-Python: >=3.8
18
18
  Requires-Dist: anthropic
19
19
  Requires-Dist: pathspec
20
20
  Requires-Dist: rich
21
+ Requires-Dist: tomli
21
22
  Requires-Dist: typer
22
23
  Description-Content-Type: text/markdown
23
24
 
@@ -25,7 +26,7 @@ Description-Content-Type: text/markdown
25
26
 
26
27
  A CLI tool for software development tasks powered by AI.
27
28
 
28
- Janito is an AI-powered assistant that helps automate common software development tasks like refactoring, documentation updates, and code optimization.
29
+ Meet Janito, your friendly AI-powered software development buddy! Janito helps you with coding tasks like refactoring, documentation updates, and code optimization while being clear and concise in its responses.
29
30
 
30
31
  ## 📥 Installation
31
32
 
@@ -59,6 +60,15 @@ export ANTHROPIC_API_KEY='your-api-key-here'
59
60
 
60
61
  You can also add this to your shell profile (~/.bashrc, ~/.zshrc, etc.) for persistence.
61
62
 
63
+ ### ⚙️ Test Command Setup
64
+ You can configure a test command to run before applying changes:
65
+
66
+ ```bash
67
+ export JANITO_TEST_CMD='your-test-command'
68
+ ```
69
+
70
+ This command will be executed in the preview directory before applying changes to verify they don't break anything.
71
+
62
72
  ## 📖 Usage
63
73
 
64
74
  Janito can be used in two modes: Command Line or Interactive Console.
@@ -70,17 +80,26 @@ janito REQUEST [OPTIONS]
70
80
  ```
71
81
 
72
82
  #### Arguments
73
- - `REQUEST`: The modification request
83
+ - `REQUEST`: The modification request to process (optional)
74
84
 
75
85
  #### Options
86
+ ##### General Options
87
+ - `--version`: Show version and exit
76
88
  - `-w, --workdir PATH`: Working directory (defaults to current directory)
77
- - `--raw`: Print raw response instead of markdown format
89
+ - `-i, --include PATH`: Additional paths to include in analysis (can be used multiple times)
90
+
91
+ ##### Operation Modes
92
+ - `--ask TEXT`: Ask a question about the codebase instead of making changes
93
+ - `--scan`: Preview files that would be analyzed without making changes
78
94
  - `--play PATH`: Replay a saved prompt file
79
- - `-i, --include PATH`: Additional paths to include in analysis
80
- - `--debug`: Show debug information
95
+
96
+ ##### Output Control
97
+ - `--raw`: Print raw response instead of markdown format
81
98
  - `-v, --verbose`: Show verbose output
82
- - `--ask`: Ask a question about the codebase
83
- - `--scan`: Preview files that would be analyzed
99
+ - `--debug`: Show debug information
100
+
101
+ ##### Testing
102
+ - `-t, --test COMMAND`: Test command to run before applying changes (overrides JANITO_TEST_CMD)
84
103
 
85
104
  ### 🖥️ Interactive Console Mode
86
105
 
@@ -105,6 +124,12 @@ janito "update tests" -i ./tests -i ./lib
105
124
  janito --ask "explain the authentication flow"
106
125
  janito --scan # Preview files to be analyzed
107
126
 
127
+ # Test Command Examples
128
+ janito "update code" --test "pytest" # Run pytest before applying changes
129
+ janito "refactor module" -t "make test" # Run make test before applying
130
+ export JANITO_TEST_CMD="python -m unittest" # Set default test command
131
+ janito "optimize function" # Will use JANITO_TEST_CMD
132
+
108
133
  # Console Mode
109
134
  janito # Starts interactive session
110
135
  ```
@@ -120,6 +145,7 @@ janito # Starts interactive session
120
145
  - 🐛 Debug and verbose output modes
121
146
  - ❓ Question-answering about codebase
122
147
  - 🔍 File scanning preview
148
+ - 🧪 Test command execution before applying changes
123
149
 
124
150
  ## 📚 History and Debugging
125
151
 
@@ -0,0 +1,21 @@
1
+ janito/__init__.py,sha256=CLeVFqpY9Ki3R3MgLAiTjNsJjsj1BD3_9CzP2kgCj-k,52
2
+ janito/__main__.py,sha256=DqbMlHNWCX-XwiZz1qEYP3qAADKtfmlDhhbTgpXB5_M,14158
3
+ janito/analysis.py,sha256=dAzMARAcDZ1LN6KC2-Xb0bIB0p5VsaT2lhurtPLLIKk,9368
4
+ janito/changeapplier.py,sha256=fDP87Hh3uY8qoSv3-SSnafLCRJHLPQZ5U8GIMkdVZ9Y,17697
5
+ janito/changeviewer.py,sha256=51J0pvGe1rfYpiYo0mhZMonsYJmyAN44mg1TfKRxdM4,12716
6
+ janito/claude.py,sha256=N7ZX6WCrLKcj9na3o2MMPqH8tlobHYtIG1HnhgMVUKw,2272
7
+ janito/common.py,sha256=1blM7rY55-mLPXD4L3xwS-RaJ2tBZx8xHhaLYQku3Rk,801
8
+ janito/config.py,sha256=ocg0lyab9ysgczKaqJTAtv0ZRa2VDMwclTJBgps7Vxw,1171
9
+ janito/console.py,sha256=E2mLPoBw7Gh1K1hPuCoRDBEFE-klIERt5KFgE3fHqUg,11100
10
+ janito/contentchange.py,sha256=2HkNhqA_ZIXDHfiv3xRcwfoxMzFUNMuFfbMtf-4jhRM,3282
11
+ janito/contextparser.py,sha256=iDX6nlqUQr-lj7lkEI4B6jHLci_Kxl-XWOaEiAQtVxA,4531
12
+ janito/fileparser.py,sha256=8nngFmR9s6NVv2YuMcNKO4udNh-ZcKN4_Jo11vKzLhI,6586
13
+ janito/prompts.py,sha256=aUDDx16L2ygPJZwrWn4FmrXt1KGNfLeYRcDSPD9BzIA,1879
14
+ janito/qa.py,sha256=qrAXbsGPCRSW378V88w8e19uU3hfbDnx6lsiRYhq324,1927
15
+ janito/scan.py,sha256=c_IKSuWx1_525RlIgPtP-tFoH7lManVGGNFMxVg1hMk,6014
16
+ janito/version.py,sha256=ylfPwGtdY8dEOFJ-DB9gKUQLggqRCvoLxhpnwjzCM94,739
17
+ janito-0.4.0.dist-info/METADATA,sha256=uUCven38ndkbyLrrLw3B42P7AGyijqTGICKRzLJkDDE,4705
18
+ janito-0.4.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
19
+ janito-0.4.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
20
+ janito-0.4.0.dist-info/licenses/LICENSE,sha256=xLIUXRPjtsgQml2zD1Pn4LpgiyZ49raw6jZDlO_gZdo,1062
21
+ janito-0.4.0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- janito/__init__.py,sha256=CLeVFqpY9Ki3R3MgLAiTjNsJjsj1BD3_9CzP2kgCj-k,52
2
- janito/__main__.py,sha256=bGp3nWUZC5omneyE3hn78D_J5PkBfk19qKfMV1kIThI,9892
3
- janito/changeviewer.py,sha256=C_CRdeD6dE4AIpOM_kryTn_HyD5XC-glaZO8n8zrQPE,2487
4
- janito/claude.py,sha256=tj0lNNVE0CW0bBkbhVDFgBl0AFoMHicWaHnYuYOk3_E,2911
5
- janito/config.py,sha256=YsS0bNVkjl7cIboP9nSDy0NXsJaVHYAIpPkc6bbErpo,967
6
- janito/console.py,sha256=ieKZ7IRbvJO_KW8A3CU4FCoO4YFEjJQT8BN98YotAnM,2770
7
- janito/contentchange.py,sha256=BxFmW8JtRjzX5lnfGfzo0JPRnGRw1RQEtqZCK1wVArw,6404
8
- janito/prompts.py,sha256=XonVVbfIg3YY1Dpbkx9m0ZSRE4bgDP3MYHO3D-5FcIE,3080
9
- janito/qa.py,sha256=F9bd18CBaZDpbJwwvwFL18gXPBA0cq8kRY0nA3_AKPY,916
10
- janito/scan.py,sha256=5JV0crOepVUqCZ3LAUqCJL2yerLFvjZ6RYdOwIGHvX0,5450
11
- janito-0.3.0.dist-info/METADATA,sha256=fq7zvCHx_PV8RND5SVfOMnM082BEptZQ2G2qWPOVisQ,3681
12
- janito-0.3.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
13
- janito-0.3.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
14
- janito-0.3.0.dist-info/licenses/LICENSE,sha256=xLIUXRPjtsgQml2zD1Pn4LpgiyZ49raw6jZDlO_gZdo,1062
15
- janito-0.3.0.dist-info/RECORD,,
File without changes