synapse-hub 2.1.1 → 2.2.1

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 (62) hide show
  1. package/.agents/agents/orchestrator.md +7 -0
  2. package/.agents/rules/lessons-learned.md +1 -0
  3. package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +1 -1
  4. package/.agents/skills/antigravity-test-sentinel/SKILL.md +1 -2
  5. package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
  6. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +11 -4
  7. package/.agents/skills/architecture-decision-records/SKILL.md +1 -1
  8. package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +1 -1
  9. package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +32 -11
  10. package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
  11. package/.agents/skills/clean-pom-crafter/SKILL.md +1 -4
  12. package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +1 -1
  13. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +1 -1
  14. package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
  15. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +1 -3
  16. package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
  17. package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +1 -1
  18. package/.agents/skills/github-actions-templates/SKILL.md +1 -1
  19. package/.agents/skills/graphify-impact-analyzer/SKILL.md +1 -3
  20. package/.agents/skills/katalon-cli-runner/SKILL.md +1 -1
  21. package/.agents/skills/lam-script-generator/SKILL.md +1 -2
  22. package/.agents/skills/playwright-live-interaction/SKILL.md +1 -4
  23. package/.agents/skills/security-dast-sentinel/SKILL.md +1 -1
  24. package/.agents/skills/self-healing-locator/SKILL.md +1 -2
  25. package/.agents/skills/skillspector-security-guard/SKILL.md +115 -0
  26. package/.agents/skills/synthetic-test-data-generator/SKILL.md +1 -1
  27. package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
  28. package/.agents/skills/visual-regression-sentinel/SKILL.md +1 -1
  29. package/.agents/skills/wcag-audit-patterns/SKILL.md +1 -1
  30. package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
  31. package/AGENTS.md +36 -0
  32. package/LICENSE +22 -0
  33. package/README.md +171 -107
  34. package/bin/agent-qa.js +4 -0
  35. package/package.json +3 -2
  36. package/skills_manifest.json +42 -29
  37. package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +15 -1
  38. package/templates/ai_bridges/CLAUDE.md +31 -0
  39. package/templates/ai_bridges/copilot-instructions.md +25 -0
  40. package/templates/ai_bridges/cursor_synapse_qa.mdc +26 -0
  41. package/templates/ai_bridges/windsurfrules +25 -0
  42. package/templates/ci/github-actions-synapse.yml +28 -0
  43. package/templates/ci/gitlab-ci-synapse.yml +16 -0
  44. package/tools/TOOLS_RUNBOOK.md +223 -12
  45. package/tools/author_integrity.py +151 -0
  46. package/tools/browser_helpers.js +2 -2
  47. package/tools/bump_version.py +8 -0
  48. package/tools/cli.py +100 -4
  49. package/tools/export_postman_collection.py +535 -218
  50. package/tools/generate_jmx_plan.py +672 -102
  51. package/tools/git_pre_commit_guard.py +21 -0
  52. package/tools/jtl_analyzer.py +474 -0
  53. package/tools/mcp_excel_server.ps1 +1 -1
  54. package/tools/mcp_server.py +1 -1
  55. package/tools/newman_analyzer.py +385 -0
  56. package/tools/setup_project.py +203 -18
  57. package/tools/skillspector.py +1145 -0
  58. package/tools/telemetry_correlator.py +351 -0
  59. package/tools/templates/jmeter-distributed/README.md +54 -0
  60. package/tools/templates/jmeter-distributed/docker-compose.yml +83 -0
  61. package/tools/update_sentinel.py +313 -0
  62. package/tools/verify_all.py +30 -1
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ tools/newman_analyzer.py — Enterprise Newman Test Results Analyzer & CI/CD Quality Gate
5
+ ---------------------------------------------------------------------------------------
6
+ Parses Postman / Newman CLI execution reports (JSON format), calculates statistical
7
+ pass/fail rates, triages failed assertions with root causes, evaluates latency SLAs,
8
+ and enforces deterministic Quality Gates in CI/CD pipelines (Exit Code 0 vs 1).
9
+
10
+ Features:
11
+ 1. Universal Newman JSON Ingestion: Reads standard `--reporters json` exports.
12
+ 2. Multi-Metric Aggregation:
13
+ - Total Executions, Passed/Failed Requests, Error Rate (%)
14
+ - Total pm.test Assertions, Failed Assertions, Pass Ratio (%)
15
+ - Latency statistics (Average, Min, Max response time in ms)
16
+ 3. Failure Triage & RCA Engine:
17
+ - Isolates exact failing Request Name, HTTP Method, and URL
18
+ - Highlights exact `pm.test` assertion name and Chai error details
19
+ - Distinguishes between HTTP 4xx/5xx crashes vs Schema / Assertion mismatches
20
+ 4. CI/CD SLA Quality Gate:
21
+ - `--max-failures <int>`: Zero-tolerance or configurable failure threshold
22
+ - `--sla-avg <ms>`: Average latency SLA threshold
23
+ - Returns deterministic Exit Code 0 on PASS, 1 on FAIL
24
+ 5. PR Reporting: Generates clean GitHub/GitLab Markdown reports and JSON telemetry.
25
+
26
+ Usage:
27
+ newman run collection.json -e env.json -r cli,json --reporter-json-export report.json
28
+ python tools/newman_analyzer.py report.json --max-failures 0 --sla-avg 500
29
+ agent-qa analyze-newman report.json --markdown pr_api_report.md
30
+ """
31
+
32
+ import os
33
+ import sys
34
+ import json
35
+ import argparse
36
+ from typing import Dict, List, Any, Optional, Tuple
37
+
38
+ # Enforce UTF-8 stdout encoding for Windows/macOS/Linux console compatibility
39
+ if hasattr(sys.stdout, "reconfigure"):
40
+ try:
41
+ sys.stdout.reconfigure(encoding="utf-8")
42
+ sys.stderr.reconfigure(encoding="utf-8")
43
+ except Exception:
44
+ pass
45
+
46
+
47
+ def parse_newman_json(report_path: str) -> Dict[str, Any]:
48
+ """Parses standard Newman JSON execution report."""
49
+ if not os.path.exists(report_path):
50
+ raise FileNotFoundError(f"Newman report file not found: {report_path}")
51
+
52
+ with open(report_path, "r", encoding="utf-8", errors="replace") as f:
53
+ data = json.load(f)
54
+
55
+ run = data.get("run", {})
56
+ stats = run.get("stats", {})
57
+ timings = run.get("timings", {})
58
+ executions = run.get("executions", [])
59
+ failures = run.get("failures", [])
60
+
61
+ # Stats extraction
62
+ req_stats = stats.get("requests", {})
63
+ total_requests = req_stats.get("total", 0)
64
+ failed_requests = req_stats.get("failed", 0)
65
+ passed_requests = total_requests - failed_requests
66
+
67
+ assert_stats = stats.get("assertions", {})
68
+ total_assertions = assert_stats.get("total", 0)
69
+ failed_assertions = assert_stats.get("failed", 0)
70
+ passed_assertions = total_assertions - failed_assertions
71
+
72
+ # Latencies
73
+ response_times = run.get("timings", {}).get("responseAverage", 0)
74
+ min_time = run.get("timings", {}).get("responseMin", 0)
75
+ max_time = run.get("timings", {}).get("responseMax", 0)
76
+
77
+ # Detailed Request Breakdown
78
+ request_rows: List[Dict[str, Any]] = []
79
+ for ex in executions:
80
+ item = ex.get("item", {})
81
+ req = ex.get("request", {})
82
+ resp = ex.get("response", {})
83
+
84
+ name = item.get("name", "Unknown Request")
85
+ method = req.get("method", "GET")
86
+ url_obj = req.get("url", {})
87
+ url_str = url_obj if isinstance(url_obj, str) else url_obj.get("raw", "/")
88
+ status_code = resp.get("code", 0) if resp else 0
89
+ resp_time = resp.get("responseTime", 0) if resp else 0
90
+
91
+ # Assertions inside this execution
92
+ assertions_list = ex.get("assertions", [])
93
+ ex_assert_total = len(assertions_list)
94
+ ex_assert_failed = sum(1 for a in assertions_list if a.get("error"))
95
+
96
+ request_rows.append({
97
+ "name": name,
98
+ "method": method,
99
+ "url": url_str,
100
+ "status_code": status_code,
101
+ "response_time_ms": resp_time,
102
+ "assertions_total": ex_assert_total,
103
+ "assertions_failed": ex_assert_failed,
104
+ "passed": (ex_assert_failed == 0 and status_code < 400 and status_code > 0)
105
+ })
106
+
107
+ # Triage Failures
108
+ triaged_failures: List[Dict[str, str]] = []
109
+ for f in failures:
110
+ err = f.get("error", {})
111
+ source = f.get("source", {})
112
+ cursor = f.get("cursor", {})
113
+
114
+ req_name = source.get("name", cursor.get("ref", "Request"))
115
+ assert_name = err.get("test", err.get("name", "Assertion Failure"))
116
+ message = err.get("message", "Unknown assertion error")
117
+
118
+ triaged_failures.append({
119
+ "request_name": req_name,
120
+ "assertion_name": assert_name,
121
+ "message": message
122
+ })
123
+
124
+ error_rate = (failed_requests / total_requests * 100.0) if total_requests > 0 else 0.0
125
+ pass_ratio = (passed_assertions / total_assertions * 100.0) if total_assertions > 0 else 100.0
126
+
127
+ return {
128
+ "collection_name": data.get("collection", {}).get("info", {}).get("name", "Postman Collection"),
129
+ "total_requests": total_requests,
130
+ "failed_requests": failed_requests,
131
+ "passed_requests": passed_requests,
132
+ "error_rate_pct": round(error_rate, 2),
133
+ "total_assertions": total_assertions,
134
+ "failed_assertions": failed_assertions,
135
+ "passed_assertions": passed_assertions,
136
+ "assertion_pass_ratio_pct": round(pass_ratio, 2),
137
+ "latency": {
138
+ "avg_ms": round(float(response_times), 2),
139
+ "min_ms": round(float(min_time), 2),
140
+ "max_ms": round(float(max_time), 2)
141
+ },
142
+ "requests": request_rows,
143
+ "failures": triaged_failures
144
+ }
145
+
146
+
147
+ def evaluate_newman_sla(
148
+ summary: Dict[str, Any],
149
+ max_failures: int = 0,
150
+ sla_avg: Optional[float] = None
151
+ ) -> Tuple[bool, List[str]]:
152
+ """Evaluates computed Newman summary against CI/CD SLA gates."""
153
+ violations: List[str] = []
154
+
155
+ total_failed = summary["failed_requests"] + summary["failed_assertions"]
156
+ if total_failed > max_failures:
157
+ violations.append(f"Failed Tests SLA Breached: {total_failed} failures > allowable limit ({max_failures})")
158
+
159
+ avg_latency = summary["latency"]["avg_ms"]
160
+ if sla_avg is not None and avg_latency > sla_avg:
161
+ violations.append(f"Average Latency SLA Breached: {avg_latency}ms > {sla_avg}ms threshold")
162
+
163
+ passed = (len(violations) == 0)
164
+ return passed, violations
165
+
166
+
167
+ def format_cli_table(summary: Dict[str, Any]) -> str:
168
+ """Formats requests into a clean CLI table."""
169
+ headers = ["Method", "Request Name", "Status", "Latency", "Asserts (Pass/Fail)"]
170
+ col_w = [8, 36, 8, 12, 20]
171
+
172
+ lines = []
173
+ header_str = " | ".join(h.ljust(col_w[i]) for i, h in enumerate(headers))
174
+ sep_str = "-+-".join("-" * col_w[i] for i in range(len(headers)))
175
+
176
+ lines.append(header_str)
177
+ lines.append(sep_str)
178
+
179
+ for r in summary["requests"]:
180
+ req_name = r["name"]
181
+ if len(req_name) > 34:
182
+ req_name = req_name[:31] + "..."
183
+ assert_info = f"{r['assertions_total'] - r['assertions_failed']} / {r['assertions_total']}"
184
+ status_display = str(r["status_code"]) if r["status_code"] > 0 else "FAIL"
185
+
186
+ lines.append(" | ".join([
187
+ f"[{r['method']}]".ljust(col_w[0]),
188
+ req_name.ljust(col_w[1]),
189
+ status_display.center(col_w[2]),
190
+ f"{r['response_time_ms']} ms".rjust(col_w[3]),
191
+ assert_info.center(col_w[4])
192
+ ]))
193
+
194
+ return "\n".join(lines)
195
+
196
+
197
+ def generate_markdown_report(summary: Dict[str, Any], passed: bool, violations: List[str]) -> str:
198
+ """Generates Pull Request Markdown report."""
199
+ status_badge = "✅ **PASSED (100% QUALITY GATE MET)**" if passed else "❌ **FAILED (QUALITY GATE BREACHED)**"
200
+
201
+ lines = [
202
+ f"# 📬 Postman / Newman API Automated Test Report: `{summary['collection_name']}`",
203
+ "",
204
+ f"- **Quality Gate Status:** {status_badge}",
205
+ f"- **Total Requests Executed:** `{summary['total_requests']}` ({summary['failed_requests']} failed)",
206
+ f"- **Total Assertions Checked:** `{summary['total_assertions']}` ({summary['failed_assertions']} failed)",
207
+ f"- **Assertion Pass Ratio:** `{summary['assertion_pass_ratio_pct']}%`",
208
+ f"- **Average Response Latency:** `{summary['latency']['avg_ms']} ms` (Min: `{summary['latency']['min_ms']} ms`, Max: `{summary['latency']['max_ms']} ms`)",
209
+ "",
210
+ ]
211
+
212
+ if violations:
213
+ lines.extend([
214
+ "### ⚠️ CI/CD Quality Gate Violations",
215
+ ""
216
+ ])
217
+ for v in violations:
218
+ lines.append(f"- 🔴 `{v}`")
219
+ lines.append("")
220
+
221
+ if summary["failures"]:
222
+ lines.extend([
223
+ "### 🔍 Failed Assertions Triage (Root Cause)",
224
+ "",
225
+ "| Request Name | Failed pm.test Assertion | Error Details |",
226
+ "| :--- | :--- | :--- |"
227
+ ])
228
+ for f in summary["failures"]:
229
+ lines.append(f"| `{f['request_name']}` | **{f['assertion_name']}** | `{f['message']}` |")
230
+ lines.append("")
231
+
232
+ lines.extend([
233
+ "### 📊 Detailed Endpoint Execution Breakdown",
234
+ "",
235
+ "| Method | Request Name | Status Code | Latency | Assertions | Result |",
236
+ "| :---: | :--- | :---: | :---: | :---: | :---: |"
237
+ ])
238
+
239
+ for r in summary["requests"]:
240
+ verdict = "✅ PASS" if r["passed"] else "❌ FAIL"
241
+ lines.append(f"| `{r['method']}` | {r['name']} | `{r['status_code']}` | `{r['response_time_ms']} ms` | `{r['assertions_total'] - r['assertions_failed']}/{r['assertions_total']}` | {verdict} |")
242
+
243
+ lines.extend([
244
+ "",
245
+ "---",
246
+ "*Report auto-generated by Synapse-Hub `newman_analyzer.py` - Enterprise API Quality Gate.*"
247
+ ])
248
+
249
+ return "\n".join(lines)
250
+
251
+
252
+ def main():
253
+ parser = argparse.ArgumentParser(
254
+ description="Postman / Newman Test Results Analyzer & CI/CD Quality Gatekeeper."
255
+ )
256
+ parser.add_argument("report_file", nargs="?", help="Path to Newman JSON report file (--reporters json).")
257
+ parser.add_argument("--max-failures", type=int, default=0, help="Allowable failed requests / assertions threshold. Default: 0.")
258
+ parser.add_argument("--sla-avg", type=float, default=None, help="Average Latency threshold in ms (fails if exceeded).")
259
+ parser.add_argument("--markdown", "-m", help="Path to export Markdown report.")
260
+ parser.add_argument("--json", "-j", help="Path to export JSON summary.")
261
+ parser.add_argument("--sample", action="store_true", help="Generate and analyze a mock Newman test dataset.")
262
+
263
+ args = parser.parse_args()
264
+
265
+ if args.sample or not args.report_file:
266
+ import tempfile
267
+ temp_file = os.path.join(tempfile.gettempdir(), "sample_newman_report.json")
268
+ sample_data = {
269
+ "collection": {"info": {"name": "Enterprise Banking & Billing API"}},
270
+ "run": {
271
+ "stats": {
272
+ "requests": {"total": 4, "failed": 0},
273
+ "assertions": {"total": 12, "failed": 1}
274
+ },
275
+ "timings": {
276
+ "responseAverage": 185.5,
277
+ "responseMin": 120.0,
278
+ "responseMax": 340.0
279
+ },
280
+ "executions": [
281
+ {
282
+ "item": {"name": "1. User Authentication (Login)"},
283
+ "request": {"method": "POST", "url": {"raw": "https://api.staging.enterprise.local/api/v1/auth/login"}},
284
+ "response": {"code": 200, "responseTime": 145},
285
+ "assertions": [
286
+ {"assertion": "Status code is 200", "error": None},
287
+ {"assertion": "Response time is below 500ms", "error": None},
288
+ {"assertion": "Response matches OpenAPI JSON Schema", "error": None}
289
+ ]
290
+ },
291
+ {
292
+ "item": {"name": "2. Get Account Balance"},
293
+ "request": {"method": "GET", "url": {"raw": "https://api.staging.enterprise.local/api/v1/accounts/me"}},
294
+ "response": {"code": 200, "responseTime": 120},
295
+ "assertions": [
296
+ {"assertion": "Status code is 200", "error": None},
297
+ {"assertion": "Response matches OpenAPI JSON Schema", "error": None}
298
+ ]
299
+ },
300
+ {
301
+ "item": {"name": "3. Transfer Funds (Dynamic Idempotency)"},
302
+ "request": {"method": "POST", "url": {"raw": "https://api.staging.enterprise.local/api/v1/transfers"}},
303
+ "response": {"code": 201, "responseTime": 340},
304
+ "assertions": [
305
+ {"assertion": "Status code is 201", "error": None},
306
+ {"assertion": "Response matches OpenAPI JSON Schema", "error": None},
307
+ {"assertion": "Transfer status is COMPLETED", "error": {"message": "expected 'PENDING' to equal 'COMPLETED'"}}
308
+ ]
309
+ },
310
+ {
311
+ "item": {"name": "4. Audit Transaction Log"},
312
+ "request": {"method": "GET", "url": {"raw": "https://api.staging.enterprise.local/api/v1/transfers/last"}},
313
+ "response": {"code": 200, "responseTime": 137},
314
+ "assertions": [
315
+ {"assertion": "Status code is 200", "error": None},
316
+ {"assertion": "Response matches OpenAPI JSON Schema", "error": None}
317
+ ]
318
+ }
319
+ ],
320
+ "failures": [
321
+ {
322
+ "source": {"name": "3. Transfer Funds (Dynamic Idempotency)"},
323
+ "error": {
324
+ "test": "Transfer status is COMPLETED",
325
+ "message": "expected 'PENDING' to equal 'COMPLETED'"
326
+ }
327
+ }
328
+ ]
329
+ }
330
+ }
331
+ with open(temp_file, "w", encoding="utf-8") as f:
332
+ json.dump(sample_data, f, indent=2)
333
+ args.report_file = temp_file
334
+ print(f"🔬 [SAMPLE MODE] Generated synthetic Newman report: {temp_file}")
335
+
336
+ summary = parse_newman_json(args.report_file)
337
+ passed, violations = evaluate_newman_sla(
338
+ summary,
339
+ max_failures=args.max_failures,
340
+ sla_avg=args.sla_avg
341
+ )
342
+
343
+ print("\n" + "=" * 90)
344
+ print(f" POSTMAN / NEWMAN API AUTOMATED TEST MATRIX: {summary['collection_name']}")
345
+ print("=" * 90)
346
+ print(format_cli_table(summary))
347
+ print("=" * 90)
348
+ print(f"Total Requests : {summary['total_requests']} (Passed: {summary['passed_requests']} | Failed: {summary['failed_requests']})")
349
+ print(f"Total Assertions : {summary['total_assertions']} (Passed: {summary['passed_assertions']} | Failed: {summary['failed_assertions']})")
350
+ print(f"Pass Ratio : {summary['assertion_pass_ratio_pct']}% | Avg Latency: {summary['latency']['avg_ms']} ms")
351
+ print("-" * 90)
352
+
353
+ # Print SLA Verdict
354
+ print(f"🎯 [QUALITY GATE SLA VERDICT]")
355
+ if passed:
356
+ print("✅ ALL API FUNCTIONAL & SLA GATES PASSED! Safe to merge/deploy.")
357
+ else:
358
+ print("❌ API QUALITY GATE BREACHED:")
359
+ for v in violations:
360
+ print(f" 🔴 {v}")
361
+ if summary["failures"]:
362
+ print("\n🔍 Failing Assertions Breakdown:")
363
+ for f in summary["failures"]:
364
+ print(f" • [{f['request_name']}] -> {f['assertion_name']}: {f['message']}")
365
+
366
+ # Export Markdown
367
+ if args.markdown:
368
+ md = generate_markdown_report(summary, passed, violations)
369
+ os.makedirs(os.path.dirname(os.path.abspath(args.markdown)) or ".", exist_ok=True)
370
+ with open(args.markdown, "w", encoding="utf-8") as f:
371
+ f.write(md)
372
+ print(f"\n📄 Markdown Report exported: {args.markdown}")
373
+
374
+ # Export JSON
375
+ if args.json:
376
+ os.makedirs(os.path.dirname(os.path.abspath(args.json)) or ".", exist_ok=True)
377
+ with open(args.json, "w", encoding="utf-8") as f:
378
+ json.dump(summary, f, indent=2)
379
+ print(f"📊 JSON Metrics exported: {args.json}")
380
+
381
+ sys.exit(0 if passed else 1)
382
+
383
+
384
+ if __name__ == "__main__":
385
+ main()
@@ -8,8 +8,10 @@ Dispatched automatically by AI Agents (Zero-Command Protocol) or via 'agent-qa s
8
8
  Automated actions:
9
9
  1. Detects project tech stack (Katalon, Playwright, Cypress, Pytest).
10
10
  2. Generates tailored 'PROJECT_AI_GUIDE.md' if missing.
11
- 3. Installs automated Git Pre-Push Hook (.git/hooks/pre-push) for zero-leakage protection.
12
- 4. Triggers 5-in-1 Health Audit (tools/analyze_project.py):
11
+ 3. Deploys Universal AI Bridges (.cursor/rules, CLAUDE.md, .github/copilot-instructions.md, .windsurfrules).
12
+ 4. Injects Antigravity Global Plugin (~/.gemini/config/plugins/synapse-hub).
13
+ 5. Installs automated Git Pre-Push Hook (.git/hooks/pre-push) for zero-leakage protection.
14
+ 6. Triggers 5-in-1 Health Audit (tools/analyze_project.py):
13
15
  - Maps AST method signatures (repo_map.txt).
14
16
  - Scans fake passes (infer_linter).
15
17
  - Audits database teardown hygiene (trace_data_flow).
@@ -54,17 +56,29 @@ def resolve_target_repo(cli_target=None, cli_repo=None):
54
56
  resolved = os.path.abspath(chosen)
55
57
  if not os.path.exists(resolved):
56
58
  raise FileNotFoundError(f"Target repository path not found: {resolved}")
59
+ if resolved.lower() == HUB_ROOT.lower():
60
+ print(f"{RED}{BOLD}X [CWD CONFUSION GUARD] Target repository cannot be Synapse-Hub itself!{RESET}")
61
+ print(" Synapse-Hub is the centralized skill hub, not an automation target project.")
62
+ print(" Please specify a target project: python tools/setup_project.py --target /path/to/target-project")
63
+ sys.exit(1)
57
64
  return resolved
58
65
 
59
66
  env_dir = os.environ.get("TARGET_PROJECT_DIR")
60
67
  if env_dir and os.path.exists(env_dir):
61
- return os.path.abspath(env_dir)
68
+ resolved_env = os.path.abspath(env_dir)
69
+ if resolved_env.lower() == HUB_ROOT.lower():
70
+ print(f"{RED}{BOLD}X [CWD CONFUSION GUARD] TARGET_PROJECT_DIR points to Synapse-Hub itself!{RESET}")
71
+ sys.exit(1)
72
+ return resolved_env
62
73
 
63
74
  cwd = os.path.abspath(os.getcwd())
64
75
  if cwd.lower() != HUB_ROOT.lower():
65
76
  return cwd
66
77
 
67
- return HUB_ROOT
78
+ print(f"{RED}{BOLD}X [CWD CONFUSION GUARD] Current working directory is Synapse-Hub!{RESET}")
79
+ print(" You must run setup inside your target project folder or supply --target:")
80
+ print(" python tools/setup_project.py --target <path_to_target_project>")
81
+ sys.exit(1)
68
82
 
69
83
 
70
84
  def detect_tech_stack(target_dir):
@@ -175,19 +189,27 @@ def install_git_pre_push_hook(target_dir):
175
189
  analyze_script = os.path.join(SCRIPT_DIR, "analyze_project.py").replace("\\", "/")
176
190
 
177
191
  hook_content = f"""#!/bin/sh
