synapse-hub 2.7.0 → 3.0.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 (141) hide show
  1. package/.agents/agents/live-explorer.md +2 -0
  2. package/.agents/agents/pom-crafter.md +2 -0
  3. package/.agents/agents/test-runner.md +3 -0
  4. package/.agents/rules/communication-style.md +4 -0
  5. package/.agents/rules/context-engineering.md +1 -0
  6. package/.agents/rules/git-push-protocol.md +17 -0
  7. package/.agents/skills/agentless-qa-repair/examples/three_phase_repair_workflow.md +40 -0
  8. package/.agents/skills/antigravity-test-sentinel/examples/sprint_dod_enforcement.md +47 -0
  9. package/.agents/skills/auth-session-and-cookie-manager/examples/multi_role_auth_matrix.md +70 -0
  10. package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_playwright_pom.ts +52 -0
  11. package/.agents/skills/failure-autopsy-and-healing/examples/closed_loop_healing_walkthrough.md +48 -0
  12. package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
  13. package/.agents/skills/lean-impact-scoreboard/examples/sample_scoreboard.md +1 -1
  14. package/.agents/skills/mipro-prompt-compiler/examples/dspy_assertion_optimizer_case.md +46 -0
  15. package/.agents/skills/persistent-planning-files/SKILL.md +1 -1
  16. package/.agents/skills/qa-aci-interface/examples/surgical_code_window_walkthrough.md +40 -0
  17. package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
  18. package/.agents/skills/review-spec/SKILL.md +1 -1
  19. package/.agents/skills/review-spec/examples/pocock_grilling_decision_rubric.md +40 -0
  20. package/.agents/skills/senior-code-review/SKILL.md +1 -1
  21. package/.agents/skills/skillspector-security-guard/examples/audit_threat_vectors_report.json +35 -0
  22. package/.agents/skills/surgical-coding-principles/SKILL.md +1 -1
  23. package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
  24. package/.agents/skills/tot-exploratory-testing/examples/payment_gateway_tot_tree.json +62 -0
  25. package/.agents/skills/verification-before-completion/SKILL.md +2 -2
  26. package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +2 -2
  27. package/.agents/skills/web-agent-gym-benchmarking/examples/enterprise_tasks_suite.json +38 -0
  28. package/.agents/skills/web-vitals-and-performance-profiler/examples/playwright_cwv_profiler_fixture.ts +61 -0
  29. package/AGENTS.md +6 -4
  30. package/CLAUDE.md +2 -1
  31. package/README.en.md +71 -292
  32. package/README.md +69 -304
  33. package/action.yml +1 -1
  34. package/bin/agent-qa.js +2 -2
  35. package/docs/ARCHITECTURE.en.md +192 -0
  36. package/docs/ARCHITECTURE.md +135 -88
  37. package/docs/CI_CD_INTEGRATION.en.md +96 -0
  38. package/docs/CI_CD_INTEGRATION.md +24 -22
  39. package/docs/CLI_REFERENCE.en.md +174 -0
  40. package/docs/CLI_REFERENCE.md +120 -115
  41. package/docs/CONTRIBUTING.en.md +49 -0
  42. package/docs/CONTRIBUTING.md +26 -68
  43. package/docs/QUALITY_GATES.en.md +115 -0
  44. package/docs/QUALITY_GATES.md +115 -0
  45. package/docs/research/agent_skill_training_analysis.md +1 -1
  46. package/package.json +1 -1
  47. package/scripts/package_dist.py +2 -1
  48. package/skills_manifest.json +6 -6
  49. package/synapse.config.json +1 -1
  50. package/templates/ai_bridges/CLAUDE.md +2 -1
  51. package/templates/ai_bridges/copilot-instructions.md +2 -2
  52. package/tools/__init__.py +18 -1
  53. package/tools/_openapi_parser.py +1 -1
  54. package/tools/agent_canary.py +42 -30
  55. package/tools/agentless_patcher.py +2 -2
  56. package/tools/analyze_project.py +3 -5
  57. package/tools/api_drift_sentinel.py +4 -4
  58. package/tools/api_fuzzer.py +8 -8
  59. package/tools/auto_heal.py +4 -5
  60. package/tools/auto_heal_reflexion.py +10 -2
  61. package/tools/autogen_qa_broker.py +2 -2
  62. package/tools/browser_helpers.js +2 -2
  63. package/tools/browsergym_eval.py +2 -3
  64. package/tools/bug_reporter.py +11 -13
  65. package/tools/bump_version.py +90 -19
  66. package/tools/cache_resolver.py +78 -83
  67. package/tools/check_env.py +9 -1
  68. package/tools/check_version_sync.py +58 -4
  69. package/tools/ci_healing_hook.py +4 -6
  70. package/tools/cli.py +5 -7
  71. package/tools/config_decoupler.py +2 -3
  72. package/tools/contract_validator.py +141 -0
  73. package/tools/core/__init__.py +52 -0
  74. package/tools/count_tokens.py +22 -20
  75. package/tools/dashboard_generator.py +629 -1000
  76. package/tools/db_sandbox.py +87 -62
  77. package/tools/debt_harvester.py +3 -3
  78. package/tools/distill_log.py +3 -3
  79. package/tools/dspy_qa_signatures.py +3 -4
  80. package/tools/export_postman_collection.py +8 -8
  81. package/tools/flaky_detector.py +2 -2
  82. package/tools/fuzz_payloads.py +5 -5
  83. package/tools/generate_allure_report.py +1 -1
  84. package/tools/generate_jmx_plan.py +4 -4
  85. package/tools/gherkin_suite.py +1 -1
  86. package/tools/git_pre_commit_guard.py +2 -2
  87. package/tools/goldfish_review.py +2 -2
  88. package/tools/heal/__init__.py +28 -0
  89. package/tools/heal_pr_automator.py +6 -6
  90. package/tools/heuristic_locator_matcher.py +2 -2
  91. package/tools/infer_linter.py +143 -29
  92. package/tools/interview_story.py +0 -1
  93. package/tools/jtl_analyzer.py +11 -11
  94. package/tools/lint/__init__.py +30 -0
  95. package/tools/lint_ai_docs.py +42 -0
  96. package/tools/lookup_tc.py +1 -1
  97. package/tools/matrix_sharder.py +15 -13
  98. package/tools/mcp/__init__.py +16 -0
  99. package/tools/mcp_excel_server.py +2 -2
  100. package/tools/mcp_server.py +9 -7
  101. package/tools/md_to_xlsx.py +26 -26
  102. package/tools/micro_mutator.py +32 -15
  103. package/tools/network_mock_shield.py +27 -27
  104. package/tools/newman_analyzer.py +2 -3
  105. package/tools/omni_emitter.py +5 -8
  106. package/tools/omni_parser.py +5 -5
  107. package/tools/omni_transpiler.py +5 -5
  108. package/tools/output_formatter.py +19 -0
  109. package/tools/perf/__init__.py +24 -0
  110. package/tools/pinpoint_failure.py +1 -1
  111. package/tools/project_state.py +0 -2
  112. package/tools/qa_event_stream.py +1 -1
  113. package/tools/repo_map.py +8 -8
  114. package/tools/report/__init__.py +32 -0
  115. package/tools/reversible_sandbox.py +3 -4
  116. package/tools/safety_firewall.py +2 -2
  117. package/tools/senior_code_review.py +4 -3
  118. package/tools/setup_project.py +10 -12
  119. package/tools/skill_collision_guard.py +3 -3
  120. package/tools/skill_vault.py +94 -6
  121. package/tools/skillspector.py +10 -8
  122. package/tools/smart_visual_masker.py +4 -6
  123. package/tools/spec_drift_sentinel.py +1 -2
  124. package/tools/swe_qa_aci.py +2 -2
  125. package/tools/synapse_qa_bench.py +3 -3
  126. package/tools/sync_agent_rules.py +18 -3
  127. package/tools/telemetry_correlator.py +3 -2
  128. package/tools/test_impact.py +2 -1
  129. package/tools/test_memory_os.py +15 -5
  130. package/tools/textgrad_mutation_optimizer.py +3 -3
  131. package/tools/tms_sync_bridge.py +5 -5
  132. package/tools/tot_exploratory_tester.py +5 -6
  133. package/tools/traffic_session_converter.py +9 -9
  134. package/tools/universal_ir.py +2 -3
  135. package/tools/update_sentinel.py +6 -9
  136. package/tools/verify_all.py +12 -2
  137. package/tools/visual_diff_canvas.py +4 -4
  138. package/tools/visual_som_helper.py +15 -13
  139. package/tools/watch_mode.py +1 -1
  140. package/tools/wcag_audit.py +22 -1
  141. package/tools/welcome_page.py +5 -6
