mindheal 1.0.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/.env.example +48 -0
- package/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +481 -0
- package/dist/cjs/ai/ai-provider.js +46 -0
- package/dist/cjs/ai/ai-provider.js.map +1 -0
- package/dist/cjs/ai/anthropic-provider.js +106 -0
- package/dist/cjs/ai/anthropic-provider.js.map +1 -0
- package/dist/cjs/ai/azure-openai-provider.js +130 -0
- package/dist/cjs/ai/azure-openai-provider.js.map +1 -0
- package/dist/cjs/ai/bedrock-provider.js +183 -0
- package/dist/cjs/ai/bedrock-provider.js.map +1 -0
- package/dist/cjs/ai/deepseek-provider.js +118 -0
- package/dist/cjs/ai/deepseek-provider.js.map +1 -0
- package/dist/cjs/ai/gemini-provider.js +129 -0
- package/dist/cjs/ai/gemini-provider.js.map +1 -0
- package/dist/cjs/ai/groq-provider.js +118 -0
- package/dist/cjs/ai/groq-provider.js.map +1 -0
- package/dist/cjs/ai/meta-provider.js +118 -0
- package/dist/cjs/ai/meta-provider.js.map +1 -0
- package/dist/cjs/ai/ollama-provider.js +127 -0
- package/dist/cjs/ai/ollama-provider.js.map +1 -0
- package/dist/cjs/ai/openai-provider.js +117 -0
- package/dist/cjs/ai/openai-provider.js.map +1 -0
- package/dist/cjs/ai/perplexity-provider.js +118 -0
- package/dist/cjs/ai/perplexity-provider.js.map +1 -0
- package/dist/cjs/ai/prompt-templates.js +174 -0
- package/dist/cjs/ai/prompt-templates.js.map +1 -0
- package/dist/cjs/ai/qwen-provider.js +118 -0
- package/dist/cjs/ai/qwen-provider.js.map +1 -0
- package/dist/cjs/analytics/healing-analytics.js +263 -0
- package/dist/cjs/analytics/healing-analytics.js.map +1 -0
- package/dist/cjs/cli/init.js +517 -0
- package/dist/cjs/cli/init.js.map +1 -0
- package/dist/cjs/config/config-loader.js +135 -0
- package/dist/cjs/config/config-loader.js.map +1 -0
- package/dist/cjs/config/defaults.js +109 -0
- package/dist/cjs/config/defaults.js.map +1 -0
- package/dist/cjs/core/dom-snapshot.js +280 -0
- package/dist/cjs/core/dom-snapshot.js.map +1 -0
- package/dist/cjs/core/enterprise-strategy.js +702 -0
- package/dist/cjs/core/enterprise-strategy.js.map +1 -0
- package/dist/cjs/core/healer.js +283 -0
- package/dist/cjs/core/healer.js.map +1 -0
- package/dist/cjs/core/interceptor.js +945 -0
- package/dist/cjs/core/interceptor.js.map +1 -0
- package/dist/cjs/core/locator-analyzer.js +172 -0
- package/dist/cjs/core/locator-analyzer.js.map +1 -0
- package/dist/cjs/core/locator-strategies.js +891 -0
- package/dist/cjs/core/locator-strategies.js.map +1 -0
- package/dist/cjs/core/self-heal-cache.js +178 -0
- package/dist/cjs/core/self-heal-cache.js.map +1 -0
- package/dist/cjs/core/smart-retry.js +248 -0
- package/dist/cjs/core/smart-retry.js.map +1 -0
- package/dist/cjs/core/visual-verification.js +262 -0
- package/dist/cjs/core/visual-verification.js.map +1 -0
- package/dist/cjs/git/code-modifier.js +184 -0
- package/dist/cjs/git/code-modifier.js.map +1 -0
- package/dist/cjs/git/git-operations.js +145 -0
- package/dist/cjs/git/git-operations.js.map +1 -0
- package/dist/cjs/git/pr-creator.js +190 -0
- package/dist/cjs/git/pr-creator.js.map +1 -0
- package/dist/cjs/index.js +97 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/rag/context-retriever.js +289 -0
- package/dist/cjs/rag/context-retriever.js.map +1 -0
- package/dist/cjs/rag/embeddings.js +82 -0
- package/dist/cjs/rag/embeddings.js.map +1 -0
- package/dist/cjs/rag/knowledge-store.js +159 -0
- package/dist/cjs/rag/knowledge-store.js.map +1 -0
- package/dist/cjs/reporters/heal-report.js +279 -0
- package/dist/cjs/reporters/heal-report.js.map +1 -0
- package/dist/cjs/reporters/heal-reporter.js +294 -0
- package/dist/cjs/reporters/heal-reporter.js.map +1 -0
- package/dist/cjs/server/review-server.js +166 -0
- package/dist/cjs/server/review-server.js.map +1 -0
- package/dist/cjs/server/routes.js +92 -0
- package/dist/cjs/server/routes.js.map +1 -0
- package/dist/cjs/utils/environment.js +57 -0
- package/dist/cjs/utils/environment.js.map +1 -0
- package/dist/cjs/utils/file-lock.js +136 -0
- package/dist/cjs/utils/file-lock.js.map +1 -0
- package/dist/cjs/utils/file-utils.js +49 -0
- package/dist/cjs/utils/file-utils.js.map +1 -0
- package/dist/cjs/utils/logger.js +78 -0
- package/dist/cjs/utils/logger.js.map +1 -0
- package/dist/esm/ai/ai-provider.js +44 -0
- package/dist/esm/ai/ai-provider.js.map +1 -0
- package/dist/esm/ai/anthropic-provider.js +104 -0
- package/dist/esm/ai/anthropic-provider.js.map +1 -0
- package/dist/esm/ai/azure-openai-provider.js +128 -0
- package/dist/esm/ai/azure-openai-provider.js.map +1 -0
- package/dist/esm/ai/bedrock-provider.js +181 -0
- package/dist/esm/ai/bedrock-provider.js.map +1 -0
- package/dist/esm/ai/deepseek-provider.js +116 -0
- package/dist/esm/ai/deepseek-provider.js.map +1 -0
- package/dist/esm/ai/gemini-provider.js +127 -0
- package/dist/esm/ai/gemini-provider.js.map +1 -0
- package/dist/esm/ai/groq-provider.js +116 -0
- package/dist/esm/ai/groq-provider.js.map +1 -0
- package/dist/esm/ai/meta-provider.js +116 -0
- package/dist/esm/ai/meta-provider.js.map +1 -0
- package/dist/esm/ai/ollama-provider.js +125 -0
- package/dist/esm/ai/ollama-provider.js.map +1 -0
- package/dist/esm/ai/openai-provider.js +115 -0
- package/dist/esm/ai/openai-provider.js.map +1 -0
- package/dist/esm/ai/perplexity-provider.js +116 -0
- package/dist/esm/ai/perplexity-provider.js.map +1 -0
- package/dist/esm/ai/prompt-templates.js +171 -0
- package/dist/esm/ai/prompt-templates.js.map +1 -0
- package/dist/esm/ai/qwen-provider.js +116 -0
- package/dist/esm/ai/qwen-provider.js.map +1 -0
- package/dist/esm/analytics/healing-analytics.js +261 -0
- package/dist/esm/analytics/healing-analytics.js.map +1 -0
- package/dist/esm/cli/init.js +495 -0
- package/dist/esm/cli/init.js.map +1 -0
- package/dist/esm/config/config-loader.js +132 -0
- package/dist/esm/config/config-loader.js.map +1 -0
- package/dist/esm/config/defaults.js +107 -0
- package/dist/esm/config/defaults.js.map +1 -0
- package/dist/esm/core/dom-snapshot.js +278 -0
- package/dist/esm/core/dom-snapshot.js.map +1 -0
- package/dist/esm/core/enterprise-strategy.js +695 -0
- package/dist/esm/core/enterprise-strategy.js.map +1 -0
- package/dist/esm/core/healer.js +281 -0
- package/dist/esm/core/healer.js.map +1 -0
- package/dist/esm/core/interceptor.js +940 -0
- package/dist/esm/core/interceptor.js.map +1 -0
- package/dist/esm/core/locator-analyzer.js +169 -0
- package/dist/esm/core/locator-analyzer.js.map +1 -0
- package/dist/esm/core/locator-strategies.js +882 -0
- package/dist/esm/core/locator-strategies.js.map +1 -0
- package/dist/esm/core/self-heal-cache.js +176 -0
- package/dist/esm/core/self-heal-cache.js.map +1 -0
- package/dist/esm/core/smart-retry.js +246 -0
- package/dist/esm/core/smart-retry.js.map +1 -0
- package/dist/esm/core/visual-verification.js +260 -0
- package/dist/esm/core/visual-verification.js.map +1 -0
- package/dist/esm/git/code-modifier.js +182 -0
- package/dist/esm/git/code-modifier.js.map +1 -0
- package/dist/esm/git/git-operations.js +143 -0
- package/dist/esm/git/git-operations.js.map +1 -0
- package/dist/esm/git/pr-creator.js +188 -0
- package/dist/esm/git/pr-creator.js.map +1 -0
- package/dist/esm/index.js +37 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/rag/context-retriever.js +287 -0
- package/dist/esm/rag/context-retriever.js.map +1 -0
- package/dist/esm/rag/embeddings.js +77 -0
- package/dist/esm/rag/embeddings.js.map +1 -0
- package/dist/esm/rag/knowledge-store.js +157 -0
- package/dist/esm/rag/knowledge-store.js.map +1 -0
- package/dist/esm/reporters/heal-report.js +277 -0
- package/dist/esm/reporters/heal-report.js.map +1 -0
- package/dist/esm/reporters/heal-reporter.js +290 -0
- package/dist/esm/reporters/heal-reporter.js.map +1 -0
- package/dist/esm/server/review-server.js +164 -0
- package/dist/esm/server/review-server.js.map +1 -0
- package/dist/esm/server/routes.js +90 -0
- package/dist/esm/server/routes.js.map +1 -0
- package/dist/esm/utils/environment.js +53 -0
- package/dist/esm/utils/environment.js.map +1 -0
- package/dist/esm/utils/file-lock.js +134 -0
- package/dist/esm/utils/file-lock.js.map +1 -0
- package/dist/esm/utils/file-utils.js +43 -0
- package/dist/esm/utils/file-utils.js.map +1 -0
- package/dist/esm/utils/logger.js +75 -0
- package/dist/esm/utils/logger.js.map +1 -0
- package/dist/types/ai/ai-provider.d.ts +4 -0
- package/dist/types/ai/ai-provider.d.ts.map +1 -0
- package/dist/types/ai/anthropic-provider.d.ts +11 -0
- package/dist/types/ai/anthropic-provider.d.ts.map +1 -0
- package/dist/types/ai/azure-openai-provider.d.ts +13 -0
- package/dist/types/ai/azure-openai-provider.d.ts.map +1 -0
- package/dist/types/ai/bedrock-provider.d.ts +14 -0
- package/dist/types/ai/bedrock-provider.d.ts.map +1 -0
- package/dist/types/ai/deepseek-provider.d.ts +12 -0
- package/dist/types/ai/deepseek-provider.d.ts.map +1 -0
- package/dist/types/ai/gemini-provider.d.ts +12 -0
- package/dist/types/ai/gemini-provider.d.ts.map +1 -0
- package/dist/types/ai/groq-provider.d.ts +12 -0
- package/dist/types/ai/groq-provider.d.ts.map +1 -0
- package/dist/types/ai/meta-provider.d.ts +12 -0
- package/dist/types/ai/meta-provider.d.ts.map +1 -0
- package/dist/types/ai/ollama-provider.d.ts +10 -0
- package/dist/types/ai/ollama-provider.d.ts.map +1 -0
- package/dist/types/ai/openai-provider.d.ts +11 -0
- package/dist/types/ai/openai-provider.d.ts.map +1 -0
- package/dist/types/ai/perplexity-provider.d.ts +12 -0
- package/dist/types/ai/perplexity-provider.d.ts.map +1 -0
- package/dist/types/ai/prompt-templates.d.ts +11 -0
- package/dist/types/ai/prompt-templates.d.ts.map +1 -0
- package/dist/types/ai/qwen-provider.d.ts +12 -0
- package/dist/types/ai/qwen-provider.d.ts.map +1 -0
- package/dist/types/analytics/healing-analytics.d.ts +36 -0
- package/dist/types/analytics/healing-analytics.d.ts.map +1 -0
- package/dist/types/cli/init.d.ts +15 -0
- package/dist/types/cli/init.d.ts.map +1 -0
- package/dist/types/config/config-loader.d.ts +4 -0
- package/dist/types/config/config-loader.d.ts.map +1 -0
- package/dist/types/config/defaults.d.ts +3 -0
- package/dist/types/config/defaults.d.ts.map +1 -0
- package/dist/types/core/dom-snapshot.d.ts +12 -0
- package/dist/types/core/dom-snapshot.d.ts.map +1 -0
- package/dist/types/core/enterprise-strategy.d.ts +56 -0
- package/dist/types/core/enterprise-strategy.d.ts.map +1 -0
- package/dist/types/core/healer.d.ts +52 -0
- package/dist/types/core/healer.d.ts.map +1 -0
- package/dist/types/core/interceptor.d.ts +64 -0
- package/dist/types/core/interceptor.d.ts.map +1 -0
- package/dist/types/core/locator-analyzer.d.ts +31 -0
- package/dist/types/core/locator-analyzer.d.ts.map +1 -0
- package/dist/types/core/locator-strategies.d.ts +45 -0
- package/dist/types/core/locator-strategies.d.ts.map +1 -0
- package/dist/types/core/self-heal-cache.d.ts +51 -0
- package/dist/types/core/self-heal-cache.d.ts.map +1 -0
- package/dist/types/core/smart-retry.d.ts +64 -0
- package/dist/types/core/smart-retry.d.ts.map +1 -0
- package/dist/types/core/visual-verification.d.ts +46 -0
- package/dist/types/core/visual-verification.d.ts.map +1 -0
- package/dist/types/git/code-modifier.d.ts +51 -0
- package/dist/types/git/code-modifier.d.ts.map +1 -0
- package/dist/types/git/git-operations.d.ts +40 -0
- package/dist/types/git/git-operations.d.ts.map +1 -0
- package/dist/types/git/pr-creator.d.ts +27 -0
- package/dist/types/git/pr-creator.d.ts.map +1 -0
- package/dist/types/index.d.ts +40 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/rag/context-retriever.d.ts +69 -0
- package/dist/types/rag/context-retriever.d.ts.map +1 -0
- package/dist/types/rag/embeddings.d.ts +32 -0
- package/dist/types/rag/embeddings.d.ts.map +1 -0
- package/dist/types/rag/index.d.ts +12 -0
- package/dist/types/rag/index.d.ts.map +1 -0
- package/dist/types/rag/knowledge-store.d.ts +38 -0
- package/dist/types/rag/knowledge-store.d.ts.map +1 -0
- package/dist/types/reporters/heal-report.d.ts +29 -0
- package/dist/types/reporters/heal-report.d.ts.map +1 -0
- package/dist/types/reporters/heal-reporter.d.ts +49 -0
- package/dist/types/reporters/heal-reporter.d.ts.map +1 -0
- package/dist/types/server/review-server.d.ts +20 -0
- package/dist/types/server/review-server.d.ts.map +1 -0
- package/dist/types/server/routes.d.ts +4 -0
- package/dist/types/server/routes.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +433 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/utils/environment.d.ts +10 -0
- package/dist/types/utils/environment.d.ts.map +1 -0
- package/dist/types/utils/file-lock.d.ts +37 -0
- package/dist/types/utils/file-lock.d.ts.map +1 -0
- package/dist/types/utils/file-utils.d.ts +7 -0
- package/dist/types/utils/file-utils.d.ts.map +1 -0
- package/dist/types/utils/logger.d.ts +9 -0
- package/dist/types/utils/logger.d.ts.map +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var logger = require('../utils/logger.js');
|
|
4
|
+
var fileUtils = require('../utils/file-utils.js');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Visual Verification
|
|
8
|
+
*
|
|
9
|
+
* After healing a locator, captures a screenshot of the healed element and
|
|
10
|
+
* verifies it's visually the correct element. This prevents "wrong element"
|
|
11
|
+
* heals where the selector matches something in a different part of the page.
|
|
12
|
+
*
|
|
13
|
+
* Checks:
|
|
14
|
+
* 1. Element is visible on the page
|
|
15
|
+
* 2. Element is within the viewport (not scrolled off-screen)
|
|
16
|
+
* 3. Element has a reasonable bounding box (not 0x0)
|
|
17
|
+
* 4. Screenshot capture succeeds (element is rendered)
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Visual Verification engine for healed locators.
|
|
21
|
+
*/
|
|
22
|
+
class VisualVerifier {
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Verify that a healed locator points to a visually valid element.
|
|
28
|
+
*
|
|
29
|
+
* Returns a VisualVerificationResult with:
|
|
30
|
+
* - `verified`: true if the element passes all visual checks
|
|
31
|
+
* - `elementScreenshotPath`: path to element screenshot (if captured)
|
|
32
|
+
* - `boundingBox`: element's position and size on page
|
|
33
|
+
* - `elementVisible`: whether the element is visible
|
|
34
|
+
* - `elementInViewport`: whether the element is within the viewport
|
|
35
|
+
*/
|
|
36
|
+
async verify(page, healedLocator, eventId) {
|
|
37
|
+
if (!this.config.enabled) {
|
|
38
|
+
return {
|
|
39
|
+
verified: true, // Skip verification, assume valid
|
|
40
|
+
elementScreenshotPath: null,
|
|
41
|
+
fullPageScreenshotPath: null,
|
|
42
|
+
boundingBox: null,
|
|
43
|
+
elementVisible: true,
|
|
44
|
+
elementInViewport: true,
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const result = {
|
|
49
|
+
verified: false,
|
|
50
|
+
elementScreenshotPath: null,
|
|
51
|
+
fullPageScreenshotPath: null,
|
|
52
|
+
boundingBox: null,
|
|
53
|
+
elementVisible: false,
|
|
54
|
+
elementInViewport: false,
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
const locator = page.locator(healedLocator.selector);
|
|
59
|
+
// 1. Check element count
|
|
60
|
+
const count = await locator.count();
|
|
61
|
+
if (count === 0) {
|
|
62
|
+
logger.logger.warn(`[Visual] Healed locator resolved to 0 elements: ${healedLocator.selector}`);
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
const element = locator.first();
|
|
66
|
+
// 2. Check visibility
|
|
67
|
+
try {
|
|
68
|
+
result.elementVisible = await element.isVisible();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
result.elementVisible = false;
|
|
72
|
+
}
|
|
73
|
+
if (!result.elementVisible) {
|
|
74
|
+
logger.logger.warn(`[Visual] Healed element is not visible: ${healedLocator.selector}`);
|
|
75
|
+
// Still partially verified — element exists but not visible
|
|
76
|
+
}
|
|
77
|
+
// 3. Get bounding box
|
|
78
|
+
try {
|
|
79
|
+
const box = await element.boundingBox();
|
|
80
|
+
if (box) {
|
|
81
|
+
result.boundingBox = {
|
|
82
|
+
x: box.x,
|
|
83
|
+
y: box.y,
|
|
84
|
+
width: box.width,
|
|
85
|
+
height: box.height,
|
|
86
|
+
};
|
|
87
|
+
// Check for zero-size elements
|
|
88
|
+
if (box.width === 0 || box.height === 0) {
|
|
89
|
+
logger.logger.warn(`[Visual] Healed element has zero dimensions: ${box.width}x${box.height}`);
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
// Check if element is in viewport
|
|
93
|
+
const viewport = page.viewportSize();
|
|
94
|
+
if (viewport) {
|
|
95
|
+
result.elementInViewport =
|
|
96
|
+
box.x >= -box.width &&
|
|
97
|
+
box.y >= -box.height &&
|
|
98
|
+
box.x < viewport.width + box.width &&
|
|
99
|
+
box.y < viewport.height + box.height;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
result.elementInViewport = true; // Can't check, assume true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
108
|
+
logger.logger.debug(`[Visual] Could not get bounding box: ${msg}`);
|
|
109
|
+
}
|
|
110
|
+
// 4. Capture element screenshot
|
|
111
|
+
if (this.config.captureElement) {
|
|
112
|
+
try {
|
|
113
|
+
fileUtils.ensureDirectory(this.config.screenshotDir);
|
|
114
|
+
const sanitizedId = eventId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
115
|
+
const elementPath = `${this.config.screenshotDir}/${sanitizedId}_element.png`;
|
|
116
|
+
await element.screenshot({
|
|
117
|
+
path: elementPath,
|
|
118
|
+
timeout: 5000,
|
|
119
|
+
});
|
|
120
|
+
result.elementScreenshotPath = elementPath;
|
|
121
|
+
logger.logger.debug(`[Visual] Element screenshot saved: ${elementPath}`);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
125
|
+
logger.logger.debug(`[Visual] Element screenshot failed: ${msg}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// 5. Capture full page screenshot for context
|
|
129
|
+
if (this.config.captureFullPage) {
|
|
130
|
+
try {
|
|
131
|
+
fileUtils.ensureDirectory(this.config.screenshotDir);
|
|
132
|
+
const sanitizedId = eventId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
133
|
+
const pagePath = `${this.config.screenshotDir}/${sanitizedId}_page.png`;
|
|
134
|
+
await page.screenshot({
|
|
135
|
+
path: pagePath,
|
|
136
|
+
fullPage: false,
|
|
137
|
+
timeout: 10000,
|
|
138
|
+
});
|
|
139
|
+
result.fullPageScreenshotPath = pagePath;
|
|
140
|
+
logger.logger.debug(`[Visual] Full page screenshot saved: ${pagePath}`);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
144
|
+
logger.logger.debug(`[Visual] Full page screenshot failed: ${msg}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// 6. Final verification decision
|
|
148
|
+
result.verified = result.elementVisible && result.boundingBox !== null;
|
|
149
|
+
if (result.verified) {
|
|
150
|
+
logger.logger.info(`[Visual] Verification passed for "${healedLocator.selector}" ` +
|
|
151
|
+
`(${result.boundingBox.width}x${result.boundingBox.height} at ${result.boundingBox.x},${result.boundingBox.y})`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
logger.logger.warn(`[Visual] Verification failed for "${healedLocator.selector}" ` +
|
|
155
|
+
`(visible=${result.elementVisible}, box=${result.boundingBox !== null})`);
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
161
|
+
logger.logger.warn(`[Visual] Verification error: ${msg}`);
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Compare two screenshots at the pixel level.
|
|
167
|
+
* Returns a similarity score (0-1).
|
|
168
|
+
*
|
|
169
|
+
* This is a lightweight comparison using a sampling approach
|
|
170
|
+
* (no external image processing libraries required).
|
|
171
|
+
*/
|
|
172
|
+
async compareScreenshots(page, screenshotA, screenshotB) {
|
|
173
|
+
// Use page.evaluate to leverage canvas API for comparison
|
|
174
|
+
try {
|
|
175
|
+
const similarity = await page.evaluate(async ({ a, b }) => {
|
|
176
|
+
// Create images from buffers
|
|
177
|
+
const loadImage = (data) => {
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
const blob = new Blob([new Uint8Array(data)], { type: 'image/png' });
|
|
180
|
+
const url = URL.createObjectURL(blob);
|
|
181
|
+
const img = new Image();
|
|
182
|
+
img.onload = () => {
|
|
183
|
+
URL.revokeObjectURL(url);
|
|
184
|
+
resolve(img);
|
|
185
|
+
};
|
|
186
|
+
img.onerror = reject;
|
|
187
|
+
img.src = url;
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const imgA = await loadImage(a);
|
|
191
|
+
const imgB = await loadImage(b);
|
|
192
|
+
// Create canvas for comparison
|
|
193
|
+
const canvas = document.createElement('canvas');
|
|
194
|
+
const ctx = canvas.getContext('2d');
|
|
195
|
+
if (!ctx)
|
|
196
|
+
return 0;
|
|
197
|
+
const width = Math.min(imgA.width, imgB.width);
|
|
198
|
+
const height = Math.min(imgA.height, imgB.height);
|
|
199
|
+
canvas.width = width;
|
|
200
|
+
canvas.height = height;
|
|
201
|
+
// Draw and get pixel data for A
|
|
202
|
+
ctx.drawImage(imgA, 0, 0);
|
|
203
|
+
const dataA = ctx.getImageData(0, 0, width, height).data;
|
|
204
|
+
// Draw and get pixel data for B
|
|
205
|
+
ctx.clearRect(0, 0, width, height);
|
|
206
|
+
ctx.drawImage(imgB, 0, 0);
|
|
207
|
+
const dataB = ctx.getImageData(0, 0, width, height).data;
|
|
208
|
+
// Sample pixels for comparison (every 4th pixel for speed)
|
|
209
|
+
let matchCount = 0;
|
|
210
|
+
let sampleCount = 0;
|
|
211
|
+
const step = 16; // Sample every 16 bytes (4 RGBA channels × 4 pixels)
|
|
212
|
+
for (let i = 0; i < dataA.length; i += step) {
|
|
213
|
+
const diffR = Math.abs(dataA[i] - dataB[i]);
|
|
214
|
+
const diffG = Math.abs(dataA[i + 1] - dataB[i + 1]);
|
|
215
|
+
const diffB = Math.abs(dataA[i + 2] - dataB[i + 2]);
|
|
216
|
+
const avgDiff = (diffR + diffG + diffB) / (3 * 255);
|
|
217
|
+
if (avgDiff < 0.1)
|
|
218
|
+
matchCount++;
|
|
219
|
+
sampleCount++;
|
|
220
|
+
}
|
|
221
|
+
return sampleCount > 0 ? matchCount / sampleCount : 0;
|
|
222
|
+
}, { a: Array.from(screenshotA), b: Array.from(screenshotB) });
|
|
223
|
+
return similarity;
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
227
|
+
logger.logger.debug(`[Visual] Screenshot comparison failed: ${msg}`);
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Cleanup old screenshots if keepScreenshots is false.
|
|
233
|
+
*/
|
|
234
|
+
cleanup() {
|
|
235
|
+
if (this.config.keepScreenshots)
|
|
236
|
+
return;
|
|
237
|
+
try {
|
|
238
|
+
const { readdirSync, unlinkSync } = require('fs');
|
|
239
|
+
const { join } = require('path');
|
|
240
|
+
if (!require('fs').existsSync(this.config.screenshotDir))
|
|
241
|
+
return;
|
|
242
|
+
const files = readdirSync(this.config.screenshotDir);
|
|
243
|
+
for (const file of files) {
|
|
244
|
+
if (file.endsWith('.png')) {
|
|
245
|
+
try {
|
|
246
|
+
unlinkSync(join(this.config.screenshotDir, file));
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// Ignore cleanup errors
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
logger.logger.debug('[Visual] Cleaned up verification screenshots');
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// Ignore
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
exports.VisualVerifier = VisualVerifier;
|
|
262
|
+
//# sourceMappingURL=visual-verification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-verification.js","sources":["../../../../src/core/visual-verification.ts"],"sourcesContent":[null],"names":["logger","ensureDirectory"],"mappings":"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"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tsMorph = require('ts-morph');
|
|
4
|
+
var logger = require('../utils/logger.js');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* AST-based source code modifier that updates Playwright locators in test files.
|
|
8
|
+
* Uses ts-morph to parse and manipulate TypeScript ASTs so that only the target
|
|
9
|
+
* locator expression is replaced while surrounding code is fully preserved.
|
|
10
|
+
*/
|
|
11
|
+
class CodeModifier {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.project = new tsMorph.Project({
|
|
14
|
+
compilerOptions: { strict: true },
|
|
15
|
+
useInMemoryFileSystem: false,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Modify a single locator in the source file described by `modification`.
|
|
21
|
+
*/
|
|
22
|
+
async modifyLocator(modification) {
|
|
23
|
+
const { filePath, line, originalCode, modifiedCode } = modification;
|
|
24
|
+
logger.logger.info(`Modifying locator in ${filePath}:${line}`);
|
|
25
|
+
logger.logger.debug('Original locator', originalCode);
|
|
26
|
+
logger.logger.debug('New locator', modifiedCode);
|
|
27
|
+
try {
|
|
28
|
+
const sourceFile = this.getOrAddSourceFile(filePath);
|
|
29
|
+
const text = sourceFile.getFullText();
|
|
30
|
+
// Strategy 1: Try AST-based replacement for known Playwright patterns.
|
|
31
|
+
const replaced = this.tryASTReplace(text, line, originalCode, modifiedCode);
|
|
32
|
+
if (replaced !== null) {
|
|
33
|
+
sourceFile.replaceWithText(replaced);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Strategy 2: Fall back to direct text replacement scoped to the target line.
|
|
37
|
+
const lineReplaced = this.replaceAtLine(text, line, originalCode, modifiedCode);
|
|
38
|
+
sourceFile.replaceWithText(lineReplaced);
|
|
39
|
+
}
|
|
40
|
+
await sourceFile.save();
|
|
41
|
+
logger.logger.info(`Locator updated in ${filePath}:${line}`);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
logger.logger.error(`[MindHeal] Failed to modify locator in ${filePath}:${line}: ${message}`);
|
|
46
|
+
throw new Error(`[MindHeal] Failed to modify locator in ${filePath}:${line}: ${message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generate a human-readable unified-diff-style string for a single modification.
|
|
51
|
+
*/
|
|
52
|
+
generateDiff(modification) {
|
|
53
|
+
const { filePath, line, originalCode, modifiedCode } = modification;
|
|
54
|
+
const header = `--- a/${filePath}\n+++ b/${filePath}`;
|
|
55
|
+
const hunk = `@@ -${line},1 +${line},1 @@`;
|
|
56
|
+
const removal = `- ${originalCode}`;
|
|
57
|
+
const addition = `+ ${modifiedCode}`;
|
|
58
|
+
return [header, hunk, removal, addition].join('\n');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Apply multiple modifications, grouping them by file to minimize I/O.
|
|
62
|
+
* Modifications within the same file are applied from bottom to top
|
|
63
|
+
* (descending line number) so that earlier line numbers stay valid.
|
|
64
|
+
*/
|
|
65
|
+
async applyAllModifications(modifications) {
|
|
66
|
+
if (modifications.length === 0) {
|
|
67
|
+
logger.logger.warn('[MindHeal] No modifications to apply');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Group by file path.
|
|
71
|
+
const grouped = new Map();
|
|
72
|
+
for (const mod of modifications) {
|
|
73
|
+
const existing = grouped.get(mod.filePath) ?? [];
|
|
74
|
+
existing.push(mod);
|
|
75
|
+
grouped.set(mod.filePath, existing);
|
|
76
|
+
}
|
|
77
|
+
logger.logger.info(`Applying ${modifications.length} modification(s) across ${grouped.size} file(s)`);
|
|
78
|
+
for (const [filePath, fileMods] of grouped) {
|
|
79
|
+
// Sort descending by line so replacements don't shift earlier positions.
|
|
80
|
+
const sorted = [...fileMods].sort((a, b) => b.line - a.line);
|
|
81
|
+
for (const mod of sorted) {
|
|
82
|
+
try {
|
|
83
|
+
await this.modifyLocator(mod);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
+
logger.logger.error(`[MindHeal] Skipping failed modification in ${filePath}:${mod.line}: ${message}`);
|
|
88
|
+
// Continue with remaining modifications in the same file.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
logger.logger.info('All modifications applied');
|
|
93
|
+
}
|
|
94
|
+
// ── Private helpers ─────────────────────────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Add (or retrieve) a ts-morph SourceFile for the given path.
|
|
97
|
+
*/
|
|
98
|
+
getOrAddSourceFile(filePath) {
|
|
99
|
+
const existing = this.project.getSourceFile(filePath);
|
|
100
|
+
if (existing) {
|
|
101
|
+
// Refresh from disk in case a prior modification changed it.
|
|
102
|
+
existing.refreshFromFileSystemSync();
|
|
103
|
+
return existing;
|
|
104
|
+
}
|
|
105
|
+
return this.project.addSourceFileAtPath(filePath);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Attempt to locate the original locator expression in the AST at the given
|
|
109
|
+
* line and replace it. Returns the new full text on success or `null` if the
|
|
110
|
+
* expression could not be found via AST traversal.
|
|
111
|
+
*
|
|
112
|
+
* Supports Playwright patterns:
|
|
113
|
+
* page.locator('selector')
|
|
114
|
+
* page.getByRole('role', { name: 'value' })
|
|
115
|
+
* page.getByText('text')
|
|
116
|
+
* page.getByTestId('id')
|
|
117
|
+
* Chained: page.locator('parent').locator('child')
|
|
118
|
+
*/
|
|
119
|
+
tryASTReplace(fullText, targetLine, originalCode, modifiedCode) {
|
|
120
|
+
// Create a temporary in-memory source for AST analysis without side-effects.
|
|
121
|
+
const tempFile = this.project.createSourceFile('__temp_analysis__.ts', fullText, {
|
|
122
|
+
overwrite: true,
|
|
123
|
+
});
|
|
124
|
+
try {
|
|
125
|
+
const calls = tempFile.getDescendantsOfKind(tsMorph.SyntaxKind.CallExpression);
|
|
126
|
+
for (const call of calls) {
|
|
127
|
+
const startLine = call.getStartLineNumber();
|
|
128
|
+
if (startLine !== targetLine)
|
|
129
|
+
continue;
|
|
130
|
+
const callText = call.getText();
|
|
131
|
+
// Check if this call (or its ancestor chain) matches the original code.
|
|
132
|
+
if (this.normalizeWhitespace(callText) === this.normalizeWhitespace(originalCode)) {
|
|
133
|
+
const start = call.getStart();
|
|
134
|
+
const end = call.getEnd();
|
|
135
|
+
return fullText.substring(0, start) + modifiedCode + fullText.substring(end);
|
|
136
|
+
}
|
|
137
|
+
// The original code might be a parent expression that contains this call
|
|
138
|
+
// (e.g., chained locators). Walk upward.
|
|
139
|
+
let ancestor = call.getParent();
|
|
140
|
+
while (ancestor) {
|
|
141
|
+
if (ancestor.getStartLineNumber() === targetLine &&
|
|
142
|
+
this.normalizeWhitespace(ancestor.getText()) ===
|
|
143
|
+
this.normalizeWhitespace(originalCode)) {
|
|
144
|
+
const start = ancestor.getStart();
|
|
145
|
+
const end = ancestor.getEnd();
|
|
146
|
+
return fullText.substring(0, start) + modifiedCode + fullText.substring(end);
|
|
147
|
+
}
|
|
148
|
+
ancestor = ancestor.getParent();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
this.project.removeSourceFile(tempFile);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Fallback: perform a direct string replacement on the specific line of `fullText`.
|
|
159
|
+
* Only the first occurrence on the target line is replaced to avoid unintended changes.
|
|
160
|
+
*/
|
|
161
|
+
replaceAtLine(fullText, targetLine, originalCode, modifiedCode) {
|
|
162
|
+
const lines = fullText.split('\n');
|
|
163
|
+
const lineIndex = targetLine - 1; // lines are 1-based
|
|
164
|
+
if (lineIndex < 0 || lineIndex >= lines.length) {
|
|
165
|
+
throw new Error(`[MindHeal] Line ${targetLine} is out of range (file has ${lines.length} lines)`);
|
|
166
|
+
}
|
|
167
|
+
const lineText = lines[lineIndex];
|
|
168
|
+
if (!lineText.includes(originalCode)) {
|
|
169
|
+
throw new Error(`[MindHeal] Original locator not found on line ${targetLine}. ` +
|
|
170
|
+
`Expected to find: ${originalCode}`);
|
|
171
|
+
}
|
|
172
|
+
lines[lineIndex] = lineText.replace(originalCode, modifiedCode);
|
|
173
|
+
return lines.join('\n');
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Collapse all whitespace to single spaces for comparison purposes.
|
|
177
|
+
*/
|
|
178
|
+
normalizeWhitespace(str) {
|
|
179
|
+
return str.replace(/\s+/g, ' ').trim();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
exports.CodeModifier = CodeModifier;
|
|
184
|
+
//# sourceMappingURL=code-modifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-modifier.js","sources":["../../../../src/git/code-modifier.ts"],"sourcesContent":[null],"names":["Project","logger","SyntaxKind"],"mappings":"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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var simpleGit = require('simple-git');
|
|
4
|
+
var logger = require('../utils/logger.js');
|
|
5
|
+
|
|
6
|
+
class GitOperations {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.git = simpleGit();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generate a branch name using the configured prefix and current timestamp.
|
|
13
|
+
* Format: {prefix}-{YYYYMMDD}-{HHmmss}
|
|
14
|
+
*/
|
|
15
|
+
generateBranchName() {
|
|
16
|
+
const prefix = this.config.branchPrefix ?? 'mindheal/auto-fix';
|
|
17
|
+
const now = new Date();
|
|
18
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
19
|
+
const timestamp = [
|
|
20
|
+
now.getFullYear(),
|
|
21
|
+
pad(now.getMonth() + 1),
|
|
22
|
+
pad(now.getDate()),
|
|
23
|
+
'-',
|
|
24
|
+
pad(now.getHours()),
|
|
25
|
+
pad(now.getMinutes()),
|
|
26
|
+
pad(now.getSeconds()),
|
|
27
|
+
].join('');
|
|
28
|
+
return `${prefix}-${timestamp}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create a new branch and check it out.
|
|
32
|
+
*/
|
|
33
|
+
async createBranch(branchName) {
|
|
34
|
+
try {
|
|
35
|
+
logger.logger.info(`Creating and checking out branch: ${branchName}`);
|
|
36
|
+
await this.git.checkoutLocalBranch(branchName);
|
|
37
|
+
logger.logger.info(`Branch created: ${branchName}`);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
41
|
+
logger.logger.error(`[MindHeal] Failed to create branch "${branchName}": ${message}`);
|
|
42
|
+
throw new Error(`[MindHeal] Failed to create branch "${branchName}": ${message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Stage specified files and create a commit.
|
|
47
|
+
*/
|
|
48
|
+
async commitChanges(files, message) {
|
|
49
|
+
if (files.length === 0) {
|
|
50
|
+
logger.logger.warn('[MindHeal] No files provided for commit, skipping');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const prefix = this.config.commitMessagePrefix ?? '[MindHeal]';
|
|
55
|
+
const fullMessage = `${prefix} ${message}`;
|
|
56
|
+
logger.logger.info(`Staging ${files.length} file(s) for commit`);
|
|
57
|
+
await this.git.add(files);
|
|
58
|
+
logger.logger.info(`Committing with message: ${fullMessage}`);
|
|
59
|
+
await this.git.commit(fullMessage);
|
|
60
|
+
logger.logger.info('Commit successful');
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const message_ = error instanceof Error ? error.message : String(error);
|
|
64
|
+
logger.logger.error(`[MindHeal] Failed to commit changes: ${message_}`);
|
|
65
|
+
throw new Error(`[MindHeal] Failed to commit changes: ${message_}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Push a branch to the remote origin.
|
|
70
|
+
*/
|
|
71
|
+
async pushBranch(branchName) {
|
|
72
|
+
try {
|
|
73
|
+
logger.logger.info(`Pushing branch "${branchName}" to origin`);
|
|
74
|
+
await this.git.push('origin', branchName, ['--set-upstream']);
|
|
75
|
+
logger.logger.info(`Branch "${branchName}" pushed successfully`);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
79
|
+
logger.logger.error(`[MindHeal] Failed to push branch "${branchName}": ${message}`);
|
|
80
|
+
throw new Error(`[MindHeal] Failed to push branch "${branchName}": ${message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the name of the currently checked-out branch.
|
|
85
|
+
*/
|
|
86
|
+
async getCurrentBranch() {
|
|
87
|
+
try {
|
|
88
|
+
const branchSummary = await this.git.branchLocal();
|
|
89
|
+
return branchSummary.current;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
93
|
+
logger.logger.error(`[MindHeal] Failed to get current branch: ${message}`);
|
|
94
|
+
throw new Error(`[MindHeal] Failed to get current branch: ${message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Switch to an existing branch.
|
|
99
|
+
*/
|
|
100
|
+
async switchBranch(branchName) {
|
|
101
|
+
try {
|
|
102
|
+
logger.logger.info(`Switching to branch: ${branchName}`);
|
|
103
|
+
await this.git.checkout(branchName);
|
|
104
|
+
logger.logger.info(`Switched to branch: ${branchName}`);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
108
|
+
logger.logger.error(`[MindHeal] Failed to switch to branch "${branchName}": ${message}`);
|
|
109
|
+
throw new Error(`[MindHeal] Failed to switch to branch "${branchName}": ${message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Stash all uncommitted changes (tracked and untracked).
|
|
114
|
+
*/
|
|
115
|
+
async stashChanges() {
|
|
116
|
+
try {
|
|
117
|
+
logger.logger.info('Stashing uncommitted changes');
|
|
118
|
+
await this.git.stash(['push', '--include-untracked']);
|
|
119
|
+
logger.logger.info('Changes stashed');
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
logger.logger.error(`[MindHeal] Failed to stash changes: ${message}`);
|
|
124
|
+
throw new Error(`[MindHeal] Failed to stash changes: ${message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Pop the most recent stash entry.
|
|
129
|
+
*/
|
|
130
|
+
async popStash() {
|
|
131
|
+
try {
|
|
132
|
+
logger.logger.info('Popping stashed changes');
|
|
133
|
+
await this.git.stash(['pop']);
|
|
134
|
+
logger.logger.info('Stash applied and dropped');
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
138
|
+
logger.logger.error(`[MindHeal] Failed to pop stash: ${message}`);
|
|
139
|
+
throw new Error(`[MindHeal] Failed to pop stash: ${message}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
exports.GitOperations = GitOperations;
|
|
145
|
+
//# sourceMappingURL=git-operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-operations.js","sources":["../../../../src/git/git-operations.ts"],"sourcesContent":[null],"names":["logger"],"mappings":"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"}
|