skydeckai-code 0.1.38__py3-none-any.whl → 0.1.40__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.
@@ -232,11 +232,15 @@ def edit_file_tool():
232
232
  "description": "Make line-based edits to a text file. "
233
233
  "WHEN TO USE: When you need to make selective changes to specific parts of a file while preserving the rest of the content. "
234
234
  "Useful for modifying configuration values, updating text while maintaining file structure, or making targeted code changes. "
235
+ "IMPORTANT: For multiple edits to the same file, use a single tool call with multiple edits in the 'edits' array rather than multiple tool calls. "
236
+ "This is more efficient and ensures all edits are applied atomically. "
235
237
  "WHEN NOT TO USE: When you want to completely replace a file's contents (use write_file instead), when you need to create a new file (use write_file instead), "
236
238
  "or when you want to apply highly complex edits with context. "
237
239
  "RETURNS: A git-style diff showing the changes made, along with information about any failed matches. "
238
240
  "The response includes sections for failed matches (if any) and the unified diff output. "
239
- "Always use dryRun first to preview changes before applying them. Only works within the allowed directory.",
241
+ "Only works within the allowed directory. "
242
+ "EXAMPLES: For a single edit: {\"path\": \"config.js\", \"edits\": [{\"oldText\": \"port: 3000\", \"newText\": \"port: 8080\"}]}. "
243
+ "For multiple edits: {\"path\": \"app.py\", \"edits\": [{\"oldText\": \"debug=False\", \"newText\": \"debug=True\"}, {\"oldText\": \"version='1.0'\", \"newText\": \"version='2.0'\"}]}",
240
244
  "inputSchema": {
241
245
  "type": "object",
242
246
  "properties": {
@@ -260,20 +264,26 @@ def edit_file_tool():
260
264
  },
261
265
  "required": ["oldText", "newText"]
262
266
  },
263
- "description": "List of edit operations to perform on the file. Each edit specifies text to find (oldText) and text to replace it with (newText). The edits are applied in sequence, and each one can modify the result of previous edits."
264
- },
265
- "dryRun": {
266
- "type": "boolean",
267
- "description": "Preview changes without applying them to the file. Set to true to see what changes would be made without actually modifying the file. Highly recommended before making actual changes.",
268
- "default": False
267
+ "description": "MUST be an array of edit objects, NOT a string. Each edit object must contain 'oldText' and 'newText' properties. "
268
+ "For multiple edits, use: [{\"oldText\": \"text1\", \"newText\": \"replacement1\"}, {\"oldText\": \"text2\", \"newText\": \"replacement2\"}]. "
269
+ "For single edit, still use array: [{\"oldText\": \"text\", \"newText\": \"replacement\"}]. "
270
+ "The edits are applied in sequence, and each one can modify the result of previous edits. "
271
+ "AVOID multiple tool calls for the same file - instead, group all edits into a single call."
269
272
  },
270
273
  "options": {
271
274
  "type": "object",
272
275
  "properties": {
273
276
  "partialMatch": {
274
277
  "type": "boolean",
275
- "description": "Enable fuzzy matching for finding text. When true, the tool will try to find the best match even if it's not an exact match, using a confidence threshold of 80%.",
278
+ "description": "Enable fuzzy matching for finding text. When true, the tool will try to find the best match even if it's not an exact match, using the confidenceThreshold (default 80%).",
276
279
  "default": True
280
+ },
281
+ "confidenceThreshold": {
282
+ "type": "number",
283
+ "description": "Minimum confidence threshold for fuzzy matching (0.0 to 1.0). Higher values require more exact matches. Default is 0.8 (80% confidence).",
284
+ "minimum": 0.0,
285
+ "maximum": 1.0,
286
+ "default": 0.8
277
287
  }
278
288
  }
279
289
  }
@@ -784,11 +794,17 @@ def find_best_match(content: str, pattern: str, partial_match: bool = True) -> t
784
794
 
785
795
  return best_start, best_end, best_score
786
796
 
787
- async def apply_file_edits(file_path: str, edits: List[dict], dry_run: bool = False, options: dict = None) -> str:
788
- """Apply edits to a file with optional formatting and return diff."""
797
+ async def apply_file_edits(file_path: str, edits: List[dict], options: dict = None) -> tuple[str, bool, int, int]:
798
+ """Apply edits to a file with optional formatting and return diff.
799
+
800
+ Returns:
801
+ tuple: (result_text, has_changes, successful_edits, failed_edits)
802
+ """
789
803
  # Set default options
790
804
  options = options or {}
791
805
  partial_match = options.get('partialMatch', True)
806
+ # Use 0.8 confidence threshold to prevent false positives while allowing reasonable fuzzy matches
807
+ confidence_threshold = options.get('confidenceThreshold', 0.8)
792
808
 
793
809
  # Read file content
794
810
  with open(file_path, 'r', encoding='utf-8') as f:
