synapse-hub 2.6.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.
- package/.agents/agents/failure-analyst.md +4 -0
- package/.agents/agents/live-explorer.md +2 -0
- package/.agents/agents/orchestrator.md +16 -0
- package/.agents/agents/pom-crafter.md +10 -0
- package/.agents/agents/quality-gatekeeper.md +5 -0
- package/.agents/agents/test-runner.md +8 -0
- package/.agents/rules/git-push-protocol.md +1 -1
- package/.agents/rules/graphify.md +2 -0
- package/.agents/rules/mandatory-playwright-live.md +9 -9
- package/.agents/rules/mobile_appium_rules.md +8 -1
- package/.agents/skills/actor-qa-swarm/SKILL.md +77 -0
- package/.agents/skills/actor-qa-swarm/examples/broker_sharding_sample.json +12 -0
- package/.agents/skills/agentless-qa-repair/SKILL.md +69 -0
- package/.agents/skills/agentless-qa-repair/examples/healing_report_sample.json +17 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +1 -1
- package/.agents/skills/clean-pom-crafter/SKILL.md +2 -2
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +4 -4
- package/.agents/skills/event-stream-qa-orchestrator/SKILL.md +79 -0
- package/.agents/skills/event-stream-qa-orchestrator/examples/timeline_sample.jsonl +2 -0
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +2 -2
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +6 -21
- package/.agents/skills/katalon-cli-runner/SKILL.md +1 -1
- package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
- package/.agents/skills/mipro-prompt-compiler/SKILL.md +59 -0
- package/.agents/skills/mipro-prompt-compiler/examples/compiled_module_sample.json +22 -0
- package/.agents/skills/qa-aci-interface/SKILL.md +67 -0
- package/.agents/skills/qa-aci-interface/examples/surgical_edit_sample.json +12 -0
- package/.agents/skills/qa-memory-os/SKILL.md +80 -0
- package/.agents/skills/qa-memory-os/examples/archival_memory_sample.json +21 -0
- package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
- package/.agents/skills/review-spec/SKILL.md +60 -0
- package/.agents/skills/review-spec/examples/sample_spec_review.md +33 -0
- package/.agents/skills/senior-code-review/SKILL.md +1 -1
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +2 -2
- package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
- package/.agents/skills/tot-exploratory-testing/SKILL.md +59 -0
- package/.agents/skills/tot-exploratory-testing/examples/tot_graph_sample.json +35 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +2 -2
- package/.agents/skills/web-agent-gym-benchmarking/SKILL.md +62 -0
- package/.agents/skills/web-agent-gym-benchmarking/examples/benchmark_report_sample.json +22 -0
- package/CLAUDE.md +2 -2
- package/README.en.md +70 -291
- package/README.md +68 -303
- package/docs/ARCHITECTURE.md +54 -8
- package/docs/CONTRIBUTING.md +1 -1
- package/docs/QUALITY_GATES.md +113 -0
- package/docs/research/EXTERNAL_AGENT_SKILLS_SURVEY.md +1062 -0
- package/package.json +1 -1
- package/scripts/package_dist.py +2 -1
- package/skills_manifest.json +92 -11
- package/synapse.config.json +1 -1
- package/templates/ai_bridges/CLAUDE.md +2 -2
- package/templates/ai_bridges/copilot-instructions.md +2 -2
- package/tools/TOOLS_RUNBOOK.md +1 -1
- package/tools/__init__.py +18 -1
- package/tools/_openapi_parser.py +158 -0
- package/tools/agent_canary.py +42 -30
- package/tools/agentless_patcher.py +417 -0
- package/tools/analyze_project.py +12 -11
- package/tools/api_drift_sentinel.py +4 -4
- package/tools/api_fuzzer.py +8 -8
- package/tools/auto_heal.py +23 -5
- package/tools/auto_heal_reflexion.py +2 -2
- package/tools/autogen_qa_broker.py +318 -0
- package/tools/browser_helpers.js +2 -2
- package/tools/browsergym_eval.py +236 -0
- package/tools/bug_reporter.py +11 -13
- package/tools/bump_version.py +75 -5
- package/tools/cache_resolver.py +78 -83
- package/tools/check_env.py +9 -1
- package/tools/check_version_sync.py +40 -3
- package/tools/ci_healing_hook.py +5 -7
- package/tools/cli.py +5 -7
- package/tools/config_decoupler.py +2 -3
- package/tools/core/__init__.py +52 -0
- package/tools/count_tokens.py +22 -20
- package/tools/dashboard_generator.py +628 -999
- package/tools/db_sandbox.py +69 -53
- package/tools/debt_harvester.py +3 -3
- package/tools/distill_log.py +3 -3
- package/tools/dspy_qa_signatures.py +3 -4
- package/tools/export_postman_collection.py +8 -8
- package/tools/flaky_detector.py +2 -2
- package/tools/fuzz_payloads.py +5 -5
- package/tools/generate_allure_report.py +1 -1
- package/tools/generate_jmx_plan.py +4 -4
- package/tools/gherkin_suite.py +1 -1
- package/tools/git_pre_commit_guard.py +2 -2
- package/tools/goldfish_review.py +2 -2
- package/tools/heal/__init__.py +28 -0
- package/tools/heal_pr_automator.py +29 -4
- package/tools/heuristic_locator_matcher.py +2 -2
- package/tools/infer_linter.py +8 -8
- package/tools/interview_story.py +0 -1
- package/tools/jtl_analyzer.py +11 -11
- package/tools/lint/__init__.py +30 -0
- package/tools/lint_ai_docs.py +42 -0
- package/tools/lookup_tc.py +1 -1
- package/tools/matrix_sharder.py +15 -13
- package/tools/mcp/__init__.py +16 -0
- package/tools/mcp_excel_server.py +2 -2
- package/tools/mcp_server.py +3 -4
- package/tools/md_to_xlsx.py +26 -26
- package/tools/micro_mutator.py +11 -12
- package/tools/mipro_assertion_tuner.py +207 -0
- package/tools/network_mock_shield.py +27 -27
- package/tools/newman_analyzer.py +2 -3
- package/tools/omni_emitter.py +5 -8
- package/tools/omni_parser.py +5 -5
- package/tools/omni_transpiler.py +5 -5
- package/tools/output_formatter.py +19 -0
- package/tools/perf/__init__.py +24 -0
- package/tools/pinpoint_failure.py +13 -1
- package/tools/project_state.py +4 -2
- package/tools/qa_event_stream.py +336 -0
- package/tools/repo_map.py +8 -8
- package/tools/report/__init__.py +32 -0
- package/tools/reversible_sandbox.py +5 -2
- package/tools/safety_firewall.py +2 -2
- package/tools/senior_code_review.py +2 -2
- package/tools/setup_project.py +10 -12
- package/tools/skill_collision_guard.py +3 -3
- package/tools/skill_vault.py +12 -3
- package/tools/skillspector.py +5 -7
- package/tools/smart_visual_masker.py +4 -6
- package/tools/spec_drift_sentinel.py +1 -2
- package/tools/swe_qa_aci.py +369 -0
- package/tools/synapse_qa_bench.py +3 -3
- package/tools/sync_agent_rules.py +18 -4
- package/tools/telemetry_correlator.py +3 -2
- package/tools/test_memory_os.py +419 -0
- package/tools/textgrad_mutation_optimizer.py +3 -3
- package/tools/tms_sync_bridge.py +5 -5
- package/tools/tot_exploratory_tester.py +239 -0
- package/tools/traffic_session_converter.py +9 -9
- package/tools/universal_ir.py +2 -3
- package/tools/update_sentinel.py +6 -9
- package/tools/verify_all.py +10 -0
- package/tools/visual_diff_canvas.py +1 -2
- package/tools/visual_som_helper.py +15 -13
- package/tools/watch_mode.py +1 -1
- package/tools/wcag_audit.py +22 -1
- package/tools/welcome_page.py +5 -6
package/tools/jtl_analyzer.py
CHANGED
|
@@ -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(
|
|
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
|
+
]
|
package/tools/lint_ai_docs.py
CHANGED
|
@@ -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")
|
package/tools/lookup_tc.py
CHANGED
package/tools/matrix_sharder.py
CHANGED
|
@@ -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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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(
|
|
124
|
+
print("\n======================================================================")
|
|
123
125
|
print(f" DURATION-BALANCED TEST MATRIX SHARDER ({args.shards} PARALLEL WORKERS) ")
|
|
124
|
-
print(
|
|
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(
|
|
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 = "
|
|
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)
|
package/tools/mcp_server.py
CHANGED
|
@@ -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,
|
|
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 = "
|
|
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")
|
package/tools/md_to_xlsx.py
CHANGED
|
@@ -16,7 +16,7 @@ import os
|
|
|
16
16
|
import sys
|
|
17
17
|
import re
|
|
18
18
|
import argparse
|
|
19
|
-
from typing import List,
|
|
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
|
|
264
|
-
if len(
|
|
265
|
-
max_len = len(
|
|
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)
|
package/tools/micro_mutator.py
CHANGED
|
@@ -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(
|
|
264
|
-
for
|
|
265
|
-
if not
|
|
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,
|
|
275
|
-
s =
|
|
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
|
|
493
|
-
if any(
|
|
494
|
-
file_full = os.path.join(root,
|
|
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(
|
|
637
|
+
print("\n======================================================================")
|
|
639
638
|
print(f" ASSERTION SENSITIVITY & SYNTACTIC MUTATION SENTINEL [{mode_tag}] ")
|
|
640
|
-
print(
|
|
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(
|
|
646
|
+
print("======================================================================\n")
|
|
648
647
|
|
|
649
648
|
for m in report['mutants'][:6]:
|
|
650
649
|
status_tag = "[KILLED] " if m["status"] == "KILLED" else "[SURVIVED]"
|