@bestdefense/bd-agent
Version:
An AI-powered coding assistant CLI that connects to AWS Bedrock
265 lines • 11.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.advancedEditTools = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const util_1 = require("util");
const chalk_1 = __importDefault(require("chalk"));
const readFile = (0, util_1.promisify)(fs.readFile);
const writeFile = (0, util_1.promisify)(fs.writeFile);
function findBestMatch(content, searchString) {
const index = content.indexOf(searchString);
if (index === -1)
return null;
// Calculate line number
const lines = content.substring(0, index).split('\n');
return { index, line: lines.length };
}
function getLineContext(content, lineNumber, contextLines = 3) {
const lines = content.split('\n');
const startLine = Math.max(0, lineNumber - contextLines - 1);
const endLine = Math.min(lines.length, lineNumber + contextLines);
return lines.slice(startLine, endLine)
.map((line, idx) => {
const currentLine = startLine + idx + 1;
const prefix = currentLine === lineNumber ? chalk_1.default.yellow('>>> ') : ' ';
return chalk_1.default.gray(`${String(currentLine).padStart(4)} │`) + prefix + line;
})
.join('\n');
}
exports.advancedEditTools = [
{
name: 'edit',
description: 'Perform precise string replacement in a file. Finds exact match of old_str and replaces with new_str.',
input_schema: {
type: 'object',
properties: {
file_path: {
type: 'string',
description: 'The file path to edit'
},
old_str: {
type: 'string',
description: 'The exact string to replace (must match exactly including whitespace)'
},
new_str: {
type: 'string',
description: 'The string to replace it with'
},
dry_run: {
type: 'boolean',
description: 'If true, show what would be changed without modifying the file',
default: false
}
},
required: ['file_path', 'old_str', 'new_str']
},
execute: async ({ file_path, old_str, new_str, dry_run = false }) => {
try {
const absolutePath = path.resolve(file_path);
// Check if file exists
if (!fs.existsSync(absolutePath)) {
return {
success: false,
error: `File not found: ${absolutePath}`
};
}
const content = await readFile(absolutePath, 'utf-8');
// Find the string to replace
const match = findBestMatch(content, old_str);
if (!match) {
// Provide helpful context about what was searched
const similarityThreshold = 0.8;
const lines = content.split('\n');
let bestMatch = { line: -1, similarity: 0, content: '' };
// Simple similarity check (could be enhanced with proper string similarity algorithms)
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes(old_str.substring(0, Math.min(20, old_str.length)))) {
bestMatch = { line: i + 1, similarity: 0.5, content: lines[i] };
break;
}
}
let errorMsg = `String not found in file: ${file_path}\n`;
errorMsg += `Searched for:\n${chalk_1.default.red(old_str)}\n`;
if (bestMatch.line > 0) {
errorMsg += `\nDid you mean this line (${bestMatch.line})?\n`;
errorMsg += chalk_1.default.gray(bestMatch.content);
}
return { success: false, error: errorMsg };
}
// Count occurrences
const occurrences = (content.match(new RegExp(old_str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
if (occurrences > 1) {
const context = getLineContext(content, match.line);
return {
success: false,
error: `String appears ${occurrences} times in the file. Please provide more context to make it unique.\n\nFirst occurrence at line ${match.line}:\n${context}`
};
}
// Perform the replacement
const updatedContent = content.replace(old_str, new_str);
if (dry_run) {
const context = getLineContext(updatedContent, match.line);
return {
success: true,
message: 'Dry run - no changes made',
diff: {
old: old_str,
new: new_str,
line_start: match.line,
line_end: match.line + old_str.split('\n').length - 1
},
preview: context
};
}
await writeFile(absolutePath, updatedContent, 'utf-8');
return {
success: true,
message: `File edited successfully at line ${match.line}`,
diff: {
old: old_str,
new: new_str,
line_start: match.line,
line_end: match.line + old_str.split('\n').length - 1
}
};
}
catch (error) {
return { success: false, error: error.message };
}
}
},
{
name: 'multi_edit',
description: 'Perform multiple edits in a single file atomically. All edits must succeed or none are applied.',
input_schema: {
type: 'object',
properties: {
file_path: {
type: 'string',
description: 'The file path to edit'
},
edits: {
type: 'array',
description: 'Array of edit operations to perform',
items: {
type: 'object',
properties: {
old_str: {
type: 'string',
description: 'The exact string to replace'
},
new_str: {
type: 'string',
description: 'The string to replace it with'
}
},
required: ['old_str', 'new_str']
}
},
dry_run: {
type: 'boolean',
description: 'If true, validate all edits without modifying the file',
default: false
}
},
required: ['file_path', 'edits']
},
execute: async ({ file_path, edits, dry_run = false }) => {
try {
const absolutePath = path.resolve(file_path);
if (!fs.existsSync(absolutePath)) {
return {
success: false,
error: `File not found: ${absolutePath}`
};
}
let content = await readFile(absolutePath, 'utf-8');
const originalContent = content;
const appliedEdits = [];
// Validate and apply each edit sequentially
for (let i = 0; i < edits.length; i++) {
const edit = edits[i];
const match = findBestMatch(content, edit.old_str);
if (!match) {
return {
success: false,
error: `Edit ${i + 1} failed: String not found: "${edit.old_str.substring(0, 50)}${edit.old_str.length > 50 ? '...' : ''}"`
};
}
// Check for uniqueness
const occurrences = (content.match(new RegExp(edit.old_str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
if (occurrences > 1) {
return {
success: false,
error: `Edit ${i + 1} failed: String appears ${occurrences} times. Must be unique for safe editing.`
};
}
// Apply the edit
content = content.replace(edit.old_str, edit.new_str);
appliedEdits.push({
...edit,
line: match.line
});
}
if (dry_run) {
return {
success: true,
message: `Dry run successful. ${edits.length} edits validated.`,
preview: appliedEdits.map((edit, i) => `Edit ${i + 1} at line ${edit.line}: "${edit.old_str.substring(0, 30)}..." → "${edit.new_str.substring(0, 30)}..."`).join('\n')
};
}
// Write the file with all edits applied
await writeFile(absolutePath, content, 'utf-8');
return {
success: true,
message: `Successfully applied ${edits.length} edits to ${file_path}`,
diff: {
old: originalContent,
new: content
}
};
}
catch (error) {
return { success: false, error: error.message };
}
}
}
];
//# sourceMappingURL=advanced-edit.js.map