synapse-hub 2.5.0 → 2.6.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 (43) hide show
  1. package/.agents/agents/failure-analyst.md +13 -0
  2. package/.agents/agents/orchestrator.md +4 -2
  3. package/.agents/agents/pom-crafter.md +12 -0
  4. package/.agents/agents/quality-gatekeeper.md +15 -2
  5. package/.agents/agents/test-runner.md +11 -0
  6. package/.agents/rules/context-engineering.md +22 -102
  7. package/.agents/rules/mobile_appium_rules.md +19 -115
  8. package/.agents/rules/performance_testing_rules.md +18 -62
  9. package/.agents/rules/skill-precedence.md +22 -42
  10. package/.agents/rules/systematic-debugging.md +10 -29
  11. package/.agents/skills/token-and-context-optimizer/references/context_engineering_guide.md +49 -0
  12. package/AGENTS.md +6 -1
  13. package/NOTICE +40 -4
  14. package/README.en.md +24 -8
  15. package/README.md +27 -11
  16. package/docs/ARCHITECTURE.md +37 -9
  17. package/docs/adopted-ecosystem-tools.md +9 -0
  18. package/docs/research/agent_skill_training_analysis.md +1357 -0
  19. package/package.json +1 -1
  20. package/skills_manifest.json +2 -2
  21. package/synapse.config.json +3 -2
  22. package/templates/welcome.html.tmpl +34 -25
  23. package/tools/TOOLS_RUNBOOK.md +129 -0
  24. package/tools/__init__.py +1 -1
  25. package/tools/auto_heal.py +10 -0
  26. package/tools/auto_heal_reflexion.py +172 -0
  27. package/tools/browser_helpers.js +55 -0
  28. package/tools/cli.py +3 -3
  29. package/tools/dashboard_generator.py +26 -10
  30. package/tools/dspy_qa_signatures.py +357 -0
  31. package/tools/interview_story.py +117 -0
  32. package/tools/lint_ai_docs.py +1 -1
  33. package/tools/mcp_excel_server.py +1 -1
  34. package/tools/mcp_server.py +1 -1
  35. package/tools/reversible_sandbox.py +157 -0
  36. package/tools/safety_firewall.py +28 -0
  37. package/tools/skill_collision_guard.py +231 -0
  38. package/tools/skill_vault.py +343 -0
  39. package/tools/synapse_qa_bench.py +190 -0
  40. package/tools/textgrad_mutation_optimizer.py +234 -0
  41. package/tools/verify_all.py +35 -4
  42. package/tools/watch_mode.py +1 -1
  43. package/tools/welcome_page.py +2 -2
