sela-core 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/README.md +103 -0
  2. package/bin/sela.js +3 -0
  3. package/dist/cli/ErrorHandler.d.ts +10 -0
  4. package/dist/cli/ErrorHandler.d.ts.map +1 -0
  5. package/dist/cli/ErrorHandler.js +70 -0
  6. package/dist/cli/commands/bulk.d.ts +3 -0
  7. package/dist/cli/commands/bulk.d.ts.map +1 -0
  8. package/dist/cli/commands/bulk.js +158 -0
  9. package/dist/cli/commands/find.d.ts +3 -0
  10. package/dist/cli/commands/find.d.ts.map +1 -0
  11. package/dist/cli/commands/find.js +51 -0
  12. package/dist/cli/commands/init.d.ts +3 -0
  13. package/dist/cli/commands/init.d.ts.map +1 -0
  14. package/dist/cli/commands/init.js +166 -0
  15. package/dist/cli/commands/list.d.ts +3 -0
  16. package/dist/cli/commands/list.d.ts.map +1 -0
  17. package/dist/cli/commands/list.js +56 -0
  18. package/dist/cli/commands/merge.d.ts +13 -0
  19. package/dist/cli/commands/merge.d.ts.map +1 -0
  20. package/dist/cli/commands/merge.js +119 -0
  21. package/dist/cli/commands/refactor.d.ts +3 -0
  22. package/dist/cli/commands/refactor.d.ts.map +1 -0
  23. package/dist/cli/commands/refactor.js +30 -0
  24. package/dist/cli/commands/showReport.d.ts +3 -0
  25. package/dist/cli/commands/showReport.d.ts.map +1 -0
  26. package/dist/cli/commands/showReport.js +82 -0
  27. package/dist/cli/commands/status.d.ts +3 -0
  28. package/dist/cli/commands/status.d.ts.map +1 -0
  29. package/dist/cli/commands/status.js +51 -0
  30. package/dist/cli/commands/sync.d.ts +3 -0
  31. package/dist/cli/commands/sync.d.ts.map +1 -0
  32. package/dist/cli/commands/sync.js +131 -0
  33. package/dist/cli/index.d.ts +2 -0
  34. package/dist/cli/index.d.ts.map +1 -0
  35. package/dist/cli/index.js +94 -0
  36. package/dist/cli/ui/DnaTable.d.ts +3 -0
  37. package/dist/cli/ui/DnaTable.d.ts.map +1 -0
  38. package/dist/cli/ui/DnaTable.js +77 -0
  39. package/dist/cli/ui/LocatorPicker.d.ts +8 -0
  40. package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
  41. package/dist/cli/ui/LocatorPicker.js +33 -0
  42. package/dist/cli/ui/ProgressReporter.d.ts +11 -0
  43. package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
  44. package/dist/cli/ui/ProgressReporter.js +33 -0
  45. package/dist/cli/ui/RefactorWizard.d.ts +26 -0
  46. package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
  47. package/dist/cli/ui/RefactorWizard.js +295 -0
  48. package/dist/config/ConfigLoader.d.ts +16 -0
  49. package/dist/config/ConfigLoader.d.ts.map +1 -0
  50. package/dist/config/ConfigLoader.js +185 -0
  51. package/dist/config/DryRunGuard.d.ts +14 -0
  52. package/dist/config/DryRunGuard.d.ts.map +1 -0
  53. package/dist/config/DryRunGuard.js +80 -0
  54. package/dist/config/PathScopeGuard.d.ts +42 -0
  55. package/dist/config/PathScopeGuard.d.ts.map +1 -0
  56. package/dist/config/PathScopeGuard.js +192 -0
  57. package/dist/config/SelaConfig.d.ts +178 -0
  58. package/dist/config/SelaConfig.d.ts.map +1 -0
  59. package/dist/config/SelaConfig.js +141 -0
  60. package/dist/config/defineConfig.d.ts +3 -0
  61. package/dist/config/defineConfig.d.ts.map +1 -0
  62. package/dist/config/defineConfig.js +9 -0
  63. package/dist/engine/FixwrightEngine.d.ts +24 -0
  64. package/dist/engine/FixwrightEngine.d.ts.map +1 -0
  65. package/dist/engine/FixwrightEngine.js +403 -0
  66. package/dist/engine/HealingRegistry.d.ts +40 -0
  67. package/dist/engine/HealingRegistry.d.ts.map +1 -0
  68. package/dist/engine/HealingRegistry.js +99 -0
  69. package/dist/engine/SelaEngine.d.ts +57 -0
  70. package/dist/engine/SelaEngine.d.ts.map +1 -0
  71. package/dist/engine/SelaEngine.js +1146 -0
  72. package/dist/engine/singleton.d.ts +3 -0
  73. package/dist/engine/singleton.d.ts.map +1 -0
  74. package/dist/engine/singleton.js +5 -0
  75. package/dist/errors/SelaError.d.ts +146 -0
  76. package/dist/errors/SelaError.d.ts.map +1 -0
  77. package/dist/errors/SelaError.js +168 -0
  78. package/dist/fixtures/expectProxy.d.ts +12 -0
  79. package/dist/fixtures/expectProxy.d.ts.map +1 -0
  80. package/dist/fixtures/expectProxy.js +246 -0
  81. package/dist/fixtures/index.d.ts +30 -0
  82. package/dist/fixtures/index.d.ts.map +1 -0
  83. package/dist/fixtures/index.js +765 -0
  84. package/dist/fixtures/moduleExpect.d.ts +2 -0
  85. package/dist/fixtures/moduleExpect.d.ts.map +1 -0
  86. package/dist/fixtures/moduleExpect.js +54 -0
  87. package/dist/fixtures/proxyTag.d.ts +12 -0
  88. package/dist/fixtures/proxyTag.d.ts.map +1 -0
  89. package/dist/fixtures/proxyTag.js +45 -0
  90. package/dist/index.d.ts +9 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +31 -0
  93. package/dist/reporter/SelaReporter.d.ts +165 -0
  94. package/dist/reporter/SelaReporter.d.ts.map +1 -0
  95. package/dist/reporter/SelaReporter.js +517 -0
  96. package/dist/services/ASTSourceUpdater.d.ts +143 -0
  97. package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
  98. package/dist/services/ASTSourceUpdater.js +3514 -0
  99. package/dist/services/AnchorResolver.d.ts +157 -0
  100. package/dist/services/AnchorResolver.d.ts.map +1 -0
  101. package/dist/services/AnchorResolver.js +289 -0
  102. package/dist/services/ArgumentTypeAnalyzer.d.ts +26 -0
  103. package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
  104. package/dist/services/ArgumentTypeAnalyzer.js +92 -0
  105. package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
  106. package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
  107. package/dist/services/BlastRadiusAnalyzer.js +107 -0
  108. package/dist/services/ChainValidator.d.ts +76 -0
  109. package/dist/services/ChainValidator.d.ts.map +1 -0
  110. package/dist/services/ChainValidator.js +570 -0
  111. package/dist/services/CrossFileHealer.d.ts +6 -0
  112. package/dist/services/CrossFileHealer.d.ts.map +1 -0
  113. package/dist/services/CrossFileHealer.js +135 -0
  114. package/dist/services/DecisionEngine.d.ts +51 -0
  115. package/dist/services/DecisionEngine.d.ts.map +1 -0
  116. package/dist/services/DecisionEngine.js +260 -0
  117. package/dist/services/DefinitionTracer.d.ts +41 -0
  118. package/dist/services/DefinitionTracer.d.ts.map +1 -0
  119. package/dist/services/DefinitionTracer.js +366 -0
  120. package/dist/services/DnaEditorService.d.ts +31 -0
  121. package/dist/services/DnaEditorService.d.ts.map +1 -0
  122. package/dist/services/DnaEditorService.js +217 -0
  123. package/dist/services/DnaIndexService.d.ts +24 -0
  124. package/dist/services/DnaIndexService.d.ts.map +1 -0
  125. package/dist/services/DnaIndexService.js +131 -0
  126. package/dist/services/HealBudget.d.ts +32 -0
  127. package/dist/services/HealBudget.d.ts.map +1 -0
  128. package/dist/services/HealBudget.js +85 -0
  129. package/dist/services/HealReportService.d.ts +161 -0
  130. package/dist/services/HealReportService.d.ts.map +1 -0
  131. package/dist/services/HealReportService.js +1039 -0
  132. package/dist/services/HealingAdvisory.d.ts +30 -0
  133. package/dist/services/HealingAdvisory.d.ts.map +1 -0
  134. package/dist/services/HealingAdvisory.js +51 -0
  135. package/dist/services/HealingCacheService.d.ts +135 -0
  136. package/dist/services/HealingCacheService.d.ts.map +1 -0
  137. package/dist/services/HealingCacheService.js +404 -0
  138. package/dist/services/HealthReportService.d.ts +10 -0
  139. package/dist/services/HealthReportService.d.ts.map +1 -0
  140. package/dist/services/HealthReportService.js +84 -0
  141. package/dist/services/InitializerUpdater.d.ts +16 -0
  142. package/dist/services/InitializerUpdater.d.ts.map +1 -0
  143. package/dist/services/InitializerUpdater.js +73 -0
  144. package/dist/services/IntentAuditor.d.ts +56 -0
  145. package/dist/services/IntentAuditor.d.ts.map +1 -0
  146. package/dist/services/IntentAuditor.js +326 -0
  147. package/dist/services/InteractiveReview.d.ts +15 -0
  148. package/dist/services/InteractiveReview.d.ts.map +1 -0
  149. package/dist/services/InteractiveReview.js +128 -0
  150. package/dist/services/LLMService.d.ts +121 -0
  151. package/dist/services/LLMService.d.ts.map +1 -0
  152. package/dist/services/LLMService.js +475 -0
  153. package/dist/services/MutationApplier.d.ts +69 -0
  154. package/dist/services/MutationApplier.d.ts.map +1 -0
  155. package/dist/services/MutationApplier.js +360 -0
  156. package/dist/services/PRAutomationService.d.ts +50 -0
  157. package/dist/services/PRAutomationService.d.ts.map +1 -0
  158. package/dist/services/PRAutomationService.js +760 -0
  159. package/dist/services/PendingPromptLedger.d.ts +51 -0
  160. package/dist/services/PendingPromptLedger.d.ts.map +1 -0
  161. package/dist/services/PendingPromptLedger.js +205 -0
  162. package/dist/services/ReportGenerator.d.ts +156 -0
  163. package/dist/services/ReportGenerator.d.ts.map +1 -0
  164. package/dist/services/ReportGenerator.js +278 -0
  165. package/dist/services/ReportMergeService.d.ts +95 -0
  166. package/dist/services/ReportMergeService.d.ts.map +1 -0
  167. package/dist/services/ReportMergeService.js +0 -0
  168. package/dist/services/SafetyGuard.d.ts +112 -0
  169. package/dist/services/SafetyGuard.d.ts.map +1 -0
  170. package/dist/services/SafetyGuard.js +87 -0
  171. package/dist/services/Sanitizer.d.ts +28 -0
  172. package/dist/services/Sanitizer.d.ts.map +1 -0
  173. package/dist/services/Sanitizer.js +142 -0
  174. package/dist/services/SanitizingAnthropic.d.ts +19 -0
  175. package/dist/services/SanitizingAnthropic.d.ts.map +1 -0
  176. package/dist/services/SanitizingAnthropic.js +84 -0
  177. package/dist/services/SelectorSanitizer.d.ts +52 -0
  178. package/dist/services/SelectorSanitizer.d.ts.map +1 -0
  179. package/dist/services/SelectorSanitizer.js +318 -0
  180. package/dist/services/SnapshotService.d.ts +11 -0
  181. package/dist/services/SnapshotService.d.ts.map +1 -0
  182. package/dist/services/SnapshotService.js +352 -0
  183. package/dist/services/SourceLinkService.d.ts +26 -0
  184. package/dist/services/SourceLinkService.d.ts.map +1 -0
  185. package/dist/services/SourceLinkService.js +165 -0
  186. package/dist/services/SourceUpdater.d.ts +45 -0
  187. package/dist/services/SourceUpdater.d.ts.map +1 -0
  188. package/dist/services/SourceUpdater.js +1091 -0
  189. package/dist/services/TemplateDiffService.d.ts +25 -0
  190. package/dist/services/TemplateDiffService.d.ts.map +1 -0
  191. package/dist/services/TemplateDiffService.js +334 -0
  192. package/dist/services/TraceBackEngine.d.ts +67 -0
  193. package/dist/services/TraceBackEngine.d.ts.map +1 -0
  194. package/dist/services/TraceBackEngine.js +672 -0
  195. package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
  196. package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
  197. package/dist/services/WorkspaceSnapshotService.js +200 -0
  198. package/dist/services/safety/PipelineContext.d.ts +63 -0
  199. package/dist/services/safety/PipelineContext.d.ts.map +1 -0
  200. package/dist/services/safety/PipelineContext.js +34 -0
  201. package/dist/services/safety/roleClassifier.d.ts +4 -0
  202. package/dist/services/safety/roleClassifier.d.ts.map +1 -0
  203. package/dist/services/safety/roleClassifier.js +233 -0
  204. package/dist/services/safety/rules/ConfidenceTierRule.d.ts +3 -0
  205. package/dist/services/safety/rules/ConfidenceTierRule.d.ts.map +1 -0
  206. package/dist/services/safety/rules/ConfidenceTierRule.js +32 -0
  207. package/dist/services/safety/rules/DangerousSinkRule.d.ts +10 -0
  208. package/dist/services/safety/rules/DangerousSinkRule.d.ts.map +1 -0
  209. package/dist/services/safety/rules/DangerousSinkRule.js +53 -0
  210. package/dist/services/safety/rules/FunctionalRoleRule.d.ts +3 -0
  211. package/dist/services/safety/rules/FunctionalRoleRule.d.ts.map +1 -0
  212. package/dist/services/safety/rules/FunctionalRoleRule.js +64 -0
  213. package/dist/services/safety/rules/HardConfidenceRule.d.ts +3 -0
  214. package/dist/services/safety/rules/HardConfidenceRule.d.ts.map +1 -0
  215. package/dist/services/safety/rules/HardConfidenceRule.js +22 -0
  216. package/dist/services/safety/rules/IntentAuditorRule.d.ts +3 -0
  217. package/dist/services/safety/rules/IntentAuditorRule.d.ts.map +1 -0
  218. package/dist/services/safety/rules/IntentAuditorRule.js +97 -0
  219. package/dist/services/safety/rules/SemanticOppositeRule.d.ts +3 -0
  220. package/dist/services/safety/rules/SemanticOppositeRule.d.ts.map +1 -0
  221. package/dist/services/safety/rules/SemanticOppositeRule.js +30 -0
  222. package/dist/services/safety/rules/StructuralOnlyRule.d.ts +3 -0
  223. package/dist/services/safety/rules/StructuralOnlyRule.d.ts.map +1 -0
  224. package/dist/services/safety/rules/StructuralOnlyRule.js +38 -0
  225. package/dist/services/safety/rules/VisibilityRule.d.ts +3 -0
  226. package/dist/services/safety/rules/VisibilityRule.d.ts.map +1 -0
  227. package/dist/services/safety/rules/VisibilityRule.js +53 -0
  228. package/dist/services/safety/rules/ZeroTrustRule.d.ts +3 -0
  229. package/dist/services/safety/rules/ZeroTrustRule.d.ts.map +1 -0
  230. package/dist/services/safety/rules/ZeroTrustRule.js +37 -0
  231. package/dist/services/safety/rules/index.d.ts +3 -0
  232. package/dist/services/safety/rules/index.d.ts.map +1 -0
  233. package/dist/services/safety/rules/index.js +34 -0
  234. package/dist/services/types.d.ts +59 -0
  235. package/dist/services/types.d.ts.map +1 -0
  236. package/dist/services/types.js +2 -0
  237. package/dist/storage/SnapshotManager.d.ts +5 -0
  238. package/dist/storage/SnapshotManager.d.ts.map +1 -0
  239. package/dist/storage/SnapshotManager.js +32 -0
  240. package/dist/types/index.d.ts +95 -0
  241. package/dist/types/index.d.ts.map +1 -0
  242. package/dist/types/index.js +7 -0
  243. package/dist/utils/DOMUtils.d.ts +49 -0
  244. package/dist/utils/DOMUtils.d.ts.map +1 -0
  245. package/dist/utils/DOMUtils.js +466 -0
  246. package/dist/utils/FileLock.d.ts +44 -0
  247. package/dist/utils/FileLock.d.ts.map +1 -0
  248. package/dist/utils/FileLock.js +189 -0
  249. package/dist/utils/IsolatedDiff.d.ts +45 -0
  250. package/dist/utils/IsolatedDiff.d.ts.map +1 -0
  251. package/dist/utils/IsolatedDiff.js +382 -0
  252. package/dist/utils/StackUtils.d.ts +11 -0
  253. package/dist/utils/StackUtils.d.ts.map +1 -0
  254. package/dist/utils/StackUtils.js +120 -0
  255. package/dist/utils/SummaryTable.d.ts +17 -0
  256. package/dist/utils/SummaryTable.d.ts.map +1 -0
  257. package/dist/utils/SummaryTable.js +37 -0
  258. package/dist/utils/atomicWrite.d.ts +9 -0
  259. package/dist/utils/atomicWrite.d.ts.map +1 -0
  260. package/dist/utils/atomicWrite.js +83 -0
  261. package/dist/utils/logger.d.ts +59 -0
  262. package/dist/utils/logger.d.ts.map +1 -0
  263. package/dist/utils/logger.js +123 -0
  264. package/dist/vendor/enquirer.d.ts +33 -0
  265. package/dist/vendor/enquirer.d.ts.map +1 -0
  266. package/dist/vendor/enquirer.js +11 -0
  267. package/package.json +71 -0
