@qa-gentic/stlc-agents 1.0.26 → 1.0.28

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 (90) hide show
  1. package/ARCHITECTURE-ADO.md +350 -0
  2. package/ARCHITECTURE-JIRA.md +203 -0
  3. package/QUICKSTART-ADO.md +400 -0
  4. package/QUICKSTART-JIRA.md +334 -0
  5. package/README.md +49 -0
  6. package/package.json +18 -6
  7. package/skills/migrate-framework/SKILL.md +207 -0
  8. package/src/stlc_agents/__pycache__/__init__.cpython-313.pyc +0 -0
  9. package/src/stlc_agents/agent_gherkin_generator/__pycache__/__init__.cpython-313.pyc +0 -0
  10. package/src/stlc_agents/agent_gherkin_generator/__pycache__/server.cpython-313.pyc +0 -0
  11. package/src/stlc_agents/agent_gherkin_generator/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  12. package/src/stlc_agents/agent_gherkin_generator/tools/__pycache__/ado_gherkin.cpython-313.pyc +0 -0
  13. package/src/stlc_agents/agent_helix_writer/__pycache__/__init__.cpython-313.pyc +0 -0
  14. package/src/stlc_agents/agent_helix_writer/__pycache__/server.cpython-313.pyc +0 -0
  15. package/src/stlc_agents/agent_helix_writer/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  16. package/src/stlc_agents/agent_helix_writer/tools/__pycache__/boilerplate.cpython-313.pyc +0 -0
  17. package/src/stlc_agents/agent_helix_writer/tools/__pycache__/helix_write.cpython-313.pyc +0 -0
  18. package/src/stlc_agents/agent_jira_manager/__pycache__/__init__.cpython-313.pyc +0 -0
  19. package/src/stlc_agents/agent_jira_manager/__pycache__/server.cpython-313.pyc +0 -0
  20. package/src/stlc_agents/agent_jira_manager/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  21. package/src/stlc_agents/agent_jira_manager/tools/__pycache__/jira_workitem.cpython-313.pyc +0 -0
  22. package/src/stlc_agents/agent_migration/__init__.py +0 -0
  23. package/src/stlc_agents/agent_migration/__pycache__/__init__.cpython-313.pyc +0 -0
  24. package/src/stlc_agents/agent_migration/__pycache__/_migrate.cpython-313.pyc +0 -0
  25. package/src/stlc_agents/agent_migration/__pycache__/cli.cpython-313.pyc +0 -0
  26. package/src/stlc_agents/agent_migration/__pycache__/detector.cpython-313.pyc +0 -0
  27. package/src/stlc_agents/agent_migration/__pycache__/mapper.cpython-313.pyc +0 -0
  28. package/src/stlc_agents/agent_migration/__pycache__/reporter.cpython-313.pyc +0 -0
  29. package/src/stlc_agents/agent_migration/__pycache__/server.cpython-313.pyc +0 -0
  30. package/src/stlc_agents/agent_migration/_migrate.py +1398 -0
  31. package/src/stlc_agents/agent_migration/cli.py +217 -0
  32. package/src/stlc_agents/agent_migration/detector.py +81 -0
  33. package/src/stlc_agents/agent_migration/mapper.py +439 -0
  34. package/src/stlc_agents/agent_migration/reporter.py +86 -0
  35. package/src/stlc_agents/agent_migration/server.py +267 -0
  36. package/src/stlc_agents/agent_migration/transformer/__init__.py +0 -0
  37. package/src/stlc_agents/agent_migration/transformer/__pycache__/__init__.cpython-313.pyc +0 -0
  38. package/src/stlc_agents/agent_migration/transformer/__pycache__/config_merger.cpython-313.pyc +0 -0
  39. package/src/stlc_agents/agent_migration/transformer/__pycache__/healer_injector.cpython-313.pyc +0 -0
  40. package/src/stlc_agents/agent_migration/transformer/__pycache__/import_fixer.cpython-313.pyc +0 -0
  41. package/src/stlc_agents/agent_migration/transformer/__pycache__/js_to_ts.cpython-313.pyc +0 -0
  42. package/src/stlc_agents/agent_migration/transformer/__pycache__/local_var_hoister.cpython-313.pyc +0 -0
  43. package/src/stlc_agents/agent_migration/transformer/__pycache__/locator_moderniser.cpython-313.pyc +0 -0
  44. package/src/stlc_agents/agent_migration/transformer/__pycache__/locator_registrar.cpython-313.pyc +0 -0
  45. package/src/stlc_agents/agent_migration/transformer/__pycache__/spec_to_bdd.cpython-313.pyc +0 -0
  46. package/src/stlc_agents/agent_migration/transformer/config_merger.py +513 -0
  47. package/src/stlc_agents/agent_migration/transformer/healer_injector.py +1143 -0
  48. package/src/stlc_agents/agent_migration/transformer/import_fixer.py +419 -0
  49. package/src/stlc_agents/agent_migration/transformer/js_to_ts.py +413 -0
  50. package/src/stlc_agents/agent_migration/transformer/local_var_hoister.py +378 -0
  51. package/src/stlc_agents/agent_migration/transformer/locator_moderniser.py +132 -0
  52. package/src/stlc_agents/agent_migration/transformer/locator_registrar.py +328 -0
  53. package/src/stlc_agents/agent_migration/transformer/spec_to_bdd.py +820 -0
  54. package/src/stlc_agents/agent_playwright_generator/__pycache__/__init__.cpython-313.pyc +0 -0
  55. package/src/stlc_agents/agent_playwright_generator/__pycache__/server.cpython-313.pyc +0 -0
  56. package/src/stlc_agents/agent_playwright_generator/server.py +926 -91
  57. package/src/stlc_agents/agent_playwright_generator/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  58. package/src/stlc_agents/agent_playwright_generator/tools/__pycache__/ado_attach.cpython-313.pyc +0 -0
  59. package/src/stlc_agents/agent_test_case_manager/__pycache__/__init__.cpython-313.pyc +0 -0
  60. package/src/stlc_agents/agent_test_case_manager/__pycache__/server.cpython-313.pyc +0 -0
  61. package/src/stlc_agents/agent_test_case_manager/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  62. package/src/stlc_agents/agent_test_case_manager/tools/__pycache__/ado_workitem.cpython-313.pyc +0 -0
  63. package/src/stlc_agents/shared/__pycache__/__init__.cpython-313.pyc +0 -0
  64. package/src/stlc_agents/shared/__pycache__/auth.cpython-313.pyc +0 -0
  65. package/src/stlc_agents/shared/__pycache__/cost_tracker.cpython-313.pyc +0 -0
  66. package/src/stlc_agents/shared/__pycache__/pricing.cpython-313.pyc +0 -0
  67. package/src/stlc_agents/shared_jira/__pycache__/__init__.cpython-313.pyc +0 -0
  68. package/src/stlc_agents/shared_jira/__pycache__/auth.cpython-313.pyc +0 -0
  69. package/src/stlc_agents/webhook_orchestrator/__pycache__/__init__.cpython-313.pyc +0 -0
  70. package/src/stlc_agents/webhook_orchestrator/__pycache__/agent_runner.cpython-313.pyc +0 -0
  71. package/src/stlc_agents/webhook_orchestrator/__pycache__/models.cpython-313.pyc +0 -0
  72. package/src/stlc_agents/webhook_orchestrator/__pycache__/orchestrator.cpython-313.pyc +0 -0
  73. package/src/stlc_agents/webhook_orchestrator/__pycache__/webhook_bridge.cpython-313.pyc +0 -0
  74. package/src/stlc_agents/agent_gherkin_generator/__pycache__/__init__.cpython-314.pyc +0 -0
  75. package/src/stlc_agents/agent_gherkin_generator/__pycache__/server.cpython-314.pyc +0 -0
  76. package/src/stlc_agents/agent_gherkin_generator/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  77. package/src/stlc_agents/agent_gherkin_generator/tools/__pycache__/ado_gherkin.cpython-314.pyc +0 -0
  78. package/src/stlc_agents/agent_helix_writer/__pycache__/server.cpython-314.pyc +0 -0
  79. package/src/stlc_agents/agent_playwright_generator/__pycache__/__init__.cpython-314.pyc +0 -0
  80. package/src/stlc_agents/agent_playwright_generator/__pycache__/server.cpython-314.pyc +0 -0
  81. package/src/stlc_agents/agent_playwright_generator/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  82. package/src/stlc_agents/agent_playwright_generator/tools/__pycache__/ado_attach.cpython-314.pyc +0 -0
  83. package/src/stlc_agents/agent_test_case_manager/__pycache__/__init__.cpython-314.pyc +0 -0
  84. package/src/stlc_agents/agent_test_case_manager/__pycache__/server.cpython-314.pyc +0 -0
  85. package/src/stlc_agents/agent_test_case_manager/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  86. package/src/stlc_agents/agent_test_case_manager/tools/__pycache__/ado_workitem.cpython-314.pyc +0 -0
  87. package/src/stlc_agents/shared/__pycache__/__init__.cpython-314.pyc +0 -0
  88. package/src/stlc_agents/shared/__pycache__/auth.cpython-314.pyc +0 -0
  89. package/src/stlc_agents/shared/__pycache__/cost_tracker.cpython-314.pyc +0 -0
  90. package/src/stlc_agents/shared/__pycache__/pricing.cpython-314.pyc +0 -0
