@qa-gentic/stlc-agents 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/bin/postinstall.js +4 -1
- package/package.json +1 -1
- package/skills/AGENT-BEHAVIOR.md +15 -22
- package/skills/deduplication-protocol/SKILL.md +51 -3
- package/skills/generate-gherkin/SKILL.md +15 -30
- package/skills/generate-gherkin/references/step-by-step.md +2 -6
- package/skills/generate-playwright-code/SKILL.md +19 -122
- package/skills/generate-playwright-code/references/file-structures.md +128 -0
- package/skills/generate-test-cases/SKILL.md +2 -2
- package/skills/write-helix-files/SKILL.md +12 -3
- package/src/stlc_agents/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_gherkin_generator/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_gherkin_generator/__pycache__/server.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_gherkin_generator/server.py +14 -62
- package/src/stlc_agents/agent_gherkin_generator/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_gherkin_generator/tools/__pycache__/ado_gherkin.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_gherkin_generator/tools/ado_gherkin.py +5 -126
- package/src/stlc_agents/agent_helix_writer/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_helix_writer/__pycache__/server.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_helix_writer/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_helix_writer/tools/__pycache__/boilerplate.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_helix_writer/tools/__pycache__/helix_write.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_jira_manager/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_jira_manager/__pycache__/server.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_jira_manager/server.py +0 -32
- package/src/stlc_agents/agent_jira_manager/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_jira_manager/tools/__pycache__/jira_workitem.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_jira_manager/tools/jira_workitem.py +3 -78
- package/src/stlc_agents/agent_playwright_generator/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_playwright_generator/__pycache__/server.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_playwright_generator/server.py +57 -7
- package/src/stlc_agents/agent_playwright_generator/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_playwright_generator/tools/__pycache__/ado_attach.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_test_case_manager/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_test_case_manager/__pycache__/server.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_test_case_manager/server.py +4 -4
- package/src/stlc_agents/agent_test_case_manager/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_test_case_manager/tools/__pycache__/ado_workitem.cpython-310.pyc +0 -0
- package/src/stlc_agents/agent_test_case_manager/tools/ado_workitem.py +3 -5
- package/src/stlc_agents/shared/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/stlc_agents/shared/__pycache__/auth.cpython-310.pyc +0 -0
- package/src/stlc_agents/shared_jira/__pycache__/__init__.cpython-310.pyc +0 -0
- 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 /
|
|
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
|
-
# ──
|
|
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
|
|
216
|
-
"
|
|
217
|
-
"
|
|
218
|
-
"For
|
|
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
|
-
# ──
|
|
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
|
|
240
|
-
"
|
|
241
|
-
"For
|
|
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 == "
|
|
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"],
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
-
"
|
|
389
|
+
"warnings": validation["warnings"],
|
|
397
390
|
}
|
|
398
391
|
|
|
399
392
|
|
|
400
393
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
401
|
-
# INTERNAL HELPER —
|
|
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
|
|
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
|
-
"
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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"]
|
|
Binary file
|
|
Binary file
|
|
@@ -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.
|
|
75
|
-
"child
|
|
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
|
# ---------------------------------------------------------------------------
|
|
Binary file
|
|
Binary file
|
|
@@ -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,
|
|
231
|
-
"
|
|
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
|
-
"
|
|
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
|
|
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
|
),
|
|
Binary file
|