178
- # Automated Zero-Leakage Pre-Push Hook (Installed by Enterprise Agent-QA)
179
- # Automatically enforces 7 Quality Gates and syncs project health before pushing.
180
-
181
- echo "\\n[AI-GUARD] Running Pre-Push Verification (7 Quality Gates DoD)..."
182
- python3 "{verify_all_script}" || python "{verify_all_script}"
192
+ # Automated Zero-Leakage Pre-Push Hook (Installed by Synapse-Hub)
193
+ # Enforces 8 Quality Gates DoD and syncs project intelligence before pushing.
194
+
195
+ echo "\\n[AI-GUARD] Running Pre-Push Verification (8 Quality Gates DoD)..."
196
+ if command -v npx >/dev/null 2>&1; then
197
+ npx synapse-hub verify
198
+ elif [ -f "{verify_all_script}" ]; then
199
+ python3 "{verify_all_script}" || python "{verify_all_script}"
200
+ fi
183
201
  if [ $? -ne 0 ]; then
184
- echo "\\n[AI-GUARD - REJECTED] Git Push aborted: 7 Quality Gates verification failed!"
185
- echo "Please fix defects or run 'agent-qa verify' locally before pushing.\\n"
202
+ echo "\\n[AI-GUARD - REJECTED] Git Push aborted: Quality Gates verification failed!"
203
+ echo "Please fix defects or run 'npx synapse-hub verify' locally before pushing.\\n"
186
204
  exit 1