@@ -0,0 +1,570 @@
1
+ "use strict";
2
+ // src/services/ChainValidator.ts
3
+ //
4
+ // ═══════════════════════════════════════════════════════════════════
5
+ // VALIDATION LAYER for AI-generated chain segments
6
+ //
7
+ // Runs 5 sequential guards before any AST mutation occurs:
8
+ //
9
+ // [1] SchemaValidator - required fields, no unknown segment types
10
+ // [2] TypeFlowValidator - tracks Locator|FrameLocator state
11
+ // [3] ReceiverValidator - root must match original chain's receiver
12
+ // [4] PlacementValidator - filter() / nth() / first() only after Locator
13
+ // [5] SemanticUpgrader - upgrades flat CSS to semantic where possible
14
+ //
15
+ // Each guard returns a ValidationResult. The pipeline short-circuits on
16
+ // the first REJECT, falling back to the original flat-CSS strategy.
17
+ // ═══════════════════════════════════════════════════════════════════
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.ChainValidator = void 0;
20
+ exports.segmentsToCode = segmentsToCode;
21
+ const logger_1 = require("../utils/logger");
22
+ // ─────────────────────────────────────────────────────────────────
23
+ // CONSTANTS
24
+ // ─────────────────────────────────────────────────────────────────
25
+ const KNOWN_SEGMENT_TYPES = new Set([
26
+ "locator",
27
+ "frameLocator",
28
+ "getByRole",
29
+ "getByLabel",
30
+ "getByText",
31
+ "getByPlaceholder",
32
+ "getByTestId",
33
+ "getByAltText",
34
+ "getByTitle",
35
+ "filter",
36
+ "first",
37
+ "last",
38
+ "nth",
39
+ ]);
40
+ // Methods that produce a Locator (not FrameLocator)
41
+ const LOCATOR_PRODUCERS = new Set([
42
+ "locator",
43
+ "getByRole",
44
+ "getByLabel",
45
+ "getByText",
46
+ "getByPlaceholder",
47
+ "getByTestId",
48
+ "getByAltText",
49
+ "getByTitle",
50
+ "filter",
51
+ "first",
52
+ "last",
53
+ "nth",
54
+ ]);
55
+ // Methods valid only when current return type is Locator
56
+ const REQUIRES_LOCATOR = new Set(["filter", "first", "last", "nth"]);
57
+ // Methods that can chain on both Locator and FrameLocator (or start fresh)
58
+ const WORKS_ON_FRAME_LOCATOR = new Set([
59
+ "locator",
60
+ "getByRole",
61
+ "getByLabel",
62
+ "getByText",
63
+ "getByPlaceholder",
64
+ "getByTestId",
65
+ "getByAltText",
66
+ "getByTitle",
67
+ "frameLocator",
68
+ ]);
69
+ const VALID_ARIA_ROLES = new Set([
70
+ // Interactive
71
+ "button",
72
+ "textbox",
73
+ "searchbox",
74
+ "checkbox",
75
+ "radio",
76
+ "switch",
77
+ "combobox",
78
+ "listbox",
79
+ "option",
80
+ "menuitem",
81
+ "menuitemcheckbox",
82
+ "menuitemradio",
83
+ "link",
84
+ "tab",
85
+ "tabpanel",
86
+ "slider",
87
+ "spinbutton",
88
+ "progressbar",
89
+ "scrollbar",
90
+ "grid",
91
+ "gridcell",
92
+ "row",
93
+ "rowheader",
94
+ "columnheader",
95
+ "cell",
96
+ "treeitem",
97
+ "tree",
98
+ "treegrid",
99
+ "dialog",
100
+ "alertdialog",
101
+ // Structural
102
+ "heading",
103
+ "banner",
104
+ "main",
105
+ "navigation",
106
+ "nav",
107
+ "complementary",
108
+ "aside",
109
+ "contentinfo",
110
+ "footer",
111
+ "region",
112
+ "landmark",
113
+ "article",
114
+ "section",
115
+ "list",
116
+ "listitem",
117
+ "term",
118
+ "definition",
119
+ "figure",
120
+ "img",
121
+ "presentation",
122
+ "none",
123
+ "separator",
124
+ "log",
125
+ "marquee",
126
+ "status",
127
+ "timer",
128
+ "alert",
129
+ "form",
130
+ "group",
131
+ "toolbar",
132
+ "menubar",
133
+ "menu",
134
+ "tooltip",
135
+ "math",
136
+ "note",
137
+ "application",
138
+ "document",
139
+ "feed",
140
+ "generic",
141
+ ]);
142
+ // ─────────────────────────────────────────────────────────────────
143
+ // GUARD 1 - Schema Validator
144
+ // ─────────────────────────────────────────────────────────────────
145
+ function schemaValidate(segments) {
146
+ if (!Array.isArray(segments) || segments.length === 0) {
147
+ return { valid: false, reason: "segments must be a non-empty array" };
148
+ }
149
+ for (let i = 0; i < segments.length; i++) {
150
+ const seg = segments[i];
151
+ if (typeof seg !== "object" || seg === null) {
152
+ return { valid: false, reason: `segment[${i}] is not an object` };
153
+ }
154
+ if (!KNOWN_SEGMENT_TYPES.has(seg.type)) {
155
+ return {
156
+ valid: false,
157
+ reason: `segment[${i}] has unknown type "${seg.type}"`,
158
+ };
159
+ }
160
+ // Per-type required fields
161
+ switch (seg.type) {
162
+ case "locator":
163
+ case "frameLocator":
164
+ if (typeof seg.selector !== "string" || seg.selector.trim() === "") {
165
+ return {
166
+ valid: false,
167
+ reason: `segment[${i}] type="${seg.type}" requires non-empty selector`,
168
+ };
169
+ }
170
+ break;
171
+ case "getByRole":
172
+ if (typeof seg.role !== "string" || seg.role.trim() === "") {
173
+ return {
174
+ valid: false,
175
+ reason: `segment[${i}] getByRole requires non-empty role`,
176
+ };
177
+ }
178
+ if (!VALID_ARIA_ROLES.has(seg.role.toLowerCase())) {
179
+ return {
180
+ valid: false,
181
+ reason: `segment[${i}] getByRole has invalid ARIA role "${seg.role}"`,
182
+ };
183
+ }
184
+ break;
185
+ case "getByLabel":
186
+ case "getByText":
187
+ case "getByPlaceholder":
188
+ case "getByAltText":
189
+ case "getByTitle":
190
+ if (typeof seg.text !== "string" || seg.text.trim() === "") {
191
+ return {
192
+ valid: false,
193
+ reason: `segment[${i}] type="${seg.type}" requires non-empty text`,
194
+ };
195
+ }
196
+ break;
197
+ case "getByTestId":
198
+ if (typeof seg.testId !== "string" || seg.testId.trim() === "") {
199
+ return {
200
+ valid: false,
201
+ reason: `segment[${i}] getByTestId requires non-empty testId`,
202
+ };
203
+ }
204
+ break;
205
+ case "nth":
206
+ if (typeof seg.index !== "number" || !Number.isInteger(seg.index)) {
207
+ return {
208
+ valid: false,
209
+ reason: `segment[${i}] nth requires integer index`,
210
+ };
211
+ }
212
+ break;
213
+ case "filter":
214
+ if (seg.hasText === undefined &&
215
+ seg.hasNotText === undefined &&
216
+ seg.has === undefined &&
217
+ seg.hasNot === undefined) {
218
+ return {
219
+ valid: false,
220
+ reason: `segment[${i}] filter requires at least one of: hasText, hasNotText, has, hasNot`,
221
+ };
222
+ }
223
+ break;
224
+ // first / last have no required fields beyond type
225
+ }
226
+ }
227
+ return { valid: true };
228
+ }
229
+ // ─────────────────────────────────────────────────────────────────
230
+ // GUARD 2 - Type Flow Validator
231
+ //
232
+ // Simulates the chain execution, tracking whether the current
233
+ // expression is a Locator, FrameLocator, or Page/unknown.
234
+ // ─────────────────────────────────────────────────────────────────
235
+ function typeFlowValidate(segments, rootType) {
236
+ let currentType = rootType;
237
+ for (let i = 0; i < segments.length; i++) {
238
+ const seg = segments[i];
239
+ if (seg.type === "frameLocator") {
240
+ // frameLocator() is valid on Page or FrameLocator, not Locator
241
+ if (currentType === "Locator") {
242
+ return {
243
+ valid: false,
244
+ reason: `segment[${i}] frameLocator() cannot be called on a Locator (type flow: ${currentType} → invalid)`,
245
+ };
246
+ }
247
+ currentType = "FrameLocator";
248
+ continue;
249
+ }
250
+ if (REQUIRES_LOCATOR.has(seg.type)) {
251
+ if (currentType !== "Locator") {
252
+ return {
253
+ valid: false,
254
+ reason: `segment[${i}] .${seg.type}() requires a Locator receiver but current type is "${currentType}"`,
255
+ };
256
+ }
257
+ currentType = "Locator";
258
+ continue;
259
+ }
260
+ if (LOCATOR_PRODUCERS.has(seg.type)) {
261
+ // These methods are valid on Page, FrameLocator, or Locator
262
+ currentType = "Locator";
263
+ continue;
264
+ }
265
+ }
266
+ // Final chain must end in a Locator (not FrameLocator) for assertions to work
267
+ if (currentType === "FrameLocator") {
268
+ return {
269
+ valid: false,
270
+ reason: "chain ends on FrameLocator - assertions require a Locator at the end",
271
+ };
272
+ }
273
+ return { valid: true };
274
+ }
275
+ // ─────────────────────────────────────────────────────────────────
276
+ // GUARD 3 - Receiver Validator
277
+ //
278
+ // The first segment's effective receiver must be compatible with
279
+ // the original code's root receiver. Prevents `page.getByRole()`
280
+ // from replacing `userRow.getByRole()`.
281
+ // ─────────────────────────────────────────────────────────────────
282
+ function receiverValidate(segments, originalRootReceiver, proposedRootReceiver) {
283
+ const orig = originalRootReceiver.trim();
284
+ const proposed = proposedRootReceiver.trim();
285
+ // If both are "page" or both are the same variable, we're fine
286
+ if (orig === proposed)
287
+ return { valid: true };
288
+ // "page" is a global receiver - the AI sometimes proposes it even when
289
+ // the original chain was scoped. This is only acceptable if the original
290
+ // chain itself started from "page".
291
+ const PAGE_RECEIVERS = new Set(["page", "context", "browser"]);
292
+ if (PAGE_RECEIVERS.has(proposed) && !PAGE_RECEIVERS.has(orig)) {
293
+ return {
294
+ valid: false,
295
+ reason: `receiver mismatch: original chain rooted at "${orig}" but proposed chain starts from "${proposed}" - this would change element scope`,
296
+ };
297
+ }
298
+ // If the original root is a local variable and proposed is different,
299
+ // accept only if proposed is also a valid-looking identifier (not page).
300
+ // We allow this since sometimes variables are renamed but semantically
301
+ // equivalent.
302
+ if (!PAGE_RECEIVERS.has(orig) && !PAGE_RECEIVERS.has(proposed)) {
303
+ // Both are custom variables - trust the AI's context
304
+ logger_1.logger.warn(`Receiver mismatch: "${orig}" vs "${proposed}" - proceeding cautiously`);
305
+ return { valid: true };
306
+ }
307
+ return { valid: true };
308
+ }
309
+ // ─────────────────────────────────────────────────────────────────
310
+ // GUARD 4 - Placement Validator
311
+ //
312
+ // Validates placement-sensitive rules:
313
+ // - filter/first/last/nth must follow a Locator-producing segment
314
+ // - frameLocator cannot follow a Locator
315
+ // - No duplicate consecutive locator calls without a filter between them
316
+ // when that would be ambiguous (e.g. locator(".a").locator(".a"))
317
+ // ─────────────────────────────────────────────────────────────────
318
+ function placementValidate(segments) {
319
+ for (let i = 0; i < segments.length; i++) {
320
+ const seg = segments[i];
321
+ const prev = i > 0 ? segments[i - 1] : null;
322
+ if (REQUIRES_LOCATOR.has(seg.type) && prev !== null) {
323
+ if (prev.type === "frameLocator") {
324
+ return {
325
+ valid: false,
326
+ reason: `segment[${i}] .${seg.type}() placed immediately after frameLocator() - invalid`,
327
+ };
328
+ }
329
+ }
330
+ // filter() must have a non-empty content
331
+ if (seg.type === "filter") {
332
+ const hasAnyPredicate = (seg.hasText && seg.hasText.trim() !== "") ||
333
+ (seg.hasNotText && seg.hasNotText.trim() !== "") ||
334
+ (seg.has && seg.has.trim() !== "") ||
335
+ (seg.hasNot && seg.hasNot.trim() !== "");
336
+ if (!hasAnyPredicate) {
337
+ return {
338
+ valid: false,
339
+ reason: `segment[${i}] filter() has all-empty predicates - would be a no-op`,
340
+ };
341
+ }
342
+ }
343
+ }
344
+ return { valid: true };
345
+ }
346
+ // ─────────────────────────────────────────────────────────────────
347
+ // GUARD 5 - Semantic Upgrader
348
+ //
349
+ // Opportunistically upgrades flat CSS locator segments to semantic
350
+ // equivalents following Playwright's priority hierarchy:
351
+ // 1. getByTestId (data-testid)
352
+ // 2. getByRole (aria role inference from CSS)
353
+ // 3. getByLabel (form elements)
354
+ // 4. locator (CSS - unchanged)
355
+ //
356
+ // This guard NEVER rejects - it only upgrades and returns the
357
+ // possibly-improved segments.
358
+ // ─────────────────────────────────────────────────────────────────
359
+ const ROLE_BY_TAG = {
360
+ button: "button",
361
+ a: "link",
362
+ input: "textbox", // overridden for checkboxes etc. below
363
+ textarea: "textbox",
364
+ select: "combobox",
365
+ h1: "heading",
366
+ h2: "heading",
367
+ h3: "heading",
368
+ h4: "heading",
369
+ h5: "heading",
370
+ h6: "heading",
371
+ nav: "navigation",
372
+ main: "main",
373
+ header: "banner",
374
+ footer: "contentinfo",
375
+ aside: "complementary",
376
+ };
377
+ function tryUpgradeToSemantic(seg) {
378
+ if (seg.type !== "locator")
379
+ return seg;
380
+ const css = seg.selector.trim();
381
+ // Priority 1: data-testid
382
+ const testIdMatch = css.match(/\[data-testid=["']([^"']+)["']\]/);
383
+ if (testIdMatch) {
384
+ return { type: "getByTestId", testId: testIdMatch[1] };
385
+ }
386
+ // Priority 2: aria-label
387
+ const ariaLabelMatch = css.match(/\[aria-label=["']([^"']+)["']\]/);
388
+ if (ariaLabelMatch) {
389
+ return { type: "getByLabel", text: ariaLabelMatch[1] };
390
+ }
391
+ // Priority 3: role inference from pure tag selectors
392
+ const tagOnlyMatch = css.match(/^([a-zA-Z][a-zA-Z0-9]*)$/);
393
+ if (tagOnlyMatch) {
394
+ const tag = tagOnlyMatch[1].toLowerCase();
395
+ const role = ROLE_BY_TAG[tag];
396
+ if (role) {
397
+ return { type: "getByRole", role };
398
+ }
399
+ }
400
+ // Priority 3b: button[type="submit"] or button.classname - still a button role
401
+ const buttonMatch = css.match(/^button(?:[.[#][^\s]*)?$/);
402
+ if (buttonMatch) {
403
+ return { type: "getByRole", role: "button" };
404
+ }
405
+ // Priority 4: has-text() - convert to getByText
406
+ const hasTextMatch = css.match(/^.*:has-text\(["']([^"']+)["']\)$/);
407
+ if (hasTextMatch) {
408
+ return { type: "getByText", text: hasTextMatch[1] };
409
+ }
410
+ // Otherwise leave as CSS locator
411
+ return seg;
412
+ }
413
+ function semanticUpgrade(segments) {
414
+ return segments.map(tryUpgradeToSemantic);
415
+ }
416
+ // ─────────────────────────────────────────────────────────────────
417
+ // CODE GENERATOR
418
+ //
419
+ // Converts validated ChainSegment[] into clean TypeScript source code.
420
+ // Respects developer style: multi-segment chains get line-broken only
421
+ // when depth >= 3 (configurable).
422
+ // ─────────────────────────────────────────────────────────────────
423
+ function segmentsToCode(receiver, segments, multiLine = false) {
424
+ const sep = multiLine ? "\n " : "";
425
+ let code = receiver;
426
+ for (const seg of segments) {
427
+ const prefix = multiLine ? `\n ` : "";
428
+ switch (seg.type) {
429
+ case "locator":
430
+ code += `${prefix}.locator(${q(seg.selector)})`;
431
+ break;
432
+ case "frameLocator":
433
+ code += `${prefix}.frameLocator(${q(seg.selector)})`;
434
+ break;
435
+ case "getByRole": {
436
+ const opts = [];
437
+ if (seg.name !== undefined)
438
+ opts.push(`name: ${q(seg.name)}`);
439
+ if (seg.exact !== undefined)
440
+ opts.push(`exact: ${seg.exact}`);
441
+ const optStr = opts.length > 0 ? `, { ${opts.join(", ")} }` : "";
442
+ code += `${prefix}.getByRole(${q(seg.role)}${optStr})`;
443
+ break;
444
+ }
445
+ case "getByLabel": {
446
+ const opts = seg.exact !== undefined ? `, { exact: ${seg.exact} }` : "";
447
+ code += `${prefix}.getByLabel(${q(seg.text)}${opts})`;
448
+ break;
449
+ }
450
+ case "getByText": {
451
+ const opts = seg.exact !== undefined ? `, { exact: ${seg.exact} }` : "";
452
+ code += `${prefix}.getByText(${q(seg.text)}${opts})`;
453
+ break;
454
+ }
455
+ case "getByPlaceholder":
456
+ code += `${prefix}.getByPlaceholder(${q(seg.text)})`;
457
+ break;
458
+ case "getByTestId":
459
+ code += `${prefix}.getByTestId(${q(seg.testId)})`;
460
+ break;
461
+ case "getByAltText":
462
+ code += `${prefix}.getByAltText(${q(seg.text)})`;
463
+ break;
464
+ case "getByTitle":
465
+ code += `${prefix}.getByTitle(${q(seg.text)})`;
466
+ break;
467
+ case "filter": {
468
+ const parts = [];
469
+ if (seg.hasText)
470
+ parts.push(`hasText: ${q(seg.hasText)}`);
471
+ if (seg.hasNotText)
472
+ parts.push(`hasNotText: ${q(seg.hasNotText)}`);
473
+ if (seg.has)
474
+ parts.push(`has: ${seg.has}`); // has is a Locator expr
475
+ if (seg.hasNot)
476
+ parts.push(`hasNot: ${seg.hasNot}`);
477
+ code += `${prefix}.filter({ ${parts.join(", ")} })`;
478
+ break;
479
+ }
480
+ case "first":
481
+ code += `${prefix}.first()`;
482
+ break;
483
+ case "last":
484
+ code += `${prefix}.last()`;
485
+ break;
486
+ case "nth":
487
+ code += `${prefix}.nth(${seg.index})`;
488
+ break;
489
+ }
490
+ }
491
+ return code;
492
+ }
493
+ // Helper: quote a string safely, escaping internal double quotes
494
+ function q(s) {
495
+ return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
496
+ }
497
+ class ChainValidator {
498
+ validate(input) {
499
+ const { segments: rawSegments, originalRootReceiver, proposedRootReceiver, rootType = "unknown", applySemanticUpgrade = true, } = input;
500
+ // Guard 1: Schema
501
+ const schemaResult = schemaValidate(rawSegments);
502
+ if (!schemaResult.valid) {
503
+ logger_1.logger.warn(`Schema: ${schemaResult.reason}`);
504
+ return schemaResult;
505
+ }
506
+ let segments = rawSegments;
507
+ // Guard 2: Type flow
508
+ const effectiveRootType = rootType === "unknown" ? "Locator" : rootType;
509
+ // For Page-level (page.xxx), the root is not yet a Locator/FrameLocator
510
+ // - we treat the first segment as the producer.
511
+ const isPageReceiver = originalRootReceiver === "page" ||
512
+ originalRootReceiver === "context" ||
513
+ originalRootReceiver === "browser";
514
+ const typeFlowResult = typeFlowValidate(segments, isPageReceiver ? "unknown" : effectiveRootType);
515
+ if (!typeFlowResult.valid) {
516
+ logger_1.logger.warn(`TypeFlow: ${typeFlowResult.reason}`);
517
+ return typeFlowResult;
518
+ }
519
+ // Guard 3: Receiver compatibility
520
+ const receiverResult = receiverValidate(segments, originalRootReceiver, proposedRootReceiver);
521
+ if (!receiverResult.valid) {
522
+ logger_1.logger.warn(`Receiver: ${receiverResult.reason}`);
523
+ return receiverResult;
524
+ }
525
+ // Guard 4: Placement
526
+ const placementResult = placementValidate(segments);
527
+ if (!placementResult.valid) {
528
+ logger_1.logger.warn(`Placement: ${placementResult.reason}`);
529
+ return placementResult;
530
+ }
531
+ // Guard 5: Semantic upgrade (non-rejecting)
532
+ if (applySemanticUpgrade) {
533
+ segments = semanticUpgrade(segments);
534
+ }
535
+ // Generate code
536
+ const multiLine = segments.length >= 3;
537
+ const code = segmentsToCode(proposedRootReceiver, segments, multiLine);
538
+ logger_1.logger.debug(`Valid chain (${segments.length} segments): ${code}`);
539
+ return { valid: true, segments, code, multiLine };
540
+ }
541
+ /**
542
+ * Lightweight check: does this segment array describe the same
543
+ * semantic intent as the old flat selector string?
544
+ * Used as a sanity check before committing an AST write.
545
+ */
546
+ intentMatches(segments, oldFlatSelector) {
547
+ const lastSeg = segments[segments.length - 1];
548
+ if (!lastSeg)
549
+ return false;
550
+ const oldLower = oldFlatSelector.toLowerCase();
551
+ switch (lastSeg.type) {
552
+ case "locator":
553
+ return oldLower.includes(lastSeg.selector.toLowerCase());
554
+ case "getByRole":
555
+ return (oldLower.includes(lastSeg.role.toLowerCase()) ||
556
+ oldLower.includes("role="));
557
+ case "getByLabel":
558
+ case "getByText":
559
+ case "getByPlaceholder":
560
+ case "getByAltText":
561
+ case "getByTitle":
562
+ return oldLower.includes(lastSeg.text.toLowerCase());
563
+ case "getByTestId":
564
+ return oldLower.includes(lastSeg.testId.toLowerCase());
565
+ default:
566
+ return true;
567
+ }
568
+ }
569
+ }
570
+ exports.ChainValidator = ChainValidator;
@@ -0,0 +1,6 @@
1
+ import { Project } from "ts-morph";
2
+ import type { ASTUpdateResult } from "./ASTSourceUpdater.js";
3
+ export declare class CrossFileHealer {
4
+ static heal(modulePath: string, symbolName: string, newValue: string, callerFilePath: string, project: Project, depth?: number): ASTUpdateResult;
5
+ }
6
+ //# sourceMappingURL=CrossFileHealer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CrossFileHealer.d.ts","sourceRoot":"","sources":["../../src/services/CrossFileHealer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGnC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAoB7D,qBAAa,eAAe;IAC1B,MAAM,CAAC,IAAI,CACT,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,OAAO,EAChB,KAAK,SAAI,GACR,eAAe;CAqFnB"}