synapse-hub 2.7.0 → 3.0.0

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 (108) 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/skills/lean-impact-scoreboard/SKILL.md +1 -1
  5. package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
  6. package/.agents/skills/review-spec/SKILL.md +1 -1
  7. package/.agents/skills/senior-code-review/SKILL.md +1 -1
  8. package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
  9. package/CLAUDE.md +1 -1
  10. package/README.en.md +70 -291
  11. package/README.md +68 -303
  12. package/docs/ARCHITECTURE.md +48 -2
  13. package/docs/CONTRIBUTING.md +1 -1
  14. package/docs/QUALITY_GATES.md +113 -0
  15. package/package.json +1 -1
  16. package/scripts/package_dist.py +2 -1
  17. package/skills_manifest.json +6 -6
  18. package/synapse.config.json +1 -1
  19. package/templates/ai_bridges/CLAUDE.md +1 -1
  20. package/templates/ai_bridges/copilot-instructions.md +2 -2
  21. package/tools/__init__.py +18 -1
  22. package/tools/_openapi_parser.py +1 -1
  23. package/tools/agent_canary.py +42 -30
  24. package/tools/agentless_patcher.py +2 -2
  25. package/tools/analyze_project.py +3 -5
  26. package/tools/api_drift_sentinel.py +4 -4
  27. package/tools/api_fuzzer.py +8 -8
  28. package/tools/auto_heal.py +4 -5
  29. package/tools/auto_heal_reflexion.py +2 -2
  30. package/tools/autogen_qa_broker.py +2 -2
  31. package/tools/browser_helpers.js +2 -2
  32. package/tools/browsergym_eval.py +2 -3
  33. package/tools/bug_reporter.py +11 -13
  34. package/tools/bump_version.py +75 -5
  35. package/tools/cache_resolver.py +78 -83
  36. package/tools/check_env.py +9 -1
  37. package/tools/check_version_sync.py +40 -3
  38. package/tools/ci_healing_hook.py +4 -6
  39. package/tools/cli.py +5 -7
  40. package/tools/config_decoupler.py +2 -3
  41. package/tools/core/__init__.py +52 -0
  42. package/tools/count_tokens.py +22 -20
  43. package/tools/dashboard_generator.py +627 -998
  44. package/tools/db_sandbox.py +65 -53
  45. package/tools/debt_harvester.py +3 -3
  46. package/tools/distill_log.py +3 -3
  47. package/tools/dspy_qa_signatures.py +3 -4
  48. package/tools/export_postman_collection.py +8 -8
  49. package/tools/flaky_detector.py +2 -2
  50. package/tools/fuzz_payloads.py +5 -5
  51. package/tools/generate_allure_report.py +1 -1
  52. package/tools/generate_jmx_plan.py +4 -4
  53. package/tools/gherkin_suite.py +1 -1
  54. package/tools/git_pre_commit_guard.py +2 -2
  55. package/tools/goldfish_review.py +2 -2
  56. package/tools/heal/__init__.py +28 -0
  57. package/tools/heal_pr_automator.py +6 -6
  58. package/tools/heuristic_locator_matcher.py +2 -2
  59. package/tools/infer_linter.py +8 -8
  60. package/tools/interview_story.py +0 -1
  61. package/tools/jtl_analyzer.py +11 -11
  62. package/tools/lint/__init__.py +30 -0
  63. package/tools/lint_ai_docs.py +42 -0
  64. package/tools/lookup_tc.py +1 -1
  65. package/tools/matrix_sharder.py +15 -13
  66. package/tools/mcp/__init__.py +16 -0
  67. package/tools/mcp_excel_server.py +2 -2
  68. package/tools/mcp_server.py +3 -4
  69. package/tools/md_to_xlsx.py +26 -26
  70. package/tools/micro_mutator.py +11 -12
  71. package/tools/network_mock_shield.py +27 -27
  72. package/tools/newman_analyzer.py +2 -3
  73. package/tools/omni_emitter.py +5 -8
  74. package/tools/omni_parser.py +5 -5
  75. package/tools/omni_transpiler.py +5 -5
  76. package/tools/output_formatter.py +19 -0
  77. package/tools/perf/__init__.py +24 -0
  78. package/tools/pinpoint_failure.py +1 -1
  79. package/tools/project_state.py +0 -2
  80. package/tools/qa_event_stream.py +1 -1
  81. package/tools/repo_map.py +8 -8
  82. package/tools/report/__init__.py +32 -0
  83. package/tools/reversible_sandbox.py +1 -2
  84. package/tools/safety_firewall.py +2 -2
  85. package/tools/senior_code_review.py +2 -2
  86. package/tools/setup_project.py +10 -12
  87. package/tools/skill_collision_guard.py +3 -3
  88. package/tools/skill_vault.py +12 -3
  89. package/tools/skillspector.py +5 -7
  90. package/tools/smart_visual_masker.py +4 -6
  91. package/tools/spec_drift_sentinel.py +1 -2
  92. package/tools/swe_qa_aci.py +2 -2
  93. package/tools/synapse_qa_bench.py +3 -3
  94. package/tools/sync_agent_rules.py +17 -3
  95. package/tools/telemetry_correlator.py +3 -2
  96. package/tools/test_memory_os.py +15 -5
  97. package/tools/textgrad_mutation_optimizer.py +3 -3
  98. package/tools/tms_sync_bridge.py +5 -5
  99. package/tools/tot_exploratory_tester.py +5 -6
  100. package/tools/traffic_session_converter.py +9 -9
  101. package/tools/universal_ir.py +2 -3
  102. package/tools/update_sentinel.py +6 -9
  103. package/tools/verify_all.py +10 -0
  104. package/tools/visual_diff_canvas.py +1 -2
  105. package/tools/visual_som_helper.py +15 -13
  106. package/tools/watch_mode.py +1 -1
  107. package/tools/wcag_audit.py +22 -1
  108. package/tools/welcome_page.py +5 -6
