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
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise AI Documentation & Artifact Linter (Anti-Drift Guard v2.0)
4
+ ---------------------------------------------------------------
5
+ Automatically cross-references AI skills, agent instructions, and markdown
6
+ documents against the actual repository files, classes, methods, and test cases.
7
+
8
+ Checks:
9
+ 1. Page Class references: Matches against Keywords/pages/*.groovy.
10
+ 2. Test Suite references: Matches against Test Suites/*.ts.
11
+ 3. Tool script references: Matches against tools/*.
12
+ 4. Test Case references: Matches against Test Cases/*.tc (and validates TC-1..259 range).
13
+ 5. Method call references: Matches EnterpriseHelper.xxx() and BasePage.xxx() against real AST/regex methods.
14
+
15
+ Prevents AI agents from hallucinating nonexistent PageClasses, suites, tools, methods, or test cases.
16
+ Supports both Target Test Project mode and Hub Repository mode.
17
+ """
18
+
19
+ import os
20
+ import sys
21
+ import glob
22
+ import re
23
+
24
+ # Enforce UTF-8 stdout encoding for Windows console compatibility
25
+ if hasattr(sys.stdout, 'reconfigure'):
26
+ try:
27
+ sys.stdout.reconfigure(encoding='utf-8')
28
+ sys.stderr.reconfigure(encoding='utf-8')
29
+ except Exception:
30
+ pass
31
+
32
+ GENERIC_PAGES = {
33
+ "WebPage", "CurrentPage", "LandingPage", "GitHubPage", "SamplePage",
34
+ "BasePage", "Page", "SinglePage", "MultiPage"
35
+ }
36
+
37
+ def extract_groovy_methods(file_path):
38
+ """Extracts standard public/static methods from a Groovy class file."""
39
+ methods = set()
40
+ if not os.path.exists(file_path):
41
+ return methods
42
+ try:
43
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as fp:
44
+ lines = fp.readlines()
45
+ method_pattern = re.compile(
46
+ r'^\s*(?:@\w+\s+)*(?:public|protected|private)?\s*(?:static\s+)?(?:final\s+)?'
47
+ r'(?:void|def|String|int|boolean|List[^\s]*|TestObject|WebDriver|[A-Za-z0-9_<>\[\]]+)\s+([a-z][a-zA-Z0-9_]*)\s*\('
48
+ )
49
+ for line in lines:
50
+ stripped = line.strip()
51
+ if stripped.startswith("//") or stripped.startswith("*"):
52
+ continue
53
+ m = method_pattern.search(line)
54
+ if m:
55
+ mname = m.group(1)
56
+ if mname not in {"if", "for", "while", "switch", "catch"}:
57
+ methods.add(mname)
58
+ except Exception:
59
+ pass
60
+ return methods
61
+
62
+ def get_actual_entities(repo_root):
63
+ pages_dir = os.path.join(repo_root, "Keywords", "pages")
64
+ helpers_dir = os.path.join(repo_root, "Keywords", "helpers")
65
+ suites_dir = os.path.join(repo_root, "Test Suites")
66
+ cases_dir = os.path.join(repo_root, "Test Cases")
67
+ tools_dir = os.path.join(repo_root, "tools")
68
+
69
+ actual_pages = set()
70
+ class_methods = {}
71
+
72
+ if os.path.isdir(pages_dir):
73
+ for f in os.listdir(pages_dir):
74
+ if f.endswith(".groovy"):
75
+ pname = f[:-7]
76
+ actual_pages.add(pname)
77
+ class_methods[pname] = extract_groovy_methods(os.path.join(pages_dir, f))
78
+
79
+ if os.path.isdir(helpers_dir):
80
+ for f in os.listdir(helpers_dir):
81
+ if f.endswith(".groovy"):
82
+ hname = f[:-7]
83
+ class_methods[hname] = extract_groovy_methods(os.path.join(helpers_dir, f))
84
+
85
+ actual_suites = set()
86
+ if os.path.isdir(suites_dir):
87
+ for f in os.listdir(suites_dir):
88
+ if f.endswith(".ts"):
89
+ actual_suites.add(f[:-3])
90
+
91
+ actual_cases = set()
92
+ actual_tc_ids = set()
93
+ if os.path.isdir(cases_dir):
94
+ for f in os.listdir(cases_dir):
95
+ if f.endswith(".tc"):
96
+ base_name = f[:-3]
97
+ actual_cases.add(base_name)
98
+ m = re.match(r'(TC-\d+)', base_name)
99
+ if m:
100
+ actual_tc_ids.add(m.group(1))
101
+
102
+ actual_tools = set()
103
+ if os.path.isdir(tools_dir):
104
+ actual_tools.update(os.listdir(tools_dir))
105
+ # Always include Hub tools directory in decoupled architecture
106
+ hub_tools = os.path.abspath(os.path.dirname(__file__))
107
+ if os.path.isdir(hub_tools):
108
+ actual_tools.update(os.listdir(hub_tools))
109
+
110
+ return actual_pages, actual_suites, actual_cases, actual_tc_ids, actual_tools, class_methods
111
+
112
+ def find_doc_files(repo_root):
113
+ md_patterns = [
114
+ os.path.join(repo_root, ".agents", "**", "*.md"),
115
+ os.path.join(repo_root, "*.md"),
116
+ ]
117
+ doc_files = []
118
+ for pattern in md_patterns:
119
+ for f in glob.glob(pattern, recursive=True):
120
+ rel = os.path.relpath(f, repo_root)
121
+ if "node_modules" in rel or ".git" in rel or "allure-results" in rel:
122
+ continue
123
+ doc_files.append(rel)
124
+ return sorted(doc_files)
125
+
126
+ def lint_docs(repo_root):
127
+ actual_pages, actual_suites, actual_cases, actual_tc_ids, actual_tools, class_methods = get_actual_entities(repo_root)
128
+ doc_files = find_doc_files(repo_root)
129
+
130
+ page_pattern = re.compile(r'\b([A-Z][A-Za-z0-9]+Page)\b')
131
+ suite_pattern = re.compile(r'(?:Test Suites/|-Suite\s+["\'](?:Test Suites/)?|--testSuitePath=["\'](?:Test Suites/)?)([A-Za-z0-9_]+)')
132
+ tool_pattern = re.compile(r'\btools/([a-zA-Z0-9_\-\.]+\.(?:py|sh|ps1|bat|js))\b')
133
+ tc_id_pattern = re.compile(r'\b(TC-\d+)\b')
134
+ tc_path_pattern = re.compile(r'Test Cases/([A-Za-z0-9_ \-\.\(\)]+?)(?:\.tc)?["\s\)]')
135
+ method_pattern = re.compile(r'\b(EnterpriseHelper|BasePage|[A-Z][a-zA-Z0-9]+Page)\.([a-z][a-zA-Z0-9_]+)\s*\(')
136
+
137
+ errors = []
138
+ warnings = []
139
+
140
+ # Ignored tool references (examples, output targets, artifacts)
141
+ ignored_tool_refs = {"api_matrix.json", "output.json", "report.json", "output.txt", "summary.json"}
142
+
143
+ for doc in doc_files:
144
+ full_path = os.path.join(repo_root, doc)
145
+ try:
146
+ with open(full_path, "r", encoding="utf-8", errors="ignore") as fp:
147
+ lines = fp.readlines()
148
+ except Exception as e:
149
+ errors.append(f"Cannot read {doc}: {e}")
150
+ continue
151
+
152
+ is_historical = "FINAL_REPORT.md" in doc or "POM_REFACTORING_PLAN.md" in doc
153
+
154
+ for line_no, line in enumerate(lines, 1):
155
+ # 1. Page Class Lint (Only strictly enforced if repository defines Page Classes in Keywords/pages)
156
+ if actual_pages:
157
+ for m in page_pattern.finditer(line):
158
+ pname = m.group(1)
159
+ if pname not in actual_pages and pname not in GENERIC_PAGES:
160
+ if is_historical:
161
+ continue
162
+ errors.append(f"[{doc}:{line_no}] Non-existent Page Class referenced: '{pname}' (Must be one of {sorted(actual_pages)})")
163
+
164
+ # 2. Test Suite Lint (Only strictly enforced if repository defines Test Suites)
165
+ if actual_suites:
166
+ for m in suite_pattern.finditer(line):
167
+ sname = m.group(1)
168
+ if "xxx" in sname.lower() or "example" in sname.lower():
169
+ continue
170
+ if is_historical:
171
+ continue
172
+ if sname not in actual_suites:
173
+ errors.append(f"[{doc}:{line_no}] Non-existent Test Suite referenced: '{sname}'")
174
+
175
+ # 3. Tool Script Lint (Enforced in both Hub and Target modes)
176
+ for m in tool_pattern.finditer(line):
177
+ tname = m.group(1)
178
+ if tname in ignored_tool_refs:
179
+ continue
180
+ if tname not in actual_tools:
181
+ errors.append(f"[{doc}:{line_no}] Non-existent tool script referenced: 'tools/{tname}'")
182
+
183
+ # 4. Test Case Validation (Only strictly enforced if repository defines Test Cases)
184
+ if actual_cases and not is_historical:
185
+ # 4a. Validate TC-xxx numeric range
186
+ for m in tc_id_pattern.finditer(line):
187
+ tcid = m.group(1)
188
+ num = int(tcid[3:])
189
+ if num < 1 or num > 259:
190
+ errors.append(f"[{doc}:{line_no}] Out-of-range Test Case ID referenced: '{tcid}' (Valid range: TC-1 to TC-259)")
191
+
192
+ # 4b. Validate full Test Cases/... paths
193
+ for m in tc_path_pattern.finditer(line):
194
+ tcname = m.group(1).strip()
195
+ if "xxx" in tcname.lower() or "*" in tcname or "example" in tcname.lower():
196
+ continue
197
+ if tcname.endswith(".tc"):
198
+ tcname = tcname[:-3]
199
+ if tcname not in actual_cases:
200
+ # Check prefix match
201
+ matching = [c for c in actual_cases if c.startswith(tcname)]
202
+ if not matching:
203
+ errors.append(f"[{doc}:{line_no}] Non-existent Test Case path referenced: 'Test Cases/{tcname}'")
204
+
205
+ # 5. Method Call Lint (Cross-checks Class.method() against Groovy AST/regex)
206
+ for m in method_pattern.finditer(line):
207
+ cname = m.group(1)
208
+ mname = m.group(2)
209
+ if "xxx" in mname.lower() or "example" in mname.lower():
210
+ continue
211
+ if cname in class_methods:
212
+ valid_methods = class_methods[cname]
213
+ # Also include inherited BasePage methods if it's a Page class
214
+ if cname != "BasePage" and "BasePage" in class_methods:
215
+ valid_methods = valid_methods.union(class_methods["BasePage"])
216
+
217
+ if mname not in valid_methods:
218
+ errors.append(f"[{doc}:{line_no}] Non-existent method referenced: '{cname}.{mname}()' (Class '{cname}' does not define this method)")
219
+
220
+ return errors, warnings
221
+
222
+ def main():
223
+ import argparse
224
+ parser = argparse.ArgumentParser(description="Enterprise AI Documentation & Artifact Linter")
225
+ parser.add_argument("target", nargs="?", default=None, help="Path to repository root to lint")
226
+ parser.add_argument("--repo", default=None, help="Path to repository root to lint")
227
+ args = parser.parse_args()
228
+
229
+ target_path = args.repo or args.target
230
+ if target_path:
231
+ repo_root = os.path.abspath(target_path)
232
+ else:
233
+ repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
234
+
235
+ print(f"[AI-DOCS-LINT] Running Documentation & Artifact Linter on: {os.path.basename(repo_root)}")
236
+ errors, warnings = lint_docs(repo_root)
237
+
238
+ if warnings:
239
+ print(f"\n[WARN] {len(warnings)} Warnings:")
240
+ for w in warnings:
241
+ print(f" - {w}")
242
+
243
+ if errors:
244
+ print(f"\n[FAIL] {len(errors)} Lint Errors Found (Documentation Drift / Hallucination):")
245
+ for e in errors:
246
+ print(f" - {e}")
247
+ print("\n[BLOCKED] Verification failed: Please align documentation with real disk entities.")
248
+ return 1
249
+ else:
250
+ print("[PASS] All documented tools, PageClasses, Suites, and Method calls verified on disk.")
251
+ return 0
252
+
253
+ if __name__ == "__main__":
254
+ sys.exit(main())
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise QA: Fast Test Case Metadata Lookup (lookup_tc.py)
4
+ -------------------------------------------------------------
5
+ Fast lookup tool for Test Cases from project JSON testcase specifications.
6
+ Enables AI Agents to load a single targeted test case (<100 tokens)
7
+ instead of reading entire massive test suites (~45,000 tokens).
8
+
9
+ Multi-tier Resolution:
10
+ 1. CLI Flag: python tools/lookup_tc.py <query> --file <path_to_json> (-f)
11
+ 2. Environment variable: TC_DATA_FILE or TESTCASES_JSON
12
+ 3. Automatic discovery: testcases.json, tc_index.json, test_cases.json...
13
+ 4. Internal Hub archive fallback: archive/legacy_references/Enterprise_testcases.json
14
+ """
15
+
16
+ import sys
17
+ import json
18
+ import os
19
+ import argparse
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
+
30
+ def resolve_json_path(cli_file=None):
31
+ """Resolves Test Case JSON file path via precedence order."""
32
+ # 1. CLI flag
33
+ if cli_file:
34
+ if os.path.exists(cli_file):
35
+ return os.path.abspath(cli_file)
36
+ else:
37
+ print(f"⚠️ Warning: File specified via --file '{cli_file}' does not exist.")
38
+
39
+ # 2. Environment variable
40
+ env_file = os.environ.get("TC_DATA_FILE") or os.environ.get("TESTCASES_JSON")
41
+ if env_file and os.path.exists(env_file):
42
+ return os.path.abspath(env_file)
43
+
44
+ # 3. Automatic discovery in CWD and common directories
45
+ script_dir = os.path.dirname(os.path.abspath(__file__))
46
+ hub_root = os.path.abspath(os.path.join(script_dir, ".."))
47
+ cwd = os.getcwd()
48
+
49
+ search_dirs = [
50
+ cwd,
51
+ os.path.join(cwd, "references"),
52
+ os.path.join(cwd, "data"),
53
+ os.path.join(cwd, "tools"),
54
+ os.path.join(cwd, "docs"),
55
+ script_dir
56
+ ]
57
+
58
+ candidate_names = [
59
+ "testcases.json",
60
+ "tc_index.json",
61
+ "Enterprise_testcases.json",
62
+ "test_cases.json",
63
+ "tc_matrix.json"
64
+ ]
65
+
66
+ for d in search_dirs:
67
+ for name in candidate_names:
68
+ candidate = os.path.join(d, name)
69
+ if os.path.isfile(candidate):
70
+ return os.path.abspath(candidate)
71
+
72
+ # 4. Hub Archive fallback
73
+ for legacy_name in ["Enterprise_testcases.json", "dmit_testcases.json", "odp_testcases.json"]:
74
+ legacy_file = os.path.join(hub_root, "archive", "legacy_references", legacy_name)
75
+ if os.path.isfile(legacy_file):
76
+ return os.path.abspath(legacy_file)
77
+
78
+ return None
79
+
80
+
81
+ def normalize_testcase_data(raw_data):
82
+ """Normalizes Test Case data across diverse JSON structures (list, dict, dict of dicts)."""
83
+ if isinstance(raw_data, list):
84
+ return raw_data
85
+ elif isinstance(raw_data, dict):
86
+ if "testcases" in raw_data and isinstance(raw_data["testcases"], list):
87
+ return raw_data["testcases"]
88
+ if "items" in raw_data and isinstance(raw_data["items"], list):
89
+ return raw_data["items"]
90
+
91
+ flat = []
92
+ for k, v in raw_data.items():
93
+ if isinstance(v, dict):
94
+ item = dict(v)
95
+ if "ID" not in item:
96
+ item["ID"] = k
97
+ flat.append(item)
98
+ else:
99
+ flat.append({"ID": k, "Name": str(v)})
100
+ return flat
101
+ return []
102
+
103
+
104
+ def main():
105
+ parser = argparse.ArgumentParser(description="Fast Test Case lookup from project JSON specifications")
106
+ parser.add_argument("query", help="Test Case ID (e.g., TC-132, 132) or search keyword")
107
+ parser.add_argument("-f", "--file", dest="file", help="Path to JSON file containing test cases")
108
+ args = parser.parse_args()
109
+
110
+ json_path = resolve_json_path(args.file)
111
+
112
+ if not json_path:
113
+ print("❌ Error: Test Case JSON data file not found.")
114
+ print("\n👉 Guidance to configure test case data for target project:")
115
+ print(" 1. Use CLI flag: python tools/lookup_tc.py <query> --file <path_to_testcases.json>")
116
+ print(" 2. Set environment variable: export TC_DATA_FILE='path/to/testcases.json' (or set TC_DATA_FILE=...)")
117
+ print(" 3. Place 'testcases.json' in project root or declare path in PROJECT_AI_GUIDE.md")
118
+ sys.exit(1)
119
+
120
+ try:
121
+ with open(json_path, "r", encoding="utf-8") as f:
122
+ raw_data = json.load(f)
123
+ except Exception as e:
124
+ print(f"❌ Error reading file '{json_path}': {e}")
125
+ sys.exit(1)
126
+
127
+ data = normalize_testcase_data(raw_data)
128
+ query = args.query.strip().lower()
129
+
130
+ exact_matches = []
131
+ fuzzy_matches = []
132
+
133
+ for tc in data:
134
+ tc_id = str(tc.get("ID") or tc.get("id") or tc.get("tc_id") or "").strip().lower()
135
+ tc_name = str(tc.get("Name") or tc.get("name") or tc.get("title") or "").strip().lower()
136
+
137
+ if query == tc_id or f"tc-{query}" == tc_id or f"tc_{query}" == tc_id:
138
+ exact_matches.append(tc)
139
+ elif query in tc_id or query in tc_name:
140
+ fuzzy_matches.append(tc)
141
+
142
+ matches = exact_matches if exact_matches else fuzzy_matches
143
+
144
+ if not matches:
145
+ print(f"No Test Cases found matching: '{args.query}' in file '{os.path.basename(json_path)}'")
146
+ sys.exit(0)
147
+
148
+ rel_source = os.path.relpath(json_path, os.getcwd()) if os.path.exists(json_path) else json_path
149
+ print(f"=== Found {len(matches)} Test Case(s) (Source: {rel_source}) ===")
150
+ for tc in matches[:5]:
151
+ tc_id = tc.get("ID") or tc.get("id") or "N/A"
152
+ tc_mod = tc.get("Module") or tc.get("module") or "GLOBAL"
153
+ tc_name = tc.get("Name") or tc.get("name") or tc.get("title") or "N/A"
154
+ tc_expected = tc.get("Expected") or tc.get("expected") or tc.get("expected_result") or "N/A"
155
+ tc_steps = tc.get("Steps") or tc.get("steps") or tc.get("description") or "N/A"
156
+
157
+ print(f"ID: {tc_id} | Module: {tc_mod}")
158
+ print(f"Name: {tc_name}")
159
+ print(f"Expected: {tc_expected}")
160
+ print(f"Steps:\n{tc_steps}")
161
+ print("-" * 50)
162
+
163
+
164
+ if __name__ == "__main__":
165
+ main()
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise QA: Fast XPath / Selector Lookup (lookup_xpath.py)
4
+ -------------------------------------------------------------
5
+ Fast lookup tool for XPaths and selectors from Page Class JSON specifications.
6
+ Enables AI Agents to retrieve only the relevant selectors (<300 tokens)
7
+ instead of reading entire selector repositories or rescanning live DOM.
8
+
9
+ Multi-tier Resolution:
10
+ 1. CLI Flag: python tools/lookup_xpath.py <query> --file <path_to_json> (-f)
11
+ 2. Environment variable: XPATH_DATA_FILE, SELECTORS_JSON, or LOCATORS_JSON
12
+ 3. Automatic discovery: selectors.json, locators.json, scraped_xpaths.json...
13
+ 4. Internal Hub archive fallback: archive/legacy_references/Enterprise_scraped_xpaths.json
14
+ """
15
+
16
+ import sys
17
+ import json
18
+ import os
19
+ import argparse
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
+
30
+ def resolve_json_path(cli_file=None):
31
+ """Resolves Selector/XPath JSON file path via precedence order."""
32
+ # 1. CLI flag
33
+ if cli_file:
34
+ if os.path.exists(cli_file):
35
+ return os.path.abspath(cli_file)
36
+ else:
37
+ print(f"⚠️ Warning: File specified via --file '{cli_file}' does not exist.")
38
+
39
+ # 2. Environment variable
40
+ env_file = os.environ.get("XPATH_DATA_FILE") or os.environ.get("SELECTORS_JSON") or os.environ.get("LOCATORS_JSON")
41
+ if env_file and os.path.exists(env_file):
42
+ return os.path.abspath(env_file)
43
+
44
+ # 3. Automatic discovery in CWD and common directories
45
+ script_dir = os.path.dirname(os.path.abspath(__file__))
46
+ hub_root = os.path.abspath(os.path.join(script_dir, ".."))
47
+ cwd = os.getcwd()
48
+
49
+ search_dirs = [
50
+ cwd,
51
+ os.path.join(cwd, "references"),
52
+ os.path.join(cwd, "data"),
53
+ os.path.join(cwd, "tools"),
54
+ os.path.join(cwd, "docs"),
55
+ script_dir
56
+ ]
57
+
58
+ candidate_names = [
59
+ "selectors.json",
60
+ "scraped_xpaths.json",
61
+ "locators.json",
62
+ "Enterprise_scraped_xpaths.json",
63
+ "page_selectors.json",
64
+ "elements.json"
65
+ ]
66
+
67
+ for d in search_dirs:
68
+ for name in candidate_names:
69
+ candidate = os.path.join(d, name)
70
+ if os.path.isfile(candidate):
71
+ return os.path.abspath(candidate)
72
+
73
+ # 4. Hub Archive fallback
74
+ for legacy_name in ["Enterprise_scraped_xpaths.json", "dmit_scraped_xpaths.json", "batch01_scraped_xpaths.json"]:
75
+ legacy_file = os.path.join(hub_root, "archive", "legacy_references", legacy_name)
76
+ if os.path.isfile(legacy_file):
77
+ return os.path.abspath(legacy_file)
78
+
79
+ return None
80
+
81
+
82
+ def normalize_selector_data(raw_data):
83
+ """Normalizes Selector data across PageClass dictionary or flat list schemas."""
84
+ if isinstance(raw_data, dict):
85
+ return raw_data
86
+ elif isinstance(raw_data, list):
87
+ grouped = {}
88
+ for item in raw_data:
89
+ page = item.get("page") or item.get("page_class") or "GLOBAL"
90
+ if page not in grouped:
91
+ grouped[page] = []
92
+ grouped[page].append(item)
93
+ return grouped
94
+ return {}
95
+
96
+
97
+ def main():
98
+ parser = argparse.ArgumentParser(description="Fast Selector / XPath lookup from project JSON specifications")
99
+ parser.add_argument("query", help="PageClass name or selector identifier keyword (e.g., TargetPlanPage, TXT_CODE)")
100
+ parser.add_argument("-f", "--file", dest="file", help="Path to JSON file containing selector/xpaths")
101
+ args = parser.parse_args()
102
+
103
+ json_path = resolve_json_path(args.file)
104
+
105
+ if not json_path:
106
+ print("❌ Error: Selector / XPath JSON data file not found.")
107
+ print("\n👉 Guidance to configure selector data for target project:")
108
+ print(" 1. Use CLI flag: python tools/lookup_xpath.py <query> --file <path_to_selectors.json>")
109
+ print(" 2. Set environment variable: export XPATH_DATA_FILE='path/to/selectors.json' (or set XPATH_DATA_FILE=...)")
110
+ print(" 3. Place 'selectors.json' in project root or declare path in PROJECT_AI_GUIDE.md")
111
+ sys.exit(1)
112
+
113
+ try:
114
+ with open(json_path, "r", encoding="utf-8") as f:
115
+ raw_data = json.load(f)
116
+ except Exception as e:
117
+ print(f"❌ Error reading file '{json_path}': {e}")
118
+ sys.exit(1)
119
+
120
+ data = normalize_selector_data(raw_data)
121
+ query = args.query.strip()
122
+
123
+ # 1. Match by Page Class name
124
+ matched_page = None
125
+ for page_name in data.keys():
126
+ if query.lower() == page_name.lower():
127
+ matched_page = page_name
128
+ break
129
+
130
+ if matched_page:
131
+ selectors = data[matched_page]
132
+ source_label = os.path.relpath(json_path, os.getcwd()) if os.path.exists(json_path) else json_path
133
+ print(f"=== {matched_page} ({len(selectors)} selectors) [Source: {source_label}] ===")
134
+ for item in selectors:
135
+ c = item.get("constant") or item.get("name") or "SELECTOR"
136
+ tag = item.get("tag") or "element"
137
+ xp = item.get("xpath") or item.get("selector") or item.get("css") or ""
138
+ print(f"- {c} ({tag}): {xp}")
139
+ return
140
+
141
+ # 2. Match keyword across constant or xpath
142
+ found = []
143
+ for page_name, selectors in data.items():
144
+ if isinstance(selectors, list):
145
+ for item in selectors:
146
+ c = item.get("constant") or item.get("name") or ""
147
+ xp = item.get("xpath") or item.get("selector") or item.get("css") or ""
148
+ tag = item.get("tag") or "element"
149
+ if query.lower() in c.lower() or query.lower() in xp.lower():
150
+ found.append((page_name, c, tag, xp))
151
+ elif isinstance(selectors, dict):
152
+ for c, xp in selectors.items():
153
+ if query.lower() in c.lower() or query.lower() in str(xp).lower():
154
+ found.append((page_name, c, "element", str(xp)))
155
+
156
+ if not found:
157
+ print(f"No selectors found matching: '{query}' in file '{os.path.basename(json_path)}'")
158
+ return
159
+
160
+ source_label = os.path.relpath(json_path, os.getcwd()) if os.path.exists(json_path) else json_path
161
+ print(f"=== Found {len(found)} selector(s) for query '{query}' [Source: {source_label}] ===")
162
+ for page, c, tag, xp in found[:15]:
163
+ print(f"[{page}] {c} ({tag}): {xp}")
164
+
165
+
166
+ if __name__ == "__main__":
167
+ main()