mcp-code-context 2.1.3 → 2.3.1
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/.io/RULES.md +12 -0
- package/CHANGELOG.md +118 -58
- package/CONTRIBUTING.md +243 -0
- package/README.md +58 -7
- package/RELEASE_NOTES_v2.2.0.md +286 -0
- package/RELEASE_NOTES_v2.3.0.md +228 -0
- package/RELEASE_NOTES_v2.3.1.md +110 -0
- package/dist/src/ast/writers/safeRename.d.ts +14 -0
- package/dist/src/ast/writers/safeRename.js +258 -0
- package/dist/src/ast/writers/safeRename.js.map +1 -0
- package/dist/src/ast/writers/symbolWriter.d.ts +2 -0
- package/dist/src/ast/writers/symbolWriter.js +9 -14
- package/dist/src/ast/writers/symbolWriter.js.map +1 -1
- package/dist/src/cache/astCache.d.ts +33 -0
- package/dist/src/cache/astCache.js +113 -0
- package/dist/src/cache/astCache.js.map +1 -0
- package/dist/src/handlers/readHandlers.d.ts +70 -0
- package/dist/src/handlers/readHandlers.js +431 -0
- package/dist/src/handlers/readHandlers.js.map +1 -0
- package/dist/src/handlers/utilHandlers.d.ts +31 -0
- package/dist/src/handlers/utilHandlers.js +69 -0
- package/dist/src/handlers/utilHandlers.js.map +1 -0
- package/dist/src/handlers/writeHandlers.d.ts +57 -0
- package/dist/src/handlers/writeHandlers.js +475 -0
- package/dist/src/handlers/writeHandlers.js.map +1 -0
- package/dist/src/index.d.ts +6 -107
- package/dist/src/index.js +147 -965
- package/dist/src/index.js.map +1 -1
- package/dist/src/tools/readFileLines.d.ts +26 -0
- package/dist/src/tools/readFileLines.js +77 -0
- package/dist/src/tools/readFileLines.js.map +1 -0
- package/dist/src/tools/searchCodePattern.d.ts +32 -0
- package/dist/src/tools/searchCodePattern.js +80 -0
- package/dist/src/tools/searchCodePattern.js.map +1 -0
- package/dist/src/utils/arrayUtils.d.ts +12 -0
- package/dist/src/utils/arrayUtils.js +27 -0
- package/dist/src/utils/arrayUtils.js.map +1 -0
- package/dist/src/utils/backupManager.d.ts +13 -6
- package/dist/src/utils/backupManager.js +73 -8
- package/dist/src/utils/backupManager.js.map +1 -1
- package/dist/src/utils/constants.d.ts +46 -0
- package/dist/src/utils/constants.js +112 -0
- package/dist/src/utils/constants.js.map +1 -0
- package/dist/src/utils/ignoreManager.d.ts +8 -0
- package/dist/src/utils/ignoreManager.js +49 -0
- package/dist/src/utils/ignoreManager.js.map +1 -1
- package/dist/src/utils/normalization.d.ts +49 -0
- package/dist/src/utils/normalization.js +173 -0
- package/dist/src/utils/normalization.js.map +1 -0
- package/dist/src/utils/projectRoot.d.ts +17 -0
- package/dist/src/utils/projectRoot.js +42 -0
- package/dist/src/utils/projectRoot.js.map +1 -0
- package/dist/src/utils/secureValidation.d.ts +15 -0
- package/dist/src/utils/secureValidation.js +41 -0
- package/dist/src/utils/secureValidation.js.map +1 -0
- package/dist/src/utils/transactionManager.d.ts +45 -0
- package/dist/src/utils/transactionManager.js +102 -0
- package/dist/src/utils/transactionManager.js.map +1 -0
- package/dist/src/utils/validation.d.ts +87 -0
- package/dist/src/utils/validation.js +231 -0
- package/dist/src/utils/validation.js.map +1 -0
- package/docs/ARCHITECTURE.md +300 -0
- package/docs/architecture/ADR-001-modular-handlers.md +119 -0
- package/llms.txt +62 -7
- package/package.json +5 -5
- package/AGENT_INSTRUCTIONS.md +0 -135
- package/DEPLOYMENT_INSTRUCTIONS.md +0 -110
- package/dist/src/tests/test-confirmation-flow.d.ts +0 -1
- package/dist/src/tests/test-confirmation-flow.js +0 -43
- package/dist/src/tests/test-confirmation-flow.js.map +0 -1
- package/dist/tests/debug-replace.d.ts +0 -1
- package/dist/tests/debug-replace.js +0 -17
- package/dist/tests/debug-replace.js.map +0 -1
- package/dist/tests/test-confirmation-flow.d.ts +0 -1
- package/dist/tests/test-confirmation-flow.js +0 -43
- package/dist/tests/test-confirmation-flow.js.map +0 -1
- package/dist/tests/test-dart.d.ts +0 -1
- package/dist/tests/test-dart.js +0 -242
- package/dist/tests/test-dart.js.map +0 -1
- package/dist/tests/test-php-ast.d.ts +0 -1
- package/dist/tests/test-php-ast.js +0 -152
- package/dist/tests/test-php-ast.js.map +0 -1
- package/dist/tests/test-php.d.ts +0 -1
- package/dist/tests/test-php.js +0 -234
- package/dist/tests/test-php.js.map +0 -1
- package/dist/tests/test-smoke.d.ts +0 -5
- package/dist/tests/test-smoke.js +0 -83
- package/dist/tests/test-smoke.js.map +0 -1
- package/dist/tests/writers/test-crlf-handling.d.ts +0 -4
- package/dist/tests/writers/test-crlf-handling.js +0 -73
- package/dist/tests/writers/test-crlf-handling.js.map +0 -1
- package/dist/tests/writers/test-write-smoke.d.ts +0 -9
- package/dist/tests/writers/test-write-smoke.js +0 -346
- package/dist/tests/writers/test-write-smoke.js.map +0 -1
- package/src/ast/dartCompressor.ts +0 -1013
- package/src/ast/phpCompressor.ts +0 -814
- package/src/ast/semanticCompressor.ts +0 -808
- package/src/ast/writers/dartWriter.ts +0 -648
- package/src/ast/writers/phpWriter.ts +0 -508
- package/src/ast/writers/pyWriter.ts +0 -446
- package/src/ast/writers/symbolWriter.ts +0 -277
- package/src/ast/writers/tsWriter.ts +0 -418
- package/src/index.ts +0 -1553
- package/src/utils/backupManager.ts +0 -66
- package/src/utils/confirmationCache.ts +0 -61
- package/src/utils/diffEngine.ts +0 -223
- package/src/utils/fuzzyMatch.ts +0 -86
- package/src/utils/ignoreManager.ts +0 -144
- package/tests/test-confirmation-flow.ts +0 -49
- package/tests/test-dart.d.ts +0 -1
- package/tests/test-dart.js +0 -209
- package/tests/test-dart.js.map +0 -1
- package/tests/test-dart.ts +0 -265
- package/tests/test-php-ast.d.ts +0 -1
- package/tests/test-php-ast.js +0 -152
- package/tests/test-php-ast.js.map +0 -1
- package/tests/test-php-ast.ts +0 -155
- package/tests/test-php.d.ts +0 -1
- package/tests/test-php.js +0 -234
- package/tests/test-php.js.map +0 -1
- package/tests/test-php.ts +0 -261
- package/tests/test-smoke.d.ts +0 -5
- package/tests/test-smoke.js +0 -83
- package/tests/test-smoke.js.map +0 -1
- package/tests/test-smoke.ts +0 -93
- package/tests/writers/test-crlf-handling.ts +0 -100
- package/tests/writers/test-write-smoke.ts +0 -568
- package/tsconfig.json +0 -19
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* backupManager.ts — File Backup & Restore Utility
|
|
3
|
-
*
|
|
4
|
-
* Provides opt-in backup functionality for surgical write operations.
|
|
5
|
-
* Creates `.bak` files in the same directory as the source file.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* - Before modifying: createBackup(filePath) → returns backup path
|
|
9
|
-
* - On failure: restoreBackup(filePath) → restores from .bak
|
|
10
|
-
* - On success: cleanBackup(filePath) → removes .bak
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import * as fs from "node:fs";
|
|
14
|
-
import * as path from "node:path";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Create a backup copy of a file before modifying it.
|
|
18
|
-
* Keeps a rolling window of the last 5 backups per file in a hidden directory.
|
|
19
|
-
*/
|
|
20
|
-
export function createBackup(filePath: string): string {
|
|
21
|
-
if (!fs.existsSync(filePath)) return "";
|
|
22
|
-
|
|
23
|
-
const parsed = path.parse(filePath);
|
|
24
|
-
const backupDir = path.join(parsed.dir, ".mcp-backups");
|
|
25
|
-
|
|
26
|
-
if (!fs.existsSync(backupDir)) {
|
|
27
|
-
fs.mkdirSync(backupDir, { recursive: true });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Push existing backups down the stack (max 5)
|
|
31
|
-
for (let i = 4; i >= 1; i--) {
|
|
32
|
-
const oldB = path.join(backupDir, `${parsed.base}.${i}.backup`);
|
|
33
|
-
const newB = path.join(backupDir, `${parsed.base}.${i + 1}.backup`);
|
|
34
|
-
if (fs.existsSync(oldB)) {
|
|
35
|
-
if (i === 4 && fs.existsSync(newB)) {
|
|
36
|
-
fs.unlinkSync(newB); // Drop the 5th one to make room
|
|
37
|
-
}
|
|
38
|
-
fs.renameSync(oldB, newB);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const backupPath = path.join(backupDir, `${parsed.base}.1.backup`);
|
|
43
|
-
fs.copyFileSync(filePath, backupPath);
|
|
44
|
-
return backupPath;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Restore a file from its `.bak` backup.
|
|
49
|
-
*/
|
|
50
|
-
export function restoreBackup(filePath: string, steps = 1): boolean {
|
|
51
|
-
const parsed = path.parse(filePath);
|
|
52
|
-
const backupDir = path.join(parsed.dir, ".mcp-backups");
|
|
53
|
-
const targetBackup = path.join(backupDir, `${parsed.base}.${steps}.backup`);
|
|
54
|
-
|
|
55
|
-
if (!fs.existsSync(targetBackup)) return false;
|
|
56
|
-
|
|
57
|
-
fs.copyFileSync(targetBackup, filePath);
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Clean legacy backups.
|
|
63
|
-
*/
|
|
64
|
-
export function cleanBackup(filePath: string): void {
|
|
65
|
-
// No-op. We keep the rolling 5 versions instead of deleting.
|
|
66
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
|
|
3
|
-
export interface PendingOperation {
|
|
4
|
-
token: string;
|
|
5
|
-
operation: "write_file_surgical" | "insert_symbol" | "rename_symbol" | "remove_symbol";
|
|
6
|
-
filePath: string;
|
|
7
|
-
payload: any; // Contains specific args needed to re-execute or apply
|
|
8
|
-
expiresAt: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const CACHE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
12
|
-
|
|
13
|
-
class ConfirmationCacheManager {
|
|
14
|
-
private cache = new Map<string, PendingOperation>();
|
|
15
|
-
|
|
16
|
-
public add(
|
|
17
|
-
operation: PendingOperation["operation"],
|
|
18
|
-
filePath: string,
|
|
19
|
-
payload: any
|
|
20
|
-
): string {
|
|
21
|
-
const token = crypto.randomUUID();
|
|
22
|
-
|
|
23
|
-
this.cache.set(token, {
|
|
24
|
-
token,
|
|
25
|
-
operation,
|
|
26
|
-
filePath,
|
|
27
|
-
payload,
|
|
28
|
-
expiresAt: Date.now() + CACHE_TIMEOUT_MS,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Clean up expired items lazily
|
|
32
|
-
this.cleanup();
|
|
33
|
-
|
|
34
|
-
return token;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
public get(token: string): PendingOperation | undefined {
|
|
38
|
-
this.cleanup();
|
|
39
|
-
return this.cache.get(token);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
public consume(token: string): PendingOperation | undefined {
|
|
43
|
-
this.cleanup();
|
|
44
|
-
const op = this.cache.get(token);
|
|
45
|
-
if (op) {
|
|
46
|
-
this.cache.delete(token);
|
|
47
|
-
}
|
|
48
|
-
return op;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private cleanup() {
|
|
52
|
-
const now = Date.now();
|
|
53
|
-
for (const [key, value] of this.cache.entries()) {
|
|
54
|
-
if (now > value.expiresAt) {
|
|
55
|
-
this.cache.delete(key);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const confirmationCache = new ConfirmationCacheManager();
|
package/src/utils/diffEngine.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* diffEngine.ts — Unified Diff Generator
|
|
3
|
-
*
|
|
4
|
-
* Generates human-readable unified diffs (like `git diff`) for dry-run reports
|
|
5
|
-
* and post-write summaries. Uses a simple Myers-style diff algorithm.
|
|
6
|
-
* No external dependencies.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// ─── Types ──────────────────────────────────────────────────────────
|
|
10
|
-
|
|
11
|
-
interface DiffHunk {
|
|
12
|
-
oldStart: number;
|
|
13
|
-
oldCount: number;
|
|
14
|
-
newStart: number;
|
|
15
|
-
newCount: number;
|
|
16
|
-
lines: string[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// ─── Public API ─────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Generate a unified diff between two versions of a single file.
|
|
23
|
-
* Returns a string formatted like `git diff --unified=3`.
|
|
24
|
-
*/
|
|
25
|
-
export function generateDiff(
|
|
26
|
-
oldContent: string,
|
|
27
|
-
newContent: string,
|
|
28
|
-
filePath: string,
|
|
29
|
-
contextLines = 3,
|
|
30
|
-
): string {
|
|
31
|
-
const oldLines = oldContent.split("\n");
|
|
32
|
-
const newLines = newContent.split("\n");
|
|
33
|
-
|
|
34
|
-
const hunks = computeHunks(oldLines, newLines, contextLines);
|
|
35
|
-
|
|
36
|
-
if (hunks.length === 0) {
|
|
37
|
-
return `--- ${filePath}\n+++ ${filePath}\n(no changes)\n`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const header = [
|
|
41
|
-
`--- a/${normalizedPath(filePath)}`,
|
|
42
|
-
`+++ b/${normalizedPath(filePath)}`,
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
const hunkTexts = hunks.map((hunk) => {
|
|
46
|
-
const hunkHeader = `@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@`;
|
|
47
|
-
return [hunkHeader, ...hunk.lines].join("\n");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
return [...header, ...hunkTexts].join("\n") + "\n";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Generate a combined diff across multiple files.
|
|
55
|
-
* Returns a single string with all file diffs separated by blank lines.
|
|
56
|
-
*/
|
|
57
|
-
export function generateMultiFileDiff(
|
|
58
|
-
changes: Array<{
|
|
59
|
-
filePath: string;
|
|
60
|
-
oldContent: string;
|
|
61
|
-
newContent: string;
|
|
62
|
-
}>,
|
|
63
|
-
): string {
|
|
64
|
-
const diffs: string[] = [];
|
|
65
|
-
|
|
66
|
-
for (const change of changes) {
|
|
67
|
-
const diff = generateDiff(
|
|
68
|
-
change.oldContent,
|
|
69
|
-
change.newContent,
|
|
70
|
-
change.filePath,
|
|
71
|
-
);
|
|
72
|
-
diffs.push(diff);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return diffs.join("\n");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ─── Diff Algorithm ─────────────────────────────────────────────────
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Compute the Longest Common Subsequence table for two arrays of strings.
|
|
82
|
-
*/
|
|
83
|
-
function computeLCS(a: string[], b: string[]): number[][] {
|
|
84
|
-
const m = a.length;
|
|
85
|
-
const n = b.length;
|
|
86
|
-
|
|
87
|
-
// Use a 2D array for the LCS table
|
|
88
|
-
const dp: number[][] = Array.from({ length: m + 1 }, () =>
|
|
89
|
-
new Array(n + 1).fill(0),
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
for (let i = 1; i <= m; i++) {
|
|
93
|
-
for (let j = 1; j <= n; j++) {
|
|
94
|
-
if (a[i - 1] === b[j - 1]) {
|
|
95
|
-
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
96
|
-
} else {
|
|
97
|
-
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return dp;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Backtrack through the LCS table to produce a list of diff operations.
|
|
107
|
-
*/
|
|
108
|
-
function backtrackDiff(
|
|
109
|
-
dp: number[][],
|
|
110
|
-
a: string[],
|
|
111
|
-
b: string[],
|
|
112
|
-
): Array<{ type: "keep" | "remove" | "add"; line: string }> {
|
|
113
|
-
const result: Array<{ type: "keep" | "remove" | "add"; line: string }> = [];
|
|
114
|
-
let i = a.length;
|
|
115
|
-
let j = b.length;
|
|
116
|
-
|
|
117
|
-
while (i > 0 || j > 0) {
|
|
118
|
-
if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
|
|
119
|
-
result.unshift({ type: "keep", line: a[i - 1] });
|
|
120
|
-
i--;
|
|
121
|
-
j--;
|
|
122
|
-
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
123
|
-
result.unshift({ type: "add", line: b[j - 1] });
|
|
124
|
-
j--;
|
|
125
|
-
} else if (i > 0) {
|
|
126
|
-
result.unshift({ type: "remove", line: a[i - 1] });
|
|
127
|
-
i--;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return result;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Group diff operations into hunks with surrounding context.
|
|
136
|
-
*/
|
|
137
|
-
function computeHunks(
|
|
138
|
-
oldLines: string[],
|
|
139
|
-
newLines: string[],
|
|
140
|
-
contextLines: number,
|
|
141
|
-
): DiffHunk[] {
|
|
142
|
-
const dp = computeLCS(oldLines, newLines);
|
|
143
|
-
const ops = backtrackDiff(dp, oldLines, newLines);
|
|
144
|
-
|
|
145
|
-
// Find ranges of changes (non-"keep" operations)
|
|
146
|
-
const changeIndices: number[] = [];
|
|
147
|
-
for (let i = 0; i < ops.length; i++) {
|
|
148
|
-
if (ops[i].type !== "keep") {
|
|
149
|
-
changeIndices.push(i);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (changeIndices.length === 0) return [];
|
|
154
|
-
|
|
155
|
-
// Group nearby changes into hunks
|
|
156
|
-
const groups: Array<{ start: number; end: number }> = [];
|
|
157
|
-
let groupStart = changeIndices[0];
|
|
158
|
-
let groupEnd = changeIndices[0];
|
|
159
|
-
|
|
160
|
-
for (let k = 1; k < changeIndices.length; k++) {
|
|
161
|
-
if (changeIndices[k] - groupEnd <= contextLines * 2 + 1) {
|
|
162
|
-
// Merge into current group
|
|
163
|
-
groupEnd = changeIndices[k];
|
|
164
|
-
} else {
|
|
165
|
-
groups.push({ start: groupStart, end: groupEnd });
|
|
166
|
-
groupStart = changeIndices[k];
|
|
167
|
-
groupEnd = changeIndices[k];
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
groups.push({ start: groupStart, end: groupEnd });
|
|
171
|
-
|
|
172
|
-
// Build hunks with context
|
|
173
|
-
const hunks: DiffHunk[] = [];
|
|
174
|
-
|
|
175
|
-
for (const group of groups) {
|
|
176
|
-
const hunkStart = Math.max(0, group.start - contextLines);
|
|
177
|
-
const hunkEnd = Math.min(ops.length - 1, group.end + contextLines);
|
|
178
|
-
|
|
179
|
-
const lines: string[] = [];
|
|
180
|
-
let oldLineNum = 0;
|
|
181
|
-
let newLineNum = 0;
|
|
182
|
-
|
|
183
|
-
// Count lines before hunk start to get correct line numbers
|
|
184
|
-
for (let i = 0; i < hunkStart; i++) {
|
|
185
|
-
if (ops[i].type === "keep" || ops[i].type === "remove") oldLineNum++;
|
|
186
|
-
if (ops[i].type === "keep" || ops[i].type === "add") newLineNum++;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const oldStart = oldLineNum + 1;
|
|
190
|
-
const newStart = newLineNum + 1;
|
|
191
|
-
let oldCount = 0;
|
|
192
|
-
let newCount = 0;
|
|
193
|
-
|
|
194
|
-
for (let i = hunkStart; i <= hunkEnd; i++) {
|
|
195
|
-
const op = ops[i];
|
|
196
|
-
switch (op.type) {
|
|
197
|
-
case "keep":
|
|
198
|
-
lines.push(` ${op.line}`);
|
|
199
|
-
oldCount++;
|
|
200
|
-
newCount++;
|
|
201
|
-
break;
|
|
202
|
-
case "remove":
|
|
203
|
-
lines.push(`-${op.line}`);
|
|
204
|
-
oldCount++;
|
|
205
|
-
break;
|
|
206
|
-
case "add":
|
|
207
|
-
lines.push(`+${op.line}`);
|
|
208
|
-
newCount++;
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
hunks.push({ oldStart, oldCount, newStart, newCount, lines });
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return hunks;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ─── Utilities ──────────────────────────────────────────────────────
|
|
220
|
-
|
|
221
|
-
function normalizedPath(filePath: string): string {
|
|
222
|
-
return filePath.replace(/\\/g, "/");
|
|
223
|
-
}
|
package/src/utils/fuzzyMatch.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
export interface SymbolInfo {
|
|
2
|
-
name: string;
|
|
3
|
-
type: string;
|
|
4
|
-
className?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function levenshteinDistance(a: string, b: string): number {
|
|
8
|
-
const matrix = [];
|
|
9
|
-
let i, j;
|
|
10
|
-
|
|
11
|
-
if (a.length === 0) return b.length;
|
|
12
|
-
if (b.length === 0) return a.length;
|
|
13
|
-
|
|
14
|
-
for (i = 0; i <= b.length; i++) {
|
|
15
|
-
matrix[i] = [i];
|
|
16
|
-
}
|
|
17
|
-
for (j = 0; j <= a.length; j++) {
|
|
18
|
-
matrix[0][j] = j;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
for (i = 1; i <= b.length; i++) {
|
|
22
|
-
for (j = 1; j <= a.length; j++) {
|
|
23
|
-
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
24
|
-
matrix[i][j] = matrix[i - 1][j - 1];
|
|
25
|
-
} else {
|
|
26
|
-
matrix[i][j] = Math.min(
|
|
27
|
-
matrix[i - 1][j - 1] + 1,
|
|
28
|
-
Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return matrix[b.length][a.length];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function findClosestSymbols(target: string, symbols: SymbolInfo[], limit = 5): SymbolInfo[] {
|
|
38
|
-
return symbols
|
|
39
|
-
.map((sym) => ({
|
|
40
|
-
...sym,
|
|
41
|
-
distance: levenshteinDistance(target.toLowerCase(), sym.name.toLowerCase()),
|
|
42
|
-
}))
|
|
43
|
-
.sort((a, b) => a.distance - b.distance)
|
|
44
|
-
.slice(0, limit);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// A simple regex-based extractor to get available symbols when the AST parsing fails
|
|
48
|
-
export function extractFallbackSymbols(content: string): SymbolInfo[] {
|
|
49
|
-
const symbols: SymbolInfo[] = [];
|
|
50
|
-
const lines = content.split('\\n');
|
|
51
|
-
|
|
52
|
-
let currentClass: string | undefined = undefined;
|
|
53
|
-
|
|
54
|
-
for (let i = 0; i < lines.length; i++) {
|
|
55
|
-
const line = lines[i].trim();
|
|
56
|
-
|
|
57
|
-
// Check for class definitions (multi-language approx)
|
|
58
|
-
const classMatch = line.match(/^\s*(?:export\s+|abstract\s+|final\s+)?class\s+([_a-zA-Z0-9]+)/);
|
|
59
|
-
if (classMatch) {
|
|
60
|
-
currentClass = classMatch[1];
|
|
61
|
-
symbols.push({ name: currentClass, type: 'class' });
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Check for function / method definitions
|
|
66
|
-
// JS/TS: function foo() | foo() { | async foo()
|
|
67
|
-
// Python: def foo()
|
|
68
|
-
// PHP: function foo()
|
|
69
|
-
// Dart: type foo()
|
|
70
|
-
const funcMatch = line.match(/^\s*(?:export\s+|async\s+|public\s+|private\s+|protected\s+|static\s+|def\s+|function\s+)*([_a-zA-Z0-9]+)\s*\(/);
|
|
71
|
-
if (funcMatch && !['if', 'else', 'for', 'while', 'switch', 'catch'].includes(funcMatch[1])) {
|
|
72
|
-
symbols.push({
|
|
73
|
-
name: funcMatch[1],
|
|
74
|
-
type: currentClass ? 'method' : 'function',
|
|
75
|
-
className: currentClass,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Basic assumption: closing brace at start of line might end a class
|
|
80
|
-
if (line === '}') {
|
|
81
|
-
currentClass = undefined;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return symbols;
|
|
86
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IgnoreManager — Reads .gitignore / .repomixignore and walks directories
|
|
3
|
-
* filtering out ignored paths. Always excludes common heavy directories.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as fs from "node:fs";
|
|
7
|
-
import * as path from "node:path";
|
|
8
|
-
import { createRequire } from "node:module";
|
|
9
|
-
import type { Ignore } from "ignore";
|
|
10
|
-
|
|
11
|
-
// CJS interop: 'ignore' uses module.exports which doesn't resolve
|
|
12
|
-
// as a callable default export under Node16 module resolution.
|
|
13
|
-
const require = createRequire(import.meta.url);
|
|
14
|
-
const ignore: (opts?: Record<string, unknown>) => Ignore = require("ignore");
|
|
15
|
-
|
|
16
|
-
/** Directories and patterns that are ALWAYS excluded regardless of .gitignore */
|
|
17
|
-
const ALWAYS_IGNORE: string[] = [
|
|
18
|
-
"node_modules",
|
|
19
|
-
".git",
|
|
20
|
-
"dist",
|
|
21
|
-
"build",
|
|
22
|
-
"vendor",
|
|
23
|
-
".next",
|
|
24
|
-
".nuxt",
|
|
25
|
-
".output",
|
|
26
|
-
"__pycache__",
|
|
27
|
-
".pytest_cache",
|
|
28
|
-
"coverage",
|
|
29
|
-
".nyc_output",
|
|
30
|
-
".turbo",
|
|
31
|
-
".cache",
|
|
32
|
-
".parcel-cache",
|
|
33
|
-
".venv",
|
|
34
|
-
"venv",
|
|
35
|
-
"env",
|
|
36
|
-
".env.local",
|
|
37
|
-
".tsbuildinfo",
|
|
38
|
-
"*.lock",
|
|
39
|
-
"package-lock.json",
|
|
40
|
-
"yarn.lock",
|
|
41
|
-
"pnpm-lock.yaml",
|
|
42
|
-
"bun.lockb",
|
|
43
|
-
"*.min.js",
|
|
44
|
-
"*.min.css",
|
|
45
|
-
"*.map",
|
|
46
|
-
"*.chunk.js",
|
|
47
|
-
"*.bundle.js",
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
/** Max file size to read (512 KB). Files larger than this are skipped. */
|
|
51
|
-
const MAX_FILE_SIZE_BYTES = 512 * 1024;
|
|
52
|
-
|
|
53
|
-
export class IgnoreManager {
|
|
54
|
-
private ig: Ignore;
|
|
55
|
-
private rootDir: string;
|
|
56
|
-
|
|
57
|
-
constructor(rootDir: string) {
|
|
58
|
-
this.rootDir = path.resolve(rootDir);
|
|
59
|
-
this.ig = ignore();
|
|
60
|
-
|
|
61
|
-
// Load hardcoded ignores first
|
|
62
|
-
this.ig.add(ALWAYS_IGNORE);
|
|
63
|
-
|
|
64
|
-
// Load .gitignore
|
|
65
|
-
this.loadIgnoreFile(path.join(this.rootDir, ".gitignore"));
|
|
66
|
-
|
|
67
|
-
// Load .repomixignore (Repomix-style overrides)
|
|
68
|
-
this.loadIgnoreFile(path.join(this.rootDir, ".repomixignore"));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Reads an ignore-pattern file and adds its rules to the filter.
|
|
73
|
-
* Silently skips if the file doesn't exist or is unreadable.
|
|
74
|
-
*/
|
|
75
|
-
private loadIgnoreFile(filePath: string): void {
|
|
76
|
-
try {
|
|
77
|
-
if (!fs.existsSync(filePath)) return;
|
|
78
|
-
|
|
79
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
80
|
-
const patterns = content
|
|
81
|
-
.split("\n")
|
|
82
|
-
.map((line) => line.trim())
|
|
83
|
-
.filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
84
|
-
|
|
85
|
-
if (patterns.length > 0) {
|
|
86
|
-
this.ig.add(patterns);
|
|
87
|
-
}
|
|
88
|
-
} catch {
|
|
89
|
-
// File unreadable — skip silently
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Check if a relative path should be ignored. */
|
|
94
|
-
public isIgnored(relativePath: string): boolean {
|
|
95
|
-
if (!relativePath) return false;
|
|
96
|
-
return this.ig.ignores(relativePath);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Recursively walk a directory, returning absolute paths to all
|
|
101
|
-
* non-ignored files.
|
|
102
|
-
*/
|
|
103
|
-
public walkDirectory(dir?: string): string[] {
|
|
104
|
-
const baseDir = dir ? path.resolve(dir) : this.rootDir;
|
|
105
|
-
const files: string[] = [];
|
|
106
|
-
this.walk(baseDir, files);
|
|
107
|
-
return files;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private walk(currentDir: string, collected: string[]): void {
|
|
111
|
-
let entries: fs.Dirent[];
|
|
112
|
-
try {
|
|
113
|
-
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
114
|
-
} catch {
|
|
115
|
-
return; // Permission denied, broken symlink, etc.
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
for (const entry of entries) {
|
|
119
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
120
|
-
const relativePath = path
|
|
121
|
-
.relative(this.rootDir, fullPath)
|
|
122
|
-
.replace(/\\/g, "/");
|
|
123
|
-
|
|
124
|
-
// Check ignore rules
|
|
125
|
-
if (this.isIgnored(relativePath)) continue;
|
|
126
|
-
|
|
127
|
-
if (entry.isDirectory()) {
|
|
128
|
-
// Also test with trailing slash for directory-level patterns
|
|
129
|
-
if (this.isIgnored(relativePath + "/")) continue;
|
|
130
|
-
this.walk(fullPath, collected);
|
|
131
|
-
} else if (entry.isFile()) {
|
|
132
|
-
// Skip files that are too large
|
|
133
|
-
try {
|
|
134
|
-
const stat = fs.statSync(fullPath);
|
|
135
|
-
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
136
|
-
} catch {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
collected.push(fullPath);
|
|
140
|
-
}
|
|
141
|
-
// Symlinks and other entries are silently skipped
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import { handleWriteFileSurgical, handleRollbackFile } from '../src/index.js';
|
|
4
|
-
|
|
5
|
-
async function testWorkflow() {
|
|
6
|
-
const testFile = path.resolve('test-artifact.ts');
|
|
7
|
-
const initialContent = 'export class Old { build() { return 1; } }';
|
|
8
|
-
fs.writeFileSync(testFile, initialContent, 'utf-8');
|
|
9
|
-
|
|
10
|
-
console.log('--- Step 1: Preview (Dry-Run) ---');
|
|
11
|
-
const previewArgs = {
|
|
12
|
-
filePath: testFile,
|
|
13
|
-
symbolName: 'build',
|
|
14
|
-
newContent: ' build() { return "new value"; }',
|
|
15
|
-
className: 'Old'
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const p1 = await handleWriteFileSurgical(previewArgs);
|
|
19
|
-
const p1Text = p1.content[0].text;
|
|
20
|
-
console.log('Preview Result:', p1Text.split('\n')[0]);
|
|
21
|
-
|
|
22
|
-
const tokenMatch = p1Text.match(/confirmationToken: "([^"]+)"/);
|
|
23
|
-
if (!tokenMatch) throw new Error('No token found');
|
|
24
|
-
const token = tokenMatch[1];
|
|
25
|
-
console.log('Token found:', token);
|
|
26
|
-
|
|
27
|
-
console.log('\n--- Step 2: Confirm ---');
|
|
28
|
-
const confirmArgs = {
|
|
29
|
-
...previewArgs,
|
|
30
|
-
confirmationToken: token,
|
|
31
|
-
confirm: true
|
|
32
|
-
};
|
|
33
|
-
const p2 = await handleWriteFileSurgical(confirmArgs);
|
|
34
|
-
console.log('Confirm Result:', p2.content[0].text.split('\n')[0]);
|
|
35
|
-
console.log('File Content:', fs.readFileSync(testFile, 'utf-8'));
|
|
36
|
-
|
|
37
|
-
console.log('\n--- Step 3: Rollback ---');
|
|
38
|
-
await handleRollbackFile({ filePath: testFile, steps: 1 });
|
|
39
|
-
console.log('After Rollback:', fs.readFileSync(testFile, 'utf-8'));
|
|
40
|
-
|
|
41
|
-
// Clean up
|
|
42
|
-
fs.unlinkSync(testFile);
|
|
43
|
-
const backupDir = path.join(path.dirname(testFile), '.mcp-backups');
|
|
44
|
-
if (fs.existsSync(backupDir)) {
|
|
45
|
-
fs.rmSync(backupDir, { recursive: true, force: true });
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
testWorkflow().catch(console.error);
|
package/tests/test-dart.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|