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.
Files changed (255) hide show
  1. package/.env.example +48 -0
  2. package/CHANGELOG.md +27 -0
  3. package/LICENSE +21 -0
  4. package/README.md +481 -0
  5. package/dist/cjs/ai/ai-provider.js +46 -0
  6. package/dist/cjs/ai/ai-provider.js.map +1 -0
  7. package/dist/cjs/ai/anthropic-provider.js +106 -0
  8. package/dist/cjs/ai/anthropic-provider.js.map +1 -0
  9. package/dist/cjs/ai/azure-openai-provider.js +130 -0
  10. package/dist/cjs/ai/azure-openai-provider.js.map +1 -0
  11. package/dist/cjs/ai/bedrock-provider.js +183 -0
  12. package/dist/cjs/ai/bedrock-provider.js.map +1 -0
  13. package/dist/cjs/ai/deepseek-provider.js +118 -0
  14. package/dist/cjs/ai/deepseek-provider.js.map +1 -0
  15. package/dist/cjs/ai/gemini-provider.js +129 -0
  16. package/dist/cjs/ai/gemini-provider.js.map +1 -0
  17. package/dist/cjs/ai/groq-provider.js +118 -0
  18. package/dist/cjs/ai/groq-provider.js.map +1 -0
  19. package/dist/cjs/ai/meta-provider.js +118 -0
  20. package/dist/cjs/ai/meta-provider.js.map +1 -0
  21. package/dist/cjs/ai/ollama-provider.js +127 -0
  22. package/dist/cjs/ai/ollama-provider.js.map +1 -0
  23. package/dist/cjs/ai/openai-provider.js +117 -0
  24. package/dist/cjs/ai/openai-provider.js.map +1 -0
  25. package/dist/cjs/ai/perplexity-provider.js +118 -0
  26. package/dist/cjs/ai/perplexity-provider.js.map +1 -0
  27. package/dist/cjs/ai/prompt-templates.js +174 -0
  28. package/dist/cjs/ai/prompt-templates.js.map +1 -0
  29. package/dist/cjs/ai/qwen-provider.js +118 -0
  30. package/dist/cjs/ai/qwen-provider.js.map +1 -0
  31. package/dist/cjs/analytics/healing-analytics.js +263 -0
  32. package/dist/cjs/analytics/healing-analytics.js.map +1 -0
  33. package/dist/cjs/cli/init.js +517 -0
  34. package/dist/cjs/cli/init.js.map +1 -0
  35. package/dist/cjs/config/config-loader.js +135 -0
  36. package/dist/cjs/config/config-loader.js.map +1 -0
  37. package/dist/cjs/config/defaults.js +109 -0
  38. package/dist/cjs/config/defaults.js.map +1 -0
  39. package/dist/cjs/core/dom-snapshot.js +280 -0
  40. package/dist/cjs/core/dom-snapshot.js.map +1 -0
  41. package/dist/cjs/core/enterprise-strategy.js +702 -0
  42. package/dist/cjs/core/enterprise-strategy.js.map +1 -0
  43. package/dist/cjs/core/healer.js +283 -0
  44. package/dist/cjs/core/healer.js.map +1 -0
  45. package/dist/cjs/core/interceptor.js +945 -0
  46. package/dist/cjs/core/interceptor.js.map +1 -0
  47. package/dist/cjs/core/locator-analyzer.js +172 -0
  48. package/dist/cjs/core/locator-analyzer.js.map +1 -0
  49. package/dist/cjs/core/locator-strategies.js +891 -0
  50. package/dist/cjs/core/locator-strategies.js.map +1 -0
  51. package/dist/cjs/core/self-heal-cache.js +178 -0
  52. package/dist/cjs/core/self-heal-cache.js.map +1 -0
  53. package/dist/cjs/core/smart-retry.js +248 -0
  54. package/dist/cjs/core/smart-retry.js.map +1 -0
  55. package/dist/cjs/core/visual-verification.js +262 -0
  56. package/dist/cjs/core/visual-verification.js.map +1 -0
  57. package/dist/cjs/git/code-modifier.js +184 -0
  58. package/dist/cjs/git/code-modifier.js.map +1 -0
  59. package/dist/cjs/git/git-operations.js +145 -0
  60. package/dist/cjs/git/git-operations.js.map +1 -0
  61. package/dist/cjs/git/pr-creator.js +190 -0
  62. package/dist/cjs/git/pr-creator.js.map +1 -0
  63. package/dist/cjs/index.js +97 -0
  64. package/dist/cjs/index.js.map +1 -0
  65. package/dist/cjs/rag/context-retriever.js +289 -0
  66. package/dist/cjs/rag/context-retriever.js.map +1 -0
  67. package/dist/cjs/rag/embeddings.js +82 -0
  68. package/dist/cjs/rag/embeddings.js.map +1 -0
  69. package/dist/cjs/rag/knowledge-store.js +159 -0
  70. package/dist/cjs/rag/knowledge-store.js.map +1 -0
  71. package/dist/cjs/reporters/heal-report.js +279 -0
  72. package/dist/cjs/reporters/heal-report.js.map +1 -0
  73. package/dist/cjs/reporters/heal-reporter.js +294 -0
  74. package/dist/cjs/reporters/heal-reporter.js.map +1 -0
  75. package/dist/cjs/server/review-server.js +166 -0
  76. package/dist/cjs/server/review-server.js.map +1 -0
  77. package/dist/cjs/server/routes.js +92 -0
  78. package/dist/cjs/server/routes.js.map +1 -0
  79. package/dist/cjs/utils/environment.js +57 -0
  80. package/dist/cjs/utils/environment.js.map +1 -0
  81. package/dist/cjs/utils/file-lock.js +136 -0
  82. package/dist/cjs/utils/file-lock.js.map +1 -0
  83. package/dist/cjs/utils/file-utils.js +49 -0
  84. package/dist/cjs/utils/file-utils.js.map +1 -0
  85. package/dist/cjs/utils/logger.js +78 -0
  86. package/dist/cjs/utils/logger.js.map +1 -0
  87. package/dist/esm/ai/ai-provider.js +44 -0
  88. package/dist/esm/ai/ai-provider.js.map +1 -0
  89. package/dist/esm/ai/anthropic-provider.js +104 -0
  90. package/dist/esm/ai/anthropic-provider.js.map +1 -0
  91. package/dist/esm/ai/azure-openai-provider.js +128 -0
  92. package/dist/esm/ai/azure-openai-provider.js.map +1 -0
  93. package/dist/esm/ai/bedrock-provider.js +181 -0
  94. package/dist/esm/ai/bedrock-provider.js.map +1 -0
  95. package/dist/esm/ai/deepseek-provider.js +116 -0
  96. package/dist/esm/ai/deepseek-provider.js.map +1 -0
  97. package/dist/esm/ai/gemini-provider.js +127 -0
  98. package/dist/esm/ai/gemini-provider.js.map +1 -0
  99. package/dist/esm/ai/groq-provider.js +116 -0
  100. package/dist/esm/ai/groq-provider.js.map +1 -0
  101. package/dist/esm/ai/meta-provider.js +116 -0
  102. package/dist/esm/ai/meta-provider.js.map +1 -0
  103. package/dist/esm/ai/ollama-provider.js +125 -0
  104. package/dist/esm/ai/ollama-provider.js.map +1 -0
  105. package/dist/esm/ai/openai-provider.js +115 -0
  106. package/dist/esm/ai/openai-provider.js.map +1 -0
  107. package/dist/esm/ai/perplexity-provider.js +116 -0
  108. package/dist/esm/ai/perplexity-provider.js.map +1 -0
  109. package/dist/esm/ai/prompt-templates.js +171 -0
  110. package/dist/esm/ai/prompt-templates.js.map +1 -0
  111. package/dist/esm/ai/qwen-provider.js +116 -0
  112. package/dist/esm/ai/qwen-provider.js.map +1 -0
  113. package/dist/esm/analytics/healing-analytics.js +261 -0
  114. package/dist/esm/analytics/healing-analytics.js.map +1 -0
  115. package/dist/esm/cli/init.js +495 -0
  116. package/dist/esm/cli/init.js.map +1 -0
  117. package/dist/esm/config/config-loader.js +132 -0
  118. package/dist/esm/config/config-loader.js.map +1 -0
  119. package/dist/esm/config/defaults.js +107 -0
  120. package/dist/esm/config/defaults.js.map +1 -0
  121. package/dist/esm/core/dom-snapshot.js +278 -0
  122. package/dist/esm/core/dom-snapshot.js.map +1 -0
  123. package/dist/esm/core/enterprise-strategy.js +695 -0
  124. package/dist/esm/core/enterprise-strategy.js.map +1 -0
  125. package/dist/esm/core/healer.js +281 -0
  126. package/dist/esm/core/healer.js.map +1 -0
  127. package/dist/esm/core/interceptor.js +940 -0
  128. package/dist/esm/core/interceptor.js.map +1 -0
  129. package/dist/esm/core/locator-analyzer.js +169 -0
  130. package/dist/esm/core/locator-analyzer.js.map +1 -0
  131. package/dist/esm/core/locator-strategies.js +882 -0
  132. package/dist/esm/core/locator-strategies.js.map +1 -0
  133. package/dist/esm/core/self-heal-cache.js +176 -0
  134. package/dist/esm/core/self-heal-cache.js.map +1 -0
  135. package/dist/esm/core/smart-retry.js +246 -0
  136. package/dist/esm/core/smart-retry.js.map +1 -0
  137. package/dist/esm/core/visual-verification.js +260 -0
  138. package/dist/esm/core/visual-verification.js.map +1 -0
  139. package/dist/esm/git/code-modifier.js +182 -0
  140. package/dist/esm/git/code-modifier.js.map +1 -0
  141. package/dist/esm/git/git-operations.js +143 -0
  142. package/dist/esm/git/git-operations.js.map +1 -0
  143. package/dist/esm/git/pr-creator.js +188 -0
  144. package/dist/esm/git/pr-creator.js.map +1 -0
  145. package/dist/esm/index.js +37 -0
  146. package/dist/esm/index.js.map +1 -0
  147. package/dist/esm/rag/context-retriever.js +287 -0
  148. package/dist/esm/rag/context-retriever.js.map +1 -0
  149. package/dist/esm/rag/embeddings.js +77 -0
  150. package/dist/esm/rag/embeddings.js.map +1 -0
  151. package/dist/esm/rag/knowledge-store.js +157 -0
  152. package/dist/esm/rag/knowledge-store.js.map +1 -0
  153. package/dist/esm/reporters/heal-report.js +277 -0
  154. package/dist/esm/reporters/heal-report.js.map +1 -0
  155. package/dist/esm/reporters/heal-reporter.js +290 -0
  156. package/dist/esm/reporters/heal-reporter.js.map +1 -0
  157. package/dist/esm/server/review-server.js +164 -0
  158. package/dist/esm/server/review-server.js.map +1 -0
  159. package/dist/esm/server/routes.js +90 -0
  160. package/dist/esm/server/routes.js.map +1 -0
  161. package/dist/esm/utils/environment.js +53 -0
  162. package/dist/esm/utils/environment.js.map +1 -0
  163. package/dist/esm/utils/file-lock.js +134 -0
  164. package/dist/esm/utils/file-lock.js.map +1 -0
  165. package/dist/esm/utils/file-utils.js +43 -0
  166. package/dist/esm/utils/file-utils.js.map +1 -0
  167. package/dist/esm/utils/logger.js +75 -0
  168. package/dist/esm/utils/logger.js.map +1 -0
  169. package/dist/types/ai/ai-provider.d.ts +4 -0
  170. package/dist/types/ai/ai-provider.d.ts.map +1 -0
  171. package/dist/types/ai/anthropic-provider.d.ts +11 -0
  172. package/dist/types/ai/anthropic-provider.d.ts.map +1 -0
  173. package/dist/types/ai/azure-openai-provider.d.ts +13 -0
  174. package/dist/types/ai/azure-openai-provider.d.ts.map +1 -0
  175. package/dist/types/ai/bedrock-provider.d.ts +14 -0
  176. package/dist/types/ai/bedrock-provider.d.ts.map +1 -0
  177. package/dist/types/ai/deepseek-provider.d.ts +12 -0
  178. package/dist/types/ai/deepseek-provider.d.ts.map +1 -0
  179. package/dist/types/ai/gemini-provider.d.ts +12 -0
  180. package/dist/types/ai/gemini-provider.d.ts.map +1 -0
  181. package/dist/types/ai/groq-provider.d.ts +12 -0
  182. package/dist/types/ai/groq-provider.d.ts.map +1 -0
  183. package/dist/types/ai/meta-provider.d.ts +12 -0
  184. package/dist/types/ai/meta-provider.d.ts.map +1 -0
  185. package/dist/types/ai/ollama-provider.d.ts +10 -0
  186. package/dist/types/ai/ollama-provider.d.ts.map +1 -0
  187. package/dist/types/ai/openai-provider.d.ts +11 -0
  188. package/dist/types/ai/openai-provider.d.ts.map +1 -0
  189. package/dist/types/ai/perplexity-provider.d.ts +12 -0
  190. package/dist/types/ai/perplexity-provider.d.ts.map +1 -0
  191. package/dist/types/ai/prompt-templates.d.ts +11 -0
  192. package/dist/types/ai/prompt-templates.d.ts.map +1 -0
  193. package/dist/types/ai/qwen-provider.d.ts +12 -0
  194. package/dist/types/ai/qwen-provider.d.ts.map +1 -0
  195. package/dist/types/analytics/healing-analytics.d.ts +36 -0
  196. package/dist/types/analytics/healing-analytics.d.ts.map +1 -0
  197. package/dist/types/cli/init.d.ts +15 -0
  198. package/dist/types/cli/init.d.ts.map +1 -0
  199. package/dist/types/config/config-loader.d.ts +4 -0
  200. package/dist/types/config/config-loader.d.ts.map +1 -0
  201. package/dist/types/config/defaults.d.ts +3 -0
  202. package/dist/types/config/defaults.d.ts.map +1 -0
  203. package/dist/types/core/dom-snapshot.d.ts +12 -0
  204. package/dist/types/core/dom-snapshot.d.ts.map +1 -0
  205. package/dist/types/core/enterprise-strategy.d.ts +56 -0
  206. package/dist/types/core/enterprise-strategy.d.ts.map +1 -0
  207. package/dist/types/core/healer.d.ts +52 -0
  208. package/dist/types/core/healer.d.ts.map +1 -0
  209. package/dist/types/core/interceptor.d.ts +64 -0
  210. package/dist/types/core/interceptor.d.ts.map +1 -0
  211. package/dist/types/core/locator-analyzer.d.ts +31 -0
  212. package/dist/types/core/locator-analyzer.d.ts.map +1 -0
  213. package/dist/types/core/locator-strategies.d.ts +45 -0
  214. package/dist/types/core/locator-strategies.d.ts.map +1 -0
  215. package/dist/types/core/self-heal-cache.d.ts +51 -0
  216. package/dist/types/core/self-heal-cache.d.ts.map +1 -0
  217. package/dist/types/core/smart-retry.d.ts +64 -0
  218. package/dist/types/core/smart-retry.d.ts.map +1 -0
  219. package/dist/types/core/visual-verification.d.ts +46 -0
  220. package/dist/types/core/visual-verification.d.ts.map +1 -0
  221. package/dist/types/git/code-modifier.d.ts +51 -0
  222. package/dist/types/git/code-modifier.d.ts.map +1 -0
  223. package/dist/types/git/git-operations.d.ts +40 -0
  224. package/dist/types/git/git-operations.d.ts.map +1 -0
  225. package/dist/types/git/pr-creator.d.ts +27 -0
  226. package/dist/types/git/pr-creator.d.ts.map +1 -0
  227. package/dist/types/index.d.ts +40 -0
  228. package/dist/types/index.d.ts.map +1 -0
  229. package/dist/types/rag/context-retriever.d.ts +69 -0
  230. package/dist/types/rag/context-retriever.d.ts.map +1 -0
  231. package/dist/types/rag/embeddings.d.ts +32 -0
  232. package/dist/types/rag/embeddings.d.ts.map +1 -0
  233. package/dist/types/rag/index.d.ts +12 -0
  234. package/dist/types/rag/index.d.ts.map +1 -0
  235. package/dist/types/rag/knowledge-store.d.ts +38 -0
  236. package/dist/types/rag/knowledge-store.d.ts.map +1 -0
  237. package/dist/types/reporters/heal-report.d.ts +29 -0
  238. package/dist/types/reporters/heal-report.d.ts.map +1 -0
  239. package/dist/types/reporters/heal-reporter.d.ts +49 -0
  240. package/dist/types/reporters/heal-reporter.d.ts.map +1 -0
  241. package/dist/types/server/review-server.d.ts +20 -0
  242. package/dist/types/server/review-server.d.ts.map +1 -0
  243. package/dist/types/server/routes.d.ts +4 -0
  244. package/dist/types/server/routes.d.ts.map +1 -0
  245. package/dist/types/types/index.d.ts +433 -0
  246. package/dist/types/types/index.d.ts.map +1 -0
  247. package/dist/types/utils/environment.d.ts +10 -0
  248. package/dist/types/utils/environment.d.ts.map +1 -0
  249. package/dist/types/utils/file-lock.d.ts +37 -0
  250. package/dist/types/utils/file-lock.d.ts.map +1 -0
  251. package/dist/types/utils/file-utils.d.ts +7 -0
  252. package/dist/types/utils/file-utils.d.ts.map +1 -0
  253. package/dist/types/utils/logger.d.ts +9 -0
  254. package/dist/types/utils/logger.d.ts.map +1 -0
  255. package/package.json +106 -0
