gitnexus 1.6.10-rc.77 → 1.6.10-rc.78
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.
- package/dist/mcp/local/local-backend.js +78 -76
- package/package.json +1 -1
|
@@ -3549,28 +3549,13 @@ export class LocalBackend {
|
|
|
3549
3549
|
if (oldName === new_name) {
|
|
3550
3550
|
return { error: 'New name is the same as the current name.' };
|
|
3551
3551
|
}
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
};
|
|
3560
|
-
// The definition itself
|
|
3561
|
-
if (sym.filePath && sym.startLine) {
|
|
3562
|
-
try {
|
|
3563
|
-
const content = await fs.readFile(assertSafePath(sym.filePath), 'utf-8');
|
|
3564
|
-
const lines = content.split('\n');
|
|
3565
|
-
const lineIdx = sym.startLine - 1;
|
|
3566
|
-
if (lineIdx >= 0 && lineIdx < lines.length && lines[lineIdx].includes(oldName)) {
|
|
3567
|
-
const defRegex = new RegExp(`\\b${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g');
|
|
3568
|
-
addEdit(sym.filePath, sym.startLine, lines[lineIdx].trim(), lines[lineIdx].replace(defRegex, new_name).trim(), 'graph');
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
catch (e) {
|
|
3572
|
-
logQueryError('rename:read-definition', e);
|
|
3573
|
-
}
|
|
3552
|
+
const escapedOldName = oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3553
|
+
// Classify each file to rewrite by how it was discovered. Definition and
|
|
3554
|
+
// graph-ref files carry graph confidence; files found only by text search
|
|
3555
|
+
// carry text_search confidence. A graph-classified file is never downgraded.
|
|
3556
|
+
const fileConfidence = new Map();
|
|
3557
|
+
if (sym.filePath) {
|
|
3558
|
+
fileConfidence.set(sym.filePath, 'graph');
|
|
3574
3559
|
}
|
|
3575
3560
|
// All incoming refs from graph (callers, importers, etc.)
|
|
3576
3561
|
const allIncoming = [
|
|
@@ -3579,31 +3564,12 @@ export class LocalBackend {
|
|
|
3579
3564
|
...(lookupResult.incoming.extends || []),
|
|
3580
3565
|
...(lookupResult.incoming.implements || []),
|
|
3581
3566
|
];
|
|
3582
|
-
let graphEdits = changes.size > 0 ? 1 : 0; // count definition edit
|
|
3583
3567
|
for (const ref of allIncoming) {
|
|
3584
|
-
if (
|
|
3585
|
-
|
|
3586
|
-
try {
|
|
3587
|
-
const content = await fs.readFile(assertSafePath(ref.filePath), 'utf-8');
|
|
3588
|
-
const lines = content.split('\n');
|
|
3589
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3590
|
-
if (lines[i].includes(oldName)) {
|
|
3591
|
-
addEdit(ref.filePath, i + 1, lines[i].trim(), lines[i]
|
|
3592
|
-
.replace(new RegExp(`\\b${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g'), new_name)
|
|
3593
|
-
.trim(), 'graph');
|
|
3594
|
-
graphEdits++;
|
|
3595
|
-
break; // one edit per file from graph refs
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
}
|
|
3599
|
-
catch (e) {
|
|
3600
|
-
logQueryError('rename:read-ref', e);
|
|
3568
|
+
if (ref.filePath) {
|
|
3569
|
+
fileConfidence.set(ref.filePath, 'graph');
|
|
3601
3570
|
}
|
|
3602
3571
|
}
|
|
3603
|
-
//
|
|
3604
|
-
let astSearchEdits = 0;
|
|
3605
|
-
const graphFiles = new Set([sym.filePath, ...allIncoming.map((r) => r.filePath)].filter(Boolean));
|
|
3606
|
-
// Simple text search across the repo for the old name (in files not already covered by graph)
|
|
3572
|
+
// Text search for files the graph might have missed entirely.
|
|
3607
3573
|
try {
|
|
3608
3574
|
const { execFileSync } = await import('child_process');
|
|
3609
3575
|
const rgArgs = [
|
|
@@ -3629,61 +3595,97 @@ export class LocalBackend {
|
|
|
3629
3595
|
.filter((f) => f.length > 0);
|
|
3630
3596
|
for (const file of files) {
|
|
3631
3597
|
const normalizedFile = file.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
const content = await fs.readFile(assertSafePath(normalizedFile), 'utf-8');
|
|
3636
|
-
const lines = content.split('\n');
|
|
3637
|
-
const regex = new RegExp(`\\b${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g');
|
|
3638
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3639
|
-
regex.lastIndex = 0;
|
|
3640
|
-
if (regex.test(lines[i])) {
|
|
3641
|
-
regex.lastIndex = 0;
|
|
3642
|
-
addEdit(normalizedFile, i + 1, lines[i].trim(), lines[i].replace(regex, new_name).trim(), 'text_search');
|
|
3643
|
-
astSearchEdits++;
|
|
3644
|
-
}
|
|
3645
|
-
}
|
|
3646
|
-
}
|
|
3647
|
-
catch (e) {
|
|
3648
|
-
logQueryError('rename:text-search-read', e);
|
|
3598
|
+
// Never downgrade a graph-classified file to text_search.
|
|
3599
|
+
if (!fileConfidence.has(normalizedFile)) {
|
|
3600
|
+
fileConfidence.set(normalizedFile, 'text_search');
|
|
3649
3601
|
}
|
|
3650
3602
|
}
|
|
3651
3603
|
}
|
|
3652
3604
|
catch (e) {
|
|
3653
3605
|
logQueryError('rename:ripgrep', e);
|
|
3654
3606
|
}
|
|
3655
|
-
//
|
|
3656
|
-
|
|
3657
|
-
|
|
3607
|
+
// Enumerate every `\boldName\b` line in each file to rewrite, so the previewed
|
|
3608
|
+
// file set is exactly the set the apply loop below rewrites. A file with no
|
|
3609
|
+
// matching line is dropped (apply would write nothing to it). `wordTest`
|
|
3610
|
+
// (non-global) probes each line; `wordReplace` (global) rewrites it and is
|
|
3611
|
+
// reused by the apply loop — compiled once each rather than once per line,
|
|
3612
|
+
// and one escaping formula serves both passes.
|
|
3613
|
+
const wordTest = new RegExp(`\\b${escapedOldName}\\b`);
|
|
3614
|
+
const wordReplace = new RegExp(`\\b${escapedOldName}\\b`, 'g');
|
|
3615
|
+
const changes = new Map();
|
|
3616
|
+
for (const [filePath, confidence] of fileConfidence) {
|
|
3617
|
+
try {
|
|
3618
|
+
const content = await fs.readFile(assertSafePath(filePath), 'utf-8');
|
|
3619
|
+
const lines = content.split('\n');
|
|
3620
|
+
const edits = [];
|
|
3621
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3622
|
+
if (!wordTest.test(lines[i])) {
|
|
3623
|
+
continue;
|
|
3624
|
+
}
|
|
3625
|
+
edits.push({
|
|
3626
|
+
line: i + 1,
|
|
3627
|
+
old_text: lines[i].trim(),
|
|
3628
|
+
new_text: lines[i].replace(wordReplace, new_name).trim(),
|
|
3629
|
+
confidence,
|
|
3630
|
+
});
|
|
3631
|
+
}
|
|
3632
|
+
if (edits.length > 0) {
|
|
3633
|
+
changes.set(filePath, { file_path: filePath, edits });
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
catch (e) {
|
|
3637
|
+
logQueryError('rename:enumerate', e);
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
// Step 4: Apply or preview.
|
|
3658
3641
|
const failedFiles = [];
|
|
3659
3642
|
if (!dry_run) {
|
|
3660
|
-
|
|
3661
|
-
for (const change of allChanges) {
|
|
3643
|
+
for (const change of changes.values()) {
|
|
3662
3644
|
try {
|
|
3663
3645
|
const fullPath = assertSafePath(change.file_path);
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
content = content.replace(regex, new_name);
|
|
3667
|
-
await fs.writeFile(fullPath, content, 'utf-8');
|
|
3646
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
3647
|
+
await fs.writeFile(fullPath, content.replace(wordReplace, new_name), 'utf-8');
|
|
3668
3648
|
}
|
|
3669
3649
|
catch (e) {
|
|
3670
|
-
// A swallowed write failure must not be reported as
|
|
3671
|
-
//
|
|
3672
|
-
// with the unwritten files listed, rather than masquerading as done.
|
|
3650
|
+
// A swallowed write failure must not be reported as success (#2283):
|
|
3651
|
+
// record the file so the result degrades to 'partial'.
|
|
3673
3652
|
logQueryError('rename:apply-edit', e);
|
|
3674
3653
|
failedFiles.push(change.file_path);
|
|
3675
3654
|
}
|
|
3676
3655
|
}
|
|
3656
|
+
// A file whose write threw did not land, so drop its edits from the
|
|
3657
|
+
// reported result — total_edits/changes must describe what actually
|
|
3658
|
+
// reached disk, not what was attempted (#2605: the report matches reality
|
|
3659
|
+
// even on partial failure). failed_files still names every dropped file.
|
|
3660
|
+
for (const f of failedFiles) {
|
|
3661
|
+
changes.delete(f);
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
// Counts derive from the reported set (dry-run: every enumerated file;
|
|
3665
|
+
// apply: only files that landed), so the graph/text_search split always
|
|
3666
|
+
// sums to total_edits and never overstates a partial apply.
|
|
3667
|
+
const reported = Array.from(changes.values());
|
|
3668
|
+
let graphEdits = 0;
|
|
3669
|
+
let astSearchEdits = 0;
|
|
3670
|
+
for (const change of reported) {
|
|
3671
|
+
for (const edit of change.edits) {
|
|
3672
|
+
if (edit.confidence === 'graph') {
|
|
3673
|
+
graphEdits++;
|
|
3674
|
+
}
|
|
3675
|
+
else {
|
|
3676
|
+
astSearchEdits++;
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3677
3679
|
}
|
|
3678
3680
|
return {
|
|
3679
3681
|
status: failedFiles.length > 0 ? 'partial' : 'success',
|
|
3680
3682
|
old_name: oldName,
|
|
3681
3683
|
new_name,
|
|
3682
|
-
files_affected:
|
|
3683
|
-
total_edits:
|
|
3684
|
+
files_affected: reported.length,
|
|
3685
|
+
total_edits: graphEdits + astSearchEdits,
|
|
3684
3686
|
graph_edits: graphEdits,
|
|
3685
3687
|
text_search_edits: astSearchEdits,
|
|
3686
|
-
changes:
|
|
3688
|
+
changes: reported,
|
|
3687
3689
|
applied: !dry_run,
|
|
3688
3690
|
...(failedFiles.length > 0 && { failed_files: failedFiles }),
|
|
3689
3691
|
};
|
package/package.json
CHANGED