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
package/tools/cli.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Enterprise Agent-QA CLI Entrypoint
|
|
4
|
+
----------------------------------
|
|
5
|
+
Provides a centralized command-line interface for the agent-skills ecosystem.
|
|
6
|
+
Dispatches commands to underlying specialized tools in tools/.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import argparse
|
|
12
|
+
|
|
13
|
+
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
14
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
15
|
+
try:
|
|
16
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
17
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
18
|
+
except Exception:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
22
|
+
REPO_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
|
|
23
|
+
sys.path.insert(0, SCRIPT_DIR)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def cmd_version(args):
|
|
27
|
+
manifest_path = os.path.join(REPO_ROOT, "skills_manifest.json")
|
|
28
|
+
version = "2.0.0"
|
|
29
|
+
if os.path.exists(manifest_path):
|
|
30
|
+
try:
|
|
31
|
+
import json
|
|
32
|
+
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
33
|
+
data = json.load(f)
|
|
34
|
+
version = data.get("version", version)
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
print(f"Agent-QA Hub CLI v{version}")
|
|
38
|
+
manifest_skills = 27
|
|
39
|
+
if os.path.exists(manifest_path):
|
|
40
|
+
try:
|
|
41
|
+
import json
|
|
42
|
+
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
43
|
+
manifest_skills = json.load(f).get("totalSkills", 27)
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
print(f"Catalog: {manifest_skills} Standardized Skills | 6 Subagent Swarm Profiles")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cmd_verify(args):
|
|
50
|
+
from verify_all import main as verify_main
|
|
51
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
52
|
+
sys.exit(verify_main())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def cmd_lint(args):
|
|
56
|
+
from infer_linter import main as lint_main
|
|
57
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
58
|
+
sys.exit(lint_main())
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def cmd_heal(args):
|
|
62
|
+
from cache_resolver import main as heal_main
|
|
63
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
64
|
+
sys.exit(heal_main())
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def cmd_distill(args):
|
|
68
|
+
from distill_log import main as distill_main
|
|
69
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
70
|
+
sys.exit(distill_main())
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def cmd_impact(args):
|
|
74
|
+
from test_impact import main as impact_main
|
|
75
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
76
|
+
sys.exit(impact_main())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def cmd_clean(args):
|
|
80
|
+
"""Cleans temporary build files, __pycache__, and compiled bytecode across the repository."""
|
|
81
|
+
import shutil
|
|
82
|
+
removed_count = 0
|
|
83
|
+
print("🧹 Cleaning temporary build artifacts and bytecode caches...")
|
|
84
|
+
for root, dirs, files in os.walk(REPO_ROOT):
|
|
85
|
+
for d in list(dirs):
|
|
86
|
+
if d == "__pycache__":
|
|
87
|
+
full_dir = os.path.join(root, d)
|
|
88
|
+
try:
|
|
89
|
+
shutil.rmtree(full_dir)
|
|
90
|
+
removed_count += 1
|
|
91
|
+
dirs.remove(d)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"⚠️ Could not remove {full_dir}: {e}")
|
|
94
|
+
for f in files:
|
|
95
|
+
if f.endswith((".pyc", ".pyo", ".tmp")):
|
|
96
|
+
try:
|
|
97
|
+
os.remove(os.path.join(root, f))
|
|
98
|
+
removed_count += 1
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
101
|
+
print(f"✅ Cleanup finished: {removed_count} build artifacts and caches removed.")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def cmd_flaky(args):
|
|
105
|
+
from flaky_detector import main as flaky_main
|
|
106
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
107
|
+
sys.exit(flaky_main())
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def cmd_canary(args):
|
|
111
|
+
from agent_canary import main as canary_main
|
|
112
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
113
|
+
sys.exit(canary_main())
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def cmd_analyze(args):
|
|
117
|
+
from analyze_project import main as analyze_main
|
|
118
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
119
|
+
sys.exit(analyze_main())
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def cmd_setup(args):
|
|
123
|
+
from setup_project import main as setup_main
|
|
124
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
125
|
+
sys.exit(setup_main())
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def cmd_auto_heal(args):
|
|
129
|
+
from auto_heal import main as auto_heal_main
|
|
130
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
131
|
+
sys.exit(auto_heal_main())
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def cmd_safety_check(args):
|
|
135
|
+
from safety_firewall import main as safety_main
|
|
136
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
137
|
+
sys.exit(safety_main())
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def cmd_contract_drift(args):
|
|
141
|
+
from api_drift_sentinel import main as drift_main
|
|
142
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
143
|
+
sys.exit(drift_main())
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def cmd_mcp_serve(args):
|
|
147
|
+
from mcp_server import run_stdio_server
|
|
148
|
+
run_stdio_server()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def cmd_mutate(args):
|
|
152
|
+
from micro_mutator import main as mut_main
|
|
153
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
154
|
+
sys.exit(mut_main())
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def cmd_heal_pr(args):
|
|
158
|
+
from heal_pr_automator import main as pr_main
|
|
159
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
160
|
+
sys.exit(pr_main())
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def cmd_shard(args):
|
|
164
|
+
from matrix_sharder import main as shard_main
|
|
165
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
166
|
+
sys.exit(shard_main())
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def cmd_transpile(args):
|
|
170
|
+
from omni_transpiler import transpile_file
|
|
171
|
+
res = transpile_file(args.source, args.to, args.out)
|
|
172
|
+
print(f"✅ Transpiled '{args.source}' -> {args.to} ({len(res)} files):")
|
|
173
|
+
for p, code in res.items():
|
|
174
|
+
print(f" • {p} ({len(code.splitlines())} lines)")
|
|
175
|
+
if not args.out:
|
|
176
|
+
print("---")
|
|
177
|
+
print(code)
|
|
178
|
+
print("---")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def cmd_omni_scan(args):
|
|
182
|
+
from omni_transpiler import scan_repository
|
|
183
|
+
inv = scan_repository(args.path)
|
|
184
|
+
import json
|
|
185
|
+
if getattr(args, "json", False):
|
|
186
|
+
print(json.dumps(inv, indent=2))
|
|
187
|
+
else:
|
|
188
|
+
print("======================================================================")
|
|
189
|
+
print(" OMNI-AUTOMATION POLYGLOT REPOSITORY INVENTORY (0-TOKEN AST SCAN)")
|
|
190
|
+
print("======================================================================")
|
|
191
|
+
print(f"📁 Target Directory : {inv['repository']}")
|
|
192
|
+
print(f"📄 Scanned Files : {inv['total_files_scanned']}")
|
|
193
|
+
print(f"🌐 Languages Detected : {inv['languages']}")
|
|
194
|
+
print(f"⚡ Frameworks : {inv['frameworks']}")
|
|
195
|
+
print(f"🏛️ Page Classes Found : {len(inv['pages'])}")
|
|
196
|
+
print(f"🎯 Total Locators : {inv['total_locators']}")
|
|
197
|
+
print(f"⚙️ Action Methods : {inv['total_methods']}")
|
|
198
|
+
print(f"🧪 Test Cases Found : {len(inv['test_cases'])}")
|
|
199
|
+
print("======================================================================")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def cmd_report_bug(args):
|
|
203
|
+
from bug_reporter import main as bug_main
|
|
204
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
205
|
+
sys.exit(bug_main())
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def cmd_mock_shield(args):
|
|
209
|
+
from network_mock_shield import main as mock_main
|
|
210
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
211
|
+
sys.exit(mock_main())
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def cmd_visual_mask(args):
|
|
215
|
+
from smart_visual_masker import main as mask_main
|
|
216
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
217
|
+
sys.exit(mask_main())
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def cmd_ci_hook(args):
|
|
221
|
+
from ci_healing_hook import main as hook_main
|
|
222
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
223
|
+
sys.exit(hook_main())
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def cmd_migrate(args):
|
|
227
|
+
"""Migrates an entire legacy test suite (Katalon, Cypress, Selenium) to modern target (Playwright TS)."""
|
|
228
|
+
from omni_transpiler import transpile_file
|
|
229
|
+
src = getattr(args, "src", None)
|
|
230
|
+
dest = getattr(args, "dest", None) or "playwright_suite"
|
|
231
|
+
target = getattr(args, "target", "playwright-ts")
|
|
232
|
+
if not src:
|
|
233
|
+
print("❌ Error: --src parameter is required for migrate.")
|
|
234
|
+
sys.exit(1)
|
|
235
|
+
|
|
236
|
+
if os.path.isdir(src):
|
|
237
|
+
migrated_count = 0
|
|
238
|
+
test_exts = {".groovy", ".java", ".js", ".ts", ".py"}
|
|
239
|
+
for root, _, files in os.walk(src):
|
|
240
|
+
for f in files:
|
|
241
|
+
if os.path.splitext(f)[1].lower() in test_exts:
|
|
242
|
+
file_path = os.path.join(root, f)
|
|
243
|
+
try:
|
|
244
|
+
transpile_file(file_path, target, output_dir=dest, hermetic=True)
|
|
245
|
+
migrated_count += 1
|
|
246
|
+
except Exception:
|
|
247
|
+
pass
|
|
248
|
+
print(f"✅ [MIGRATION COMPLETE] {migrated_count} test files converted to {target} in '{dest}'.")
|
|
249
|
+
else:
|
|
250
|
+
transpile_file(src, target, output_dir=dest, hermetic=True)
|
|
251
|
+
print(f"✅ [MIGRATION COMPLETE] '{src}' -> {target} in '{dest}'.")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def cmd_decouple_config(args):
|
|
255
|
+
from config_decoupler import main as decouple_main
|
|
256
|
+
sys.argv = [sys.argv[0]] + (sys.argv[2:] if len(sys.argv) > 2 else [])
|
|
257
|
+
sys.exit(decouple_main())
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def main():
|
|
261
|
+
parser = argparse.ArgumentParser(
|
|
262
|
+
prog="agent-qa",
|
|
263
|
+
description="Enterprise AI Agent Quality Engineering & Test Automation Hub CLI"
|
|
264
|
+
)
|
|
265
|
+
parser.add_argument(
|
|
266
|
+
"--offline", "--offline-deterministic",
|
|
267
|
+
action="store_true",
|
|
268
|
+
dest="offline",
|
|
269
|
+
help="Force 100%% offline air-gapped deterministic mode (zero external network / LLM API calls)"
|
|
270
|
+
)
|
|
271
|
+
subparsers = parser.add_subparsers(dest="subcommand", help="Available subcommands")
|
|
272
|
+
|
|
273
|
+
# version
|
|
274
|
+
p_ver = subparsers.add_parser("version", help="Show Hub version and catalog metadata")
|
|
275
|
+
p_ver.set_defaults(func=cmd_version)
|
|
276
|
+
|
|
277
|
+
# setup
|
|
278
|
+
p_setup = subparsers.add_parser("setup", help="Autonomous 1-click bootstrap of target project (AI guide, Git hook, AST map)")
|
|
279
|
+
p_setup.set_defaults(func=cmd_setup)
|
|
280
|
+
|
|
281
|
+
# auto-heal
|
|
282
|
+
p_auto_heal = subparsers.add_parser("auto-heal", help="Autonomous closed-loop test failure diagnosis and healing plan")
|
|
283
|
+
p_auto_heal.set_defaults(func=cmd_auto_heal)
|
|
284
|
+
|
|
285
|
+
# heal-pr
|
|
286
|
+
p_heal_pr = subparsers.add_parser("heal-pr", help="Calculate self-healing confidence and generate Git PR draft")
|
|
287
|
+
p_heal_pr.set_defaults(func=cmd_heal_pr)
|
|
288
|
+
|
|
289
|
+
# mutate
|
|
290
|
+
p_mut = subparsers.add_parser("mutate", help="Inject AST micro-mutants to verify test assertion strength")
|
|
291
|
+
p_mut.set_defaults(func=cmd_mutate)
|
|
292
|
+
|
|
293
|
+
# shard
|
|
294
|
+
p_shard = subparsers.add_parser("shard", help="Balance parallel CI/CD test execution using duration-balanced bin-packing")
|
|
295
|
+
p_shard.set_defaults(func=cmd_shard)
|
|
296
|
+
|
|
297
|
+
# safety-check
|
|
298
|
+
p_safety = subparsers.add_parser("safety-check", help="Action Safety Firewall & Blast Radius Containment")
|
|
299
|
+
p_safety.set_defaults(func=cmd_safety_check)
|
|
300
|
+
|
|
301
|
+
# contract-drift
|
|
302
|
+
p_drift = subparsers.add_parser("contract-drift", help="Detect breaking API changes across OpenAPI / Swagger specs")
|
|
303
|
+
p_drift.set_defaults(func=cmd_contract_drift)
|
|
304
|
+
|
|
305
|
+
# mcp-serve
|
|
306
|
+
p_mcp = subparsers.add_parser("mcp-serve", help="Start the JSON-RPC 2.0 stdio Model Context Protocol (MCP) server")
|
|
307
|
+
p_mcp.set_defaults(func=cmd_mcp_serve)
|
|
308
|
+
|
|
309
|
+
# analyze
|
|
310
|
+
p_analyze = subparsers.add_parser("analyze", help="Execute Comprehensive Project Health & AST Analysis")
|
|
311
|
+
p_analyze.set_defaults(func=cmd_analyze)
|
|
312
|
+
|
|
313
|
+
# verify
|
|
314
|
+
p_verify = subparsers.add_parser("verify", help="Execute 7 Quality Gates DoD Verification")
|
|
315
|
+
p_verify.set_defaults(func=cmd_verify)
|
|
316
|
+
|
|
317
|
+
# lint
|
|
318
|
+
p_lint = subparsers.add_parser("lint", help="Run AST Fake-Pass & Zero-Assertion Sentinel")
|
|
319
|
+
p_lint.set_defaults(func=cmd_lint)
|
|
320
|
+
|
|
321
|
+
# heal
|
|
322
|
+
p_heal = subparsers.add_parser("heal", help="Query, learn, or record-failed into semantic memory cache")
|
|
323
|
+
p_heal.set_defaults(func=cmd_heal)
|
|
324
|
+
|
|
325
|
+
# distill
|
|
326
|
+
p_distill = subparsers.add_parser("distill", help="Distill and filter verbose CLI logs by 95 percent")
|
|
327
|
+
p_distill.set_defaults(func=cmd_distill)
|
|
328
|
+
|
|
329
|
+
# impact
|
|
330
|
+
p_impact = subparsers.add_parser("impact", help="Analyze code change impact using AST Knowledge Graph")
|
|
331
|
+
p_impact.set_defaults(func=cmd_impact)
|
|
332
|
+
|
|
333
|
+
# canary
|
|
334
|
+
p_canary = subparsers.add_parser("canary", help="Run autonomous canary resilience benchmark")
|
|
335
|
+
p_canary.set_defaults(func=cmd_canary)
|
|
336
|
+
|
|
337
|
+
# clean
|
|
338
|
+
p_clean = subparsers.add_parser("clean", help="Clean build artifacts, __pycache__, and bytecode")
|
|
339
|
+
p_clean.set_defaults(func=cmd_clean)
|
|
340
|
+
|
|
341
|
+
# flaky
|
|
342
|
+
p_flaky = subparsers.add_parser("flaky", help="Detect flaky tests and manage quarantine lifecycle")
|
|
343
|
+
p_flaky.set_defaults(func=cmd_flaky)
|
|
344
|
+
|
|
345
|
+
# transpile
|
|
346
|
+
p_trans = subparsers.add_parser("transpile", help="Transpile test files across languages and frameworks (0-token)")
|
|
347
|
+
p_trans.add_argument("source", help="Source file path")
|
|
348
|
+
p_trans.add_argument("--to", required=True, choices=["playwright-ts", "playwright-python", "selenium-java", "katalon-groovy", "cypress-js"], help="Target framework")
|
|
349
|
+
p_trans.add_argument("--out", default=None, help="Output directory")
|
|
350
|
+
p_trans.set_defaults(func=cmd_transpile)
|
|
351
|
+
|
|
352
|
+
# omni-scan
|
|
353
|
+
p_omni = subparsers.add_parser("omni-scan", help="Scan repository and build polyglot test inventory (0-token)")
|
|
354
|
+
p_omni.add_argument("path", default=".", nargs="?", help="Directory to scan")
|
|
355
|
+
p_omni.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
356
|
+
p_omni.set_defaults(func=cmd_omni_scan)
|
|
357
|
+
|
|
358
|
+
# report-bug
|
|
359
|
+
p_bug = subparsers.add_parser("report-bug", help="Auto-generate enterprise Jira/Markdown Bug Ticket with RCA")
|
|
360
|
+
p_bug.set_defaults(func=cmd_report_bug)
|
|
361
|
+
|
|
362
|
+
# mock-shield
|
|
363
|
+
p_mock = subparsers.add_parser("mock-shield", help="Generate API Route Interception & Chaos Fault Injection Shield")
|
|
364
|
+
p_mock.set_defaults(func=cmd_mock_shield)
|
|
365
|
+
|
|
366
|
+
# visual-mask
|
|
367
|
+
p_mask = subparsers.add_parser("visual-mask", help="Smart Visual Masking & Anti-Flake Animation Blocker")
|
|
368
|
+
p_mask.set_defaults(func=cmd_visual_mask)
|
|
369
|
+
|
|
370
|
+
# ci-hook
|
|
371
|
+
p_ci = subparsers.add_parser("ci-hook", help="CI/CD Silent Self-Healing Pipeline Hook & Automated PR Generator")
|
|
372
|
+
p_ci.set_defaults(func=cmd_ci_hook)
|
|
373
|
+
|
|
374
|
+
# migrate
|
|
375
|
+
p_migrate = subparsers.add_parser("migrate", help="Autonomous suite migration from legacy (Katalon/Cypress) to modern Playwright")
|
|
376
|
+
p_migrate.add_argument("--src", required=True, help="Source legacy test suite file or directory")
|
|
377
|
+
p_migrate.add_argument("--dest", default="playwright_suite", help="Destination directory for migrated code")
|
|
378
|
+
p_migrate.add_argument("--target", default="playwright-ts", choices=["playwright-ts", "playwright-python", "selenium-java", "cypress-js"], help="Target framework")
|
|
379
|
+
p_migrate.set_defaults(func=cmd_migrate)
|
|
380
|
+
|
|
381
|
+
# decouple-config
|
|
382
|
+
p_decouple = subparsers.add_parser("decouple-config", help="Decouple hardcoded URLs, credentials, and secrets into env profiles")
|
|
383
|
+
p_decouple.add_argument("target", nargs="?", default=os.getcwd(), help="Target directory to scan")
|
|
384
|
+
p_decouple.add_argument("--fix", action="store_true", help="Apply environment variable replacements in-place")
|
|
385
|
+
p_decouple.add_argument("--out-dir", default=None, help="Output directory for config profiles")
|
|
386
|
+
p_decouple.set_defaults(func=cmd_decouple_config)
|
|
387
|
+
|
|
388
|
+
args, unknown = parser.parse_known_args()
|
|
389
|
+
if getattr(args, "offline", False):
|
|
390
|
+
os.environ["AGENT_QA_OFFLINE"] = "1"
|
|
391
|
+
print("🔒 [OFFLINE MODE] Air-Gapped Deterministic Mode Active. 100% Local AST & Heuristics (Zero External API calls).")
|
|
392
|
+
|
|
393
|
+
if not args.subcommand:
|
|
394
|
+
parser.print_help()
|
|
395
|
+
sys.exit(0)
|
|
396
|
+
|
|
397
|
+
# Dispatch subcommand
|
|
398
|
+
args.func(args)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
if __name__ == "__main__":
|
|
402
|
+
main()
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Environment & Secret Decoupling Engine
|
|
4
|
+
---------------------------------------
|
|
5
|
+
Scans automation test suites (Playwright, Katalon, Selenium, Cypress) for hardcoded
|
|
6
|
+
environment values (URLs, credentials, bearer tokens, DB connection strings).
|
|
7
|
+
Decouples them into standardized environment configurations:
|
|
8
|
+
1. config/profiles/env.default.json
|
|
9
|
+
2. .env.example
|
|
10
|
+
3. Replaces hardcoded literals in AST/sources with process.env / os.getenv references.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
import json
|
|
17
|
+
import argparse
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Dict, List, Any, Tuple
|
|
20
|
+
|
|
21
|
+
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
22
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
23
|
+
try:
|
|
24
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
25
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
# Regex patterns for sensitive literals
|
|
30
|
+
URL_PATTERN = re.compile(r'https?://(?!www\.w3\.org|json-schema\.org|schemas\.xmlsoap\.org)[a-zA-Z0-9.\-]+(?::\d+)?(?:/[a-zA-Z0-9_\.\-~%!$&\'()*+,;=:@]*)*')
|
|
31
|
+
PASSWORD_PATTERN = re.compile(r'(?i)(?:password|secret|pwd|pass|client_secret)\s*[:=]\s*["\']([^"\']{4,})["\']')
|
|
32
|
+
TOKEN_PATTERN = re.compile(r'(?i)\b(?:Bearer\s+([A-Za-z0-9_\-\.]{16,})|api[_\-]?key\s*[:=]\s*["\']([A-Za-z0-9_\-]{16,})["\'])')
|
|
33
|
+
DB_URL_PATTERN = re.compile(r'(?:mongodb|mysql|postgres|postgresql|jdbc:[a-z0-9]+)://[^\s"\'<>]+')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConfigDecoupler:
|
|
37
|
+
"""Automated configuration and credentials decoupling engine."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, target_dir: str):
|
|
40
|
+
self.target_dir = os.path.abspath(target_dir)
|
|
41
|
+
self.decoupled_configs: Dict[str, Any] = {
|
|
42
|
+
"APP_BASE_URL": "http://localhost:3000",
|
|
43
|
+
"TEST_USER_USERNAME": "test_user@example.com",
|
|
44
|
+
"TEST_USER_PASSWORD": "TestPassword123!",
|
|
45
|
+
"API_AUTH_TOKEN": "mock_auth_token_secret"
|
|
46
|
+
}
|
|
47
|
+
self.findings: List[Dict[str, Any]] = []
|
|
48
|
+
|
|
49
|
+
def scan(self) -> List[Dict[str, Any]]:
|
|
50
|
+
"""Scans the target directory for hardcoded URLs, credentials, and secrets."""
|
|
51
|
+
extensions = {".ts", ".js", ".groovy", ".java", ".py", ".json"}
|
|
52
|
+
exclude_dirs = {"node_modules", ".git", ".gradle", "build", "dist", "__pycache__", "config"}
|
|
53
|
+
|
|
54
|
+
for root, dirs, files in os.walk(self.target_dir):
|
|
55
|
+
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
|
56
|
+
for file_name in files:
|
|
57
|
+
ext = os.path.splitext(file_name)[1].lower()
|
|
58
|
+
if ext in extensions:
|
|
59
|
+
file_path = os.path.join(root, file_name)
|
|
60
|
+
self._scan_file(file_path)
|
|
61
|
+
|
|
62
|
+
return self.findings
|
|
63
|
+
|
|
64
|
+
def _scan_file(self, file_path: str):
|
|
65
|
+
try:
|
|
66
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
67
|
+
content = f.read()
|
|
68
|
+
except Exception:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
rel_path = os.path.relpath(file_path, self.target_dir).replace("\\", "/")
|
|
72
|
+
lines = content.splitlines()
|
|
73
|
+
|
|
74
|
+
for idx, line in enumerate(lines, 1):
|
|
75
|
+
if line.strip().startswith("//") or line.strip().startswith("#"):
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
# 1. URLs
|
|
79
|
+
for url in URL_PATTERN.findall(line):
|
|
80
|
+
key = "APP_BASE_URL"
|
|
81
|
+
self.decoupled_configs[key] = url
|
|
82
|
+
self.findings.append({
|
|
83
|
+
"file": rel_path,
|
|
84
|
+
"line": idx,
|
|
85
|
+
"type": "URL",
|
|
86
|
+
"key": key,
|
|
87
|
+
"literal": url,
|
|
88
|
+
"replacement": f"process.env.{key} || '{url}'"
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
# 2. Passwords
|
|
92
|
+
for pwd in PASSWORD_PATTERN.findall(line):
|
|
93
|
+
key = "TEST_USER_PASSWORD"
|
|
94
|
+
self.decoupled_configs[key] = pwd
|
|
95
|
+
self.findings.append({
|
|
96
|
+
"file": rel_path,
|
|
97
|
+
"line": idx,
|
|
98
|
+
"type": "PASSWORD",
|
|
99
|
+
"key": key,
|
|
100
|
+
"literal": pwd,
|
|
101
|
+
"replacement": f"process.env.{key}"
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
# 3. Tokens
|
|
105
|
+
for t_match in TOKEN_PATTERN.findall(line):
|
|
106
|
+
token = t_match[0] or t_match[1]
|
|
107
|
+
if token:
|
|
108
|
+
key = "API_AUTH_TOKEN"
|
|
109
|
+
self.decoupled_configs[key] = token
|
|
110
|
+
self.findings.append({
|
|
111
|
+
"file": rel_path,
|
|
112
|
+
"line": idx,
|
|
113
|
+
"type": "TOKEN",
|
|
114
|
+
"key": key,
|
|
115
|
+
"literal": token,
|
|
116
|
+
"replacement": f"process.env.{key}"
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
# 4. DB URLs
|
|
120
|
+
for db_url in DB_URL_PATTERN.findall(line):
|
|
121
|
+
key = "DATABASE_URL"
|
|
122
|
+
self.decoupled_configs[key] = db_url
|
|
123
|
+
self.findings.append({
|
|
124
|
+
"file": rel_path,
|
|
125
|
+
"line": idx,
|
|
126
|
+
"type": "DATABASE_URL",
|
|
127
|
+
"key": key,
|
|
128
|
+
"literal": db_url,
|
|
129
|
+
"replacement": f"process.env.{key}"
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
def export_configs(self, output_dir: str = None) -> Tuple[str, str]:
|
|
133
|
+
"""Generates env.default.json and .env.example configuration files."""
|
|
134
|
+
out_base = output_dir or os.path.join(self.target_dir, "config", "profiles")
|
|
135
|
+
os.makedirs(out_base, exist_ok=True)
|
|
136
|
+
|
|
137
|
+
json_path = os.path.join(out_base, "env.default.json")
|
|
138
|
+
with open(json_path, "w", encoding="utf-8") as f:
|
|
139
|
+
json.dump(self.decoupled_configs, f, indent=2)
|
|
140
|
+
|
|
141
|
+
env_example_path = os.path.join(self.target_dir, ".env.example")
|
|
142
|
+
env_lines = [
|
|
143
|
+
"# ===================================================================",
|
|
144
|
+
"# AUTO-GENERATED ENVIRONMENT CONFIGURATION TEMPLATE",
|
|
145
|
+
"# Decoupled via tools/config_decoupler.py",
|
|
146
|
+
"# ===================================================================",
|
|
147
|
+
""
|
|
148
|
+
]
|
|
149
|
+
for k, v in self.decoupled_configs.items():
|
|
150
|
+
env_lines.append(f"{k}={v}")
|
|
151
|
+
|
|
152
|
+
with open(env_example_path, "w", encoding="utf-8") as f:
|
|
153
|
+
f.write("\n".join(env_lines) + "\n")
|
|
154
|
+
|
|
155
|
+
return json_path, env_example_path
|
|
156
|
+
|
|
157
|
+
def apply_decoupling(self) -> int:
|
|
158
|
+
"""Replaces detected hardcoded literals with decoupled environment references in-place."""
|
|
159
|
+
modified_count = 0
|
|
160
|
+
grouped_by_file: Dict[str, List[Dict[str, Any]]] = {}
|
|
161
|
+
for f in self.findings:
|
|
162
|
+
grouped_by_file.setdefault(f["file"], []).append(f)
|
|
163
|
+
|
|
164
|
+
for rel_file, items in grouped_by_file.items():
|
|
165
|
+
full_path = os.path.join(self.target_dir, rel_file)
|
|
166
|
+
if not os.path.exists(full_path):
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
with open(full_path, "r", encoding="utf-8") as fp:
|
|
171
|
+
content = fp.read()
|
|
172
|
+
except Exception:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
new_content = content
|
|
176
|
+
ext = os.path.splitext(full_path)[1].lower()
|
|
177
|
+
|
|
178
|
+
for it in items:
|
|
179
|
+
lit = it["literal"]
|
|
180
|
+
key = it["key"]
|
|
181
|
+
|
|
182
|
+
if ext in {".ts", ".js"}:
|
|
183
|
+
replacement = f"process.env.{key} || '{lit}'" if it["type"] == "URL" else f"process.env.{key}"
|
|
184
|
+
elif ext == ".py":
|
|
185
|
+
replacement = f"os.getenv('{key}', '{lit}')" if it["type"] == "URL" else f"os.getenv('{key}')"
|
|
186
|
+
elif ext in {".groovy", ".java"}:
|
|
187
|
+
replacement = f"(System.getenv('{key}') ?: '{lit}')" if it["type"] == "URL" else f"System.getenv('{key}')"
|
|
188
|
+
else:
|
|
189
|
+
replacement = f"${{{key}}}"
|
|
190
|
+
|
|
191
|
+
# Only replace quotes wrapped around literal
|
|
192
|
+
new_content = new_content.replace(f"'{lit}'", replacement)
|
|
193
|
+
new_content = new_content.replace(f'"{lit}"', replacement)
|
|
194
|
+
|
|
195
|
+
if new_content != content:
|
|
196
|
+
with open(full_path, "w", encoding="utf-8") as fp:
|
|
197
|
+
fp.write(new_content)
|
|
198
|
+
modified_count += 1
|
|
199
|
+
|
|
200
|
+
return modified_count
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def main():
|
|
204
|
+
parser = argparse.ArgumentParser(description="Automated Environment & Secret Decoupling Engine")
|
|
205
|
+
parser.add_argument("target", nargs="?", default=os.getcwd(), help="Target project root directory")
|
|
206
|
+
parser.add_argument("--fix", action="store_true", help="Apply decoupling modifications in-place to files")
|
|
207
|
+
parser.add_argument("--out-dir", default=None, help="Output directory for config profiles")
|
|
208
|
+
parser.add_argument("--json", action="store_true", help="Output results in JSON format")
|
|
209
|
+
args = parser.parse_args()
|
|
210
|
+
|
|
211
|
+
decoupler = ConfigDecoupler(args.target)
|
|
212
|
+
findings = decoupler.scan()
|
|
213
|
+
json_path, env_path = decoupler.export_configs(args.out_dir)
|
|
214
|
+
|
|
215
|
+
if args.fix:
|
|
216
|
+
modified = decoupler.apply_decoupling()
|
|
217
|
+
|
|
218
|
+
if args.json:
|
|
219
|
+
print(json.dumps({
|
|
220
|
+
"target": decoupler.target_dir,
|
|
221
|
+
"total_findings": len(findings),
|
|
222
|
+
"findings": findings,
|
|
223
|
+
"config_json": json_path,
|
|
224
|
+
"env_example": env_path
|
|
225
|
+
}, indent=2))
|
|
226
|
+
else:
|
|
227
|
+
print("\n======================================================================")
|
|
228
|
+
print(" ENVIRONMENT & SECRET DECOUPLING ENGINE (HERMETIC CONFIG) ")
|
|
229
|
+
print("======================================================================")
|
|
230
|
+
print(f"📁 Target Scope : {decoupler.target_dir}")
|
|
231
|
+
print(f"🔍 Hardcoded Findings : {len(findings)} literals detected")
|
|
232
|
+
print(f"📄 Decoupled Config : {json_path}")
|
|
233
|
+
print(f"📄 Environment Template: {env_path}")
|
|
234
|
+
print("======================================================================\n")
|
|
235
|
+
|
|
236
|
+
for f in findings[:8]:
|
|
237
|
+
print(f" • [{f['type']}] {f['file']}:{f['line']} -> {f['key']}")
|
|
238
|
+
print(f" Literal : {f['literal']}")
|
|
239
|
+
print(f" Replacement: {f['replacement']}\n")
|
|
240
|
+
|
|
241
|
+
if len(findings) > 8:
|
|
242
|
+
print(f" ... and {len(findings) - 8} more literals decoupled.\n")
|
|
243
|
+
|
|
244
|
+
if args.fix:
|
|
245
|
+
print(f"✅ [IN-PLACE REFACTOR COMPLETED] {modified} files updated with environment variables.")
|
|
246
|
+
else:
|
|
247
|
+
print("💡 Run with '--fix' to apply environment variable replacements automatically in-place.")
|
|
248
|
+
|
|
249
|
+
return 0
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
if __name__ == "__main__":
|
|
253
|
+
sys.exit(main())
|