ripperdoc 0.1.0__py3-none-any.whl → 0.2.2__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 (57) hide show
  1. ripperdoc/__init__.py +1 -1
  2. ripperdoc/cli/cli.py +75 -15
  3. ripperdoc/cli/commands/__init__.py +4 -0
  4. ripperdoc/cli/commands/agents_cmd.py +23 -1
  5. ripperdoc/cli/commands/context_cmd.py +13 -3
  6. ripperdoc/cli/commands/cost_cmd.py +1 -1
  7. ripperdoc/cli/commands/doctor_cmd.py +200 -0
  8. ripperdoc/cli/commands/memory_cmd.py +209 -0
  9. ripperdoc/cli/commands/models_cmd.py +25 -0
  10. ripperdoc/cli/commands/resume_cmd.py +3 -3
  11. ripperdoc/cli/commands/status_cmd.py +5 -5
  12. ripperdoc/cli/commands/tasks_cmd.py +32 -5
  13. ripperdoc/cli/ui/context_display.py +4 -3
  14. ripperdoc/cli/ui/rich_ui.py +205 -43
  15. ripperdoc/cli/ui/spinner.py +3 -4
  16. ripperdoc/core/agents.py +10 -6
  17. ripperdoc/core/config.py +48 -3
  18. ripperdoc/core/default_tools.py +26 -6
  19. ripperdoc/core/permissions.py +19 -0
  20. ripperdoc/core/query.py +238 -302
  21. ripperdoc/core/query_utils.py +537 -0
  22. ripperdoc/core/system_prompt.py +2 -1
  23. ripperdoc/core/tool.py +14 -1
  24. ripperdoc/sdk/client.py +1 -1
  25. ripperdoc/tools/background_shell.py +9 -3
  26. ripperdoc/tools/bash_tool.py +19 -4
  27. ripperdoc/tools/file_edit_tool.py +9 -2
  28. ripperdoc/tools/file_read_tool.py +9 -2
  29. ripperdoc/tools/file_write_tool.py +15 -2
  30. ripperdoc/tools/glob_tool.py +57 -17
  31. ripperdoc/tools/grep_tool.py +9 -2
  32. ripperdoc/tools/ls_tool.py +244 -75
  33. ripperdoc/tools/mcp_tools.py +47 -19
  34. ripperdoc/tools/multi_edit_tool.py +13 -2
  35. ripperdoc/tools/notebook_edit_tool.py +9 -6
  36. ripperdoc/tools/task_tool.py +20 -5
  37. ripperdoc/tools/todo_tool.py +163 -29
  38. ripperdoc/tools/tool_search_tool.py +15 -4
  39. ripperdoc/utils/git_utils.py +276 -0
  40. ripperdoc/utils/json_utils.py +28 -0
  41. ripperdoc/utils/log.py +130 -29
  42. ripperdoc/utils/mcp.py +83 -10
  43. ripperdoc/utils/memory.py +14 -1
  44. ripperdoc/utils/message_compaction.py +51 -14
  45. ripperdoc/utils/messages.py +63 -4
  46. ripperdoc/utils/output_utils.py +36 -9
  47. ripperdoc/utils/permissions/path_validation_utils.py +6 -0
  48. ripperdoc/utils/safe_get_cwd.py +4 -0
  49. ripperdoc/utils/session_history.py +27 -9
  50. ripperdoc/utils/todo.py +2 -2
  51. {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.2.dist-info}/METADATA +4 -2
  52. ripperdoc-0.2.2.dist-info/RECORD +86 -0
  53. ripperdoc-0.1.0.dist-info/RECORD +0 -81
  54. {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.2.dist-info}/WHEEL +0 -0
  55. {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.2.dist-info}/entry_points.txt +0 -0
  56. {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.2.dist-info}/licenses/LICENSE +0 -0
  57. {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.2.dist-info}/top_level.txt +0 -0
