janito 2.24.0__py3-none-any.whl → 2.25.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 (47) hide show
  1. janito/cli/chat_mode/session.py +2 -2
  2. janito/cli/cli_commands/list_plugins.py +32 -0
  3. janito/cli/core/getters.py +2 -0
  4. janito/cli/main_cli.py +6 -2
  5. janito/cli/single_shot_mode/handler.py +2 -2
  6. janito/config_manager.py +8 -0
  7. janito/exceptions.py +19 -1
  8. janito/plugins/base.py +53 -2
  9. janito/plugins/config.py +87 -0
  10. janito/plugins/discovery.py +32 -0
  11. janito/plugins/manager.py +56 -2
  12. janito/tools/adapters/local/adapter.py +8 -26
  13. janito/tools/adapters/local/ask_user.py +1 -1
  14. janito/tools/adapters/local/copy_file.py +3 -1
  15. janito/tools/adapters/local/create_directory.py +2 -2
  16. janito/tools/adapters/local/create_file.py +8 -4
  17. janito/tools/adapters/local/fetch_url.py +25 -22
  18. janito/tools/adapters/local/find_files.py +3 -2
  19. janito/tools/adapters/local/get_file_outline/core.py +3 -1
  20. janito/tools/adapters/local/get_file_outline/search_outline.py +1 -1
  21. janito/tools/adapters/local/move_file.py +3 -2
  22. janito/tools/adapters/local/open_html_in_browser.py +1 -1
  23. janito/tools/adapters/local/open_url.py +1 -1
  24. janito/tools/adapters/local/python_file_run.py +2 -0
  25. janito/tools/adapters/local/read_chart.py +61 -54
  26. janito/tools/adapters/local/read_files.py +4 -3
  27. janito/tools/adapters/local/remove_directory.py +2 -0
  28. janito/tools/adapters/local/remove_file.py +3 -3
  29. janito/tools/adapters/local/run_powershell_command.py +1 -0
  30. janito/tools/adapters/local/search_text/core.py +3 -2
  31. janito/tools/adapters/local/validate_file_syntax/core.py +3 -1
  32. janito/tools/adapters/local/view_file.py +3 -1
  33. janito/tools/loop_protection_decorator.py +64 -25
  34. janito/tools/path_utils.py +39 -0
  35. janito/tools/tools_adapter.py +68 -22
  36. {janito-2.24.0.dist-info → janito-2.25.0.dist-info}/METADATA +1 -1
  37. {janito-2.24.0.dist-info → janito-2.25.0.dist-info}/RECORD +46 -39
  38. janito-2.25.0.dist-info/top_level.txt +2 -0
  39. janito-coder/janito_coder/__init__.py +9 -0
  40. janito-coder/janito_coder/plugins/__init__.py +27 -0
  41. janito-coder/janito_coder/plugins/code_navigator.py +618 -0
  42. janito-coder/janito_coder/plugins/git_analyzer.py +273 -0
  43. janito-coder/pyproject.toml +347 -0
  44. janito-2.24.0.dist-info/top_level.txt +0 -1
  45. {janito-2.24.0.dist-info → janito-2.25.0.dist-info}/WHEEL +0 -0
  46. {janito-2.24.0.dist-info → janito-2.25.0.dist-info}/entry_points.txt +0 -0
  47. {janito-2.24.0.dist-info → janito-2.25.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,618 @@
1
+ """
2
+ Code navigation and analysis plugin using tree-sitter.
3
+ """
4
+
5
+ import os
6
+ import re
7
+ from typing import Dict, List, Optional, Any
8
+ from pathlib import Path
9
+
10
+ try:
11
+ from tree_sitter import Language, Parser
12
+ from tree_sitter_languages import get_language, get_parser
13
+
14
+ TREESITTER_AVAILABLE = True
15
+ except ImportError:
16
+ TREESITTER_AVAILABLE = False
17
+
18
+ from janito.plugins.base import Plugin, PluginMetadata
19
+ from janito.tools.tool_base import ToolBase, ToolPermissions
20
+
21
+
22
+ class FindDefinitionTool(ToolBase):
23
+ """Tool to find function/class definitions in code."""
24
+
25
+ tool_name = "find_definition"
26
+ permissions = ToolPermissions(read=True, write=False, execute=True)
27
+
28
+ def run(self, symbol: str, path: str = ".", language: str = None) -> str:
29
+ """
30
+ Find definitions of functions, classes, or variables.
31
+
32
+ Args:
33
+ symbol: Name of the symbol to find
34
+ path: Directory or file to search in
35
+ language: Specific language to search (python, javascript, etc.)
36
+
37
+ Returns:
38
+ Definition locations as string
39
+ """
40
+ if not TREESITTER_AVAILABLE:
41
+ return "Tree-sitter not available. Please install janito-coder with tree-sitter support."
42
+
43
+ try:
44
+ results = []
45
+ search_path = Path(path)
46
+
47
+ if search_path.is_file():
48
+ files = [search_path]
49
+ else:
50
+ # Find all source files
51
+ extensions = {
52
+ "python": [".py"],
53
+ "javascript": [".js", ".jsx"],
54
+ "typescript": [".ts", ".tsx"],
55
+ "java": [".java"],
56
+ "c": [".c", ".h"],
57
+ "cpp": [".cpp", ".cc", ".cxx", ".hpp"],
58
+ "rust": [".rs"],
59
+ "go": [".go"],
60
+ "ruby": [".rb"],
61
+ "php": [".php"],
62
+ "swift": [".swift"],
63
+ "kotlin": [".kt"],
64
+ "scala": [".scala"],
65
+ "csharp": [".cs"],
66
+ }
67
+
68
+ if language:
69
+ extensions_to_search = extensions.get(language.lower(), [])
70
+ else:
71
+ extensions_to_search = [
72
+ ext for exts in extensions.values() for ext in exts
73
+ ]
74
+
75
+ files = []
76
+ for ext in extensions_to_search:
77
+ files.extend(search_path.rglob(f"*{ext}"))
78
+
79
+ for file_path in files:
80
+ if file_path.is_file():
81
+ definitions = self._find_definitions_in_file(file_path, symbol)
82
+ results.extend(definitions)
83
+
84
+ if not results:
85
+ return f"No definitions found for '{symbol}'"
86
+
87
+ output = [f"Found {len(results)} definitions for '{symbol}':"]
88
+ for result in results:
89
+ output.append(f" {result}")
90
+
91
+ return "\n".join(output)
92
+
93
+ except Exception as e:
94
+ return f"Error finding definitions: {str(e)}"
95
+
96
+ def _find_definitions_in_file(self, file_path: Path, symbol: str) -> List[str]:
97
+ """Find definitions in a single file."""
98
+ results = []
99
+
100
+ try:
101
+ # Determine language from file extension
102
+ ext = file_path.suffix.lower()
103
+ language_map = {
104
+ ".py": "python",
105
+ ".js": "javascript",
106
+ ".jsx": "javascript",
107
+ ".ts": "typescript",
108
+ ".tsx": "typescript",
109
+ ".java": "java",
110
+ ".c": "c",
111
+ ".h": "c",
112
+ ".cpp": "cpp",
113
+ ".cc": "cpp",
114
+ ".cxx": "cpp",
115
+ ".hpp": "cpp",
116
+ ".rs": "rust",
117
+ ".go": "go",
118
+ ".rb": "ruby",
119
+ ".php": "php",
120
+ ".swift": "swift",
121
+ ".kt": "kotlin",
122
+ ".scala": "scala",
123
+ ".cs": "csharp",
124
+ }
125
+
126
+ language_name = language_map.get(ext)
127
+ if not language_name:
128
+ return results
129
+
130
+ # Get parser for the language
131
+ try:
132
+ parser = get_parser(language_name)
133
+ language = get_language(language_name)
134
+ except:
135
+ return results
136
+
137
+ # Parse the file
138
+ with open(file_path, "r", encoding="utf-8") as f:
139
+ source_code = f.read()
140
+
141
+ tree = parser.parse(bytes(source_code, "utf8"))
142
+
143
+ # Find definitions based on language
144
+ if language_name == "python":
145
+ results.extend(
146
+ self._find_python_definitions(tree, source_code, symbol, file_path)
147
+ )
148
+ elif language_name in ["javascript", "typescript"]:
149
+ results.extend(
150
+ self._find_js_definitions(tree, source_code, symbol, file_path)
151
+ )
152
+ elif language_name == "java":
153
+ results.extend(
154
+ self._find_java_definitions(tree, source_code, symbol, file_path)
155
+ )
156
+ elif language_name in ["c", "cpp"]:
157
+ results.extend(
158
+ self._find_cpp_definitions(tree, source_code, symbol, file_path)
159
+ )
160
+ elif language_name == "rust":
161
+ results.extend(
162
+ self._find_rust_definitions(tree, source_code, symbol, file_path)
163
+ )
164
+ elif language_name == "go":
165
+ results.extend(
166
+ self._find_go_definitions(tree, source_code, symbol, file_path)
167
+ )
168
+
169
+ except Exception:
170
+ pass # Skip files that can't be parsed
171
+
172
+ return results
173
+
174
+ def _find_python_definitions(
175
+ self, tree, source_code: str, symbol: str, file_path: Path
176
+ ) -> List[str]:
177
+ """Find Python function/class definitions."""
178
+ results = []
179
+ lines = source_code.split("\n")
180
+
181
+ def find_nodes(node, type_name):
182
+ if node.type in ["function_definition", "class_definition"]:
183
+ for child in node.children:
184
+ if (
185
+ child.type == "identifier"
186
+ and child.text.decode("utf8") == symbol
187
+ ):
188
+ start_line = node.start_point[0] + 1
189
+ results.append(
190
+ f"{file_path}:{start_line}: {node.type} '{symbol}'"
191
+ )
192
+ break
193
+
194
+ for child in node.children:
195
+ find_nodes(child, type_name)
196
+
197
+ find_nodes(tree.root_node, symbol)
198
+ return results
199
+
200
+ def _find_js_definitions(
201
+ self, tree, source_code: str, symbol: str, file_path: Path
202
+ ) -> List[str]:
203
+ """Find JavaScript/TypeScript function/class definitions."""
204
+ results = []
205
+
206
+ def find_nodes(node, type_name):
207
+ if node.type in [
208
+ "function_declaration",
209
+ "class_declaration",
210
+ "method_definition",
211
+ ]:
212
+ for child in node.children:
213
+ if (
214
+ child.type == "identifier"
215
+ and child.text.decode("utf8") == symbol
216
+ ):
217
+ start_line = node.start_point[0] + 1
218
+ results.append(
219
+ f"{file_path}:{start_line}: {node.type} '{symbol}'"
220
+ )
221
+ break
222
+
223
+ for child in node.children:
224
+ find_nodes(child, type_name)
225
+
226
+ find_nodes(tree.root_node, symbol)
227
+ return results
228
+
229
+ def _find_java_definitions(
230
+ self, tree, source_code: str, symbol: str, file_path: Path
231
+ ) -> List[str]:
232
+ """Find Java class/method definitions."""
233
+ results = []
234
+
235
+ def find_nodes(node, type_name):
236
+ if node.type in ["class_declaration", "method_declaration"]:
237
+ for child in node.children:
238
+ if (
239
+ child.type == "identifier"
240
+ and child.text.decode("utf8") == symbol
241
+ ):
242
+ start_line = node.start_point[0] + 1
243
+ results.append(
244
+ f"{file_path}:{start_line}: {node.type} '{symbol}'"
245
+ )
246
+ break
247
+
248
+ for child in node.children:
249
+ find_nodes(child, type_name)
250
+
251
+ find_nodes(tree.root_node, symbol)
252
+ return results
253
+
254
+ def _find_cpp_definitions(
255
+ self, tree, source_code: str, symbol: str, file_path: Path
256
+ ) -> List[str]:
257
+ """Find C++ function/class definitions."""
258
+ results = []
259
+
260
+ def find_nodes(node, type_name):
261
+ if node.type in [
262
+ "function_definition",
263
+ "class_specifier",
264
+ "struct_specifier",
265
+ ]:
266
+ for child in node.children:
267
+ if (
268
+ child.type == "identifier"
269
+ and child.text.decode("utf8") == symbol
270
+ ):
271
+ start_line = node.start_point[0] + 1
272
+ results.append(
273
+ f"{file_path}:{start_line}: {node.type} '{symbol}'"
274
+ )
275
+ break
276
+
277
+ for child in node.children:
278
+ find_nodes(child, type_name)
279
+
280
+ find_nodes(tree.root_node, symbol)
281
+ return results
282
+
283
+ def _find_rust_definitions(
284
+ self, tree, source_code: str, symbol: str, file_path: Path
285
+ ) -> List[str]:
286
+ """Find Rust function/struct definitions."""
287
+ results = []
288
+
289
+ def find_nodes(node, type_name):
290
+ if node.type in ["function_item", "struct_item", "enum_item", "trait_item"]:
291
+ for child in node.children:
292
+ if (
293
+ child.type == "identifier"
294
+ and child.text.decode("utf8") == symbol
295
+ ):
296
+ start_line = node.start_point[0] + 1
297
+ results.append(
298
+ f"{file_path}:{start_line}: {node.type} '{symbol}'"
299
+ )
300
+ break
301
+
302
+ for child in node.children:
303
+ find_nodes(child, type_name)
304
+
305
+ find_nodes(tree.root_node, symbol)
306
+ return results
307
+
308
+ def _find_go_definitions(
309
+ self, tree, source_code: str, symbol: str, file_path: Path
310
+ ) -> List[str]:
311
+ """Find Go function/type definitions."""
312
+ results = []
313
+
314
+ def find_nodes(node, type_name):
315
+ if node.type in ["function_declaration", "type_declaration"]:
316
+ for child in node.children:
317
+ if (
318
+ child.type == "identifier"
319
+ and child.text.decode("utf8") == symbol
320
+ ):
321
+ start_line = node.start_point[0] + 1
322
+ results.append(
323
+ f"{file_path}:{start_line}: {node.type} '{symbol}'"
324
+ )
325
+ break
326
+
327
+ for child in node.children:
328
+ find_nodes(child, type_name)
329
+
330
+ find_nodes(tree.root_node, symbol)
331
+ return results
332
+
333
+
334
+ class FindReferencesTool(ToolBase):
335
+ """Tool to find references to symbols in code."""
336
+
337
+ tool_name = "find_references"
338
+ permissions = ToolPermissions(read=True, write=False, execute=True)
339
+
340
+ def run(self, symbol: str, path: str = ".", language: str = None) -> str:
341
+ """
342
+ Find all references to a symbol.
343
+
344
+ Args:
345
+ symbol: Name of the symbol to find references for
346
+ path: Directory or file to search in
347
+ language: Specific language to search
348
+
349
+ Returns:
350
+ Reference locations as string
351
+ """
352
+ if not TREESITTER_AVAILABLE:
353
+ return "Tree-sitter not available. Please install janito-coder with tree-sitter support."
354
+
355
+ try:
356
+ results = []
357
+ search_path = Path(path)
358
+
359
+ if search_path.is_file():
360
+ files = [search_path]
361
+ else:
362
+ # Find all source files
363
+ extensions = {
364
+ "python": [".py"],
365
+ "javascript": [".js", ".jsx"],
366
+ "typescript": [".ts", ".tsx"],
367
+ "java": [".java"],
368
+ "c": [".c", ".h"],
369
+ "cpp": [".cpp", ".cc", ".cxx", ".hpp"],
370
+ "rust": [".rs"],
371
+ "go": [".go"],
372
+ "ruby": [".rb"],
373
+ "php": [".php"],
374
+ "swift": [".swift"],
375
+ "kotlin": [".kt"],
376
+ "scala": [".scala"],
377
+ "csharp": [".cs"],
378
+ }
379
+
380
+ if language:
381
+ extensions_to_search = extensions.get(language.lower(), [])
382
+ else:
383
+ extensions_to_search = [
384
+ ext for exts in extensions.values() for ext in exts
385
+ ]
386
+
387
+ files = []
388
+ for ext in extensions_to_search:
389
+ files.extend(search_path.rglob(f"*{ext}"))
390
+
391
+ for file_path in files:
392
+ if file_path.is_file():
393
+ references = self._find_references_in_file(file_path, symbol)
394
+ results.extend(references)
395
+
396
+ if not results:
397
+ return f"No references found for '{symbol}'"
398
+
399
+ output = [f"Found {len(results)} references to '{symbol}':"]
400
+ for result in results:
401
+ output.append(f" {result}")
402
+
403
+ return "\n".join(output)
404
+
405
+ except Exception as e:
406
+ return f"Error finding references: {str(e)}"
407
+
408
+ def _find_references_in_file(self, file_path: Path, symbol: str) -> List[str]:
409
+ """Find references in a single file."""
410
+ results = []
411
+
412
+ try:
413
+ with open(file_path, "r", encoding="utf-8") as f:
414
+ content = f.read()
415
+
416
+ # Simple regex-based reference finding
417
+ # This is a basic implementation - a more sophisticated approach
418
+ # would use tree-sitter queries
419
+ lines = content.split("\n")
420
+
421
+ for i, line in enumerate(lines, 1):
422
+ # Skip comments and strings (basic filtering)
423
+ stripped_line = line.strip()
424
+ if (
425
+ stripped_line.startswith("#")
426
+ or stripped_line.startswith("//")
427
+ or stripped_line.startswith("/*")
428
+ or stripped_line.startswith("*")
429
+ ):
430
+ continue
431
+
432
+ # Find symbol usage (basic pattern matching)
433
+ pattern = r"\b" + re.escape(symbol) + r"\b"
434
+ matches = re.finditer(pattern, line)
435
+
436
+ for match in matches:
437
+ # Get context around the match
438
+ start = max(0, match.start() - 20)
439
+ end = min(len(line), match.end() + 20)
440
+ context = line[start:end].strip()
441
+
442
+ results.append(f"{file_path}:{i}: {context}")
443
+
444
+ except Exception:
445
+ pass # Skip files that can't be read
446
+
447
+ return results
448
+
449
+
450
+ class CodeStructureTool(ToolBase):
451
+ """Tool to analyze code structure and complexity."""
452
+
453
+ tool_name = "code_structure"
454
+ permissions = ToolPermissions(read=True, write=False, execute=True)
455
+
456
+ def run(self, path: str = ".", language: str = None) -> str:
457
+ """
458
+ Analyze code structure and provide overview.
459
+
460
+ Args:
461
+ path: Directory or file to analyze
462
+ language: Specific language to analyze
463
+
464
+ Returns:
465
+ Code structure analysis as string
466
+ """
467
+ try:
468
+ search_path = Path(path)
469
+
470
+ if search_path.is_file():
471
+ files = [search_path]
472
+ else:
473
+ # Find all source files
474
+ extensions = {
475
+ "python": [".py"],
476
+ "javascript": [".js", ".jsx"],
477
+ "typescript": [".ts", ".tsx"],
478
+ "java": [".java"],
479
+ "c": [".c", ".h"],
480
+ "cpp": [".cpp", ".cc", ".cxx", ".hpp"],
481
+ "rust": [".rs"],
482
+ "go": [".go"],
483
+ "ruby": [".rb"],
484
+ "php": [".php"],
485
+ "swift": [".swift"],
486
+ "kotlin": [".kt"],
487
+ "scala": [".scala"],
488
+ "csharp": [".cs"],
489
+ }
490
+
491
+ if language:
492
+ extensions_to_search = extensions.get(language.lower(), [])
493
+ else:
494
+ extensions_to_search = [
495
+ ext for exts in extensions.values() for ext in exts
496
+ ]
497
+
498
+ files = []
499
+ for ext in extensions_to_search:
500
+ files.extend(search_path.rglob(f"*{ext}"))
501
+
502
+ analysis = {
503
+ "total_files": len(files),
504
+ "languages": {},
505
+ "total_lines": 0,
506
+ "total_size": 0,
507
+ "file_details": [],
508
+ }
509
+
510
+ for file_path in files:
511
+ if file_path.is_file():
512
+ try:
513
+ with open(file_path, "r", encoding="utf-8") as f:
514
+ content = f.read()
515
+
516
+ lines = len(content.split("\n"))
517
+ size = file_path.stat().st_size
518
+
519
+ ext = file_path.suffix.lower()
520
+ language_name = {
521
+ ".py": "Python",
522
+ ".js": "JavaScript",
523
+ ".jsx": "JavaScript",
524
+ ".ts": "TypeScript",
525
+ ".tsx": "TypeScript",
526
+ ".java": "Java",
527
+ ".c": "C",
528
+ ".h": "C",
529
+ ".cpp": "C++",
530
+ ".cc": "C++",
531
+ ".cxx": "C++",
532
+ ".hpp": "C++",
533
+ ".rs": "Rust",
534
+ ".go": "Go",
535
+ ".rb": "Ruby",
536
+ ".php": "PHP",
537
+ ".swift": "Swift",
538
+ ".kt": "Kotlin",
539
+ ".scala": "Scala",
540
+ ".cs": "C#",
541
+ }.get(ext, "Unknown")
542
+
543
+ analysis["languages"][language_name] = (
544
+ analysis["languages"].get(language_name, 0) + 1
545
+ )
546
+ analysis["total_lines"] += lines
547
+ analysis["total_size"] += size
548
+
549
+ analysis["file_details"].append(
550
+ {
551
+ "path": str(file_path),
552
+ "language": language_name,
553
+ "lines": lines,
554
+ "size": size,
555
+ }
556
+ )
557
+
558
+ except Exception:
559
+ continue
560
+
561
+ # Generate report
562
+ output = [
563
+ "Code Structure Analysis",
564
+ "=" * 30,
565
+ f"Total files: {analysis['total_files']}",
566
+ f"Total lines: {analysis['total_lines']:,}",
567
+ f"Total size: {analysis['total_size']:,} bytes",
568
+ "",
569
+ "Languages:",
570
+ ]
571
+
572
+ for lang, count in sorted(
573
+ analysis["languages"].items(), key=lambda x: x[1], reverse=True
574
+ ):
575
+ output.append(f" {lang}: {count} files")
576
+
577
+ output.extend(["", "Largest files:"])
578
+
579
+ # Sort by size and show top 10
580
+ sorted_files = sorted(
581
+ analysis["file_details"], key=lambda x: x["size"], reverse=True
582
+ )[:10]
583
+ for file_info in sorted_files:
584
+ output.append(
585
+ f" {file_info['path']}: {file_info['lines']} lines, {file_info['size']} bytes"
586
+ )
587
+
588
+ return "\n".join(output)
589
+
590
+ except Exception as e:
591
+ return f"Error analyzing code structure: {str(e)}"
592
+
593
+
594
+ class CodeNavigatorPlugin(Plugin):
595
+ """Plugin providing code navigation and analysis tools."""
596
+
597
+ def get_metadata(self) -> PluginMetadata:
598
+ return PluginMetadata(
599
+ name="code_navigator",
600
+ version="1.0.0",
601
+ description="Code navigation and static analysis tools",
602
+ author="Janito Coder Team",
603
+ license="MIT",
604
+ homepage="https://github.com/ikignosis/janito-coder",
605
+ )
606
+
607
+ def get_tools(self):
608
+ return [FindDefinitionTool, FindReferencesTool, CodeStructureTool]
609
+
610
+ def initialize(self):
611
+ print("Code Navigator plugin initialized!")
612
+
613
+ def cleanup(self):
614
+ print("Code Navigator plugin cleaned up!")
615
+
616
+
617
+ # This makes the plugin discoverable
618
+ PLUGIN_CLASS = CodeNavigatorPlugin