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.
- package/.agents/agents/live-explorer.md +2 -0
- package/.agents/agents/pom-crafter.md +2 -0
- package/.agents/agents/test-runner.md +3 -0
- package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
- package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
- package/.agents/skills/review-spec/SKILL.md +1 -1
- package/.agents/skills/senior-code-review/SKILL.md +1 -1
- package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
- package/CLAUDE.md +1 -1
- package/README.en.md +70 -291
- package/README.md +68 -303
- package/docs/ARCHITECTURE.md +48 -2
- package/docs/CONTRIBUTING.md +1 -1
- package/docs/QUALITY_GATES.md +113 -0
- package/package.json +1 -1
- package/scripts/package_dist.py +2 -1
- package/skills_manifest.json +6 -6
- package/synapse.config.json +1 -1
- package/templates/ai_bridges/CLAUDE.md +1 -1
- package/templates/ai_bridges/copilot-instructions.md +2 -2
- package/tools/__init__.py +18 -1
- package/tools/_openapi_parser.py +1 -1
- package/tools/agent_canary.py +42 -30
- package/tools/agentless_patcher.py +2 -2
- package/tools/analyze_project.py +3 -5
- package/tools/api_drift_sentinel.py +4 -4
- package/tools/api_fuzzer.py +8 -8
- package/tools/auto_heal.py +4 -5
- package/tools/auto_heal_reflexion.py +2 -2
- package/tools/autogen_qa_broker.py +2 -2
- package/tools/browser_helpers.js +2 -2
- package/tools/browsergym_eval.py +2 -3
- 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 +4 -6
- 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 +627 -998
- package/tools/db_sandbox.py +65 -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 +6 -6
- 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/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 +1 -1
- package/tools/project_state.py +0 -2
- package/tools/qa_event_stream.py +1 -1
- package/tools/repo_map.py +8 -8
- package/tools/report/__init__.py +32 -0
- package/tools/reversible_sandbox.py +1 -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 +2 -2
- package/tools/synapse_qa_bench.py +3 -3
- package/tools/sync_agent_rules.py +17 -3
- package/tools/telemetry_correlator.py +3 -2
- package/tools/test_memory_os.py +15 -5
- package/tools/textgrad_mutation_optimizer.py +3 -3
- package/tools/tms_sync_bridge.py +5 -5
- package/tools/tot_exploratory_tester.py +5 -6
- 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/db_sandbox.py
CHANGED
|
@@ -34,7 +34,7 @@ import sqlite3
|
|
|
34
34
|
import argparse
|
|
35
35
|
import urllib.request
|
|
36
36
|
import urllib.error
|
|
37
|
-
from typing import Dict, Any, List,
|
|
37
|
+
from typing import Dict, Any, List, Tuple
|
|
38
38
|
|
|
39
39
|
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
40
40
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
@@ -86,18 +86,24 @@ class DBSandbox:
|
|
|
86
86
|
|
|
87
87
|
if not os.path.exists(self.db_path):
|
|
88
88
|
conn = sqlite3.connect(self.db_path)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
try:
|
|
90
|
+
cursor = conn.cursor()
|
|
91
|
+
cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, code TEXT, status TEXT)")
|
|
92
|
+
cursor.execute("CREATE TABLE IF NOT EXISTS orders (id INTEGER PRIMARY KEY, user_id INTEGER, amount REAL)")
|
|
93
|
+
conn.commit()
|
|
94
|
+
finally:
|
|
95
|
+
conn.close()
|
|
94
96
|
|
|
95
97
|
# Perform atomic SQLite backup
|
|
96
98
|
src_conn = sqlite3.connect(self.db_path)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
try:
|
|
100
|
+
dst_conn = sqlite3.connect(snapshot_file)
|
|
101
|
+
try:
|
|
102
|
+
src_conn.backup(dst_conn)
|
|
103
|
+
finally:
|
|
104
|
+
dst_conn.close()
|
|
105
|
+
finally:
|
|
106
|
+
src_conn.close()
|
|
101
107
|
|
|
102
108
|
else:
|
|
103
109
|
# PostgreSQL / MySQL Docker hook or file copy
|
|
@@ -150,10 +156,14 @@ class DBSandbox:
|
|
|
150
156
|
|
|
151
157
|
if self.db_type == "sqlite":
|
|
152
158
|
src_conn = sqlite3.connect(snapshot_file)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
159
|
+
try:
|
|
160
|
+
dst_conn = sqlite3.connect(self.db_path)
|
|
161
|
+
try:
|
|
162
|
+
src_conn.backup(dst_conn)
|
|
163
|
+
finally:
|
|
164
|
+
dst_conn.close()
|
|
165
|
+
finally:
|
|
166
|
+
src_conn.close()
|
|
157
167
|
else:
|
|
158
168
|
shutil.copy2(snapshot_file, self.db_path)
|
|
159
169
|
|
|
@@ -199,7 +209,6 @@ class DBSandbox:
|
|
|
199
209
|
cursor.execute("SELECT COUNT(*) FROM users")
|
|
200
210
|
count_after_rollback = cursor.fetchone()[0]
|
|
201
211
|
|
|
202
|
-
conn.close()
|
|
203
212
|
return {
|
|
204
213
|
"status": "SUCCESS",
|
|
205
214
|
"statements_executed": executed_count,
|
|
@@ -211,7 +220,6 @@ class DBSandbox:
|
|
|
211
220
|
except Exception as e:
|
|
212
221
|
try:
|
|
213
222
|
cursor.execute(f"ROLLBACK TO SAVEPOINT {savepoint_name};")
|
|
214
|
-
conn.close()
|
|
215
223
|
except sqlite3.Error as rollback_err:
|
|
216
224
|
sys.stderr.write(f"Notice: Savepoint rollback notice: {rollback_err}\n")
|
|
217
225
|
return {
|
|
@@ -219,6 +227,8 @@ class DBSandbox:
|
|
|
219
227
|
"error": str(e),
|
|
220
228
|
"zero_pollution_verified": True
|
|
221
229
|
}
|
|
230
|
+
finally:
|
|
231
|
+
conn.close()
|
|
222
232
|
|
|
223
233
|
# -------------------------------------------------------------------------
|
|
224
234
|
# 3. TAINT POLLUTION AUDIT & PURGE
|
|
@@ -254,43 +264,45 @@ class DBSandbox:
|
|
|
254
264
|
}
|
|
255
265
|
|
|
256
266
|
conn = sqlite3.connect(self.db_path)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
for
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
267
|
+
try:
|
|
268
|
+
cursor = conn.cursor()
|
|
269
|
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';")
|
|
270
|
+
tables = [r[0] for r in cursor.fetchall()]
|
|
271
|
+
|
|
272
|
+
total_purged = 0
|
|
273
|
+
purge_log = []
|
|
274
|
+
|
|
275
|
+
for table in tables:
|
|
276
|
+
# Validate table identifier to protect against injection
|
|
277
|
+
if not re.match(r'^[a-zA-Z0-9_]+$', table):
|
|
278
|
+
continue
|
|
279
|
+
cursor.execute(f"PRAGMA table_info([{table}]);")
|
|
280
|
+
columns = [c[1] for c in cursor.fetchall()]
|
|
281
|
+
|
|
282
|
+
# Look for code or name columns
|
|
283
|
+
target_cols = [c for c in columns if c.lower() in ("code", "username", "name", "email", "status") and re.match(r'^[a-zA-Z0-9_]+$', c)]
|
|
284
|
+
for col in target_cols:
|
|
285
|
+
for prefix in known_test_prefixes:
|
|
286
|
+
escaped_pattern = self._escape_sql_like(prefix) + "%"
|
|
287
|
+
cursor.execute(
|
|
288
|
+
f"SELECT COUNT(*) FROM [{table}] WHERE [{col}] LIKE ? ESCAPE '\\'",
|
|
289
|
+
(escaped_pattern,)
|
|
290
|
+
)
|
|
291
|
+
c_count = cursor.fetchone()[0]
|
|
292
|
+
if c_count > 0:
|
|
293
|
+
if not dry_run:
|
|
294
|
+
cursor.execute(
|
|
295
|
+
f"DELETE FROM [{table}] WHERE [{col}] LIKE ? ESCAPE '\\'",
|
|
296
|
+
(escaped_pattern,)
|
|
297
|
+
)
|
|
298
|
+
total_purged += c_count
|
|
299
|
+
action_str = "previewed" if dry_run else "deleted"
|
|
300
|
+
purge_log.append(f"Table '{table}' ({col}): {action_str} {c_count} rows matching literal prefix '{prefix}'")
|
|
301
|
+
|
|
302
|
+
if not dry_run:
|
|
303
|
+
conn.commit()
|
|
304
|
+
finally:
|
|
305
|
+
conn.close()
|
|
294
306
|
|
|
295
307
|
return {
|
|
296
308
|
"status": "CLEAN" if not dry_run else "DRY_RUN",
|
package/tools/debt_harvester.py
CHANGED
|
@@ -67,9 +67,9 @@ class ShortcutEntry:
|
|
|
67
67
|
# 1. shortcut: <desc>, ceiling: <limit>, upgrade: <trigger>
|
|
68
68
|
# 2. shortcut: <desc>, <limit>, <trigger>
|
|
69
69
|
# 3. shortcut: <limit>, <when to upgrade>
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
parts = [p.strip() for p in text.split(',') if p.strip()]
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
# Check explicit labels
|
|
74
74
|
ceiling_match = re.search(r'(?:ceiling|limit):\s*([^,]+)', text, re.IGNORECASE)
|
|
75
75
|
upgrade_match = re.search(r'(?:upgrade|when|trigger):\s*([^,]+)', text, re.IGNORECASE)
|
|
@@ -153,7 +153,7 @@ def format_markdown(entries: List[ShortcutEntry], target_dir: str) -> str:
|
|
|
153
153
|
"""Renders shortcut debt entries into a structured Markdown ledger."""
|
|
154
154
|
total = len(entries)
|
|
155
155
|
no_trigger = sum(1 for e in entries if not e.has_trigger)
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
lines = [
|
|
158
158
|
"# Technical Debt & Shortcut Ledger",
|
|
159
159
|
"",
|
package/tools/distill_log.py
CHANGED
|
@@ -22,7 +22,7 @@ if hasattr(sys.stdout, 'reconfigure'):
|
|
|
22
22
|
def distill(log_text, max_lines=40):
|
|
23
23
|
lines = log_text.splitlines()
|
|
24
24
|
total_raw_lines = len(lines)
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
# Noise filters
|
|
27
27
|
noise_patterns = [
|
|
28
28
|
re.compile(r'^\s*$'),
|
|
@@ -54,7 +54,7 @@ def distill(log_text, max_lines=40):
|
|
|
54
54
|
|
|
55
55
|
tc_pattern = re.compile(r'(TC-\d+|test_[A-Za-z0-9_]+|[A-Za-z0-9_-]+\.(?:spec|test)\.[jt]sx?)')
|
|
56
56
|
line_num_pattern = re.compile(r'((?:Script\d+|\w+Page|\w+\.(?:groovy|ts|js|py)):(\d+))|File "([^"]+)", line (\d+)')
|
|
57
|
-
|
|
57
|
+
re.compile(r'([A-Za-z0-9_.]*(?:Exception|Error)):?\s*(.*)')
|
|
58
58
|
|
|
59
59
|
captured_failures = []
|
|
60
60
|
failing_tcs = set()
|
|
@@ -110,7 +110,7 @@ def distill(log_text, max_lines=40):
|
|
|
110
110
|
output.append(f" Targeted TCs: {', '.join(sorted(failing_tcs))}")
|
|
111
111
|
else:
|
|
112
112
|
output.append(f"❌ FAILURES / ERRORS DETECTED: {len(captured_failures)} failure block(s) isolated.")
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
if failing_locs:
|
|
115
115
|
unique_locs = sorted(set(failing_locs))
|
|
116
116
|
output.append(f" Failing Script Location(s): {', '.join(unique_locs[:5])}")
|
|
@@ -15,12 +15,11 @@ Reference:
|
|
|
15
15
|
- Synapse-Hub Subsystem 3: Section 6.4 of agent_skill_training_analysis.md.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
import os
|
|
19
18
|
import sys
|
|
20
19
|
import json
|
|
21
20
|
import re
|
|
22
21
|
import argparse
|
|
23
|
-
from typing import Dict, Any, Optional, Tuple
|
|
22
|
+
from typing import Dict, Any, Optional, Tuple
|
|
24
23
|
|
|
25
24
|
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
26
25
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
@@ -261,7 +260,7 @@ def qa_dod_metric(gold: Any, pred: Any, trace: Optional[Any] = None) -> float:
|
|
|
261
260
|
Returns 1.0 (PASS) or 0.0 (FAIL).
|
|
262
261
|
"""
|
|
263
262
|
test_code = getattr(pred, "test_script_code", "")
|
|
264
|
-
|
|
263
|
+
getattr(pred, "page_class_code", "")
|
|
265
264
|
|
|
266
265
|
if not test_code:
|
|
267
266
|
return 0.0
|
|
@@ -305,7 +304,7 @@ def main():
|
|
|
305
304
|
return 0
|
|
306
305
|
|
|
307
306
|
if args.rca:
|
|
308
|
-
print(
|
|
307
|
+
print("\n🩺 Diagnosing Failure Trace with RCA Signature...")
|
|
309
308
|
diagnosis = OfflineSignatureCompiler.diagnose_rca_failure(args.rca)
|
|
310
309
|
print(json.dumps(diagnosis, indent=2))
|
|
311
310
|
return 0
|
|
@@ -41,7 +41,7 @@ import uuid
|
|
|
41
41
|
import csv
|
|
42
42
|
import argparse
|
|
43
43
|
from typing import Dict, Any, List, Optional
|
|
44
|
-
from urllib.parse import urlparse
|
|
44
|
+
from urllib.parse import urlparse
|
|
45
45
|
|
|
46
46
|
# Enforce UTF-8 stdout encoding for Windows/macOS/Linux console compatibility
|
|
47
47
|
if hasattr(sys.stdout, "reconfigure"):
|
|
@@ -151,7 +151,7 @@ def mock_schema_payload(schema: Dict[str, Any], depth: int = 0) -> Any:
|
|
|
151
151
|
return "{{$guid}}"
|
|
152
152
|
elif fmt in ("date-time", "date"):
|
|
153
153
|
return "{{$isoTimestamp}}"
|
|
154
|
-
return schema.get("example") or
|
|
154
|
+
return schema.get("example") or "AUTO_{{$randomAlphaNumeric}}"
|
|
155
155
|
|
|
156
156
|
elif stype in ("integer", "number"):
|
|
157
157
|
return schema.get("example") or schema.get("minimum", 100)
|
|
@@ -191,7 +191,7 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
191
191
|
base_url_default = "https://api.staging.enterprise.local"
|
|
192
192
|
|
|
193
193
|
# Postman root structure
|
|
194
|
-
collection = {
|
|
194
|
+
collection: Dict[str, Any] = {
|
|
195
195
|
"info": {
|
|
196
196
|
"_postman_id": generate_uuid(),
|
|
197
197
|
"name": col_name,
|
|
@@ -302,7 +302,7 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
302
302
|
else:
|
|
303
303
|
normalized_segments.append(seg)
|
|
304
304
|
|
|
305
|
-
url_obj = {
|
|
305
|
+
url_obj: Dict[str, Any] = {
|
|
306
306
|
"raw": f"{{{{baseUrl}}}}{raw_path}",
|
|
307
307
|
"host": ["{{baseUrl}}"],
|
|
308
308
|
"path": normalized_segments
|
|
@@ -325,7 +325,7 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
325
325
|
if not resp_schema and "schema" in resp_info:
|
|
326
326
|
resp_schema = resp_info["schema"]
|
|
327
327
|
|
|
328
|
-
postman_request = {
|
|
328
|
+
postman_request: Dict[str, Any] = {
|
|
329
329
|
"name": summary,
|
|
330
330
|
"event": [
|
|
331
331
|
{
|
|
@@ -380,7 +380,7 @@ def parse_har_file(har_data: Dict[str, Any], base_url_override: Optional[str] =
|
|
|
380
380
|
|
|
381
381
|
base_url_default = base_url_override or "https://api.staging.enterprise.local"
|
|
382
382
|
|
|
383
|
-
collection = {
|
|
383
|
+
collection: Dict[str, Any] = {
|
|
384
384
|
"info": {
|
|
385
385
|
"_postman_id": generate_uuid(),
|
|
386
386
|
"name": collection_name,
|
|
@@ -428,7 +428,7 @@ def parse_har_file(har_data: Dict[str, Any], base_url_override: Optional[str] =
|
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
expected_status = resp.get("status", 200) or 200
|
|
431
|
-
postman_item = {
|
|
431
|
+
postman_item: Dict[str, Any] = {
|
|
432
432
|
"name": f"[{method}] {p.path or '/'}",
|
|
433
433
|
"event": [
|
|
434
434
|
{
|
|
@@ -918,7 +918,7 @@ def main():
|
|
|
918
918
|
with open(target_output, "w", encoding="utf-8") as f:
|
|
919
919
|
json.dump(collection, f, indent=2, ensure_ascii=False)
|
|
920
920
|
print(f"✅ [POSTMAN EXPORT] Successfully created Postman Collection v2.1.0: {target_output}")
|
|
921
|
-
print(
|
|
921
|
+
print("🛡️ Embedded: Pre-request Nonce & Auth | SLA Tests (< 1000ms) | AJV JSON Schema Validation | Token Chaining")
|
|
922
922
|
|
|
923
923
|
# Optional Companion Environment
|
|
924
924
|
env_file = args.env_output or (os.path.splitext(target_output)[0] + "_env.json")
|
package/tools/flaky_detector.py
CHANGED
|
@@ -213,7 +213,7 @@ def benchmark_live_command(cmd: str, iterations: int = 5, test_id: Optional[str]
|
|
|
213
213
|
print(f" Recommendation : {RED}{BOLD}[QUARANTINE REQUIRED]{RESET} Tag with @quarantine immediately!")
|
|
214
214
|
else:
|
|
215
215
|
print(f" Recommendation : {GREEN}[SUITE APPROVED]{RESET} Test is deterministic.")
|
|
216
|
-
print(
|
|
216
|
+
print("======================================================================\n")
|
|
217
217
|
|
|
218
218
|
# Persist Report
|
|
219
219
|
os.makedirs(REPORTS_DIR, exist_ok=True)
|
|
@@ -334,7 +334,7 @@ def main():
|
|
|
334
334
|
if args.isolate:
|
|
335
335
|
auto_quarantine_test_file(c["test_id"], target_dir=args.target_dir)
|
|
336
336
|
|
|
337
|
-
print(
|
|
337
|
+
print("======================================================================")
|
|
338
338
|
print(f"Quarantine Recommendations: {quarantined_count}/{len(candidates)}")
|
|
339
339
|
sys.exit(0)
|
|
340
340
|
|
package/tools/fuzz_payloads.py
CHANGED
|
@@ -164,9 +164,9 @@ def main():
|
|
|
164
164
|
if args.json:
|
|
165
165
|
print(json.dumps(scenarios, indent=2, ensure_ascii=False))
|
|
166
166
|
else:
|
|
167
|
-
print(
|
|
167
|
+
print("\n======================================================================")
|
|
168
168
|
print(f" EDGE-CASE FUZZING SCENARIOS FOR FIELD: '{args.field}' ({args.type.upper()})")
|
|
169
|
-
print(
|
|
169
|
+
print("======================================================================")
|
|
170
170
|
for idx, sc in enumerate(scenarios, start=1):
|
|
171
171
|
val_disp = repr(sc['value']) if len(str(sc['value'])) < 60 else repr(str(sc['value'])[:50] + "...")
|
|
172
172
|
print(f" [{idx:02d}] {sc['scenario']:<45} | Expected: HTTP {sc['expected']} | Value: {val_disp}")
|
|
@@ -177,9 +177,9 @@ def main():
|
|
|
177
177
|
if args.json:
|
|
178
178
|
print(json.dumps(catalog, indent=2, ensure_ascii=False))
|
|
179
179
|
else:
|
|
180
|
-
print(
|
|
181
|
-
print(
|
|
182
|
-
print(
|
|
180
|
+
print("\n======================================================================")
|
|
181
|
+
print(" ENTERPRISE EDGE-CASE FUZZING & SANITIZATION CATALOG ")
|
|
182
|
+
print("======================================================================")
|
|
183
183
|
total_payloads = sum(len(items) for items in catalog.values())
|
|
184
184
|
print(f"Categories: {len(catalog)} | Total Payloads: {total_payloads}\n")
|
|
185
185
|
|
|
@@ -41,7 +41,7 @@ def generate_allure_report(results_dir=None, report_dir=None, clean=True, open_b
|
|
|
41
41
|
|
|
42
42
|
print(f"[+] Generating Allure HTML report from '{results_path}' to '{report_path}'...")
|
|
43
43
|
try:
|
|
44
|
-
|
|
44
|
+
subprocess.run(cmd, check=True)
|
|
45
45
|
print(f"[+] Report generated successfully at: {report_path}")
|
|
46
46
|
except subprocess.CalledProcessError as e:
|
|
47
47
|
print(f"[ERROR] Allure report generation failed with exit code {e.returncode}")
|
|
@@ -125,7 +125,7 @@ def build_jmx_xml(
|
|
|
125
125
|
kafka_config: Optional[Dict[str, Any]] = None
|
|
126
126
|
) -> str:
|
|
127
127
|
"""Builds valid, production-grade Apache JMeter 5.x XML Test Plan."""
|
|
128
|
-
|
|
128
|
+
|
|
129
129
|
loops_str = "-1" if loop_count < 0 else str(loop_count)
|
|
130
130
|
continue_forever = "true" if loop_count < 0 else "false"
|
|
131
131
|
|
|
@@ -960,7 +960,7 @@ def parse_openapi_for_jmeter(spec_data: Dict[str, Any]) -> tuple:
|
|
|
960
960
|
for p_name, p_schema in props.items():
|
|
961
961
|
ptype = p_schema.get("type", "string")
|
|
962
962
|
if ptype == "string":
|
|
963
|
-
sample_payload[p_name] = p_schema.get("example") or
|
|
963
|
+
sample_payload[p_name] = p_schema.get("example") or "PERF_AUTO_${__time(HMS,)}"
|
|
964
964
|
elif ptype in ("integer", "number"):
|
|
965
965
|
sample_payload[p_name] = p_schema.get("example") or 100
|
|
966
966
|
elif ptype == "boolean":
|
|
@@ -1075,7 +1075,7 @@ def parse_har_for_jmeter(har_data: Dict[str, Any]) -> tuple:
|
|
|
1075
1075
|
url = req.get("url", "/")
|
|
1076
1076
|
p = urlparse(url)
|
|
1077
1077
|
method = req.get("method", "GET").upper()
|
|
1078
|
-
|
|
1078
|
+
|
|
1079
1079
|
post_data = req.get("postData", {})
|
|
1080
1080
|
body_str = post_data.get("text", "")
|
|
1081
1081
|
status_code = e.get("response", {}).get("status", 200)
|
|
@@ -1107,7 +1107,7 @@ def main():
|
|
|
1107
1107
|
parser.add_argument("--output", "-o", help="Target output JMeter test plan file path (.jmx).")
|
|
1108
1108
|
parser.add_argument("--emit-locust", dest="emit_locust", help="Also/instead emit a runnable Locust locustfile.py (code-as-code load model).")
|
|
1109
1109
|
parser.add_argument("--sample", action="store_true", help="Generate a sample load test plan using built-in enterprise schema.")
|
|
1110
|
-
|
|
1110
|
+
|
|
1111
1111
|
# Advanced JMeter 360° Extensions
|
|
1112
1112
|
parser.add_argument("--no-cookies", action="store_true", help="Disable automatic HTTP Cookie Manager.")
|
|
1113
1113
|
parser.add_argument("--no-cache", action="store_true", help="Disable automatic HTTP Cache Manager.")
|
package/tools/gherkin_suite.py
CHANGED
|
@@ -649,7 +649,7 @@ def compile_feature(path: str) -> Dict[str, Any]:
|
|
|
649
649
|
errors = list(parser.errors)
|
|
650
650
|
|
|
651
651
|
cases = expand_feature(feature, errors)
|
|
652
|
-
|
|
652
|
+
sum(len(c["errors"]) for c in cases) + len(errors)
|
|
653
653
|
gaps = analyze_gaps(feature, cases)
|
|
654
654
|
|
|
655
655
|
mapped = {c["id"]: [map_step_to_ir(s).to_dict() for s in c["steps"]] for c in cases}
|
|
@@ -94,7 +94,7 @@ def main():
|
|
|
94
94
|
if not git_dir or not os.path.isdir(git_dir):
|
|
95
95
|
print("❌ Error: Not a git repository.")
|
|
96
96
|
sys.exit(1)
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
# 1. Pre-commit hook
|
|
99
99
|
pre_commit_path = os.path.join(git_dir, "hooks", "pre-commit")
|
|
100
100
|
pre_commit_content = (
|
|
@@ -134,7 +134,7 @@ def main():
|
|
|
134
134
|
fp.write(pre_push_content)
|
|
135
135
|
os.chmod(pre_push_path, 0o755)
|
|
136
136
|
|
|
137
|
-
print(
|
|
137
|
+
print("✅ Git hooks installed successfully:")
|
|
138
138
|
print(f" - Pre-commit: {pre_commit_path}")
|
|
139
139
|
print(f" - Post-merge: {post_merge_path} (Auto-runs cache_resolver.py init on git pull)")
|
|
140
140
|
print(f" - Pre-push: {pre_push_path} (Auto-runs cache_resolver.py sync on git push)")
|
package/tools/goldfish_review.py
CHANGED
|
@@ -108,8 +108,8 @@ def evaluate_spec_content(content: str, filename: str = "") -> dict:
|
|
|
108
108
|
findings = []
|
|
109
109
|
vector_scores = {}
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
[line.strip() for line in content.splitlines() if line.strip()]
|
|
112
|
+
content.lower()
|
|
113
113
|
|
|
114
114
|
# -------------------------------------------------------------
|
|
115
115
|
# Vector 1: Comprehension & System Boundaries (Weight: 20)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Synapse Hub — Self-Healing & Autonomous Repair Engines (tools.heal)
|
|
3
|
+
-------------------------------------------------------------------
|
|
4
|
+
Domain package grouping all locator repair, AOM semantic diffing,
|
|
5
|
+
episodic Reflexion memory, and agentless patching tools.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from tools import (
|
|
9
|
+
agentless_patcher,
|
|
10
|
+
auto_heal,
|
|
11
|
+
auto_heal_reflexion,
|
|
12
|
+
cache_resolver,
|
|
13
|
+
ci_healing_hook,
|
|
14
|
+
heal_pr_automator,
|
|
15
|
+
heuristic_locator_matcher,
|
|
16
|
+
reversible_sandbox,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"agentless_patcher",
|
|
21
|
+
"auto_heal",
|
|
22
|
+
"auto_heal_reflexion",
|
|
23
|
+
"cache_resolver",
|
|
24
|
+
"ci_healing_hook",
|
|
25
|
+
"heal_pr_automator",
|
|
26
|
+
"heuristic_locator_matcher",
|
|
27
|
+
"reversible_sandbox",
|
|
28
|
+
]
|
|
@@ -114,8 +114,8 @@ def generate_pr_draft(
|
|
|
114
114
|
|
|
115
115
|
pr_body = fr"""### 🛡️ Automated Self-Healing Verification PR
|
|
116
116
|
|
|
117
|
-
> **Origin:** Enterprise Autonomous QA Sentinel
|
|
118
|
-
> **Confidence Rating:** `{confidence_info['confidence_percentage']}%` ({confidence_info['risk_tier']})
|
|
117
|
+
> **Origin:** Enterprise Autonomous QA Sentinel
|
|
118
|
+
> **Confidence Rating:** `{confidence_info['confidence_percentage']}%` ({confidence_info['risk_tier']})
|
|
119
119
|
> **Zero Silent Drift Guarantee:** This change was isolated to prevent undocumented modifications on `main`.
|
|
120
120
|
|
|
121
121
|
---
|
|
@@ -183,9 +183,9 @@ def main():
|
|
|
183
183
|
if args.json:
|
|
184
184
|
print(json.dumps(res, indent=2))
|
|
185
185
|
else:
|
|
186
|
-
print(
|
|
186
|
+
print("\n======================================================================")
|
|
187
187
|
print(f" CI/CD SILENT SELF-HEALING HOOK EXECUTED ({conf['risk_tier']})")
|
|
188
|
-
print(
|
|
188
|
+
print("======================================================================")
|
|
189
189
|
print(f" Branch Name : {res['branch']}")
|
|
190
190
|
print(f" PR Title : {res['pr_title']}")
|
|
191
191
|
print(f" Confidence : {conf['confidence_percentage']}%")
|
|
@@ -205,9 +205,9 @@ def main():
|
|
|
205
205
|
if args.json:
|
|
206
206
|
print(json.dumps({"confidence": conf, "pr": pr}, indent=2))
|
|
207
207
|
else:
|
|
208
|
-
print(
|
|
208
|
+
print("\n======================================================================")
|
|
209
209
|
print(f" SELF-HEALING CONFIDENCE SCORE: {conf['confidence_percentage']}% ({conf['risk_tier']})")
|
|
210
|
-
print(
|
|
210
|
+
print("======================================================================")
|
|
211
211
|
print(f" Branch Name : {pr['branch_name']}")
|
|
212
212
|
print(f" PR Title : {pr['pr_title']}")
|
|
213
213
|
print(f" Qualified : {'✅ YES (Auto-PR Recommended)' if conf['qualified_for_pr'] else '⚠️ NO (Manual QA Review Needed)'}\n")
|
|
@@ -75,7 +75,7 @@ class HeuristicLocatorMatcher:
|
|
|
75
75
|
("left", "top", "width", "height"),
|
|
76
76
|
):
|
|
77
77
|
if all(k in el for k in keys):
|
|
78
|
-
return
|
|
78
|
+
return (float(el[keys[0]]), float(el[keys[1]]), float(el[keys[2]]), float(el[keys[3]]))
|
|
79
79
|
bbox = el.get("bbox")
|
|
80
80
|
if isinstance(bbox, dict) and all(k in bbox for k in ("x", "y", "width", "height")):
|
|
81
81
|
return (float(bbox["x"]), float(bbox["y"]),
|
|
@@ -270,7 +270,7 @@ class HeuristicLocatorMatcher:
|
|
|
270
270
|
]
|
|
271
271
|
candidates = [el for el in candidates if cls._get_bbox(el) is not None]
|
|
272
272
|
if candidates:
|
|
273
|
-
target = min(candidates, key=lambda el: cls._center_distance(cls._get_bbox(el), anchor_bbox))
|
|
273
|
+
target = min(candidates, key=lambda el: cls._center_distance(cls._get_bbox(el) or (0.0, 0.0, 0.0, 0.0), anchor_bbox))
|
|
274
274
|
if target.get("id"):
|
|
275
275
|
healed = f"#{target['id']}"
|
|
276
276
|
elif target.get("data-testid") or target.get("test-id"):
|
package/tools/infer_linter.py
CHANGED
|
@@ -44,7 +44,7 @@ RESET = "\033[0m"
|
|
|
44
44
|
|
|
45
45
|
# Optional Tree-Sitter Integration for compiler-grade AST parsing across TS/Java/C#
|
|
46
46
|
try:
|
|
47
|
-
import tree_sitter # type: ignore
|
|
47
|
+
import tree_sitter # type: ignore # noqa: F401
|
|
48
48
|
HAS_TREE_SITTER = True
|
|
49
49
|
except ImportError:
|
|
50
50
|
HAS_TREE_SITTER = False
|
|
@@ -135,7 +135,7 @@ THREAD_SLEEP_PATTERN = MS_DELAY_PATTERN
|
|
|
135
135
|
|
|
136
136
|
def strip_comments_and_strings(source_code, preserve_newlines=True):
|
|
137
137
|
"""
|
|
138
|
-
Lexically strips block comments, line comments, multiline strings,
|
|
138
|
+
Lexically strips block comments, line comments, multiline strings,
|
|
139
139
|
and quoted literals while preserving line numbering.
|
|
140
140
|
Prevents false-positive / false-negative matches in AST linter.
|
|
141
141
|
"""
|
|
@@ -173,7 +173,7 @@ def _is_benign_empty_catch(clean_code, match_start):
|
|
|
173
173
|
if not stripped:
|
|
174
174
|
continue
|
|
175
175
|
if re.match(r'try\s*(?:[:{]|$)', stripped):
|
|
176
|
-
return bool(body_lines) and all(BENIGN_TRY_CALL_PATTERN.match(
|
|
176
|
+
return bool(body_lines) and all(BENIGN_TRY_CALL_PATTERN.match(bl) for bl in body_lines)
|
|
177
177
|
body_lines.append(stripped)
|
|
178
178
|
return False
|
|
179
179
|
|
|
@@ -185,7 +185,7 @@ def load_config(repo_root=None, config_path=None):
|
|
|
185
185
|
with open(config_path, "r", encoding="utf-8") as f:
|
|
186
186
|
return json.load(f)
|
|
187
187
|
except (OSError, json.JSONDecodeError):
|
|
188
|
-
|
|
188
|
+
_ = False
|
|
189
189
|
|
|
190
190
|
root = repo_root or REPO_ROOT
|
|
191
191
|
candidates = [
|
|
@@ -371,7 +371,7 @@ def analyze_script(file_path, baseline_set=None, disabled_rules=None):
|
|
|
371
371
|
|
|
372
372
|
# Lexically strip comments and string literals for accurate analysis
|
|
373
373
|
clean_code = strip_comments_and_strings(content, preserve_newlines=True)
|
|
374
|
-
clean_lines = [
|
|
374
|
+
clean_lines = [line.strip() for line in clean_code.splitlines()]
|
|
375
375
|
|
|
376
376
|
raw_lines = content.splitlines()
|
|
377
377
|
|
|
@@ -447,8 +447,8 @@ def analyze_script(file_path, baseline_set=None, disabled_rules=None):
|
|
|
447
447
|
# Test Script Specific Invariants (Do NOT apply to Page Objects or Helpers)
|
|
448
448
|
if role == "TEST_SCRIPT":
|
|
449
449
|
executable_lines = [
|
|
450
|
-
|
|
451
|
-
if
|
|
450
|
+
line for line in clean_lines
|
|
451
|
+
if line and not line.startswith("import ") and not line.startswith("package ") and not line.startswith("from ")
|
|
452
452
|
]
|
|
453
453
|
# Rule 1: Empty Test Script
|
|
454
454
|
if len(executable_lines) == 0:
|
|
@@ -740,7 +740,7 @@ def run_infer_linter(target_dir=None, output_path=None, strict: bool = False,
|
|
|
740
740
|
print(f" Target Scope : 0 Test Scripts discovered in {os.path.basename(scan_dir)}/ (No tests found)")
|
|
741
741
|
print(f" Critical Defects : {GREEN}0{RESET}")
|
|
742
742
|
print(f" Warnings / Suggestions : {GREEN}0{RESET}")
|
|
743
|
-
print(
|
|
743
|
+
print(" Clean Scripts : 0/0 (N/A)")
|
|
744
744
|
print(f" Codebase Health Score : {YELLOW}N/A (0 test scripts discovered){RESET}")
|
|
745
745
|
else:
|
|
746
746
|
print(f" Target Scope : {total_scripts} Test Scripts in {os.path.basename(scan_dir)}/ (Polyglot)")
|