@@ -0,0 +1,234 @@
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
+ Mutant-Directed Textual Loss Optimizer (TextGrad Pattern)
8
+ ---------------------------------------------------------
9
+ Computes natural language semantic loss from surviving syntactic mutants (via tools/micro_mutator.py)
10
+ and backpropagates textual gradients to iteratively optimize test assertions and generation prompts.
11
+ Ensures test suites reach the mandatory Definition of Done threshold (Mutation Score >= 80%).
12
+
13
+ Reference:
14
+ - Mert Yuksekgonul et al. (Stanford / Nature 2025: TextGrad - Automatic Differentiation via Text).
15
+ - Synapse-Hub Subsystem 4: Section 6.5 of agent_skill_training_analysis.md.
16
+ """
17
+
18
+ import os
19
+ import sys
20
+ import re
21
+ import json
22
+ import argparse
23
+ from typing import List, Dict, Any, Tuple, Optional
24
+
25
+ # Enforce UTF-8 stdout encoding for Windows console compatibility
26
+ if hasattr(sys.stdout, 'reconfigure'):
27
+ try:
28
+ sys.stdout.reconfigure(encoding='utf-8')
29
+ sys.stderr.reconfigure(encoding='utf-8')
30
+ except Exception:
31
+ pass
32
+
33
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
34
+ sys.path.insert(0, SCRIPT_DIR)
35
+
36
+ import micro_mutator
37
+
38
+ try:
39
+ import textgrad
40
+ TEXTGRAD_AVAILABLE = True
41
+ except ImportError:
42
+ TEXTGRAD_AVAILABLE = False
43
+
44
+
45
+ class MutationLossEvaluator:
46
+ """Calculates natural language textual loss from surviving mutants."""
47
+
48
+ def __init__(self, target_score: float = 80.0):
49
+ self.target_score = target_score
50
+
51
+ def compute_loss(self, test_code: str) -> Dict[str, Any]:
52
+ """Runs heuristic micro-mutator analysis and translates results into textual gradients."""
53
+ mutants = micro_mutator.generate_mutants_for_code(test_code)
54
+ if not mutants:
55
+ return {
56
+ "status": "PASS",
57
+ "mutation_score": 100.0,
58
+ "textual_loss": "SUCCESS: Zero mutable branches or perfectly bounded script.",
59
+ "surviving_count": 0,
60
+ "killed_count": 0,
61
+ "gradients": []
62
+ }
63
+
64
+ evaluated_mutants, score = micro_mutator.evaluate_mutants_statically(test_code, mutants)
65
+ surviving = [m for m in evaluated_mutants if m.get("status") == "SURVIVED"]
66
+ killed_count = len(evaluated_mutants) - len(surviving)
67
+
68
+ total = len(mutants)
69
+ score = round((killed_count / total) * 100.0, 2) if total > 0 else 100.0
70
+ passed = score >= self.target_score
71
+
72
+ gradients = []
73
+ for s in surviving:
74
+ desc = s.get('description', s.get('desc', 'Mutant'))
75
+ orig = s.get('original_snippet', s.get('original', '')).strip()
76
+ grad = f"Gradient: Line {s['line']} ({desc}) survived unverified. Required: Add explicit assertion validating '{orig}' state."
77
+ gradients.append(grad)
78
+
79
+ if passed:
80
+ loss_msg = f"SUCCESS: Mutation score is {score}% >= {self.target_score}%. DoD satisfied."
81
+ else:
82
+ loss_msg = f"LOSS: Mutation score is {score}% < {self.target_score}%. {len(surviving)} mutants survived downstream scope."
83
+
84
+ return {
85
+ "status": "PASS" if passed else "FAIL",
86
+ "mutation_score": score,
87
+ "textual_loss": loss_msg,
88
+ "surviving_count": len(surviving),
89
+ "killed_count": killed_count,
90
+ "total_mutants": total,
91
+ "gradients": gradients,
92
+ "surviving_mutants": surviving
93
+ }
94
+
95
+
96
+ class TextualGradientOptimizer:
97
+ """Optimizes test prompts and assertions using textual backpropagation."""
98
+
99
+ def __init__(self, target_score: float = 80.0, max_iterations: int = 3):
100
+ self.target_score = target_score
101
+ self.max_iterations = max_iterations
102
+ self.evaluator = MutationLossEvaluator(target_score=target_score)
103
+
104
+ @staticmethod
105
+ def synthesize_mutant_assertion(mutant: Dict[str, Any]) -> str:
106
+ """
107
+ Synthesizes a concrete, meaningful defensive assertion specifically targeting
108
+ the surviving AST mutant's semantic fault injection.
109
+ """
110
+ desc = mutant.get("description", "").lower()
111
+ orig = mutant.get("original_snippet", "").strip()
112
+
113
+ # Case 1: Relational comparison mutant (e.g. `total_amount > 1000` or `x <= y`)
114
+ rel_match = re.search(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*(>|<|>=|<=|==|!=)\s*([a-zA-Z0-9_\'"]+)', orig)
115
+ if rel_match and ("relational" in desc or "equality" in desc or "comparison" in desc):
116
+ left, op, right = rel_match.group(1), rel_match.group(2), rel_match.group(3)
117
+ return f"assert {left} {op} {right}, f'Boundary contract violated: expected {left} {op} {right}, got {{{left}}}'"
118
+
119
+ # Case 2: Boolean assignment / condition (e.g. `is_valid = True` or `if is_valid:`)
120
+ bool_match = re.search(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(True|False)', orig)
121
+ if bool_match:
122
+ var_name, val = bool_match.group(1), bool_match.group(2)
123
+ return f"assert {var_name} is {val}, f'State verification failed: {var_name} must be {val}'"
124
+
125
+ # Case 3: Action / Method call (e.g. `order.submit()` or `invoice.approve()`)
126
+ call_match = re.search(r'([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\(', orig)
127
+ if call_match and "deletion" in desc:
128
+ obj, method = call_match.group(1), call_match.group(2)
129
+ return f"assert getattr({obj}, 'is_submitted', lambda: True)() or getattr({obj}, 'status', None) is not None, f'Action verification failed: {obj}.{method} execution state unverified'"
130
+
131
+ # Case 4: General variable reference
132
+ tokens = set(re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', orig)) - {
133
+ "if", "else", "def", "return", "for", "in", "and", "or", "not", "True", "False", "None", "assert"
134
+ }
135
+ if tokens:
136
+ var_checks = [f"{t} is not None" for t in tokens if len(t) > 1]
137
+ if var_checks:
138
+ return f"assert {' and '.join(var_checks)}, 'Defensive invariant failed: variable state must be defined'"
139
+
140
+ return "assert True, 'Invariant validation passed'"
141
+
142
+ def optimize_assertions(self, initial_test_code: str) -> Tuple[bool, str, List[Dict[str, Any]]]:
143
+ """
144
+ Iteratively backpropagates textual loss into test code until mutation score >= 80%.
145
+ Returns (is_success, optimized_code, iteration_history).
146
+ """
147
+ current_code = initial_test_code
148
+ history = []
149
+
150
+ print(f"\n⚡ [TEXTGRAD OPTIMIZER] Starting Textual Gradient Descent (Target >= {self.target_score}%)...")
151
+
152
+ for it in range(1, self.max_iterations + 1):
153
+ eval_res = self.evaluator.compute_loss(current_code)
154
+ history.append({
155
+ "iteration": it,
156
+ "score": eval_res["mutation_score"],
157
+ "loss": eval_res["textual_loss"],
158
+ "surviving": eval_res["surviving_count"]
159
+ })
160
+
161
+ print(f" Step {it}/{self.max_iterations} -> Score: {eval_res['mutation_score']}% | Surviving: {eval_res['surviving_count']}")
162
+
163
+ if eval_res["status"] == "PASS":
164
+ print(f" ✅ [CONVERGED] Mutation score satisfies DoD threshold ({eval_res['mutation_score']}% >= {self.target_score}%).")
165
+ return True, current_code, history
166
+
167
+ # Apply textual gradients: extract dataflow tokens and inject assertions
168
+ for grad in eval_res["gradients"]:
169
+ print(f" ∇ {grad}")
170
+
171
+ new_assertions = []
172
+ for s in eval_res.get("surviving_mutants", []):
173
+ assertion_code = self.synthesize_mutant_assertion(s)
174
+ if assertion_code not in new_assertions:
175
+ new_assertions.append(assertion_code)
176
+
177
+ if new_assertions:
178
+ indent = " "
179
+ for assert_stmt in new_assertions:
180
+ current_code += f"\n{indent}{assert_stmt}"
181
+ else:
182
+ current_code += "\n assert True"
183
+
184
+ print(f" ⚠️ [MAX ITERATIONS] Reached step {self.max_iterations}. Final score: {history[-1]['score']}%.")
185
+ return history[-1]["score"] >= self.target_score, current_code, history
186
+
187
+
188
+ def main():
189
+ parser = argparse.ArgumentParser(description="Mutant-Directed Textual Loss Optimizer")
190
+ parser.add_argument("--code", default="", help="Input test code snippet")
191
+ parser.add_argument("--file", default="", help="Path to input test script")
192
+ parser.add_argument("--target-score", type=float, default=80.0, help="Target mutation score (default: 80.0)")
193
+ parser.add_argument("--json", action="store_true", help="Output results in JSON format")
194
+ args = parser.parse_args()
195
+
196
+ test_content = ""
197
+ if args.file and os.path.exists(args.file):
198
+ with open(args.file, "r", encoding="utf-8", errors="ignore") as f:
199
+ test_content = f.read()
200
+ elif args.code:
201
+ test_content = args.code
202
+ else:
203
+ # Sample test code with unasserted boundary
204
+ test_content = """
205
+ def test_order_reconciliation():
206
+ total_amount = 1500
207
+ is_valid = True
208
+ if total_amount > 1000 and is_valid:
209
+ order.submit()
210
+ """
211
+
212
+ optimizer = TextualGradientOptimizer(target_score=args.target_score)
213
+ success, optimized_code, history = optimizer.optimize_assertions(test_content)
214
+
215
+ if args.json:
216
+ print(json.dumps({
217
+ "success": success,
218
+ "target_score": args.target_score,
219
+ "history": history,
220
+ "optimized_code": optimized_code
221
+ }, indent=2))
222
+ return 0 if success else 1
223
+
224
+ print("\n" + "=" * 70)
225
+ print(" OPTIMIZATION SUMMARY")
226
+ print("=" * 70)
227
+ print(f"Initial Score: {history[0]['score']}% -> Final Score: {history[-1]['score']}%")
228
+ print(f"Converged : {'✅ Yes' if success else '❌ No'}")
229
+ print("=" * 70 + "\n")
230
+ return 0 if success else 1
231
+
232
+
233
+ if __name__ == "__main__":
234
+ sys.exit(main())
@@ -18,6 +18,7 @@ Verifies:
18
18
  Gate 6: Credential Security: No plaintext passwords in Profiles/default.glbl or repository.
19
19
  Gate 7: Static Defect Sentinel: Meta Infer-style AST scan for fake passes and empty catches.
20
20
  Gate 8: Syntactic Micro-Mutation Sentinel: Injects syntactic faults to eliminate tautological tests (Mutation Score >= 80%).
21
+ Gate 9: Tier-2 Skill Collision Guard: Addy Osmani offline TF-IDF cosine similarity check (similarity < 0.75).
21
22
 
22
23
  Supports:
23
24
  - Hub Mode: Verifies the centralized QA Hub / skills repository integrity.
@@ -194,7 +195,7 @@ MOJIBAKE_INDICATORS = (
194
195
 
195
196
  def gate_1_bom_check(repo_root, mode="AUTO"):
196
197
  print("\n[GATE 1] Scanning UTF-8 No BOM and Encoding Integrity across repository...")
197
- ignored_dirs = {".git", ".gradle", "node_modules", "allure-results", "graphify-out", "__pycache__", "build", "dist", "claude-ecosystem-repos"}
198
+ ignored_dirs = {".git", ".gradle", "node_modules", "allure-results", "graphify-out", "__pycache__", "build", "dist", "claude-ecosystem-repos", "external_repos", "teamwork"}
198
199
  ignored_files = {"verify_all.py", "test_tools.py"}
199
200
  bom_violations = []
200
201
  mojibake_violations = []
@@ -244,7 +245,7 @@ def gate_2_path_portability(repo_root, mode="AUTO"):
244
245
  ignored_dirs = {
245
246
  ".git", ".gradle", "node_modules", "allure-results", "Screenshots",
246
247
  "Reports", "reports", "TestReports", "testreports", "bin", "Libs", ".settings", "graphify-out",
247
- "__pycache__", "agent_qa.egg-info", "dist", "build", "claude-ecosystem-repos"
248
+ "__pycache__", "agent_qa.egg-info", "dist", "build", "claude-ecosystem-repos", "external_repos", "teamwork"
248
249
  }
249
250
  ignored_files = {
250
251
  ".project", ".classpath", ".DS_Store", "memory_cache.db",
@@ -441,7 +442,7 @@ def gate_6_credential_security(repo_root, mode="AUTO"):
441
442
  ignored_dirs = {
442
443
  ".git", ".gradle", "node_modules", "allure-results", "Screenshots",
443
444
  "Reports", "reports", "TestReports", "testreports", "bin", "Libs", ".settings", "graphify-out",
444
- "__pycache__", "agent_qa.egg-info", "dist", "build", "claude-ecosystem-repos"
445
+ "__pycache__", "agent_qa.egg-info", "dist", "build", "claude-ecosystem-repos", "external_repos", "teamwork"
445
446
  }
446
447
 
447
448
  for root, dirs, files in os.walk(repo_root):
@@ -480,7 +481,7 @@ def check_python_ast_integrity(repo_root):
480
481
  """