@@ -163,16 +163,16 @@ def parse_csv_jtl(filepath: str) -> Tuple[Dict[str, SamplerStats], SamplerStats]
163
163
  # Column names can vary slightly across JMeter versions
164
164
  elapsed = float(row.get("elapsed") or row.get("Elapsed") or row.get("time") or 0)
165
165
  label = row.get("label") or row.get("Label") or "Unknown"
166
-
166
+
167
167
  success_val = row.get("success") or row.get("Success") or "true"
168
168
  success = str(success_val).strip().lower() in ("true", "1")
169
-
169
+
170
170
  ts_val = row.get("timeStamp") or row.get("Timestamp") or row.get("t") or 0
171
171
  timestamp = int(float(ts_val))
172
-
172
+
173
173
  bytes_val = row.get("bytes") or row.get("Bytes") or 0
174
174
  bytes_recv = int(float(bytes_val))
175
-
175
+
176
176
  resp_code = row.get("responseCode") or row.get("ResponseCode") or row.get("rc") or "200"
177
177
 
178
178
  if label not in per_sampler:
@@ -236,7 +236,7 @@ def evaluate_sla(
236
236
  ) -> Tuple[bool, List[str]]:
237
237
  """Evaluates computed aggregate metrics against SLA limits."""
238
238
  violations: List[str] = []
239
-
239
+
240
240
  if sla_p95 is not None and agg_stats["p95_ms"] > sla_p95:
241
241
  violations.append(f"P95 Latency SLA Breached: {agg_stats['p95_ms']}ms > {sla_p95}ms")
242
242
 
