@qa-gentic/stlc-agents 1.0.14 → 1.0.16

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 (43) hide show
  1. package/README.md +1 -2
  2. package/package.json +1 -1
  3. package/skills/AGENT-BEHAVIOR.md +15 -22
  4. package/skills/deduplication-protocol/SKILL.md +51 -3
  5. package/skills/generate-gherkin/SKILL.md +15 -30
  6. package/skills/generate-gherkin/references/step-by-step.md +2 -6
  7. package/skills/generate-playwright-code/SKILL.md +19 -122
  8. package/skills/generate-playwright-code/references/file-structures.md +128 -0
  9. package/skills/generate-test-cases/SKILL.md +2 -2
  10. package/skills/write-helix-files/SKILL.md +12 -3
  11. package/src/stlc_agents/__pycache__/__init__.cpython-310.pyc +0 -0
  12. package/src/stlc_agents/agent_gherkin_generator/__pycache__/__init__.cpython-310.pyc +0 -0
  13. package/src/stlc_agents/agent_gherkin_generator/__pycache__/server.cpython-310.pyc +0 -0
  14. package/src/stlc_agents/agent_gherkin_generator/server.py +14 -62
  15. package/src/stlc_agents/agent_gherkin_generator/tools/__pycache__/__init__.cpython-310.pyc +0 -0
  16. package/src/stlc_agents/agent_gherkin_generator/tools/__pycache__/ado_gherkin.cpython-310.pyc +0 -0
  17. package/src/stlc_agents/agent_gherkin_generator/tools/ado_gherkin.py +5 -126
  18. package/src/stlc_agents/agent_helix_writer/__pycache__/__init__.cpython-310.pyc +0 -0
  19. package/src/stlc_agents/agent_helix_writer/__pycache__/server.cpython-310.pyc +0 -0
  20. package/src/stlc_agents/agent_helix_writer/tools/__pycache__/__init__.cpython-310.pyc +0 -0
  21. package/src/stlc_agents/agent_helix_writer/tools/__pycache__/boilerplate.cpython-310.pyc +0 -0
  22. package/src/stlc_agents/agent_helix_writer/tools/__pycache__/helix_write.cpython-310.pyc +0 -0
  23. package/src/stlc_agents/agent_jira_manager/__pycache__/__init__.cpython-310.pyc +0 -0
  24. package/src/stlc_agents/agent_jira_manager/__pycache__/server.cpython-310.pyc +0 -0
  25. package/src/stlc_agents/agent_jira_manager/server.py +0 -32
  26. package/src/stlc_agents/agent_jira_manager/tools/__pycache__/__init__.cpython-310.pyc +0 -0
  27. package/src/stlc_agents/agent_jira_manager/tools/__pycache__/jira_workitem.cpython-310.pyc +0 -0
  28. package/src/stlc_agents/agent_jira_manager/tools/jira_workitem.py +3 -78
  29. package/src/stlc_agents/agent_playwright_generator/__pycache__/__init__.cpython-310.pyc +0 -0
  30. package/src/stlc_agents/agent_playwright_generator/__pycache__/server.cpython-310.pyc +0 -0
  31. package/src/stlc_agents/agent_playwright_generator/server.py +57 -7
  32. package/src/stlc_agents/agent_playwright_generator/tools/__pycache__/__init__.cpython-310.pyc +0 -0
  33. package/src/stlc_agents/agent_playwright_generator/tools/__pycache__/ado_attach.cpython-310.pyc +0 -0
  34. package/src/stlc_agents/agent_test_case_manager/__pycache__/__init__.cpython-310.pyc +0 -0
  35. package/src/stlc_agents/agent_test_case_manager/__pycache__/server.cpython-310.pyc +0 -0
  36. package/src/stlc_agents/agent_test_case_manager/server.py +4 -4
  37. package/src/stlc_agents/agent_test_case_manager/tools/__pycache__/__init__.cpython-310.pyc +0 -0
  38. package/src/stlc_agents/agent_test_case_manager/tools/__pycache__/ado_workitem.cpython-310.pyc +0 -0
  39. package/src/stlc_agents/agent_test_case_manager/tools/ado_workitem.py +3 -5
  40. package/src/stlc_agents/shared/__pycache__/__init__.cpython-310.pyc +0 -0
  41. package/src/stlc_agents/shared/__pycache__/auth.cpython-310.pyc +0 -0
  42. package/src/stlc_agents/shared_jira/__pycache__/__init__.cpython-310.pyc +0 -0
  43. package/src/stlc_agents/shared_jira/__pycache__/auth.cpython-310.pyc +0 -0
@@ -27,7 +27,6 @@ from stlc_agents.shared.auth import get_auth_headers, get_signed_in_user
27
27
  from stlc_agents.agent_gherkin_generator.tools.ado_gherkin import (
28
28
  fetch_feature_hierarchy as _fetch_feature,
29
29
  fetch_work_item_for_gherkin as _fetch_wi_for_gherkin,
30
- fetch_epic_hierarchy as _fetch_epic,
31
30
  attach_feature_file as _attach_feature,
32
31
  attach_work_item_file as _attach_wi_file,
33
32
  validate_gherkin_content as _validate_gherkin,
@@ -43,7 +42,7 @@ app = Server("qa-gherkin-generator")
43
42
  # ---------------------------------------------------------------------------
44
43
 
45
44
  def _validate_hierarchy_response(result: dict, scope: str) -> dict:
46
- """Validate fetch_feature_hierarchy / fetch_epic_hierarchy / fetch_work_item_for_gherkin
45
+ """Validate fetch_feature_hierarchy / fetch_work_item_for_gherkin
47
46
  response quality before returning to the user.
48
47
 
49
48
  Checks:
@@ -168,54 +167,17 @@ def _validate_attach_response(result: dict, scope: str) -> dict:
168
167
  @app.list_tools()
169
168
  async def list_tools() -> list[types.Tool]:
170
169
  return [
171
- # ── Epic-scoped fetch (NEW — must be listed FIRST so routing fires before Feature path) ──
172
- types.Tool(
173
- name="fetch_epic_hierarchy",
174
- description=(
175
- "Fetch an Epic work item from Azure DevOps along with ALL child Features, "
176
- "every Feature's child PBIs/Bugs, and every linked test case (including full "
177
- "test steps). ALWAYS use this tool as Step 1 when the user provides an Epic ID. "
178
- "Never fall through to fetch_feature_hierarchy when an Epic ID is given. "
179
- "The response contains: epic metadata, a 'features' list where each entry "
180
- "includes the Feature's own metadata plus its 'child_work_items' (PBIs/Bugs) "
181
- "and 'existing_test_cases'. Analyse ALL child Features and ALL child PBIs/Bugs "
182
- "before generating any Gherkin scenarios — the full hierarchy is the only "
183
- "correct input for Epic-scope regression suites. "
184
- "Generates one .feature file per child Feature; attaches each via "
185
- "attach_gherkin_to_feature. "
186
- "IMPORTANT: Manual test cases (create_and_link_test_cases) MUST NOT be created "
187
- "for the Epic itself — inform the user that test cases can only be created for "
188
- "PBIs, Bugs, or Features."
189
- ),
190
- inputSchema={
191
- "type": "object",
192
- "properties": {
193
- "epic_id": {
194
- "type": "integer",
195
- "description": "ADO Epic work item ID",
196
- },
197
- "organization_url": {
198
- "type": "string",
199
- "description": "e.g. https://dev.azure.com/myorg",
200
- },
201
- "project_name": {
202
- "type": "string",
203
- "description": "ADO project name",
204
- },
205
- },
206
- "required": ["epic_id", "organization_url", "project_name"],
207
- },
208
- ),
209
-
210
- # ── Existing: Feature-scoped fetch ───────────────────────────────────
170
+ # ── Feature-scoped fetch ─────────────────────────────────────────────
211
171
  types.Tool(
212
172
  name="fetch_feature_hierarchy",
213
173
  description=(
214
- "Fetch a Feature work item from Azure DevOps along with all child PBIs/Bugs, "
215
- "their linked test cases with full test steps, and a Gherkin best-practice "
216
- "reference example. Use this as Step 1 when the user provides a Feature ID "
217
- "or when you need the full cross-PBI flow map. "
218
- "For a single PBI or Bug ID, use fetch_work_item_for_gherkin instead."
174
+ "Fetch a Feature work item from Azure DevOps along with all child PBIs/Bugs "
175
+ "and their linked test cases. Use this as Step 1 when the user provides a "
176
+ "Feature ID. Generates 5–10 scenarios covering the full Feature. "
177
+ "For a single PBI or Bug ID, use fetch_work_item_for_gherkin instead. "
178
+ "For an Epic ID: do NOT call any fetch tool — instead tell the user: "
179
+ "'Epic-scoped Gherkin generation is not supported. Please provide a Feature ID "
180
+ "or PBI/Bug ID. To find child Features, look up the Epic in Azure DevOps.'"
219
181
  ),
220
182
  inputSchema={
221
183
  "type": "object",
@@ -228,7 +190,7 @@ async def list_tools() -> list[types.Tool]:
228
190
  },
229
191
  ),
230
192
 
231
- # ── NEW: PBI/Bug-scoped fetch ────────────────────────────────────────
193
+ # ── PBI/Bug-scoped fetch ─────────────────────────────────────────────
232
194
  types.Tool(
233
195
  name="fetch_work_item_for_gherkin",
234
196
  description=(
@@ -236,9 +198,9 @@ async def list_tools() -> list[types.Tool]:
236
198
  "a scoped Gherkin feature file for that work item alone. "
237
199
  "Use this when the user provides a PBI or Bug ID and wants a focused .feature "
238
200
  "file covering only that work item's acceptance criteria (3–9 scenarios). "
239
- "Returns the work item's AC, linked test cases with full steps, parent Feature "
240
- "metadata, a suggested file name, and the Gherkin best-practice reference. "
241
- "For Feature IDs, use fetch_feature_hierarchy instead."
201
+ "Returns the work item's AC, linked test case titles, and parent Feature metadata. "
202
+ "For Feature IDs, use fetch_feature_hierarchy instead. "
203
+ "For Epic IDs, do not call this tool — tell the user to provide a Feature or PBI ID."
242
204
  ),
243
205
  inputSchema={
244
206
  "type": "object",
@@ -367,17 +329,7 @@ async def list_tools() -> list[types.Tool]:
367
329
  @app.call_tool()
368
330
  async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
369
331
  try:
370
- if name == "fetch_epic_hierarchy":
371
- result = await asyncio.to_thread(
372
- _fetch_epic,
373
- arguments["organization_url"],
374
- arguments["project_name"],
375
- arguments["epic_id"],
376
- )
377
- # ── Pre-output validation ─────────────────────────────────────
378
- result["_validation"] = _validate_hierarchy_response(result, "epic")
379
-
380
- elif name == "fetch_feature_hierarchy":
332
+ if name == "fetch_feature_hierarchy":
381
333
  result = await asyncio.to_thread(
382
334
  _fetch_feature,
383
335
  arguments["organization_url"],
@@ -255,11 +255,6 @@ def fetch_work_item_for_gherkin(
255
255
  fields.get("Microsoft.VSTS.TCM.ReproSteps", "") or ""
256
256
  ),
257
257
  "state": fields.get("System.State", ""),
258
- "priority": fields.get("Microsoft.VSTS.Common.Priority", ""),
259
- "story_points": fields.get("Microsoft.VSTS.Scheduling.StoryPoints", ""),
260
- "tags": fields.get("System.Tags", ""),
261
- "area_path": fields.get("System.AreaPath", ""),
262
- "iteration_path": fields.get("System.IterationPath", ""),
263
258
  }
264
259
 
265
260
  # ── Collect linked TC IDs ────────────────────────────────────────────────
@@ -309,8 +304,6 @@ def fetch_work_item_for_gherkin(
309
304
  "existing_test_cases_count": len(test_cases),
310
305
  "suggested_file_name": suggested_file_name,
311
306
  "scope": "work_item",
312
- "scenario_count_rule": f"{_SCENARIO_MIN_WI}–{_SCENARIO_MAX_WI} scenarios for a single PBI/Bug",
313
- "best_practice_example": _GHERKIN_EXAMPLE,
314
307
  }
315
308
 
316
309
 
@@ -393,20 +386,19 @@ def attach_work_item_file(
393
386
  "work_item_id": work_item_id,
394
387
  "file_name": file_name,
395
388
  "attachment_url": attachment_url,
396
- "validation": validation,
389
+ "warnings": validation["warnings"],
397
390
  }
398
391
 
399
392
 
400
393
  # ─────────────────────────────────────────────────────────────────────────────
401
- # INTERNAL HELPER — shared by fetch_feature_hierarchy and fetch_epic_hierarchy
394
+ # INTERNAL HELPER — used by fetch_feature_hierarchy
402
395
  # ─────────────────────────────────────────────────────────────────────────────
403
396
 
404
397
  def _fetch_feature_data(org_url: str, project: str, feature_id: int) -> dict:
405
398
  """
406
399
  Core logic: fetch one Feature + its child PBIs/Bugs + their test cases.
407
400
  Returns the same shape as the old fetch_feature_hierarchy body.
408
- Called by both fetch_feature_hierarchy (single feature) and
409
- fetch_epic_hierarchy (once per child feature of an epic).
401
+ Called by fetch_feature_hierarchy.
410
402
  """
411
403
  org_url = org_url.rstrip("/")
412
404
  headers = get_auth_headers()
@@ -431,10 +423,6 @@ def _fetch_feature_data(org_url: str, project: str, feature_id: int) -> dict:
431
423
  fields.get("Microsoft.VSTS.Common.AcceptanceCriteria", "") or ""
432
424
  ),
433
425
  "state": fields.get("System.State", ""),
434
- "priority": fields.get("Microsoft.VSTS.Common.Priority", ""),
435
- "area_path": fields.get("System.AreaPath", ""),
436
- "iteration_path": fields.get("System.IterationPath", ""),
437
- "tags": fields.get("System.Tags", ""),
438
426
  }
439
427
 
440
428
  # ── Fetch child PBIs / Bugs ──────────────────────────────────────────────
@@ -469,8 +457,6 @@ def _fetch_feature_data(org_url: str, project: str, feature_id: int) -> dict:
469
457
  cf.get("Microsoft.VSTS.Common.AcceptanceCriteria", "") or ""
470
458
  ),
471
459
  "state": cf.get("System.State", ""),
472
- "priority": cf.get("Microsoft.VSTS.Common.Priority", ""),
473
- "story_points": cf.get("Microsoft.VSTS.Scheduling.StoryPoints", ""),
474
460
  })
