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.
Files changed (110) hide show
  1. janito/__init__.py +0 -47
  2. janito/__main__.py +105 -17
  3. janito/agents/__init__.py +9 -9
  4. janito/agents/agent.py +10 -3
  5. janito/agents/claudeai.py +15 -34
  6. janito/agents/openai.py +5 -1
  7. janito/change/__init__.py +29 -16
  8. janito/change/__main__.py +0 -0
  9. janito/{analysis → change/analysis}/__init__.py +5 -15
  10. janito/change/analysis/__main__.py +7 -0
  11. janito/change/analysis/analyze.py +62 -0
  12. janito/change/analysis/formatting.py +78 -0
  13. janito/change/analysis/options.py +81 -0
  14. janito/{analysis → change/analysis}/prompts.py +33 -18
  15. janito/change/analysis/view/__init__.py +9 -0
  16. janito/change/analysis/view/terminal.py +181 -0
  17. janito/change/applier/__init__.py +5 -0
  18. janito/change/applier/file.py +58 -0
  19. janito/change/applier/main.py +156 -0
  20. janito/change/applier/text.py +247 -0
  21. janito/change/applier/workspace_dir.py +58 -0
  22. janito/change/core.py +124 -0
  23. janito/{changehistory.py → change/history.py} +12 -14
  24. janito/change/operations.py +7 -0
  25. janito/change/parser.py +287 -0
  26. janito/change/play.py +54 -0
  27. janito/change/preview.py +82 -0
  28. janito/change/prompts.py +121 -0
  29. janito/change/test.py +0 -0
  30. janito/change/validator.py +269 -0
  31. janito/{changeviewer → change/viewer}/__init__.py +3 -4
  32. janito/change/viewer/content.py +66 -0
  33. janito/{changeviewer → change/viewer}/diff.py +19 -4
  34. janito/change/viewer/panels.py +533 -0
  35. janito/change/viewer/styling.py +114 -0
  36. janito/{changeviewer → change/viewer}/themes.py +3 -5
  37. janito/clear_statement_parser/clear_statement_format.txt +328 -0
  38. janito/clear_statement_parser/examples.txt +326 -0
  39. janito/clear_statement_parser/models.py +104 -0
  40. janito/clear_statement_parser/parser.py +496 -0
  41. janito/cli/base.py +30 -0
  42. janito/cli/commands.py +75 -40
  43. janito/cli/functions.py +19 -194
  44. janito/cli/history.py +61 -0
  45. janito/common.py +65 -8
  46. janito/config.py +70 -5
  47. janito/demo/__init__.py +4 -0
  48. janito/demo/data.py +13 -0
  49. janito/demo/mock_data.py +20 -0
  50. janito/demo/operations.py +45 -0
  51. janito/demo/runner.py +59 -0
  52. janito/demo/scenarios.py +32 -0
  53. janito/prompt.py +36 -0
  54. janito/qa.py +6 -14
  55. janito/search_replace/README.md +192 -0
  56. janito/search_replace/__init__.py +7 -0
  57. janito/search_replace/__main__.py +21 -0
  58. janito/search_replace/core.py +120 -0
  59. janito/search_replace/logger.py +35 -0
  60. janito/search_replace/parser.py +52 -0
  61. janito/search_replace/play.py +61 -0
  62. janito/search_replace/replacer.py +36 -0
  63. janito/search_replace/searcher.py +411 -0
  64. janito/search_replace/strategy_result.py +10 -0
  65. janito/shell/__init__.py +38 -0
  66. janito/shell/bus.py +31 -0
  67. janito/shell/commands.py +136 -0
  68. janito/shell/history.py +20 -0
  69. janito/shell/processor.py +32 -0
  70. janito/shell/prompt.py +48 -0
  71. janito/shell/registry.py +60 -0
  72. janito/tui/__init__.py +21 -0
  73. janito/tui/base.py +22 -0
  74. janito/tui/flows/__init__.py +5 -0
  75. janito/tui/flows/changes.py +65 -0
  76. janito/tui/flows/content.py +128 -0
  77. janito/tui/flows/selection.py +117 -0
  78. janito/tui/screens/__init__.py +3 -0
  79. janito/tui/screens/app.py +1 -0
  80. janito/workspace/__init__.py +6 -0
  81. janito/workspace/analysis.py +121 -0
  82. janito/workspace/show.py +141 -0
  83. janito/workspace/stats.py +43 -0
  84. janito/workspace/types.py +98 -0
  85. janito/workspace/workset.py +108 -0
  86. janito/workspace/workspace.py +114 -0
  87. janito-0.7.0.dist-info/METADATA +167 -0
  88. janito-0.7.0.dist-info/RECORD +96 -0
  89. {janito-0.5.0.dist-info → janito-0.7.0.dist-info}/WHEEL +1 -1
  90. janito/_contextparser.py +0 -113
  91. janito/analysis/display.py +0 -149
  92. janito/analysis/options.py +0 -112
  93. janito/change/applier.py +0 -269
  94. janito/change/content.py +0 -62
  95. janito/change/indentation.py +0 -33
  96. janito/change/position.py +0 -169
  97. janito/changeviewer/panels.py +0 -268
  98. janito/changeviewer/styling.py +0 -59
  99. janito/console/__init__.py +0 -3
  100. janito/console/commands.py +0 -112
  101. janito/console/core.py +0 -62
  102. janito/console/display.py +0 -157
  103. janito/fileparser.py +0 -334
  104. janito/prompts.py +0 -81
  105. janito/scan.py +0 -176
  106. janito/tests/test_fileparser.py +0 -26
  107. janito-0.5.0.dist-info/METADATA +0 -146
  108. janito-0.5.0.dist-info/RECORD +0 -45
  109. {janito-0.5.0.dist-info → janito-0.7.0.dist-info}/entry_points.txt +0 -0
  110. {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
+ [![PyPI version](https://badge.fury.io/py/janito.svg)](https://badge.fury.io/py/janito)
29
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.26.3
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
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
-
@@ -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
@@ -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