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.
Files changed (122) hide show
  1. package/.agents/agents/failure-analyst.md +47 -0
  2. package/.agents/agents/live-explorer.md +41 -0
  3. package/.agents/agents/orchestrator.md +43 -0
  4. package/.agents/agents/pom-crafter.md +81 -0
  5. package/.agents/agents/quality-gatekeeper.md +47 -0
  6. package/.agents/agents/test-runner.md +43 -0
  7. package/.agents/rules/context-engineering.md +88 -0
  8. package/.agents/rules/git-push-protocol.md +101 -0
  9. package/.agents/rules/graphify.md +14 -0
  10. package/.agents/rules/lessons-learned.md +23 -0
  11. package/.agents/rules/mandatory-playwright-live.md +29 -0
  12. package/.agents/rules/skill-precedence.md +42 -0
  13. package/.agents/rules/systematic-debugging.md +99 -0
  14. package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +239 -0
  15. package/.agents/skills/antigravity-test-sentinel/SKILL.md +64 -0
  16. package/.agents/skills/api-network-mocking-interception/SKILL.md +142 -0
  17. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +70 -0
  18. package/.agents/skills/architecture-decision-records/SKILL.md +441 -0
  19. package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +72 -0
  20. package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +214 -0
  21. package/.agents/skills/bats-testing-patterns/SKILL.md +100 -0
  22. package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -0
  23. package/.agents/skills/clean-pom-crafter/SKILL.md +75 -0
  24. package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -0
  25. package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -0
  26. package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +64 -0
  27. package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +879 -0
  28. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +123 -0
  29. package/.agents/skills/e2e-testing-patterns/SKILL.md +103 -0
  30. package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -0
  31. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +54 -0
  32. package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -0
  33. package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -0
  34. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -0
  35. package/.agents/skills/file-and-report-validator/SKILL.md +90 -0
  36. package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +114 -0
  37. package/.agents/skills/github-actions-templates/SKILL.md +334 -0
  38. package/.agents/skills/graphify-impact-analyzer/SKILL.md +78 -0
  39. package/.agents/skills/katalon-cli-runner/SKILL.md +76 -0
  40. package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -0
  41. package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -0
  42. package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -0
  43. package/.agents/skills/lam-script-generator/SKILL.md +83 -0
  44. package/.agents/skills/playwright-live-interaction/SKILL.md +86 -0
  45. package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -0
  46. package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -0
  47. package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -0
  48. package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -0
  49. package/.agents/skills/security-dast-sentinel/SKILL.md +176 -0
  50. package/.agents/skills/self-healing-locator/SKILL.md +73 -0
  51. package/.agents/skills/synthetic-test-data-generator/SKILL.md +135 -0
  52. package/.agents/skills/token-and-context-optimizer/SKILL.md +70 -0
  53. package/.agents/skills/visual-regression-sentinel/SKILL.md +62 -0
  54. package/.agents/skills/wcag-audit-patterns/SKILL.md +77 -0
  55. package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -0
  56. package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +65 -0
  57. package/.agents/templates/completion_report.md +57 -0
  58. package/.agents/workflows/graphify.md +10 -0
  59. package/.agents/workflows/test-impact.md +15 -0
  60. package/.agents/workflows/verify.md +34 -0
  61. package/AGENTS.md +115 -0
  62. package/README.md +394 -2
  63. package/bin/agent-qa.js +90 -0
  64. package/package.json +59 -4
  65. package/skills_manifest.json +262 -0
  66. package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +97 -0
  67. package/templates/PROJECT_STATE_TEMPLATE.md +20 -0
  68. package/tools/TOOLS_RUNBOOK.md +298 -0
  69. package/tools/agent_canary.py +501 -0
  70. package/tools/analyze_project.py +242 -0
  71. package/tools/api_drift_sentinel.py +208 -0
  72. package/tools/auto_heal.py +189 -0
  73. package/tools/browser_helpers.js +593 -0
  74. package/tools/bug_reporter.py +224 -0
  75. package/tools/cache_resolver.py +846 -0
  76. package/tools/check_env.py +288 -0
  77. package/tools/ci_healing_hook.py +128 -0
  78. package/tools/cli.py +402 -0
  79. package/tools/config_decoupler.py +253 -0
  80. package/tools/db_sandbox.py +341 -0
  81. package/tools/distill_log.py +152 -0
  82. package/tools/e2e_flow_prober.ps1 +65 -0
  83. package/tools/flaky_detector.py +222 -0
  84. package/tools/fuzz_payloads.py +197 -0
  85. package/tools/generate_allure_report.ps1 +30 -0
  86. package/tools/generate_api_matrix.py +193 -0
  87. package/tools/generate_tc_from_story.py +182 -0
  88. package/tools/generate_test_reports.ps1 +135 -0
  89. package/tools/git_pre_commit_guard.py +203 -0
  90. package/tools/heal_pr_automator.py +195 -0
  91. package/tools/heuristic_locator_matcher.py +202 -0
  92. package/tools/infer_linter.py +238 -0
  93. package/tools/lint_ai_docs.py +254 -0
  94. package/tools/lookup_tc.py +165 -0
  95. package/tools/lookup_xpath.py +167 -0
  96. package/tools/matrix_sharder.py +146 -0
  97. package/tools/mcp_excel_server.ps1 +251 -0
  98. package/tools/mcp_server.py +490 -0
  99. package/tools/micro_mutator.py +309 -0
  100. package/tools/network_mock_shield.py +170 -0
  101. package/tools/omni_emitter.py +500 -0
  102. package/tools/omni_parser.py +435 -0
  103. package/tools/omni_transpiler.py +168 -0
  104. package/tools/pinpoint_failure.py +88 -0
  105. package/tools/project_state.py +169 -0
  106. package/tools/repo_map.json +12 -0
  107. package/tools/repo_map.py +277 -0
  108. package/tools/repo_map.txt +7 -0
  109. package/tools/safety_firewall.py +245 -0
  110. package/tools/setup_project.py +265 -0
  111. package/tools/smart_visual_masker.py +134 -0
  112. package/tools/test_impact.py +321 -0
  113. package/tools/tms_sync_bridge.py +342 -0
  114. package/tools/trace_data_flow.py +240 -0
  115. package/tools/traffic_session_converter.py +256 -0
  116. package/tools/universal_ir.py +480 -0
  117. package/tools/validate_groovy_scripts.py +173 -0
  118. package/tools/verify_all.py +496 -0
  119. package/tools/verify_all.sh +4 -0
  120. package/tools/visual_diff_canvas.py +142 -0
  121. package/tools/visual_som_helper.py +418 -0
  122. 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())