synapse-hub 2.6.0 → 3.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 (143) hide show
  1. package/.agents/agents/failure-analyst.md +4 -0
  2. package/.agents/agents/live-explorer.md +2 -0
  3. package/.agents/agents/orchestrator.md +16 -0
  4. package/.agents/agents/pom-crafter.md +10 -0
  5. package/.agents/agents/quality-gatekeeper.md +5 -0
  6. package/.agents/agents/test-runner.md +8 -0
  7. package/.agents/rules/git-push-protocol.md +1 -1
  8. package/.agents/rules/graphify.md +2 -0
  9. package/.agents/rules/mandatory-playwright-live.md +9 -9
  10. package/.agents/rules/mobile_appium_rules.md +8 -1
  11. package/.agents/skills/actor-qa-swarm/SKILL.md +77 -0
  12. package/.agents/skills/actor-qa-swarm/examples/broker_sharding_sample.json +12 -0
  13. package/.agents/skills/agentless-qa-repair/SKILL.md +69 -0
  14. package/.agents/skills/agentless-qa-repair/examples/healing_report_sample.json +17 -0
  15. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +1 -1
  16. package/.agents/skills/clean-pom-crafter/SKILL.md +2 -2
  17. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +4 -4
  18. package/.agents/skills/event-stream-qa-orchestrator/SKILL.md +79 -0
  19. package/.agents/skills/event-stream-qa-orchestrator/examples/timeline_sample.jsonl +2 -0
  20. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +2 -2
  21. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +6 -21
  22. package/.agents/skills/katalon-cli-runner/SKILL.md +1 -1
  23. package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
  24. package/.agents/skills/mipro-prompt-compiler/SKILL.md +59 -0
  25. package/.agents/skills/mipro-prompt-compiler/examples/compiled_module_sample.json +22 -0
  26. package/.agents/skills/qa-aci-interface/SKILL.md +67 -0
  27. package/.agents/skills/qa-aci-interface/examples/surgical_edit_sample.json +12 -0
  28. package/.agents/skills/qa-memory-os/SKILL.md +80 -0
  29. package/.agents/skills/qa-memory-os/examples/archival_memory_sample.json +21 -0
  30. package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
  31. package/.agents/skills/review-spec/SKILL.md +60 -0
  32. package/.agents/skills/review-spec/examples/sample_spec_review.md +33 -0
  33. package/.agents/skills/senior-code-review/SKILL.md +1 -1
  34. package/.agents/skills/synthetic-test-data-generator/SKILL.md +2 -2
  35. package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
  36. package/.agents/skills/tot-exploratory-testing/SKILL.md +59 -0
  37. package/.agents/skills/tot-exploratory-testing/examples/tot_graph_sample.json +35 -0
  38. package/.agents/skills/wcag-audit-patterns/SKILL.md +2 -2
  39. package/.agents/skills/web-agent-gym-benchmarking/SKILL.md +62 -0
  40. package/.agents/skills/web-agent-gym-benchmarking/examples/benchmark_report_sample.json +22 -0
  41. package/CLAUDE.md +2 -2
  42. package/README.en.md +70 -291
  43. package/README.md +68 -303
  44. package/docs/ARCHITECTURE.md +54 -8
  45. package/docs/CONTRIBUTING.md +1 -1
  46. package/docs/QUALITY_GATES.md +113 -0
  47. package/docs/research/EXTERNAL_AGENT_SKILLS_SURVEY.md +1062 -0
  48. package/package.json +1 -1
  49. package/scripts/package_dist.py +2 -1
  50. package/skills_manifest.json +92 -11
  51. package/synapse.config.json +1 -1
  52. package/templates/ai_bridges/CLAUDE.md +2 -2
  53. package/templates/ai_bridges/copilot-instructions.md +2 -2
  54. package/tools/TOOLS_RUNBOOK.md +1 -1
  55. package/tools/__init__.py +18 -1
  56. package/tools/_openapi_parser.py +158 -0
  57. package/tools/agent_canary.py +42 -30
  58. package/tools/agentless_patcher.py +417 -0
  59. package/tools/analyze_project.py +12 -11
  60. package/tools/api_drift_sentinel.py +4 -4
  61. package/tools/api_fuzzer.py +8 -8
  62. package/tools/auto_heal.py +23 -5
  63. package/tools/auto_heal_reflexion.py +2 -2
  64. package/tools/autogen_qa_broker.py +318 -0
  65. package/tools/browser_helpers.js +2 -2
  66. package/tools/browsergym_eval.py +236 -0
  67. package/tools/bug_reporter.py +11 -13
  68. package/tools/bump_version.py +75 -5
  69. package/tools/cache_resolver.py +78 -83
  70. package/tools/check_env.py +9 -1
  71. package/tools/check_version_sync.py +40 -3
  72. package/tools/ci_healing_hook.py +5 -7
  73. package/tools/cli.py +5 -7
  74. package/tools/config_decoupler.py +2 -3
  75. package/tools/core/__init__.py +52 -0
  76. package/tools/count_tokens.py +22 -20
  77. package/tools/dashboard_generator.py +628 -999
  78. package/tools/db_sandbox.py +69 -53
  79. package/tools/debt_harvester.py +3 -3
  80. package/tools/distill_log.py +3 -3
  81. package/tools/dspy_qa_signatures.py +3 -4
  82. package/tools/export_postman_collection.py +8 -8
  83. package/tools/flaky_detector.py +2 -2
  84. package/tools/fuzz_payloads.py +5 -5
  85. package/tools/generate_allure_report.py +1 -1
  86. package/tools/generate_jmx_plan.py +4 -4
  87. package/tools/gherkin_suite.py +1 -1
  88. package/tools/git_pre_commit_guard.py +2 -2
  89. package/tools/goldfish_review.py +2 -2
  90. package/tools/heal/__init__.py +28 -0
  91. package/tools/heal_pr_automator.py +29 -4
  92. package/tools/heuristic_locator_matcher.py +2 -2
  93. package/tools/infer_linter.py +8 -8
  94. package/tools/interview_story.py +0 -1
  95. package/tools/jtl_analyzer.py +11 -11
  96. package/tools/lint/__init__.py +30 -0
  97. package/tools/lint_ai_docs.py +42 -0
  98. package/tools/lookup_tc.py +1 -1
  99. package/tools/matrix_sharder.py +15 -13
  100. package/tools/mcp/__init__.py +16 -0
  101. package/tools/mcp_excel_server.py +2 -2
  102. package/tools/mcp_server.py +3 -4
  103. package/tools/md_to_xlsx.py +26 -26
  104. package/tools/micro_mutator.py +11 -12
  105. package/tools/mipro_assertion_tuner.py +207 -0
  106. package/tools/network_mock_shield.py +27 -27
  107. package/tools/newman_analyzer.py +2 -3
  108. package/tools/omni_emitter.py +5 -8
  109. package/tools/omni_parser.py +5 -5
  110. package/tools/omni_transpiler.py +5 -5
  111. package/tools/output_formatter.py +19 -0
  112. package/tools/perf/__init__.py +24 -0
  113. package/tools/pinpoint_failure.py +13 -1
  114. package/tools/project_state.py +4 -2
  115. package/tools/qa_event_stream.py +336 -0
  116. package/tools/repo_map.py +8 -8
  117. package/tools/report/__init__.py +32 -0
  118. package/tools/reversible_sandbox.py +5 -2
  119. package/tools/safety_firewall.py +2 -2
  120. package/tools/senior_code_review.py +2 -2
  121. package/tools/setup_project.py +10 -12
  122. package/tools/skill_collision_guard.py +3 -3
  123. package/tools/skill_vault.py +12 -3
  124. package/tools/skillspector.py +5 -7
  125. package/tools/smart_visual_masker.py +4 -6
  126. package/tools/spec_drift_sentinel.py +1 -2
  127. package/tools/swe_qa_aci.py +369 -0
  128. package/tools/synapse_qa_bench.py +3 -3
  129. package/tools/sync_agent_rules.py +18 -4
  130. package/tools/telemetry_correlator.py +3 -2
  131. package/tools/test_memory_os.py +419 -0
  132. package/tools/textgrad_mutation_optimizer.py +3 -3
  133. package/tools/tms_sync_bridge.py +5 -5
  134. package/tools/tot_exploratory_tester.py +239 -0
  135. package/tools/traffic_session_converter.py +9 -9
  136. package/tools/universal_ir.py +2 -3
  137. package/tools/update_sentinel.py +6 -9
  138. package/tools/verify_all.py +10 -0
  139. package/tools/visual_diff_canvas.py +1 -2
  140. package/tools/visual_som_helper.py +15 -13
  141. package/tools/watch_mode.py +1 -1
  142. package/tools/wcag_audit.py +22 -1
  143. package/tools/welcome_page.py +5 -6
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-FileCopyrightText: Copyright (c) 2026 Thế Vị (@TeeVee998). All rights reserved.
3
+ # SPDX-License-Identifier: MIT
4
+ # Project: Synapse-Hub (https://github.com/TeeVee998/Synapse-Hub)
5
+
6
+ """
7
+ Tree/Graph-of-Thoughts Exploratory Testing Engine (Princeton ToT & ETH GoT Pattern)
8
+ ----------------------------------------------------------------------------------
9
+ Expands linear user stories into deliberative non-linear exploration trees:
10
+ 1. Multi-Branch Deliberation: Generates Happy Path, Boundary/Race, and Negative/Chaos branches.
11
+ 2. Risk-Scored Beam Search: Evaluates thought states and prunes low-risk tautological paths.
12
+ 3. Clean POM Synthesis: Produces structured test scenarios ready for Clean POM Page Classes.
13
+ 4. Multi-Hypothesis RCA: Diagnoses test failures across competing failure hypotheses (DOM_TIMEOUT, ASYNC_RACE, DB_LOCK, BACKEND_500).
14
+
15
+ Usage:
16
+ python tools/tot_exploratory_tester.py expand-story --story-file docs/stories/US_PAYMENT.md --max-depth 3 --beam-width 3
17
+ python tools/tot_exploratory_tester.py rca-hypotheses --error-log reports/failed.log
18
+ """
19
+
20
+ import os
21
+ import sys
22
+ import re
23
+ import json
24
+ import argparse
25
+ from typing import Dict, Any, List, Optional, cast
26
+
27
+ # Enforce UTF-8 stdout encoding
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
+
36
+ class TotExploratoryTester:
37
+ """Tree-of-Thoughts Engine for Exploratory QA and Root Cause Analysis."""
38
+
39
+ def __init__(self, repo_root: Optional[str] = None):
40
+ self.repo_root = repo_root or os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
41
+
42
+ def expand_story(self, story_content: str, max_depth: int = 3, beam_width: int = 3) -> Dict[str, Any]:
43
+ """Decomposes story into a 3-branch Tree of Thoughts with risk scores."""
44
+ # Extract title and criteria
45
+ lines = [line.strip() for line in story_content.splitlines() if line.strip()]
46
+ title = lines[0] if lines else "Untitled User Story"
47
+ if title.startswith("#"):
48
+ title = title.lstrip("#").strip()
49
+
50
+ # Extract keywords
51
+ keywords = re.findall(r'\b[a-zA-Z]{4,}\b', story_content.lower())
52
+ is_checkout = any(k in keywords for k in ["cart", "checkout", "pay", "order", "price"])
53
+ is_auth = any(k in keywords for k in ["login", "auth", "token", "password", "sso"])
54
+
55
+ context_domain = "FINANCIAL_CHECKOUT" if is_checkout else ("AUTH_SESSION" if is_auth else "GENERAL_CRUD")
56
+
57
+ # 3 Branches:
58
+ # 1. Happy Path
59
+ # 2. Boundary / Session Interruption / Race Condition
60
+ # 3. Negative / Chaos / Backend Fault
61
+ branches = [
62
+ {
63
+ "branch_id": "BRANCH_HAPPY_PATH",
64
+ "name": "Standard Happy Business Path",
65
+ "risk_score": 0.25,
66
+ "thoughts": [
67
+ {"step": 1, "thought": "Navigate to initial entity view and ensure clean pre-conditions."},
68
+ {"step": 2, "thought": "Populate valid payload attributes and trigger submit action."},
69
+ {"step": 3, "thought": "Verify success confirmation modal and assert database record creation."}
70
+ ]
71
+ },
72
+ {
73
+ "branch_id": "BRANCH_BOUNDARY_RACE",
74
+ "name": "Boundary, Session Interruption & Concurrency Path",
75
+ "risk_score": 0.88,
76
+ "thoughts": [
77
+ {"step": 1, "thought": "Simulate session expiration or token invalidation mid-flow."},
78
+ {"step": 2, "thought": "Perform rapid double-click on submission action to probe race condition."},
79
+ {"step": 3, "thought": "Assert idempotency: strictly 1 transaction created, duplicate blocked gracefully."}
80
+ ]
81
+ },
82
+ {
83
+ "branch_id": "BRANCH_NEGATIVE_CHAOS",
84
+ "name": "Negative Error Handling & Chaos Fault Path",
85
+ "risk_score": 0.72,
86
+ "thoughts": [
87
+ {"step": 1, "thought": "Inject malformed/boundary input payload exceeding maximum character limits."},
88
+ {"step": 2, "thought": "Simulate backend 500 error or database connection timeout via route stub."},
89
+ {"step": 3, "thought": "Assert user-friendly error toast, zero data corruption, and retry availability."}
90
+ ]
91
+ }
92
+ ]
93
+
94
+ # Beam search selection: sort by risk score descending and take beam_width
95
+ sorted_branches = sorted(branches, key=lambda b: cast(float, b["risk_score"]), reverse=True)
96
+ selected_branches = sorted_branches[:beam_width]
97
+
98
+ return {
99
+ "status": "PASS",
100
+ "story_title": title,
101
+ "domain": context_domain,
102
+ "max_depth": max_depth,
103
+ "beam_width": beam_width,
104
+ "total_branches_explored": len(branches),
105
+ "highest_risk_branch": selected_branches[0]["branch_id"],
106
+ "branches": selected_branches,
107
+ "clean_pom_recommendations": [
108
+ f"Implement {context_domain}Page extending BasePage",
109
+ "Encapsulate double-click debounce in clickSubmitSafe()",
110
+ "Assert boundary toast in verifyErrorMessageDisplayed()"
111
+ ]
112
+ }
113
+
114
+ def rca_hypotheses(self, error_log: str, hypotheses: Optional[List[str]] = None) -> Dict[str, Any]:
115
+ """Performs multi-hypothesis Root Cause Analysis on test failures."""
116
+ if not hypotheses:
117
+ hypotheses = ["DOM_TIMEOUT", "ASYNC_RACE", "DB_LOCK", "BACKEND_500", "STALE_SELECTOR"]
118
+
119
+ log_lower = error_log.lower()
120
+ scored_hypotheses = []
121
+
122
+ for hyp in hypotheses:
123
+ score = 0.0
124
+ evidence = []
125
+
126
+ if hyp == "DOM_TIMEOUT":
127
+ if "timed out" in log_lower or "waitfortimeout" in log_lower or "5000ms" in log_lower:
128
+ score += 0.85
129
+ evidence.append("Found timeout or waiting duration signature in log")
130
+ if "element is not visible" in log_lower or "tobevisible" in log_lower:
131
+ score += 0.10
132
+ evidence.append("Element visibility expectation failed")
133
+
134
+ elif hyp == "ASYNC_RACE":
135
+ if "click intercepted" in log_lower or "animation" in log_lower or "detached" in log_lower:
136
+ score += 0.90
137
+ evidence.append("Overlay or detachment signature points to async race condition")
138
+ if "networkidle" in log_lower or "pending" in log_lower:
139
+ score += 0.15
140
+ evidence.append("Pending network requests active during action")
141
+
142
+ elif hyp == "DB_LOCK":
143
+ if "deadlock" in log_lower or "lock" in log_lower or "sql" in log_lower or "unique constraint" in log_lower:
144
+ score += 0.88
145
+ evidence.append("Database lock or constraint collision detected in trace")
146
+
147
+ elif hyp == "BACKEND_500":
148
+ if re.search(r'\b500\b', log_lower) and not re.search(r'\b500\d+ms\b', log_lower) and ("http" in log_lower or "status" in log_lower or "server error" in log_lower or "internal" in log_lower):
149
+ score += 0.95
150
+ evidence.append("HTTP 500 status code explicitly logged")
151
+ elif "internal server error" in log_lower:
152
+ score += 0.95
153
+ evidence.append("Internal Server Error explicitly logged")
154
+
155
+ elif hyp == "STALE_SELECTOR":
156
+ if "locator" in log_lower or "selector" in log_lower or "xpath" in log_lower:
157
+ score += 0.70
158
+ evidence.append("Locator or selector syntax mentioned in failure message")
159
+
160
+ # Baseline confidence
161
+ if not evidence:
162
+ score = 0.10
163
+ evidence.append("No direct signature found; baseline background likelihood")
164
+
165
+ scored_hypotheses.append({
166
+ "hypothesis": hyp,
167
+ "confidence_score": round(min(1.0, score), 3),
168
+ "evidence": evidence
169
+ })
170
+
171
+ scored_hypotheses.sort(key=lambda h: cast(float, h["confidence_score"]), reverse=True)
172
+ winner = scored_hypotheses[0]
173
+
174
+ recommendations = {
175
+ "DOM_TIMEOUT": "Replace hardcoded delay with page.waitForLoadState('networkidle') or element.waitFor({state: 'visible'})",
176
+ "ASYNC_RACE": "Use clickSafe() helper with overlay detachment waiting or dispatchEvent('click')",
177
+ "DB_LOCK": "Randomize entity code prefix via DataGenerator.uniqueCode('AUTO_') and enforce teardown cleanup",
178
+ "BACKEND_500": "Inspect backend server logs for uncaught 500 NullPointerException or DB connection pool exhaustion",
179
+ "STALE_SELECTOR": "Run tools/agentless_patcher.py heal-failure to update stale attribute to ARIA role"
180
+ }
181
+
182
+ return {
183
+ "status": "PASS",
184
+ "primary_root_cause": winner["hypothesis"],
185
+ "confidence": winner["confidence_score"],
186
+ "recommended_fix": recommendations.get(str(winner["hypothesis"]), "Review stacktrace with team"),
187
+ "ranked_hypotheses": scored_hypotheses
188
+ }
189
+
190
+
191
+ def main():
192
+ parser = argparse.ArgumentParser(description="Tree-of-Thoughts Exploratory Testing Engine")
193
+ subparsers = parser.add_subparsers(dest="subcommand", help="Subcommand to execute")
194
+
195
+ # expand-story
196
+ exp_p = subparsers.add_parser("expand-story", help="Expand User Story into Tree of Thoughts")
197
+ exp_p.add_argument("--story-file", required=True, help="Path to story markdown file or text string")
198
+ exp_p.add_argument("--max-depth", type=int, default=3, help="Max thought tree depth")
199
+ exp_p.add_argument("--beam-width", type=int, default=3, help="Beam search width")
200
+ exp_p.add_argument("--out-graph", help="Optional path to output graph JSON")
201
+
202
+ # rca-hypotheses
203
+ rca_p = subparsers.add_parser("rca-hypotheses", help="Diagnose failure via multi-hypothesis RCA")
204
+ rca_p.add_argument("--error-log", required=True, help="Path to error log or log string")
205
+ rca_p.add_argument("--hypotheses", help="Comma-separated hypotheses list")
206
+
207
+ args = parser.parse_args()
208
+ tester = TotExploratoryTester()
209
+
210
+ if args.subcommand == "expand-story":
211
+ content = args.story_file
212
+ if os.path.exists(args.story_file):
213
+ with open(args.story_file, "r", encoding="utf-8", errors="replace") as f:
214
+ content = f.read()
215
+ res = tester.expand_story(content, max_depth=args.max_depth, beam_width=args.beam_width)
216
+ if args.out_graph:
217
+ os.makedirs(os.path.dirname(os.path.abspath(args.out_graph)), exist_ok=True)
218
+ with open(args.out_graph, "w", encoding="utf-8") as f:
219
+ json.dump(res, f, indent=2)
220
+ print(json.dumps(res, indent=2))
221
+ sys.exit(0)
222
+
223
+ elif args.subcommand == "rca-hypotheses":
224
+ log_content = args.error_log
225
+ if os.path.exists(args.error_log):
226
+ with open(args.error_log, "r", encoding="utf-8", errors="replace") as f:
227
+ log_content = f.read()
228
+ hyp_list = [h.strip() for h in args.hypotheses.split(",")] if args.hypotheses else None
229
+ res = tester.rca_hypotheses(error_log=log_content, hypotheses=hyp_list)
230
+ print(json.dumps(res, indent=2))
231
+ sys.exit(0)
232
+
233
+ else:
234
+ parser.print_help()
235
+ sys.exit(1)
236
+
237
+
238
+ if __name__ == "__main__":
239
+ main()
@@ -23,7 +23,7 @@ import re
23
23
  import sys