@@ -261,7 +261,7 @@ def format_table(per_sampler_data: List[Dict[str, Any]], agg_data: Dict[str, Any
261
261
  lines = []
262
262
  header_str = " | ".join(h.ljust(col_w[i]) for i, h in enumerate(headers))
263
263
  sep_str = "-+-".join("-" * col_w[i] for i in range(len(headers)))
264
-
264
+
265
265
  lines.append(header_str)
266
266
  lines.append(sep_str)
267
267
 
@@ -271,7 +271,7 @@ def format_table(per_sampler_data: List[Dict[str, Any]], agg_data: Dict[str, Any
271
271
  if len(label) > 28:
272
272
  label = label[:25] + "..."
273
273
  err_str = f"{row['errors']} ({row['error_rate_pct']}%)"
274
-
274
+
275
275
  row_str = " | ".join([
276
276
  label.ljust(col_w[0]),
277
277
  str(row["samples"]).rjust(col_w[1]),
@@ -301,7 +301,7 @@ def generate_markdown_report(
301
301
  ) -> str:
302
302
  """Generates GitHub / GitLab Markdown report for Pull Request annotations."""
303
303
  status_badge = "✅ **PASSED**" if passed else "❌ **FAILED (SLA BREACHED)**"
304
-
304
+
305
305
  lines = [
306
306
  "# 🚀 Apache JMeter Performance & Load Test Report",
307
307
  "",
@@ -377,7 +377,7 @@ def main():
377
377
  with open(temp_jtl, "w", encoding="utf-8", newline="") as f:
378
378
  writer = csv.writer(f)
379
379
  writer.writerow(["timeStamp", "elapsed", "label", "responseCode", "responseMessage", "threadName", "dataType", "success", "failureMessage", "bytes", "sentBytes", "grpThreads", "allThreads", "URL", "Latency", "IdleTime", "Connect"])
380
-
380
+
381
381
  # Generate 200 synthetic rows
382
382
  import time
383
383
  base_time = int(time.time() * 1000)
@@ -387,7 +387,7 @@ def main():
387
387
  writer.writerow([base_time + (i * 100), 280 + (i % 80), "[POST] /api/v1/orders", "201", "Created", "Thread 1-2", "text", "true", "", 2048, 512, 10, 10, "http://api.local/orders", 270, 0, 20])
388
388
  for i in range(5):
389
389
  writer.writerow([base_time + (i * 100), 1500, "[POST] /api/v1/orders", "500", "Internal Error", "Thread 1-2", "text", "false", "DB Timeout", 512, 512, 10, 10, "http://api.local/orders", 1490, 0, 25])
390
-
390
+
391
391
  args.jtl_file = temp_jtl
392
392
  print(f"🔬 [SAMPLE MODE] Generated synthetic benchmark JTL: {temp_jtl}")
393
393
 
@@ -414,7 +414,7 @@ def main():
414
414
  print("=" * 90)
415
415
 
416
416
  # Print SLA Verdict
417
- print(f"\n🎯 [QUALITY GATE SLA VERDICT]")
417
+ print("\n🎯 [QUALITY GATE SLA VERDICT]")
418
418
  if passed:
419
419
  print("✅ ALL PERFORMANCE SLAs PASSED! Ready for deployment/merge.")
420
420
  else:
@@ -0,0 +1,30 @@
1
+ """
2
+ Synapse Hub — Static Quality & Policy Sentinels (tools.lint)
3
+ -----------------------------------------------------------
4
+ Domain package grouping all static code defect, encoding, license,
5
+ and AI documentation drift verification tools.
6
+ """
7
+
8
+ from tools import (
9
+ author_integrity,
10
+ check_version_sync,
11
+ git_pre_commit_guard,
12
+ infer_linter,
13
+ lint_ai_docs,
14
+ skill_collision_guard,
15
+ skillspector,
16
+ spec_drift_sentinel,
17
+ validate_groovy_scripts,
18
+ )
19
+
20
+ __all__ = [
21
+ "author_integrity",
22
+ "check_version_sync",
23
+ "git_pre_commit_guard",
24
+ "infer_linter",
25
+ "lint_ai_docs",
26
+ "skill_collision_guard",
27
+ "skillspector",
28
+ "spec_drift_sentinel",
29
+ "validate_groovy_scripts",
30
+ ]
@@ -281,8 +281,50 @@ def lint_docs(repo_root):
281
281
  if mname not in valid_methods:
282
282
  errors.append(f"[{doc}:{line_no}] Non-existent method referenced: '{cname}.{mname}()' (Class '{cname}' does not define this method)")
283
283
 
284
+ # 6. Documentation Metrics Consistency (Detects stale skill/tool/gate numbers)
285
+ metric_errors = check_doc_metrics_consistency(repo_root)
286
+ errors.extend(metric_errors)
287
+
284
288
  return errors, warnings
285
289
 
290
+
291
+ def check_doc_metrics_consistency(repo_root):
292
+ """Verifies that documented counts (skills, tools, DoD gates) accurately match disk."""
293
+ errors = []
294
+ skills_dir = os.path.join(repo_root, ".agents", "skills")
295
+ tools_dir = os.path.join(repo_root, "tools")
296
+
297
+ if not os.path.isdir(skills_dir) or not os.path.isdir(tools_dir):
298
+ return errors
299
+
300
+ skills_count = len([d for d in os.listdir(skills_dir) if os.path.isdir(os.path.join(skills_dir, d)) and os.path.exists(os.path.join(skills_dir, d, "SKILL.md"))])
301
+ tools_count = len([f for f in os.listdir(tools_dir) if f.endswith(".py") and f != "__init__.py"])
302
+
303
+ obsolete_patterns = [
304
+ (re.compile(r'\b(42|50)\s+(kỹ năng|skills?)\b', re.IGNORECASE), f"Obsolete skill count detected (real count: {skills_count})"),
305
+ (re.compile(r'\b(60|68)\s+(công cụ|tools?)\b', re.IGNORECASE), f"Obsolete tool count detected (real count: {tools_count})"),
306
+ (re.compile(r'\b9\s+Cổng\s+Chất\s+Lượng\b', re.IGNORECASE), "Obsolete gate count detected: must refer to 10 Cổng Chất Lượng DoD (Gate 0–9)"),
307
+ ]
308
+
309
+ docs_to_check = [
310
+ os.path.join(repo_root, "README.md"),
311
+ os.path.join(repo_root, "README.en.md"),
312
+ os.path.join(repo_root, "docs", "ARCHITECTURE.md"),
313
+ os.path.join(repo_root, "docs", "CONTRIBUTING.md"),
314
+ ]
315
+
316
+ for doc_path in docs_to_check:
317
+ if not os.path.exists(doc_path):
318
+ continue
319
+ rel_doc = os.path.relpath(doc_path, repo_root)
320
+ with open(doc_path, "r", encoding="utf-8", errors="ignore") as f:
321
+ for line_no, line in enumerate(f, 1):
322
+ for pattern, msg in obsolete_patterns:
323
+ if pattern.search(line):
324
+ errors.append(f"[{rel_doc}:{line_no}] {msg}: '{line.strip()}'")
325
+
326
+ return errors
327
+
286
328
  def main():
287
329
  import argparse
288
330
  parser = argparse.ArgumentParser(description="Enterprise AI Documentation & Artifact Linter")
@@ -87,7 +87,7 @@ def normalize_testcase_data(raw_data):
87
87
  return raw_data["testcases"]
88
88
  if "items" in raw_data and isinstance(raw_data["items"], list):
89
89
  return raw_data["items"]
90
-
90
+
91
91
  flat = []
92
92
  for k, v in raw_data.items():
93
93
  if isinstance(v, dict):
@@ -52,13 +52,15 @@ def estimate_test_duration(file_path: str, full_path: str) -> float:
52
52
  if os.path.exists(CACHE_DB_PATH):
53
53
  try:
54
54
  conn = sqlite3.connect(CACHE_DB_PATH)
55
- cur = conn.cursor()
56
- cur.execute("CREATE TABLE IF NOT EXISTS test_runtimes (test_path TEXT PRIMARY KEY, duration REAL)")
57
- cur.execute("SELECT duration FROM test_runtimes WHERE test_path = ?", (file_path,))
58
- row = cur.fetchone()
59
- conn.close()
60
- if row and row[0]:
61
- return float(row[0])
55
+ try:
56
+ cur = conn.cursor()
57
+ cur.execute("CREATE TABLE IF NOT EXISTS test_runtimes (test_path TEXT PRIMARY KEY, duration REAL)")
58
+ cur.execute("SELECT duration FROM test_runtimes WHERE test_path = ?", (file_path,))
59
+ row = cur.fetchone()
60
+ if row and row[0]:
61
+ return float(row[0])
62
+ finally:
63
+ conn.close()
62
64
  except sqlite3.Error:
63
65
  conn = None
64
66
 
@@ -93,14 +95,14 @@ def shard_tests_lpt(test_files: List[str], target_dir: str, num_shards: int) ->
93
95
  tests_with_cost.sort(key=lambda x: x[1], reverse=True)
94
96
 
95
97
  # 3. Initialize shards
96
- shards = [{"shard_index": i + 1, "total_duration": 0.0, "tests": []} for i in range(num_shards)]
98
+ shards: List[Dict[str, Any]] = [{"shard_index": i + 1, "total_duration": 0.0, "tests": []} for i in range(num_shards)]
97
99
 
98
100
  # 4. Greedily assign to the shard with the least current cumulative duration
99
101
  for test_file, dur in tests_with_cost:
100
102
  # Find shard with min duration
101
- min_shard = min(shards, key=lambda s: s["total_duration"])
103
+ min_shard = min(shards, key=lambda s: float(s["total_duration"]))
102
104
  min_shard["tests"].append(test_file)
103
- min_shard["total_duration"] = round(min_shard["total_duration"] + dur, 2)
105
+ min_shard["total_duration"] = round(float(min_shard["total_duration"]) + dur, 2)
104
106
 
105
107
  return shards
106
108
 
@@ -119,9 +121,9 @@ def main():
119
121
  if args.json:
120
122
  print(json.dumps({"total_tests": len(test_files), "shards_count": args.shards, "shards": shards}, indent=2))
121
123
  else:
122
- print(f"\n======================================================================")
124
+ print("\n======================================================================")
123
125
  print(f" DURATION-BALANCED TEST MATRIX SHARDER ({args.shards} PARALLEL WORKERS) ")
124
- print(f"======================================================================")
126
+ print("======================================================================")
125
127
  print(f" Target Scope : {target_dir}")
126
128
  print(f" Total Tests : {len(test_files)} files\n")
127
129
 
@@ -137,7 +139,7 @@ def main():
137
139
  durations = [s["total_duration"] for s in shards]
138
140
  delta = max(durations) - min(durations) if durations else 0
139
141
  print(f"⚡ Shard Imbalance Delta: {delta:.1f}s (Optimal parallel distribution)")
140
- print(f"======================================================================\n")
142
+ print("======================================================================\n")
141
143
 
142
144
  return 0
143
145
 
@@ -0,0 +1,16 @@
1
+ """
2
+ Synapse Hub — Model Context Protocol (MCP) Servers (tools.mcp)
3
+ --------------------------------------------------------------
4
+ Domain package grouping all standard MCP services: core QA automation
5
+ MCP server and offline Excel spreadsheet manipulation server.
6
+ """
7
+
8
+ from tools import (
9
+ mcp_excel_server,
10
+ mcp_server,
11
+ )
12
+
13
+ __all__ = [
14
+ "mcp_excel_server",
15
+ "mcp_server",
16
+ ]
@@ -23,7 +23,7 @@ if hasattr(sys.stdout, 'reconfigure'):
23
23
  except Exception:
24
24
  pass
25
25
 
26
- SERVER_VERSION = "2.7.0"
26
+ SERVER_VERSION = "3.0.0"
27
27
 
28
28
  NS_MAIN = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}"
29
29
  NS_REL = "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}"
@@ -88,7 +88,7 @@ def read_sheet_rows(xlsx_path: str, sheet_name: Optional[str] = None, max_rows:
88
88
  for c in row.iter(f"{NS_MAIN}c"):
89
89
  cell_type = c.attrib.get("t")
90
90
  val_elem = c.find(f"{NS_MAIN}v")
91
- raw_val = val_elem.text if val_elem is not None else ""
91
+ raw_val = (val_elem.text or "") if val_elem is not None else ""
92
92
 
93
93
  if cell_type == "s" and raw_val.isdigit():
94
94
  idx = int(raw_val)
@@ -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.0"
51
50
 
52
51
  MCP_TOOLS = [
53
52
  {
@@ -599,7 +598,7 @@ def handle_tool_call(name: str, arguments: Dict[str, Any]) -> str:
599
598
  return handler(arguments)
600
599
 
601
600
 
602
- def process_message(msg: Dict[str, Any]) -> Dict[str, Any]:
601
+ def process_message(msg: Dict[str, Any]) -> Optional[Dict[str, Any]]:
603
602
  """Processes an incoming JSON-RPC 2.0 message."""
604
603
  method = msg.get("method")
605
604
  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)
@@ -26,7 +26,6 @@ import shlex
26
26
  import html as html_lib
27
27
  import argparse
28
28
  import subprocess
29
- import tempfile
30
29
  from pathlib import Path
31
30
  from typing import List, Dict, Any, Tuple
32
31
 
@@ -260,9 +259,9 @@ def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tup
260
259
  """
261
260
  if not mutants:
262
261
  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(("//", "#", "/*"))
262
+ ASSERTION_PATTERN.search(line) and not TAUTOLOGY_PATTERN.search(line)
263
+ for line in code.splitlines()
264
+ if not line.strip().startswith(("//", "#", "/*"))
266
265
  )
267
266
  return mutants, (100.0 if has_asserts else 0.0)
268
267
 
@@ -271,8 +270,8 @@ def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tup
271
270
 
272
271
  # Identify lines containing assertions
273
272
  assertion_entries = []
274
- for idx, l in enumerate(lines, start=1):
275
- s = l.strip()
273
+ for idx, line in enumerate(lines, start=1):
274
+ s = line.strip()
276
275
  if s.startswith(("//", "#", "/*")):
277
276
  continue
278
277
  if ASSERTION_PATTERN.search(s) and not TAUTOLOGY_PATTERN.search(s):
@@ -489,9 +488,9 @@ def evaluate_target(target_path: str, min_score: float = 80.0, test_cmd: str = "
489
488
  valid_exts = {".groovy", ".py", ".ts", ".js"}
490
489
  file_scores = {}
491
490
  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)
491
+ for filename in files:
492
+ if any(filename.endswith(ext) for ext in valid_exts):
493
+ file_full = os.path.join(root, filename)
495
494
  try:
496
495
  with open(file_full, "r", encoding="utf-8", errors="ignore") as fp:
497
496
  code = fp.read()
@@ -635,16 +634,16 @@ def main():
635
634
  else:
636
635
  mode_tag = "LIVE DYNAMIC RUNNER" if report.get("mode") == "DYNAMIC" else "STATIC HEURISTIC"
637
636
  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======================================================================")
637
+ print("\n======================================================================")
639
638
  print(f" ASSERTION SENSITIVITY & SYNTACTIC MUTATION SENTINEL [{mode_tag}] ")
640
- print(f"======================================================================")
639
+ print("======================================================================")
641
640
  print(f" Target Scope : {report['target']}")
642
641
  print(f" Evaluation Mode : {mode_desc}")
643
642
  print(f" Mutants Injected : {report['total_mutants']}")
644
643
  print(f" Mutants Caught/Killed : {report['killed']}")
645
644
  print(f" Mutants Survived : {report['survived']}")
646
645
  print(f" Assertion Sensitivity : {report['mutation_score']}% (Required: >={report['min_score']}%)")
647
- print(f"======================================================================\n")
646
+ print("======================================================================\n")
648
647
 
649
648
  for m in report['mutants'][:6]:
650
649
  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: