ripperdoc 0.2.7__py3-none-any.whl → 0.2.9__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 (87) hide show
  1. ripperdoc/__init__.py +1 -1
  2. ripperdoc/cli/cli.py +33 -115
  3. ripperdoc/cli/commands/__init__.py +70 -6
  4. ripperdoc/cli/commands/agents_cmd.py +6 -3
  5. ripperdoc/cli/commands/clear_cmd.py +1 -4
  6. ripperdoc/cli/commands/config_cmd.py +1 -1
  7. ripperdoc/cli/commands/context_cmd.py +3 -2
  8. ripperdoc/cli/commands/doctor_cmd.py +18 -4
  9. ripperdoc/cli/commands/help_cmd.py +11 -1
  10. ripperdoc/cli/commands/hooks_cmd.py +610 -0
  11. ripperdoc/cli/commands/models_cmd.py +26 -9
  12. ripperdoc/cli/commands/permissions_cmd.py +57 -37
  13. ripperdoc/cli/commands/resume_cmd.py +6 -4
  14. ripperdoc/cli/commands/status_cmd.py +4 -4
  15. ripperdoc/cli/commands/tasks_cmd.py +8 -4
  16. ripperdoc/cli/ui/file_mention_completer.py +64 -8
  17. ripperdoc/cli/ui/interrupt_handler.py +3 -4
  18. ripperdoc/cli/ui/message_display.py +5 -3
  19. ripperdoc/cli/ui/panels.py +13 -10
  20. ripperdoc/cli/ui/provider_options.py +247 -0
  21. ripperdoc/cli/ui/rich_ui.py +196 -77
  22. ripperdoc/cli/ui/spinner.py +25 -1
  23. ripperdoc/cli/ui/tool_renderers.py +8 -2
  24. ripperdoc/cli/ui/wizard.py +215 -0
  25. ripperdoc/core/agents.py +9 -3
  26. ripperdoc/core/config.py +49 -12
  27. ripperdoc/core/custom_commands.py +412 -0
  28. ripperdoc/core/default_tools.py +11 -2
  29. ripperdoc/core/hooks/__init__.py +99 -0
  30. ripperdoc/core/hooks/config.py +301 -0
  31. ripperdoc/core/hooks/events.py +535 -0
  32. ripperdoc/core/hooks/executor.py +496 -0
  33. ripperdoc/core/hooks/integration.py +344 -0
  34. ripperdoc/core/hooks/manager.py +745 -0
  35. ripperdoc/core/permissions.py +40 -8
  36. ripperdoc/core/providers/anthropic.py +548 -68
  37. ripperdoc/core/providers/gemini.py +70 -5
  38. ripperdoc/core/providers/openai.py +60 -5
  39. ripperdoc/core/query.py +140 -39
  40. ripperdoc/core/query_utils.py +2 -0
  41. ripperdoc/core/skills.py +9 -3
  42. ripperdoc/core/system_prompt.py +4 -2
  43. ripperdoc/core/tool.py +9 -5
  44. ripperdoc/sdk/client.py +2 -2
  45. ripperdoc/tools/ask_user_question_tool.py +5 -3
  46. ripperdoc/tools/background_shell.py +2 -1
  47. ripperdoc/tools/bash_output_tool.py +1 -1
  48. ripperdoc/tools/bash_tool.py +30 -20
  49. ripperdoc/tools/dynamic_mcp_tool.py +29 -8
  50. ripperdoc/tools/enter_plan_mode_tool.py +1 -1
  51. ripperdoc/tools/exit_plan_mode_tool.py +1 -1
  52. ripperdoc/tools/file_edit_tool.py +8 -4
  53. ripperdoc/tools/file_read_tool.py +9 -5
  54. ripperdoc/tools/file_write_tool.py +9 -5
  55. ripperdoc/tools/glob_tool.py +3 -2
  56. ripperdoc/tools/grep_tool.py +3 -2
  57. ripperdoc/tools/kill_bash_tool.py +1 -1
  58. ripperdoc/tools/ls_tool.py +1 -1
  59. ripperdoc/tools/mcp_tools.py +13 -10
  60. ripperdoc/tools/multi_edit_tool.py +8 -7
  61. ripperdoc/tools/notebook_edit_tool.py +7 -4
  62. ripperdoc/tools/skill_tool.py +1 -1
  63. ripperdoc/tools/task_tool.py +5 -4
  64. ripperdoc/tools/todo_tool.py +2 -2
  65. ripperdoc/tools/tool_search_tool.py +3 -2
  66. ripperdoc/utils/conversation_compaction.py +11 -7
  67. ripperdoc/utils/file_watch.py +8 -2
  68. ripperdoc/utils/json_utils.py +2 -1
  69. ripperdoc/utils/mcp.py +11 -3
  70. ripperdoc/utils/memory.py +4 -2
  71. ripperdoc/utils/message_compaction.py +21 -7
  72. ripperdoc/utils/message_formatting.py +11 -7
  73. ripperdoc/utils/messages.py +105 -66
  74. ripperdoc/utils/path_ignore.py +38 -12
  75. ripperdoc/utils/permissions/path_validation_utils.py +2 -1
  76. ripperdoc/utils/permissions/shell_command_validation.py +427 -91
  77. ripperdoc/utils/safe_get_cwd.py +2 -1
  78. ripperdoc/utils/session_history.py +13 -6
  79. ripperdoc/utils/todo.py +2 -1
  80. ripperdoc/utils/token_estimation.py +6 -1
  81. {ripperdoc-0.2.7.dist-info → ripperdoc-0.2.9.dist-info}/METADATA +24 -3
  82. ripperdoc-0.2.9.dist-info/RECORD +123 -0
  83. ripperdoc-0.2.7.dist-info/RECORD +0 -113
  84. {ripperdoc-0.2.7.dist-info → ripperdoc-0.2.9.dist-info}/WHEEL +0 -0
  85. {ripperdoc-0.2.7.dist-info → ripperdoc-0.2.9.dist-info}/entry_points.txt +0 -0
  86. {ripperdoc-0.2.7.dist-info → ripperdoc-0.2.9.dist-info}/licenses/LICENSE +0 -0
  87. {ripperdoc-0.2.7.dist-info → ripperdoc-0.2.9.dist-info}/top_level.txt +0 -0
