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.
- {skydeckai_code-0.1.38.dist-info → skydeckai_code-0.1.40.dist-info}/METADATA +6 -246
- {skydeckai_code-0.1.38.dist-info → skydeckai_code-0.1.40.dist-info}/RECORD +10 -10
- src/aidd/tools/code_execution.py +135 -98
- src/aidd/tools/code_tools.py +53 -7
- src/aidd/tools/directory_tools.py +285 -140
- src/aidd/tools/file_tools.py +90 -21
- src/aidd/tools/system_tools.py +4 -1
- {skydeckai_code-0.1.38.dist-info → skydeckai_code-0.1.40.dist-info}/WHEEL +0 -0
- {skydeckai_code-0.1.38.dist-info → skydeckai_code-0.1.40.dist-info}/entry_points.txt +0 -0
- {skydeckai_code-0.1.38.dist-info → skydeckai_code-0.1.40.dist-info}/licenses/LICENSE +0 -0
src/aidd/tools/file_tools.py
CHANGED
@@ -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
|
-
"
|
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": "
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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
|
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],
|
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 >=
|
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
|
865
|
-
|
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
|
-
#
|
870
|
-
|
871
|
-
|
872
|
-
|
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
|
-
|
904
|
-
|
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
|
|
src/aidd/tools/system_tools.py
CHANGED
@@ -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"{
|
148
|
+
"used_percentage": f"{disk_used_percentage}%"
|
146
149
|
}
|
147
150
|
}
|
148
151
|
|
File without changes
|
File without changes
|
File without changes
|