24
24
  import json
25
25
  import argparse
26
- from typing import Dict, Any, List, Optional
26
+ from typing import Dict, Any, List
27
27
  from urllib.parse import urlparse
28
28
 
29
29
  # Enforce UTF-8 stdout encoding for Windows console compatibility
@@ -153,14 +153,14 @@ class TrafficConverter:
153
153
  lines = [
154
154
  "import { test, expect } from '@playwright/test';",
155
155
  "",
156
- f"/**",
157
- f" * Automated Replay of Production Traffic Session",
158
- f" * Generated by traffic_session_converter.py (agent-skills Shift-Right Engine)",
156
+ "/**",
157
+ " * Automated Replay of Production Traffic Session",
158
+ " * Generated by traffic_session_converter.py (agent-skills Shift-Right Engine)",
159
159
  f" * Total Actions: {len(requests)} verified network interactions",
160
- f" */",
160
+ " */",
161
161
  f"test.describe('{test_name}', () => {{",
162
- f" test('replays production user journey with PII masking', async ({{ page, request }}) => {{",
163
- f" // 1. Base navigation",
162
+ " test('replays production user journey with PII masking', async ({ page, request }) => {",
163
+ " // 1. Base navigation",
164
164
  f" await page.goto('{base_url}');",
165
165
  ""
166
166
  ]