ripperdoc/core/skills.py CHANGED
@@ -84,10 +84,15 @@ def _split_frontmatter(raw_text: str) -> Tuple[Dict[str, Any], str]:
84
84
  body = "\n".join(lines[idx + 1 :])
85
85
  try:
86
86
  frontmatter = yaml.safe_load(frontmatter_text) or {}
87
- except (yaml.YAMLError, ValueError, TypeError) as exc: # pragma: no cover - defensive
87
+ except (
88
+ yaml.YAMLError,
89
+ ValueError,
90
+ TypeError,
91
+ ) as exc: # pragma: no cover - defensive
88
92
  logger.warning(
89
93
  "[skills] Invalid frontmatter in SKILL.md: %s: %s",
90
- type(exc).__name__, exc,
94
+ type(exc).__name__,
95
+ exc,
91
96
  )
92
97
  return {"__error__": f"Invalid frontmatter: {exc}"}, body
93
98
  return frontmatter, body
@@ -118,7 +123,8 @@ def _load_skill_file(
118
123
  except (OSError, IOError, UnicodeDecodeError) as exc:
119
124
  logger.warning(
120
125
  "[skills] Failed to read skill file: %s: %s",
121
- type(exc).__name__, exc,
126
+ type(exc).__name__,
127
+ exc,
122
128
  extra={"path": str(path)},
123
129
  )
124
130
  return None, SkillLoadError(path=path, reason=f"Failed to read file: {exc}")
@@ -49,7 +49,8 @@ def _detect_git_repo(cwd: Path) -> bool:
49
49
  except (OSError, subprocess.SubprocessError) as exc:
50
50
  logger.warning(
51
51
  "[system_prompt] Failed to detect git repository: %s: %s",
52
- type(exc).__name__, exc,
52
+ type(exc).__name__,
53
+ exc,
53
54
  extra={"cwd": str(cwd)},
54
55
  )
55
56
  return False
@@ -393,7 +394,8 @@ def build_system_prompt(
393
394
  except (OSError, ValueError, RuntimeError) as exc:
394
395
  logger.warning(
395
396
  "Failed to load agent definitions: %s: %s",
396
- type(exc).__name__, exc,
397
+ type(exc).__name__,
398
+ exc,
397
399
  )
398
400
  agent_section = (
399
401
  "# Subagents\nTask tool available, but agent definitions could not be loaded."
ripperdoc/core/tool.py CHANGED
@@ -37,13 +37,15 @@ class ToolUseContext(BaseModel):
37
37
 
38
38
  message_id: Optional[str] = None
39
39
  agent_id: Optional[str] = None
40
- safe_mode: bool = False
40
+ yolo_mode: bool = False
41
41
  verbose: bool = False
42
42
  permission_checker: Optional[Any] = None
43
43
  read_file_timestamps: Dict[str, float] = Field(default_factory=dict)
44
44
  # SkipValidation prevents Pydantic from copying the dict during validation,
45
45
  # ensuring Read and Edit tools share the same cache instance
46
- file_state_cache: Annotated[Dict[str, FileSnapshot], SkipValidation] = Field(default_factory=dict)
46
+ file_state_cache: Annotated[Dict[str, FileSnapshot], SkipValidation] = Field(
47
+ default_factory=dict
48
+ )
47
49
  tool_registry: Optional[Any] = None
48
50
  abort_signal: Optional[Any] = None
49
51
  # UI control callbacks for tools that need user interaction
@@ -110,7 +112,7 @@ class Tool(ABC, Generic[TInput, TOutput]):
110
112
  pass
111
113
 
112
114
  @abstractmethod
113
- async def prompt(self, safe_mode: bool = False) -> str:
115
+ async def prompt(self, yolo_mode: bool = False) -> str:
114
116
  """Get the system prompt for this tool."""
115
117
  pass
116
118
 
@@ -213,7 +215,8 @@ async def build_tool_description(
213
215
  except (TypeError, ValueError, AttributeError, KeyError) as exc:
214
216
  logger.warning(
215
217
  "[tool] Failed to build input example section: %s: %s",
216
- type(exc).__name__, exc,
218
+ type(exc).__name__,
219
+ exc,
217
220
  extra={"tool": getattr(tool, "name", None)},
218
221
  )
219
222
  return description_text
@@ -233,7 +236,8 @@ def tool_input_examples(tool: Tool[Any, Any], limit: int = 5) -> List[Dict[str,
233
236
  except (TypeError, ValueError, AttributeError) as exc:
234
237
  logger.warning(
235
238
  "[tool] Failed to format tool input example: %s: %s",
236
- type(exc).__name__, exc,
239
+ type(exc).__name__,
240
+ exc,
237
241
  extra={"tool": getattr(tool, "name", None)},
238
242
  )
239
243
  continue
ripperdoc/sdk/client.py CHANGED
@@ -83,7 +83,7 @@ class RipperdocOptions:
83
83
  tools: Optional[Sequence[Tool[Any, Any]]] = None
84
84
  allowed_tools: Optional[Sequence[str]] = None
85
85
  disallowed_tools: Optional[Sequence[str]] = None
86
- safe_mode: bool = False
86
+ yolo_mode: bool = False
87
87
  verbose: bool = False
88
88
  model: str = "main"
89
89
  max_thinking_tokens: int = 0
@@ -227,7 +227,7 @@ class RipperdocClient:
227
227
  query_context = QueryContext(
228
228
  tools=self._tools,
229
229
  max_thinking_tokens=self.options.max_thinking_tokens,
230
- safe_mode=self.options.safe_mode,
230
+ yolo_mode=self.options.yolo_mode,
231
231
  model=self.options.model,
232
232
  verbose=self.options.verbose,
233
233
  )
@@ -228,7 +228,8 @@ async def prompt_user_for_answer(
228
228
  except (OSError, RuntimeError, ValueError) as e:
229
229
  logger.warning(
230
230
  "[ask_user_question_tool] Error during prompt: %s: %s",
231
- type(e).__name__, e,
231
+ type(e).__name__,
232
+ e,
232
233
  )
233
234
  return None
234
235
 
@@ -275,7 +276,7 @@ class AskUserQuestionTool(Tool[AskUserQuestionToolInput, AskUserQuestionToolOutp
275
276
  def input_schema(self) -> type[AskUserQuestionToolInput]:
276
277
  return AskUserQuestionToolInput
277
278
 
278
- async def prompt(self, safe_mode: bool = False) -> str: # noqa: ARG002
279
+ async def prompt(self, yolo_mode: bool = False) -> str: # noqa: ARG002
279
280
  return ASK_USER_QUESTION_PROMPT
280
281
 
281
282
  def user_facing_name(self) -> str:
@@ -410,7 +411,8 @@ class AskUserQuestionTool(Tool[AskUserQuestionToolInput, AskUserQuestionToolOutp
410
411
  except (OSError, RuntimeError, ValueError, KeyError) as exc:
411
412
  logger.warning(
412
413
  "[ask_user_question_tool] Error collecting answers: %s: %s",
413
- type(exc).__name__, exc,
414
+ type(exc).__name__,
415
+ exc,
414
416
  )
415
417
  output = AskUserQuestionToolOutput(
416
418
  questions=questions,
@@ -162,7 +162,8 @@ async def _monitor_task(task: BackgroundTask) -> None:
162
162
  except (OSError, RuntimeError, ProcessLookupError) as exc:
163
163
  logger.warning(
164
164
  "Error monitoring background task: %s: %s",
165
- type(exc).__name__, exc,
165
+ type(exc).__name__,
166
+ exc,
166
167
  extra={"task_id": task.id, "command": task.command},
167
168
  )
168
169
  with _tasks_lock:
@@ -40,7 +40,7 @@ class BashOutputTool(Tool[BashOutputInput, BashOutputData]):
40
40
  async def description(self) -> str:
41
41
  return "Read output and status from a background bash command started with BashTool(run_in_background=True)."
42
42
 
43
- async def prompt(self, safe_mode: bool = False) -> str:
43
+ async def prompt(self, yolo_mode: bool = False) -> str:
44
44
  return "Fetch buffered output and status for a background bash task by id."
45
45
 
46
46
  @property
@@ -150,7 +150,7 @@ build projects, run tests, and interact with the file system."""
150
150
  ),
151
151
  ]
152
152
 
153
- async def prompt(self, safe_mode: bool = False) -> str:
153
+ async def prompt(self, yolo_mode: bool = False) -> str:
154
154
  sandbox_available = is_sandbox_available()
155
155
  try:
156
156
  current_shell = find_suitable_shell()
@@ -398,6 +398,11 @@ build projects, run tests, and interact with the file system."""
398
398
 
399
399
  validation = validate_shell_command(input_data.command)
400
400
  if validation.behavior == "ask":
401
+ # In yolo mode, allow shell metacharacters
402
+ if context and hasattr(context, 'yolo_mode') and context.yolo_mode \
403
+ and "shell metacharacters" in validation.message:
404
+ # Allow commands with shell metacharacters in yolo mode
405
+ return ValidationResult(result=True)
401
406
  return ValidationResult(result=False, message=validation.message)
402
407
 
403
408
  return ValidationResult(result=True)
@@ -505,9 +510,7 @@ build projects, run tests, and interact with the file system."""
505
510
 
506
511
  return command, False
507
512
 
508
- def _create_error_output(
509
- self, command: str, stderr: str, sandbox: bool
510
- ) -> BashToolOutput:
513
+ def _create_error_output(self, command: str, stderr: str, sandbox: bool) -> BashToolOutput:
511
514
  """Create a standardized error output."""
512
515
  return BashToolOutput(
513
516
  stdout="",
@@ -531,9 +534,13 @@ build projects, run tests, and interact with the file system."""
531
534
  return command, None, None
532
535
 
533
536
  if not is_sandbox_available():
534
- return None, self._create_error_output(
535
- command, "Sandbox mode requested but not available on this system", True
536
- ), None
537
+ return (
538
+ None,
539
+ self._create_error_output(
540
+ command, "Sandbox mode requested but not available on this system", True
541
+ ),
542
+ None,
543
+ )
537
544
 
538
545
  try:
539
546
  wrapper = create_sandbox_wrapper(command)
@@ -541,12 +548,15 @@ build projects, run tests, and interact with the file system."""
541
548
  except (OSError, RuntimeError, ValueError) as exc:
542
549
  logger.warning(
543
550
  "[bash_tool] Failed to enable sandbox: %s: %s",
544
- type(exc).__name__, exc,
551
+ type(exc).__name__,
552
+ exc,
545
553
  extra={"command": command},
546
554
  )
547
- return None, self._create_error_output(
548
- command, f"Failed to enable sandbox: {exc}", True
549
- ), None
555
+ return (
556
+ None,
557
+ self._create_error_output(command, f"Failed to enable sandbox: {exc}", True),
558
+ None,
559
+ )
550
560
 
551
561
  async def _run_background_command(
552
562
  self,
@@ -570,7 +580,8 @@ build projects, run tests, and interact with the file system."""
570
580
  # pragma: no cover - defensive import
571
581
  logger.warning(
572
582
  "[bash_tool] Failed to import background shell runner: %s: %s",
573
- type(e).__name__, e,
583
+ type(e).__name__,
584
+ e,
574
585
  extra={"command": effective_command},
575
586
  )
576
587
  return self._create_error_output(
@@ -652,7 +663,7 @@ build projects, run tests, and interact with the file system."""
652
663
  # Emit progress updates for newly received output chunks
653
664
  while not queue.empty():
654
665
  label, text = queue.get_nowait()
655
- yield ToolProgress(content=f"{label}: {text}")
666
+ yield ToolProgress(content=f"{label}: {text}") # type: ignore[misc]
656
667
 
657
668
  # Report progress at intervals
658
669
  if now - last_progress_time >= PROGRESS_INTERVAL_SECONDS:
@@ -660,7 +671,7 @@ build projects, run tests, and interact with the file system."""
660
671
  if combined_output:
661
672
  preview = get_last_n_lines(combined_output, 5)
662
673
  elapsed = format_duration((now - start_time) * 1000)
663
- yield ToolProgress(content=f"Running... ({elapsed})\n{preview}")
674
+ yield ToolProgress(content=f"Running... ({elapsed})\n{preview}") # type: ignore[misc]
664
675
  last_progress_time = now
665
676
 
666
677
  # Check timeout
@@ -696,9 +707,7 @@ build projects, run tests, and interact with the file system."""
696
707
  # Store results in a way that the caller can access
697
708
  self._last_execution_result = (stdout_lines, stderr_lines, timed_out)
698
709
 
699
- async def _drain_stream(
700
- self, stream: Optional[asyncio.StreamReader], sink: list[str]
701
- ) -> None:
710
+ async def _drain_stream(self, stream: Optional[asyncio.StreamReader], sink: list[str]) -> None:
702
711
  """Drain any remaining data from a stream."""
703
712
  if not stream:
704
713
  return
@@ -907,14 +916,14 @@ build projects, run tests, and interact with the file system."""
907
916
 
908
917
  while not queue.empty():
909
918
  label, text = queue.get_nowait()
910
- yield ToolProgress(content=f"{label}: {text}")
919
+ yield ToolProgress(content=f"{label}: {text}") # type: ignore[misc]
911
920
 
912
921
  if now - last_progress_time >= PROGRESS_INTERVAL_SECONDS:
913
922
  combined_output = "".join(stdout_lines + stderr_lines)
914
923
  if combined_output:
915
924
  preview = get_last_n_lines(combined_output, 5)
916
925
  elapsed = format_duration((now - start) * 1000)
917
- yield ToolProgress(content=f"Running... ({elapsed})\n{preview}")
926
+ yield ToolProgress(content=f"Running... ({elapsed})\n{preview}") # type: ignore[misc]
918
927
  last_progress_time = now
919
928
 
920
929
  if deadline is not None and now >= deadline:
@@ -969,7 +978,8 @@ build projects, run tests, and interact with the file system."""
969
978
  raise # Re-raise cancellation
970
979
  logger.warning(
971
980
  "[bash_tool] Error executing command: %s: %s",
972
- type(e).__name__, e,
981
+ type(e).__name__,
982
+ e,
973
983
  extra={"command": effective_command},
974
984
  )
975
985
  error_output = self._create_error_output(
@@ -74,7 +74,8 @@ def _annotation_flag(tool_info: Any, key: str) -> bool:
74
74
  except (AttributeError, TypeError, KeyError) as exc:
75
75
  logger.debug(
76
76
  "[mcp_tools] Failed to read annotation flag: %s: %s",
77
- type(exc).__name__, exc,
77
+ type(exc).__name__,
78
+ exc,
78
79
  )
79
80
  return False
80
81
  return False
@@ -204,7 +205,7 @@ class DynamicMcpTool(Tool[BaseModel, McpToolCallOutput]):
204
205
  def input_schema(self) -> type[BaseModel]:
205
206
  return self._input_model
206
207
 
207
- async def prompt(self, _safe_mode: bool = False) -> str:
208
+ async def prompt(self, _yolo_mode: bool = False) -> str:
208
209
  return await self.description()
209
210
 
210
211
  def is_read_only(self) -> bool:
@@ -321,7 +322,14 @@ class DynamicMcpTool(Tool[BaseModel, McpToolCallOutput]):
321
322
  data=annotated_output,
322
323
  result_for_assistant=final_text,
323
324
  )
324
- except (OSError, RuntimeError, ConnectionError, ValueError, KeyError, TypeError) as exc: # pragma: no cover - runtime errors
325
+ except (
326
+ OSError,
327
+ RuntimeError,
328
+ ConnectionError,
329
+ ValueError,
330
+ KeyError,
331
+ TypeError,
332
+ ) as exc: # pragma: no cover - runtime errors
325
333
  output = McpToolCallOutput(
326
334
  server=self.server_name,
327
335
  tool=self.tool_info.name,
@@ -333,7 +341,8 @@ class DynamicMcpTool(Tool[BaseModel, McpToolCallOutput]):
333
341
  )
334
342
  logger.warning(
335
343
  "Error calling MCP tool: %s: %s",
336
- type(exc).__name__, exc,
344
+ type(exc).__name__,
345
+ exc,
337
346
  extra={
338
347
  "server": self.server_name,
339
348
  "tool": self.tool_info.name,
@@ -381,10 +390,16 @@ def load_dynamic_mcp_tools_sync(project_path: Optional[Path] = None) -> List[Dyn
381
390
 
382
391
  try:
383
392
  return asyncio.run(_load_and_keep())
384
- except (OSError, RuntimeError, ConnectionError, ValueError) as exc: # pragma: no cover - SDK/runtime failures
393
+ except (
394
+ OSError,
395
+ RuntimeError,
396
+ ConnectionError,
397
+ ValueError,
398
+ ) as exc: # pragma: no cover - SDK/runtime failures
385
399
  logger.warning(
386
400
  "Failed to initialize MCP runtime for dynamic tools (sync): %s: %s",
387
- type(exc).__name__, exc,
401
+ type(exc).__name__,
402
+ exc,
388
403
  )
389
404
  return []
390
405
 
@@ -393,10 +408,16 @@ async def load_dynamic_mcp_tools_async(project_path: Optional[Path] = None) -> L
393
408
  """Async loader for MCP tools when already in an event loop."""
394
409
  try:
395
410
  runtime = await ensure_mcp_runtime(project_path)
396
- except (OSError, RuntimeError, ConnectionError, ValueError) as exc: # pragma: no cover - SDK/runtime failures
411
+ except (
412
+ OSError,
413
+ RuntimeError,
414
+ ConnectionError,
415
+ ValueError,
416
+ ) as exc: # pragma: no cover - SDK/runtime failures
397
417
  logger.warning(
398
418
  "Failed to initialize MCP runtime for dynamic tools (async): %s: %s",
399
- type(exc).__name__, exc,
419
+ type(exc).__name__,
420
+ exc,
400
421
  )
401
422
  return []
402
423
  return _build_dynamic_mcp_tools(runtime)
@@ -151,7 +151,7 @@ class EnterPlanModeTool(Tool[EnterPlanModeToolInput, EnterPlanModeToolOutput]):
151
151
  def input_schema(self) -> type[EnterPlanModeToolInput]:
152
152
  return EnterPlanModeToolInput
153
153
 
154
- async def prompt(self, safe_mode: bool = False) -> str: # noqa: ARG002
154
+ async def prompt(self, yolo_mode: bool = False) -> str: # noqa: ARG002
155
155
  return ENTER_PLAN_MODE_PROMPT
156
156
 
157
157
  def user_facing_name(self) -> str:
@@ -84,7 +84,7 @@ class ExitPlanModeTool(Tool[ExitPlanModeToolInput, ExitPlanModeToolOutput]):
84
84
  def input_schema(self) -> type[ExitPlanModeToolInput]:
85
85
  return ExitPlanModeToolInput
86
86
 
87
- async def prompt(self, safe_mode: bool = False) -> str: # noqa: ARG002
87
+ async def prompt(self, yolo_mode: bool = False) -> str: # noqa: ARG002
88
88
  return EXIT_PLAN_MODE_PROMPT
89
89
 
90
90
  def user_facing_name(self) -> str:
@@ -84,7 +84,7 @@ match exactly (including whitespace and indentation)."""
84
84
  ),
85
85
  ]
86
86
 
87
- async def prompt(self, safe_mode: bool = False) -> str:
87
+ async def prompt(self, yolo_mode: bool = False) -> str:
88
88
  return (
89
89
  "Performs exact string replacements in files.\n\n"
90
90
  "Usage:\n"
@@ -159,7 +159,9 @@ match exactly (including whitespace and indentation)."""
159
159
 
160
160
  # Check if path is ignored (warning for edit operations)
161
161
  file_path_obj = Path(file_path)
162
- should_proceed, warning_msg = check_path_for_tool(file_path_obj, tool_name="Edit", warn_only=True)
162
+ should_proceed, warning_msg = check_path_for_tool(
163
+ file_path_obj, tool_name="Edit", warn_only=True
164
+ )
163
165
  if warning_msg:
164
166
  logger.warning("[file_edit_tool] %s", warning_msg)
165
167
 
@@ -238,7 +240,8 @@ match exactly (including whitespace and indentation)."""
238
240
  except (OSError, IOError, RuntimeError) as exc:
239
241
  logger.warning(
240
242
  "[file_edit_tool] Failed to record file snapshot: %s: %s",
241
- type(exc).__name__, exc,
243
+ type(exc).__name__,
244
+ exc,
242
245
  extra={"file_path": abs_file_path},
243
246
  )
244
247
 
@@ -330,7 +333,8 @@ match exactly (including whitespace and indentation)."""
330
333
  except (OSError, IOError, PermissionError, UnicodeDecodeError, ValueError) as e:
331
334
  logger.warning(
332
335
  "[file_edit_tool] Error editing file: %s: %s",
333
- type(e).__name__, e,
336
+ type(e).__name__,
337
+ e,
334
338
  extra={"file_path": input_data.file_path},
335
339
  )
336
340
  error_output = FileEditToolOutput(
@@ -18,7 +18,7 @@ from ripperdoc.core.tool import (
18
18
  )
19
19
  from ripperdoc.utils.log import get_logger
20
20
  from ripperdoc.utils.file_watch import record_snapshot
21
- from ripperdoc.utils.path_ignore import check_path_for_tool, is_path_ignored
21
+ from ripperdoc.utils.path_ignore import check_path_for_tool
22
22
 
23
23
  logger = get_logger()
24
24
 
@@ -70,7 +70,7 @@ and limit to read only a portion of the file."""
70
70
  ),
71
71
  ]
72
72
 
73
- async def prompt(self, safe_mode: bool = False) -> str:
73
+ async def prompt(self, yolo_mode: bool = False) -> str:
74
74
  return (
75
75
  "Read a file from the local filesystem.\n\n"
76
76
  "Usage:\n"
@@ -106,7 +106,9 @@ and limit to read only a portion of the file."""
106
106
 
107
107
  # Check if path is ignored (warning only for read operations)
108
108
  file_path = Path(input_data.file_path)
109
- should_proceed, warning_msg = check_path_for_tool(file_path, tool_name="Read", warn_only=True)
109
+ should_proceed, warning_msg = check_path_for_tool(
110
+ file_path, tool_name="Read", warn_only=True
111
+ )
110
112
  if warning_msg:
111
113
  logger.info("[file_read_tool] %s", warning_msg)
112
114
 
@@ -166,7 +168,8 @@ and limit to read only a portion of the file."""
166
168
  except (OSError, IOError, RuntimeError) as exc:
167
169
  logger.warning(
168
170
  "[file_read_tool] Failed to record file snapshot: %s: %s",
169
- type(exc).__name__, exc,
171
+ type(exc).__name__,
172
+ exc,
170
173
  extra={"file_path": input_data.file_path},
171
174
  )
172
175
 
@@ -185,7 +188,8 @@ and limit to read only a portion of the file."""
185
188
  except (OSError, IOError, UnicodeDecodeError, ValueError) as e:
186
189
  logger.warning(
187
190
  "[file_read_tool] Error reading file: %s: %s",
188
- type(e).__name__, e,
191
+ type(e).__name__,
192
+ e,
189
193
  extra={"file_path": input_data.file_path},
190
194
  )
191
195
  # Create an error output
@@ -72,10 +72,10 @@ the file if it already exists."""
72
72
  ),
73
73
  ]
74
74
 
75
- async def prompt(self, safe_mode: bool = False) -> str:
75
+ async def prompt(self, yolo_mode: bool = False) -> str:
76
76
  prompt = """Use the Write tool to create new files. """
77
77
 
78
- if safe_mode:
78
+ if not yolo_mode:
79
79
  prompt += """IMPORTANT: You must ALWAYS prefer editing existing files.
80
80
  NEVER write new files unless explicitly required by the user."""
81
81
 
@@ -134,7 +134,9 @@ NEVER write new files unless explicitly required by the user."""
134
134
 
135
135
  # Check if path is ignored (warning for write operations)
136
136
  file_path_obj = Path(file_path)
137
- should_proceed, warning_msg = check_path_for_tool(file_path_obj, tool_name="Write", warn_only=True)
137
+ should_proceed, warning_msg = check_path_for_tool(
138
+ file_path_obj, tool_name="Write", warn_only=True
139
+ )
138
140
  if warning_msg:
139
141
  logger.warning("[file_write_tool] %s", warning_msg)
140
142
 
@@ -171,7 +173,8 @@ NEVER write new files unless explicitly required by the user."""
171
173
  except (OSError, IOError, RuntimeError) as exc:
172
174
  logger.warning(
173
175
  "[file_write_tool] Failed to record file snapshot: %s: %s",
174
- type(exc).__name__, exc,
176
+ type(exc).__name__,
177
+ exc,
175
178
  extra={"file_path": abs_file_path},
176
179
  )
177
180
 
@@ -189,7 +192,8 @@ NEVER write new files unless explicitly required by the user."""
189
192
  except (OSError, IOError, PermissionError, UnicodeEncodeError) as e:
190
193
  logger.warning(
191
194
  "[file_write_tool] Error writing file: %s: %s",
192
- type(e).__name__, e,
195
+ type(e).__name__,
196
+ e,
193
197
  extra={"file_path": input_data.file_path},
194
198
  )
195
199
  error_output = FileWriteToolOutput(
@@ -76,7 +76,7 @@ class GlobTool(Tool[GlobToolInput, GlobToolOutput]):
76
76
  ),
77
77
  ]
78
78
 
79
- async def prompt(self, _safe_mode: bool = False) -> str:
79
+ async def prompt(self, _yolo_mode: bool = False) -> str:
80
80
  return GLOB_USAGE
81
81
 
82
82
  def is_read_only(self) -> bool:
@@ -169,7 +169,8 @@ class GlobTool(Tool[GlobToolInput, GlobToolOutput]):
169
169
  except (OSError, RuntimeError, ValueError) as e:
170
170
  logger.warning(
171
171
  "[glob_tool] Error executing glob: %s: %s",
172
- type(e).__name__, e,
172
+ type(e).__name__,
173
+ e,
173
174
  extra={"pattern": input_data.pattern, "path": input_data.path},
174
175
  )
175
176
  error_output = GlobToolOutput(matches=[], pattern=input_data.pattern, count=0)
@@ -148,7 +148,7 @@ class GrepTool(Tool[GrepToolInput, GrepToolOutput]):
148
148
  ),
149
149
  ]
150
150
 
151
- async def prompt(self, _safe_mode: bool = False) -> str:
151
+ async def prompt(self, _yolo_mode: bool = False) -> str:
152
152
  return GREP_USAGE
153
153
 
154
154
  def is_read_only(self) -> bool:
@@ -358,7 +358,8 @@ class GrepTool(Tool[GrepToolInput, GrepToolOutput]):
358
358
  except (OSError, RuntimeError, ValueError, subprocess.SubprocessError) as e:
359
359
  logger.warning(
360
360
  "[grep_tool] Error executing grep: %s: %s",
361
- type(e).__name__, e,
361
+ type(e).__name__,
362
+ e,
362
363
  extra={"pattern": input_data.pattern, "path": input_data.path},
363
364
  )
364
365
  error_output = GrepToolOutput(
@@ -53,7 +53,7 @@ class KillBashTool(Tool[KillBashInput, KillBashOutput]):
53
53
  async def description(self) -> str:
54
54
  return "Kill a background bash shell by ID"
55
55
 
56
- async def prompt(self, safe_mode: bool = False) -> str:
56
+ async def prompt(self, yolo_mode: bool = False) -> str:
57
57
  return KILL_BASH_PROMPT
58
58
 
59
59
  @property
@@ -320,7 +320,7 @@ class LSTool(Tool[LSToolInput, LSToolOutput]):
320
320
  ),
321
321
  ]
322
322
 
323
- async def prompt(self, safe_mode: bool = False) -> str:
323
+ async def prompt(self, yolo_mode: bool = False) -> str:
324
324
  return (
325
325
  "Lists files and directories in a given path. The path parameter must be an absolute path, "
326
326
  "not a relative path. You can optionally provide an array of glob patterns to ignore with "