@@ -0,0 +1,328 @@
1
+ """
2
+ Transform plain-string locator files into the agent-friendly format.
3
+
4
+ Input (migrated raw format):
5
+ export const dashboardLocators = {
6
+ userAvatarButton: "navbar_avatar-btn",
7
+ saveButton: 'button.save-button',
8
+ };
9
+
10
+ Output (agent-friendly):
11
+ export const dashboardLocators = {
12
+ userAvatarButton: { selector: "navbar_avatar-btn", intent: "user avatar button", stability: 80 },
13
+ saveButton: { selector: "button.save-button", intent: "save button", stability: 30 },
14
+ } as const;
15
+ export type DashboardLocatorKey = keyof typeof dashboardLocators;
16
+
17
+ Arrow-function / method-shorthand entries are kept unchanged because they
18
+ produce dynamic selectors that cannot be statically registered.
19
+
20
+ Text-only values (display strings, URLs, error messages) are also kept as-is.
21
+ """
22
+ from __future__ import annotations
23
+ import re
24
+
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Helpers
28
+ # ---------------------------------------------------------------------------
29
+
30
+ def _camel_to_words(name: str) -> str:
31
+ """userAvatarButton → 'user avatar button'."""
32
+ s = re.sub(r"([A-Z])", r" \1", name)
33
+ return s.strip().lower()
34
+
35
+
36
+ def _infer_stability(selector: str) -> int:
37
+ """Assign a 0-100 confidence score based on selector robustness."""
38
+ if "[data-testid" in selector or "data-testid=" in selector:
39
+ return 100
40
+ if selector.startswith("#"):
41
+ return 80
42
+ # Bare test-id strings: alphanumeric + hyphens/underscores only (no CSS operators)
43
+ if re.match(r"^[a-z_][\w-]*$", selector):
44
+ return 80
45
+ if "[aria-label=" in selector or "[placeholder=" in selector:
46
+ return 70
47
+ if "[dusk=" in selector or "[data-" in selector:
48
+ return 70
49
+ if selector.startswith(".") or any(c in selector for c in ">+~"):
50
+ return 30
51
+ return 50
52
+
53
+
54
+ def _is_pure_selector(value: str) -> bool:
55
+ """
56
+ Return True only for values that look like CSS / test-id selectors.
57
+ Excludes: URLs, plain text labels, error messages.
58
+ """
59
+ if value.startswith("http://") or value.startswith("https://"):
60
+ return False
61
+ # Contains a space AND no CSS-selector chars → probably display text
62
+ if " " in value and not any(c in value for c in "#.[>+~:=*^$|@(\"'"):
63
+ return False
64
+ return True
65
+
66
+
67
+ def _quote(value: str, original_quote: str) -> str:
68
+ """Pick inner quote avoiding the same char that appears in value."""
69
+ if '"' in value and "'" not in value:
70
+ return "'"
71
+ if "'" in value and '"' not in value:
72
+ return '"'
73
+ # Prefer double-quotes; escape would be needed only if value has both — rare.
74
+ return '"'
75
+
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Per-object body transformer
79
+ # ---------------------------------------------------------------------------
80
+
81
+ def _transform_body(block_name: str, body: str) -> tuple[str, list[str]]:
82
+ """
83
+ Transform the body text (content between the outer { }) of a locator object.
84
+ Returns (transformed_body, list_of_change_descriptions).
85
+ """
86
+ changes: list[str] = []
87
+ lines = body.split("\n")
88
+ result: list[str] = []
89
+ i = 0
90
+
91
+ while i < len(lines):
92
+ raw = lines[i]
93
+ stripped = raw.rstrip()
94
+
95
+ # ── Pattern 1: single-line string property ──────────────────────────
96
+ # ` key: "value",` or ` key: 'value',`
97
+ m1 = re.match(r"^( {2,})(\w+)\s*:\s*(['\"])(.*?)\3,?\s*$", stripped)
98
+ if m1:
99
+ indent, key, q, value = m1.group(1), m1.group(2), m1.group(3), m1.group(4)
100
+ if _is_pure_selector(value):
101
+ intent = _camel_to_words(key)
102
+ stability = _infer_stability(value)
103
+ iq = _quote(value, q)
104
+ result.append(
105
+ f"{indent}{key}: {{ selector: {iq}{value}{iq},"
106
+ f' intent: "{intent}", stability: {stability} }},'
107
+ )
108
+ changes.append(f"Registered locator: {block_name}.{key}")
109
+ i += 1
110
+ continue
111
+
112
+ # ── Pattern 2: multi-line string property ────────────────────────────
113
+ # ` key:\n "value",`
114
+ m2 = re.match(r"^( {2,})(\w+)\s*:\s*$", stripped)
115
+ if m2 and (i + 1) < len(lines):
116
+ next_line = lines[i + 1].strip()
117
+ mval = re.match(r"^(['\"])(.*?)\1,?\s*$", next_line)
118
+ if mval:
119
+ indent, key = m2.group(1), m2.group(2)
120
+ q, value = mval.group(1), mval.group(2)
121
+ if _is_pure_selector(value):
122
+ intent = _camel_to_words(key)
123
+ stability = _infer_stability(value)
124
+ iq = _quote(value, q)
125
+ result.append(
126
+ f"{indent}{key}: {{ selector: {iq}{value}{iq},"
127
+ f' intent: "{intent}", stability: {stability} }},'
128
+ )
129
+ changes.append(f"Registered multi-line locator: {block_name}.{key}")
130
+ i += 2
131
+ continue
132
+
133
+ # ── Pattern 3: multi-line string-concat property ─────────────────
134
+ # ```
135
+ # key:
136
+ # "a" +
137
+ # "b" +
138
+ # "c",
139
+ # ```
140
+ # Collect adjacent quoted strings joined by `+` until we hit the
141
+ # entry's trailing comma. Emits one `{ selector: "a" + "b" + "c",
142
+ # intent, stability }` entry. Stability is computed from the
143
+ # concatenated string so multi-line selectors get the same score
144
+ # they'd receive if written on one line.
145
+ indent, key = m2.group(1), m2.group(2)
146
+ concat_re = re.compile(r"^(['\"])(.*?)\1\s*(\+)?\s*$")
147
+ parts: list[tuple[str, str]] = [] # (quote, value)
148
+ j = i + 1
149
+ done = False
150
+ while j < len(lines):
151
+ seg = lines[j].strip().rstrip(",")
152
+ terminates_here = lines[j].rstrip().endswith(",")
153
+ seg_m = concat_re.match(seg)
154
+ if not seg_m:
155
+ break
156
+ parts.append((seg_m.group(1), seg_m.group(2)))
157
+ j += 1
158
+ if terminates_here:
159
+ done = True
160
+ break
161
+ if done and len(parts) >= 2:
162
+ joined_value = "".join(p[1] for p in parts)
163
+ if _is_pure_selector(joined_value):
164
+ intent = _camel_to_words(key)
165
+ stability = _infer_stability(joined_value)
166
+ # Preserve the original quoted-string form so authors keep
167
+ # their choice of quote and line-by-line layout signal —
168
+ # the runtime concatenation is identical.
169
+ rendered = " + ".join(f"{q}{v}{q}" for q, v in parts)
170
+ result.append(
171
+ f"{indent}{key}: {{ selector: {rendered},"
172
+ f' intent: "{intent}", stability: {stability} }},'
173
+ )
174
+ changes.append(f"Registered concat locator: {block_name}.{key}")
175
+ i = j
176
+ continue
177
+
178
+ result.append(raw)
179
+ i += 1
180
+
181
+ return "\n".join(result), changes
182
+
183
+
184
+ # ---------------------------------------------------------------------------
185
+ # File-level transformer
186
+ # ---------------------------------------------------------------------------
187
+
188
+ # Matches the opening of a locator constant:
189
+ # export const xxxLocators = {
190
+ _BLOCK_OPEN_RE = re.compile(
191
+ r"(export\s+const\s+(\w+Locators)\s*=\s*\{)[ \t]*\n",
192
+ re.MULTILINE,
193
+ )
194
+
195
+ # After transformation we append: } as const;\nexport type ...
196
+ _AS_CONST_RE = re.compile(r"\}\s*;?\s*$")
197
+ _TYPE_EXPORT_RE = re.compile(r"export type \w+LocatorKey = keyof typeof \w+Locators;")
198
+
199
+
200
+ def transform_locator_file(content: str) -> tuple[str, list[str]]:
201
+ """
202
+ Transform all locator-constant blocks in a file.
203
+ Returns (transformed_content, changes).
204
+ """
205
+ if "selector:" in content and "intent:" in content:
206
+ # Already in agent-friendly format — idempotent
207
+ return content, []
208
+
209
+ all_changes: list[str] = []
210
+ output_parts: list[str] = []
211
+ cursor = 0
212
+
213
+ for m in _BLOCK_OPEN_RE.finditer(content):
214
+ block_start = m.start()
215
+ body_start = m.end() # first char after the opening `{\n`
216
+ block_name = m.group(2)
217
+
218
+ # Find the matching closing brace with depth counting
219
+ depth = 1
220
+ j = body_start
221
+ while j < len(content) and depth > 0:
222
+ c = content[j]
223
+ if c == "{":
224
+ depth += 1
225
+ elif c == "}":
226
+ depth -= 1
227
+ j += 1
228
+ # j now points to one past the closing `}`
229
+ body = content[body_start : j - 1] # exclude the final `}`
230
+ suffix = content[j - 1 :] # `}...` to end (or next block)
231
+
232
+ # Transform the body
233
+ new_body, changes = _transform_body(block_name, body)
234
+ all_changes.extend(changes)
235
+
236
+ # Build a pascal-case type name: dashboardLocators → Dashboard
237
+ pascal = block_name[0].upper() + block_name[1:]
238
+ pascal = pascal.replace("Locators", "")
239
+ type_line = f"export type {pascal}LocatorKey = keyof typeof {block_name};"
240
+
241
+ # Everything before this block
242
+ output_parts.append(content[cursor:block_start])
243
+
244
+ # Reconstructed block
245
+ output_parts.append(m.group(1) + "\n") # `export const xxx = {`
246
+ output_parts.append(new_body)
247
+ # Close: `} as const;`
248
+ output_parts.append("} as const;\n")
249
+ if changes: # only add type export if we actually transformed something
250
+ output_parts.append(type_line + "\n")
251
+
252
+ # Skip over the original closing `};` in suffix
253
+ # suffix starts with `}` — find the end of `};` or `}`
254
+ tail_m = re.match(r"\}\s*;?\s*\n?", suffix)
255
+ tail_skip = tail_m.end() if tail_m else 1
256
+ cursor = j - 1 + tail_skip
257
+
258
+ # Append remaining file content (after last block)
259
+ output_parts.append(content[cursor:])
260
+
261
+ return "".join(output_parts), all_changes
262
+
263
+
264
+ # ---------------------------------------------------------------------------
265
+ # Post-migration: fix selector accesses in page objects / step files
266
+ # ---------------------------------------------------------------------------
267
+
268
+ def collect_object_keys(locator_file_content: str) -> dict[str, set[str]]:
269
+ """
270
+ Parse an already-transformed locator file and return a mapping of
271
+ { locator_name → set_of_keys_that_are_objects }.
272
+
273
+ Only keys with `{ selector: ... }` values are object-valued.
274
+ String-valued keys (display text, URLs) are excluded.
275
+ """
276
+ result: dict[str, set[str]] = {}
277
+ _OBJ_KEY_RE = re.compile(r"^\s{2,}(\w+)\s*:\s*\{[^}]*selector:", re.MULTILINE)
278
+
279
+ for m_block in _BLOCK_OPEN_RE.finditer(locator_file_content):
280
+ block_name = m_block.group(2)
281
+ body_start = m_block.end()
282
+ depth = 1
283
+ j = body_start
284
+ while j < len(locator_file_content) and depth > 0:
285
+ c = locator_file_content[j]
286
+ if c == "{":
287
+ depth += 1
288
+ elif c == "}":
289
+ depth -= 1
290
+ j += 1
291
+ body = locator_file_content[body_start : j - 1]
292
+ object_keys = {km.group(1) for km in _OBJ_KEY_RE.finditer(body)}
293
+ result[block_name] = object_keys
294
+
295
+ return result
296
+
297
+
298
+ def fix_selector_accesses(
299
+ content: str,
300
+ object_keys_by_name: dict[str, set[str]],
301
+ ) -> tuple[str, list[str]]:
302
+ """
303
+ In a page-object / step file, rewrite `xxxLocators.key` → `xxxLocators.key.selector`
304
+ for every key that is now a structured object `{ selector, intent, stability }`.
305
+
306
+ Leaves:
307
+ - string-valued keys unchanged (display text, URLs)
308
+ - function-call entries unchanged: xxxLocators.fn(arg)
309
+ - already-fixed accesses: xxxLocators.key.selector
310
+ """
311
+ changes: list[str] = []
312
+ for loc_name, obj_keys in object_keys_by_name.items():
313
+ if not obj_keys:
314
+ continue
315
+ # Build alternation of all object keys for this locator group
316
+ keys_alt = "|".join(re.escape(k) for k in sorted(obj_keys, key=len, reverse=True))
317
+ pattern = re.compile(
318
+ rf"\b({re.escape(loc_name)})\.({keys_alt})\b"
319
+ r"(?!\.(?:selector|intent|stability))" # not already .selector/.intent/.stability
320
+ r"(?!\s*\()", # not a function call
321
+ )
322
+
323
+ def _replace(m: re.Match, ln=loc_name) -> str:
324
+ changes.append(f"Added .selector: {m.group(0)}")
325
+ return f"{m.group(1)}.{m.group(2)}.selector"
326
+
327
+ content = pattern.sub(_replace, content)
328
+ return content, changes