mcp-code-context 2.2.0 → 2.4.0
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 +114 -1
- package/README.md +28 -1
- 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 -143
- package/dist/src/index.js +77 -1091
- package/dist/src/index.js.map +1 -1
- package/dist/src/tools/searchCodePattern.d.ts +7 -2
- package/dist/src/tools/searchCodePattern.js +68 -4
- package/dist/src/tools/searchCodePattern.js.map +1 -1
- 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/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 +12 -5
- package/dist/src/utils/projectRoot.js +22 -20
- package/dist/src/utils/projectRoot.js.map +1 -1
- 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 +10 -1
- package/package.json +5 -5
- package/AGENT_INSTRUCTIONS.md +0 -135
- package/DEPLOYMENT_INSTRUCTIONS.md +0 -110
- package/MEJORAS_PROPUESTAS.md +0 -324
- package/RELEASE_NOTES_v2.1.3.md +0 -78
- package/RELEASE_NOTES_v2.2.0.md +0 -286
- 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-backup-system.d.ts +0 -7
- package/dist/tests/test-backup-system.js +0 -152
- package/dist/tests/test-backup-system.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-new-tools.d.ts +0 -4
- package/dist/tests/test-new-tools.js +0 -147
- package/dist/tests/test-new-tools.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 -1784
- package/src/tools/readFileLines.ts +0 -104
- package/src/tools/searchCodePattern.ts +0 -129
- package/src/utils/backupManager.ts +0 -141
- 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/src/utils/projectRoot.ts +0 -44
- package/tests/test-backup-system.ts +0 -174
- 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-new-tools.ts +0 -165
- 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
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* normalization.ts — Code Normalization Utilities
|
|
3
|
+
*
|
|
4
|
+
* Centralizes line ending normalization and indentation logic
|
|
5
|
+
* to eliminate duplication across writer modules.
|
|
6
|
+
*/
|
|
7
|
+
import { TS_INDENT, PHP_INDENT, PYTHON_INDENT } from "./constants.js";
|
|
8
|
+
// ─── Line Ending Normalization ──────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Normalize line endings to LF (\n) for consistent processing.
|
|
11
|
+
* Handles CRLF (\r\n), CR (\r), and mixed line endings.
|
|
12
|
+
*
|
|
13
|
+
* Why: Windows files use CRLF, which breaks brace-counting and line-based
|
|
14
|
+
* operations. Normalizing to LF ensures consistent behavior across platforms.
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeLineEndings(content) {
|
|
17
|
+
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
18
|
+
}
|
|
19
|
+
// ─── Indentation Detection ──────────────────────────────────────────
|
|
20
|
+
/**
|
|
21
|
+
* Extract the leading whitespace from a line.
|
|
22
|
+
*/
|
|
23
|
+
export function getIndent(line) {
|
|
24
|
+
const match = line.match(/^(\s*)/);
|
|
25
|
+
return match ? match[1] : "";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Measure the indentation level of a line (number of spaces/tabs).
|
|
29
|
+
*/
|
|
30
|
+
export function measureIndent(line) {
|
|
31
|
+
const indent = getIndent(line);
|
|
32
|
+
// Convert tabs to 4 spaces for measurement
|
|
33
|
+
return indent.replace(/\t/g, " ").length;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Detect the predominant indentation style in a file.
|
|
37
|
+
* Returns the most common indentation string (e.g., " ", " ", "\t").
|
|
38
|
+
*/
|
|
39
|
+
export function detectIndentStyle(content) {
|
|
40
|
+
const lines = content.split("\n");
|
|
41
|
+
const indents = new Map();
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
if (line.trim() === "")
|
|
44
|
+
continue;
|
|
45
|
+
const indent = getIndent(line);
|
|
46
|
+
if (indent.length === 0)
|
|
47
|
+
continue;
|
|
48
|
+
// Extract the base indent unit (first level of indentation)
|
|
49
|
+
const match = indent.match(/^(\s+)/);
|
|
50
|
+
if (match) {
|
|
51
|
+
const unit = match[1];
|
|
52
|
+
indents.set(unit, (indents.get(unit) || 0) + 1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (indents.size === 0)
|
|
56
|
+
return " "; // Default to 2 spaces
|
|
57
|
+
// Return the most common indent
|
|
58
|
+
let maxCount = 0;
|
|
59
|
+
let commonIndent = " ";
|
|
60
|
+
for (const [indent, count] of indents.entries()) {
|
|
61
|
+
if (count > maxCount) {
|
|
62
|
+
maxCount = count;
|
|
63
|
+
commonIndent = indent;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return commonIndent;
|
|
67
|
+
}
|
|
68
|
+
// ─── Code Reindentation ─────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Reindent code to match a target indentation level.
|
|
71
|
+
* Preserves relative indentation between lines.
|
|
72
|
+
*
|
|
73
|
+
* @param code - The code to reindent
|
|
74
|
+
* @param targetIndent - The base indentation to apply
|
|
75
|
+
* @param language - Optional language hint for default indent style
|
|
76
|
+
*/
|
|
77
|
+
export function reindentCode(code, targetIndent, language) {
|
|
78
|
+
// Normalize line endings first
|
|
79
|
+
code = normalizeLineEndings(code);
|
|
80
|
+
const lines = code.split("\n");
|
|
81
|
+
if (lines.length === 0)
|
|
82
|
+
return code;
|
|
83
|
+
// Find minimum indentation (excluding empty lines)
|
|
84
|
+
let minIndent = Infinity;
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
if (line.trim() === "")
|
|
87
|
+
continue;
|
|
88
|
+
const indent = measureIndent(line);
|
|
89
|
+
if (indent < minIndent)
|
|
90
|
+
minIndent = indent;
|
|
91
|
+
}
|
|
92
|
+
if (minIndent === Infinity)
|
|
93
|
+
minIndent = 0;
|
|
94
|
+
// Apply target indentation while preserving relative indentation
|
|
95
|
+
return lines
|
|
96
|
+
.map((line) => {
|
|
97
|
+
if (line.trim() === "")
|
|
98
|
+
return "";
|
|
99
|
+
const currentIndent = measureIndent(line);
|
|
100
|
+
const relativeIndent = currentIndent - minIndent;
|
|
101
|
+
// Calculate how many indent units are needed
|
|
102
|
+
const indentUnit = getIndentUnit(language);
|
|
103
|
+
const indentUnitSize = indentUnit.length;
|
|
104
|
+
const numUnits = Math.floor(relativeIndent / indentUnitSize);
|
|
105
|
+
return targetIndent + indentUnit.repeat(numUnits) + line.trimStart();
|
|
106
|
+
})
|
|
107
|
+
.join("\n");
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the standard indentation unit for a language.
|
|
111
|
+
*/
|
|
112
|
+
function getIndentUnit(language) {
|
|
113
|
+
switch (language) {
|
|
114
|
+
case "dart":
|
|
115
|
+
case "typescript":
|
|
116
|
+
return TS_INDENT;
|
|
117
|
+
case "php":
|
|
118
|
+
return PHP_INDENT;
|
|
119
|
+
case "python":
|
|
120
|
+
return PYTHON_INDENT;
|
|
121
|
+
default:
|
|
122
|
+
return " "; // Default to 2 spaces
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Reindent code for Python (indentation-aware language).
|
|
127
|
+
* Handles Python's strict indentation requirements.
|
|
128
|
+
*/
|
|
129
|
+
export function reindentPython(code, targetIndent) {
|
|
130
|
+
code = normalizeLineEndings(code);
|
|
131
|
+
const lines = code.split("\n");
|
|
132
|
+
if (lines.length === 0)
|
|
133
|
+
return code;
|
|
134
|
+
let minIndent = Infinity;
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
if (line.trim() === "")
|
|
137
|
+
continue;
|
|
138
|
+
const indent = measureIndent(line);
|
|
139
|
+
if (indent < minIndent)
|
|
140
|
+
minIndent = indent;
|
|
141
|
+
}
|
|
142
|
+
if (minIndent === Infinity)
|
|
143
|
+
minIndent = 0;
|
|
144
|
+
return lines
|
|
145
|
+
.map((line) => {
|
|
146
|
+
if (line.trim() === "")
|
|
147
|
+
return "";
|
|
148
|
+
const currentIndent = measureIndent(line);
|
|
149
|
+
const relativeIndent = currentIndent - minIndent;
|
|
150
|
+
// Python uses 4-space indentation by convention
|
|
151
|
+
const numLevels = Math.floor(relativeIndent / 4);
|
|
152
|
+
return targetIndent + PYTHON_INDENT.repeat(numLevels) + line.trimStart();
|
|
153
|
+
})
|
|
154
|
+
.join("\n");
|
|
155
|
+
}
|
|
156
|
+
// ─── Whitespace Cleanup ─────────────────────────────────────────────
|
|
157
|
+
/**
|
|
158
|
+
* Remove excessive blank lines (more than 2 consecutive).
|
|
159
|
+
*/
|
|
160
|
+
export function normalizeBlankLines(content) {
|
|
161
|
+
return content.replace(/\n{3,}/g, "\n\n");
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Ensure file ends with a single newline (POSIX standard).
|
|
165
|
+
*/
|
|
166
|
+
export function ensureTrailingNewline(content) {
|
|
167
|
+
if (!content.endsWith("\n")) {
|
|
168
|
+
return content + "\n";
|
|
169
|
+
}
|
|
170
|
+
// Remove multiple trailing newlines
|
|
171
|
+
return content.replace(/\n+$/, "\n");
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=normalization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalization.js","sourceRoot":"","sources":["../../../src/utils/normalization.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Finds the root directory of a project by looking for common markers.
|
|
2
|
+
* Secure Project Root Detection
|
|
3
|
+
* Ensures all file operations are contained within project boundaries
|
|
5
4
|
*/
|
|
6
5
|
/**
|
|
7
|
-
* Find
|
|
8
|
-
* Returns undefined if no root marker
|
|
6
|
+
* Find project root by walking up from a file path
|
|
7
|
+
* Returns undefined if no root marker found
|
|
9
8
|
*/
|
|
10
9
|
export declare function findProjectRoot(filePath: string): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Validate that a file path is within project boundaries
|
|
12
|
+
* MANDATORY for all write operations
|
|
13
|
+
*/
|
|
14
|
+
export declare function enforceProjectBoundary(filePath: string, projectRoot: string): {
|
|
15
|
+
valid: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
};
|
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Finds the root directory of a project by looking for common markers.
|
|
2
|
+
* Secure Project Root Detection
|
|
3
|
+
* Ensures all file operations are contained within project boundaries
|
|
5
4
|
*/
|
|
6
5
|
import * as fs from "node:fs";
|
|
7
6
|
import * as path from "node:path";
|
|
8
|
-
const ROOT_MARKERS = [
|
|
9
|
-
"package.json",
|
|
10
|
-
".git",
|
|
11
|
-
"pyproject.toml",
|
|
12
|
-
"setup.py",
|
|
13
|
-
"Cargo.toml",
|
|
14
|
-
"go.mod",
|
|
15
|
-
"pom.xml",
|
|
16
|
-
"build.gradle",
|
|
17
|
-
"CMakeLists.txt",
|
|
18
|
-
".gitignore",
|
|
19
|
-
"Gemfile",
|
|
20
|
-
"composer.json",
|
|
21
|
-
"pubspec.yaml",
|
|
22
|
-
];
|
|
7
|
+
const ROOT_MARKERS = ["package.json", ".git", "pubspec.yaml", "pyproject.toml", "Cargo.toml"];
|
|
23
8
|
/**
|
|
24
|
-
* Find
|
|
25
|
-
* Returns undefined if no root marker
|
|
9
|
+
* Find project root by walking up from a file path
|
|
10
|
+
* Returns undefined if no root marker found
|
|
26
11
|
*/
|
|
27
12
|
export function findProjectRoot(filePath) {
|
|
28
13
|
let current = path.dirname(path.resolve(filePath));
|
|
@@ -37,4 +22,21 @@ export function findProjectRoot(filePath) {
|
|
|
37
22
|
}
|
|
38
23
|
return undefined;
|
|
39
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Validate that a file path is within project boundaries
|
|
27
|
+
* MANDATORY for all write operations
|
|
28
|
+
*/
|
|
29
|
+
export function enforceProjectBoundary(filePath, projectRoot) {
|
|
30
|
+
const normalized = path.resolve(filePath);
|
|
31
|
+
const normalizedRoot = path.resolve(projectRoot);
|
|
32
|
+
const relative = path.relative(normalizedRoot, normalized);
|
|
33
|
+
// If relative path starts with .., it's outside the project root
|
|
34
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
35
|
+
return {
|
|
36
|
+
valid: false,
|
|
37
|
+
error: `Security: Path outside project root. File: ${normalized}, Root: ${normalizedRoot}`
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return { valid: true };
|
|
41
|
+
}
|
|
40
42
|
//# sourceMappingURL=projectRoot.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projectRoot.js","sourceRoot":"","sources":["../../../src/utils/projectRoot.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"projectRoot.js","sourceRoot":"","sources":["../../../src/utils/projectRoot.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,MAAM,YAAY,GAAG,CAAC,cAAc,EAAE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAC;AAE9F;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;IAEtC,OAAO,OAAO,KAAK,IAAI,EAAE,CAAC;QACxB,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;YAClC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;gBAC9C,OAAO,OAAO,CAAC;YACjB,CAAC;QACH,CAAC;QACD,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAgB,EAChB,WAAmB;IAEnB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IAE3D,iEAAiE;IACjE,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3D,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,8CAA8C,UAAU,WAAW,cAAc,EAAE;SAC3F,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure validation helper for write operations
|
|
3
|
+
* Enforces project boundary checks
|
|
4
|
+
*/
|
|
5
|
+
export interface SecureValidationResult {
|
|
6
|
+
valid: boolean;
|
|
7
|
+
resolvedPath?: string;
|
|
8
|
+
projectRoot?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Validate file path with mandatory project boundary enforcement
|
|
13
|
+
* Use this for ALL write operations
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateFilePathSecure(filePath: string): SecureValidationResult;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure validation helper for write operations
|
|
3
|
+
* Enforces project boundary checks
|
|
4
|
+
*/
|
|
5
|
+
import { validateFilePath } from "./validation.js";
|
|
6
|
+
import { findProjectRoot, enforceProjectBoundary } from "./projectRoot.js";
|
|
7
|
+
/**
|
|
8
|
+
* Validate file path with mandatory project boundary enforcement
|
|
9
|
+
* Use this for ALL write operations
|
|
10
|
+
*/
|
|
11
|
+
export function validateFilePathSecure(filePath) {
|
|
12
|
+
const validation = validateFilePath(filePath);
|
|
13
|
+
if (!validation.valid) {
|
|
14
|
+
return {
|
|
15
|
+
valid: false,
|
|
16
|
+
error: validation.error
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const resolvedPath = validation.normalizedPath;
|
|
20
|
+
// SECURITY: Enforce project boundary for write operations
|
|
21
|
+
const projectRoot = findProjectRoot(resolvedPath);
|
|
22
|
+
if (!projectRoot) {
|
|
23
|
+
return {
|
|
24
|
+
valid: false,
|
|
25
|
+
error: "Cannot determine project root. Ensure file is within a valid project."
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const boundaryCheck = enforceProjectBoundary(resolvedPath, projectRoot);
|
|
29
|
+
if (!boundaryCheck.valid) {
|
|
30
|
+
return {
|
|
31
|
+
valid: false,
|
|
32
|
+
error: boundaryCheck.error
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
valid: true,
|
|
37
|
+
resolvedPath,
|
|
38
|
+
projectRoot
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=secureValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secureValidation.js","sourceRoot":"","sources":["../../../src/utils/secureValidation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAS3E;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACrD,MAAM,UAAU,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,UAAU,CAAC,KAAK;SACxB,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,UAAU,CAAC,cAAe,CAAC;IAEhD,0DAA0D;IAC1D,MAAM,WAAW,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;IAClD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,uEAAuE;SAC/E,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,sBAAsB,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IACxE,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;QACzB,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,aAAa,CAAC,KAAK;SAC3B,CAAC;IACJ,CAAC;IAED,OAAO;QACL,KAAK,EAAE,IAAI;QACX,YAAY;QACZ,WAAW;KACZ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction Manager - Atomic Multi-File Operations
|
|
3
|
+
* Ensures all-or-nothing writes with automatic rollback on failure
|
|
4
|
+
*/
|
|
5
|
+
export interface FileChange {
|
|
6
|
+
filePath: string;
|
|
7
|
+
newContent: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class TransactionManager {
|
|
10
|
+
private staging;
|
|
11
|
+
private backedUpFiles;
|
|
12
|
+
/**
|
|
13
|
+
* Stage a file change for commit
|
|
14
|
+
*/
|
|
15
|
+
stage(filePath: string, newContent: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Stage multiple file changes
|
|
18
|
+
*/
|
|
19
|
+
stageMultiple(changes: FileChange[]): void;
|
|
20
|
+
/**
|
|
21
|
+
* Commit all staged changes atomically
|
|
22
|
+
* Creates backups first, then writes all files
|
|
23
|
+
* Rolls back automatically on any failure
|
|
24
|
+
*/
|
|
25
|
+
commit(): Promise<{
|
|
26
|
+
success: boolean;
|
|
27
|
+
error?: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Rollback all changes by restoring from backups
|
|
31
|
+
*/
|
|
32
|
+
rollback(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Clear staging area and backup tracking
|
|
35
|
+
*/
|
|
36
|
+
clear(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Get number of staged changes
|
|
39
|
+
*/
|
|
40
|
+
get stagedCount(): number;
|
|
41
|
+
/**
|
|
42
|
+
* Get list of staged file paths
|
|
43
|
+
*/
|
|
44
|
+
getStagedFiles(): string[];
|
|
45
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction Manager - Atomic Multi-File Operations
|
|
3
|
+
* Ensures all-or-nothing writes with automatic rollback on failure
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { createBackup, restoreBackup } from "./backupManager.js";
|
|
8
|
+
export class TransactionManager {
|
|
9
|
+
staging = new Map();
|
|
10
|
+
backedUpFiles = new Set();
|
|
11
|
+
/**
|
|
12
|
+
* Stage a file change for commit
|
|
13
|
+
*/
|
|
14
|
+
stage(filePath, newContent) {
|
|
15
|
+
const resolved = path.resolve(filePath);
|
|
16
|
+
this.staging.set(resolved, newContent);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Stage multiple file changes
|
|
20
|
+
*/
|
|
21
|
+
stageMultiple(changes) {
|
|
22
|
+
for (const change of changes) {
|
|
23
|
+
this.stage(change.filePath, change.newContent);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Commit all staged changes atomically
|
|
28
|
+
* Creates backups first, then writes all files
|
|
29
|
+
* Rolls back automatically on any failure
|
|
30
|
+
*/
|
|
31
|
+
async commit() {
|
|
32
|
+
if (this.staging.size === 0) {
|
|
33
|
+
return { success: true };
|
|
34
|
+
}
|
|
35
|
+
// Phase 1: Create backups
|
|
36
|
+
try {
|
|
37
|
+
for (const [filePath] of this.staging) {
|
|
38
|
+
createBackup(filePath);
|
|
39
|
+
this.backedUpFiles.add(filePath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
await this.rollback();
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: `Backup failed: ${error instanceof Error ? error.message : String(error)}`
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Phase 2: Write all files
|
|
50
|
+
try {
|
|
51
|
+
for (const [filePath, content] of this.staging) {
|
|
52
|
+
await fs.promises.writeFile(filePath, content, "utf-8");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
await this.rollback();
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
error: `Write failed: ${error instanceof Error ? error.message : String(error)}`
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Phase 3: Validate (optional syntax check could go here)
|
|
63
|
+
// For now, we trust the write succeeded
|
|
64
|
+
this.clear();
|
|
65
|
+
return { success: true };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Rollback all changes by restoring from backups
|
|
69
|
+
*/
|
|
70
|
+
async rollback() {
|
|
71
|
+
for (const filePath of this.backedUpFiles) {
|
|
72
|
+
try {
|
|
73
|
+
restoreBackup(filePath);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// Log but continue rolling back other files
|
|
77
|
+
console.error(`Failed to restore ${filePath}:`, error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this.clear();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Clear staging area and backup tracking
|
|
84
|
+
*/
|
|
85
|
+
clear() {
|
|
86
|
+
this.staging.clear();
|
|
87
|
+
this.backedUpFiles.clear();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get number of staged changes
|
|
91
|
+
*/
|
|
92
|
+
get stagedCount() {
|
|
93
|
+
return this.staging.size;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get list of staged file paths
|
|
97
|
+
*/
|
|
98
|
+
getStagedFiles() {
|
|
99
|
+
return Array.from(this.staging.keys());
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=transactionManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactionManager.js","sourceRoot":"","sources":["../../../src/utils/transactionManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* validation.ts — Input Validation & Security
|
|
3
|
+
*
|
|
4
|
+
* Centralized validation logic to prevent security vulnerabilities:
|
|
5
|
+
* - Path traversal attacks
|
|
6
|
+
* - ReDoS (Regular Expression Denial of Service)
|
|
7
|
+
* - Injection attacks
|
|
8
|
+
* - Resource exhaustion
|
|
9
|
+
*/
|
|
10
|
+
export interface PathValidationResult {
|
|
11
|
+
valid: boolean;
|
|
12
|
+
error?: string;
|
|
13
|
+
normalizedPath?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate that a file path is safe and within allowed boundaries.
|
|
17
|
+
* Prevents path traversal attacks and access to sensitive system files.
|
|
18
|
+
*
|
|
19
|
+
* @param filePath - The path to validate
|
|
20
|
+
* @param projectRoot - Optional project root to enforce containment
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateFilePath(filePath: string, projectRoot?: string): PathValidationResult;
|
|
23
|
+
/**
|
|
24
|
+
* Validate that a directory path is safe for operations.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateDirectoryPath(dirPath: string, projectRoot?: string): PathValidationResult;
|
|
27
|
+
export interface SymbolValidationResult {
|
|
28
|
+
valid: boolean;
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate a symbol name to prevent ReDoS and injection attacks.
|
|
33
|
+
*
|
|
34
|
+
* @param symbolName - The symbol name to validate
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateSymbolName(symbolName: string): SymbolValidationResult;
|
|
37
|
+
export interface RegexValidationResult {
|
|
38
|
+
valid: boolean;
|
|
39
|
+
error?: string;
|
|
40
|
+
safeRegex?: RegExp;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Validate and sanitize a regex pattern to prevent ReDoS attacks.
|
|
44
|
+
*
|
|
45
|
+
* @param pattern - The regex pattern to validate
|
|
46
|
+
* @param flags - Optional regex flags
|
|
47
|
+
*/
|
|
48
|
+
export declare function validateRegexPattern(pattern: string, flags?: string): RegexValidationResult;
|
|
49
|
+
/**
|
|
50
|
+
* Create a safe regex with timeout protection.
|
|
51
|
+
* Uses a simple timeout mechanism to prevent long-running regex operations.
|
|
52
|
+
*
|
|
53
|
+
* @param pattern - The regex pattern
|
|
54
|
+
* @param flags - Optional regex flags
|
|
55
|
+
* @param timeoutMs - Timeout in milliseconds (default: 1000ms)
|
|
56
|
+
*/
|
|
57
|
+
export declare function createSafeRegex(pattern: string, flags?: string, timeoutMs?: number): RegExp;
|
|
58
|
+
export interface FileSizeValidationResult {
|
|
59
|
+
valid: boolean;
|
|
60
|
+
error?: string;
|
|
61
|
+
size?: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Validate that a file is not too large to process safely.
|
|
65
|
+
*
|
|
66
|
+
* @param filePath - Path to the file
|
|
67
|
+
*/
|
|
68
|
+
export declare function validateFileSize(filePath: string): FileSizeValidationResult;
|
|
69
|
+
/**
|
|
70
|
+
* Check if file content contains null bytes (likely binary).
|
|
71
|
+
*/
|
|
72
|
+
export declare function isBinaryContent(content: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Validate that file content is safe to process.
|
|
75
|
+
*/
|
|
76
|
+
export declare function validateFileContent(content: string): SymbolValidationResult;
|
|
77
|
+
/**
|
|
78
|
+
* Validate multiple file paths at once.
|
|
79
|
+
* Returns only valid paths and collects errors.
|
|
80
|
+
*/
|
|
81
|
+
export declare function validateFilePaths(filePaths: string[], projectRoot?: string): {
|
|
82
|
+
validPaths: string[];
|
|
83
|
+
errors: Array<{
|
|
84
|
+
path: string;
|
|
85
|
+
error: string;
|
|
86
|
+
}>;
|
|
87
|
+
};
|