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,242 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise QA Project Analyzer & Intelligence Aggregator
4
+ ---------------------------------------------------------
5
+ Consolidates all project diagnostic and AST analysis tools into a single workflow.
6
+ Dispatched when user requests: 'analyze project', 're-analyze', 'audit project'.
7
+
8
+ Executes in sequence:
9
+ 1. Environment & Structure Diagnostic (tools/check_env.py)
10
+ 2. Page Class & AST Repository Mapping (tools/repo_map.py)
11
+ 3. Static Defect & Fake-Pass Sentinel (tools/infer_linter.py)
12
+ 4. Data Flow Lifecycle & Teardown Audit (tools/trace_data_flow.py)
13
+ 5. Definition of Done 7 Quality Gates (tools/verify_all.py)
14
+
15
+ Generates & Updates In-Place inside the Target Project:
16
+ - PROJECT_HEALTH_REPORT.md (Root & reports/ of target project)
17
+ - repo_map.txt (AST method map inside target project root)
18
+ """
19
+
20
+ import os
21
+ import sys
22
+ import json
23
+ import argparse
24
+ from datetime import datetime
25
+ from pathlib import Path
26
+
27
+ # Enforce UTF-8 stdout encoding for Windows console compatibility
28
+ if hasattr(sys.stdout, 'reconfigure'):
29
+ try:
30
+ sys.stdout.reconfigure(encoding='utf-8')
31
+ sys.stderr.reconfigure(encoding='utf-8')
32
+ except Exception:
33
+ pass
34
+
35
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
36
+ HUB_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
37
+ sys.path.insert(0, SCRIPT_DIR)
38
+
39
+ import check_env
40
+ import repo_map
41
+ import infer_linter
42
+ import trace_data_flow
43
+ import verify_all
44
+
45
+ # ANSI formatting
46
+ GREEN = "\033[92m"
47
+ RED = "\033[91m"
48
+ YELLOW = "\033[93m"
49
+ CYAN = "\033[96m"
50
+ BOLD = "\033[1m"
51
+ RESET = "\033[0m"
52
+
53
+
54
+ def resolve_target_repo(cli_target=None, cli_repo=None):
55
+ chosen = cli_repo or cli_target
56
+ if chosen:
57
+ resolved = os.path.abspath(chosen)
58
+ if not os.path.exists(resolved):
59
+ raise FileNotFoundError(f"Target repository path not found: {resolved}")
60
+ return resolved
61
+
62
+ env_dir = os.environ.get("TARGET_PROJECT_DIR")
63
+ if env_dir and os.path.exists(env_dir):
64
+ return os.path.abspath(env_dir)
65
+
66
+ cwd = os.path.abspath(os.getcwd())
67
+ if cwd.lower() != HUB_ROOT.lower():
68
+ return cwd
69
+
70
+ return HUB_ROOT
71
+
72
+
73
+ def run_full_analysis(target_repo):
74
+ """Executes the complete diagnostic and AST intelligence suite."""
75
+ start_time = datetime.now()
76
+ mode = verify_all.detect_repo_mode(target_repo)
77
+
78
+ print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
79
+ print(f"{BOLD}{CYAN} ENTERPRISE QA - COMPREHENSIVE PROJECT ANALYSIS SUITE {RESET}")
80
+ print(f"{BOLD}{CYAN}======================================================================{RESET}")
81
+ print(f"📁 Target Repository : {target_repo}")
82
+ print(f"🏷️ Operational Mode : {mode}")
83
+ print(f"⏱️ Timestamp : {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n")
84
+
85
+ summary = {
86
+ "timestamp": start_time.isoformat(),
87
+ "target_repo": target_repo,
88
+ "mode": mode,
89
+ "env_status": "UNKNOWN",
90
+ "repo_map": {},
91
+ "infer_linter": {},
92
+ "data_flow": {},
93
+ "gates_status": "UNKNOWN"
94
+ }
95
+
96
+ # Step 1: Environment & Project Structure
97
+ print(f"{BOLD}[1/5] Checking Environment & Project Structure (check_env)...{RESET}")
98
+ env_runner = check_env.DiagnosticRunner(target_dir=target_repo)
99
+ env_code = env_runner.run_all()
100
+ summary["env_status"] = "READY" if env_code == 0 else "WARNINGS_OR_MISSING"
101
+
102
+ # Step 2: Page Classes & Method AST Mapping
103
+ print(f"\n{BOLD}[2/5] Mapping Page Classes & API Signatures (repo_map)...{RESET}")
104
+ try:
105
+ map_stats = repo_map.build_repo_map(target_repo=target_repo)
106
+ summary["repo_map"] = {
107
+ "classes_count": map_stats["classes_count"],
108
+ "methods_count": map_stats["methods_count"],
109
+ "source_lines": map_stats["source_lines"],
110
+ "output_file": map_stats["output_file"]
111
+ }
112
+ print(f" -> Indexed {map_stats['classes_count']} Page Classes ({map_stats['methods_count']} API signatures).")
113
+ print(f" -> Condensed context map: {map_stats['output_file']}")
114
+ except Exception as e:
115
+ print(f" ⚠️ repo_map error: {e}")
116
+
117
+ # Step 3: Static Defect & Fake-Pass Sentinel
118
+ print(f"\n{BOLD}[3/5] Scanning Static Defects & Fake-Passes (infer_linter)...{RESET}")
119
+ scripts_dir = os.path.join(target_repo, "Scripts")
120
+ try:
121
+ linter_code = infer_linter.run_infer_linter(target_dir=scripts_dir)
122
+ summary["infer_linter"]["exit_code"] = linter_code
123
+ except Exception as e:
124
+ print(f" ⚠️ infer_linter error: {e}")
125
+
126
+ # Step 4: Data Flow Lifecycle & Teardown Hygiene
127
+ print(f"\n{BOLD}[4/5] Auditing Synthetic Data Lifecycle & Teardown (trace_data_flow)...{RESET}")
128
+ try:
129
+ data_flow_code = trace_data_flow.run_data_flow_tracer(target_dir=scripts_dir, repo_root=target_repo)
130
+ summary["data_flow"]["exit_code"] = data_flow_code
131
+ except Exception as e:
132
+ print(f" ⚠️ trace_data_flow error: {e}")
133
+
134
+ # Step 5: 7 Quality Gates DoD Verification
135
+ print(f"\n{BOLD}[5/5] Verifying 7 Quality Gates DoD (verify_all)...{RESET}")
136
+ gates_passed = True
137
+ gates = [
138
+ ("Gate 1: UTF-8 No BOM", verify_all.gate_1_bom_check),
139
+ ("Gate 2: Path Portability", verify_all.gate_2_path_portability),
140
+ ("Gate 3: Clean POM & Flat Architecture", verify_all.gate_3_clean_pom),
141
+ ("Gate 4: AI Documentation Linter", verify_all.gate_4_ai_docs_lint),
142
+ ("Gate 5: Temporary Files Blacklist", verify_all.gate_5_cleanup_blacklist),
143
+ ("Gate 6: Credential Security", verify_all.gate_6_credential_security),
144
+ ("Gate 7: Static Defect Sentinel", verify_all.gate_7_infer_linter),
145
+ ]
146
+ for name, gate_fn in gates:
147
+ if not gate_fn(target_repo, mode=mode):
148
+ gates_passed = False
149
+
150
+ summary["gates_status"] = "100%_PASS" if gates_passed else "FAILURES_DETECTED"
151
+
152
+ # Generate Markdown Executive Report
153
+ elapsed = (datetime.now() - start_time).total_seconds()
154
+ reports_dir = os.path.join(target_repo, "reports") if mode == "TARGET" else os.path.join(HUB_ROOT, "reports")
155
+ os.makedirs(reports_dir, exist_ok=True)
156
+ report_in_reports = os.path.join(reports_dir, "PROJECT_HEALTH_REPORT.md")
157
+ report_in_root = os.path.join(target_repo, "PROJECT_HEALTH_REPORT.md")
158
+
159
+ md_content = f"""# Project Health & AST Analysis Report
160
+
161
+ - **Target Repository:** `{target_repo}`
162
+ - **Operational Mode:** `{mode}`
163
+ - **Execution Timestamp:** `{start_time.strftime('%Y-%m-%d %H:%M:%S')}` (Completed in `{elapsed:.2f}s`)
164
+
165
+ ---
166
+
167
+ ## 1. Executive Health Metrics
168
+
169
+ | Metric / Standard | Result | Status |
170
+ | :--- | :---: | :---: |
171
+ | **Environment & Structure** | `{summary['env_status']}` | {'✅ PASS' if summary['env_status'] == 'READY' else '⚠️ WARN'} |
172
+ | **Page Classes & Helpers** | `{summary['repo_map'].get('classes_count', 0)}` classes (`{summary['repo_map'].get('methods_count', 0)}` methods) | ✅ INDEXED |
173
+ | **Static Defect Sentinel** | `infer_linter` exit `{summary['infer_linter'].get('exit_code', 0)}` | {'✅ CLEAN' if summary['infer_linter'].get('exit_code', 0) == 0 else '❌ CRITICAL'} |
174
+ | **Lifecycle & Teardown** | `trace_data_flow` exit `{summary['data_flow'].get('exit_code', 0)}` | ✅ AUDITED |
175
+ | **7 Quality Gates DoD** | `{summary['gates_status']}` | {'✅ 100% PASS' if gates_passed else '❌ BLOCKED'} |
176
+
177
+ ---
178
+
179
+ ## 2. In-Project Synchronized Artifacts
180
+
181
+ 1. **Executive Health Summary (This file):** `PROJECT_HEALTH_REPORT.md`
182
+ 2. **Condensed API Signatures Map (< 600 tokens):** `repo_map.txt`
183
+ 3. **AST Static Defect Log:** `reports/infer_linter_report.json`
184
+ 4. **Data Lifecycle Taint Audit:** `reports/data_flow_trace_report.json`
185
+
186
+ ---
187
+
188
+ ## 3. AI Context Anchoring & Post-Audit Protocol (0-Token Overhead)
189
+
190
+ > 📌 **AI Agent Guidelines:**
191
+ > - In subsequent sessions on this project, **DO NOT rescan** the entire codebase from scratch.
192
+ > - Anchor context directly by reading only two local in-project artifacts:
193
+ > 1. `PROJECT_HEALTH_REPORT.md` (immediate architecture status, warnings, and DoD readiness).
194
+ > 2. `repo_map.txt` (instant lookup for Page Classes, methods, and parameters in < 600 tokens).
195
+ > - **Post-Audit Git Push (Mandatory):** Once analysis completes, stage, commit, and push updated in-project artifacts to git (`git add PROJECT_HEALTH_REPORT.md repo_map.txt && git commit -m "docs(audit): update project health report and AST repo map" && git push origin <branch>`).
196
+ > - Whenever the user requests **"analyze project"** or **"project audit"**, re-run `agent-qa analyze` to update this file in-place.
197
+ """
198
+ with open(report_in_reports, "w", encoding="utf-8") as f:
199
+ f.write(md_content)
200
+
201
+ # Save directly at the root of target_repo as well so it's instantly discoverable
202
+ if os.path.abspath(target_repo).lower() != os.path.abspath(HUB_ROOT).lower():
203
+ try:
204
+ with open(report_in_root, "w", encoding="utf-8") as f:
205
+ f.write(md_content)
206
+ except Exception:
207
+ pass
208
+
209
+ print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
210
+ print(f"{BOLD}{CYAN} EXECUTIVE ANALYSIS COMPLETE {RESET}")
211
+ print(f"{BOLD}{CYAN}======================================================================{RESET}")
212
+ print(f" Elapsed Time : {elapsed:.2f}s")
213
+ print(f" Page Classes Indexed : {summary['repo_map'].get('classes_count', 0)} classes")
214
+ print(f" 7 Quality Gates DoD : {GREEN + '100% PASS' if gates_passed else RED + 'FAILED'}{RESET}")
215
+ print(f" In-Project Report Path : {report_in_root if os.path.abspath(target_repo).lower() != os.path.abspath(HUB_ROOT).lower() else report_in_reports}")
216
+ if os.path.abspath(target_repo).lower() != os.path.abspath(HUB_ROOT).lower():
217
+ print(f" {YELLOW}💡 Post-Analysis Action: Push updated files to Git:{RESET}")
218
+ print(f" git add PROJECT_HEALTH_REPORT.md repo_map.txt")
219
+ print(f" git commit -m \"docs(audit): update project health report and AST repo map\"")
220
+ print(f" git push origin <branch>")
221
+ print(f"{BOLD}{CYAN}======================================================================{RESET}\n")
222
+
223
+ return 0 if gates_passed else 1
224
+
225
+
226
+ def main():
227
+ parser = argparse.ArgumentParser(description="Comprehensive Enterprise QA Project Analyzer")
228
+ parser.add_argument("target", nargs="?", default=None, help="Target repository path to analyze")
229
+ parser.add_argument("--repo", "--path", dest="repo_path", default=None, help="Target repository path")
230
+ args = parser.parse_args()
231
+
232
+ try:
233
+ target_repo = resolve_target_repo(cli_target=args.target, cli_repo=args.repo_path)
234
+ except FileNotFoundError as e:
235
+ print(f"❌ Error: {e}")
236
+ return 1
237
+
238
+ return run_full_analysis(target_repo)
239
+
240
+
241
+ if __name__ == "__main__":
242
+ sys.exit(main())
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise API Contract Drift & Breaking Change Sentinel
4
+ ---------------------------------------------------------
5
+ Compares OpenAPI / Swagger specifications (v2 / v3) across versions to detect
6
+ breaking changes (deleted endpoints, removed fields, newly required parameters, type shifts)
7
+ before UI test suites run and fail due to backend contract drift.
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ import json
13
+ import argparse
14
+ from typing import Dict, List, Any, Tuple
15
+
16
+ # Enforce UTF-8 stdout encoding for Windows console compatibility
17
+ if hasattr(sys.stdout, 'reconfigure'):
18
+ try:
19
+ sys.stdout.reconfigure(encoding='utf-8')
20
+ sys.stderr.reconfigure(encoding='utf-8')
21
+ except Exception:
22
+ pass
23
+
24
+
25
+ def load_spec(file_path: str) -> Dict[str, Any]:
26
+ """Loads an OpenAPI/Swagger spec from a JSON file."""
27
+ if not os.path.exists(file_path):
28
+ raise FileNotFoundError(f"Spec file not found: {file_path}")
29
+ with open(file_path, "r", encoding="utf-8") as f:
30
+ return json.load(f)
31
+
32
+
33
+ def compare_specs(old_spec: Dict[str, Any], new_spec: Dict[str, Any]) -> Dict[str, Any]:
34
+ """
35
+ Computes breaking changes and non-breaking contract modifications between two specs.
36
+ """
37
+ breaking: List[Dict[str, str]] = []
38
+ warnings: List[Dict[str, str]] = []
39
+ info: List[Dict[str, str]] = []
40
+
41
+ old_paths = old_spec.get("paths", {})
42
+ new_paths = new_spec.get("paths", {})
43
+
44
+ # 1. Check for removed endpoints (Paths)
45
+ for path, old_methods in old_paths.items():
46
+ if path not in new_paths:
47
+ breaking.append({
48
+ "type": "REMOVED_ENDPOINT",
49
+ "path": path,
50
+ "detail": f"Entire endpoint path was deleted: '{path}'"
51
+ })
52
+ continue
53
+
54
+ new_methods = new_paths[path]
55
+
56
+ # 2. Check for removed HTTP methods on existing path
57
+ for method, old_op in old_methods.items():
58
+ if method.lower() in ("parameters", "$ref", "summary", "description"):
59
+ continue
60
+
61
+ if method not in new_methods:
62
+ breaking.append({
63
+ "type": "REMOVED_HTTP_METHOD",
64
+ "path": f"{method.upper()} {path}",
65
+ "detail": f"HTTP method '{method.upper()}' was deleted from path '{path}'"
66
+ })
67
+ continue
68
+
69
+ new_op = new_methods[method]
70
+
71
+ # 3. Check Parameters
72
+ old_params = {p.get("name"): p for p in old_op.get("parameters", []) if isinstance(p, dict)}
73
+ new_params = {p.get("name"): p for p in new_op.get("parameters", []) if isinstance(p, dict)}
74
+
75
+ # Check for newly added REQUIRED parameters (Breaks existing clients)
76
+ for p_name, p_info in new_params.items():
77
+ if p_name not in old_params and p_info.get("required") is True:
78
+ breaking.append({
79
+ "type": "NEW_REQUIRED_PARAM",
80
+ "path": f"{method.upper()} {path}",
81
+ "detail": f"Added newly required parameter '{p_name}' in '{p_info.get('in', 'query')}'"
82
+ })
83
+
84
+ # Check for changed parameter types or newly required existing parameters
85
+ for p_name, old_p in old_params.items():
86
+ if p_name in new_params:
87
+ new_p = new_params[p_name]
88
+ if not old_p.get("required") and new_p.get("required") is True:
89
+ breaking.append({
90
+ "type": "PARAM_NOW_REQUIRED",
91
+ "path": f"{method.upper()} {path}",
92
+ "detail": f"Parameter '{p_name}' was changed from optional to required"
93
+ })
94
+
95
+ old_type = old_p.get("type") or old_p.get("schema", {}).get("type")
96
+ new_type = new_p.get("type") or new_p.get("schema", {}).get("type")
97
+ if old_type and new_type and old_type != new_type:
98
+ breaking.append({
99
+ "type": "PARAM_TYPE_CHANGED",
100
+ "path": f"{method.upper()} {path}",
101
+ "detail": f"Parameter '{p_name}' type shifted from '{old_type}' to '{new_type}'"
102
+ })
103
+
104
+ # 4. Check Responses (Removed expected status codes)
105
+ old_resps = old_op.get("responses", {})
106
+ new_resps = new_op.get("responses", {})
107
+ for status_code, old_resp in old_resps.items():
108
+ if status_code in ("200", "201", "204") and status_code not in new_resps:
109
+ warnings.append({
110
+ "type": "REMOVED_SUCCESS_RESPONSE",
111
+ "path": f"{method.upper()} {path}",
112
+ "detail": f"Success response code '{status_code}' was removed or modified"
113
+ })
114
+
115
+ # Non-breaking additions
116
+ for path, new_methods in new_paths.items():
117
+ if path not in old_paths:
118
+ info.append({
119
+ "type": "NEW_ENDPOINT_ADDED",
120
+ "path": path,
121
+ "detail": f"New endpoint added: '{path}' (Backward compatible)"
122
+ })
123
+
124
+ health_score = 100.0 if not breaking else max(0.0, 100.0 - (len(breaking) * 20.0))
125
+ is_compatible = len(breaking) == 0
126
+
127
+ return {
128
+ "is_backward_compatible": is_compatible,
129
+ "contract_health_score": health_score,
130
+ "breaking_changes_count": len(breaking),
131
+ "warnings_count": len(warnings),
132
+ "additions_count": len(info),
133
+ "breaking_changes": breaking,
134
+ "warnings": warnings,
135
+ "additions": info
136
+ }
137
+
138
+
139
+ def generate_markdown_report(analysis: Dict[str, Any], old_path: str, new_path: str) -> str:
140
+ status_icon = "✅ COMPATIBLE" if analysis["is_backward_compatible"] else "❌ BREAKING DRIFT DETECTED"
141
+ lines = [
142
+ "# API Contract Drift & Breaking Change Report",
143
+ f"- **Baseline Spec:** `{old_path}`",
144
+ f"- **Current Spec:** `{new_path}`",
145
+ f"- **Verdict:** {status_icon} (Health Score: `{analysis['contract_health_score']:.1f}%`)",
146
+ "",
147
+ "## Summary Metrics",
148
+ f"| Breaking Changes | Warnings | Compatible Additions |",
149
+ f"| :---: | :---: | :---: |",
150
+ f"| **{analysis['breaking_changes_count']}** | **{analysis['warnings_count']}** | **{analysis['additions_count']}** |",
151
+ ""
152
+ ]
153
+
154
+ if analysis["breaking_changes"]:
155
+ lines.append("## ❌ Breaking Changes (Action Required)")
156
+ lines.append("| Type | Endpoint / Method | Root Cause Detail |")
157
+ lines.append("| :--- | :--- | :--- |")
158
+ for b in analysis["breaking_changes"]:
159
+ lines.append(f"| `{b['type']}` | `{b['path']}` | {b['detail']} |")
160
+ lines.append("")
161
+
162
+ if analysis["warnings"]:
163
+ lines.append("## ⚠️ Warnings & Potential Regressions")
164
+ for w in analysis["warnings"]:
165
+ lines.append(f"- **{w['path']}**: {w['detail']}")
166
+ lines.append("")
167
+
168
+ if analysis["additions"]:
169
+ lines.append("## ℹ️ Compatible New Additions")
170
+ for a in analysis["additions"]:
171
+ lines.append(f"- **{a['path']}**: {a['detail']}")
172
+
173
+ return "\n".join(lines)
174
+
175
+
176
+ def main():
177
+ parser = argparse.ArgumentParser(description="API Contract Drift & Schema Diff Sentinel")
178
+ parser.add_argument("--old", "--baseline", dest="old_spec", required=True, help="Path to baseline OpenAPI/Swagger JSON")
179
+ parser.add_argument("--new", "--current", dest="new_spec", required=True, help="Path to current OpenAPI/Swagger JSON")
180
+ parser.add_argument("--json", action="store_true", help="Output raw JSON results")
181
+ parser.add_argument("-o", "--output", dest="output_file", default=None, help="Save report to file")
182
+ args = parser.parse_args()
183
+
184
+ try:
185
+ old_data = load_spec(args.old_spec)
186
+ new_data = load_spec(args.new_spec)
187
+ except Exception as e:
188
+ print(f"❌ Error loading specs: {e}")
189
+ return 1
190
+
191
+ analysis = compare_specs(old_data, new_data)
192
+ md_report = generate_markdown_report(analysis, args.old_spec, args.new_spec)
193
+
194
+ if args.output_file:
195
+ with open(args.output_file, "w", encoding="utf-8") as f:
196
+ f.write(md_report)
197
+ print(f"✅ Drift report saved to: {args.output_file}")
198
+
199
+ if args.json:
200
+ print(json.dumps(analysis, indent=2))
201
+ else:
202
+ print(md_report)
203
+
204
+ return 0 if analysis["is_backward_compatible"] else 1
205
+
206
+
207
+ if __name__ == "__main__":
208
+ sys.exit(main())
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise Closed-Loop Autonomous Test Healer Engine
4
+ -----------------------------------------------------
5
+ Diagnoses test failures in 0.1s from JUnit XML and terminal logs, queries the
6
+ semantic memory cache for proven patches, and orchestrates targeted re-tests.
7
+ Dispatched automatically by AI Agents when test runs encounter failures.
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ import re
13
+ import json
14
+ import argparse
15
+ from pathlib import Path
16
+
17
+ # Enforce UTF-8 stdout encoding for Windows console compatibility
18
+ if hasattr(sys.stdout, 'reconfigure'):
19
+ try:
20
+ sys.stdout.reconfigure(encoding='utf-8')
21
+ sys.stderr.reconfigure(encoding='utf-8')
22
+ except Exception:
23
+ pass
24
+
25
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
26
+ HUB_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
27
+ sys.path.insert(0, SCRIPT_DIR)
28
+
29
+ import pinpoint_failure
30
+ import cache_resolver
31
+ import project_state
32
+ import heuristic_locator_matcher
33
+
34
+ GREEN = "\033[92m"
35
+ RED = "\033[91m"
36
+ YELLOW = "\033[93m"
37
+ CYAN = "\033[96m"
38
+ BOLD = "\033[1m"
39
+ RESET = "\033[0m"
40
+
41
+
42
+ def diagnose_and_plan_heal(target_dir, junit_xml_path=None):
43
+ """
44
+ Parses test failure, checks memory cache, updates project state watchpoints,
45
+ and returns a structured healing plan.
46
+ """
47
+ print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
48
+ print(f"{BOLD}{CYAN} AUTONOMOUS CLOSED-LOOP TEST HEALING SENTINEL {RESET}")
49
+ print(f"{BOLD}{CYAN}======================================================================{RESET}")
50
+ print(f"📁 Target Project : {target_dir}")
51
+
52
+ # 1. Pinpoint failure from JUnit XML or logs
53
+ failure_info = None
54
+ if junit_xml_path and os.path.exists(junit_xml_path):
55
+ failures = pinpoint_failure.parse_junit_xml(junit_xml_path)
56
+ if failures:
57
+ failure_info = failures[0]
58
+
59
+ if not failure_info:
60
+ # Search for any recent report in Reports/
61
+ reports_dir = os.path.join(target_dir, "Reports")
62
+ if os.path.exists(reports_dir):
63
+ for root, _, files in os.walk(reports_dir):
64
+ for f in files:
65
+ if f.endswith(".xml"):
66
+ parsed = pinpoint_failure.parse_junit_xml(os.path.join(root, f))
67
+ if parsed:
68
+ failure_info = parsed[0]
69
+ break
70
+ if failure_info:
71
+ break
72
+
73
+ if not failure_info:
74
+ print(" [INFO] Zero active JUnit XML failures detected on disk.")
75
+ return {"status": "NO_FAILURE_DETECTED"}
76
+
77
+ err_sig = failure_info.get("message", failure_info.get("type", "UnknownError"))
78
+ test_name = failure_info.get("name", "UnknownTest")
79
+ failing_file = failure_info.get("file", "UnknownFile")
80
+ line_num = failure_info.get("line", "?")
81
+
82
+ print(f" ❌ Failing Test : {test_name}")
83
+ print(f" 📍 Source Location: {failing_file}:{line_num}")
84
+ print(f" ⚠️ Error Signature: {err_sig[:120]}...\n")
85
+
86
+ # 2. Query 0-token memory cache & 3-layer heuristic matcher
87
+ print(f"{BOLD}[1/2] Checking 3-Layer Heuristic Matcher & Memory Cache...{RESET}")
88
+ cache_solution = cache_resolver.query_cache(err_sig, threshold=0.35)
89
+
90
+ # 2b. Query Episodic Reflexion Memory (Top 2 historical lessons)
91
+ lessons = cache_resolver.query_lessons(f"{test_name} {err_sig}", limit=2)
92
+
93
+ plan = {
94
+ "status": "HEALING_PLAN_READY",
95
+ "test_name": test_name,
96
+ "failing_file": failing_file,
97
+ "line": line_num,
98
+ "error_signature": err_sig,
99
+ "cache_match": bool(cache_solution),
100
+ "heuristic_match": None,
101
+ "recommended_patch": None,
102
+ "episodic_lessons": [l["lesson_summary"] for l in lessons]
103
+ }
104
+
105
+ if lessons:
106
+ print(f" {CYAN}🧠 [EPISODIC REFLEXION] Top Historical Lessons for Context:{RESET}")
107
+ for idx, l in enumerate(lessons, 1):
108
+ print(f" {idx}. [{l['page_context']}]: {l['lesson_summary']}")
109
+
110
+ if cache_solution:
111
+ print(f" {GREEN}[MATCH] Found cached resolution (Score: {cache_solution['score']:.2f}):{RESET}")
112
+ print(f" 💡 Prescribed Fix : {cache_solution['solution']}")
113
+ plan["recommended_patch"] = cache_solution["solution"]
114
+ else:
115
+ # Check if error message contains broken locator pattern
116
+ loc_candidate_m = re.search(r"locator\(['\"]([^'\"]+)['\"]\)|By\.[a-zA-Z]+\(['\"]([^'\"]+)['\"]\)", err_sig)
117
+ if loc_candidate_m:
118
+ broken_loc = loc_candidate_m.group(1) or loc_candidate_m.group(2)
119
+ heuristic_res = heuristic_locator_matcher.HeuristicLocatorMatcher.match(broken_loc, dom_snapshot=[])
120
+ plan["heuristic_match"] = heuristic_res
121
+ if heuristic_res.get("matched"):
122
+ print(f" {GREEN}[HEURISTIC L{heuristic_res['layer']}] 0-Token match ({heuristic_res['confidence']*100:.1f}%): {heuristic_res['healed_locator']}{RESET}")
123
+ plan["recommended_patch"] = f"Update locator to: {heuristic_res['healed_locator']}"
124
+ else:
125
+ print(" [MISS] Heuristic confidence < 85%. Fallback to Live DOM exploration.")
126
+ plan["recommended_patch"] = "Inspect element using Playwright Live Explorer and apply resilient locator / smart wait."
127
+ else:
128
+ print(" [MISS] No direct cache match. AI Agent will inspect live DOM/AOM via Playwright.")
129
+ plan["recommended_patch"] = "Inspect element using Playwright Live Explorer and apply resilient locator / smart wait."
130
+
131
+ # 3. Anchor watchpoint in PROJECT_STATE.md
132
+ print(f"\n{BOLD}[2/2] Anchoring Watchpoint in PROJECT_STATE.md...{RESET}")
133
+ try:
134
+ project_state.update_state(
135
+ target_dir=target_dir,
136
+ watchpoint=f"{test_name} -> {failing_file}:{line_num}",
137
+ gate_status="HEALING_IN_PROGRESS"
138
+ )
139
+ print(" [OK] Project state watchpoint updated.")
140
+ except Exception as e:
141
+ print(f" ⚠️ Could not update PROJECT_STATE.md: {e}")
142
+
143
+ print(f"{BOLD}{CYAN}======================================================================{RESET}\n")
144
+ return plan
145
+
146
+
147
+ def record_heal_lesson(page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary):
148
+ """Saves a 2-line practical lesson learned into SQLite after successful healing."""
149
+ return cache_resolver.record_lesson(
150
+ page_context=page_context,
151
+ error_signature=error_signature,
152
+ root_cause=root_cause,
153
+ failed_attempt=failed_attempt,
154
+ successful_fix=successful_fix,
155
+ lesson_summary=lesson_summary
156
+ )
157
+
158
+
159
+ def main():
160
+ parser = argparse.ArgumentParser(description="Autonomous Closed-Loop Test Healer")
161
+ parser.add_argument("target", nargs="?", default=os.getcwd(), help="Target project root directory")
162
+ parser.add_argument("--xml", default=None, help="Path to JUnit XML test report")
163
+ parser.add_argument("--record-lesson", default=None, help="Record a 2-line lesson learned after healing")
164
+ parser.add_argument("--context", default="GLOBAL", help="Page context for the recorded lesson")
165
+ parser.add_argument("--error", default="UnknownError", help="Error signature for the recorded lesson")
166
+ parser.add_argument("--fix", default="", help="Successful fix for the recorded lesson")
167
+ args = parser.parse_args()
168
+
169
+ if args.record_lesson:
170
+ ok = record_heal_lesson(
171
+ page_context=args.context,
172
+ error_signature=args.error,
173
+ root_cause="Autonomous Healer Diagnosis",
174
+ failed_attempt="",
175
+ successful_fix=args.fix,
176
+ lesson_summary=args.record_lesson
177
+ )
178
+ if ok:
179
+ print(f"✅ [EPISODIC REFLEXION] Lesson recorded for '{args.context}': {args.record_lesson}")
180
+ return 0
181
+
182
+ target_dir = os.path.abspath(args.target)
183
+ plan = diagnose_and_plan_heal(target_dir, junit_xml_path=args.xml)
184
+ print(json.dumps(plan, indent=2))
185
+ return 0
186
+
187
+
188
+ if __name__ == "__main__":
189
+ sys.exit(main())