synapse-hub 0.0.0-stage → 2.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 +47 -0
- package/.agents/agents/live-explorer.md +41 -0
- package/.agents/agents/orchestrator.md +43 -0
- package/.agents/agents/pom-crafter.md +81 -0
- package/.agents/agents/quality-gatekeeper.md +47 -0
- package/.agents/agents/test-runner.md +43 -0
- package/.agents/rules/context-engineering.md +88 -0
- package/.agents/rules/git-push-protocol.md +101 -0
- package/.agents/rules/graphify.md +14 -0
- package/.agents/rules/lessons-learned.md +23 -0
- package/.agents/rules/mandatory-playwright-live.md +29 -0
- package/.agents/rules/skill-precedence.md +42 -0
- package/.agents/rules/systematic-debugging.md +99 -0
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +239 -0
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +64 -0
- package/.agents/skills/api-network-mocking-interception/SKILL.md +142 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +70 -0
- package/.agents/skills/architecture-decision-records/SKILL.md +441 -0
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +72 -0
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +214 -0
- package/.agents/skills/bats-testing-patterns/SKILL.md +100 -0
- package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -0
- package/.agents/skills/clean-pom-crafter/SKILL.md +75 -0
- package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -0
- package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -0
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +64 -0
- package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +879 -0
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +123 -0
- package/.agents/skills/e2e-testing-patterns/SKILL.md +103 -0
- package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -0
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +54 -0
- package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -0
- package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -0
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -0
- package/.agents/skills/file-and-report-validator/SKILL.md +90 -0
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +114 -0
- package/.agents/skills/github-actions-templates/SKILL.md +334 -0
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +78 -0
- package/.agents/skills/katalon-cli-runner/SKILL.md +76 -0
- package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -0
- package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -0
- package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -0
- package/.agents/skills/lam-script-generator/SKILL.md +83 -0
- package/.agents/skills/playwright-live-interaction/SKILL.md +86 -0
- package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -0
- package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -0
- package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -0
- package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -0
- package/.agents/skills/security-dast-sentinel/SKILL.md +176 -0
- package/.agents/skills/self-healing-locator/SKILL.md +73 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +135 -0
- package/.agents/skills/token-and-context-optimizer/SKILL.md +70 -0
- package/.agents/skills/visual-regression-sentinel/SKILL.md +62 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +77 -0
- package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -0
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +65 -0
- package/.agents/templates/completion_report.md +57 -0
- package/.agents/workflows/graphify.md +10 -0
- package/.agents/workflows/test-impact.md +15 -0
- package/.agents/workflows/verify.md +34 -0
- package/AGENTS.md +115 -0
- package/README.md +394 -2
- package/bin/agent-qa.js +90 -0
- package/package.json +59 -4
- package/skills_manifest.json +262 -0
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +97 -0
- package/templates/PROJECT_STATE_TEMPLATE.md +20 -0
- package/tools/TOOLS_RUNBOOK.md +298 -0
- package/tools/agent_canary.py +501 -0
- package/tools/analyze_project.py +242 -0
- package/tools/api_drift_sentinel.py +208 -0
- package/tools/auto_heal.py +189 -0
- package/tools/browser_helpers.js +593 -0
- package/tools/bug_reporter.py +224 -0
- package/tools/cache_resolver.py +846 -0
- package/tools/check_env.py +288 -0
- package/tools/ci_healing_hook.py +128 -0
- package/tools/cli.py +402 -0
- package/tools/config_decoupler.py +253 -0
- package/tools/db_sandbox.py +341 -0
- package/tools/distill_log.py +152 -0
- package/tools/e2e_flow_prober.ps1 +65 -0
- package/tools/flaky_detector.py +222 -0
- package/tools/fuzz_payloads.py +197 -0
- package/tools/generate_allure_report.ps1 +30 -0
- package/tools/generate_api_matrix.py +193 -0
- package/tools/generate_tc_from_story.py +182 -0
- package/tools/generate_test_reports.ps1 +135 -0
- package/tools/git_pre_commit_guard.py +203 -0
- package/tools/heal_pr_automator.py +195 -0
- package/tools/heuristic_locator_matcher.py +202 -0
- package/tools/infer_linter.py +238 -0
- package/tools/lint_ai_docs.py +254 -0
- package/tools/lookup_tc.py +165 -0
- package/tools/lookup_xpath.py +167 -0
- package/tools/matrix_sharder.py +146 -0
- package/tools/mcp_excel_server.ps1 +251 -0
- package/tools/mcp_server.py +490 -0
- package/tools/micro_mutator.py +309 -0
- package/tools/network_mock_shield.py +170 -0
- package/tools/omni_emitter.py +500 -0
- package/tools/omni_parser.py +435 -0
- package/tools/omni_transpiler.py +168 -0
- package/tools/pinpoint_failure.py +88 -0
- package/tools/project_state.py +169 -0
- package/tools/repo_map.json +12 -0
- package/tools/repo_map.py +277 -0
- package/tools/repo_map.txt +7 -0
- package/tools/safety_firewall.py +245 -0
- package/tools/setup_project.py +265 -0
- package/tools/smart_visual_masker.py +134 -0
- package/tools/test_impact.py +321 -0
- package/tools/tms_sync_bridge.py +342 -0
- package/tools/trace_data_flow.py +240 -0
- package/tools/traffic_session_converter.py +256 -0
- package/tools/universal_ir.py +480 -0
- package/tools/validate_groovy_scripts.py +173 -0
- package/tools/verify_all.py +496 -0
- package/tools/verify_all.sh +4 -0
- package/tools/visual_diff_canvas.py +142 -0
- package/tools/visual_som_helper.py +418 -0
- package/tools/wcag_audit.py +214 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Enterprise Action Safety Firewall & Blast Radius Containment Engine
|
|
4
|
+
--------------------------------------------------------------------
|
|
5
|
+
Prevents AI agents and test runners from executing catastrophic or irreversible
|
|
6
|
+
actions on Live/Staging/UAT environments (e.g. Delete All, Purge, Wipe, Pay, Drop).
|
|
7
|
+
|
|
8
|
+
Supports:
|
|
9
|
+
- Direct action checking (--action, --text, --selector)
|
|
10
|
+
- Code snippet and script inspection (--code, --file)
|
|
11
|
+
- Pipeline stdin stream inspection (e.g. echo 'page.click(...)' | python safety_firewall.py --dry-run)
|
|
12
|
+
- Pre-execution validation hooks before Playwright/CDP browser dispatch
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
import json
|
|
19
|
+
import argparse
|
|
20
|
+
from typing import List, Dict, Any, Tuple
|
|
21
|
+
|
|
22
|
+
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
23
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
24
|
+
try:
|
|
25
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
26
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
27
|
+
except Exception:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
DANGEROUS_ACTION_KEYWORDS = [
|
|
31
|
+
r"\bdelete\b",
|
|
32
|
+
r"\bremove\b",
|
|
33
|
+
r"\bpurge\b",
|
|
34
|
+
r"\bwipe\b",
|
|
35
|
+
r"\bdrop\b",
|
|
36
|
+
r"\bpay\b",
|
|
37
|
+
r"\bpurchase\b",
|
|
38
|
+
r"\bcheckout\b",
|
|
39
|
+
r"\bterminate\b",
|
|
40
|
+
r"\bdestroy\b",
|
|
41
|
+
r"\breset\b",
|
|
42
|
+
r"\btransfer\b",
|
|
43
|
+
r"\bformat\b",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
DANGEROUS_SELECTOR_PATTERNS = [
|
|
47
|
+
r"\[data-action\*=['\"]?delete['\"]?\]",
|
|
48
|
+
r"\[data-action\*=['\"]?pay['\"]?\]",
|
|
49
|
+
r"\.btn-danger\b",
|
|
50
|
+
r"\.p-button-danger\b",
|
|
51
|
+
r"\[aria-label\*=['\"]?delete['\"]?\]",
|
|
52
|
+
r"button[#\.\-_a-zA-Z0-9]*[_\-]*(?:del|delete|purge|remove|wipe|drop)",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
COMPILED_TEXT_REGEX = re.compile("|".join(DANGEROUS_ACTION_KEYWORDS), re.IGNORECASE)
|
|
56
|
+
COMPILED_SEL_REGEX = re.compile("|".join(DANGEROUS_SELECTOR_PATTERNS), re.IGNORECASE)
|
|
57
|
+
|
|
58
|
+
# Pattern to extract interaction statements from Playwright / Selenium / Groovy
|
|
59
|
+
INTERACTION_CODE_PATTERNS = [
|
|
60
|
+
# page.click('selector'), page.fill('selector', 'val')
|
|
61
|
+
re.compile(r"""(?:page|frame)\.(?P<act>click|dblclick|fill|type|press|check)\s*\(\s*['"](?P<sel>[^'"]+)['"](?:\s*,\s*['"](?P<text>[^'"]*)['"])?""", re.IGNORECASE),
|
|
62
|
+
# page.locator('selector').click()
|
|
63
|
+
re.compile(r"""(?:page|frame)\.locator\s*\(\s*['"](?P<sel>[^'"]+)['"]\s*\)\.(?P<act>click|fill|press)""", re.IGNORECASE),
|
|
64
|
+
# page.getByText('text').click()
|
|
65
|
+
re.compile(r"""(?:page|frame)\.getByText\s*\(\s*['"](?P<text>[^'"]+)['"]\s*\)\.(?P<act>click)""", re.IGNORECASE),
|
|
66
|
+
# page.getByRole('button', { name: 'Delete' }).click()
|
|
67
|
+
re.compile(r"""(?:page|frame)\.getByRole\s*\(\s*['"](?P<role>[^'"]+)['"]\s*,\s*\{[^}]*name:\s*['"](?P<text>[^'"]+)['"]""", re.IGNORECASE),
|
|
68
|
+
# WebUI.click(findTestObject(...)) or WebUI.clickOffset
|
|
69
|
+
re.compile(r"""WebUI\.(?P<act>click|setText)\s*\([^)]*['"](?P<sel>[^'"]*)['"]""", re.IGNORECASE),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def is_action_dangerous(action_type: str, text: str = "", selector: str = "") -> Tuple[bool, str]:
|
|
74
|
+
"""
|
|
75
|
+
Evaluates whether an intended action touches a dangerous or irreversible UI element.
|
|
76
|
+
Returns (is_dangerous: bool, reason: str).
|
|
77
|
+
"""
|
|
78
|
+
text_clean = text.strip() if text else ""
|
|
79
|
+
sel_clean = selector.strip() if selector else ""
|
|
80
|
+
|
|
81
|
+
# Check text content
|
|
82
|
+
if text_clean and COMPILED_TEXT_REGEX.search(text_clean):
|
|
83
|
+
match = COMPILED_TEXT_REGEX.search(text_clean).group(0)
|
|
84
|
+
return True, f"Element text contains dangerous keyword: '{match}'"
|
|
85
|
+
|
|
86
|
+
# Check selector
|
|
87
|
+
if sel_clean and COMPILED_SEL_REGEX.search(sel_clean):
|
|
88
|
+
return True, f"Element selector matches destructive pattern: '{sel_clean}'"
|
|
89
|
+
|
|
90
|
+
# Specific dangerous action types
|
|
91
|
+
if action_type.lower() in ("drop_database", "wipe_environment", "execute_payment"):
|
|
92
|
+
return True, f"Action type '{action_type}' is classified as irreversible"
|
|
93
|
+
|
|
94
|
+
return False, "Action passed safety firewall inspection"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def inspect_and_authorize(action_type: str, text: str = "", selector: str = "", force: bool = False) -> Dict[str, Any]:
|
|
98
|
+
"""
|
|
99
|
+
Evaluates action and checks authorization policy.
|
|
100
|
+
Bypass is permitted only if force=True or ALLOW_DESTRUCTIVE=1 environment variable is set.
|
|
101
|
+
"""
|
|
102
|
+
dangerous, reason = is_action_dangerous(action_type, text, selector)
|
|
103
|
+
bypass = force or os.environ.get("ALLOW_DESTRUCTIVE", "").lower() in ("true", "1", "yes")
|
|
104
|
+
|
|
105
|
+
result = {
|
|
106
|
+
"action": action_type,
|
|
107
|
+
"text": text,
|
|
108
|
+
"selector": selector,
|
|
109
|
+
"dangerous": dangerous,
|
|
110
|
+
"reason": reason,
|
|
111
|
+
"authorized": (not dangerous) or bypass,
|
|
112
|
+
"bypass_granted": bypass if dangerous else False
|
|
113
|
+
}
|
|
114
|
+
return result
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def inspect_code_snippet(code: str, force: bool = False) -> List[Dict[str, Any]]:
|
|
118
|
+
"""
|
|
119
|
+
Parses a code snippet or script and checks all discovered UI interaction statements.
|
|
120
|
+
"""
|
|
121
|
+
inspections = []
|
|
122
|
+
lines = code.splitlines()
|
|
123
|
+
|
|
124
|
+
for line_idx, line in enumerate(lines, start=1):
|
|
125
|
+
stripped = line.strip()
|
|
126
|
+
if not stripped or stripped.startswith(("//", "#", "/*", "*")):
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
matched = False
|
|
130
|
+
for pattern in INTERACTION_CODE_PATTERNS:
|
|
131
|
+
for match in pattern.finditer(line):
|
|
132
|
+
matched = True
|
|
133
|
+
groups = match.groupdict()
|
|
134
|
+
act = groups.get("act") or "click"
|
|
135
|
+
sel = groups.get("sel") or ""
|
|
136
|
+
text = groups.get("text") or ""
|
|
137
|
+
auth = inspect_and_authorize(act, text=text, selector=sel, force=force)
|
|
138
|
+
auth["line"] = line_idx
|
|
139
|
+
auth["code"] = stripped
|
|
140
|
+
inspections.append(auth)
|
|
141
|
+
|
|
142
|
+
# Fallback: line itself contains dangerous keyword and an action verb
|
|
143
|
+
if not matched and COMPILED_TEXT_REGEX.search(stripped) and any(w in stripped.lower() for w in ("click", "press", "submit", "post", "delete")):
|
|
144
|
+
auth = inspect_and_authorize("dispatch", text=stripped, selector="", force=force)
|
|
145
|
+
auth["line"] = line_idx
|
|
146
|
+
auth["code"] = stripped
|
|
147
|
+
inspections.append(auth)
|
|
148
|
+
|
|
149
|
+
return inspections
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def main():
|
|
153
|
+
parser = argparse.ArgumentParser(description="Action Safety Firewall & Blast Radius Containment Engine")
|
|
154
|
+
parser.add_argument("--action", default="click", help="Action type (click, fill, submit)")
|
|
155
|
+
parser.add_argument("--text", default="", help="Text content of target element")
|
|
156
|
+
parser.add_argument("--selector", default="", help="CSS/XPath selector of target element")
|
|
157
|
+
parser.add_argument("--code", default="", help="Direct code snippet or command line to evaluate (use '-' for stdin)")
|
|
158
|
+
parser.add_argument("--file", default="", help="Path to script file to evaluate")
|
|
159
|
+
parser.add_argument("--stdin", action="store_true", help="Read code or action from standard input")
|
|
160
|
+
parser.add_argument("--dry-run", action="store_true", help="Execute in pre-execution dry-run mode (outputs validation report)")
|
|
161
|
+
parser.add_argument("--force", action="store_true", help="Force bypass safety firewall")
|
|
162
|
+
parser.add_argument("--json", action="store_true", help="Output results in JSON format")
|
|
163
|
+
args = parser.parse_args()
|
|
164
|
+
|
|
165
|
+
# Determine input source: explicit stdin flag/arg, --file, --code, or parameters
|
|
166
|
+
code_content = ""
|
|
167
|
+
if args.code and args.code != "-":
|
|
168
|
+
code_content = args.code
|
|
169
|
+
elif args.file and os.path.exists(args.file):
|
|
170
|
+
with open(args.file, "r", encoding="utf-8", errors="ignore") as f:
|
|
171
|
+
code_content = f.read()
|
|
172
|
+
elif args.stdin or args.code == "-":
|
|
173
|
+
try:
|
|
174
|
+
stdin_data = sys.stdin.read().strip()
|
|
175
|
+
if stdin_data:
|
|
176
|
+
# Check if stdin is JSON
|
|
177
|
+
if stdin_data.startswith("{") and stdin_data.endswith("}"):
|
|
178
|
+
try:
|
|
179
|
+
parsed = json.loads(stdin_data)
|
|
180
|
+
if "action" in parsed or "selector" in parsed:
|
|
181
|
+
args.action = parsed.get("action", args.action)
|
|
182
|
+
args.text = parsed.get("text", args.text)
|
|
183
|
+
args.selector = parsed.get("selector", args.selector)
|
|
184
|
+
else:
|
|
185
|
+
code_content = stdin_data
|
|
186
|
+
except Exception:
|
|
187
|
+
code_content = stdin_data
|
|
188
|
+
else:
|
|
189
|
+
code_content = stdin_data
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
# Evaluation
|
|
194
|
+
if code_content:
|
|
195
|
+
inspections = inspect_code_snippet(code_content, force=args.force)
|
|
196
|
+
has_blocked = any(not item["authorized"] for item in inspections)
|
|
197
|
+
all_authorized = not has_blocked
|
|
198
|
+
|
|
199
|
+
report = {
|
|
200
|
+
"mode": "dry-run" if args.dry_run else "enforce",
|
|
201
|
+
"total_inspected": len(inspections),
|
|
202
|
+
"authorized": all_authorized,
|
|
203
|
+
"blocked_count": sum(1 for item in inspections if not item["authorized"]),
|
|
204
|
+
"inspections": inspections
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if args.json:
|
|
208
|
+
print(json.dumps(report, indent=2))
|
|
209
|
+
else:
|
|
210
|
+
tag = "DRY-RUN INSPECTION" if args.dry_run else "FIREWALL SENTINEL"
|
|
211
|
+
print(f"\n🛡️ [{tag}] Inspected {len(inspections)} interaction statements:")
|
|
212
|
+
for item in inspections:
|
|
213
|
+
status = "[PASS]" if item["authorized"] else "[BLOCKED]"
|
|
214
|
+
line_info = f"Line {item.get('line', '?')}: " if 'line' in item else ""
|
|
215
|
+
print(f" {status} {line_info}{item['action']} -> {item['reason']}")
|
|
216
|
+
if not item["authorized"]:
|
|
217
|
+
print(f" Code: {item.get('code', '')}")
|
|
218
|
+
|
|
219
|
+
if all_authorized:
|
|
220
|
+
print(f"\n✅ All actions cleared by Safety Firewall{' (Dry-Run)' if args.dry_run else ''}.\n")
|
|
221
|
+
else:
|
|
222
|
+
print(f"\n❌ Pre-execution Blocked: Destructive actions intercepted without --force or ALLOW_DESTRUCTIVE=1.\n")
|
|
223
|
+
|
|
224
|
+
return 0 if all_authorized else 1
|
|
225
|
+
|
|
226
|
+
# Single-action check (legacy / parameter mode)
|
|
227
|
+
auth = inspect_and_authorize(args.action, args.text, args.selector, args.force)
|
|
228
|
+
auth["mode"] = "dry-run" if args.dry_run else "enforce"
|
|
229
|
+
|
|
230
|
+
if args.json:
|
|
231
|
+
print(json.dumps(auth, indent=2))
|
|
232
|
+
else:
|
|
233
|
+
prefix = "[DRY-RUN] " if args.dry_run else ""
|
|
234
|
+
if auth["authorized"]:
|
|
235
|
+
status_tag = f"{prefix}[PASS] Action Authorized" if not auth["dangerous"] else f"{prefix}[BYPASS] Destructive Action Force-Authorized"
|
|
236
|
+
print(f"🛡️ {status_tag}: {auth['reason']}")
|
|
237
|
+
else:
|
|
238
|
+
print(f"❌ {prefix}[BLOCKED] Action Safety Firewall Intercepted: {auth['reason']}")
|
|
239
|
+
print(" 👉 To authorize, pass --force or set ALLOW_DESTRUCTIVE=1.")
|
|
240
|
+
|
|
241
|
+
return 0 if auth["authorized"] else 1
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
if __name__ == "__main__":
|
|
245
|
+
sys.exit(main())
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Enterprise QA Autonomous Project Setup & Bootstrap Engine
|
|
4
|
+
----------------------------------------------------------
|
|
5
|
+
Transforms any target test project into an AI-Ready Enterprise Suite in 1-click.
|
|
6
|
+
Dispatched automatically by AI Agents (Zero-Command Protocol) or via 'agent-qa setup'.
|
|
7
|
+
|
|
8
|
+
Automated actions:
|
|
9
|
+
1. Detects project tech stack (Katalon, Playwright, Cypress, Pytest).
|
|
10
|
+
2. Generates tailored 'PROJECT_AI_GUIDE.md' if missing.
|
|
11
|
+
3. Installs automated Git Pre-Push Hook (.git/hooks/pre-push) for zero-leakage protection.
|
|
12
|
+
4. Triggers 5-in-1 Health Audit (tools/analyze_project.py):
|
|
13
|
+
- Maps AST method signatures (repo_map.txt).
|
|
14
|
+
- Scans fake passes (infer_linter).
|
|
15
|
+
- Audits database teardown hygiene (trace_data_flow).
|
|
16
|
+
- Generates in-project health report (PROJECT_HEALTH_REPORT.md).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import shutil
|
|
22
|
+
import argparse
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from datetime import datetime
|
|
25
|
+
|
|
26
|
+
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
27
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
28
|
+
try:
|
|
29
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
30
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
31
|
+
except Exception:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
35
|
+
HUB_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
|
|
36
|
+
TEMPLATES_DIR = os.path.join(HUB_ROOT, "templates")
|
|
37
|
+
sys.path.insert(0, SCRIPT_DIR)
|
|
38
|
+
|
|
39
|
+
import analyze_project
|
|
40
|
+
import verify_all
|
|
41
|
+
import project_state
|
|
42
|
+
|
|
43
|
+
GREEN = "\033[92m"
|
|
44
|
+
RED = "\033[91m"
|
|
45
|
+
YELLOW = "\033[93m"
|
|
46
|
+
CYAN = "\033[96m"
|
|
47
|
+
BOLD = "\033[1m"
|
|
48
|
+
RESET = "\033[0m"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def resolve_target_repo(cli_target=None, cli_repo=None):
|
|
52
|
+
chosen = cli_repo or cli_target
|
|
53
|
+
if chosen:
|
|
54
|
+
resolved = os.path.abspath(chosen)
|
|
55
|
+
if not os.path.exists(resolved):
|
|
56
|
+
raise FileNotFoundError(f"Target repository path not found: {resolved}")
|
|
57
|
+
return resolved
|
|
58
|
+
|
|
59
|
+
env_dir = os.environ.get("TARGET_PROJECT_DIR")
|
|
60
|
+
if env_dir and os.path.exists(env_dir):
|
|
61
|
+
return os.path.abspath(env_dir)
|
|
62
|
+
|
|
63
|
+
cwd = os.path.abspath(os.getcwd())
|
|
64
|
+
if cwd.lower() != HUB_ROOT.lower():
|
|
65
|
+
return cwd
|
|
66
|
+
|
|
67
|
+
return HUB_ROOT
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def detect_tech_stack(target_dir):
|
|
71
|
+
"""Detects target project automation framework and script language."""
|
|
72
|
+
target_path = Path(target_dir)
|
|
73
|
+
|
|
74
|
+
# 1. Katalon Studio check
|
|
75
|
+
prj_files = list(target_path.glob("*.prj"))
|
|
76
|
+
if prj_files or (target_path / "Keywords").is_dir() or (target_path / "Scripts").is_dir():
|
|
77
|
+
prj_name = prj_files[0].stem if prj_files else target_path.name
|
|
78
|
+
return {
|
|
79
|
+
"name": prj_name,
|
|
80
|
+
"framework": "Katalon Studio (Enterprise Web)",
|
|
81
|
+
"language": "Groovy",
|
|
82
|
+
"page_dir": "Keywords/pages",
|
|
83
|
+
"test_dir": "Scripts",
|
|
84
|
+
"run_cmd": "./tools/run_headless_kre.sh (or katalonc CLI)"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# 2. Playwright check
|
|
88
|
+
if (target_path / "playwright.config.ts").exists() or (target_path / "playwright.config.js").exists():
|
|
89
|
+
is_ts = (target_path / "playwright.config.ts").exists() or (target_path / "tsconfig.json").exists()
|
|
90
|
+
return {
|
|
91
|
+
"name": target_path.name,
|
|
92
|
+
"framework": "Playwright Test",
|
|
93
|
+
"language": "TypeScript" if is_ts else "JavaScript",
|
|
94
|
+
"page_dir": "src/pages" if (target_path / "src" / "pages").exists() else "pages",
|
|
95
|
+
"test_dir": "tests",
|
|
96
|
+
"run_cmd": "npx playwright test --project=chromium"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# 3. Cypress check
|
|
100
|
+
if (target_path / "cypress.config.ts").exists() or (target_path / "cypress.config.js").exists():
|
|
101
|
+
return {
|
|
102
|
+
"name": target_path.name,
|
|
103
|
+
"framework": "Cypress E2E",
|
|
104
|
+
"language": "TypeScript" if (target_path / "cypress.config.ts").exists() else "JavaScript",
|
|
105
|
+
"page_dir": "cypress/pages",
|
|
106
|
+
"test_dir": "cypress/e2e",
|
|
107
|
+
"run_cmd": "npx cypress run --headless"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# 4. Pytest check
|
|
111
|
+
if (target_path / "pytest.ini").exists() or list(target_path.glob("test_*.py")):
|
|
112
|
+
return {
|
|
113
|
+
"name": target_path.name,
|
|
114
|
+
"framework": "Pytest",
|
|
115
|
+
"language": "Python",
|
|
116
|
+
"page_dir": "pages",
|
|
117
|
+
"test_dir": "tests",
|
|
118
|
+
"run_cmd": "pytest tests/ -v"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Default fallback
|
|
122
|
+
return {
|
|
123
|
+
"name": target_path.name,
|
|
124
|
+
"framework": "Enterprise Test Automation",
|
|
125
|
+
"language": "TypeScript / Groovy",
|
|
126
|
+
"page_dir": "pages",
|
|
127
|
+
"test_dir": "tests",
|
|
128
|
+
"run_cmd": "npm test"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def ensure_project_ai_guide(target_dir, stack_info):
|
|
133
|
+
"""Generates PROJECT_AI_GUIDE.md tailored to the target project if missing."""
|
|
134
|
+
guide_path = os.path.join(target_dir, "PROJECT_AI_GUIDE.md")
|
|
135
|
+
if os.path.exists(guide_path):
|
|
136
|
+
print(f" [PASS] PROJECT_AI_GUIDE.md already exists: {guide_path}")
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
template_path = os.path.join(TEMPLATES_DIR, "PROJECT_AI_GUIDE_TEMPLATE.md")
|
|
140
|
+
content = ""
|
|
141
|
+
if os.path.exists(template_path):
|
|
142
|
+
with open(template_path, "r", encoding="utf-8") as f:
|
|
143
|
+
content = f.read()
|
|
144
|
+
else:
|
|
145
|
+
content = f"# PROJECT AI GUIDE: {stack_info['name']}\n\n## 1. General System Under Test\n"
|
|
146
|
+
|
|
147
|
+
# Populate template placeholders with detected project metadata
|
|
148
|
+
content = content.replace("[TARGET_PROJECT_NAME]", stack_info["name"])
|
|
149
|
+
content = content.replace("[e.g., Enterprise Billing Platform / Customer Portal]", stack_info["name"])
|
|
150
|
+
content = content.replace("[e.g., Playwright Test, Katalon Studio 10.x, Cypress, Selenium]", stack_info["framework"])
|
|
151
|
+
content = content.replace("[e.g., TypeScript, Groovy, Python, JavaScript]", stack_info["language"])
|
|
152
|
+
content = content.replace("src/pages/... or Keywords/pages/...", stack_info["page_dir"])
|
|
153
|
+
content = content.replace("tests/e2e/... or Scripts/...", stack_info["test_dir"])
|
|
154
|
+
content = content.replace("[e.g., npx playwright test --project=chromium --reporter=html,junit:results.xml]", stack_info["run_cmd"])
|
|
155
|
+
|
|
156
|
+
with open(guide_path, "w", encoding="utf-8") as f:
|
|
157
|
+
f.write(content)
|
|
158
|
+
|
|
159
|
+
print(f" {GREEN}[OK] Generated PROJECT_AI_GUIDE.md tailored to {stack_info['framework']}:{RESET} {guide_path}")
|
|
160
|
+
return True
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def install_git_pre_push_hook(target_dir):
|
|
164
|
+
"""Installs an automated pre-push hook into target project's .git repository."""
|
|
165
|
+
git_dir = os.path.join(target_dir, ".git")
|
|
166
|
+
if not os.path.isdir(git_dir):
|
|
167
|
+
print(" [SKIP] Not a Git repository root (.git folder not found). Skipped Git Hook installation.")
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
hooks_dir = os.path.join(git_dir, "hooks")
|
|
171
|
+
os.makedirs(hooks_dir, exist_ok=True)
|
|
172
|
+
hook_file = os.path.join(hooks_dir, "pre-push")
|
|
173
|
+
|
|
174
|
+
verify_all_script = os.path.join(SCRIPT_DIR, "verify_all.py").replace("\\", "/")
|
|
175
|
+
analyze_script = os.path.join(SCRIPT_DIR, "analyze_project.py").replace("\\", "/")
|
|
176
|
+
|
|
177
|
+
hook_content = f"""#!/bin/sh
|
|
178
|
+
# Automated Zero-Leakage Pre-Push Hook (Installed by Enterprise Agent-QA)
|
|
179
|
+
# Automatically enforces 7 Quality Gates and syncs project health before pushing.
|
|
180
|
+
|
|
181
|
+
echo "\\n[AI-GUARD] Running Pre-Push Verification (7 Quality Gates DoD)..."
|
|
182
|
+
python3 "{verify_all_script}" || python "{verify_all_script}"
|
|
183
|
+
if [ $? -ne 0 ]; then
|
|
184
|
+
echo "\\n[AI-GUARD - REJECTED] Git Push aborted: 7 Quality Gates verification failed!"
|
|
185
|
+
echo "Please fix defects or run 'agent-qa verify' locally before pushing.\\n"
|
|
186
|
+
exit 1
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
echo "[AI-GUARD - PASSED] 7 Quality Gates satisfied. Syncing project health report..."
|
|
190
|
+
python3 "{analyze_script}" || python "{analyze_script}"
|
|
191
|
+
echo "[AI-GUARD - READY] Pushing to remote...\\n"
|
|
192
|
+
exit 0
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
with open(hook_file, "w", encoding="utf-8", newline="\n") as f:
|
|
196
|
+
f.write(hook_content)
|
|
197
|
+
|
|
198
|
+
# Attempt to make executable on Unix-like environments
|
|
199
|
+
try:
|
|
200
|
+
os.chmod(hook_file, 0o755)
|
|
201
|
+
except Exception:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
print(f" {GREEN}[OK] Installed automated Pre-Push Git Hook:{RESET} {hook_file}")
|
|
205
|
+
return True
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print(f" ⚠️ Could not install Git hook: {e}")
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def run_setup(target_repo):
|
|
212
|
+
"""Main execution entrypoint for automated project setup."""
|
|
213
|
+
print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
|
|
214
|
+
print(f"{BOLD}{CYAN} AUTONOMOUS ZERO-COMMAND PROJECT BOOTSTRAP (AI-READY SETUP) {RESET}")
|
|
215
|
+
print(f"{BOLD}{CYAN}======================================================================{RESET}")
|
|
216
|
+
print(f"📁 Target Project : {target_repo}")
|
|
217
|
+
|
|
218
|
+
# 1. Detect Stack
|
|
219
|
+
stack_info = detect_tech_stack(target_repo)
|
|
220
|
+
print(f"🔍 Detected Stack : {BOLD}{stack_info['framework']}{RESET} ({stack_info['language']})")
|
|
221
|
+
print(f"📂 Structure Scope : Pages: `{stack_info['page_dir']}` | Tests: `{stack_info['test_dir']}`\n")
|
|
222
|
+
|
|
223
|
+
# 2. Ensure PROJECT_AI_GUIDE.md & PROJECT_STATE.md
|
|
224
|
+
print(f"{BOLD}[1/3] Checking Project AI Guide & State Anchor...{RESET}")
|
|
225
|
+
guide_created = ensure_project_ai_guide(target_repo, stack_info)
|
|
226
|
+
project_state.init_state(target_repo)
|
|
227
|
+
|
|
228
|
+
# 3. Install Pre-Push Git Hook
|
|
229
|
+
print(f"\n{BOLD}[2/3] Installing Zero-Leakage Git Pre-Push Hook...{RESET}")
|
|
230
|
+
hook_installed = install_git_pre_push_hook(target_repo)
|
|
231
|
+
|
|
232
|
+
# 4. Run 5-in-1 Comprehensive Health & AST Analysis
|
|
233
|
+
print(f"\n{BOLD}[3/3] Executing Initial Health Audit & AST Mapping...{RESET}")
|
|
234
|
+
analysis_code = analyze_project.run_full_analysis(target_repo)
|
|
235
|
+
|
|
236
|
+
print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
|
|
237
|
+
print(f"{BOLD}{CYAN} 🎉 PROJECT IS NOW 100% AI-READY (LEVEL 5 SETUP) {RESET}")
|
|
238
|
+
print(f"{BOLD}{CYAN}======================================================================{RESET}")
|
|
239
|
+
print(f" Target Framework : {stack_info['framework']}")
|
|
240
|
+
print(f" AI Project Guide : {os.path.join(target_repo, 'PROJECT_AI_GUIDE.md')}")
|
|
241
|
+
print(f" AST Method Map : {os.path.join(target_repo, 'repo_map.txt')}")
|
|
242
|
+
print(f" Health Report : {os.path.join(target_repo, 'PROJECT_HEALTH_REPORT.md')}")
|
|
243
|
+
print(f" Pre-Push Sentinel : {'ACTIVE (.git/hooks/pre-push)' if hook_installed else 'MANUAL (No .git folder)'}")
|
|
244
|
+
print(f"{BOLD}{CYAN}======================================================================{RESET}\n")
|
|
245
|
+
|
|
246
|
+
return analysis_code
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def main():
|
|
250
|
+
parser = argparse.ArgumentParser(description="Autonomous Enterprise QA Project Setup")
|
|
251
|
+
parser.add_argument("target", nargs="?", default=None, help="Target repository path to setup")
|
|
252
|
+
parser.add_argument("--repo", "--path", dest="repo_path", default=None, help="Target repository path")
|
|
253
|
+
args = parser.parse_args()
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
target_repo = resolve_target_repo(cli_target=args.target, cli_repo=args.repo_path)
|
|
257
|
+
except FileNotFoundError as e:
|
|
258
|
+
print(f"❌ Error: {e}")
|
|
259
|
+
return 1
|
|
260
|
+
|
|
261
|
+
return run_setup(target_repo)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if __name__ == "__main__":
|
|
265
|
+
sys.exit(main())
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Smart Visual Masker & Anti-Flake Visual Engine
|
|
4
|
+
----------------------------------------------
|
|
5
|
+
Eliminates 99% of false alarms in Visual Regression Testing.
|
|
6
|
+
Automatically disables dynamic CSS animations, freezes SVG spinners,
|
|
7
|
+
and masks fluctuating UI elements (timestamps, clocks, avatars, ad banners).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import json
|
|
13
|
+
import argparse
|
|
14
|
+
from typing import List, Dict, Any, Tuple
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SmartVisualMasker:
|
|
18
|
+
"""Enterprise Visual Regression Anti-Flake & Dynamic Masking Engine."""
|
|
19
|
+
|
|
20
|
+
ANTI_FLAKE_CSS = """
|
|
21
|
+
*, *::before, *::after {
|
|
22
|
+
transition-property: none !important;
|
|
23
|
+
transform: none !important;
|
|
24
|
+
animation: none !important;
|
|
25
|
+
caret-color: transparent !important;
|
|
26
|
+
scroll-behavior: auto !important;
|
|
27
|
+
}
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
DEFAULT_MASK_SELECTORS = [
|
|
31
|
+
# Timestamps and dates
|
|
32
|
+
"time",
|
|
33
|
+
".timestamp",
|
|
34
|
+
".date-time",
|
|
35
|
+
".clock",
|
|
36
|
+
"[data-testid*='date']",
|
|
37
|
+
"[data-testid*='time']",
|
|
38
|
+
"[class*='countdown']",
|
|
39
|
+
# Random avatars & dynamic user content
|
|
40
|
+
".avatar",
|
|
41
|
+
"img.user-avatar",
|
|
42
|
+
"[data-testid*='avatar']",
|
|
43
|
+
# Ads and third-party widgets
|
|
44
|
+
".ad-banner",
|
|
45
|
+
".advertisement",
|
|
46
|
+
"iframe[id*='google']",
|
|
47
|
+
# Skeleton loaders & spinners
|
|
48
|
+
".skeleton",
|
|
49
|
+
".loading-placeholder",
|
|
50
|
+
".spinner"
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def get_anti_flake_css(cls) -> str:
|
|
55
|
+
"""Returns normalized CSS string to freeze all animations and transitions."""
|
|
56
|
+
return cls.ANTI_FLAKE_CSS.strip()
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def generate_playwright_ts_options(cls, additional_selectors: List[str] = None) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Generates Playwright TypeScript assertion options with masking and disabled animations.
|
|
62
|
+
"""
|
|
63
|
+
selectors = cls.DEFAULT_MASK_SELECTORS + (additional_selectors or [])
|
|
64
|
+
locators_ts = ",\n ".join([f"page.locator('{sel}')" for sel in selectors])
|
|
65
|
+
|
|
66
|
+
return f"""await expect(page).toHaveScreenshot('visual-baseline.png', {{
|
|
67
|
+
animations: 'disabled',
|
|
68
|
+
caret: 'hide',
|
|
69
|
+
scale: 'css',
|
|
70
|
+
maxDiffPixelRatio: 0.01,
|
|
71
|
+
mask: [
|
|
72
|
+
{locators_ts}
|
|
73
|
+
]
|
|
74
|
+
}});"""
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def generate_browser_mask_script(cls, selectors: List[str] = None) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Generates JavaScript snippet to run inside the live browser
|
|
80
|
+
to immediately freeze DOM animations and paint solid black masks
|
|
81
|
+
over dynamic timestamp/avatar elements.
|
|
82
|
+
"""
|
|
83
|
+
target_selectors = selectors or cls.DEFAULT_MASK_SELECTORS
|
|
84
|
+
selectors_json = json.dumps(target_selectors)
|
|
85
|
+
return f"""(() => {{
|
|
86
|
+
// 1. Inject anti-flake styles
|
|
87
|
+
const style = document.createElement('style');
|
|
88
|
+
style.innerHTML = `{cls.get_anti_flake_css()}`;
|
|
89
|
+
document.head.appendChild(style);
|
|
90
|
+
|
|
91
|
+
// 2. Solid mask for dynamic elements
|
|
92
|
+
const selectors = {selectors_json};
|
|
93
|
+
let maskedCount = 0;
|
|
94
|
+
selectors.forEach(sel => {{
|
|
95
|
+
document.querySelectorAll(sel).forEach(el => {{
|
|
96
|
+
el.style.backgroundColor = '#000000';
|
|
97
|
+
el.style.color = '#000000';
|
|
98
|
+
el.style.visibility = 'visible';
|
|
99
|
+
el.setAttribute('data-visual-masked', 'true');
|
|
100
|
+
maskedCount++;
|
|
101
|
+
}});
|
|
102
|
+
}});
|
|
103
|
+
return {{ status: 'OK', maskedCount }};
|
|
104
|
+
}})();"""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main():
|
|
108
|
+
parser = argparse.ArgumentParser(description="Smart Visual Masker & Anti-Flake Visual Engine")
|
|
109
|
+
parser.add_argument("--css", action="store_true", help="Print anti-flake CSS")
|
|
110
|
+
parser.add_argument("--js-script", action="store_true", help="Print browser in-page masking JavaScript")
|
|
111
|
+
parser.add_argument("--playwright-snippet", action="store_true", help="Generate Playwright TS visual assertion snippet")
|
|
112
|
+
parser.add_argument("--mask", action="append", default=[], help="Additional CSS selectors to mask")
|
|
113
|
+
|
|
114
|
+
args = parser.parse_args()
|
|
115
|
+
|
|
116
|
+
if args.css:
|
|
117
|
+
print(SmartVisualMasker.get_anti_flake_css())
|
|
118
|
+
elif args.js_script:
|
|
119
|
+
print(SmartVisualMasker.generate_browser_mask_script(args.mask or None))
|
|
120
|
+
elif args.playwright_snippet:
|
|
121
|
+
print(SmartVisualMasker.generate_playwright_ts_options(args.mask or None))
|
|
122
|
+
else:
|
|
123
|
+
print("======================================================================")
|
|
124
|
+
print(" SMART VISUAL MASKER & ANTI-FLAKE VISUAL ENGINE")
|
|
125
|
+
print("======================================================================")
|
|
126
|
+
print("Default Masked Selectors:")
|
|
127
|
+
for sel in SmartVisualMasker.DEFAULT_MASK_SELECTORS:
|
|
128
|
+
print(f" • {sel}")
|
|
129
|
+
print("\nUse --css, --js-script, or --playwright-snippet for code output.")
|
|
130
|
+
print("======================================================================")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
main()
|