475
461
 
476
462
  child_tc_map[child_id] = [
@@ -505,109 +491,7 @@ def fetch_feature_hierarchy(org_url: str, project: str, feature_id: int) -> dict
505
491
  reference example.
506
492
  """
507
493
  result = _fetch_feature_data(org_url, project, feature_id)
508
- return {
509
- **result,
510
- "scope": "feature",
511
- "scenario_count_rule": (
512
- f"{_SCENARIO_MIN_FEATURE}–{_SCENARIO_MAX_FEATURE} scenarios for a Feature"
513
- ),
514
- "best_practice_example": _GHERKIN_EXAMPLE,
515
- }
516
-
517
-
518
- # ─────────────────────────────────────────────────────────────────────────────
519
- # PUBLIC TOOL — Epic entry point (new)
520
- # ─────────────────────────────────────────────────────────────────────────────
521
-
522
- def fetch_epic_hierarchy(org_url: str, project: str, epic_id: int) -> dict:
523
- """
524
- Fetch an Epic work item with ALL child Features, their child PBIs/Bugs,
525
- and every linked test case (including full test steps). Use this as Step 1 when the user provides an Epic ID.
526
-
527
- Hierarchy resolved:
528
- Epic
529
- └── Feature (one _fetch_feature_data call per feature)
530
- └── PBI / Bug
531
- └── Test Cases
532
- """
533
- org_url = org_url.rstrip("/")
534
- headers = get_auth_headers()
535
-
536
- # ── Fetch the Epic ───────────────────────────────────────────────────────
537
- resp = requests.get(
538
- f"{org_url}/{project}/_apis/wit/workitems/{epic_id}",
539
- headers=headers,
540
- params={"$expand": "relations", "api-version": _API},
541
- timeout=30,
542
- )
543
- resp.raise_for_status()
544
- data = resp.json()
545
- fields = data.get("fields", {})
546
-
547
- epic = {
548
- "id": data["id"],
549
- "type": fields.get("System.WorkItemType", ""),
550
- "title": fields.get("System.Title", ""),
551
- "description": _strip_html(fields.get("System.Description", "") or ""),
552
- "acceptance_criteria": _strip_html(
553
- fields.get("Microsoft.VSTS.Common.AcceptanceCriteria", "") or ""
554
- ),
555
- "state": fields.get("System.State", ""),
556
- "priority": fields.get("Microsoft.VSTS.Common.Priority", ""),
557
- "area_path": fields.get("System.AreaPath", ""),
558
- "iteration_path": fields.get("System.IterationPath", ""),
559
- "tags": fields.get("System.Tags", ""),
560
- }
561
-
562
- # ── Walk child Features and delegate to the shared helper ────────────────
563
- features: list[dict] = []
564
- total_children = 0
565
- total_test_cases = 0
566
-
567
- for rel in data.get("relations", []):
568
- if rel.get("rel") != "System.LinkTypes.Hierarchy-Forward":
569
- continue
570
- child_url = rel["url"]
571
- child_id = int(child_url.split("/")[-1])
572
-
573
- # Peek at work item type before doing the full fetch
574
- try:
575
- peek = requests.get(
576
- f"{child_url}?api-version={_API}",
577
- headers=get_auth_headers(),
578
- timeout=30,
579
- )
580
- if not peek.ok:
581
- continue
582
- child_type = peek.json().get("fields", {}).get("System.WorkItemType", "")
583
- if child_type != "Feature":
584
- continue # skip non-Feature children (e.g. orphan PBIs directly under Epic)
585
- except Exception:
586
- continue
587
-
588
- # Full deep fetch via shared helper
589
- try:
590
- feature_data = _fetch_feature_data(org_url, project, child_id)
591
- except Exception:
592
- continue
593
-
594
- features.append(feature_data)
595
- total_children += feature_data["child_work_items_count"]
596
- total_test_cases += feature_data["existing_test_cases_count"]
597
-
598
- return {
599
- "epic": epic,
600
- "features": features,
601
- "features_count": len(features),
602
- "total_child_work_items_count": total_children,
603
- "total_existing_test_cases_count": total_test_cases,
604
- "scope": "epic",
605
- "scenario_count_rule": (
606
- f"{_SCENARIO_MIN_FEATURE}–{_SCENARIO_MAX_FEATURE} scenarios per Feature "
607
- f"when generating at Epic scope"
608
- ),
609
- "best_practice_example": _GHERKIN_EXAMPLE,
610
- }
494
+ return {**result, "scope": "feature"}
611
495
 
612
496
 
613
497
  # ---------------------------------------------------------------------------
@@ -685,7 +569,7 @@ def attach_feature_file(
685
569
  "feature_id": feature_id,
686
570
  "file_name": file_name,
687
571
  "attachment_url": attachment_url,
688
- "validation": validation,
572
+ "warnings": validation["warnings"],
689
573
  }
690
574
 
691
575
 
@@ -706,14 +590,9 @@ def _fetch_test_cases(org_url: str, project: str, tc_ids: List[int]) -> list:
706
590
  if not resp.ok:
707
591
  continue
708
592
  f = resp.json().get("fields", {})
709
- steps = _parse_steps_xml(f.get("Microsoft.VSTS.TCM.Steps", ""))
710
593
  results.append({
711
594
  "id": tc_id,
712
595
  "title": f.get("System.Title", ""),
713
- "state": f.get("System.State", ""),
714
- "priority": f.get("Microsoft.VSTS.Common.Priority", ""),
715
- "steps": steps,
716
- "steps_count": len(steps),
717
596
  })
718
597
  except Exception:
719
598
  continue
@@ -3,7 +3,6 @@ Agent 5: QA Jira Manager — MCP Server
3
3
 
4
4
  Tools:
5
5
  fetch_jira_issue — Fetch a Jira issue with acceptance criteria and coverage hints
6
- fetch_jira_epic_hierarchy — Fetch an Epic with all child issues
7
6
  create_and_link_test_cases — Create test case issues in Jira and link them to the source issue
8
7
  get_linked_test_cases — Return test cases already linked to a Jira issue
9
8
 
@@ -32,7 +31,6 @@ from mcp import types
32
31
  from stlc_agents.shared_jira.auth import get_auth_headers, get_cloud_id, get_signed_in_user
33
32
  from stlc_agents.agent_jira_manager.tools.jira_workitem import (
34
33
  fetch_work_item as _fetch_work_item,
35
- fetch_epic_hierarchy as _fetch_epic_hierarchy,
36
34
  create_test_case as _create_test_case,
37
35
  link_test_cases_to_issue as _link_test_cases,
38
36
  get_linked_test_cases as _get_linked_test_cases,
@@ -182,29 +180,6 @@ async def list_tools() -> list[types.Tool]:
182
180
  "required": ["issue_key"],
183
181
  },
184
182
  ),
185
- types.Tool(
186
- name="fetch_jira_epic_hierarchy",
187
- description=(
188
- "Fetch a Jira Epic with all child issues (Stories, Tasks, Bugs, Sub-tasks). "
189
- "Use this when the caller passes an Epic key. "
190
- "Returns the Epic fields and a children list with key, type, summary, "
191
- "status, priority, and assignee for each child."
192
- ),
193
- inputSchema={
194
- "type": "object",
195
- "properties": {
196
- "epic_key": {
197
- "type": "string",
198
- "description": "Epic issue key, e.g. 'PROJ-10'",
199
- },
200
- "cloud_id": {
201
- "type": "string",
202
- "description": "Atlassian cloud ID. Leave blank to use JIRA_CLOUD_ID env var.",
203
- },
204
- },
205
- "required": ["epic_key"],
206
- },
207
- ),
208
183
  types.Tool(
209
184
  name="create_and_link_test_cases",
210
185
  description=(
@@ -320,13 +295,6 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
320
295
  )
321
296
  result["_validation"] = _validate_fetch_response(result)
322
297
 
323
- elif name == "fetch_jira_epic_hierarchy":
324
- result = await asyncio.to_thread(
325
- _fetch_epic_hierarchy,
326
- cloud_id,
327
- arguments["epic_key"],
328
- )
329
-
330
298
  elif name == "create_and_link_test_cases":
331
299
  issue_key = arguments["issue_key"]
332
300
  test_cases = arguments["test_cases"]
@@ -5,7 +5,6 @@ All functions use get_auth_headers() from shared_jira/auth.py — no credentials
5
5
 
6
6
  Public API:
7
7
  fetch_work_item(cloud_id, issue_key) -> dict
8
- fetch_epic_hierarchy(cloud_id, epic_key) -> dict
9
8
  create_test_case(cloud_id, project_key, title, steps, priority, labels, epic_link) -> dict
10
9
  link_test_cases_to_issue(cloud_id, issue_key, tc_keys) -> dict
11
10
  get_linked_test_cases(cloud_id, issue_key) -> dict
@@ -64,16 +63,15 @@ def fetch_work_item(cloud_id: str, issue_key: str) -> dict:
64
63
 
65
64
  issue_type = (fields.get("issuetype") or {}).get("name", "")
66
65
 
67
- # Epic guard
66
+ # Epic guard — Epics are not supported for test case operations
68
67
  if issue_type == "Epic":
69
68
  return {
70
69
  "error": "epic_use_hierarchy",
71
70
  "issue_type": issue_type,
72
71
  "issue_key": issue_key,
73
72
  "message": (
74
- f"{issue_key} is an Epic. Use fetch_epic_hierarchy to walk its "
75
- "child Stories/Tasks/Bugs, then call create_and_link_test_cases "
76
- "on each individual child issue."
73
+ f"{issue_key} is an Epic. Epic-scoped operations are not supported. "
74
+ "Please provide a child Story, Task, or Bug key instead."
77
75
  ),
78
76
  }
79
77
 
@@ -91,13 +89,8 @@ def fetch_work_item(cloud_id: str, issue_key: str) -> dict:
91
89
  "acceptance_criteria": acceptance_criteria,
92
90
  "status": (fields.get("status") or {}).get("name", ""),
93
91
  "priority": (fields.get("priority") or {}).get("name", ""),
94
- "assignee": (fields.get("assignee") or {}).get("displayName", ""),
95
92
  "labels": fields.get("labels", []),
96
- "story_points": fields.get("customfield_10016") or fields.get("customfield_10028"),
97
93
  "project_key": (fields.get("project") or {}).get("key", ""),
98
- "project_name": (fields.get("project") or {}).get("name", ""),
99
- "fix_versions": [v.get("name", "") for v in (fields.get("fixVersions") or [])],
100
- "components": [c.get("name", "") for c in (fields.get("components") or [])],
101
94
  "epic_key": fields.get("customfield_10014", ""), # Epic Link
102
95
  }
103
96
 
@@ -129,74 +122,6 @@ def fetch_work_item(cloud_id: str, issue_key: str) -> dict:
129
122
  }
130
123
 
131
124
 
132
- # ---------------------------------------------------------------------------
133
- # fetch_epic_hierarchy
134
- # ---------------------------------------------------------------------------
135
-
136
- def fetch_epic_hierarchy(cloud_id: str, epic_key: str) -> dict:
137
- """Fetch an Epic with all child issues (Stories, Tasks, Bugs, Sub-tasks)."""
138
- headers = get_auth_headers()
139
-
140
- # Fetch the Epic itself
141
- epic_resp = requests.get(
142
- f"{_base(cloud_id)}/issue/{epic_key}",
143
- headers=headers,
144
- params={"fields": "summary,issuetype,status,priority,project"},
145
- timeout=30,
146
- )
147
- epic_resp.raise_for_status()
148
- epic_data = epic_resp.json()
149
- epic_fields = epic_data.get("fields", {})
150
-
151
- if (epic_fields.get("issuetype") or {}).get("name", "") != "Epic":
152
- return {
153
- "error": "not_an_epic",
154
- "issue_key": epic_key,
155
- "message": f"{epic_key} is not an Epic. Use fetch_work_item instead.",
156
- }
157
-
158
- # JQL: all issues whose parent is this epic (next-gen / team-managed projects)
159
- # "Epic Link" field is deprecated/removed in newer Jira Cloud instances
160
- # GET /rest/api/3/search is deprecated (410 Gone) — use POST /rest/api/3/search/jql
161
- jql = f'parent = {epic_key}'
162
- search_resp = requests.post(
163
- f"{_base(cloud_id)}/search/jql",
164
- headers=get_auth_headers(),
165
- json={
166
- "jql": jql,
167
- "fields": ["summary", "issuetype", "status", "priority", "assignee", "issuelinks"],
168
- "maxResults": 200,
169
- },
170
- timeout=30,
171
- )
172
- search_resp.raise_for_status()
173
- issues = search_resp.json().get("issues", [])
174
-
175
- children = [
176
- {
177
- "key": i["key"],
178
- "id": i["id"],
179
- "type": (i.get("fields", {}).get("issuetype") or {}).get("name", ""),
180
- "summary": i.get("fields", {}).get("summary", ""),
181
- "status": (i.get("fields", {}).get("status") or {}).get("name", ""),
182
- "priority": (i.get("fields", {}).get("priority") or {}).get("name", ""),
183
- "assignee": (i.get("fields", {}).get("assignee") or {}).get("displayName", ""),
184
- }
185
- for i in issues
186
- ]
187
-
188
- return {
189
- "epic": {
190
- "key": epic_data["key"],
191
- "id": epic_data["id"],
192
- "summary": epic_fields.get("summary", ""),
193
- "project_key": (epic_fields.get("project") or {}).get("key", ""),
194
- },
195
- "children": children,
196
- "child_count": len(children),
197
- }
198
-
199
-
200
125
  # ---------------------------------------------------------------------------
201
126
  # create_test_case
202
127
  # ---------------------------------------------------------------------------
@@ -23,7 +23,7 @@ CI/CD Telemetry:
23
23
  before they are permanently committed to the repository.
24
24
  """
25
25
  from __future__ import annotations
26
- import asyncio, json, re, sys, os
26
+ import asyncio, json, re, sys, os, uuid
27
27
  from pathlib import Path
28
28
  from dotenv import load_dotenv
29
29
  from mcp.server import Server
@@ -35,6 +35,13 @@ from stlc_agents.agent_playwright_generator.tools.ado_attach import attach_file_
35
35
  load_dotenv()
36
36
  app = Server("qa-playwright-generator")
37
37
 
38
+ # ---------------------------------------------------------------------------
39
+ # File content cache — keeps large TS blobs out of conversation history.
40
+ # generate_playwright_code and scaffold_locator_repository store files here
41
+ # keyed by a short cache_key; get_generated_files retrieves them on demand.
42
+ # ---------------------------------------------------------------------------
43
+ _file_cache: dict[str, dict] = {}
44
+
38
45
 
39
46
  # ---------------------------------------------------------------------------
40
47
  # Pre-output validation helpers
@@ -227,9 +234,8 @@ async def list_tools() -> list[types.Tool]:
227
234
  description=(
228
235
  "Attach generated TypeScript files to an ADO work item (Feature, PBI, or Bug). "
229
236
  "NEVER pass an Epic ID — attachments on Epics are invisible in normal ADO workflow "
230
- "views. If you have an Epic ID, use qa-gherkin-generator:fetch_epic_hierarchy "
231
- "to walk its child Features and PBIs/Bugs, then call this tool once per Feature "
232
- "or PBI/Bug work item ID."
237
+ "views. If you have an Epic ID, look up the child Features and PBIs/Bugs in ADO "
238
+ "and call this tool once per Feature or PBI/Bug work item ID."
233
239
  ),
234
240
  inputSchema={
235
241
  "type": "object",
@@ -290,7 +296,7 @@ async def list_tools() -> list[types.Tool]:
290
296
  "type": "string",
291
297
  "description": (
292
298
  "Content of the generated *.steps.ts file to validate. "
293
- "Pass the value from generate_playwright_code['files']['<feature>.steps.ts']."
299
+ "Retrieve it via get_generated_files(cache_key)['files']['<feature>.steps.ts']."
294
300
  ),
295
301
  },
296
302
  "gherkin_content": {
@@ -317,6 +323,26 @@ async def list_tools() -> list[types.Tool]:
317
323
  "required": ["steps_ts_content"],
318
324
  },
319
325
  ),