@@ -797,9 +813,10 @@ async def apply_file_edits(file_path: str, edits: List[dict], dry_run: bool = Fa
797
813
  # Track modifications
798
814
  modified_content = content
799
815
  failed_matches = []
816
+ successful_edits = []
800
817
 
801
818
  # Apply each edit
802
- for edit in edits:
819
+ for edit_idx, edit in enumerate(edits):
803
820
  old_text = edit['oldText']
804
821
  new_text = edit['newText']
805
822
 
@@ -810,7 +827,7 @@ async def apply_file_edits(file_path: str, edits: List[dict], dry_run: bool = Fa
810
827
  # Find best match
811
828
  start, end, confidence = find_best_match(working_content, search_text, partial_match)
812
829
 
813
- if confidence >= 0.8:
830
+ if confidence >= confidence_threshold:
814
831
  # Fix indentation while preserving relative structure
815
832
  if start >= 0:
816
833
  # Get the indentation of the first line of the matched text
@@ -851,31 +868,77 @@ async def apply_file_edits(file_path: str, edits: List[dict], dry_run: bool = Fa
851
868
 
852
869
  # Apply the edit
853
870
  modified_content = modified_content[:start] + replacement + modified_content[end:]
871
+ successful_edits.append({
872
+ 'index': edit_idx,
873
+ 'oldText': old_text,
874
+ 'newText': new_text,
875
+ 'confidence': confidence
876
+ })
854
877
  else:
855
878
  failed_matches.append({
879
+ 'index': edit_idx,
856
880
  'oldText': old_text,
881
+ 'newText': new_text,
857
882
  'confidence': confidence,
858
883
  'bestMatch': working_content[start:end] if start >= 0 and end > start else None
859
884
  })
860
885
 
861
886
  # Create diff
862
887
  diff = create_unified_diff(content, modified_content, os.path.basename(file_path))
888
+ has_changes = modified_content != content
863
889
 
864
- # Write changes if not dry run
865
- if not dry_run and not failed_matches:
890
+ # CRITICAL FIX: Write changes even if some edits failed (partial success)
891
+ # This prevents the infinite retry loop
892
+ if has_changes:
866
893
  with open(file_path, 'w', encoding='utf-8') as f:
867
894
  f.write(modified_content)
868
895
 
869
- # Return results
870
- failed_matches_text = '=== Failed Matches ===\n' + json.dumps(failed_matches, indent=2) + '\n\n' if failed_matches else ''
871
- diff_text = f'=== Diff ===\n{diff}'
872
- return failed_matches_text + diff_text
896
+ # Build comprehensive result message
897
+ result_parts = []
898
+
899
+ # Summary
900
+ total_edits = len(edits)
901
+ successful_count = len(successful_edits)
902
+ failed_count = len(failed_matches)
903
+
904
+ result_parts.append(f'=== Edit Summary ===')
905
+ result_parts.append(f'Total edits: {total_edits}')
906
+ result_parts.append(f'Successful: {successful_count}')
907
+ result_parts.append(f'Failed: {failed_count}')
908
+ result_parts.append(f'File modified: {has_changes}')
909
+ result_parts.append('')
910
+
911
+ # Failed matches details
912
+ if failed_matches:
913
+ result_parts.append('=== Failed Matches ===')
914
+ for failed in failed_matches:
915
+ result_parts.append(f"Edit #{failed['index'] + 1}: Confidence {failed['confidence']:.2f}")
916
+ result_parts.append(f" Searched for: {repr(failed['oldText'][:100])}...")
917
+ if failed['bestMatch']:
918
+ result_parts.append(f" Best match: {repr(failed['bestMatch'][:100])}...")
919
+ result_parts.append('')
920
+
921
+ # Successful edits
922
+ if successful_edits:
923
+ result_parts.append('=== Successful Edits ===')
924
+ for success in successful_edits:
925
+ result_parts.append(f"Edit #{success['index'] + 1}: Confidence {success['confidence']:.2f}")
926
+ result_parts.append('')
927
+
928
+ # Diff
929
+ if diff.strip():
930
+ result_parts.append('=== Diff ===')
931
+ result_parts.append(diff)
932
+ else:
933
+ result_parts.append('=== No Changes ===')
934
+ result_parts.append('No modifications were made to the file.')
935
+
936
+ return '\n'.join(result_parts), has_changes, successful_count, failed_count
873
937
 
874
938
  async def handle_edit_file(arguments: dict):
875
939
  """Handle editing a file with pattern matching and formatting."""
876
940
  path = arguments.get("path")
877
941
  edits = arguments.get("edits")
878
- dry_run = arguments.get("dryRun", False)
879
942
  options = arguments.get("options", {})
880
943
 
881
944
  if not path:
@@ -900,8 +963,14 @@ async def handle_edit_file(arguments: dict):
900
963
  raise ValueError(f"Access denied: Path ({full_path}) must be within allowed directory ({state.allowed_directory})")
901
964
 
902
965
  try:
903
- result = await apply_file_edits(full_path, edits, dry_run, options)
904
- return [TextContent(type="text", text=result)]
966
+ result_text, has_changes, successful_count, failed_count = await apply_file_edits(full_path, edits, options)
967
+
968
+ # CRITICAL FIX: Raise an exception only if ALL edits failed AND no changes were made
969
+ # This prevents silent failures that cause infinite retry loops
970
+ if failed_count > 0 and successful_count == 0:
971
+ raise ValueError(f"All {failed_count} edits failed to match. No changes were made to the file. Check the 'oldText' patterns and ensure they match the file content exactly.")
972
+
973
+ return [TextContent(type="text", text=result_text)]
905
974
  except Exception as e:
906
975
  raise ValueError(f"Error editing file: {str(e)}")
907
976
 
@@ -112,6 +112,9 @@ def get_system_details() -> Dict[str, Any]:
112
112
  """Gather detailed system information."""
113
113
 
114
114
  is_mac = platform.system() == "Darwin"
115
+ disk_total = psutil.disk_usage('/').total
116
+ disk_free = psutil.disk_usage('/').free
117
+ disk_used_percentage = (disk_total - disk_free) / disk_total * 100
115
118
 
116
119
  # System and OS Information
117
120
  system_info = {
@@ -142,7 +145,7 @@ def get_system_details() -> Dict[str, Any]:
142
145
  "disk": {
143
146
  "total": get_size(psutil.disk_usage('/').total),
144
147
  "free": get_size(psutil.disk_usage('/').free),
145
- "used_percentage": f"{psutil.disk_usage('/').percent}%"
148
+ "used_percentage": f"{disk_used_percentage}%"
146
149
  }
147
150
  }
148
151