@@ -49,6 +49,9 @@ from ripperdoc.utils.permissions.tool_permission_utils import (
49
49
  from ripperdoc.utils.permissions import PermissionDecision
50
50
  from ripperdoc.utils.sandbox_utils import create_sandbox_wrapper, is_sandbox_available
51
51
  from ripperdoc.utils.safe_get_cwd import get_original_cwd, safe_get_cwd
52
+ from ripperdoc.utils.log import get_logger
53
+
54
+ logger = get_logger()
52
55
 
53
56
 
54
57
  DEFAULT_TIMEOUT_MS = get_bash_default_timeout_ms()
@@ -134,11 +137,11 @@ build projects, run tests, and interact with the file system."""
134
137
  return [
135
138
  ToolUseExample(
136
139
  description="Run a read-only listing in sandboxed mode",
137
- input={"command": "ls -la", "sandbox": True, "timeout": 10000},
140
+ example={"command": "ls -la", "sandbox": True, "timeout": 10000},
138
141
  ),
139
142
  ToolUseExample(
140
143
  description="Start a long task in the background with a timeout",
141
- input={
144
+ example={
142
145
  "command": "npm test",
143
146
  "run_in_background": True,
144
147
  "timeout": 600000,
@@ -395,10 +398,10 @@ build projects, run tests, and interact with the file system."""
395
398
  if output.duration_ms:
396
399
  timing = f" ({format_duration(output.duration_ms)}"
397
400
  if output.timeout_ms:
398
- timing += f" / timeout {output.timeout_ms/1000:.0f}s"
401
+ timing += f" / timeout {output.timeout_ms / 1000:.0f}s"
399
402
  timing += ")"
400
403
  elif output.timeout_ms:
401
- timing = f" (timeout {output.timeout_ms/1000:.0f}s)"
404
+ timing = f" (timeout {output.timeout_ms / 1000:.0f}s)"
402
405
 
403
406
  result_parts.append(f"{exit_code_text}{timing}")
404
407
 
@@ -516,6 +519,10 @@ build projects, run tests, and interact with the file system."""
516
519
  final_command = wrapper.final_command
517
520
  sandbox_cleanup = wrapper.cleanup
518
521
  except Exception as exc:
522
+ logger.exception(
523
+ "[bash_tool] Failed to enable sandbox",
524
+ extra={"command": effective_command, "error": str(exc)},
525
+ )
519
526
  error_output = BashToolOutput(
520
527
  stdout="",
521
528
  stderr=f"Failed to enable sandbox: {exc}",
@@ -561,6 +568,10 @@ build projects, run tests, and interact with the file system."""
561
568
  try:
562
569
  from ripperdoc.tools.background_shell import start_background_command
563
570
  except Exception as e: # pragma: no cover - defensive import
571
+ logger.exception(
572
+ "[bash_tool] Failed to import background shell runner",
573
+ extra={"command": effective_command},
574
+ )
564
575
  error_output = BashToolOutput(
565
576
  stdout="",
566
577
  stderr=f"Failed to start background task: {str(e)}",
@@ -767,6 +778,10 @@ build projects, run tests, and interact with the file system."""
767
778
  )
768
779
 
769
780
  except Exception as e:
781
+ logger.exception(
782
+ "[bash_tool] Error executing command",
783
+ extra={"command": effective_command, "error": str(e)},
784
+ )
770
785
  error_output = BashToolOutput(
771
786
  stdout="",
772
787
  stderr=f"Error executing command: {str(e)}",
@@ -15,6 +15,9 @@ from ripperdoc.core.tool import (
15
15
  ToolUseExample,
16
16
  ValidationResult,
17
17
  )
18
+ from ripperdoc.utils.log import get_logger
19
+
20
+ logger = get_logger()
18
21
 
19
22
 
20
23
  class FileEditToolInput(BaseModel):
@@ -60,7 +63,7 @@ match exactly (including whitespace and indentation)."""
60
63
  return [
61
64
  ToolUseExample(
62
65
  description="Rename a function definition once",
63
- input={
66
+ example={
64
67
  "file_path": "/repo/src/app.py",
65
68
  "old_string": "def old_name(",
66
69
  "new_string": "def new_name(",
@@ -69,7 +72,7 @@ match exactly (including whitespace and indentation)."""
69
72
  ),
70
73
  ToolUseExample(
71
74
  description="Replace every occurrence of a constant across a file",
72
- input={
75
+ example={
73
76
  "file_path": "/repo/src/config.ts",
74
77
  "old_string": 'API_BASE = "http://localhost"',
75
78
  "new_string": 'API_BASE = "https://api.example.com"',
@@ -268,6 +271,10 @@ match exactly (including whitespace and indentation)."""
268
271
  )
269
272
 
270
273
  except Exception as e:
274
+ logger.exception(
275
+ "[file_edit_tool] Error editing file",
276
+ extra={"file_path": input_data.file_path, "error": str(e)},
277
+ )
271
278
  error_output = FileEditToolOutput(
272
279
  file_path=input_data.file_path,
273
280
  replacements_made=0,
@@ -15,6 +15,9 @@ from ripperdoc.core.tool import (
15
15
  ToolUseExample,
16
16
  ValidationResult,
17
17
  )
18
+ from ripperdoc.utils.log import get_logger
19
+
20
+ logger = get_logger()
18
21
 
19
22
 
20
23
  class FileReadToolInput(BaseModel):
@@ -56,11 +59,11 @@ and limit to read only a portion of the file."""
56
59
  return [
57
60
  ToolUseExample(
58
61
  description="Read the top of a file to understand structure",
59
- input={"file_path": "/repo/src/main.py", "limit": 50},
62
+ example={"file_path": "/repo/src/main.py", "limit": 50},
60
63
  ),
61
64
  ToolUseExample(
62
65
  description="Inspect a slice of a large log without loading everything",
63
- input={"file_path": "/repo/logs/server.log", "offset": 200, "limit": 40},
66
+ example={"file_path": "/repo/logs/server.log", "offset": 200, "limit": 40},
64
67
  ),
65
68
  ]
66
69
 
@@ -153,6 +156,10 @@ and limit to read only a portion of the file."""
153
156
  )
154
157
 
155
158
  except Exception as e:
159
+ logger.exception(
160
+ "[file_read_tool] Error reading file",
161
+ extra={"file_path": input_data.file_path, "error": str(e)},
162
+ )
156
163
  # Create an error output
157
164
  error_output = FileReadToolOutput(
158
165
  content=f"Error reading file: {str(e)}",
@@ -16,6 +16,9 @@ from ripperdoc.core.tool import (
16
16
  ToolUseExample,
17
17
  ValidationResult,
18
18
  )
19
+ from ripperdoc.utils.log import get_logger
20
+
21
+ logger = get_logger()
19
22
 
20
23
 
21
24
  class FileWriteToolInput(BaseModel):
@@ -53,11 +56,17 @@ the file if it already exists."""
53
56
  return [
54
57
  ToolUseExample(
55
58
  description="Create a JSON fixture file",
56
- input={"file_path": "/repo/tests/fixtures/sample.json", "content": '{\n "items": []\n}\n'},
59
+ example={
60
+ "file_path": "/repo/tests/fixtures/sample.json",
61
+ "content": '{\n "items": []\n}\n',
62
+ },
57
63
  ),
58
64
  ToolUseExample(
59
65
  description="Write a short markdown note",
60
- input={"file_path": "/repo/docs/USAGE.md", "content": "# Usage\n\nRun `make test`.\n"},
66
+ example={
67
+ "file_path": "/repo/docs/USAGE.md",
68
+ "content": "# Usage\n\nRun `make test`.\n",
69
+ },
61
70
  ),
62
71
  ]
63
72
 
@@ -128,6 +137,10 @@ NEVER write new files unless explicitly required by the user."""
128
137
  )
129
138
 
130
139
  except Exception as e:
140
+ logger.exception(
141
+ "[file_write_tool] Error writing file",
142
+ extra={"file_path": input_data.file_path, "error": str(e)},
143
+ )
131
144
  error_output = FileWriteToolOutput(
132
145
  file_path=input_data.file_path,
133
146
  bytes_written=0,
@@ -15,17 +15,22 @@ from ripperdoc.core.tool import (
15
15
  ToolUseExample,
16
16
  ValidationResult,
17
17
  )
18
+ from ripperdoc.utils.log import get_logger
19
+
20
+ logger = get_logger()
18
21
 
19
22
 
20
23
  GLOB_USAGE = (
21
- "- Fast file pattern matching tool for any codebase size\n"
24
+ "- Fast file pattern matching tool that works with any codebase size\n"
22
25
  '- Supports glob patterns like "**/*.js" or "src/**/*.ts"\n'
23
- "- Returns matching file paths sorted by modification time (newest first)\n"
24
- "- Use this when you need to find files by name patterns\n"
25
- "- For open-ended searches that need multiple rounds of globbing and grepping, run the searches iteratively with these tools\n"
26
- "- You can call multiple tools in a single response; speculatively batch useful searches together"
26
+ "- Returns matching file paths sorted by modification time\n"
27
+ "- Use this tool when you need to find files by name patterns\n"
28
+ "- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead\n"
29
+ "- You have the capability to call multiple tools in a single response. It is always better to speculatively perform multiple searches as a batch that are potentially useful.\n"
27
30
  )
28
31
 
32
+ RESULT_LIMIT = 100
33
+
29
34
 
30
35
  class GlobToolInput(BaseModel):
31
36
  """Input schema for GlobTool."""
@@ -42,6 +47,7 @@ class GlobToolOutput(BaseModel):
42
47
  matches: List[str]
43
48
  pattern: str
44
49
  count: int
50
+ truncated: bool = False
45
51
 
46
52
 
47
53
  class GlobTool(Tool[GlobToolInput, GlobToolOutput]):
@@ -62,11 +68,11 @@ class GlobTool(Tool[GlobToolInput, GlobToolOutput]):
62
68
  return [
63
69
  ToolUseExample(
64
70
  description="Find Python sources inside src",
65
- input={"pattern": "src/**/*.py"},
71
+ example={"pattern": "src/**/*.py"},
66
72
  ),
67
73
  ToolUseExample(
68
74
  description="Locate snapshot files within tests",
69
- input={"pattern": "tests/**/__snapshots__/*.snap", "path": "/repo"},
75
+ example={"pattern": "tests/**/__snapshots__/*.snap", "path": "/repo"},
70
76
  ),
71
77
  ]
72
78
 
@@ -92,14 +98,34 @@ class GlobTool(Tool[GlobToolInput, GlobToolOutput]):
92
98
  if not output.matches:
93
99
  return f"No files found matching pattern: {output.pattern}"
94
100
 
95
- result = f"Found {output.count} file(s) matching '{output.pattern}':\n\n"
96
- result += "\n".join(output.matches)
97
-
98
- return result
101
+ lines = list(output.matches)
102
+ if output.truncated:
103
+ lines.append("(Results are truncated. Consider using a more specific path or pattern.)")
104
+ return "\n".join(lines)
99
105
 
100
106
  def render_tool_use_message(self, input_data: GlobToolInput, verbose: bool = False) -> str:
101
107
  """Format the tool use for display."""
102
- return f"Glob: {input_data.pattern}"
108
+ if not input_data.pattern:
109
+ return "Glob"
110
+
111
+ base_path = Path.cwd()
112
+ rendered_path = ""
113
+ if input_data.path:
114
+ candidate_path = Path(input_data.path)
115
+ absolute_path = candidate_path if candidate_path.is_absolute() else (base_path / candidate_path).resolve()
116
+
117
+ try:
118
+ relative_path = absolute_path.relative_to(base_path)
119
+ except ValueError:
120
+ relative_path = None
121
+
122
+ if verbose or not relative_path or str(relative_path) == ".":
123
+ rendered_path = str(absolute_path)
124
+ else:
125
+ rendered_path = str(relative_path)
126
+
127
+ path_fragment = f', path: "{rendered_path}"' if rendered_path else ""
128
+ return f'pattern: "{input_data.pattern}"{path_fragment}'
103
129
 
104
130
  async def call(
105
131
  self, input_data: GlobToolInput, context: ToolUseContext
@@ -108,9 +134,8 @@ class GlobTool(Tool[GlobToolInput, GlobToolOutput]):
108
134
 
109
135
  try:
110
136
  search_path = Path(input_data.path) if input_data.path else Path.cwd()
111
-
112
- # Use glob to find matches, sorted by modification time (newest first)
113
- paths = list(search_path.glob(input_data.pattern))
137
+ if not search_path.is_absolute():
138
+ search_path = (Path.cwd() / search_path).resolve()
114
139
 
115
140
  def _mtime(path: Path) -> float:
116
141
  try:
@@ -118,15 +143,30 @@ class GlobTool(Tool[GlobToolInput, GlobToolOutput]):
118
143
  except OSError:
119
144
  return float("-inf")
120
145
 
121
- matches = [str(p) for p in sorted(paths, key=_mtime, reverse=True)]
146
+ # Find matching files, sorted by modification time
147
+ paths = sorted(
148
+ (p for p in search_path.glob(input_data.pattern) if p.is_file()),
149
+ key=_mtime,
150
+ )
151
+
152
+ truncated = len(paths) > RESULT_LIMIT
153
+ paths = paths[:RESULT_LIMIT]
122
154
 
123
- output = GlobToolOutput(matches=matches, pattern=input_data.pattern, count=len(matches))
155
+ matches = [str(p) for p in paths]
156
+
157
+ output = GlobToolOutput(
158
+ matches=matches, pattern=input_data.pattern, count=len(matches), truncated=truncated
159
+ )
124
160
 
125
161
  yield ToolResult(
126
162
  data=output, result_for_assistant=self.render_result_for_assistant(output)
127
163
  )
128
164
 
129
165
  except Exception as e:
166
+ logger.exception(
167
+ "[glob_tool] Error executing glob",
168
+ extra={"pattern": input_data.pattern, "path": input_data.path},
169
+ )
130
170
  error_output = GlobToolOutput(matches=[], pattern=input_data.pattern, count=0)
131
171
 
132
172
  yield ToolResult(
@@ -15,6 +15,9 @@ from ripperdoc.core.tool import (
15
15
  ToolUseExample,
16
16
  ValidationResult,
17
17
  )
18
+ from ripperdoc.utils.log import get_logger
19
+
20
+ logger = get_logger()
18
21
 
19
22
 
20
23
  GREP_USAGE = (
@@ -80,11 +83,11 @@ class GrepTool(Tool[GrepToolInput, GrepToolOutput]):
80
83
  return [
81
84
  ToolUseExample(
82
85
  description="Find TODO comments in TypeScript files",
83
- input={"pattern": "TODO", "glob": "**/*.ts", "output_mode": "content"},
86
+ example={"pattern": "TODO", "glob": "**/*.ts", "output_mode": "content"},
84
87
  ),
85
88
  ToolUseExample(
86
89
  description="List files referencing a function name",
87
- input={
90
+ example={
88
91
  "pattern": "fetchUserData",
89
92
  "output_mode": "files_with_matches",
90
93
  "path": "/repo/src",
@@ -223,6 +226,10 @@ class GrepTool(Tool[GrepToolInput, GrepToolOutput]):
223
226
  )
224
227
 
225
228
  except Exception as e:
229
+ logger.exception(
230
+ "[grep_tool] Error executing grep",
231
+ extra={"pattern": input_data.pattern, "path": input_data.path},
232
+ )
226
233
  error_output = GrepToolOutput(
227
234
  matches=[], pattern=input_data.pattern, total_files=0, total_matches=0
228
235
  )