326
+ types.Tool(
327
+ name="get_generated_files",
328
+ description=(
329
+ "Retrieve the full TypeScript file contents that were generated by a previous "
330
+ "call to generate_playwright_code or scaffold_locator_repository. "
331
+ "Use the cache_key returned by those tools. Call this only when you are about "
332
+ "to attach files to ADO (attach_code_to_work_item) or write them to Helix-QA "
333
+ "(qa-helix-writer:write_helix_files). Do not call this just to inspect the output."
334
+ ),
335
+ inputSchema={
336
+ "type": "object",
337
+ "properties": {
338
+ "cache_key": {
339
+ "type": "string",
340
+ "description": "The cache_key value returned by generate_playwright_code or scaffold_locator_repository.",
341
+ },
342
+ },
343
+ "required": ["cache_key"],
344
+ },
345
+ ),
320
346
  ]
321
347
 
322
348
 
@@ -336,6 +362,13 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
336
362
  arguments.get("context_map"),
337
363
  arguments.get("helix_project_root"),
338
364
  )
365
+ # Cache files; return manifest so TS content stays out of conversation
366
+ cache_key = uuid.uuid4().hex[:8]
367
+ _file_cache[cache_key] = result.pop("files")
368
+ result["cache_key"] = cache_key
369
+ result["files_manifest"] = list(_file_cache[cache_key].keys())
370
+ result.pop("healing_layers", None)
371
+ result.pop("scaffold_note", None)
339
372
  elif name == "scaffold_locator_repository":
