code-puppy 0.0.59__py3-none-any.whl → 0.0.61__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.
@@ -205,145 +205,152 @@ def _write_to_file(
205
205
  return {"error": str(exc), "diff": ""}
206
206
 
207
207
 
208
- def register_file_modifications_tools(agent):
209
- """Attach file-editing tools to *agent* with mandatory diff rendering."""
208
+ def delete_snippet_from_file(
209
+ context: RunContext, file_path: str, snippet: str
210
+ ) -> Dict[str, Any]:
211
+ console.log(f"🗑️ Deleting snippet from file [bold red]{file_path}[/bold red]")
212
+ res = _delete_snippet_from_file(context, file_path, snippet)
213
+ diff = res.get("diff", "")
214
+ if diff:
215
+ _print_diff(diff)
216
+ return res
210
217
 
211
- def delete_snippet_from_file(
212
- context: RunContext, file_path: str, snippet: str
213
- ) -> Dict[str, Any]:
214
- console.log(f"🗑️ Deleting snippet from file [bold red]{file_path}[/bold red]")
215
- res = _delete_snippet_from_file(context, file_path, snippet)
216
- diff = res.get("diff", "")
217
- if diff:
218
- _print_diff(diff)
219
- return res
220
-
221
- def write_to_file(
222
- context: RunContext, path: str, content: str, overwrite: bool
223
- ) -> Dict[str, Any]:
224
- console.log(f"✏️ Writing file [bold blue]{path}[/bold blue]")
225
- res = _write_to_file(context, path, content, overwrite=overwrite)
226
- diff = res.get("diff", "")
227
- if diff:
228
- _print_diff(diff)
229
- return res
230
-
231
- def replace_in_file(
232
- context: RunContext, path: str, replacements: List[Dict[str, str]]
233
- ) -> Dict[str, Any]:
234
- console.log(f"♻️ Replacing text in [bold yellow]{path}[/bold yellow]")
235
- res = _replace_in_file(context, path, replacements)
236
- diff = res.get("diff", "")
237
- if diff:
238
- _print_diff(diff)
239
- return res
240
218
 
241
- @agent.tool(retries=5)
242
- def edit_file(context: RunContext, path: str, diff: str) -> Dict[str, Any]:
243
- """
244
- Unified file editing tool that can:
245
- - Create/write a new file when the target does not exist (using raw content or a JSON payload with a "content" key)
246
- - Replace text within an existing file via a JSON payload with "replacements" (delegates to internal replace logic)
247
- - Delete a snippet from an existing file via a JSON payload with "delete_snippet"
248
- Parameters
249
- ----------
250
- path : str
251
- Path to the target file (relative or absolute)
252
- diff : str
253
- Either:
254
- * Raw file content (for file creation)
255
- * A JSON string with one of the following shapes:
256
- {"content": "full file contents", "overwrite": true}
257
- {"replacements": [ {"old_str": "foo", "new_str": "bar"}, ... ] }
258
- {"delete_snippet": "text to remove"}
259
- The function auto-detects the payload type and routes to the appropriate internal helper.
260
- """
261
- console.print("\n[bold white on blue] EDIT FILE [/bold white on blue]")
262
- file_path = os.path.abspath(path)
263
- try:
264
- parsed_payload = json.loads(diff)
265
- except json.JSONDecodeError:
266
- try:
267
- console.print(
268
- "[bold yellow] JSON Parsing Failed! TRYING TO REPAIR! [/bold yellow]"
269
- )
270
- parsed_payload = json.loads(repair_json(diff))
271
- console.print(
272
- "[bold green on cyan] SUCCESS - WOOF! [/bold green on cyan]"
273
- )
274
- except Exception as e:
275
- console.print(
276
- f"[bold red] Unable to parse diff [/bold red] -- {str(e)}"
277
- )
278
- return {
279
- "success": False,
280
- "path": file_path,
281
- "message": f"Unable to parse diff JSON -- {str(e)}",
282
- "changed": False,
283
- "diff": "",
284
- }
219
+ def write_to_file(
220
+ context: RunContext, path: str, content: str, overwrite: bool
221
+ ) -> Dict[str, Any]:
222
+ console.log(f"✏️ Writing file [bold blue]{path}[/bold blue]")
223
+ res = _write_to_file(context, path, content, overwrite=overwrite)
224
+ diff = res.get("diff", "")
225
+ if diff:
226
+ _print_diff(diff)
227
+ return res
228
+
229
+
230
+ def replace_in_file(
231
+ context: RunContext, path: str, replacements: List[Dict[str, str]]
232
+ ) -> Dict[str, Any]:
233
+ console.log(f"♻️ Replacing text in [bold yellow]{path}[/bold yellow]")
234
+ res = _replace_in_file(context, path, replacements)
235
+ diff = res.get("diff", "")
236
+ if diff:
237
+ _print_diff(diff)
238
+ return res
239
+
240
+
241
+ def _edit_file(context: RunContext, path: str, diff: str) -> Dict[str, Any]:
242
+ """
243
+ Unified file editing tool that can:
244
+ - Create/write a new file when the target does not exist (using raw content or a JSON payload with a "content" key)
245
+ - Replace text within an existing file via a JSON payload with "replacements" (delegates to internal replace logic)
246
+ - Delete a snippet from an existing file via a JSON payload with "delete_snippet"
247
+ Parameters
248
+ ----------
249
+ path : str
250
+ Path to the target file (relative or absolute)
251
+ diff : str
252
+ Either:
253
+ * Raw file content (for file creation)
254
+ * A JSON string with one of the following shapes:
255
+ {"content": "full file contents", "overwrite": true}
256
+ {"replacements": [ {"old_str": "foo", "new_str": "bar"}, ... ] }
257
+ {"delete_snippet": "text to remove"}
258
+ The function auto-detects the payload type and routes to the appropriate internal helper.
259
+ """
260
+ console.print("\n[bold white on blue] EDIT FILE [/bold white on blue]")
261
+ file_path = os.path.abspath(path)
262
+ try:
263
+ parsed_payload = json.loads(diff)
264
+ except json.JSONDecodeError:
285
265
  try:
286
- if isinstance(parsed_payload, dict):
287
- if "delete_snippet" in parsed_payload:
288
- snippet = parsed_payload["delete_snippet"]
289
- return delete_snippet_from_file(context, file_path, snippet)
290
- if "replacements" in parsed_payload:
291
- replacements = parsed_payload["replacements"]
292
- return replace_in_file(context, file_path, replacements)
293
- if "content" in parsed_payload:
294
- content = parsed_payload["content"]
295
- overwrite = bool(parsed_payload.get("overwrite", False))
296
- file_exists = os.path.exists(file_path)
297
- if file_exists and not overwrite:
298
- return {
299
- "success": False,
300
- "path": file_path,
301
- "message": f"File '{file_path}' exists. Set 'overwrite': true to replace.",
302
- "changed": False,
303
- }
304
- return write_to_file(context, file_path, content, overwrite)
305
- return write_to_file(context, file_path, diff, overwrite=False)
306
- except Exception as e:
307
266
  console.print(
308
- "[bold red] Unable to route file modification tool call to sub-tool [/bold red]"
267
+ "[bold yellow] JSON Parsing Failed! TRYING TO REPAIR! [/bold yellow]"
309
268
  )
310
- console.print(str(e))
269
+ parsed_payload = json.loads(repair_json(diff))
270
+ console.print("[bold white on blue] SUCCESS - WOOF! [/bold white on blue]")
271
+ except Exception as e:
272
+ console.print(f"[bold red] Unable to parse diff [/bold red] -- {str(e)}")
311
273
  return {
312
274
  "success": False,
313
275
  "path": file_path,
314
- "message": f"Something went wrong in file editing: {str(e)}",
276
+ "message": f"Unable to parse diff JSON -- {str(e)}",
315
277
  "changed": False,
278
+ "diff": "",
279
+ }
280
+ try:
281
+ if isinstance(parsed_payload, dict):
282
+ if "delete_snippet" in parsed_payload:
283
+ snippet = parsed_payload["delete_snippet"]
284
+ return delete_snippet_from_file(context, file_path, snippet)
285
+ if "replacements" in parsed_payload:
286
+ replacements = parsed_payload["replacements"]
287
+ return replace_in_file(context, file_path, replacements)
288
+ if "content" in parsed_payload:
289
+ content = parsed_payload["content"]
290
+ overwrite = bool(parsed_payload.get("overwrite", False))
291
+ file_exists = os.path.exists(file_path)
292
+ if file_exists and not overwrite:
293
+ return {
294
+ "success": False,
295
+ "path": file_path,
296
+ "message": f"File '{file_path}' exists. Set 'overwrite': true to replace.",
297
+ "changed": False,
298
+ }
299
+ return write_to_file(context, file_path, content, overwrite)
300
+ return write_to_file(context, file_path, diff, overwrite=False)
301
+ except Exception as e:
302
+ console.print(
303
+ "[bold red] Unable to route file modification tool call to sub-tool [/bold red]"
304
+ )
305
+ console.print(str(e))
306
+ return {
307
+ "success": False,
308
+ "path": file_path,
309
+ "message": f"Something went wrong in file editing: {str(e)}",
310
+ "changed": False,
311
+ }
312
+
313
+
314
+ def _delete_file(context: RunContext, file_path: str) -> Dict[str, Any]:
315
+ console.log(f"🗑️ Deleting file [bold red]{file_path}[/bold red]")
316
+ file_path = os.path.abspath(file_path)
317
+ try:
318
+ if not os.path.exists(file_path) or not os.path.isfile(file_path):
319
+ res = {"error": f"File '{file_path}' does not exist.", "diff": ""}
320
+ else:
321
+ with open(file_path, "r", encoding="utf-8") as f:
322
+ original = f.read()
323
+ diff_text = "".join(
324
+ difflib.unified_diff(
325
+ original.splitlines(keepends=True),
326
+ [],
327
+ fromfile=f"a/{os.path.basename(file_path)}",
328
+ tofile=f"b/{os.path.basename(file_path)}",
329
+ n=3,
330
+ )
331
+ )
332
+ os.remove(file_path)
333
+ res = {
334
+ "success": True,
335
+ "path": file_path,
336
+ "message": f"File '{file_path}' deleted successfully.",
337
+ "changed": True,
338
+ "diff": diff_text,
316
339
  }
340
+ except Exception as exc:
341
+ _log_error("Unhandled exception in delete_file", exc)
342
+ res = {"error": str(exc), "diff": ""}
343
+ _print_diff(res.get("diff", ""))
344
+ return res
345
+
346
+
347
+ def register_file_modifications_tools(agent):
348
+ """Attach file-editing tools to *agent* with mandatory diff rendering."""
349
+
350
+ @agent.tool(retries=5)
351
+ def edit_file(context: RunContext, path: str, diff: str) -> Dict[str, Any]:
352
+ return _edit_file(context, path, diff)
317
353
 
318
- @agent.tool
354
+ @agent.tool(retries=5)
319
355
  def delete_file(context: RunContext, file_path: str) -> Dict[str, Any]:
320
- console.log(f"🗑️ Deleting file [bold red]{file_path}[/bold red]")
321
- file_path = os.path.abspath(file_path)
322
- try:
323
- if not os.path.exists(file_path) or not os.path.isfile(file_path):
324
- res = {"error": f"File '{file_path}' does not exist.", "diff": ""}
325
- else:
326
- with open(file_path, "r", encoding="utf-8") as f:
327
- original = f.read()
328
- diff_text = "".join(
329
- difflib.unified_diff(
330
- original.splitlines(keepends=True),
331
- [],
332
- fromfile=f"a/{os.path.basename(file_path)}",
333
- tofile=f"b/{os.path.basename(file_path)}",
334
- n=3,
335
- )
336
- )
337
- os.remove(file_path)
338
- res = {
339
- "success": True,
340
- "path": file_path,
341
- "message": f"File '{file_path}' deleted successfully.",
342
- "changed": True,
343
- "diff": diff_text,
344
- }
345
- except Exception as exc:
346
- _log_error("Unhandled exception in delete_file", exc)
347
- res = {"error": str(exc), "diff": ""}
348
- _print_diff(res.get("diff", ""))
349
- return res
356
+ return _delete_file(context, file_path)
@@ -1,5 +1,5 @@
1
1
  # file_operations.py
2
- import fnmatch
2
+
3
3
  import os
4
4
  from typing import Any, Dict, List
5
5
 
@@ -10,33 +10,7 @@ from code_puppy.tools.common import console
10
10
  # ---------------------------------------------------------------------------
11
11
  # Module-level helper functions (exposed for unit tests _and_ used as tools)
12
12
  # ---------------------------------------------------------------------------
13
- IGNORE_PATTERNS = [
14
- "**/node_modules/**",
15
- "**/.git/**",
16
- "**/__pycache__/**",
17
- "**/.DS_Store",
18
- "**/.env",
19
- "**/.venv/**",
20
- "**/venv/**",
21
- "**/.idea/**",
22
- "**/.vscode/**",
23
- "**/dist/**",
24
- "**/build/**",
25
- "**/*.pyc",
26
- "**/*.pyo",
27
- "**/*.pyd",
28
- "**/*.so",
29
- "**/*.dll",
30
- "**/*.exe",
31
- ]
32
-
33
-
34
- def should_ignore_path(path: str) -> bool:
35
- """Return True if *path* matches any pattern in IGNORE_PATTERNS."""
36
- for pattern in IGNORE_PATTERNS:
37
- if fnmatch.fnmatch(path, pattern):
38
- return True
39
- return False
13
+ from code_puppy.tools.common import should_ignore_path
40
14
 
41
15
 
42
16
  def _list_files(
@@ -306,3 +280,20 @@ def register_file_operations_tools(agent):
306
280
  context: RunContext, search_string: str, directory: str = "."
307
281
  ) -> List[Dict[str, Any]]:
308
282
  return _grep(context, search_string, directory)
283
+
284
+ @agent.tool
285
+ def code_map(context: RunContext, directory: str = ".") -> str:
286
+ """Generate a code map for the specified directory.
287
+ This will have a list of all function / class names and nested structure
288
+ Args:
289
+ context: The context object.
290
+ directory: The directory to generate the code map for.
291
+
292
+ Returns:
293
+ A string containing the code map.
294
+ """
295
+ console.print("[bold white on blue] CODE MAP [/bold white on blue]")
296
+ from code_puppy.tools.ts_code_map import make_code_map
297
+
298
+ result = make_code_map(directory, ignore_tests=True)
299
+ return result