@@ -11,9 +11,8 @@ import os
11
11
  import sys
12
12
  import json
13
13
  import re
14
- import traceback
15
14
  from pathlib import Path
16
- from typing import Dict, Any, List, Optional, Set
15
+ from typing import Dict, Any, Optional, Set
17
16
 
18
17
  # Enforce UTF-8 stdio encoding
19
18
  if hasattr(sys.stdout, 'reconfigure'):
@@ -47,7 +46,7 @@ import gherkin_suite
47
46
  import api_fuzzer
48
47
 
49
48
  SERVER_NAME = "enterprise-agent-qa"
50
- SERVER_VERSION = "2.7.0"
49
+ SERVER_VERSION = "3.0.1"
51
50
 
52
51
  MCP_TOOLS = [
53
52
  {
@@ -474,13 +473,15 @@ def _handle_pull_alm_story(arguments: Dict[str, Any]) -> str:
474
473
 
475
474
  def _handle_report_alm_defect(arguments: Dict[str, Any]) -> str:
476
475
  bridge = tms_sync_bridge.TMSBridge()
476
+ raw_screenshot = arguments.get("screenshot_path", "")
477
+ safe_screenshot = _validate_safe_path(raw_screenshot) if raw_screenshot else ""
477
478
  ticket = bridge.report_defect(
478
479
  project_key=arguments["project_key"],
479
480
  test_case_id=arguments["test_case_id"],
480
481
  summary=arguments["summary"],
481
482
  error_message=arguments["error_message"],
482
483
  stack_trace=arguments.get("stack_trace", ""),
483
- screenshot_path=arguments.get("screenshot_path", "")
484
+ screenshot_path=safe_screenshot
484
485
  )
485
486
  return json.dumps(ticket, indent=2)
486
487
 
@@ -518,7 +519,8 @@ def _handle_convert_traffic_session(arguments: Dict[str, Any]) -> str:
518
519
 
519
520
  def _handle_resolve_visual_som_coordinates(arguments: Dict[str, Any]) -> str:
520
521
  helper = visual_som_helper.VisualSoMHelper()
521
- img = arguments.get("image_path", "screenshot.png")
522
+ raw_img = arguments.get("image_path", "screenshot.png")
523
+ img = _validate_safe_path(raw_img)
522
524
  lbl = arguments.get("target_label")
523
525
  mid = arguments.get("mark_id")
524
526
  marked_img, marks_map = helper.generate_som_overlay(img, [])
@@ -527,7 +529,7 @@ def _handle_resolve_visual_som_coordinates(arguments: Dict[str, Any]) -> str:
527
529
 
528
530
 
529
531
  def _handle_compile_gherkin_suite(arguments: Dict[str, Any]) -> str:
530
- target = arguments["target"]
532
+ target = _validate_safe_path(arguments["target"])
531
533
  res = gherkin_suite.compile_feature(target) if os.path.isfile(target) else gherkin_suite.run(target)
532
534
  if isinstance(res, int):
533
535
  return json.dumps({"exit_code": res, "detail": "see reports/gherkin_suite_report.json"}, indent=2)
@@ -599,7 +601,7 @@ def handle_tool_call(name: str, arguments: Dict[str, Any]) -> str:
599
601
  return handler(arguments)
600
602
 
601
603
 
602
- def process_message(msg: Dict[str, Any]) -> Dict[str, Any]:
604
+ def process_message(msg: Dict[str, Any]) -> Optional[Dict[str, Any]]:
603
605
  """Processes an incoming JSON-RPC 2.0 message."""
604
606
  method = msg.get("method")
605
607
  msg_id = msg.get("id")
@@ -16,7 +16,7 @@ import os
16
16
  import sys
17
17
  import re
18
18
  import argparse
19
- from typing import List, Dict, Any, Tuple
19
+ from typing import List, Tuple
20
20
 
21
21
  # UTF-8 terminal encoding
22
22
  if hasattr(sys.stdout, 'reconfigure'):
@@ -52,11 +52,11 @@ def parse_markdown_tables(content: str) -> List[Tuple[List[str], List[List[str]]
52
52
  """
53
53
  lines = content.splitlines()
54
54
  tables = []
55
-
55
+
56
56
  current_header = None
57
57
  current_rows = []
58
58
  in_table = False
59
-
59
+
60
60
  for line in lines:
61
61
  stripped = line.strip()
62
62
  if not stripped.startswith('|') or not stripped.endswith('|'):
@@ -66,15 +66,15 @@ def parse_markdown_tables(content: str) -> List[Tuple[List[str], List[List[str]]
66
66
  current_rows = []
67
67
  in_table = False
68
68
  continue
69
-
69
+
70
70
  # Split cells
71
71
  raw_cells = [c.strip() for c in stripped[1:-1].split('|')]
72
-
72
+
73
73
  # Check if separator row (|---|---|)
74
74
  if all(re.match(r'^\s*:?-+:?\s*$', c) for c in raw_cells if c):
75
75
  in_table = True
76
76
  continue
77
-
77
+
78
78
  if not in_table:
79
79
  # First line with pipes is potential header
80
80
  current_header = [clean_cell_content(c) for c in raw_cells]
@@ -88,10 +88,10 @@ def parse_markdown_tables(content: str) -> List[Tuple[List[str], List[List[str]]
88
88
  elif len(cleaned_row) > len(current_header):
89
89
  cleaned_row = cleaned_row[:len(current_header)]
90
90
  current_rows.append(cleaned_row)
91
-
91
+
92
92
  if in_table and current_header and current_rows:
93
93
  tables.append((current_header, current_rows))
94
-
94
+
95
95
  return tables
96
96
 
97
97
 
@@ -108,32 +108,32 @@ def create_styled_excel(tables: List[Tuple[List[str], List[List[str]]]], output_
108
108
  NAVY_BLUE = "1F4E78"
109
109
  WHITE = "FFFFFF"
110
110
  BORDER_COLOR = "D9D9D9"
111
-
111
+
112
112
  # Styles
113
113
  header_fill = PatternFill(start_color=NAVY_BLUE, end_color=NAVY_BLUE, fill_type="solid")
114
114
  header_font = Font(name="Calibri", size=11, bold=True, color=WHITE)
115
115
  header_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
116
-
116
+
117
117
  cell_border = Border(
118
118
  left=Side(style='thin', color=BORDER_COLOR),
119
119
  right=Side(style='thin', color=BORDER_COLOR),
120
120
  top=Side(style='thin', color=BORDER_COLOR),
121
121
  bottom=Side(style='thin', color=BORDER_COLOR)
122
122
  )
123
-
123
+
124
124
  # Status badges
125
125
  fill_high = PatternFill(start_color="FCE4D6", fill_type="solid") # Soft Red
126
126
  font_high = Font(name="Calibri", size=10, bold=True, color="C00000")
127
-
127
+
128
128
  fill_med = PatternFill(start_color="FFF2CC", fill_type="solid") # Soft Yellow
129
129
  font_med = Font(name="Calibri", size=10, bold=True, color="B25900")
130
-
130
+
131
131
  fill_low = PatternFill(start_color="E2EFDA", fill_type="solid") # Soft Green
132
132
  font_low = Font(name="Calibri", size=10, bold=True, color="375623")
133
-
133
+
134
134
  fill_yes = PatternFill(start_color="E2EFDA", fill_type="solid")
135
135
  font_yes = Font(name="Calibri", size=10, color="276A3C", bold=True)
136
-
136
+
137
137
  fill_no = PatternFill(start_color="F2F2F2", fill_type="solid")
138
138
  font_no = Font(name="Calibri", size=10, color="595959")
139
139
 
@@ -191,16 +191,16 @@ def create_styled_excel(tables: List[Tuple[List[str], List[List[str]]]], output_
191
191
  for row_data in all_rows:
192
192
  ws.append(row_data)
193
193
  ws.row_dimensions[current_row_idx].height = None # Auto height for wrapped text
194
-
194
+
195
195
  for col_idx, val in enumerate(row_data, 1):
196
196
  cell = ws.cell(row=current_row_idx, column=col_idx)
197
197
  cell.border = cell_border
198
198
  cell.font = Font(name="Calibri", size=10)
199
-
199
+
200
200
  h_name = col_types.get(col_idx, "")
201
201
  val_str = str(val).strip()
202
202
  val_lower = val_str.lower()
203
-
203
+
204
204
  # Alignments & Styling based on column semantic
205
205
  if any(k in h_name for k in ["steps", "expected", "title", "scenario", "pre-condition"]):
206
206
  cell.alignment = Alignment(horizontal="left", vertical="top", wrap_text=True)
@@ -246,23 +246,23 @@ def create_styled_excel(tables: List[Tuple[List[str], List[List[str]]]], output_
246
246
  for col_idx, header in enumerate(primary_headers, 1):
247
247
  h_lower = header.lower().strip()
248
248
  col_letter = get_column_letter(col_idx)
249
-
249
+
250
250
  # Check presets
251
251
  target_width = None
252
252
  for key, width in width_map.items():
253
253
  if key in h_lower:
254
254
  target_width = width
255
255
  break
256
-
256
+
257
257
  if not target_width:
258
258
  # Dynamic max calculation
259
259
  max_len = len(header)
260
260
  for r in all_rows:
261
261
  if col_idx - 1 < len(r):
262
262
  lines = str(r[col_idx - 1]).split('\n')
263
- for l in lines:
264
- if len(l) > max_len:
265
- max_len = len(l)
263
+ for line_val in lines:
264
+ if len(line_val) > max_len:
265
+ max_len = len(line_val)
266
266
  target_width = min(max(max_len + 3, 12), 45)
267
267
 
268
268
  ws.column_dimensions[col_letter].width = target_width
@@ -278,13 +278,13 @@ def main():
278
278
  parser.add_argument("input", help="Path to input Markdown file (.md)")
279
279
  parser.add_argument("-o", "--output", help="Path to output Excel file (.xlsx). Defaults to same name as input.")
280
280
  parser.add_argument("--sheet-name", default="Test Cases", help="Worksheet title (default: Test Cases)")
281
-
281
+
282
282
  args = parser.parse_args()
283
-
283
+
284
284
  if not os.path.exists(args.input):
285
285
  print(f"❌ Error: Input file not found: {args.input}", file=sys.stderr)
286
286
  sys.exit(1)
287
-
287
+
288
288
  output_path = args.output
289
289
  if not output_path:
290
290
  base, _ = os.path.splitext(args.input)
@@ -22,11 +22,11 @@ import os
22
22
  import re
23
23
  import sys
24
24
  import json
25
+ import ast
25
26
  import shlex
26
27
  import html as html_lib
27
28
  import argparse
28
29
  import subprocess
29
- import tempfile
30
30
  from pathlib import Path
31
31
  from typing import List, Dict, Any, Tuple
32
32
 
@@ -60,11 +60,12 @@ MUTATION_RULES = [
60
60
  (r"\breturn\s+true\b", "return false", "Return Value Inversion (return true -> return false)"),
61
61
  (r"\breturn\s+false\b", "return true", "Return Value Inversion (return false -> return true)"),
62
62
  (r"\breturn\s+0\b", "return -1", "Return Value Shift (return 0 -> return -1)"),
63
+ (r"\s\?\?\s", " || ", "Nullish Coalescing Inversion (?? -> ||)"),
63
64
  ]
64
65
 
65
66
  # Action statement pattern for Statement Deletion Mutants
66
67
  ACTION_STATEMENT_PATTERN = re.compile(
67
- r'\.(click|submit|save|update|delete|send|fill|press|type|execute)\s*\(',
68
+ r'\.(click|submit|save|update|delete|send|fill|press|type|execute|check|uncheck|hover|dragTo|selectOption|clear|focus|setInputFiles)\s*\(',
68
69
  re.IGNORECASE
69
70
  )
70
71
 
@@ -75,7 +76,12 @@ ASSERTION_PATTERN = re.compile(
75
76
 
76
77
  # Tautological / fake assertions that do not test application logic
77
78
  TAUTOLOGY_PATTERN = re.compile(
78
- r'(?:assert\s+(?:true|1\s*==\s*1)\b|assert\s+([a-zA-Z_]\w*)\s*==\s*\1\b|assertEquals\s*\(\s*([a-zA-Z_]\w*)\s*,\s*\2\s*\)|expect\s*\(\s*([a-zA-Z_]\w*)\s*\)\.toBe\s*\(\s*\3\s*\)|assertTrue\s*\(\s*true\s*\))',
79
+ r'(?:assert\s+(?:true|1\s*==\s*1)\b'
80
+ r'|\bassert\s+([a-zA-Z0-9_\.]+(?:\([^)]*\))?)\s*==\s*\1\b'
81
+ r'|\bassert\s+(?:len\s*\([^)]*\)|[a-zA-Z0-9_\.]+\.(?:length|size\(\)))\s*(?:>=\s*0|>\s*-(?:1|0))\b'
82
+ r'|assertEquals\s*\(\s*([a-zA-Z_]\w*)\s*,\s*\1\s*\)'
83
+ r'|expect\s*\(\s*([a-zA-Z_]\w*)\s*\)\.toBe\s*\(\s*\1\s*\)'
84
+ r'|assertTrue\s*\(\s*true\s*\))',
79
85
  re.IGNORECASE
80
86
  )
81
87
 
@@ -260,9 +266,9 @@ def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tup
260
266
  """
261
267
  if not mutants:
262
268
  has_asserts = any(
263
- ASSERTION_PATTERN.search(l) and not TAUTOLOGY_PATTERN.search(l)
264
- for l in code.splitlines()
265
- if not l.strip().startswith(("//", "#", "/*"))
269
+ ASSERTION_PATTERN.search(line) and not TAUTOLOGY_PATTERN.search(line)
270
+ for line in code.splitlines()
271
+ if not line.strip().startswith(("//", "#", "/*"))
266
272
  )
267
273
  return mutants, (100.0 if has_asserts else 0.0)
268
274
 
@@ -271,8 +277,8 @@ def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tup
271
277
 
272
278
  # Identify lines containing assertions
273
279
  assertion_entries = []
274
- for idx, l in enumerate(lines, start=1):
275
- s = l.strip()
280
+ for idx, line in enumerate(lines, start=1):
281
+ s = line.strip()
276
282
  if s.startswith(("//", "#", "/*")):
277
283
  continue
278
284
  if ASSERTION_PATTERN.search(s) and not TAUTOLOGY_PATTERN.search(s):
@@ -302,6 +308,10 @@ def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tup
302
308
 
303
309
  # Case 1: Mutation directly in an assertion statement
304
310
  if ASSERTION_PATTERN.search(snippet):
311
+ if TAUTOLOGY_PATTERN.search(snippet):
312
+ m["status"] = "SURVIVED"
313
+ m["reason"] = "Mutant in tautological/dummy assertion - cannot kill mutant without genuine assertion logic"
314
+ continue
305
315
  m["status"] = "KILLED"
306
316
  m["reason"] = "Direct assertion mutation caught by verification condition"
307
317
  killed_count += 1
@@ -325,6 +335,13 @@ def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tup
325
335
  tgt = asgn.group(1)
326
336
  if any(re.search(rf'\b{re.escape(t)}\b', l_code) for t in dataflow_tokens):
327
337
  dataflow_tokens.add(tgt)
338
+ destruct = re.match(r'^\s*(?:(?:const|let|var|val)\s+)?[\{\(]([^\}\)]+)[\}\)]\s*=', l_code)
339
+ if destruct:
340
+ vars_in_destruct = [v.strip().split(':')[0].strip() for v in destruct.group(1).split(',') if v.strip()]
341
+ if any(re.search(rf'\b{re.escape(t)}\b', l_code) for t in dataflow_tokens):
342
+ for v in vars_in_destruct:
343
+ if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', v):
344
+ dataflow_tokens.add(v)
328
345
 
329
346
  # Check if any assertion references the variables mutated in this logic branch or downstream dataflow
330
347
  has_variable_link = any(any(re.search(rf'\b{re.escape(tok)}\b', a_text) for tok in dataflow_tokens) for _, a_text in downstream)
@@ -489,9 +506,9 @@ def evaluate_target(target_path: str, min_score: float = 80.0, test_cmd: str = "
489
506
  valid_exts = {".groovy", ".py", ".ts", ".js"}
490
507
  file_scores = {}
491
508
  for root, _, files in os.walk(target):
492
- for f in files:
493
- if any(f.endswith(ext) for ext in valid_exts):
494
- file_full = os.path.join(root, f)
509
+ for filename in files:
510
+ if any(filename.endswith(ext) for ext in valid_exts):
511
+ file_full = os.path.join(root, filename)
495
512
  try:
496
513
  with open(file_full, "r", encoding="utf-8", errors="ignore") as fp:
497
514
  code = fp.read()
@@ -634,17 +651,17 @@ def main():
634
651
  print(format_junit(report))
635
652
  else:
636
653
  mode_tag = "LIVE DYNAMIC RUNNER" if report.get("mode") == "DYNAMIC" else "STATIC HEURISTIC"
637
- mode_desc = f"LIVE DYNAMIC RUNNER (cmd: '{args.test_cmd}')" if report.get("mode") == "DYNAMIC" else "STATIC HEURISTIC (fast 0.1s AST; pass --test-cmd for dynamic run)"
638
- print(f"\n======================================================================")
654
+ mode_desc = f"LIVE DYNAMIC RUNNER (cmd: '{args.test_cmd}')" if report.get("mode") == "DYNAMIC" else "STATIC HEURISTIC (fast heuristic assertion sensitivity scan; pass --test-cmd for dynamic run)"
655
+ print("\n======================================================================")
639
656
  print(f" ASSERTION SENSITIVITY & SYNTACTIC MUTATION SENTINEL [{mode_tag}] ")
640
- print(f"======================================================================")
657
+ print("======================================================================")
641
658
  print(f" Target Scope : {report['target']}")
642
659
  print(f" Evaluation Mode : {mode_desc}")
643
660
  print(f" Mutants Injected : {report['total_mutants']}")
644
661
  print(f" Mutants Caught/Killed : {report['killed']}")
645
662
  print(f" Mutants Survived : {report['survived']}")
646
663
  print(f" Assertion Sensitivity : {report['mutation_score']}% (Required: >={report['min_score']}%)")
647
- print(f"======================================================================\n")
664
+ print("======================================================================\n")
648
665
 
649
666
  for m in report['mutants'][:6]:
650
667
  status_tag = "[KILLED] " if m["status"] == "KILLED" else "[SURVIVED]"
@@ -88,16 +88,16 @@ class NetworkMockShield:
88
88
  code = [
89
89
  f"# Mock API Interception for '{url_pattern}'",
90
90
  "import time, json",
91
- f"def handle_route(route):",
91
+ "def handle_route(route):",
92
92
  ]
93
93
  if delay_ms > 0:
94
94
  code.append(f" time.sleep({delay_ms / 1000.0})")
95
95
  code.extend([
96
- f" route.fulfill(",
96
+ " route.fulfill(",
97
97
  f" status={status},",
98
- f" content_type='application/json',",
98
+ " content_type='application/json',",
99
99
  f" body=json.dumps({body_json})",
100
- f" )",
100
+ " )",
101
101
  f"page.route('{url_pattern}', handle_route)"
102
102
  ])
103
103
  return "\n".join(code)
@@ -119,52 +119,52 @@ class NetworkMockShield:
119
119
  if mode == "record":
120
120
  if lang == "python":
121
121
  return "\n".join([
122
- f"# --- ZERO-LATENCY HAR RECORDING HARNESS (Playwright Python) ---",
122
+ "# --- ZERO-LATENCY HAR RECORDING HARNESS (Playwright Python) ---",
123
123
  f"# Dumps live network traffic to '{har_path}'",
124
- f"context = browser.new_context(",
124
+ "context = browser.new_context(",
125
125
  f" record_har_path='{har_path}',",
126
- f" record_har_mode='minimal',",
126
+ " record_har_mode='minimal',",
127
127
  f" record_har_url_filter='{url_glob}'",
128
- f")",
129
- f"page = context.new_page()",
128
+ ")",
129
+ "page = context.new_page()",
130
130
  f"# Note: Run 'python tools/network_mock_shield.py --har-sanitize {har_path}' before committing to git!"
131
131
  ])
132
132
  else:
133
133
  return "\n".join([
134
- f"// --- ZERO-LATENCY HAR RECORDING HARNESS (Playwright TS) ---",
134
+ "// --- ZERO-LATENCY HAR RECORDING HARNESS (Playwright TS) ---",
135
135
  f"// Dumps live network traffic to '{har_path}'",
136
- f"const context = await browser.newContext({{",
137
- f" recordHar: {{",
136
+ "const context = await browser.newContext({",
137
+ " recordHar: {",
138
138
  f" path: '{har_path}',",
139
- f" mode: 'minimal',",
139
+ " mode: 'minimal',",
140
140
  f" urlFilter: '{url_glob}'",
141
- f" }}",
142
- f"}});",
143
- f"const page = await context.newPage();",
141
+ " }",
142
+ "});",
143
+ "const page = await context.newPage();",
144
144
  f"// Note: Run 'python tools/network_mock_shield.py --har-sanitize {har_path}' before committing to git!"
145
145
  ])
146
146
  else:
147
147
  # Replay Mode
148
148
  if lang == "python":
149
149
  return "\n".join([
150
- f"# --- ZERO-LATENCY HAR REPLAY HARNESS (Playwright Python) ---",
151
- f"# Replays 100% network responses from local HAR with 0ms delay (Immune to backend downtime)",
152
- f"page.route_from_har(",
150
+ "# --- ZERO-LATENCY HAR REPLAY HARNESS (Playwright Python) ---",
151
+ "# Replays 100% network responses from local HAR with 0ms delay (Immune to backend downtime)",
152
+ "page.route_from_har(",
153
153
  f" har_id='{har_path}',",
154
154
  f" url='{url_glob}',",
155
- f" not_found='fallback',",
156
- f" update=False",
157
- f")"
155
+ " not_found='fallback',",
156
+ " update=False",
157
+ ")"
158
158
  ])
159
159
  else:
160
160
  return "\n".join([
161
- f"// --- ZERO-LATENCY HAR REPLAY HARNESS (Playwright TS) ---",
162
- f"// Replays 100% network responses from local HAR with 0ms delay (Immune to backend downtime)",
161
+ "// --- ZERO-LATENCY HAR REPLAY HARNESS (Playwright TS) ---",
162
+ "// Replays 100% network responses from local HAR with 0ms delay (Immune to backend downtime)",
163
163
  f"await page.routeFromHAR('{har_path}', {{",
164
164
  f" url: '{url_glob}',",
165
- f" notFound: 'fallback',",
166
- f" update: false",
167
- f"}});"
165
+ " notFound: 'fallback',",
166
+ " update: false",
167
+ "});"
168
168
  ])
169
169
 
170
170
  @classmethod
@@ -54,7 +54,6 @@ def parse_newman_json(report_path: str) -> Dict[str, Any]:
54
54
 
55
55
  run = data.get("run", {})
56
56
  stats = run.get("stats", {})
57
- timings = run.get("timings", {})
58
57
  executions = run.get("executions", [])
59
58
  failures = run.get("failures", [])
60
59
 
@@ -110,7 +109,7 @@ def parse_newman_json(report_path: str) -> Dict[str, Any]:
110
109
  err = f.get("error", {})
111
110
  source = f.get("source", {})
112
111
  cursor = f.get("cursor", {})
113
-
112
+
114
113
  req_name = source.get("name", cursor.get("ref", "Request"))
115
114
  assert_name = err.get("test", err.get("name", "Assertion Failure"))
116
115
  message = err.get("message", "Unknown assertion error")
@@ -351,7 +350,7 @@ def main():
351
350
  print("-" * 90)
352
351
 
353
352
  # Print SLA Verdict
354
- print(f"🎯 [QUALITY GATE SLA VERDICT]")
353
+ print("🎯 [QUALITY GATE SLA VERDICT]")
355
354
  if passed:
356
355
  print("✅ ALL API FUNCTIONAL & SLA GATES PASSED! Safe to merge/deploy.")
357
356
  else:
@@ -12,18 +12,15 @@ Supports:
12
12
  5. Cypress JavaScript (cypress-js)
13
13
  """
14
14
 
15
- import os
16
- from typing import Dict, List, Optional
15
+ from typing import Dict
17
16
 
18
17
  try:
19
18
  from tools.universal_ir import (
20
- ProjectIR, PageClassIR, TestCaseIR, MethodIR, StepIR, LocatorIR,
21
- ActionType, LocatorStrategy, AssertionType
19
+ ProjectIR, ActionType, LocatorStrategy
22
20
  )
23
21
  except ImportError:
24
22
  from universal_ir import (
25
- ProjectIR, PageClassIR, TestCaseIR, MethodIR, StepIR, LocatorIR,
26
- ActionType, LocatorStrategy, AssertionType
23
+ ProjectIR, ActionType, LocatorStrategy
27
24
  )
28
25
 
29
26
 
@@ -243,7 +240,7 @@ export async function safeInteract(
243
240
  for tc in project.test_cases:
244
241
  t_code.extend([
245
242
  f"test('{escape_js(tc.id)}: {escape_js(tc.title)}', async ({{ page }}) => {{",
246
- f" const uniqueCode = 'AUTO_' + Date.now();",
243
+ " const uniqueCode = 'AUTO_' + Date.now();",
247
244
  ])
248
245
 
249
246
  # Emit Hermetic Pre-flight Fixtures if present
@@ -445,7 +442,7 @@ export async function safeInteract(
445
442
  "import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI",
446
443
  "import pages.*",
447
444
  "",
448
- f"String uniqueCode = BasePage.uniqueCode('AUTO_')",
445
+ "String uniqueCode = BasePage.uniqueCode('AUTO_')",
449
446
  "",
450
447
  ]
451
448
  for s in tc.steps:
@@ -10,7 +10,6 @@ the Universal Intermediate Representation (universal_ir.ProjectIR).
10
10
 
11
11
  import os
12
12
  import re
13
- import sys
14
13
  from typing import Dict, List, Optional, Tuple
15
14
 
16
15
  try:
@@ -38,7 +37,7 @@ class OmniParser:
38
37
  @staticmethod
39
38
  def detect_language_and_framework(file_path: str, content: str) -> Tuple[str, str]:
40
39
  ext = os.path.splitext(file_path)[1].lower()
41
-
40
+
42
41
  # Groovy / Katalon
43
42
  if ext == ".groovy":
44
43
  return "groovy", "katalon"
@@ -369,8 +368,8 @@ class OmniParser:
369
368
  # 0. Detect Hardcoded Sleep Antipatterns (Thread.sleep, WebUI.delay, cy.wait, time.sleep)
370
369
  sleep_m = re.search(r"(?:Thread\.sleep|WebUI\.delay|time\.sleep|cy\.wait)\s*\(\s*(\d+)\s*\)", line_str, re.IGNORECASE)
371
370
  if sleep_m:
372
- val = int(sleep_m.group(1))
373
- ms = val * 1000 if val <= 60 else val # WebUI.delay(3) vs Thread.sleep(3000)
371
+ sleep_val = int(sleep_m.group(1))
372
+ ms = sleep_val * 1000 if sleep_val <= 60 else sleep_val # WebUI.delay(3) vs Thread.sleep(3000)
374
373
  steps.append(StepIR(
375
374
  action=ActionType.WAIT,
376
375
  wait_strategy=WaitStrategy.STATIC_SLEEP,
@@ -389,7 +388,8 @@ class OmniParser:
389
388
  if cy_act == "click":
390
389
  steps.append(StepIR(action=ActionType.CLICK, target=sel, description=f"Cypress click on {sel}"))
391
390
  elif cy_act in ["type", "clear"]:
392
- type_val = extract_quoted_string(re.search(r"(?:\"([^\"]*)\"|'([^']*)')", cy_args).groups()) if re.search(r"(?:\"([^\"]*)\"|'([^']*)')", cy_args) else ""
391
+ m_type = re.search(r"(?:\"([^\"]*)\"|'([^']*)')", cy_args)
392
+ type_val = extract_quoted_string((m_type.group(1), m_type.group(2))) if m_type else ""
393
393
  steps.append(StepIR(action=ActionType.FILL, target=sel, value=type_val, description=f"Cypress type on {sel}"))
394
394
  elif cy_act == "should":
395
395
  steps.append(StepIR(action=ActionType.ASSERT, target=sel, assertion_type=AssertionType.VISIBLE, description=line_str))
@@ -12,15 +12,15 @@ import os
12
12
  import sys
13
13
  import json
14
14
  import argparse
15
- from typing import Dict, List, Any, Optional
15
+ from typing import Dict, Any, Optional
16
16
 
17
17
  # Dynamic imports
18
18
  try:
19
- from tools.universal_ir import ProjectIR, resolve_hermetic_fixtures, analyze_data_dependencies
19
+ from tools.universal_ir import resolve_hermetic_fixtures
20
20
  from tools.omni_parser import OmniParser
21
21
  from tools.omni_emitter import OmniEmitter
22
22
  except ImportError:
23
- from universal_ir import ProjectIR, resolve_hermetic_fixtures, analyze_data_dependencies
23
+ from universal_ir import resolve_hermetic_fixtures
24
24
  from omni_parser import OmniParser
25
25
  from omni_emitter import OmniEmitter
26
26
 
@@ -65,7 +65,7 @@ def scan_repository(repo_path: str) -> Dict[str, Any]:
65
65
  if not os.path.exists(repo_path):
66
66
  raise FileNotFoundError(f"Path does not exist: {repo_path}")
67
67
 
68
- inventory = {
68
+ inventory: Dict[str, Any] = {
69
69
  "repository": os.path.abspath(repo_path),
70
70
  "total_files_scanned": 0,
71
71
  "languages": {},
@@ -88,7 +88,7 @@ def scan_repository(repo_path: str) -> Dict[str, Any]:
88
88
  try:
89
89
  p_ir = OmniParser.parse_file(file_full)
90
90
  inventory["total_files_scanned"] += 1
91
-
91
+
92
92
  # Language counts
93
93
  inventory["languages"][p_ir.source_language] = inventory["languages"].get(p_ir.source_language, 0) + 1
94
94
  inventory["frameworks"][p_ir.source_framework] = inventory["frameworks"].get(p_ir.source_framework, 0) + 1