187
205
  fi
188
206
 
189
- echo "[AI-GUARD - PASSED] 7 Quality Gates satisfied. Syncing project health report..."
190
- python3 "{analyze_script}" || python "{analyze_script}"
207
+ echo "[AI-GUARD - PASSED] 8 Quality Gates satisfied. Syncing project health report..."
208
+ if command -v npx >/dev/null 2>&1; then
209
+ npx synapse-hub analyze
210
+ elif [ -f "{analyze_script}" ]; then
211
+ python3 "{analyze_script}" || python "{analyze_script}"
212
+ fi
191
213
  echo "[AI-GUARD - READY] Pushing to remote...\\n"
192
214
  exit 0
193
215
  """
@@ -208,6 +230,151 @@ exit 0
208
230
  return False
209
231
 
210
232
 
233
+ def inject_antigravity_global_plugin():
234
+ """
235
+ Automatically injects Synapse-Hub into Antigravity's Global Plugin Directory
236
+ (~/.gemini/config/plugins/synapse-hub).
237
+ Ensures that running `npx synapse-hub setup` activates the 27 skills and 6 subagents
238
+ globally across ALL projects in Google Antigravity.
239
+ """
240
+ try:
241
+ plugins_dir = os.path.expanduser("~/.gemini/config/plugins")
242
+ target_plugin_dir = os.path.join(plugins_dir, "synapse-hub")
243
+ os.makedirs(target_plugin_dir, exist_ok=True)
244
+
245
+ # 1. plugin.json
246
+ src_plugin_json = os.path.join(HUB_ROOT, "plugin.json")
247
+ dst_plugin_json = os.path.join(target_plugin_dir, "plugin.json")
248
+ if os.path.exists(src_plugin_json):
249
+ if not os.path.exists(dst_plugin_json) or not os.path.samefile(src_plugin_json, dst_plugin_json):
250
+ shutil.copy2(src_plugin_json, dst_plugin_json)
251
+
252
+ # 2. README.md
253
+ src_readme = os.path.join(HUB_ROOT, "README.md")
254
+ dst_readme = os.path.join(target_plugin_dir, "README.md")
255
+ if os.path.exists(src_readme):
256
+ if not os.path.exists(dst_readme) or not os.path.samefile(src_readme, dst_readme):
257
+ shutil.copy2(src_readme, dst_readme)
258
+
259
+ # Helper to sync directory (junction or copy)
260
+ def sync_dir(src, dst):
261
+ if not os.path.exists(src):
262
+ return
263
+ if os.path.exists(dst) and os.path.samefile(src, dst):
264
+ return
265
+ if os.path.islink(dst):
266
+ try:
267
+ os.unlink(dst)
268
+ except Exception:
269
+ pass
270
+ if sys.platform == "win32":
271
+ try:
272
+ import _winapi
273
+ if not os.path.exists(dst):
274
+ _winapi.CreateJunction(src, dst)
275
+ return
276
+ except Exception:
277
+ pass
278
+ shutil.copytree(src, dst, dirs_exist_ok=True, ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.tmp"))
279
+
280
+ sync_dir(os.path.join(HUB_ROOT, ".agents", "skills"), os.path.join(target_plugin_dir, "skills"))
281
+ sync_dir(os.path.join(HUB_ROOT, ".agents", "agents"), os.path.join(target_plugin_dir, "agents"))
282
+ sync_dir(os.path.join(HUB_ROOT, "tools"), os.path.join(target_plugin_dir, "tools"))
283
+
284
+ # 3. Rules (AGENTS.md)
285
+ dst_rules = os.path.join(target_plugin_dir, "rules")
286
+ os.makedirs(dst_rules, exist_ok=True)
287
+ src_agents_md = os.path.join(HUB_ROOT, "AGENTS.md")
288
+ dst_agents_md = os.path.join(dst_rules, "AGENTS.md")
289
+ if os.path.exists(src_agents_md):
290
+ if not os.path.exists(dst_agents_md) or not os.path.samefile(src_agents_md, dst_agents_md):
291
+ shutil.copy2(src_agents_md, dst_agents_md)
292
+
293
+ print(f" {GREEN}[OK] Injected Antigravity Global Plugin:{RESET} {target_plugin_dir}")
294
+ return True
295
+ except Exception as e:
296
+ print(f" ⚠️ Antigravity Global Plugin notice: {e}")
297
+ return False
298
+
299
+
300
+ def install_universal_ai_bridges(target_dir):
301
+ """
302
+ Deploys Universal AI Bridge configuration files into the target project workspace
303
+ for seamless integration with Cursor, Claude Code, GitHub Copilot, and Windsurf.
304
+ """
305
+ bridges_dir = os.path.join(TEMPLATES_DIR, "ai_bridges")
306
+ if not os.path.exists(bridges_dir):
307
+ return False
308
+
309
+ deployed = []
310
+
311
+ # 1. Cursor (.cursor/rules/synapse-qa.mdc)
312
+ cursor_dir = os.path.join(target_dir, ".cursor", "rules")
313
+ os.makedirs(cursor_dir, exist_ok=True)
314
+ src_cursor = os.path.join(bridges_dir, "cursor_synapse_qa.mdc")
315
+ dst_cursor = os.path.join(cursor_dir, "synapse-qa.mdc")
316
+ if os.path.exists(src_cursor):
317
+ shutil.copy2(src_cursor, dst_cursor)
318
+ deployed.append("Cursor (.cursor/rules/synapse-qa.mdc)")
319
+
320
+ # 2. Claude Code (CLAUDE.md)
321
+ dst_claude = os.path.join(target_dir, "CLAUDE.md")
322
+ src_claude = os.path.join(bridges_dir, "CLAUDE.md")
323
+ if os.path.exists(src_claude):
324
+ if not os.path.exists(dst_claude):
325
+ shutil.copy2(src_claude, dst_claude)
326
+ deployed.append("Claude Code (CLAUDE.md)")
327
+ else:
328
+ with open(dst_claude, "r", encoding="utf-8", errors="ignore") as f:
329
+ c_content = f.read()
330
+ if "Synapse-Hub" not in c_content:
331
+ with open(src_claude, "r", encoding="utf-8") as f:
332
+ append_data = f.read()
333
+ with open(dst_claude, "a", encoding="utf-8") as f:
334
+ f.write("\n\n" + append_data)
335
+ deployed.append("Claude Code (appended to CLAUDE.md)")
336
+
337
+ # 3. GitHub Copilot (.github/copilot-instructions.md)
338
+ github_dir = os.path.join(target_dir, ".github")
339
+ os.makedirs(github_dir, exist_ok=True)
340
+ dst_copilot = os.path.join(github_dir, "copilot-instructions.md")
341
+ src_copilot = os.path.join(bridges_dir, "copilot-instructions.md")
342
+ if os.path.exists(src_copilot):
343
+ if not os.path.exists(dst_copilot):
344
+ shutil.copy2(src_copilot, dst_copilot)
345
+ deployed.append("GitHub Copilot (.github/copilot-instructions.md)")
346
+ else:
347
+ with open(dst_copilot, "r", encoding="utf-8", errors="ignore") as f:
348
+ cp_content = f.read()
349
+ if "Synapse-Hub" not in cp_content:
350
+ with open(src_copilot, "r", encoding="utf-8") as f:
351
+ append_data = f.read()
352
+ with open(dst_copilot, "a", encoding="utf-8") as f:
353
+ f.write("\n\n" + append_data)
354
+ deployed.append("GitHub Copilot (appended to copilot-instructions.md)")
355
+
356
+ # 4. Windsurf (.windsurfrules)
357
+ dst_windsurf = os.path.join(target_dir, ".windsurfrules")
358
+ src_windsurf = os.path.join(bridges_dir, "windsurfrules")
359
+ if os.path.exists(src_windsurf):
360
+ if not os.path.exists(dst_windsurf):
361
+ shutil.copy2(src_windsurf, dst_windsurf)
362
+ deployed.append("Windsurf (.windsurfrules)")
363
+ else:
364
+ with open(dst_windsurf, "r", encoding="utf-8", errors="ignore") as f:
365
+ w_content = f.read()
366
+ if "Synapse-Hub" not in w_content:
367
+ with open(src_windsurf, "r", encoding="utf-8") as f:
368
+ append_data = f.read()
369
+ with open(dst_windsurf, "a", encoding="utf-8") as f:
370
+ f.write("\n\n" + append_data)
371
+ deployed.append("Windsurf (appended to .windsurfrules)")
372
+
373
+ if deployed:
374
+ print(f" {GREEN}[OK] Deployed Universal AI Bridges:{RESET} {', '.join(deployed)}")
375
+ return True
376
+
377
+
211
378
  def run_setup(target_repo):
212
379
  """Main execution entrypoint for automated project setup."""
213
380
  print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
@@ -221,16 +388,32 @@ def run_setup(target_repo):
221
388
  print(f"📂 Structure Scope : Pages: `{stack_info['page_dir']}` | Tests: `{stack_info['test_dir']}`\n")
222
389
 
223
390
  # 2. Ensure PROJECT_AI_GUIDE.md & PROJECT_STATE.md
224
- print(f"{BOLD}[1/3] Checking Project AI Guide & State Anchor...{RESET}")
391
+ print(f"{BOLD}[1/5] Checking Project AI Guide & State Anchor...{RESET}")
225
392
  guide_created = ensure_project_ai_guide(target_repo, stack_info)
226
393
  project_state.init_state(target_repo)
227
394
 
228
- # 3. Install Pre-Push Git Hook
229
- print(f"\n{BOLD}[2/3] Installing Zero-Leakage Git Pre-Push Hook...{RESET}")
395
+ # 3. Deploy Universal AI Bridges (Cursor, Claude Code, GitHub Copilot, Windsurf)
396
+ print(f"\n{BOLD}[2/5] Deploying Universal AI Bridges (Cursor, Claude, Copilot, Windsurf)...{RESET}")
397
+ bridges_deployed = install_universal_ai_bridges(target_repo)
398
+
399
+ # 4. Inject Antigravity Global Plugin
400
+ print(f"\n{BOLD}[3/5] Injecting Antigravity Global Plugin (~/.gemini/config/plugins)...{RESET}")
401
+ plugin_injected = inject_antigravity_global_plugin()
402
+
403
+ # 4.1 Initialize Central Agent Memory (SQLite Cache)
404
+ try:
405
+ import cache_resolver
406
+ cache_resolver.init_db(seed=True)
407
+ print(f" {GREEN}[OK] Initialized Central Agent Memory (SQLite):{RESET} ~/.agent_memory/global_cache.db")
408
+ except Exception as e:
409
+ print(f" ⚠️ Could not initialize memory cache: {e}")
410
+
411
+ # 5. Install Pre-Push Git Hook
412
+ print(f"\n{BOLD}[4/5] Installing Zero-Leakage Git Pre-Push Hook...{RESET}")
230
413
  hook_installed = install_git_pre_push_hook(target_repo)
231
414
 
232
- # 4. Run 5-in-1 Comprehensive Health & AST Analysis
233
- print(f"\n{BOLD}[3/3] Executing Initial Health Audit & AST Mapping...{RESET}")
415
+ # 6. Run 5-in-1 Comprehensive Health & AST Analysis
416
+ print(f"\n{BOLD}[5/5] Executing Initial Health Audit & AST Mapping...{RESET}")
234
417
  analysis_code = analyze_project.run_full_analysis(target_repo)
235
418
 
236
419
  print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
@@ -238,6 +421,8 @@ def run_setup(target_repo):
238
421
  print(f"{BOLD}{CYAN}======================================================================{RESET}")
239
422
  print(f" Target Framework : {stack_info['framework']}")
240
423
  print(f" AI Project Guide : {os.path.join(target_repo, 'PROJECT_AI_GUIDE.md')}")
424
+ print(f" Universal Bridges : Cursor (.cursor/rules), Claude Code, Copilot, Windsurf")
425
+ print(f" Antigravity Plugin : {'ACTIVE (~/.gemini/config/plugins/synapse-hub)' if plugin_injected else 'STANDALONE'}")
241
426
  print(f" AST Method Map : {os.path.join(target_repo, 'repo_map.txt')}")
242
427
  print(f" Health Report : {os.path.join(target_repo, 'PROJECT_HEALTH_REPORT.md')}")
243
428
  print(f" Pre-Push Sentinel : {'ACTIVE (.git/hooks/pre-push)' if hook_installed else 'MANUAL (No .git folder)'}")