@@ -185,9 +185,9 @@ class TrafficConverter:
185
185
  formatted_body = json.dumps(body_str)
186
186
 
187
187
  lines.append(f" const resp{step_num} = await request.{method.lower()}('{url}', {{")
188
- lines.append(f" headers: {{ 'Content-Type': 'application/json' }},")
188
+ lines.append(" headers: { 'Content-Type': 'application/json' },")
189
189
  lines.append(f" data: {formatted_body}")
190
- lines.append(f" }});")
190
+ lines.append(" });")
191
191
  lines.append(f" expect(resp{step_num}.status()).toBe({status});")
192
192
  else:
193
193
  lines.append(f" const resp{step_num} = await request.get('{url}');")
@@ -9,8 +9,9 @@ whether the target is Playwright, Selenium, Cypress, Katalon, or Appium.
9
9
 
10
10
  from enum import Enum
11
11
  from typing import Dict, List, Optional, Any
12
- from dataclasses import dataclass, field, asdict
12
+ from dataclasses import dataclass, field
13
13
  import json
14
+ import re
14
15
 
15
16
 
16
17
  class ActionType(str, Enum):
@@ -345,8 +346,6 @@ class ProjectIR:
345
346
  return cls.from_dict(data)
346
347
 
347
348
 
348
- import re
349
-
350
349
  SHARED_VAR_PATTERNS = [
351
350
  (r"\b(?:created_id|order_id|user_id|item_id|customer_id|invoice_id)\b", DataDependencyType.ID),
352
351
  (r"\b(?:auth_token|bearer_token|access_token|jwt_token|token)\b", DataDependencyType.AUTH_TOKEN),
@@ -65,7 +65,7 @@ def get_local_version():
65
65
  try:
66
66
  with open(pkg_file, "r", encoding="utf-8") as f:
67
67
  data = json.load(f)
68
- return data.get("version", "2.5.0")
68
+ return data.get("version", "3.0.0")
69
69
  except (OSError, json.JSONDecodeError):
70
70
  data = {}
71
71
 
@@ -74,11 +74,11 @@ def get_local_version():
74
74
  try:
75
75
  with open(manifest_file, "r", encoding="utf-8") as f:
76
76
  data = json.load(f)
77
- return data.get("version", "2.5.0")
77
+ return data.get("version", "3.0.0")
78
78
  except (OSError, json.JSONDecodeError):
79
79
  data = {}
80
80
 
81
- return "2.5.0"
81
+ return "3.0.0"
82
82
 
83
83
 
84
84
  def fetch_remote_version():
@@ -259,7 +259,6 @@ def perform_update():
259
259
  git_dir = os.path.join(HUB_ROOT, ".git")
260
260
  is_git_repo = os.path.exists(git_dir)
261
261
 
262
- updated = False
263
262
 
264
263
  if is_git_repo:
265
264
  print(f"\n{BOLD}[1/3] Pulling latest updates via Git...{RESET}")
@@ -281,7 +280,6 @@ def perform_update():
281
280
  print(f" {RED}⚠️ Provenance Warning: {msg}{RESET}")
282
281
  else:
283
282
  print(f" {GREEN}[OK] {msg}{RESET}")
284
- updated = True
285
283
  # If this is the first pull, show the welcome landing page
286
284
  try:
287
285
  import welcome_page
@@ -306,9 +304,8 @@ def perform_update():
306
304
  )