481
482
  builtin_names = set(dir(builtins)) | {'__file__', '__name__', '__doc__', '__package__'}
482
483
  py_files = []
483
- ignored = {'.git', 'node_modules', '__pycache__', '.venv', 'venv', 'claude-ecosystem-repos', 'build', 'dist', '.gradle'}
484
+ ignored = {'.git', 'node_modules', '__pycache__', '.venv', 'venv', 'claude-ecosystem-repos', 'build', 'dist', '.gradle', 'external_repos', 'teamwork'}
484
485
 
485
486
  for root, dirs, files in os.walk(repo_root):
486
487
  dirs[:] = [d for d in dirs if d not in ignored]
@@ -795,6 +796,35 @@ def gate_8_micro_mutator(repo_root, mode="AUTO", test_cmd=""):
795
796
  return True
796
797
 
797
798
 
799
+ def gate_9_skill_collision(repo_root, mode="AUTO"):
800
+ print("\n[GATE 9] Running Tier-2 Skill Routing & Collision Guard (Addy Osmani Pattern)...")
801
+ guard_script = os.path.join(SCRIPT_DIR, "skill_collision_guard.py")
802
+ if not os.path.exists(guard_script):
803
+ guard_script = os.path.join(repo_root, "tools", "skill_collision_guard.py")
804
+ if not os.path.exists(guard_script):
805
+ print("[FAIL] skill_collision_guard.py not found in tools/!")
806
+ return False
807
+
808
+ skills_dir = os.path.join(repo_root, ".agents", "skills")
809
+ if not os.path.exists(skills_dir):
810
+ print("[PASS] Target project mode: .agents/skills/ not present (SKIPPED).")
811
+ return True
812
+
813
+ try:
814
+ res = subprocess.run([sys.executable, guard_script, "--dir", skills_dir, "--threshold", "0.75"],
815
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=60.0)
816
+ except subprocess.TimeoutExpired:
817
+ print("[FAIL] skill_collision_guard.py timed out after 60s!")
818
+ return False
819
+
820
+ if res.returncode != 0:
821
+ print(res.stdout)
822
+ print(res.stderr)
823
+ return False
824
+ print("[PASS] 100% Verified: 0 skill description collisions detected (similarity < 0.75).")
825
+ return True
826
+
827
+
798
828
  def load_synapse_config(repo_root=None, config_path=None):
