vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
147 lines (146 loc) • 4.82 kB
JavaScript
export class StringSimilarity {
static fuzzyMatch(query, target, options = {}) {
const { caseSensitive = false, maxEditDistance = 2, threshold = 0.6 } = options;
if (!query || !target) {
return {
score: 0,
isMatch: false,
editDistance: Infinity,
matchType: 'none'
};
}
const q = caseSensitive ? query : query.toLowerCase();
const t = caseSensitive ? target : target.toLowerCase();
if (q === t) {
return {
score: 1.0,
isMatch: true,
editDistance: 0,
matchType: 'exact'
};
}
if (t.includes(q)) {
const ratio = q.length / t.length;
const score = 0.8 + (ratio * 0.2);
return {
score,
isMatch: score >= threshold,
editDistance: t.length - q.length,
matchType: 'substring'
};
}
const editDistance = this.levenshteinDistance(q, t);
if (editDistance > maxEditDistance) {
return {
score: 0,
isMatch: false,
editDistance,
matchType: 'none'
};
}
const maxLength = Math.max(q.length, t.length);
let similarity = 1 - (editDistance / maxLength);
let prefixBonus = 0;
const minLength = Math.min(q.length, t.length);
for (let i = 0; i < minLength; i++) {
if (q[i] === t[i]) {
prefixBonus += 0.1;
}
else {
break;
}
}
let suffixBonus = 0;
for (let i = 1; i <= minLength; i++) {
if (q[q.length - i] === t[t.length - i]) {
suffixBonus += 0.05;
}
else {
break;
}
}
similarity = Math.min(similarity + prefixBonus + suffixBonus, 0.79);
return {
score: similarity,
isMatch: similarity >= threshold,
editDistance,
matchType: similarity > 0 ? 'fuzzy' : 'none'
};
}
static isTypoMatch(query, target, options = {}) {
const result = this.fuzzyMatch(query, target, {
threshold: 0.6,
maxEditDistance: 2,
...options
});
if (!result.isMatch && result.editDistance <= 2) {
if (this.hasTransposition(query, target)) {
return true;
}
if (this.hasDoubledChar(query, target)) {
return true;
}
}
return result.isMatch;
}
static levenshteinDistance(a, b) {
if (a === b)
return 0;
if (a.length === 0)
return b.length;
if (b.length === 0)
return a.length;
let previousRow = Array(b.length + 1).fill(0).map((_, i) => i);
for (let i = 0; i < a.length; i++) {
const currentRow = [i + 1];
for (let j = 0; j < b.length; j++) {
const insertCost = currentRow[j] + 1;
const deleteCost = previousRow[j + 1] + 1;
const replaceCost = previousRow[j] + (a[i] !== b[j] ? 1 : 0);
currentRow.push(Math.min(insertCost, deleteCost, replaceCost));
}
previousRow = currentRow;
}
return previousRow[b.length];
}
static hasTransposition(a, b) {
if (Math.abs(a.length - b.length) !== 0)
return false;
let differences = 0;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
differences++;
if (differences > 2)
return false;
if (i + 1 < a.length &&
a[i] === b[i + 1] &&
a[i + 1] === b[i]) {
i++;
}
}
}
return differences === 2;
}
static hasDoubledChar(a, b) {
const longer = a.length > b.length ? a : b;
const shorter = a.length > b.length ? b : a;
if (longer.length - shorter.length !== 1)
return false;
let shorterIndex = 0;
let differences = 0;
for (let i = 0; i < longer.length; i++) {
if (shorterIndex < shorter.length && longer[i] === shorter[shorterIndex]) {
shorterIndex++;
}
else {
differences++;
if (differences > 1)
return false;
}
}
return shorterIndex === shorter.length && differences === 1;
}
static similarity(a, b) {
return this.fuzzyMatch(a, b).score;
}
}