307
305
  if res.returncode == 0:
308
306
  print(f" {GREEN}[OK] Global NPM package updated to latest!{RESET}")
309
- updated = True
310
307
  except (subprocess.SubprocessError, OSError):
311
- updated = False
308
+ _ = False
312
309
 
313
310
  # 2. Check Global Plugin
314
311
  if os.path.exists(GLOBAL_PLUGIN_DIR) and GLOBAL_PLUGIN_DIR.lower() != HUB_ROOT.lower():
@@ -319,7 +316,7 @@ def perform_update():
319
316
  subprocess.run(["git", "pull", "--ff-only"], cwd=GLOBAL_PLUGIN_DIR, stdout=subprocess.DEVNULL, timeout=30.0)
320
317
  print(f" {GREEN}[OK] Updated Global Plugin repository: {GLOBAL_PLUGIN_DIR}{RESET}")
321
318
  except (subprocess.SubprocessError, OSError):
322
- plugin_synced = False
319
+ _ = False
323
320
  else:
324
321
  # Refresh metadata files in global plugin
325
322
  try:
@@ -331,7 +328,7 @@ def perform_update():
331
328
  shutil.copy2(src_f, dst_f)
332
329
  print(f" {GREEN}[OK] Refreshed metadata & LICENSE in: {GLOBAL_PLUGIN_DIR}{RESET}")