@@ -0,0 +1,171 @@
1
+ const VALID_LOCATOR_TYPES = new Set([
2
+ 'css', 'xpath', 'role', 'text', 'testid', 'label', 'placeholder', 'alttext', 'title',
3
+ ]);
4
+ /**
5
+ * Builds a complete prompt for the AI provider to suggest a healed locator.
6
+ */
7
+ function buildHealingPrompt(request) {
8
+ const systemPrompt = `You are an expert Playwright test engineer specializing in locator strategies.
9
+ Your task is to analyze a broken Playwright locator and suggest a healed replacement based on the current DOM snapshot.
10
+
11
+ LOCATOR BEST PRACTICES (follow in priority order):
12
+ 1. Prefer \`getByRole\` with accessible name — most resilient to DOM changes
13
+ 2. Prefer \`getByTestId\` when a data-testid attribute is present
14
+ 3. Prefer \`getByLabel\` for form inputs with associated labels
15
+ 4. Prefer \`getByPlaceholder\` for inputs with placeholder text
16
+ 5. Prefer \`getByText\` for elements with unique, stable visible text
17
+ 6. Prefer \`getByAltText\` for images with alt attributes
18
+ 7. Prefer \`getByTitle\` when title attribute is the best identifier
19
+ 8. Use CSS selectors only as a last resort
20
+ 9. Avoid XPath unless no other strategy is viable
21
+
22
+ SPECIAL CONTEXTS:
23
+ - MODALS/DIALOGS: If the element is inside a modal, dialog, popup, or overlay (look for \`dialog\`, \`role="dialog"\`, \`role="alertdialog"\`, \`aria-modal="true"\`, or classes like \`.modal\`), scope your locator to the modal context. Use \`page.getByRole('dialog').getByRole(...)\` or similar chaining to avoid matching elements behind the modal.
24
+ - WEB TABLES: If the element is inside a \`<table>\`, consider using:
25
+ - \`page.getByRole('cell', { name: '...' })\` for specific cell content
26
+ - \`page.getByRole('row', { name: '...' }).getByRole('cell')\` for row+cell targeting
27
+ - \`page.locator('table >> tr:nth-child(N) >> td:nth-child(M)')\` for index-based access
28
+ - Column header text can help identify the right column
29
+ - SHADOW DOM: If elements are inside shadow roots (\`#shadow-root\` markers in the snapshot), Playwright can pierce shadow DOM with CSS selectors or via \`page.locator('host-element').locator('inner-selector')\`
30
+ - ENTERPRISE APPLICATIONS: If the DOM contains SAP (ui5-*, sap-*), Salesforce (lightning-*, force-*), ServiceNow (now-*, sn-*), or similar enterprise elements:
31
+ - NEVER rely on dynamically generated IDs (e.g., \`__xmlview0--\`, \`globalId_\`, \`auraId_\`). Use \`[id$="stablePart"]\` suffix matching instead.
32
+ - Prefer \`data-automation-id\`, \`data-testid\`, \`data-field\`, \`data-field-name\`, \`data-component-id\`, or \`data-control-name\` attributes.
33
+ - For SAP UI5, use \`[data-sap-ui]\` or \`[data-sap-ui-id]\` attributes.
34
+ - For Salesforce Lightning, prefer \`lightning-*\` custom element selectors with stable attributes.
35
+ - For ServiceNow, use \`[data-table-name]\`, \`[data-field-name]\`, or \`[data-element]\` attributes.
36
+
37
+ IMPORTANT:
38
+ - The playwrightExpression must be a valid Playwright locator call (e.g., \`page.getByRole('button', { name: 'Submit' })\`)
39
+ - Confidence should be between 0 and 1 (1 = certainty, 0.5 = moderate guess)
40
+ - Provide clear reasoning explaining why you chose this locator
41
+
42
+ You MUST respond with ONLY a JSON object in this exact format (no markdown, no code fences):
43
+ {
44
+ "selector": "<the selector value>",
45
+ "locatorType": "<one of: css, xpath, role, text, testid, label, placeholder, alttext, title>",
46
+ "confidence": <number between 0 and 1>,
47
+ "reasoning": "<brief explanation of why this locator was chosen>",
48
+ "playwrightExpression": "<full Playwright locator expression starting with page.>"
49
+ }`;
50
+ const userPrompt = `A Playwright locator has broken. Please analyze the DOM and suggest a healed locator.
51
+
52
+ BROKEN LOCATOR:
53
+ - Type: ${request.originalLocator.type}
54
+ - Selector: ${request.originalLocator.selector}
55
+ - Playwright expression: ${request.originalLocator.playwrightExpression}
56
+
57
+ ERROR MESSAGE:
58
+ ${request.errorMessage}
59
+
60
+ PAGE URL: ${request.pageUrl}
61
+ ACTION ATTEMPTED: ${request.action}
62
+
63
+ CURRENT DOM SNAPSHOT:
64
+ ${request.domSnapshot}${request.nearbyElements ? `
65
+
66
+ NEARBY ELEMENTS:
67
+ ${request.nearbyElements}` : ''}${request.ragContext && request.ragContext.length > 0 ? `
68
+
69
+ PROJECT CONTEXT (from RAG knowledge base):
70
+ ${formatRAGContext(request.ragContext)}
71
+
72
+ Use the above project context to make a more informed decision. For example:
73
+ - If healing history shows a previous fix for a similar locator, prefer that pattern.
74
+ - If page object metadata lists known selectors, use them.
75
+ - If git changes show a recent rename, the new name is likely correct.
76
+ - If component docs describe the expected structure, align with it.` : ''}
77
+
78
+ Respond with ONLY the JSON object — no extra text.`;
79
+ return `${systemPrompt}\n\n---\n\n${userPrompt}`;
80
+ }
81
+ /**
82
+ * Parses the AI provider's raw text response into a structured AIHealingResponse.
83
+ * Handles malformed responses gracefully by attempting multiple extraction strategies.
84
+ */
85
+ function parseHealingResponse(response) {
86
+ const trimmed = response.trim();
87
+ // Strategy 1: Try direct JSON parse
88
+ const directParsed = tryParseJSON(trimmed);
89
+ if (directParsed) {
90
+ return validateAndNormalize(directParsed);
91
+ }
92
+ // Strategy 2: Extract JSON from markdown code fences
93
+ const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
94
+ if (fenceMatch) {
95
+ const fenceParsed = tryParseJSON(fenceMatch[1].trim());
96
+ if (fenceParsed) {
97
+ return validateAndNormalize(fenceParsed);
98
+ }
99
+ }
100
+ // Strategy 3: Find first { ... } block in the response
101
+ const braceStart = trimmed.indexOf('{');
102
+ const braceEnd = trimmed.lastIndexOf('}');
103
+ if (braceStart !== -1 && braceEnd > braceStart) {
104
+ const extracted = trimmed.slice(braceStart, braceEnd + 1);
105
+ const extractedParsed = tryParseJSON(extracted);
106
+ if (extractedParsed) {
107
+ return validateAndNormalize(extractedParsed);
108
+ }
109
+ }
110
+ throw new Error(`[MindHeal] Failed to parse AI healing response. Raw response: ${trimmed.slice(0, 500)}`);
111
+ }
112
+ function tryParseJSON(text) {
113
+ try {
114
+ const parsed = JSON.parse(text);
115
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
116
+ return parsed;
117
+ }
118
+ return null;
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
124
+ function validateAndNormalize(data) {
125
+ const selector = extractString(data, 'selector');
126
+ if (!selector) {
127
+ throw new Error('[MindHeal] AI response missing required field: selector');
128
+ }
129
+ const playwrightExpression = extractString(data, 'playwrightExpression');
130
+ if (!playwrightExpression) {
131
+ throw new Error('[MindHeal] AI response missing required field: playwrightExpression');
132
+ }
133
+ const rawLocatorType = extractString(data, 'locatorType') ?? 'css';
134
+ const locatorType = VALID_LOCATOR_TYPES.has(rawLocatorType)
135
+ ? rawLocatorType
136
+ : 'css';
137
+ const rawConfidence = typeof data['confidence'] === 'number' ? data['confidence'] : 0.5;
138
+ const confidence = Math.max(0, Math.min(1, rawConfidence));
139
+ const reasoning = extractString(data, 'reasoning') ?? 'No reasoning provided by AI';
140
+ return {
141
+ selector,
142
+ locatorType,
143
+ confidence,
144
+ reasoning,
145
+ playwrightExpression,
146
+ };
147
+ }
148
+ function extractString(data, key) {
149
+ const value = data[key];
150
+ if (typeof value === 'string' && value.length > 0) {
151
+ return value;
152
+ }
153
+ return undefined;
154
+ }
155
+ /**
156
+ * Formats RAG context chunks into a readable section for the AI prompt.
157
+ */
158
+ function formatRAGContext(chunks) {
159
+ return chunks
160
+ .map((chunk, i) => {
161
+ const header = `[${i + 1}] Source: ${chunk.source} (relevance: ${chunk.relevanceScore.toFixed(2)})`;
162
+ const meta = Object.entries(chunk.metadata)
163
+ .map(([k, v]) => ` ${k}: ${v}`)
164
+ .join('\n');
165
+ return `${header}\n${meta ? meta + '\n' : ''}${chunk.content}`;
166
+ })
167
+ .join('\n\n---\n\n');
168
+ }
169
+
170
+ export { buildHealingPrompt, parseHealingResponse };
171
+ //# sourceMappingURL=prompt-templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-templates.js","sources":["../../../../src/ai/prompt-templates.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ import { buildHealingPrompt, parseHealingResponse } from './prompt-templates.js';
2
+ import { logger } from '../utils/logger.js';
3
+
4
+ const DEFAULT_MODEL = 'qwen-plus';
5
+ const DEFAULT_MAX_TOKENS = 1024;
6
+ const DEFAULT_TEMPERATURE = 0;
7
+ const DEFAULT_API_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions';
8
+ const REQUEST_TIMEOUT_MS = 30000;
9
+ class QwenProvider {
10
+ constructor(config) {
11
+ this.name = 'qwen';
12
+ if (!config.apiKey) {
13
+ throw new Error('[MindHeal] Qwen API key is required');
14
+ }
15
+ this.apiKey = config.apiKey;
16
+ this.model = config.model ?? DEFAULT_MODEL;
17
+ this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
18
+ this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
19
+ this.apiUrl = config.baseUrl ?? DEFAULT_API_URL;
20
+ }
21
+ async suggestLocator(request) {
22
+ const fullPrompt = buildHealingPrompt(request);
23
+ const startTime = Date.now();
24
+ logger.debug('Qwen API: sending healing request', {
25
+ model: this.model,
26
+ originalLocator: request.originalLocator.selector,
27
+ pageUrl: request.pageUrl,
28
+ });
29
+ // Split the combined prompt into system + user messages at the separator
30
+ const separatorIndex = fullPrompt.indexOf('\n\n---\n\n');
31
+ let systemContent;
32
+ let userContent;
33
+ if (separatorIndex !== -1) {
34
+ systemContent = fullPrompt.slice(0, separatorIndex);
35
+ userContent = fullPrompt.slice(separatorIndex + 7); // length of '\n\n---\n\n'
36
+ }
37
+ else {
38
+ systemContent = 'You are an expert Playwright test engineer. Respond with only valid JSON.';
39
+ userContent = fullPrompt;
40
+ }
41
+ const messages = [
42
+ { role: 'system', content: systemContent },
43
+ { role: 'user', content: userContent },
44
+ ];
45
+ const body = JSON.stringify({
46
+ model: this.model,
47
+ max_tokens: this.maxTokens,
48
+ temperature: this.temperature,
49
+ messages,
50
+ });
51
+ let responseData;
52
+ try {
53
+ const controller = new AbortController();
54
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
55
+ const response = await fetch(this.apiUrl, {
56
+ method: 'POST',
57
+ headers: {
58
+ 'content-type': 'application/json',
59
+ 'authorization': `Bearer ${this.apiKey}`,
60
+ },
61
+ body,
62
+ signal: controller.signal,
63
+ });
64
+ clearTimeout(timeoutId);
65
+ if (response.status === 429) {
66
+ const retryAfter = response.headers.get('retry-after');
67
+ const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 5;
68
+ logger.warn(`Qwen API: rate limited, retrying after ${waitSeconds}s`);
69
+ await sleep(waitSeconds * 1000);
70
+ return this.suggestLocator(request);
71
+ }
72
+ responseData = (await response.json());
73
+ if (!response.ok) {
74
+ const errorResp = responseData;
75
+ throw new Error(`Qwen API error (${response.status}): ${errorResp.error?.message ?? response.statusText}`);
76
+ }
77
+ }
78
+ catch (error) {
79
+ if (error instanceof DOMException && error.name === 'AbortError') {
80
+ throw new Error(`[MindHeal] Qwen API request timed out after ${REQUEST_TIMEOUT_MS}ms`);
81
+ }
82
+ if (error instanceof Error && error.message.startsWith('Qwen API error')) {
83
+ throw new Error(`[MindHeal] ${error.message}`);
84
+ }
85
+ throw new Error(`[MindHeal] Qwen API request failed: ${error instanceof Error ? error.message : String(error)}`);
86
+ }
87
+ const successResp = responseData;
88
+ const choice = successResp.choices[0];
89
+ if (!choice?.message?.content) {
90
+ throw new Error('[MindHeal] Qwen API returned empty response');
91
+ }
92
+ const duration = Date.now() - startTime;
93
+ logger.debug('Qwen API: received response', {
94
+ model: successResp.model,
95
+ duration: `${duration}ms`,
96
+ promptTokens: successResp.usage.prompt_tokens,
97
+ completionTokens: successResp.usage.completion_tokens,
98
+ });
99
+ try {
100
+ return parseHealingResponse(choice.message.content);
101
+ }
102
+ catch (parseError) {
103
+ logger.error('Qwen API: failed to parse response', {
104
+ rawText: choice.message.content.slice(0, 500),
105
+ error: parseError instanceof Error ? parseError.message : String(parseError),
106
+ });
107
+ throw parseError;
108
+ }
109
+ }
110
+ }
111
+ function sleep(ms) {
112
+ return new Promise((resolve) => setTimeout(resolve, ms));
113
+ }
114
+
115
+ export { QwenProvider };
116
+ //# sourceMappingURL=qwen-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qwen-provider.js","sources":["../../../../src/ai/qwen-provider.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,261 @@
1
+ import { logger } from '../utils/logger.js';
2
+ import { fileExists, readJsonFile, ensureDirectory, writeJsonFile } from '../utils/file-utils.js';
3
+
4
+ /**
5
+ * Healing Analytics & Metrics
6
+ *
7
+ * Tracks healing success rates, strategy effectiveness, per-locator healing
8
+ * frequency, confidence trends, and test stability scores over time.
9
+ * File-backed with auto-pruning and exportable JSON.
10
+ */
11
+ const ANALYTICS_VERSION = '1';
12
+ class HealingAnalytics {
13
+ constructor(config) {
14
+ this.dirty = false;
15
+ this.config = config;
16
+ this.snapshot = this.emptySnapshot();
17
+ }
18
+ // ─── Lifecycle ──────────────────────────────────────────────────────────
19
+ load() {
20
+ if (!this.config.enabled)
21
+ return;
22
+ try {
23
+ if (!fileExists(this.config.storePath)) {
24
+ this.snapshot = this.emptySnapshot();
25
+ return;
26
+ }
27
+ const data = readJsonFile(this.config.storePath);
28
+ if (!data || data.version !== ANALYTICS_VERSION) {
29
+ this.snapshot = this.emptySnapshot();
30
+ return;
31
+ }
32
+ this.snapshot = data;
33
+ this.prune();
34
+ logger.debug(`[Analytics] Loaded ${this.snapshot.entries.length} entries`);
35
+ }
36
+ catch (err) {
37
+ const msg = err instanceof Error ? err.message : String(err);
38
+ logger.warn(`[Analytics] Failed to load: ${msg}`);
39
+ this.snapshot = this.emptySnapshot();
40
+ }
41
+ }
42
+ save() {
43
+ if (!this.config.enabled || !this.dirty)
44
+ return;
45
+ try {
46
+ const pathModule = require('path');
47
+ ensureDirectory(pathModule.dirname(this.config.storePath));
48
+ this.snapshot.lastUpdated = Date.now();
49
+ writeJsonFile(this.config.storePath, this.snapshot);
50
+ this.dirty = false;
51
+ logger.debug(`[Analytics] Saved ${this.snapshot.entries.length} entries`);
52
+ }
53
+ catch (err) {
54
+ const msg = err instanceof Error ? err.message : String(err);
55
+ logger.warn(`[Analytics] Failed to save: ${msg}`);
56
+ }
57
+ }
58
+ // ─── Recording ──────────────────────────────────────────────────────────
59
+ recordEvent(event) {
60
+ if (!this.config.enabled)
61
+ return;
62
+ const entry = {
63
+ timestamp: event.timestamp,
64
+ testFile: event.testFile,
65
+ testTitle: event.testTitle,
66
+ pageUrl: event.pageUrl,
67
+ locatorExpression: event.originalLocator.playwrightExpression,
68
+ action: event.action,
69
+ strategy: event.strategy,
70
+ confidence: event.confidence,
71
+ duration: event.duration,
72
+ success: event.status === 'healed',
73
+ };
74
+ this.snapshot.entries.push(entry);
75
+ this.dirty = true;
76
+ // Update aggregates
77
+ if (this.config.trackStrategies && event.strategy) {
78
+ this.updateStrategyStats(event.strategy, entry);
79
+ }
80
+ if (this.config.trackLocators) {
81
+ this.updateLocatorStats(entry);
82
+ }
83
+ if (this.config.trackTestStability) {
84
+ this.updateTestStability(entry);
85
+ }
86
+ // Enforce max entries
87
+ if (this.snapshot.entries.length > this.config.maxEntries) {
88
+ this.snapshot.entries = this.snapshot.entries.slice(-this.config.maxEntries);
89
+ }
90
+ }
91
+ // ─── Strategy Stats ─────────────────────────────────────────────────────
92
+ updateStrategyStats(strategy, entry) {
93
+ const key = strategy;
94
+ const existing = this.snapshot.strategyStats[key];
95
+ if (!existing) {
96
+ this.snapshot.strategyStats[key] = {
97
+ name: strategy,
98
+ totalAttempts: 1,
99
+ successCount: entry.success ? 1 : 0,
100
+ failCount: entry.success ? 0 : 1,
101
+ avgConfidence: entry.confidence,
102
+ avgDuration: entry.duration,
103
+ successRate: entry.success ? 1 : 0,
104
+ };
105
+ return;
106
+ }
107
+ existing.totalAttempts++;
108
+ if (entry.success) {
109
+ existing.successCount++;
110
+ }
111
+ else {
112
+ existing.failCount++;
113
+ }
114
+ existing.successRate = existing.successCount / existing.totalAttempts;
115
+ existing.avgConfidence =
116
+ (existing.avgConfidence * (existing.totalAttempts - 1) + entry.confidence) /
117
+ existing.totalAttempts;
118
+ existing.avgDuration =
119
+ (existing.avgDuration * (existing.totalAttempts - 1) + entry.duration) /
120
+ existing.totalAttempts;
121
+ }
122
+ // ─── Locator Stats ──────────────────────────────────────────────────────
123
+ updateLocatorStats(entry) {
124
+ const key = entry.locatorExpression;
125
+ const existing = this.snapshot.locatorStats[key];
126
+ if (!existing) {
127
+ this.snapshot.locatorStats[key] = {
128
+ expression: entry.locatorExpression,
129
+ healCount: entry.success ? 1 : 0,
130
+ failCount: entry.success ? 0 : 1,
131
+ lastHealed: entry.timestamp,
132
+ strategies: entry.strategy ? [entry.strategy] : [],
133
+ pages: [entry.pageUrl],
134
+ };
135
+ return;
136
+ }
137
+ if (entry.success) {
138
+ existing.healCount++;
139
+ }
140
+ else {
141
+ existing.failCount++;
142
+ }
143
+ existing.lastHealed = entry.timestamp;
144
+ if (entry.strategy && !existing.strategies.includes(entry.strategy)) {
145
+ existing.strategies.push(entry.strategy);
146
+ }
147
+ if (!existing.pages.includes(entry.pageUrl)) {
148
+ existing.pages.push(entry.pageUrl);
149
+ }
150
+ }
151
+ // ─── Test Stability ─────────────────────────────────────────────────────
152
+ updateTestStability(entry) {
153
+ const key = `${entry.testFile}::${entry.testTitle}`;
154
+ const existing = this.snapshot.testStability[key];
155
+ if (!existing) {
156
+ this.snapshot.testStability[key] = {
157
+ testFile: entry.testFile,
158
+ testTitle: entry.testTitle,
159
+ totalRuns: 1,
160
+ healsNeeded: 1,
161
+ failedHeals: entry.success ? 0 : 1,
162
+ stabilityScore: entry.success ? 80 : 40,
163
+ lastRun: entry.timestamp,
164
+ trend: 'stable',
165
+ };
166
+ return;
167
+ }
168
+ existing.totalRuns++;
169
+ existing.healsNeeded++;
170
+ if (!entry.success) {
171
+ existing.failedHeals++;
172
+ }
173
+ existing.lastRun = entry.timestamp;
174
+ // Compute stability score (0-100)
175
+ // Score decreases with more healing needed and more failures
176
+ const healRatio = existing.healsNeeded / existing.totalRuns;
177
+ const failRatio = existing.failedHeals / existing.healsNeeded;
178
+ existing.stabilityScore = Math.round(Math.max(0, 100 - healRatio * 60 - failRatio * 40));
179
+ // Compute trend from recent entries
180
+ const recentEntries = this.snapshot.entries
181
+ .filter((e) => e.testFile === entry.testFile && e.testTitle === entry.testTitle)
182
+ .slice(-10);
183
+ if (recentEntries.length >= 3) {
184
+ const firstHalf = recentEntries.slice(0, Math.floor(recentEntries.length / 2));
185
+ const secondHalf = recentEntries.slice(Math.floor(recentEntries.length / 2));
186
+ const firstRate = firstHalf.filter((e) => e.success).length / firstHalf.length;
187
+ const secondRate = secondHalf.filter((e) => e.success).length / secondHalf.length;
188
+ if (secondRate > firstRate + 0.1) {
189
+ existing.trend = 'improving';
190
+ }
191
+ else if (secondRate < firstRate - 0.1) {
192
+ existing.trend = 'degrading';
193
+ }
194
+ else {
195
+ existing.trend = 'stable';
196
+ }
197
+ }
198
+ }
199
+ // ─── Queries ────────────────────────────────────────────────────────────
200
+ getStrategyStats() {
201
+ return Object.values(this.snapshot.strategyStats)
202
+ .sort((a, b) => b.successRate - a.successRate);
203
+ }
204
+ getMostHealedLocators(limit = 10) {
205
+ return Object.values(this.snapshot.locatorStats)
206
+ .sort((a, b) => (b.healCount + b.failCount) - (a.healCount + a.failCount))
207
+ .slice(0, limit);
208
+ }
209
+ getUnstableTests(limit = 10) {
210
+ return Object.values(this.snapshot.testStability)
211
+ .sort((a, b) => a.stabilityScore - b.stabilityScore)
212
+ .slice(0, limit);
213
+ }
214
+ getDegradingTests() {
215
+ return Object.values(this.snapshot.testStability)
216
+ .filter((t) => t.trend === 'degrading');
217
+ }
218
+ getOverallStats() {
219
+ const entries = this.snapshot.entries;
220
+ const total = entries.length;
221
+ const successful = entries.filter((e) => e.success).length;
222
+ return {
223
+ totalHeals: total,
224
+ successRate: total > 0 ? successful / total : 0,
225
+ avgConfidence: total > 0
226
+ ? entries.reduce((sum, e) => sum + e.confidence, 0) / total
227
+ : 0,
228
+ avgDuration: total > 0
229
+ ? entries.reduce((sum, e) => sum + e.duration, 0) / total
230
+ : 0,
231
+ uniqueLocators: Object.keys(this.snapshot.locatorStats).length,
232
+ uniqueTests: Object.keys(this.snapshot.testStability).length,
233
+ };
234
+ }
235
+ getSnapshot() {
236
+ return { ...this.snapshot };
237
+ }
238
+ // ─── Maintenance ────────────────────────────────────────────────────────
239
+ prune() {
240
+ const cutoff = Date.now() - this.config.retentionDays * 24 * 60 * 60 * 1000;
241
+ const before = this.snapshot.entries.length;
242
+ this.snapshot.entries = this.snapshot.entries.filter((e) => e.timestamp > cutoff);
243
+ if (this.snapshot.entries.length < before) {
244
+ this.dirty = true;
245
+ logger.debug(`[Analytics] Pruned ${before - this.snapshot.entries.length} old entries`);
246
+ }
247
+ }
248
+ emptySnapshot() {
249
+ return {
250
+ version: ANALYTICS_VERSION,
251
+ entries: [],
252
+ strategyStats: {},
253
+ locatorStats: {},
254
+ testStability: {},
255
+ lastUpdated: Date.now(),
256
+ };
257
+ }
258
+ }
259
+
260
+ export { HealingAnalytics };
261
+ //# sourceMappingURL=healing-analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healing-analytics.js","sources":["../../../../src/analytics/healing-analytics.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}