janito 0.5.0__py3-none-any.whl → 0.7.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/__init__.py +0 -47
- janito/__main__.py +105 -17
- janito/agents/__init__.py +9 -9
- janito/agents/agent.py +10 -3
- janito/agents/claudeai.py +15 -34
- janito/agents/openai.py +5 -1
- janito/change/__init__.py +29 -16
- janito/change/__main__.py +0 -0
- janito/{analysis → change/analysis}/__init__.py +5 -15
- janito/change/analysis/__main__.py +7 -0
- janito/change/analysis/analyze.py +62 -0
- janito/change/analysis/formatting.py +78 -0
- janito/change/analysis/options.py +81 -0
- janito/{analysis → change/analysis}/prompts.py +33 -18
- janito/change/analysis/view/__init__.py +9 -0
- janito/change/analysis/view/terminal.py +181 -0
- janito/change/applier/__init__.py +5 -0
- janito/change/applier/file.py +58 -0
- janito/change/applier/main.py +156 -0
- janito/change/applier/text.py +247 -0
- janito/change/applier/workspace_dir.py +58 -0
- janito/change/core.py +124 -0
- janito/{changehistory.py → change/history.py} +12 -14
- janito/change/operations.py +7 -0
- janito/change/parser.py +287 -0
- janito/change/play.py +54 -0
- janito/change/preview.py +82 -0
- janito/change/prompts.py +121 -0
- janito/change/test.py +0 -0
- janito/change/validator.py +269 -0
- janito/{changeviewer → change/viewer}/__init__.py +3 -4
- janito/change/viewer/content.py +66 -0
- janito/{changeviewer → change/viewer}/diff.py +19 -4
- janito/change/viewer/panels.py +533 -0
- janito/change/viewer/styling.py +114 -0
- janito/{changeviewer → change/viewer}/themes.py +3 -5
- janito/clear_statement_parser/clear_statement_format.txt +328 -0
- janito/clear_statement_parser/examples.txt +326 -0
- janito/clear_statement_parser/models.py +104 -0
- janito/clear_statement_parser/parser.py +496 -0
- janito/cli/base.py +30 -0
- janito/cli/commands.py +75 -40
- janito/cli/functions.py +19 -194
- janito/cli/history.py +61 -0
- janito/common.py +65 -8
- janito/config.py +70 -5
- janito/demo/__init__.py +4 -0
- janito/demo/data.py +13 -0
- janito/demo/mock_data.py +20 -0
- janito/demo/operations.py +45 -0
- janito/demo/runner.py +59 -0
- janito/demo/scenarios.py +32 -0
- janito/prompt.py +36 -0
- janito/qa.py +6 -14
- janito/search_replace/README.md +192 -0
- janito/search_replace/__init__.py +7 -0
- janito/search_replace/__main__.py +21 -0
- janito/search_replace/core.py +120 -0
- janito/search_replace/logger.py +35 -0
- janito/search_replace/parser.py +52 -0
- janito/search_replace/play.py +61 -0
- janito/search_replace/replacer.py +36 -0
- janito/search_replace/searcher.py +411 -0
- janito/search_replace/strategy_result.py +10 -0
- janito/shell/__init__.py +38 -0
- janito/shell/bus.py +31 -0
- janito/shell/commands.py +136 -0
- janito/shell/history.py +20 -0
- janito/shell/processor.py +32 -0
- janito/shell/prompt.py +48 -0
- janito/shell/registry.py +60 -0
- janito/tui/__init__.py +21 -0
- janito/tui/base.py +22 -0
- janito/tui/flows/__init__.py +5 -0
- janito/tui/flows/changes.py +65 -0
- janito/tui/flows/content.py +128 -0
- janito/tui/flows/selection.py +117 -0
- janito/tui/screens/__init__.py +3 -0
- janito/tui/screens/app.py +1 -0
- janito/workspace/__init__.py +6 -0
- janito/workspace/analysis.py +121 -0
- janito/workspace/show.py +141 -0
- janito/workspace/stats.py +43 -0
- janito/workspace/types.py +98 -0
- janito/workspace/workset.py +108 -0
- janito/workspace/workspace.py +114 -0
- janito-0.7.0.dist-info/METADATA +167 -0
- janito-0.7.0.dist-info/RECORD +96 -0
- {janito-0.5.0.dist-info → janito-0.7.0.dist-info}/WHEEL +1 -1
- janito/_contextparser.py +0 -113
- janito/analysis/display.py +0 -149
- janito/analysis/options.py +0 -112
- janito/change/applier.py +0 -269
- janito/change/content.py +0 -62
- janito/change/indentation.py +0 -33
- janito/change/position.py +0 -169
- janito/changeviewer/panels.py +0 -268
- janito/changeviewer/styling.py +0 -59
- janito/console/__init__.py +0 -3
- janito/console/commands.py +0 -112
- janito/console/core.py +0 -62
- janito/console/display.py +0 -157
- janito/fileparser.py +0 -334
- janito/prompts.py +0 -81
- janito/scan.py +0 -176
- janito/tests/test_fileparser.py +0 -26
- janito-0.5.0.dist-info/METADATA +0 -146
- janito-0.5.0.dist-info/RECORD +0 -45
- {janito-0.5.0.dist-info → janito-0.7.0.dist-info}/entry_points.txt +0 -0
- {janito-0.5.0.dist-info → janito-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: janito
|
3
|
+
Version: 0.7.0
|
4
|
+
Summary: A CLI tool for software development tasks powered by AI
|
5
|
+
Project-URL: Homepage, https://github.com/joaompinto/janito
|
6
|
+
Project-URL: Repository, https://github.com/joaompinto/janito.git
|
7
|
+
Author-email: João Pinto <lamego.pinto@gmail.com>
|
8
|
+
License: MIT
|
9
|
+
License-File: LICENSE
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
11
|
+
Classifier: Environment :: Console
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
17
|
+
Classifier: Topic :: Software Development
|
18
|
+
Requires-Python: >=3.8
|
19
|
+
Requires-Dist: anthropic
|
20
|
+
Requires-Dist: pathspec
|
21
|
+
Requires-Dist: rich
|
22
|
+
Requires-Dist: tomli
|
23
|
+
Requires-Dist: typer
|
24
|
+
Description-Content-Type: text/markdown
|
25
|
+
|
26
|
+
# Janito
|
27
|
+
|
28
|
+
[](https://badge.fury.io/py/janito)
|
29
|
+
[](https://opensource.org/licenses/MIT)
|
30
|
+
|
31
|
+
AI-powered CLI tool for code modifications and analysis. Janito helps you modify, analyze, and understand your codebase using natural language commands.
|
32
|
+
|
33
|
+
## Table of Contents
|
34
|
+
|
35
|
+
- [Features](#features)
|
36
|
+
- [Installation](#installation)
|
37
|
+
- [Usage](#usage)
|
38
|
+
- [Basic Commands](#basic-commands)
|
39
|
+
- [Examples](#examples)
|
40
|
+
- [Configuration](#configuration)
|
41
|
+
- [Development](#development)
|
42
|
+
- [License](#license)
|
43
|
+
|
44
|
+
## Features
|
45
|
+
|
46
|
+
- 🤖 AI-powered code analysis and modifications
|
47
|
+
- 🔄 Incremental code changes with search/replace operations
|
48
|
+
- 🎯 Precise text modifications with context matching
|
49
|
+
- 💬 Natural language interface for code operations
|
50
|
+
- 🔍 Interactive code exploration
|
51
|
+
- 📝 Automatic documentation generation
|
52
|
+
- ⚡ Fast and efficient codebase navigation
|
53
|
+
- 💾 Smart Claude AI prompt caching for faster responses
|
54
|
+
|
55
|
+
## Installation
|
56
|
+
|
57
|
+
### Prerequisites
|
58
|
+
|
59
|
+
- Python 3.8 or higher
|
60
|
+
- Anthropic API key (with smart caching to reduce API costs)
|
61
|
+
|
62
|
+
### Install via pip
|
63
|
+
|
64
|
+
```bash
|
65
|
+
pip install janito
|
66
|
+
```
|
67
|
+
|
68
|
+
### Set up API key
|
69
|
+
|
70
|
+
```bash
|
71
|
+
export ANTHROPIC_API_KEY=your_api_key_here
|
72
|
+
```
|
73
|
+
|
74
|
+
## Usage
|
75
|
+
|
76
|
+
### Basic Commands
|
77
|
+
|
78
|
+
Janito supports incremental code changes through precise text operations:
|
79
|
+
- Search and replace with context matching
|
80
|
+
- Delete specific code blocks
|
81
|
+
- File operations (create, replace, rename, move, remove)
|
82
|
+
|
83
|
+
```bash
|
84
|
+
# Start interactive shell
|
85
|
+
janito
|
86
|
+
|
87
|
+
# Modify code with natural language
|
88
|
+
janito "add docstrings to this file"
|
89
|
+
|
90
|
+
# Ask questions about the codebase
|
91
|
+
janito --ask "explain the main function in this file"
|
92
|
+
|
93
|
+
# Preview files that would be analyzed
|
94
|
+
janito --scan
|
95
|
+
```
|
96
|
+
|
97
|
+
### Examples
|
98
|
+
|
99
|
+
1. Add documentation to a file:
|
100
|
+
```bash
|
101
|
+
janito "add docstrings to all functions in src/main.py"
|
102
|
+
```
|
103
|
+
|
104
|
+
2. Analyze code structure:
|
105
|
+
```bash
|
106
|
+
janito --ask "what are the main classes in this project?"
|
107
|
+
```
|
108
|
+
|
109
|
+
3. Refactor code:
|
110
|
+
```bash
|
111
|
+
janito "convert this function to use async/await"
|
112
|
+
```
|
113
|
+
|
114
|
+
4. Generate tests:
|
115
|
+
```bash
|
116
|
+
janito "create unit tests for the User class"
|
117
|
+
```
|
118
|
+
|
119
|
+
## Configuration
|
120
|
+
|
121
|
+
### Environment Variables
|
122
|
+
|
123
|
+
- `ANTHROPIC_API_KEY`: Anthropic API key for Claude AI
|
124
|
+
- `JANITO_TEST_CMD`: Default test command to run after changes
|
125
|
+
|
126
|
+
### Command Line Options
|
127
|
+
|
128
|
+
- `-w, --workspace_dir`: Set working directory
|
129
|
+
- `-i, --include`: Additional paths to include
|
130
|
+
- `--debug`: Show debug information
|
131
|
+
- `--verbose`: Show verbose output
|
132
|
+
- `--auto-apply`: Apply changes without confirmation
|
133
|
+
|
134
|
+
## Development
|
135
|
+
|
136
|
+
### Setting up Development Environment
|
137
|
+
|
138
|
+
```bash
|
139
|
+
# Clone the repository
|
140
|
+
git clone https://github.com/joaompinto/janito.git
|
141
|
+
cd janito
|
142
|
+
|
143
|
+
# Create and activate virtual environment
|
144
|
+
python -m venv venv
|
145
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
146
|
+
|
147
|
+
# Install development dependencies
|
148
|
+
pip install -e ".[dev]"
|
149
|
+
```
|
150
|
+
|
151
|
+
### Running Tests
|
152
|
+
|
153
|
+
```bash
|
154
|
+
pytest
|
155
|
+
```
|
156
|
+
|
157
|
+
### Contributing
|
158
|
+
|
159
|
+
1. Fork the repository
|
160
|
+
2. Create a feature branch
|
161
|
+
3. Commit your changes
|
162
|
+
4. Push to the branch
|
163
|
+
5. Create a Pull Request
|
164
|
+
|
165
|
+
## License
|
166
|
+
|
167
|
+
MIT License - see [LICENSE](LICENSE)
|
@@ -0,0 +1,96 @@
|
|
1
|
+
janito/__init__.py,sha256=Svp3i5NGeapDV4xB-CDu9TjqEzvsnjQwSzdmDT9DDqc,47
|
2
|
+
janito/__main__.py,sha256=l20_V0iIX_8yjJeP-THirJATybgCbmikzu5colMb1P0,5544
|
3
|
+
janito/common.py,sha256=VaFTF5VM6dClNGFVC1A9b-EbpqcqrQpybl6eidl8T9c,3381
|
4
|
+
janito/config.py,sha256=f0chVHjfds62W-wUDPLlshxh8W1VviqHw2kbm-_qLSA,3126
|
5
|
+
janito/prompt.py,sha256=Rd6I0C4y0uSgWCggoLJiSiET9vT3nlgtwfSKo06jMDc,1011
|
6
|
+
janito/qa.py,sha256=8MBQnKgUGErgJ3XWE2cd9WvSAHyke-AL5jO8iPhfhng,1794
|
7
|
+
janito/review.py,sha256=5Oc6BfxMGNmKbIeDP5_EiAKUDeQwVOD0YL7iqfgJLRE,471
|
8
|
+
janito/version.py,sha256=ylfPwGtdY8dEOFJ-DB9gKUQLggqRCvoLxhpnwjzCM94,739
|
9
|
+
janito/agents/__init__.py,sha256=o4CL_6UkTx62W9VyOshecAgnPrlBeiDqH3l82RKN2gA,755
|
10
|
+
janito/agents/agent.py,sha256=81Bx9I6U0U-KDFMKkV0Q-ZlzCxbuJBUHGKUHqG45S_E,927
|
11
|
+
janito/agents/claudeai.py,sha256=WWCFWJ4T6v6dPZubpGgsQTzgd9lB4ViNm29FC66wl_U,1608
|
12
|
+
janito/agents/openai.py,sha256=VdskCojBIbRvKTjiVqwn1f3rfGHynUFDaIvnlwRYs1c,2282
|
13
|
+
janito/agents/test.py,sha256=xoN1q9DUSYpUbnvTP1qZsEfxYrZfocJlt9DkIuMDvvY,1552
|
14
|
+
janito/change/__init__.py,sha256=vGn9qudqiusp4LsR2zzr3mbb4dhXvcGBY2NNzl1JC5M,955
|
15
|
+
janito/change/__main__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
+
janito/change/core.py,sha256=fTAFv3FwYgPxwhuMOSigglYX0l30KY6X17L_VxBviB4,3871
|
17
|
+
janito/change/history.py,sha256=rbprE1lnOl5We85c-VsDnHAiGphW0t2fA7zu4N9uulw,1405
|
18
|
+
janito/change/operations.py,sha256=5HeUvFa503Foemngu-ShomPn9W82XH-gLlTfXxAZoEw,165
|
19
|
+
janito/change/parser.py,sha256=EYQgy1L8yWNPTgdVNWeiXFbAtzPJNBwU15-dE4ThG68,11169
|
20
|
+
janito/change/play.py,sha256=ZzYp-eaIBn578EiPB9uVR-G-mxXiK_hD_GI15htb0DU,1835
|
21
|
+
janito/change/preview.py,sha256=xfwo0q7jAx-U92U3iEwDDlsZnzndj7fshq8OLgjv3Vw,2720
|
22
|
+
janito/change/prompts.py,sha256=00HcNIEZwLCEY1SvT5unaIiAkCoiSCG-DYk94YmWcPQ,4345
|
23
|
+
janito/change/test.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
+
janito/change/validator.py,sha256=sd8QXruxQPdG-rVCM6NIXsVmK50ytzrtsx2J3is06Ww,10943
|
25
|
+
janito/change/analysis/__init__.py,sha256=yg9LhcC1nCvleGt4cnQMuJYCARXBZNvmXaT23S3szJo,593
|
26
|
+
janito/change/analysis/__main__.py,sha256=M6JmJnZfOKB0OB29EpzzfbNZu7cusKT92KQhmnaWd5c,196
|
27
|
+
janito/change/analysis/analyze.py,sha256=CRpfRn_3KQHNdTZ1wQtsSafzmJzPblNggEf-ZkiTEzw,1760
|
28
|
+
janito/change/analysis/formatting.py,sha256=VqDL6fBzvVm5pdDN4fX9nQQP1wZwhlxDKyaMlDj9ju4,2602
|
29
|
+
janito/change/analysis/options.py,sha256=JNyXCFjOxS1Qm1oqqn8JSXrF4KQ9MotclfKMLD7dZFc,2957
|
30
|
+
janito/change/analysis/prompts.py,sha256=UmuJzrdxpEpL3EcgrsJO4OwKWDWMlFucd162Oje-7F4,2962
|
31
|
+
janito/change/analysis/view/__init__.py,sha256=oQd_ClbL7sWmb8jbie74qRrn09CIUTxLH18b02PJFLk,204
|
32
|
+
janito/change/analysis/view/terminal.py,sha256=IxsIZG7IJQwfJ1dtZDCga7E7Qq3u5EI5lPszbvi82u4,6598
|
33
|
+
janito/change/applier/__init__.py,sha256=KdViLadNAWcSUbJcsfAt66cDJpaqYGIHdzSDKZjzsEw,183
|
34
|
+
janito/change/applier/file.py,sha256=NQnoywcVXAxLU0xipsByajs2SCRtsSqFPO8tlJl1J2w,2420
|
35
|
+
janito/change/applier/main.py,sha256=Ua-0tmOHYImSlYzmRPppz9stg50bGM1AFFUdQEFMxhY,6644
|
36
|
+
janito/change/applier/text.py,sha256=cORCCLZdph31tJ2Jr5bueRTawrBr-m2csaGYmJexD2I,11031
|
37
|
+
janito/change/applier/workspace_dir.py,sha256=whiuIIYyDHh2m-K_ZRMZGvMMQ1nh6lzXaWGhnFK1B1o,2443
|
38
|
+
janito/change/viewer/__init__.py,sha256=tj-rIVbuAm80B7-no-YgdxbtkDlwRK_0hqUfIDtDOAc,250
|
39
|
+
janito/change/viewer/content.py,sha256=iHRjhbpTJMi9v0W91XLogDfbVQEwLg04hlfYJOochzo,2036
|
40
|
+
janito/change/viewer/diff.py,sha256=OzaocnrnH4RCdzZPjT05pEmFJLcd_EgpKhRiAXJrvpc,1815
|
41
|
+
janito/change/viewer/panels.py,sha256=9i-b_vhpDK8cnh1qJ78xtg7cHxvOIMpvotdgK5oHNtU,21612
|
42
|
+
janito/change/viewer/styling.py,sha256=6i3xdnj4s5YkSH58oMgfgwZ3nD8T1tI-4Zy7HvcuoMg,4204
|
43
|
+
janito/change/viewer/themes.py,sha256=HISrK7-qR0m4dGZqFmC5loW2E1RSwAicP0mnpZvbSv0,1470
|
44
|
+
janito/clear_statement_parser/clear_statement_format.txt,sha256=_5iQylnCqgBaMVFVgx33QvEColV16oOwl1W4scu_-zc,13497
|
45
|
+
janito/clear_statement_parser/examples.txt,sha256=NnxDraTpCPZCjJjL2HvCMtA1NxBY7C0l3b5v61Y-5iY,8547
|
46
|
+
janito/clear_statement_parser/models.py,sha256=b7-lLHfQLI4q5dtaF4BVCwE9DI2xWwVQAHQqAl1c6s0,3640
|
47
|
+
janito/clear_statement_parser/parser.py,sha256=KO7Bj0pEl7iPH3FQ3knTGSLMGqKS_pXe3_eqfEHBtXw,20847
|
48
|
+
janito/cli/__init__.py,sha256=3gyMSaEAH2N4mXfZTLsHXKxXDxdhlYUeAPYQuhnOVBE,77
|
49
|
+
janito/cli/base.py,sha256=WKFSxX5FKk5Se5QUZ0X3_fAQVIWG6OEGKAXKebWiRaM,1188
|
50
|
+
janito/cli/commands.py,sha256=SlS39cSKEbM1oRup96ys9SlR59JxidJp6rnR-JeRk80,2823
|
51
|
+
janito/cli/functions.py,sha256=7DSFQaxKJ7JvkA8-wPuifogGk9NvqTUqC24nU2UUbVM,4143
|
52
|
+
janito/cli/history.py,sha256=UIP_6UiGnJXmBke5hYqKXQVsOPrtKeyfBXW8vDxIoHQ,2114
|
53
|
+
janito/cli/registry.py,sha256=R1sI45YonxjMSLhAle7Dt18X_devrMsLt0ljb-rNza4,690
|
54
|
+
janito/demo/__init__.py,sha256=vIGC6fs44yzuSK3-RgYnnanBtZwnW52ivdVbA8Yc8Lk,108
|
55
|
+
janito/demo/data.py,sha256=t208Kixcc3J6xbn43P2OSpqkpcRrrFoEXZ44oOQMcCE,415
|
56
|
+
janito/demo/mock_data.py,sha256=mBg-qF8GcRx7M40IGXGytQJOWihEUm51CJqcez1BKIk,695
|
57
|
+
janito/demo/operations.py,sha256=6QXFsrMVJqX30Y19nBD8fxTj5I6MJN4duQ2qwVG_lHY,1392
|
58
|
+
janito/demo/runner.py,sha256=Xv6v8QVlIkEnlSaNU-Jt6Yjl_NbNg_g8DMZnp8kiFGQ,2385
|
59
|
+
janito/demo/scenarios.py,sha256=xKW6cAL3tSHWP76fvbKHf2HyruhamAzHY1VaTVCkM8o,1060
|
60
|
+
janito/search_replace/README.md,sha256=ctN4ubykUdUMaqHtM9TGM31pCrdyCKiwfHn03b0cwBw,4614
|
61
|
+
janito/search_replace/__init__.py,sha256=7Er1wQRTqJ9fE7qFgzwnnhGHiEPWHel3KslOSWWDW5Q,319
|
62
|
+
janito/search_replace/__main__.py,sha256=i0Wz387fqgiHq46QXCXpU9wnwdJYinjyY6HyoJJr_xw,520
|
63
|
+
janito/search_replace/core.py,sha256=wxx-f0SCMmNfxymfobUlhQizcFyS1rD6RnMx0YekEQs,5904
|
64
|
+
janito/search_replace/logger.py,sha256=eJR1FA-KiGfZTo416NmGCdlpp3Cgaq7jj-CDFsAGqSU,1052
|
65
|
+
janito/search_replace/parser.py,sha256=wFr5Dc7Im6oh4AcVUkpwKIc0Gf5oNBCYKT2lhexmyQg,2123
|
66
|
+
janito/search_replace/play.py,sha256=vmEqbna8CAEYk4B6Og0eETCRAYRmrBSnFTdiXopsH3M,1820
|
67
|
+
janito/search_replace/replacer.py,sha256=7MKO9PpbAMUqmnxDk1mqkZ_yWE13QwjDfvjT0uU4ypA,1556
|
68
|
+
janito/search_replace/searcher.py,sha256=F23WQ14myJ4pCIpDgS0wCFM4poimrlXBz7_XF3lmTZg,17228
|
69
|
+
janito/search_replace/strategy_result.py,sha256=-XaT7blkoADV7VjFJZWTm2VztADojHmYn1dAvUBIVyw,293
|
70
|
+
janito/shell/__init__.py,sha256=TFZi_TSEefpGXQwYcavsgJ0mX8d--wzNk-7qLgqmnOI,1158
|
71
|
+
janito/shell/bus.py,sha256=xRl2-zlJsa0KPL2aSVtVJy9xH6GLw_7tEplvMIY5eCk,1041
|
72
|
+
janito/shell/commands.py,sha256=pjiJhf7jZB0TORDEewyFEKjjKiSus2VRLZdvs3BiQpg,4997
|
73
|
+
janito/shell/history.py,sha256=EMSbdW9ccHQ__WA-WM5VHjnu_ihTk-4sCpQXmi7e8iI,706
|
74
|
+
janito/shell/processor.py,sha256=GvINV9eR2JLG9lZ0ZbVIfGz6E6575iMhtst4oN5qwKw,1102
|
75
|
+
janito/shell/prompt.py,sha256=zyHXbuugu8kyVtqbg3SvIWwpCQpv1qHtEBE2jbhNY8I,1744
|
76
|
+
janito/shell/registry.py,sha256=5nDeTQ8rl5ZAEaR6RefZOuf9k5wB0T20QIJV1hTciI4,2339
|
77
|
+
janito/tui/__init__.py,sha256=5u1TfGO99LmCtC8ilpXahkXWVJSWtBACqU_O8VtJewI,773
|
78
|
+
janito/tui/base.py,sha256=qU_8i0aQ3sWIkgnKQDUGr3bhypDzSdqYCkNGFfRbIos,692
|
79
|
+
janito/tui/flows/__init__.py,sha256=uI8MtTdIe2BWqbqcfJNnBdhxZcwc207wVjIWusGuDq4,161
|
80
|
+
janito/tui/flows/changes.py,sha256=e-zHzmY8kbrbPRyyfcecFW21mH0xnn9AXnr7gsYI82s,2153
|
81
|
+
janito/tui/flows/content.py,sha256=mAi4R0ncpC_PzjETqqH9kvy2n1xRKWLhNX5xqPgQjoM,4197
|
82
|
+
janito/tui/flows/selection.py,sha256=PcrcY8hTl3ljZOmLYA_I3r3CVRdqKfqFlRxZSgS9rDo,4031
|
83
|
+
janito/tui/screens/__init__.py,sha256=YZ8NVoXMs7c_aT1NuIBiZrkLfXCzaQ85yMKZ_Uh6wlE,69
|
84
|
+
janito/tui/screens/app.py,sha256=XEiADppmDOYkjR1p7sH6N7Zqqm7RWvXSd5_udlAdvbA,92
|
85
|
+
janito/workspace/__init__.py,sha256=BV-O8czw125wprurtIOm-DNA01ZpSojNTYGBWZx14Cw,111
|
86
|
+
janito/workspace/analysis.py,sha256=pJ6fe7P8Ex1WXlZfhjr_73ZJpPfkjxt9BMoH6aSMLh8,4084
|
87
|
+
janito/workspace/show.py,sha256=nyHBM4xTYQL61axXemq0b4O6sHtS00EQQSoeT2PdCnc,4932
|
88
|
+
janito/workspace/stats.py,sha256=KcLouvJ3xqDIf1qTZaZeAaojeuixkBNcooob2twqFPg,1325
|
89
|
+
janito/workspace/types.py,sha256=qPZj8DCC38ygFB9am2j4sXsw-cmtMMG60aAOjoVPxr0,3450
|
90
|
+
janito/workspace/workset.py,sha256=my9HBWdGz09zHGR1BmwYiPxr-rNHH-pDEs_YVNVHd64,3903
|
91
|
+
janito/workspace/workspace.py,sha256=dDx78YQjTCHoiTu7gP4n2TdFnNbmFBOC-n07ka7YKEQ,4509
|
92
|
+
janito-0.7.0.dist-info/METADATA,sha256=7KpfpqDHPRgcU2dgvHoqX6JzQyhl_1Fnih_6zpmL6gE,3990
|
93
|
+
janito-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
94
|
+
janito-0.7.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
|
95
|
+
janito-0.7.0.dist-info/licenses/LICENSE,sha256=xLIUXRPjtsgQml2zD1Pn4LpgiyZ49raw6jZDlO_gZdo,1062
|
96
|
+
janito-0.7.0.dist-info/RECORD,,
|
janito/_contextparser.py
DELETED
@@ -1,113 +0,0 @@
|
|
1
|
-
from typing import List, Tuple, Optional, NamedTuple
|
2
|
-
from difflib import SequenceMatcher
|
3
|
-
from janito.config import config
|
4
|
-
from rich.console import Console
|
5
|
-
|
6
|
-
class ContextError(NamedTuple):
|
7
|
-
"""Contains error details for context matching failures"""
|
8
|
-
pre_context: List[str]
|
9
|
-
post_context: List[str]
|
10
|
-
content: str
|
11
|
-
|
12
|
-
def parse_change_block(content: str) -> Tuple[List[str], List[str], List[str]]:
|
13
|
-
"""Parse a change block into pre-context, post-context and change lines.
|
14
|
-
Returns (pre_context_lines, post_context_lines, change_lines)"""
|
15
|
-
pre_context_lines = []
|
16
|
-
post_context_lines = []
|
17
|
-
change_lines = []
|
18
|
-
in_pre_context = True
|
19
|
-
|
20
|
-
for line in content.splitlines():
|
21
|
-
if line.startswith('='):
|
22
|
-
if in_pre_context:
|
23
|
-
pre_context_lines.append(line[1:])
|
24
|
-
else:
|
25
|
-
post_context_lines.append(line[1:])
|
26
|
-
elif line.startswith('>'):
|
27
|
-
in_pre_context = False
|
28
|
-
change_lines.append(line[1:])
|
29
|
-
|
30
|
-
return pre_context_lines, post_context_lines, change_lines
|
31
|
-
|
32
|
-
def find_context_match(file_content: str, pre_context: List[str], post_context: List[str], min_context: int = 2) -> Optional[Tuple[int, int]]:
|
33
|
-
"""Find exact matching location using line-by-line matching.
|
34
|
-
Returns (start_index, end_index) or None if no match found."""
|
35
|
-
if not (pre_context or post_context) or (len(pre_context) + len(post_context)) < min_context:
|
36
|
-
return None
|
37
|
-
|
38
|
-
file_lines = file_content.splitlines()
|
39
|
-
|
40
|
-
# Function to check if lines match at a given position
|
41
|
-
def lines_match_at(pos: int, target_lines: List[str]) -> bool:
|
42
|
-
if pos + len(target_lines) > len(file_lines):
|
43
|
-
return False
|
44
|
-
return all(a == b for a, b in zip(file_lines[pos:pos + len(target_lines)], target_lines))
|
45
|
-
|
46
|
-
# For debug output
|
47
|
-
debug_matches = []
|
48
|
-
|
49
|
-
# Try to find pre_context match
|
50
|
-
pre_match_pos = None
|
51
|
-
if pre_context:
|
52
|
-
for i in range(len(file_lines) - len(pre_context) + 1):
|
53
|
-
if lines_match_at(i, pre_context):
|
54
|
-
pre_match_pos = i
|
55
|
-
break
|
56
|
-
if config.debug:
|
57
|
-
# Record first 20 non-matches for debug output
|
58
|
-
if len(debug_matches) < 20:
|
59
|
-
debug_matches.append((i, file_lines[i:i + len(pre_context)]))
|
60
|
-
|
61
|
-
# Try to find post_context match after pre_context if found
|
62
|
-
if pre_match_pos is not None and post_context:
|
63
|
-
expected_post_pos = pre_match_pos + len(pre_context)
|
64
|
-
if not lines_match_at(expected_post_pos, post_context):
|
65
|
-
pre_match_pos = None
|
66
|
-
|
67
|
-
if pre_match_pos is None and config.debug:
|
68
|
-
console = Console()
|
69
|
-
console.print("\n[bold red]Context Match Debug:[/bold red]")
|
70
|
-
|
71
|
-
if pre_context:
|
72
|
-
console.print("\n[yellow]Expected pre-context:[/yellow]")
|
73
|
-
for i, line in enumerate(pre_context):
|
74
|
-
console.print(f" {i+1:2d} | '{line}'")
|
75
|
-
|
76
|
-
if post_context:
|
77
|
-
console.print("\n[yellow]Expected post-context:[/yellow]")
|
78
|
-
for i, line in enumerate(post_context):
|
79
|
-
console.print(f" {i+1:2d} | '{line}'")
|
80
|
-
|
81
|
-
console.print("\n[yellow]First 20 attempted matches in file:[/yellow]")
|
82
|
-
for pos, lines in debug_matches:
|
83
|
-
console.print(f"\n[cyan]At line {pos+1}:[/cyan]")
|
84
|
-
for i, line in enumerate(lines):
|
85
|
-
match_status = "≠" if i < len(pre_context) and line != pre_context[i] else "="
|
86
|
-
console.print(f" {i+1:2d} | '{line}' {match_status}")
|
87
|
-
|
88
|
-
return None
|
89
|
-
|
90
|
-
if pre_match_pos is None:
|
91
|
-
return None
|
92
|
-
|
93
|
-
end_pos = pre_match_pos + len(pre_context)
|
94
|
-
|
95
|
-
return pre_match_pos, end_pos
|
96
|
-
|
97
|
-
def apply_changes(content: str,
|
98
|
-
pre_context_lines: List[str],
|
99
|
-
post_context_lines: List[str],
|
100
|
-
change_lines: List[str]) -> Optional[Tuple[str, Optional[ContextError]]]:
|
101
|
-
"""Apply changes with context matching, returns (new_content, error_details)"""
|
102
|
-
if not content.strip() and not pre_context_lines and not post_context_lines:
|
103
|
-
return '\n'.join(change_lines), None
|
104
|
-
|
105
|
-
pre_context = '\n'.join(pre_context_lines)
|
106
|
-
post_context = '\n'.join(post_context_lines)
|
107
|
-
|
108
|
-
if pre_context and pre_context not in content:
|
109
|
-
return None, ContextError(pre_context_lines, post_context_lines, content)
|
110
|
-
|
111
|
-
if post_context and post_context not in content:
|
112
|
-
return None, ContextError(pre_context_lines, post_context_lines, content)
|
113
|
-
|
janito/analysis/display.py
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
"""Display formatting for analysis results."""
|
2
|
-
|
3
|
-
from typing import Optional, Dict
|
4
|
-
from pathlib import Path
|
5
|
-
from datetime import datetime, timezone
|
6
|
-
from rich.console import Console
|
7
|
-
from rich.markdown import Markdown
|
8
|
-
from rich.panel import Panel
|
9
|
-
from rich.text import Text
|
10
|
-
from rich import box
|
11
|
-
from rich.columns import Columns
|
12
|
-
from rich.rule import Rule
|
13
|
-
from janito.agents import AIAgent, AgentSingleton
|
14
|
-
from .options import AnalysisOption
|
15
|
-
from .options import parse_analysis_options
|
16
|
-
|
17
|
-
MIN_PANEL_WIDTH = 40
|
18
|
-
|
19
|
-
def get_analysis_summary(options: Dict[str, AnalysisOption]) -> str:
|
20
|
-
"""Generate a summary of affected directories and their file counts."""
|
21
|
-
dirs_summary = {}
|
22
|
-
for _, option in options.items():
|
23
|
-
for file in option.affected_files:
|
24
|
-
clean_path = option.get_clean_path(file)
|
25
|
-
dir_path = str(Path(clean_path).parent)
|
26
|
-
dirs_summary[dir_path] = dirs_summary.get(dir_path, 0) + 1
|
27
|
-
|
28
|
-
return " | ".join([f"{dir}: {count} files" for dir, count in dirs_summary.items()])
|
29
|
-
|
30
|
-
def _display_options(options: Dict[str, AnalysisOption]) -> None:
|
31
|
-
"""Display available options in a single horizontal row with equal widths."""
|
32
|
-
console = Console()
|
33
|
-
|
34
|
-
console.print()
|
35
|
-
console.print(Rule(" Available Options ", style="bold cyan", align="center"))
|
36
|
-
console.print()
|
37
|
-
|
38
|
-
term_width = console.width or 100
|
39
|
-
spacing = 4
|
40
|
-
total_spacing = spacing * (len(options) - 1)
|
41
|
-
panel_width = max(MIN_PANEL_WIDTH, (term_width - total_spacing) // len(options))
|
42
|
-
|
43
|
-
panels = []
|
44
|
-
for letter, option in options.items():
|
45
|
-
content = Text()
|
46
|
-
|
47
|
-
content.append("Description:\n", style="bold cyan")
|
48
|
-
for item in option.description_items:
|
49
|
-
content.append(f"• {item}\n", style="white")
|
50
|
-
content.append("\n")
|
51
|
-
|
52
|
-
if option.affected_files:
|
53
|
-
content.append("Affected files:\n", style="bold cyan")
|
54
|
-
unique_files = {}
|
55
|
-
for file in option.affected_files:
|
56
|
-
clean_path = option.get_clean_path(file)
|
57
|
-
unique_files[clean_path] = file
|
58
|
-
|
59
|
-
for file in unique_files.values():
|
60
|
-
if '(new)' in file:
|
61
|
-
color = "green"
|
62
|
-
elif '(removed)' in file:
|
63
|
-
color = "red"
|
64
|
-
else:
|
65
|
-
color = "yellow"
|
66
|
-
content.append(f"• {file}\n", style=color)
|
67
|
-
|
68
|
-
panel = Panel(
|
69
|
-
content,
|
70
|
-
box=box.ROUNDED,
|
71
|
-
border_style="cyan",
|
72
|
-
title=f"Option {letter}: {option.summary}",
|
73
|
-
title_align="center",
|
74
|
-
padding=(1, 2),
|
75
|
-
width=panel_width
|
76
|
-
)
|
77
|
-
panels.append(panel)
|
78
|
-
|
79
|
-
if panels:
|
80
|
-
columns = Columns(
|
81
|
-
panels,
|
82
|
-
align="center",
|
83
|
-
expand=True,
|
84
|
-
equal=True,
|
85
|
-
padding=(0, spacing // 2)
|
86
|
-
)
|
87
|
-
console.print(columns)
|
88
|
-
|
89
|
-
def _display_markdown(content: str) -> None:
|
90
|
-
"""Display content in markdown format."""
|
91
|
-
console = Console()
|
92
|
-
md = Markdown(content)
|
93
|
-
console.print(md)
|
94
|
-
|
95
|
-
def _display_raw_history(agent: AIAgent) -> None:
|
96
|
-
"""Display raw message history from Claude agent."""
|
97
|
-
console = Console()
|
98
|
-
console.print("\n=== Message History ===")
|
99
|
-
for role, content in agent.messages_history:
|
100
|
-
console.print(f"\n[bold cyan]{role.upper()}:[/bold cyan]")
|
101
|
-
console.print(content)
|
102
|
-
console.print("\n=== End Message History ===\n")
|
103
|
-
|
104
|
-
def format_analysis(analysis: str, raw: bool = False, workdir: Optional[Path] = None) -> None:
|
105
|
-
"""Format and display the analysis output with enhanced capabilities."""
|
106
|
-
console = Console()
|
107
|
-
|
108
|
-
agent = AgentSingleton.get_agent()
|
109
|
-
if raw and agent:
|
110
|
-
_display_raw_history(agent)
|
111
|
-
else:
|
112
|
-
options = parse_analysis_options(analysis)
|
113
|
-
if options:
|
114
|
-
_display_options(options)
|
115
|
-
else:
|
116
|
-
console.print("\n[yellow]Warning: No valid options found in response. Displaying as markdown.[/yellow]\n")
|
117
|
-
_display_markdown(analysis)
|
118
|
-
|
119
|
-
def get_history_file_type(filepath: Path) -> str:
|
120
|
-
"""Determine the type of saved file based on its name"""
|
121
|
-
name = filepath.name.lower()
|
122
|
-
if 'changes' in name:
|
123
|
-
return 'changes'
|
124
|
-
elif 'selected' in name:
|
125
|
-
return 'selected'
|
126
|
-
elif 'analysis' in name:
|
127
|
-
return 'analysis'
|
128
|
-
elif 'response' in name:
|
129
|
-
return 'response'
|
130
|
-
return 'unknown'
|
131
|
-
|
132
|
-
def get_history_path(workdir: Path) -> Path:
|
133
|
-
"""Create and return the history directory path"""
|
134
|
-
history_dir = workdir / '.janito' / 'history'
|
135
|
-
history_dir.mkdir(parents=True, exist_ok=True)
|
136
|
-
return history_dir
|
137
|
-
|
138
|
-
def get_timestamp() -> str:
|
139
|
-
"""Get current UTC timestamp in YMD_HMS format with leading zeros"""
|
140
|
-
return datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')
|
141
|
-
|
142
|
-
def save_to_file(content: str, prefix: str, workdir: Path) -> Path:
|
143
|
-
"""Save content to a timestamped file in history directory"""
|
144
|
-
history_dir = get_history_path(workdir)
|
145
|
-
timestamp = get_timestamp()
|
146
|
-
filename = f"{timestamp}_{prefix}.txt"
|
147
|
-
file_path = history_dir / filename
|
148
|
-
file_path.write_text(content)
|
149
|
-
return file_path
|
janito/analysis/options.py
DELETED
@@ -1,112 +0,0 @@
|
|
1
|
-
"""Options handling for analysis module."""
|
2
|
-
|
3
|
-
from dataclasses import dataclass
|
4
|
-
from pathlib import Path
|
5
|
-
from typing import List, Dict, Tuple
|
6
|
-
import re
|
7
|
-
|
8
|
-
@dataclass
|
9
|
-
class AnalysisOption:
|
10
|
-
letter: str
|
11
|
-
summary: str
|
12
|
-
affected_files: List[str]
|
13
|
-
description_items: List[str]
|
14
|
-
|
15
|
-
def get_clean_path(self, file_path: str) -> str:
|
16
|
-
"""Get clean path without markers"""
|
17
|
-
return file_path.split(' (')[0].strip()
|
18
|
-
|
19
|
-
def is_new_file(self, file_path: str) -> bool:
|
20
|
-
"""Check if file is marked as new"""
|
21
|
-
return '(new)' in file_path
|
22
|
-
|
23
|
-
def is_removed_file(self, file_path: str) -> bool:
|
24
|
-
"""Check if file is marked as removed"""
|
25
|
-
return '(removed)' in file_path
|
26
|
-
|
27
|
-
def get_affected_paths(self, workdir: Path = None) -> List[Path]:
|
28
|
-
"""Get list of affected paths, resolving against workdir if provided"""
|
29
|
-
paths = []
|
30
|
-
for file_path in self.affected_files:
|
31
|
-
clean_path = self.get_clean_path(file_path)
|
32
|
-
path = workdir / clean_path if workdir else Path(clean_path)
|
33
|
-
paths.append(path)
|
34
|
-
return paths
|
35
|
-
|
36
|
-
def process_file_path(self, path: str) -> Tuple[str, bool, bool, bool]:
|
37
|
-
"""Process a file path to extract clean path and modification flags
|
38
|
-
Returns: (clean_path, is_new, is_modified, is_removed)
|
39
|
-
"""
|
40
|
-
clean_path = path.strip()
|
41
|
-
is_new = False
|
42
|
-
is_modified = False
|
43
|
-
is_removed = False
|
44
|
-
|
45
|
-
if "(new)" in clean_path:
|
46
|
-
is_new = True
|
47
|
-
clean_path = clean_path.replace("(new)", "").strip()
|
48
|
-
if "(modified)" in clean_path:
|
49
|
-
is_modified = True
|
50
|
-
clean_path = clean_path.replace("(modified)", "").strip()
|
51
|
-
if "(removed)" in clean_path:
|
52
|
-
is_removed = True
|
53
|
-
clean_path = clean_path.replace("(removed)", "").strip()
|
54
|
-
|
55
|
-
return clean_path, is_new, is_modified, is_removed
|
56
|
-
|
57
|
-
def parse_analysis_options(response: str) -> Dict[str, AnalysisOption]:
|
58
|
-
"""Parse options from the response text."""
|
59
|
-
options = {}
|
60
|
-
|
61
|
-
if 'END_OF_OPTIONS' in response:
|
62
|
-
response = response.split('END_OF_OPTIONS')[0]
|
63
|
-
|
64
|
-
current_option = None
|
65
|
-
current_section = None
|
66
|
-
|
67
|
-
lines = response.split('\n')
|
68
|
-
|
69
|
-
for line in lines:
|
70
|
-
line = line.strip()
|
71
|
-
if not line:
|
72
|
-
continue
|
73
|
-
|
74
|
-
option_match = re.match(r'^([A-Z])\.\s+(.+)$', line)
|
75
|
-
if option_match:
|
76
|
-
if current_option:
|
77
|
-
options[current_option.letter] = current_option
|
78
|
-
|
79
|
-
letter, summary = option_match.groups()
|
80
|
-
current_option = AnalysisOption(
|
81
|
-
letter=letter,
|
82
|
-
summary=summary,
|
83
|
-
affected_files=[],
|
84
|
-
description_items=[]
|
85
|
-
)
|
86
|
-
current_section = None
|
87
|
-
continue
|
88
|
-
|
89
|
-
if re.match(r'^-+$', line):
|
90
|
-
continue
|
91
|
-
|
92
|
-
if current_option:
|
93
|
-
if line.lower() == 'description:':
|
94
|
-
current_section = 'description'
|
95
|
-
continue
|
96
|
-
elif line.lower() == 'affected files:':
|
97
|
-
current_section = 'files'
|
98
|
-
continue
|
99
|
-
|
100
|
-
if line.startswith('- '):
|
101
|
-
content = line[2:].strip()
|
102
|
-
if current_section == 'description':
|
103
|
-
current_option.description_items.append(content)
|
104
|
-
elif current_section == 'files':
|
105
|
-
# Accept any combination of new, modified or removed markers
|
106
|
-
if any(marker in content for marker in ['(new)', '(modified)', '(removed)']):
|
107
|
-
current_option.affected_files.append(content)
|
108
|
-
|
109
|
-
if current_option:
|
110
|
-
options[current_option.letter] = current_option
|
111
|
-
|
112
|
-
return options
|