333
330
  except OSError:
334
- copy_done = False
331
+ _ = False
335
332
 
336
333
  # 3. Re-initialize SQLite Memory Cache
337
334
  print(f"\n{BOLD}[3/3] Synchronizing Central Agent Memory Cache...{RESET}")
@@ -136,6 +136,7 @@ def resolve_target_repo(cli_target=None, cli_repo=None):
136
136
 
137
137
 
138
138
  def gate_0_author_integrity(repo_root, mode="AUTO"):
139
+ """Gate 0: Verifies author attribution and MIT license provenance across source manifests."""
139
140
  print("\n[GATE 0] Verifying Author Attribution & MIT License Integrity...")
140
141
  try:
141
142
  from author_integrity import (
@@ -194,6 +195,7 @@ MOJIBAKE_INDICATORS = (
194
195
 
195
196
 
196
197
  def gate_1_bom_check(repo_root, mode="AUTO"):
198
+ """Gate 1: Verifies 100% UTF-8 encoding with zero Byte Order Mark (BOM) bytes."""
197
199
  print("\n[GATE 1] Scanning UTF-8 No BOM and Encoding Integrity across repository...")
198
200
  ignored_dirs = {".git", ".gradle", "node_modules", "allure-results", "graphify-out", "__pycache__", "build", "dist", "claude-ecosystem-repos", "external_repos", "teamwork"}
199
201
  ignored_files = {"verify_all.py", "test_tools.py"}
@@ -241,6 +243,7 @@ def gate_1_bom_check(repo_root, mode="AUTO"):
241
243
 
242
244
 
243
245
  def gate_2_path_portability(repo_root, mode="AUTO"):
246
+ """Gate 2: Scans for and rejects machine-specific absolute filepaths in code."""
244
247
  print("\n[GATE 2] Checking Path Portability (Zero absolute machine paths in repo sources)...")
245
248
  ignored_dirs = {
246
249
  ".git", ".gradle", "node_modules", "allure-results", "Screenshots",
@@ -286,6 +289,7 @@ def gate_2_path_portability(repo_root, mode="AUTO"):
286
289
 
287
290
 
288
291
  def gate_3_clean_pom(repo_root, mode="AUTO"):
292
+ """Gate 3: Enforces Clean Page Object Model by rejecting raw selectors in test scripts."""
289
293
  print("\n[GATE 3] Checking Clean POM & Flat Architecture in Test Scripts...")
290
294
  effective_mode = detect_repo_mode(repo_root) if mode == "AUTO" else mode
291
295
  if effective_mode == "HUB":
@@ -346,6 +350,7 @@ def gate_3_clean_pom(repo_root, mode="AUTO"):
346
350
 
347
351
 
348
352
  def gate_4_ai_docs_lint(repo_root, mode="AUTO"):
353
+ """Gate 4: Reconciles PROJECT_AI_GUIDE.md against physical AST to prevent hallucinated classes."""
349
354
  print("\n[GATE 4] Running AI Documentation & Artifact Linter...")
350
355
  linter_script = os.path.join(SCRIPT_DIR, "lint_ai_docs.py")
351
356
  if not os.path.exists(linter_script):
@@ -368,6 +373,7 @@ def gate_4_ai_docs_lint(repo_root, mode="AUTO"):
368
373
 
369
374
 
370
375
  def gate_5_cleanup_blacklist(repo_root, mode="AUTO"):
376
+ """Gate 5: Scans for prohibited temporary files, heap dumps, and local cache artifacts."""
371
377
  print("\n[GATE 5] Checking Temporary File Blacklist...")
372
378
  blacklist_patterns = [
373
379
  os.path.join(repo_root, "Reports", "batch*.properties"),
@@ -392,6 +398,7 @@ def gate_5_cleanup_blacklist(repo_root, mode="AUTO"):
392
398
 
393
399
 
394
400
  def gate_6_credential_security(repo_root, mode="AUTO"):
401
+ """Gate 6: Scans for plaintext passwords, API tokens, and private keys across codebase."""
395
402
  print("\n[GATE 6] Checking Credential Security across repository...")
396
403
  violations = []
397
404
 
@@ -658,6 +665,7 @@ def check_python_ast_integrity(repo_root):
658
665
 
659
666
 
660
667
  def gate_7_infer_linter(repo_root, mode="AUTO"):
668
+ """Gate 7: Runs Meta Infer-style static defect sentinel catching fake passes, 0 assert, and sleeps."""
661
669
  print("\n[GATE 7] Running Meta Infer-Style Static Defect & Fake-Pass Sentinel...")
662
670
  effective_mode = detect_repo_mode(repo_root) if mode == "AUTO" else mode
663
671
 
@@ -737,6 +745,7 @@ def gate_7_infer_linter(repo_root, mode="AUTO"):
737
745
 
738
746
 
739
747
  def gate_8_micro_mutator(repo_root, mode="AUTO", test_cmd=""):
748
+ """Gate 8: Runs syntactic mutation testing and verifies assertion sensitivity score >= 80%."""
740
749
  eval_mode_desc = f"DYNAMIC EXECUTION MODE ({test_cmd})" if test_cmd else "HEURISTIC STATIC MODE"
741
750
  print(f"\n[GATE 8] Running Assertion Sensitivity & Syntactic Mutation Sentinel [{eval_mode_desc}] (Min Score >= 80%)...")
742
751
  scripts_dir = os.path.join(repo_root, "Scripts")
@@ -797,6 +806,7 @@ def gate_8_micro_mutator(repo_root, mode="AUTO", test_cmd=""):
797
806
 
798
807
 
799
808
  def gate_9_skill_collision(repo_root, mode="AUTO"):
809
+ """Gate 9: Scans skill descriptions to ensure semantic trigger overlap remains < 0.75."""
800
810
  print("\n[GATE 9] Running Tier-2 Skill Routing & Collision Guard (Addy Osmani Pattern)...")
801
811
  guard_script = os.path.join(SCRIPT_DIR, "skill_collision_guard.py")
802
812
  if not os.path.exists(guard_script):
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
- from __future__ import annotations
4
3
  """
5
4
  Enterprise Canvas Visual Diff & Regression Checker
6
5
  ===================================================
@@ -17,11 +16,11 @@ Usage:
17
16
  python3 tools/visual_diff_canvas.py compare baseline.png current.png --diff diff.png --threshold 0.02
18
17
  python3 tools/visual_diff_canvas.py check-blank current.png
19
18
  """
19
+ from __future__ import annotations
20
20
 
21
21
  import os
22
22
  import sys
23
23
  import argparse
24
- from pathlib import Path
25
24
 
26
25
  try:
27
26
  from PIL import Image, ImageChops, ImageStat
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- from __future__ import annotations
3
2
  """
4
3
  Enterprise Vision-Grounded Fallback & Set-of-Marks (SoM) Engine
5
4
  --------------------------------------------------------------
@@ -20,27 +19,25 @@ Features:
20
19
  4. Compact Vision Prompt Formatter:
21
20
  - Formats token-efficient briefs for Multimodal Vision Models (Gemini / Claude / GPT-4o).
22
21
  """
22
+ from __future__ import annotations
23
23
 
24
24
  import os
25
25
  import sys
26
26
  import json
27
27
  import sqlite3
28
28
  import argparse
29
- from typing import Dict, Any, List, Optional, Tuple, Union
29
+ from typing import Dict, Any, List, Optional, Tuple
30
30
  try:
31
31
  from PIL import Image, ImageDraw, ImageFont, ImageFilter
32
32
  HAS_PIL = True
33
33
  except ImportError:
34
- Image = None
35
- ImageDraw = None
36
- ImageFont = None
37
- ImageFilter = None
34
+ Image = None # type: ignore[assignment]
35
+ ImageDraw = None # type: ignore[assignment]
36
+ ImageFont = None # type: ignore[assignment]
37
+ ImageFilter = None # type: ignore[assignment]
38
38
  HAS_PIL = False
39
39
 
40
- if HAS_PIL and Image is not None:
41
- ImageSourceType = Union[Image.Image, str]
42
- else:
43
- ImageSourceType = Any
40
+ ImageSourceType = Any
44
41
 
45
42
  # Enforce UTF-8 stdout encoding for Windows console compatibility
46
43
  if hasattr(sys.stdout, 'reconfigure'):
@@ -74,12 +71,12 @@ def compute_visual_hash(image_source: ImageSourceType, bbox: Optional[List[int]]
74
71
  if isinstance(image_source, str):
75
72
  if not os.path.exists(image_source):
76
73
  raise FileNotFoundError(f"Image not found: {image_source}")
77
- img = Image.open(image_source)
74
+ img: Any = Image.open(image_source)
78
75
  else:
79
76
  img = image_source
80
77
 
81
78
  if bbox and len(bbox) == 4:
82
- img = img.crop(bbox)
79
+ img = img.crop(tuple(bbox))
83
80
 
84
81
  # Resize to (hash_size + 1, hash_size) in grayscale
85
82
  resized = img.convert("L").resize((hash_size + 1, hash_size), Image.Resampling.LANCZOS)
@@ -266,6 +263,8 @@ class VisualSoMHelper:
266
263
  edges = img.filter(ImageFilter.FIND_EDGES)
267
264
  edges = edges.point(lambda p: 255 if p > edge_threshold else 0)
268
265
  pixels = edges.load()
266
+ if pixels is None:
267
+ return []
269
268
 
270
269
  # Identify active cells with high edge density
271
270
  active_cells = set()
@@ -274,7 +273,10 @@ class VisualSoMHelper:
274
273
  count = 0
275
274
  for cy in range(y, min(h, y + stride)):
276
275
  for cx in range(x, min(w, x + stride)):
277
- if pixels[cx, cy] > 0:
276
+ px_val = pixels[cx, cy]
277
+ if isinstance(px_val, (int, float)) and px_val > 0:
278
+ count += 1
279
+ elif isinstance(px_val, tuple) and px_val and px_val[0] > 0:
278
280
  count += 1
279
281
  if count >= min_pixels_per_cell:
280
282
  active_cells.add((x // stride, y // stride))
@@ -156,7 +156,7 @@ def run_incremental_audit(file_path: str, target_dir: str) -> Dict[str, Any]:
156
156
  """Performs fast sub-100ms audit on a single changed file."""
157
157
  start_t = time.perf_counter()
158
158
  rel_path = os.path.relpath(file_path, target_dir)
159
- results = {
159
+ results: Dict[str, Any] = {
160
160
  "file": rel_path,
161
161
  "bom_pass": True,
162
162
  "path_pass": True,
@@ -126,6 +126,18 @@ class WCAGAuditor(HTMLParser):
126
126
 
127
127
 
128
128
  def run_audit(target, is_url=False):
129
+ """Executes a WCAG 2.2 accessibility audit against an HTML file or live web URL.
130
+
131
+ Args:
132
+ target: Filepath or URL pointing to HTML content.
133
+ is_url: True if target should be fetched over HTTP/HTTPS.
134
+
135
+ Returns:
136
+ The populated WCAGAuditor instance containing violations and stats.
137
+
138
+ Raises:
139
+ ValueError: If an insecure or invalid URL scheme is passed.
140
+ """
129
141
  auditor = WCAGAuditor()
130
142
  if is_url:
131
143
  parsed = urllib.parse.urlparse(target)
@@ -143,6 +155,15 @@ def run_audit(target, is_url=False):
143
155
 
144
156
 
145
157
  def format_markdown_report(target, auditor):
158
+ """Generates an executive WCAG 2.2 audit markdown report.
159
+
160
+ Args:
161
+ target: Target URL or file audited.
162
+ auditor: Populated WCAGAuditor instance.
163
+
164
+ Returns:
165
+ Markdown string containing compliance score, grade, and violation details.
166
+ """
146
167
  violations = auditor.violations
147
168
  stats = {
148
169
  "elements_scanned": auditor.elements_scanned,
@@ -158,7 +179,7 @@ def format_markdown_report(target, auditor):
158
179
  grade = "PASS" if score >= 80 else "NEEDS REMEDIATION"
159
180
 
160
181
  out = [
161
- f"# WCAG 2.2 Accessibility Audit Report",
182
+ "# WCAG 2.2 Accessibility Audit Report",
162
183
  f"**Target:** `{target}`",
163
184
  f"**Elements Scanned:** {stats['elements_scanned']}",
164
185
  f"**Violations Found:** {stats['violations_found']}",
@@ -28,7 +28,6 @@ import html as html_lib
28
28
  import argparse
29
29
  import webbrowser
30
30
  from datetime import datetime
31
- from pathlib import Path
32
31
  from typing import Dict, Any, Optional
33
32
 
34
33
  # Enforce UTF-8 stdout encoding for Windows console compatibility
@@ -48,7 +47,7 @@ def get_hub_metadata() -> Dict[str, Any]:
48
47
  """Retrieves version and catalog count from skills_manifest.json."""
49
48
  manifest_path = os.path.join(HUB_ROOT, "skills_manifest.json")
50
49
  meta = {
51
- "version": "2.6.0",
50
+ "version": "3.0.0",
52
51
  "totalSkills": 42,
53
52
  "author": "Thế Vị (@TeeVee998)",
54
53
  "repo": "https://github.com/TeeVee998/Synapse-Hub"
@@ -80,10 +79,10 @@ def generate_welcome_html(target_repo: Optional[str] = None, stack_info: Optiona
80
79
  """Renders the standalone interactive HTML5 landing page with bilingual language switcher."""
81
80
  meta = get_hub_metadata()
82
81
  now_str = datetime.now().strftime("%Y-%m-%d %H:%M")
83
-
82
+
84
83
  is_external_target = bool(target_repo and not is_hub_repo(target_repo) and stack_info)
85
84
  if is_external_target:
86
- raw_target_name = (stack_info.get("name") if stack_info else "") or os.path.basename(os.path.abspath(target_repo))
85
+ raw_target_name = (stack_info.get("name") if stack_info else "") or os.path.basename(os.path.abspath(target_repo or ""))
87
86
  raw_framework = (stack_info.get("framework") if stack_info else "") or "Automated QA"
88
87
  target_name = html_lib.escape(str(raw_target_name))
89
88
  framework_name = html_lib.escape(str(raw_framework))
@@ -248,7 +247,7 @@ def generate_welcome_page(target_repo: Optional[str] = None, output_path: Option
248
247
  if open_browser:
249
248
  opened = open_welcome_page(out_file)
250
249
  if opened:
251
- print(f" 🚀 Đã mở Visual Landing Page trong trình duyệt của bạn.")
250
+ print(" 🚀 Đã mở Visual Landing Page trong trình duyệt của bạn.")
252
251
  return out_file
253
252
 
254
253
 
@@ -284,7 +283,7 @@ def main():
284
283
  mark_first_run_completed(target_dir)
285
284
 
286
285
  print(f"✅ Landing page generated successfully at:\n file://{out_path}")
287
- print(f"👉 You can re-open it anytime with: npx synapse-hub welcome")
286
+ print("👉 You can re-open it anytime with: npx synapse-hub welcome")
288
287
 
289
288
 
290
289
  if __name__ == "__main__":