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.
- ripperdoc/__init__.py +1 -1
- ripperdoc/cli/cli.py +75 -15
- ripperdoc/cli/commands/__init__.py +4 -0
- ripperdoc/cli/commands/agents_cmd.py +23 -1
- ripperdoc/cli/commands/context_cmd.py +13 -3
- ripperdoc/cli/commands/cost_cmd.py +1 -1
- ripperdoc/cli/commands/doctor_cmd.py +200 -0
- ripperdoc/cli/commands/memory_cmd.py +209 -0
- ripperdoc/cli/commands/models_cmd.py +25 -0
- ripperdoc/cli/commands/resume_cmd.py +3 -3
- ripperdoc/cli/commands/status_cmd.py +5 -5
- ripperdoc/cli/commands/tasks_cmd.py +32 -5
- ripperdoc/cli/ui/context_display.py +4 -3
- ripperdoc/cli/ui/rich_ui.py +205 -43
- ripperdoc/cli/ui/spinner.py +3 -4
- ripperdoc/core/agents.py +10 -6
- ripperdoc/core/config.py +48 -3
- ripperdoc/core/default_tools.py +26 -6
- ripperdoc/core/permissions.py +19 -0
- ripperdoc/core/query.py +238 -302
- ripperdoc/core/query_utils.py +537 -0
- ripperdoc/core/system_prompt.py +2 -1
- ripperdoc/core/tool.py +14 -1
- ripperdoc/sdk/client.py +1 -1
- ripperdoc/tools/background_shell.py +9 -3
- ripperdoc/tools/bash_tool.py +19 -4
- ripperdoc/tools/file_edit_tool.py +9 -2
- ripperdoc/tools/file_read_tool.py +9 -2
- ripperdoc/tools/file_write_tool.py +15 -2
- ripperdoc/tools/glob_tool.py +57 -17
- ripperdoc/tools/grep_tool.py +9 -2
- ripperdoc/tools/ls_tool.py +244 -75
- ripperdoc/tools/mcp_tools.py +47 -19
- ripperdoc/tools/multi_edit_tool.py +13 -2
- ripperdoc/tools/notebook_edit_tool.py +9 -6
- ripperdoc/tools/task_tool.py +20 -5
- ripperdoc/tools/todo_tool.py +163 -29
- ripperdoc/tools/tool_search_tool.py +15 -4
- ripperdoc/utils/git_utils.py +276 -0
- ripperdoc/utils/json_utils.py +28 -0
- ripperdoc/utils/log.py +130 -29
- ripperdoc/utils/mcp.py +83 -10
- ripperdoc/utils/memory.py +14 -1
- ripperdoc/utils/message_compaction.py +51 -14
- ripperdoc/utils/messages.py +63 -4
- ripperdoc/utils/output_utils.py +36 -9
- ripperdoc/utils/permissions/path_validation_utils.py +6 -0
- ripperdoc/utils/safe_get_cwd.py +4 -0
- ripperdoc/utils/session_history.py +27 -9
- ripperdoc/utils/todo.py +2 -2
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.2.dist-info}/METADATA +4 -2
- ripperdoc-0.2.2.dist-info/RECORD +86 -0
- ripperdoc-0.1.0.dist-info/RECORD +0 -81
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.2.dist-info}/WHEEL +0 -0
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.2.dist-info}/entry_points.txt +0 -0
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.2.dist-info}/top_level.txt +0 -0
ripperdoc/tools/bash_tool.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
ripperdoc/tools/glob_tool.py
CHANGED
|
@@ -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
|
|
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
|
|
24
|
-
"- Use this when you need to find files by name patterns\n"
|
|
25
|
-
"-
|
|
26
|
-
"- You
|
|
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
|
-
|
|
71
|
+
example={"pattern": "src/**/*.py"},
|
|
66
72
|
),
|
|
67
73
|
ToolUseExample(
|
|
68
74
|
description="Locate snapshot files within tests",
|
|
69
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
ripperdoc/tools/grep_tool.py
CHANGED
|
@@ -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
|
-
|
|
86
|
+
example={"pattern": "TODO", "glob": "**/*.ts", "output_mode": "content"},
|
|
84
87
|
),
|
|
85
88
|
ToolUseExample(
|
|
86
89
|
description="List files referencing a function name",
|
|
87
|
-
|
|
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
|
)
|