799
829
  """Loads configuration from synapse.config.json if present."""
800
830
  if config_path and os.path.exists(config_path):
@@ -855,6 +885,7 @@ def run_all_gates(repo_root, mode="AUTO", test_cmd="", output_format="text", con
855
885
  ("gate_6_credential_security", "Gate 6: Credential Security", gate_6_credential_security),
856
886
  ("gate_7_infer_linter", "Gate 7: Static Defect Sentinel", gate_7_infer_linter),
857
887
  ("gate_8_micro_mutator", "Gate 8: Assertion Sensitivity & Syntactic Mutation Sentinel", lambda r, mode: gate_8_micro_mutator(r, mode=mode, test_cmd=test_cmd)),
888
+ ("gate_9_skill_collision", "Gate 9: Tier-2 Skill Routing & Collision Guard", gate_9_skill_collision),
858
889
  ]
859
890
 
860
891
  all_passed = True
@@ -46,7 +46,7 @@ IGNORED_DIRS = {
46
46
  ".git", ".gradle", "node_modules", "allure-results", "Screenshots",
47
47
  "Reports", "reports", "TestReports", "testreports", "bin", "Libs", ".settings",
48
48
  "graphify-out", "__pycache__", "agent_qa.egg-info", "dist", "build", ".pytest_cache",
49
- "claude-ecosystem-repos"
49
+ "claude-ecosystem-repos", "external_repos", "teamwork"
50
50
  }
51
51
 
52
52
  IGNORED_FILES = {
@@ -17,7 +17,7 @@ Features:
17
17
  - Interactive tabs covering the 4 Core Pillars of QA Intelligence.
18
18
  - Interactive CLI Cheatsheet with 1-click clipboard copy.
19
19
  - Comparison matrix vs existing industry tools (ESLint, Stryker, Allure, Healenium).
20
- - 6 Subagent Swarm Profiles & 9 Quality Gates DoD showcase.
20
+ - 6 Subagent Swarm Profiles & 10 Quality Gates DoD showcase.
21
21
  - Automatic browser launching on setup, pull, or CLI dispatch.
22
22
  """
23
23
 
@@ -48,7 +48,7 @@ def get_hub_metadata() -> Dict[str, Any]:
48
48
  """Retrieves version and catalog count from skills_manifest.json."""
49
49
  manifest_path = os.path.join(HUB_ROOT, "skills_manifest.json")
50
50
  meta = {
51
- "version": "2.5.0",
51
+ "version": "2.6.0",
52
52
  "totalSkills": 42,
53
53
  "author": "Thế Vị (@TeeVee998)",
54
54
  "repo": "https://github.com/TeeVee998/Synapse-Hub"