deepdebug-local-agent 1.0.22 → 1.1.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/.claude/context.md +76 -0
- package/.claude/skills/README.md +25 -0
- package/.claude/skills/tdd-debugging/SKILL.md +53 -0
- package/.claude/skills/tdd-debugging/references/anti-patterns.md +43 -0
- package/.claude/skills/tdd-debugging/references/command-reference.md +46 -0
- package/.claude/standards/README.md +51 -0
- package/.claude/standards/clean-code.md +75 -0
- package/.claude/standards/code-review-checklist.md +61 -0
- package/.claude/standards/layered-architecture.md +163 -0
- package/.claude/standards/node.md +45 -0
- package/.claude/standards/security-owasp.md +114 -0
- package/.claude/standards/simplicity.md +42 -0
- package/.claude/standards/solid.md +64 -0
- package/.claude/standards/testing.md +59 -0
- package/.cursor/rules/standards.mdc +17 -0
- package/.github/workflows/build-byoc-image.yml +315 -0
- package/.github/workflows/deploy-gcp.yml +102 -0
- package/.github/workflows/npm-publish.yml +69 -8
- package/AGENTS.md +21 -0
- package/CHANGELOG.md +69 -0
- package/CLAUDE.md +13 -0
- package/CLAUDE_CODE_AGENT_INFINITE.md +5 -3
- package/Dockerfile +4 -0
- package/Dockerfile.full +55 -3
- package/README.md +81 -0
- package/bin/install.js +16 -5
- package/cloudbuild-full.yaml +57 -0
- package/docker-compose.yml +5 -3
- package/docs/OBSERVABILITY.md +164 -0
- package/docs/byoc-agent-image.md +70 -0
- package/docs/graphify-packaging-plan.md +133 -0
- package/docs/graphify-validation-run-2026-10-28.md +243 -0
- package/docs/legacy-connectors.md +268 -0
- package/docs/releasing.md +43 -0
- package/docs/signed-commands.md +163 -0
- package/env.example +30 -0
- package/helm/Chart.yaml +2 -2
- package/helm/templates/deployment.yaml +13 -3
- package/helm/templates/networkpolicy.yaml +22 -0
- package/helm/templates/secret.yaml +1 -0
- package/helm/values.yaml +13 -14
- package/mcp-server.js +4 -1
- package/package.json +12 -4
- package/scripts/sync-fixtures.mjs +246 -0
- package/scripts/upgrade-observability-push.sh +227 -0
- package/src/adapters/as400/as400-parser.js +12 -0
- package/src/adapters/as400/index.js +134 -0
- package/src/adapters/cobol/cobol-parser.js +37 -0
- package/src/adapters/cobol/index.js +151 -0
- package/src/adapters/ibmmq/ibmmq-parser.js +23 -0
- package/src/adapters/ibmmq/index.js +137 -0
- package/src/adapters/index.js +79 -0
- package/src/adapters/oracle-forms/index.js +145 -0
- package/src/adapters/oracle-forms/oracle-forms-parser.js +11 -0
- package/src/adapters/sap/index.js +146 -0
- package/src/adapters/sap/sap-parser.js +41 -0
- package/src/ai-engine.js +22 -22
- package/src/config/trusted-signing-keys.json +3 -0
- package/src/detectors/build-tool-detector.js +50 -0
- package/src/detectors/multi-project-detector.js +22 -22
- package/src/detectors/service-detector.js +24 -24
- package/src/exec-utils.js +26 -4
- package/src/git/base-git-provider.js +117 -117
- package/src/git/bitbucket-provider.js +2 -2
- package/src/git/git-provider-registry.js +18 -18
- package/src/git/github-provider.js +13 -13
- package/src/git/multi-repo-pr.js +161 -0
- package/src/mcp-http-server.js +32 -16
- package/src/observability/bundle-files.js +418 -0
- package/src/observability/detect.js +151 -0
- package/src/observability/provision.js +212 -0
- package/src/observability/render-cli.js +59 -0
- package/src/observability/routes.js +88 -0
- package/src/patch/patch-engine.js +28 -28
- package/src/patch-manager.js +85 -85
- package/src/patch_bkp.js +14 -14
- package/src/ports.js +4 -4
- package/src/routes/workspace.route.js +47 -16
- package/src/runtimes/base-runtime.js +75 -75
- package/src/runtimes/java/gradle-runtime.js +17 -17
- package/src/runtimes/java/java-integrations.js +25 -25
- package/src/runtimes/java/maven-runtime.js +34 -34
- package/src/runtimes/node/node-integrations.js +1 -1
- package/src/runtimes/node/npm-runtime.js +14 -14
- package/src/runtimes/node/yarn-runtime.js +4 -4
- package/src/runtimes/runtime-registry.js +25 -25
- package/src/server-local.js +4 -4
- package/src/server.js +1412 -335
- package/src/siblings/burst-suppressor.js +89 -0
- package/src/siblings/capabilities.js +273 -0
- package/src/siblings/env-compat.cjs +58 -0
- package/src/siblings/incident-forwarder.js +320 -0
- package/src/siblings/logger.js +15 -0
- package/src/siblings/metrics.js +94 -0
- package/src/utils/agent-auth.js +184 -0
- package/src/utils/approval-token.js +56 -0
- package/src/utils/bind-host.js +29 -0
- package/src/utils/branch-refspec.js +98 -0
- package/src/utils/child-env.js +34 -0
- package/src/utils/environment-diagnostics.js +55 -55
- package/src/utils/exec-policy.js +962 -0
- package/src/utils/exec-utils.js +15 -15
- package/src/utils/file-count.js +46 -0
- package/src/utils/fs-utils.js +10 -10
- package/src/utils/multi-repo-open.js +132 -0
- package/src/utils/open-gate.js +183 -0
- package/src/utils/ranged-read.js +65 -0
- package/src/utils/request-guards.js +77 -0
- package/src/utils/request-signing.js +342 -0
- package/src/utils/root-recovery.js +244 -0
- package/src/utils/safe-exec-args.js +58 -0
- package/src/utils/sandbox.js +182 -0
- package/src/utils/search-outcome.js +56 -0
- package/src/utils/workspace-root.js +80 -0
- package/src/validators/syntax-validator.js +149 -0
- package/src/workspace/detect-port.js +16 -16
- package/src/workspace/file-reader.js +1 -1
- package/src/workspace/process-manager.js +46 -46
- package/src/workspace-manager.js +86 -24
- package/test/adapters/as400/as400.test.js +121 -0
- package/test/adapters/cobol/cobol.test.js +217 -0
- package/test/adapters/ibmmq/ibmmq.test.js +122 -0
- package/test/adapters/oracle-forms/oracle-forms.test.js +180 -0
- package/test/adapters/registry.test.js +108 -0
- package/test/adapters/sap/sap.test.js +142 -0
- package/test/agent-auth.test.js +194 -0
- package/test/bind-host.test.js +26 -0
- package/test/branch-refspec.test.js +224 -0
- package/test/burst-suppressor.test.js +60 -0
- package/test/capabilities-access.test.js +47 -0
- package/test/capabilities.test.js +87 -0
- package/test/child-env.test.js +23 -0
- package/test/env-compat.test.js +71 -0
- package/test/exec-policy.test.js +463 -0
- package/test/file-count.test.js +64 -0
- package/test/incident-forwarder.test.js +231 -0
- package/test/multi-repo-open.test.js +180 -0
- package/test/multi-repo-pr.test.js +129 -0
- package/test/observability-detect.test.js +199 -0
- package/test/open-gate.test.js +341 -0
- package/test/ranged-read.test.js +60 -0
- package/test/request-guards.test.js +91 -0
- package/test/request-signing.test.js +284 -0
- package/test/root-recovery-sidecar.test.js +171 -0
- package/test/root-recovery.test.js +274 -0
- package/test/safe-exec-args.test.js +58 -0
- package/test/sandbox.test.js +98 -0
- package/test/search-outcome.test.js +50 -0
- package/test/workspace-root.test.js +64 -0
- package/tools/graphify-validation/README.md +92 -0
- package/tools/graphify-validation/config/questions.json +31 -0
- package/tools/graphify-validation/config/repos.json +9 -0
- package/tools/graphify-validation/config/symbols.json +6 -0
- package/tools/graphify-validation/fixtures/spring/expected.json +34 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AppConfig.java +29 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AuditLogger.java +15 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/Cache.java +7 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/CheckoutService.java +25 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/InMemoryCache.java +18 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/OrderController.java +22 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaymentGateway.java +5 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaypalGateway.java +11 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/RefundService.java +14 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/StripeGateway.java +11 -0
- package/tools/graphify-validation/lib/affected_vs_rg.py +81 -0
- package/tools/graphify-validation/lib/audit_hook/sitecustomize.py +22 -0
- package/tools/graphify-validation/lib/graph_metrics.py +96 -0
- package/tools/graphify-validation/lib/gv_common.py +130 -0
- package/tools/graphify-validation/lib/licenses.py +47 -0
- package/tools/graphify-validation/lib/pip_audit_summary.py +18 -0
- package/tools/graphify-validation/lib/recall.py +115 -0
- package/tools/graphify-validation/lib/report.py +226 -0
- package/tools/graphify-validation/lib/run_all.py +237 -0
- package/tools/graphify-validation/lib/run_graphify.py +144 -0
- package/tools/graphify-validation/lib/rusage_helper.py +17 -0
- package/tools/graphify-validation/lib/sample_edges.py +246 -0
- package/tools/graphify-validation/lib/spring_fixture.py +81 -0
- package/tools/graphify-validation/requirements-py311.lock +508 -0
- package/tools/graphify-validation/requirements-tools.in +1 -0
- package/tools/graphify-validation/requirements-tools.lock +371 -0
- package/tools/graphify-validation/requirements.in +1 -0
- package/tools/graphify-validation/requirements.lock +502 -0
- package/tools/graphify-validation/run.sh +43 -0
- package/tools/graphify-validation/tests/test_harness.py +151 -0
- package/cloudbuild-agent-qa.yaml +0 -43
- package/cloudbuild-agent-staging.yaml +0 -40
- package/cloudbuild.yaml.deprecated +0 -42
- package/src/local-agent-source.tar.gz +0 -0
package/src/patch-manager.js
CHANGED
|
@@ -3,14 +3,14 @@ import path from 'path';
|
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* PatchManager -
|
|
6
|
+
* PatchManager - Robust patch application system
|
|
7
7
|
*
|
|
8
8
|
* Features:
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
9
|
+
* - Strict unified diff validation
|
|
10
|
+
* - Backup system with rollback
|
|
11
|
+
* - Detailed error messages
|
|
12
|
+
* - Multi-file support
|
|
13
|
+
* - Automatic cleanup of old backups
|
|
14
14
|
*/
|
|
15
15
|
export class PatchManager {
|
|
16
16
|
constructor(workspaceRoot) {
|
|
@@ -24,8 +24,8 @@ export class PatchManager {
|
|
|
24
24
|
// ============================================
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
* @param {string} diffText -
|
|
27
|
+
* Validates whether the diff is in the correct unified diff format
|
|
28
|
+
* @param {string} diffText - Diff text
|
|
29
29
|
* @returns {ValidationResult} - { valid: boolean, errors: string[] }
|
|
30
30
|
*/
|
|
31
31
|
validateDiff(diffText) {
|
|
@@ -37,7 +37,7 @@ export class PatchManager {
|
|
|
37
37
|
|
|
38
38
|
const lines = diffText.split('\n');
|
|
39
39
|
|
|
40
|
-
//
|
|
40
|
+
// Check that it has at least the basic headers
|
|
41
41
|
const hasOldFileHeader = lines.some(l => l.startsWith('--- '));
|
|
42
42
|
const hasNewFileHeader = lines.some(l => l.startsWith('+++ '));
|
|
43
43
|
const hasHunkHeader = lines.some(l => /^@@ -\d+,?\d* \+\d+,?\d* @@/.test(l));
|
|
@@ -52,7 +52,7 @@ export class PatchManager {
|
|
|
52
52
|
errors.push('Missing hunk header (@@ -line,count +line,count @@)');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
//
|
|
55
|
+
// Validate the structure of the hunks
|
|
56
56
|
const hunkErrors = this._validateHunks(lines);
|
|
57
57
|
errors.push(...hunkErrors);
|
|
58
58
|
|
|
@@ -63,9 +63,9 @@ export class PatchManager {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
* @param {string} diffText -
|
|
68
|
-
* @returns {ParsedPatch} -
|
|
66
|
+
* Parses the diff into a manipulable structure
|
|
67
|
+
* @param {string} diffText - Diff text
|
|
68
|
+
* @returns {ParsedPatch} - Parsed patch structure
|
|
69
69
|
*/
|
|
70
70
|
parseDiff(diffText) {
|
|
71
71
|
const validation = this.validateDiff(diffText);
|
|
@@ -81,7 +81,7 @@ export class PatchManager {
|
|
|
81
81
|
for (let i = 0; i < lines.length; i++) {
|
|
82
82
|
const line = lines[i];
|
|
83
83
|
|
|
84
|
-
//
|
|
84
|
+
// Detect the start of a new file
|
|
85
85
|
if (line.startsWith('--- ')) {
|
|
86
86
|
if (currentPatch) {
|
|
87
87
|
if (currentHunk) {
|
|
@@ -104,7 +104,7 @@ export class PatchManager {
|
|
|
104
104
|
continue;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
//
|
|
107
|
+
// Detect the start of a hunk
|
|
108
108
|
const hunkMatch = line.match(/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@(.*)$/);
|
|
109
109
|
if (hunkMatch && currentPatch) {
|
|
110
110
|
if (currentHunk) {
|
|
@@ -122,18 +122,18 @@ export class PatchManager {
|
|
|
122
122
|
continue;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
//
|
|
125
|
+
// Collect the hunk lines
|
|
126
126
|
if (currentHunk && (line.startsWith(' ') || line.startsWith('+') || line.startsWith('-') || line === '')) {
|
|
127
127
|
const type = line.startsWith('+') ? 'add' :
|
|
128
128
|
line.startsWith('-') ? 'remove' : 'context';
|
|
129
129
|
currentHunk.lines.push({
|
|
130
130
|
type,
|
|
131
|
-
content: line.substring(1) // Remove
|
|
131
|
+
content: line.substring(1) // Remove the prefix
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
//
|
|
136
|
+
// Add the last patch/hunk
|
|
137
137
|
if (currentPatch) {
|
|
138
138
|
if (currentHunk) {
|
|
139
139
|
currentPatch.hunks.push(currentHunk);
|
|
@@ -153,40 +153,40 @@ export class PatchManager {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
|
-
*
|
|
157
|
-
* @param {string} filePath -
|
|
156
|
+
* Creates a backup of a file
|
|
157
|
+
* @param {string} filePath - Relative path of the file
|
|
158
158
|
* @returns {Promise<BackupResult>}
|
|
159
159
|
*/
|
|
160
160
|
async backupFile(filePath) {
|
|
161
161
|
const fullPath = path.join(this.workspaceRoot, filePath);
|
|
162
162
|
|
|
163
|
-
//
|
|
163
|
+
// Check whether the file exists
|
|
164
164
|
try {
|
|
165
165
|
await fs.access(fullPath);
|
|
166
166
|
} catch {
|
|
167
167
|
throw new PatchError('FILE_NOT_FOUND', `File not found: ${filePath}`);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
//
|
|
170
|
+
// Create the backup directory
|
|
171
171
|
await this._ensureBackupDir();
|
|
172
172
|
|
|
173
|
-
//
|
|
173
|
+
// Read the original content
|
|
174
174
|
const originalContent = await fs.readFile(fullPath, 'utf8');
|
|
175
175
|
|
|
176
|
-
//
|
|
176
|
+
// Generate a unique ID for the backup
|
|
177
177
|
const timestamp = Date.now();
|
|
178
178
|
const hash = crypto.createHash('md5').update(filePath + timestamp).digest('hex').substring(0, 8);
|
|
179
179
|
const backupId = `${timestamp}-${hash}`;
|
|
180
180
|
|
|
181
|
-
//
|
|
181
|
+
// Build the backup file name
|
|
182
182
|
const safeFileName = filePath.replace(/[\/\\]/g, '_');
|
|
183
183
|
const backupFileName = `${backupId}_${safeFileName}`;
|
|
184
184
|
const backupPath = path.join(this.backupDir, backupFileName);
|
|
185
185
|
|
|
186
|
-
//
|
|
186
|
+
// Save the backup
|
|
187
187
|
await fs.writeFile(backupPath, originalContent, 'utf8');
|
|
188
188
|
|
|
189
|
-
//
|
|
189
|
+
// Save the metadata
|
|
190
190
|
const metadataPath = path.join(this.backupDir, `${backupId}.meta.json`);
|
|
191
191
|
const metadata = {
|
|
192
192
|
backupId,
|
|
@@ -199,10 +199,10 @@ export class PatchManager {
|
|
|
199
199
|
};
|
|
200
200
|
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf8');
|
|
201
201
|
|
|
202
|
-
// Cleanup
|
|
202
|
+
// Cleanup of old backups
|
|
203
203
|
await this._cleanupOldBackups(filePath);
|
|
204
204
|
|
|
205
|
-
console.log(
|
|
205
|
+
console.log(`📦 Backup created: ${backupId} for ${filePath}`);
|
|
206
206
|
|
|
207
207
|
return {
|
|
208
208
|
backupId,
|
|
@@ -214,9 +214,9 @@ export class PatchManager {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
/**
|
|
217
|
-
*
|
|
218
|
-
* @param {ParsedPatch} parsedPatch -
|
|
219
|
-
* @param {object} options -
|
|
217
|
+
* Applies a parsed patch to the workspace
|
|
218
|
+
* @param {ParsedPatch} parsedPatch - Result of parseDiff
|
|
219
|
+
* @param {object} options - Options { createBackup: boolean, dryRun: boolean }
|
|
220
220
|
* @returns {Promise<ApplyResult>}
|
|
221
221
|
*/
|
|
222
222
|
async applyPatch(parsedPatch, options = {}) {
|
|
@@ -237,20 +237,20 @@ export class PatchManager {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
try {
|
|
240
|
-
//
|
|
240
|
+
// Create a backup if requested
|
|
241
241
|
if (createBackup && !dryRun) {
|
|
242
242
|
const backup = await this.backupFile(targetPath);
|
|
243
243
|
backups.push(backup);
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
//
|
|
246
|
+
// Read the original file
|
|
247
247
|
const fullPath = path.join(this.workspaceRoot, targetPath);
|
|
248
248
|
let originalContent;
|
|
249
249
|
|
|
250
250
|
try {
|
|
251
251
|
originalContent = await fs.readFile(fullPath, 'utf8');
|
|
252
252
|
} catch (err) {
|
|
253
|
-
//
|
|
253
|
+
// A new file is being created
|
|
254
254
|
if (patch.oldFile === '/dev/null' || patch.oldFile.endsWith('/dev/null')) {
|
|
255
255
|
originalContent = '';
|
|
256
256
|
} else {
|
|
@@ -258,10 +258,10 @@ export class PatchManager {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
//
|
|
261
|
+
// Apply each hunk
|
|
262
262
|
const originalLines = originalContent.split('\n');
|
|
263
263
|
let modifiedLines = [...originalLines];
|
|
264
|
-
let offset = 0; // Offset
|
|
264
|
+
let offset = 0; // Offset caused by previous additions/removals
|
|
265
265
|
|
|
266
266
|
for (let hunkIndex = 0; hunkIndex < patch.hunks.length; hunkIndex++) {
|
|
267
267
|
const hunk = patch.hunks[hunkIndex];
|
|
@@ -275,11 +275,11 @@ export class PatchManager {
|
|
|
275
275
|
offset = hunkResult.newOffset;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
//
|
|
278
|
+
// Save the modified file (unless it is a dry run)
|
|
279
279
|
if (!dryRun) {
|
|
280
280
|
const newContent = modifiedLines.join('\n');
|
|
281
281
|
await fs.writeFile(fullPath, newContent, 'utf8');
|
|
282
|
-
console.log(
|
|
282
|
+
console.log(`✅ Patched: ${targetPath}`);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
results.push({
|
|
@@ -290,14 +290,14 @@ export class PatchManager {
|
|
|
290
290
|
});
|
|
291
291
|
|
|
292
292
|
} catch (err) {
|
|
293
|
-
//
|
|
293
|
+
// Roll back the backups already created in this operation
|
|
294
294
|
if (!dryRun && backups.length > 0) {
|
|
295
|
-
console.log(
|
|
295
|
+
console.log(`⚠️ Rolling back ${backups.length} backups due to error...`);
|
|
296
296
|
for (const backup of backups) {
|
|
297
297
|
try {
|
|
298
298
|
await this.rollback(backup.backupId);
|
|
299
299
|
} catch (rollbackErr) {
|
|
300
|
-
console.error(
|
|
300
|
+
console.error(`❌ Rollback failed for ${backup.backupId}: ${rollbackErr.message}`);
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
303
|
}
|
|
@@ -308,7 +308,7 @@ export class PatchManager {
|
|
|
308
308
|
error: err instanceof PatchError ? err.message : err.toString()
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
-
//
|
|
311
|
+
// Stop at the first error
|
|
312
312
|
break;
|
|
313
313
|
}
|
|
314
314
|
}
|
|
@@ -326,14 +326,14 @@ export class PatchManager {
|
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
/**
|
|
329
|
-
*
|
|
330
|
-
* @param {string} backupId - ID
|
|
329
|
+
* Restores a file from a backup
|
|
330
|
+
* @param {string} backupId - Backup ID
|
|
331
331
|
* @returns {Promise<boolean>}
|
|
332
332
|
*/
|
|
333
333
|
async rollback(backupId) {
|
|
334
334
|
const metadataPath = path.join(this.backupDir, `${backupId}.meta.json`);
|
|
335
335
|
|
|
336
|
-
//
|
|
336
|
+
// Read the metadata
|
|
337
337
|
let metadata;
|
|
338
338
|
try {
|
|
339
339
|
const metadataContent = await fs.readFile(metadataPath, 'utf8');
|
|
@@ -342,7 +342,7 @@ export class PatchManager {
|
|
|
342
342
|
throw new PatchError('BACKUP_NOT_FOUND', `Backup not found: ${backupId}`);
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
//
|
|
345
|
+
// Read the backup content
|
|
346
346
|
const backupFullPath = path.join(this.backupDir, path.basename(metadata.backupPath));
|
|
347
347
|
let backupContent;
|
|
348
348
|
try {
|
|
@@ -351,22 +351,22 @@ export class PatchManager {
|
|
|
351
351
|
throw new PatchError('BACKUP_CORRUPTED', `Backup file corrupted or missing: ${backupId}`);
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
//
|
|
354
|
+
// Verify the checksum
|
|
355
355
|
const currentChecksum = crypto.createHash('md5').update(backupContent).digest('hex');
|
|
356
356
|
if (currentChecksum !== metadata.checksum) {
|
|
357
357
|
throw new PatchError('CHECKSUM_MISMATCH', `Backup checksum mismatch for ${backupId}`);
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
//
|
|
360
|
+
// Restore the original file
|
|
361
361
|
await fs.writeFile(metadata.fullPath, backupContent, 'utf8');
|
|
362
362
|
|
|
363
|
-
console.log(
|
|
363
|
+
console.log(`🔄 Rolled back: ${metadata.originalPath} from backup ${backupId}`);
|
|
364
364
|
|
|
365
365
|
return true;
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
/**
|
|
369
|
-
*
|
|
369
|
+
* Lists all available backups
|
|
370
370
|
* @returns {Promise<BackupInfo[]>}
|
|
371
371
|
*/
|
|
372
372
|
async listBackups() {
|
|
@@ -391,18 +391,18 @@ export class PatchManager {
|
|
|
391
391
|
size: metadata.size
|
|
392
392
|
});
|
|
393
393
|
} catch {
|
|
394
|
-
//
|
|
394
|
+
// Ignore corrupted metadata files
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
-
//
|
|
398
|
+
// Sort by timestamp (most recent first)
|
|
399
399
|
backups.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
400
400
|
|
|
401
401
|
return backups;
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
/**
|
|
405
|
-
*
|
|
405
|
+
* Applies a patch safely with automatic backup and rollback on failure
|
|
406
406
|
* @param {string} diffText - Texto do diff
|
|
407
407
|
* @param {string} incidentId - ID do incident (para logging)
|
|
408
408
|
* @returns {Promise<SafePatchResult>}
|
|
@@ -410,10 +410,10 @@ export class PatchManager {
|
|
|
410
410
|
async safePatch(diffText, incidentId = null) {
|
|
411
411
|
const startTime = Date.now();
|
|
412
412
|
|
|
413
|
-
console.log(
|
|
414
|
-
console.log(
|
|
413
|
+
console.log(`🔧 SafePatch starting${incidentId ? ` for incident ${incidentId}` : ''}...`);
|
|
414
|
+
console.log(`📝 Diff size: ${diffText.length} characters`);
|
|
415
415
|
|
|
416
|
-
// 1.
|
|
416
|
+
// 1. Validate the diff
|
|
417
417
|
const validation = this.validateDiff(diffText);
|
|
418
418
|
if (!validation.valid) {
|
|
419
419
|
return {
|
|
@@ -424,11 +424,11 @@ export class PatchManager {
|
|
|
424
424
|
};
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
-
// 2.
|
|
427
|
+
// 2. Parse the diff
|
|
428
428
|
let parsed;
|
|
429
429
|
try {
|
|
430
430
|
parsed = this.parseDiff(diffText);
|
|
431
|
-
console.log(
|
|
431
|
+
console.log(`📊 Parsed: ${parsed.totalFiles} file(s), ${parsed.totalHunks} hunk(s)`);
|
|
432
432
|
} catch (err) {
|
|
433
433
|
return {
|
|
434
434
|
success: false,
|
|
@@ -438,7 +438,7 @@ export class PatchManager {
|
|
|
438
438
|
};
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
-
// 3.
|
|
441
|
+
// 3. Apply with backup
|
|
442
442
|
let result;
|
|
443
443
|
try {
|
|
444
444
|
result = await this.applyPatch(parsed, { createBackup: true, dryRun: false });
|
|
@@ -454,7 +454,7 @@ export class PatchManager {
|
|
|
454
454
|
const duration = Date.now() - startTime;
|
|
455
455
|
|
|
456
456
|
if (result.success) {
|
|
457
|
-
console.log(
|
|
457
|
+
console.log(`✅ SafePatch completed in ${duration}ms`);
|
|
458
458
|
return {
|
|
459
459
|
success: true,
|
|
460
460
|
filesModified: result.filesModified,
|
|
@@ -464,7 +464,7 @@ export class PatchManager {
|
|
|
464
464
|
duration
|
|
465
465
|
};
|
|
466
466
|
} else {
|
|
467
|
-
console.log(
|
|
467
|
+
console.log(`❌ SafePatch failed: ${result.results.find(r => !r.success)?.error}`);
|
|
468
468
|
return {
|
|
469
469
|
success: false,
|
|
470
470
|
error: result.results.find(r => !r.success)?.error || 'Unknown error',
|
|
@@ -479,7 +479,7 @@ export class PatchManager {
|
|
|
479
479
|
// ============================================
|
|
480
480
|
|
|
481
481
|
/**
|
|
482
|
-
*
|
|
482
|
+
* Validates the structure of the hunks
|
|
483
483
|
*/
|
|
484
484
|
_validateHunks(lines) {
|
|
485
485
|
const errors = [];
|
|
@@ -493,10 +493,10 @@ export class PatchManager {
|
|
|
493
493
|
for (let i = 0; i < lines.length; i++) {
|
|
494
494
|
const line = lines[i];
|
|
495
495
|
|
|
496
|
-
//
|
|
496
|
+
// Detect the start of a hunk
|
|
497
497
|
const hunkMatch = line.match(/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@/);
|
|
498
498
|
if (hunkMatch) {
|
|
499
|
-
//
|
|
499
|
+
// Check the previous hunk
|
|
500
500
|
if (inHunk) {
|
|
501
501
|
if (actualOldLines !== expectedOldLines) {
|
|
502
502
|
errors.push(`Hunk #${hunkNumber}: expected ${expectedOldLines} old lines, got ${actualOldLines}`);
|
|
@@ -515,7 +515,7 @@ export class PatchManager {
|
|
|
515
515
|
continue;
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
-
//
|
|
518
|
+
// Count lines inside the hunk
|
|
519
519
|
if (inHunk) {
|
|
520
520
|
if (line.startsWith(' ')) {
|
|
521
521
|
actualOldLines++;
|
|
@@ -525,16 +525,16 @@ export class PatchManager {
|
|
|
525
525
|
} else if (line.startsWith('+')) {
|
|
526
526
|
actualNewLines++;
|
|
527
527
|
} else if (line.startsWith('\\')) {
|
|
528
|
-
// "" -
|
|
528
|
+
// "" - ignore
|
|
529
529
|
} else if (line.startsWith('diff ') || line.startsWith('index ') ||
|
|
530
530
|
line.startsWith('--- ') || line.startsWith('+++ ')) {
|
|
531
|
-
//
|
|
531
|
+
// New file, reset
|
|
532
532
|
inHunk = false;
|
|
533
533
|
}
|
|
534
534
|
}
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
-
//
|
|
537
|
+
// Check the last hunk
|
|
538
538
|
if (inHunk) {
|
|
539
539
|
if (actualOldLines !== expectedOldLines && expectedOldLines > 0) {
|
|
540
540
|
errors.push(`Hunk #${hunkNumber}: expected ${expectedOldLines} old lines, got ${actualOldLines}`);
|
|
@@ -548,17 +548,17 @@ export class PatchManager {
|
|
|
548
548
|
}
|
|
549
549
|
|
|
550
550
|
/**
|
|
551
|
-
*
|
|
551
|
+
* Extracts the file path from the header
|
|
552
552
|
*/
|
|
553
553
|
_extractFilePath(line, prefix) {
|
|
554
554
|
let filePath = line.substring(prefix.length).trim();
|
|
555
555
|
|
|
556
|
-
//
|
|
556
|
+
// Remove the a/ or b/ prefix
|
|
557
557
|
if (filePath.startsWith('a/') || filePath.startsWith('b/')) {
|
|
558
558
|
filePath = filePath.substring(2);
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
-
//
|
|
561
|
+
// Remove the timestamp if present (git diff format)
|
|
562
562
|
const tabIndex = filePath.indexOf('\t');
|
|
563
563
|
if (tabIndex > 0) {
|
|
564
564
|
filePath = filePath.substring(0, tabIndex);
|
|
@@ -568,7 +568,7 @@ export class PatchManager {
|
|
|
568
568
|
}
|
|
569
569
|
|
|
570
570
|
/**
|
|
571
|
-
*
|
|
571
|
+
* Applies a single hunk to the array of lines
|
|
572
572
|
*/
|
|
573
573
|
_applyHunk(lines, hunk, offset, hunkNumber) {
|
|
574
574
|
let startLine = hunk.oldStart - 1 + offset; // 0-indexed
|
|
@@ -609,24 +609,24 @@ export class PatchManager {
|
|
|
609
609
|
// Context verified (exact or fuzzy match) - apply the changes
|
|
610
610
|
const newLines = [...lines];
|
|
611
611
|
|
|
612
|
-
//
|
|
612
|
+
// Calculate how many lines to remove and which lines to add
|
|
613
613
|
const linesToAdd = hunk.lines.filter(l => l.type === 'add').map(l => l.content);
|
|
614
614
|
const removeCount = hunk.lines.filter(l => l.type === 'remove').length;
|
|
615
615
|
const contextAndRemoveCount = originalLines.length;
|
|
616
616
|
|
|
617
|
-
//
|
|
617
|
+
// Build the new content for this section
|
|
618
618
|
const newSectionLines = [];
|
|
619
619
|
for (const hunkLine of hunk.lines) {
|
|
620
620
|
if (hunkLine.type === 'context' || hunkLine.type === 'add') {
|
|
621
621
|
newSectionLines.push(hunkLine.content);
|
|
622
622
|
}
|
|
623
|
-
// 'remove' lines
|
|
623
|
+
// 'remove' lines are ignored (they do not go into the new content)
|
|
624
624
|
}
|
|
625
625
|
|
|
626
|
-
//
|
|
626
|
+
// Replace the old section with the new one
|
|
627
627
|
newLines.splice(startLine, contextAndRemoveCount, ...newSectionLines);
|
|
628
628
|
|
|
629
|
-
//
|
|
629
|
+
// Calculate the new offset
|
|
630
630
|
const addedCount = linesToAdd.length;
|
|
631
631
|
const newOffset = offset + addedCount - removeCount;
|
|
632
632
|
|
|
@@ -640,7 +640,7 @@ export class PatchManager {
|
|
|
640
640
|
}
|
|
641
641
|
|
|
642
642
|
/**
|
|
643
|
-
*
|
|
643
|
+
* Truncates a string for display
|
|
644
644
|
*/
|
|
645
645
|
/**
|
|
646
646
|
* Check if context lines match at a given position in the file.
|
|
@@ -682,19 +682,19 @@ export class PatchManager {
|
|
|
682
682
|
}
|
|
683
683
|
|
|
684
684
|
/**
|
|
685
|
-
*
|
|
685
|
+
* Ensures the backup directory exists
|
|
686
686
|
*/
|
|
687
687
|
async _ensureBackupDir() {
|
|
688
688
|
try {
|
|
689
689
|
await fs.access(this.backupDir);
|
|
690
690
|
} catch {
|
|
691
691
|
await fs.mkdir(this.backupDir, { recursive: true });
|
|
692
|
-
console.log(
|
|
692
|
+
console.log(`📁 Created backup directory: ${this.backupDir}`);
|
|
693
693
|
}
|
|
694
694
|
}
|
|
695
695
|
|
|
696
696
|
/**
|
|
697
|
-
*
|
|
697
|
+
* Removes old backups for a specific file
|
|
698
698
|
*/
|
|
699
699
|
async _cleanupOldBackups(originalPath) {
|
|
700
700
|
const backups = await this.listBackups();
|
|
@@ -704,7 +704,7 @@ export class PatchManager {
|
|
|
704
704
|
return;
|
|
705
705
|
}
|
|
706
706
|
|
|
707
|
-
//
|
|
707
|
+
// Remove the oldest backups
|
|
708
708
|
const toRemove = fileBackups.slice(this.maxBackupsPerFile);
|
|
709
709
|
|
|
710
710
|
for (const backup of toRemove) {
|
|
@@ -715,9 +715,9 @@ export class PatchManager {
|
|
|
715
715
|
await fs.unlink(path.join(this.backupDir, backupFileName));
|
|
716
716
|
await fs.unlink(path.join(this.backupDir, `${backup.backupId}.meta.json`));
|
|
717
717
|
|
|
718
|
-
console.log(
|
|
718
|
+
console.log(`🗑️ Removed old backup: ${backup.backupId}`);
|
|
719
719
|
} catch (err) {
|
|
720
|
-
console.warn(
|
|
720
|
+
console.warn(`⚠️ Failed to remove old backup ${backup.backupId}: ${err.message}`);
|
|
721
721
|
}
|
|
722
722
|
}
|
|
723
723
|
}
|
package/src/patch_bkp.js
CHANGED
|
@@ -5,8 +5,8 @@ import pkg from "unidiff";
|
|
|
5
5
|
const { parsePatch, applyPatch } = pkg;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* VERSION 2:
|
|
8
|
+
* Applies a unified diff to the target file.
|
|
9
|
+
* VERSION 2: Better logging and fallback to direct replace.
|
|
10
10
|
*/
|
|
11
11
|
export async function applyUnifiedDiff(root, diffText) {
|
|
12
12
|
console.log("🔧 [patch_bkp.js] Applying unified diff...");
|
|
@@ -17,14 +17,14 @@ export async function applyUnifiedDiff(root, diffText) {
|
|
|
17
17
|
throw new Error("Empty diff text provided");
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// Log
|
|
20
|
+
// Log the first lines of the diff for debugging
|
|
21
21
|
const diffLines = diffText.split("\n");
|
|
22
22
|
console.log("📋 Diff preview:");
|
|
23
23
|
for (let i = 0; i < Math.min(10, diffLines.length); i++) {
|
|
24
24
|
console.log(` ${i + 1}: ${diffLines[i]}`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// Parse
|
|
27
|
+
// Parse the diff
|
|
28
28
|
let patches;
|
|
29
29
|
try {
|
|
30
30
|
patches = parsePatch(diffText);
|
|
@@ -45,7 +45,7 @@ export async function applyUnifiedDiff(root, diffText) {
|
|
|
45
45
|
hunks: p?.hunks?.length || 0
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// Determine the target path from the patch
|
|
49
49
|
const targetRel =
|
|
50
50
|
p?.newFileName?.replace(/^b\//, "") ||
|
|
51
51
|
p?.oldFileName?.replace(/^a\//, "");
|
|
@@ -59,12 +59,12 @@ export async function applyUnifiedDiff(root, diffText) {
|
|
|
59
59
|
const full = path.join(root, targetRel);
|
|
60
60
|
console.log("📄 Full path:", full);
|
|
61
61
|
|
|
62
|
-
//
|
|
62
|
+
// Check whether the file exists
|
|
63
63
|
if (!(await exists(full))) {
|
|
64
64
|
throw new Error(`Target file not found: ${full}`);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
//
|
|
67
|
+
// Read the original file
|
|
68
68
|
let original;
|
|
69
69
|
try {
|
|
70
70
|
original = await readFile(full, "utf8");
|
|
@@ -73,7 +73,7 @@ export async function applyUnifiedDiff(root, diffText) {
|
|
|
73
73
|
throw new Error(`Failed to read target file: ${readErr.message}`);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
//
|
|
76
|
+
// Try to apply the patch
|
|
77
77
|
let result;
|
|
78
78
|
try {
|
|
79
79
|
result = applyPatch(original, p);
|
|
@@ -84,13 +84,13 @@ export async function applyUnifiedDiff(root, diffText) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
if (result === false) {
|
|
87
|
-
// Log
|
|
87
|
+
// Log more details about why it failed
|
|
88
88
|
console.error("❌ Patch failed to apply. Possible reasons:");
|
|
89
89
|
console.error(" - Line numbers don't match current file");
|
|
90
90
|
console.error(" - Context lines don't match");
|
|
91
91
|
console.error(" - File was modified since diff was generated");
|
|
92
92
|
|
|
93
|
-
// Log hunks
|
|
93
|
+
// Log hunks for debugging
|
|
94
94
|
if (p.hunks) {
|
|
95
95
|
for (const hunk of p.hunks) {
|
|
96
96
|
console.error(` Hunk: @@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`);
|
|
@@ -100,7 +100,7 @@ export async function applyUnifiedDiff(root, diffText) {
|
|
|
100
100
|
throw new Error("Patch failed to apply - context mismatch");
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
//
|
|
103
|
+
// Write the result
|
|
104
104
|
try {
|
|
105
105
|
await writeFile(full, result, "utf8");
|
|
106
106
|
console.log("✅ Patch applied successfully!");
|
|
@@ -118,7 +118,7 @@ export async function applyUnifiedDiff(root, diffText) {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
|
-
*
|
|
121
|
+
* Counts how many lines were changed
|
|
122
122
|
*/
|
|
123
123
|
function countChangedLines(patch) {
|
|
124
124
|
if (!patch.hunks) return 0;
|
|
@@ -132,8 +132,8 @@ function countChangedLines(patch) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
|
-
* Fallback:
|
|
136
|
-
*
|
|
135
|
+
* Fallback: applies a direct file replacement
|
|
136
|
+
* Used when the unidiff patch fails but we have the full content
|
|
137
137
|
*/
|
|
138
138
|
export async function applyDirectReplace(root, targetRel, newContent) {
|
|
139
139
|
console.log("🔄 [patch_bkp.js] Applying direct replace...");
|