340
373
  result = await asyncio.to_thread(
341
374
  _scaffold_locator_repository,
@@ -347,8 +380,25 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
347
380
  arguments.get("enable_visual_regression", True),
348
381
  arguments.get("ai_healing_provider", "anthropic"),
349
382
  )
350
- # ── Pre-output validation ─────────────────────────────────────
383
+ # ── Pre-output validation (reads files before we cache them) ──
351
384
  result["_validation"] = _validate_scaffold_output(result)
385
+ # Cache files; return manifest so TS content stays out of conversation
386
+ cache_key = uuid.uuid4().hex[:8]
387
+ _file_cache[cache_key] = result.pop("files")
388
+ result["cache_key"] = cache_key
389
+ result["files_manifest"] = list(_file_cache[cache_key].keys())
390
+ result.pop("healing_strategies", None)
391
+ result.pop("healing_layers", None)
392
+ result.pop("env_vars", None)
393
+ elif name == "get_generated_files":
394
+ cache_key = arguments["cache_key"]
395
+ if cache_key not in _file_cache:
396
+ result = {
397
+ "error": f"Cache key '{cache_key}' not found. "
398
+ "Re-run generate_playwright_code or scaffold_locator_repository.",
399
+ }
400
+ else:
401
+ result = {"cache_key": cache_key, "files": _file_cache[cache_key]}
352
402
  elif name == "attach_code_to_work_item":
353
403
  # ── Pre-attach input validation ────────────────────────────────
354
404
  input_validation = _validate_attach_inputs(arguments.get("files", []))
@@ -388,7 +438,7 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
388
438
  f"Work item {wi_id} is an Epic. Playwright TypeScript files "
389
439
  "must NOT be attached directly to an Epic — attachments on Epics "
390
440
  "are effectively invisible in normal ADO workflow views. "
391
- "Use qa-gherkin-generator:fetch_epic_hierarchy to walk the child "
441
+ "Use qa-gherkin-generator to walk the child "
392
442
  "Features and PBIs/Bugs, then call attach_code_to_work_item once "
393
443
  "per Feature or PBI/Bug work item ID."
394
444
  ),