synapse-hub 2.7.0 → 3.0.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 (141) hide show
  1. package/.agents/agents/live-explorer.md +2 -0
  2. package/.agents/agents/pom-crafter.md +2 -0
  3. package/.agents/agents/test-runner.md +3 -0
  4. package/.agents/rules/communication-style.md +4 -0
  5. package/.agents/rules/context-engineering.md +1 -0
  6. package/.agents/rules/git-push-protocol.md +17 -0
  7. package/.agents/skills/agentless-qa-repair/examples/three_phase_repair_workflow.md +40 -0
  8. package/.agents/skills/antigravity-test-sentinel/examples/sprint_dod_enforcement.md +47 -0
  9. package/.agents/skills/auth-session-and-cookie-manager/examples/multi_role_auth_matrix.md +70 -0
  10. package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_playwright_pom.ts +52 -0
  11. package/.agents/skills/failure-autopsy-and-healing/examples/closed_loop_healing_walkthrough.md +48 -0
  12. package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
  13. package/.agents/skills/lean-impact-scoreboard/examples/sample_scoreboard.md +1 -1
  14. package/.agents/skills/mipro-prompt-compiler/examples/dspy_assertion_optimizer_case.md +46 -0
  15. package/.agents/skills/persistent-planning-files/SKILL.md +1 -1
  16. package/.agents/skills/qa-aci-interface/examples/surgical_code_window_walkthrough.md +40 -0
  17. package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
  18. package/.agents/skills/review-spec/SKILL.md +1 -1
  19. package/.agents/skills/review-spec/examples/pocock_grilling_decision_rubric.md +40 -0
  20. package/.agents/skills/senior-code-review/SKILL.md +1 -1
  21. package/.agents/skills/skillspector-security-guard/examples/audit_threat_vectors_report.json +35 -0
  22. package/.agents/skills/surgical-coding-principles/SKILL.md +1 -1
  23. package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
  24. package/.agents/skills/tot-exploratory-testing/examples/payment_gateway_tot_tree.json +62 -0
  25. package/.agents/skills/verification-before-completion/SKILL.md +2 -2
  26. package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +2 -2
  27. package/.agents/skills/web-agent-gym-benchmarking/examples/enterprise_tasks_suite.json +38 -0
  28. package/.agents/skills/web-vitals-and-performance-profiler/examples/playwright_cwv_profiler_fixture.ts +61 -0
  29. package/AGENTS.md +6 -4
  30. package/CLAUDE.md +2 -1
  31. package/README.en.md +71 -292
  32. package/README.md +69 -304
  33. package/action.yml +1 -1
  34. package/bin/agent-qa.js +2 -2
  35. package/docs/ARCHITECTURE.en.md +192 -0
  36. package/docs/ARCHITECTURE.md +135 -88
  37. package/docs/CI_CD_INTEGRATION.en.md +96 -0
  38. package/docs/CI_CD_INTEGRATION.md +24 -22
  39. package/docs/CLI_REFERENCE.en.md +174 -0
  40. package/docs/CLI_REFERENCE.md +120 -115
  41. package/docs/CONTRIBUTING.en.md +49 -0
  42. package/docs/CONTRIBUTING.md +26 -68
  43. package/docs/QUALITY_GATES.en.md +115 -0
  44. package/docs/QUALITY_GATES.md +115 -0
  45. package/docs/research/agent_skill_training_analysis.md +1 -1
  46. package/package.json +1 -1
  47. package/scripts/package_dist.py +2 -1
  48. package/skills_manifest.json +6 -6
  49. package/synapse.config.json +1 -1
  50. package/templates/ai_bridges/CLAUDE.md +2 -1
  51. package/templates/ai_bridges/copilot-instructions.md +2 -2
  52. package/tools/__init__.py +18 -1
  53. package/tools/_openapi_parser.py +1 -1
  54. package/tools/agent_canary.py +42 -30
  55. package/tools/agentless_patcher.py +2 -2
  56. package/tools/analyze_project.py +3 -5
  57. package/tools/api_drift_sentinel.py +4 -4
  58. package/tools/api_fuzzer.py +8 -8
  59. package/tools/auto_heal.py +4 -5
  60. package/tools/auto_heal_reflexion.py +10 -2
  61. package/tools/autogen_qa_broker.py +2 -2
  62. package/tools/browser_helpers.js +2 -2
  63. package/tools/browsergym_eval.py +2 -3
  64. package/tools/bug_reporter.py +11 -13
  65. package/tools/bump_version.py +90 -19
  66. package/tools/cache_resolver.py +78 -83
  67. package/tools/check_env.py +9 -1
  68. package/tools/check_version_sync.py +58 -4
  69. package/tools/ci_healing_hook.py +4 -6
  70. package/tools/cli.py +5 -7
  71. package/tools/config_decoupler.py +2 -3
  72. package/tools/contract_validator.py +141 -0
  73. package/tools/core/__init__.py +52 -0
  74. package/tools/count_tokens.py +22 -20
  75. package/tools/dashboard_generator.py +629 -1000
  76. package/tools/db_sandbox.py +87 -62
  77. package/tools/debt_harvester.py +3 -3
  78. package/tools/distill_log.py +3 -3
  79. package/tools/dspy_qa_signatures.py +3 -4
  80. package/tools/export_postman_collection.py +8 -8
  81. package/tools/flaky_detector.py +2 -2
  82. package/tools/fuzz_payloads.py +5 -5
  83. package/tools/generate_allure_report.py +1 -1
  84. package/tools/generate_jmx_plan.py +4 -4
  85. package/tools/gherkin_suite.py +1 -1
  86. package/tools/git_pre_commit_guard.py +2 -2
  87. package/tools/goldfish_review.py +2 -2
  88. package/tools/heal/__init__.py +28 -0
  89. package/tools/heal_pr_automator.py +6 -6
  90. package/tools/heuristic_locator_matcher.py +2 -2
  91. package/tools/infer_linter.py +143 -29
  92. package/tools/interview_story.py +0 -1
  93. package/tools/jtl_analyzer.py +11 -11
  94. package/tools/lint/__init__.py +30 -0
  95. package/tools/lint_ai_docs.py +42 -0
  96. package/tools/lookup_tc.py +1 -1
  97. package/tools/matrix_sharder.py +15 -13
  98. package/tools/mcp/__init__.py +16 -0
  99. package/tools/mcp_excel_server.py +2 -2
  100. package/tools/mcp_server.py +9 -7
  101. package/tools/md_to_xlsx.py +26 -26
  102. package/tools/micro_mutator.py +32 -15
  103. package/tools/network_mock_shield.py +27 -27
  104. package/tools/newman_analyzer.py +2 -3
  105. package/tools/omni_emitter.py +5 -8
  106. package/tools/omni_parser.py +5 -5
  107. package/tools/omni_transpiler.py +5 -5
  108. package/tools/output_formatter.py +19 -0
  109. package/tools/perf/__init__.py +24 -0
  110. package/tools/pinpoint_failure.py +1 -1
  111. package/tools/project_state.py +0 -2
  112. package/tools/qa_event_stream.py +1 -1
  113. package/tools/repo_map.py +8 -8
  114. package/tools/report/__init__.py +32 -0
  115. package/tools/reversible_sandbox.py +3 -4
  116. package/tools/safety_firewall.py +2 -2
  117. package/tools/senior_code_review.py +4 -3
  118. package/tools/setup_project.py +10 -12
  119. package/tools/skill_collision_guard.py +3 -3
  120. package/tools/skill_vault.py +94 -6
  121. package/tools/skillspector.py +10 -8
  122. package/tools/smart_visual_masker.py +4 -6
  123. package/tools/spec_drift_sentinel.py +1 -2
  124. package/tools/swe_qa_aci.py +2 -2
  125. package/tools/synapse_qa_bench.py +3 -3
  126. package/tools/sync_agent_rules.py +18 -3
  127. package/tools/telemetry_correlator.py +3 -2
  128. package/tools/test_impact.py +2 -1
  129. package/tools/test_memory_os.py +15 -5
  130. package/tools/textgrad_mutation_optimizer.py +3 -3
  131. package/tools/tms_sync_bridge.py +5 -5
  132. package/tools/tot_exploratory_tester.py +5 -6
  133. package/tools/traffic_session_converter.py +9 -9
  134. package/tools/universal_ir.py +2 -3
  135. package/tools/update_sentinel.py +6 -9
  136. package/tools/verify_all.py +12 -2
  137. package/tools/visual_diff_canvas.py +4 -4
  138. package/tools/visual_som_helper.py +15 -13
  139. package/tools/watch_mode.py +1 -1
  140. package/tools/wcag_audit.py +22 -1
  141. package/tools/welcome_page.py +5 -6
@@ -58,26 +58,32 @@ class Colors:
58
58
 
59
59
  @classmethod
60
60
  def cyan(cls, text: str) -> str:
61
+ """Wraps text with ANSI cyan color if color output is enabled."""
61
62
  return f"{cls._c(cls.CYAN)}{text}{cls._c(cls.RESET)}"
62
63
 
63
64
  @classmethod
64
65
  def green(cls, text: str) -> str:
66
+ """Wraps text with ANSI green color if color output is enabled."""
65
67
  return f"{cls._c(cls.GREEN)}{text}{cls._c(cls.RESET)}"
66
68
 
67
69
  @classmethod
68
70
  def yellow(cls, text: str) -> str:
71
+ """Wraps text with ANSI yellow color if color output is enabled."""
69
72
  return f"{cls._c(cls.YELLOW)}{text}{cls._c(cls.RESET)}"
70
73
 
71
74
  @classmethod
72
75
  def red(cls, text: str) -> str:
76
+ """Wraps text with ANSI red color if color output is enabled."""
73
77
  return f"{cls._c(cls.RED)}{text}{cls._c(cls.RESET)}"
74
78
 
75
79
  @classmethod
76
80
  def bold(cls, text: str) -> str:
81
+ """Wraps text with ANSI bold weight if color output is enabled."""
77
82
  return f"{cls._c(cls.BOLD)}{text}{cls._c(cls.RESET)}"
78
83
 
79
84
  @classmethod
80
85
  def dim(cls, text: str) -> str:
86
+ """Wraps text with ANSI dim intensity if color output is enabled."""
81
87
  return f"{cls._c(cls.DIM)}{text}{cls._c(cls.RESET)}"
82
88
 
83
89
 
@@ -85,27 +91,34 @@ class OutputFormatter:
85
91
  """Standardized console & machine-readable output renderer."""
86
92
 
87
93
  def __init__(self, quiet: bool = False, json_mode: bool = False, verbose: bool = False):
94
+ """Initializes the output formatter with verbosity and mode toggles."""
88
95
  self.quiet = quiet
89
96
  self.json_mode = json_mode
90
97
  self.verbose = verbose
91
98
 
92
99
  def debug(self, message: str):
100
+ """Prints a debug message when verbose mode is active."""
93
101
  if self.verbose and not self.quiet and not self.json_mode:
94
102
  print(f"{Colors.dim('🔍 [DEBUG]')} {message}")
95
103
 
96
104
  def info(self, message: str):
105
+ """Alias for info_msg printing informational text."""
97
106
  self.info_msg(message)
98
107
 
99
108
  def warn(self, message: str):
109
+ """Alias for warn_msg printing warning text."""
100
110
  self.warn_msg(message)
101
111
 
102
112
  def error(self, message: str):
113
+ """Alias for fail_msg printing failure/error text."""
103
114
  self.fail_msg(message)
104
115
 
105
116
  def success(self, message: str):
117
+ """Alias for pass_msg printing passing/success text."""
106
118
  self.pass_msg(message)
107
119
 
108
120
  def header(self, title: str):
121
+ """Renders an eye-catching framed header box for CLI stages."""
109
122
  if not self.quiet and not self.json_mode:
110
123
  border = "=" * 70
111
124
  print(f"\n{Colors.cyan(border)}")
@@ -113,26 +126,32 @@ class OutputFormatter:
113
126
  print(f"{Colors.cyan(border)}\n")
114
127
 
115
128
  def step(self, current: int, total: int, title: str):
129
+ """Prints a numbered step indicator for multi-phase operations."""
116
130
  if not self.quiet and not self.json_mode:
117
131
  print(f"{Colors.bold(f'[{current}/{total}]')} {title}")
118
132
 
119
133
  def pass_msg(self, message: str):
134
+ """Prints a green checkmark pass badge with the given message."""
120
135
  if not self.json_mode:
121
136
  print(f"{Colors.green('✅ [PASS]')} {message}")
122
137
 
123
138
  def fail_msg(self, message: str):
139
+ """Prints a red cross fail badge with the given message."""
124
140
  if not self.json_mode:
125
141
  print(f"{Colors.red('❌ [FAIL]')} {message}")
126
142
 
127
143
  def warn_msg(self, message: str):
144
+ """Prints a yellow warning badge with the given message."""
128
145
  if not self.quiet and not self.json_mode:
129
146
  print(f"{Colors.yellow('⚠️ [WARN]')} {message}")
130
147
 
131
148
  def info_msg(self, message: str):
149
+ """Prints a cyan informational badge with the given message."""
132
150
  if not self.quiet and not self.json_mode:
133
151
  print(f"{Colors.cyan('ℹ️ [INFO]')} {message}")
134
152
 
135
153
  def print_data(self, data: Any, format_type: str = "text"):
154
+ """Formats and outputs data payload as either indented JSON or raw text."""
136
155
  if format_type == "json" or self.json_mode:
137
156
  if isinstance(data, (dict, list)):
138
157
  print(json.dumps(data, indent=2, ensure_ascii=False))
@@ -0,0 +1,24 @@
1
+ """
2
+ Synapse Hub — Performance, Stress & Flakiness Profilers (tools.perf)
3
+ ---------------------------------------------------------------------
4
+ Domain package grouping all JMeter test plan generators, flakiness
5
+ sentinels, BFCL agent gym benchmarks, and test matrix sharders.
6
+ """
7
+
8
+ from tools import (
9
+ browsergym_eval,
10
+ db_sandbox,
11
+ flaky_detector,
12
+ generate_jmx_plan,
13
+ matrix_sharder,
14
+ synapse_qa_bench,
15
+ )
16
+
17
+ __all__ = [
18
+ "browsergym_eval",
19
+ "db_sandbox",
20
+ "flaky_detector",
21
+ "generate_jmx_plan",
22
+ "matrix_sharder",
23
+ "synapse_qa_bench",
24
+ ]
@@ -58,7 +58,7 @@ def parse_failures(xml_path):
58
58
  if reason_match:
59
59
  reason = reason_match.group(1).strip()
60
60
  else:
61
- lines = [l.strip() for l in combined.split("\n") if l.strip()]
61
+ lines = [line.strip() for line in combined.split("\n") if line.strip()]
62
62
  reason = lines[0] if lines else "Unknown Error"
63
63
 
64
64
  # Extract Failed Line in Groovy Page / Script
@@ -8,9 +8,7 @@ Enforces a strict Rolling Window (< 50 lines, ~200 tokens) to guarantee zero tok
8
8
 
9
9
  import os
10
10
  import sys
11
- import json
12
11
  from datetime import datetime
13
- from pathlib import Path
14
12
 
15
13
  # Enforce UTF-8 stdout encoding for Windows console compatibility
16
14
  if hasattr(sys.stdout, 'reconfigure'):
@@ -25,7 +25,7 @@ import json
25
25
  import hashlib
26
26
  import argparse
27
27
  from datetime import datetime, timezone
28
- from typing import Dict, Any, List, Optional, Tuple
28
+ from typing import Dict, Any, Optional
29
29
 
30
30
  # Enforce UTF-8 stdout encoding
31
31
  if hasattr(sys.stdout, 'reconfigure'):
package/tools/repo_map.py CHANGED
@@ -199,13 +199,13 @@ def build_repo_map(target_repo=None, output_txt=None, output_json=None):
199
199
  # Render text map
200
200
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
201
201
  out_lines = [
202
- f"# ==============================================================================",
203
- f"# ENTERPRISE AUTOMATION REPOSITORY MAP (Aider-Style AST Compression)",
202
+ "# ==============================================================================",
203
+ "# ENTERPRISE AUTOMATION REPOSITORY MAP (Aider-Style AST Compression)",
204
204
  f"# Generated : {timestamp}",
205
205
  f"# Target : {repo}",
206
206
  f"# Scope : {len(classes)} Classes ({total_raw_lines} source lines, {total_raw_bytes / 1024:.1f} KB)",
207
- f"# Purpose : Token-budgeted index (< 600 tokens) for instant method lookup",
208
- f"# ==============================================================================\n"
207
+ "# Purpose : Token-budgeted index (< 600 tokens) for instant method lookup",
208
+ "# ==============================================================================\n"
209
209
  ]
210
210
 
211
211
  total_methods = 0
@@ -294,9 +294,9 @@ def main():
294
294
  return 1
295
295
 
296
296
  stats = build_repo_map(target_repo=target_repo)
297
- print(f"\n======================================================================")
298
- print(f" Enterprise Repository Map Compressor (Aider-Style AST Map) ")
299
- print(f"======================================================================")
297
+ print("\n======================================================================")
298
+ print(" Enterprise Repository Map Compressor (Aider-Style AST Map) ")
299
+ print("======================================================================")
300
300
  print(f" Target Repository : {stats['target_repo']}")
301
301
  print(f" Indexed Classes : {stats['classes_count']} Page Classes & Helpers")
302
302
  print(f" Extracted Methods : {stats['methods_count']} API signatures")
@@ -304,7 +304,7 @@ def main():
304
304
  print(f" Compressed Map Size : {stats['compressed_bytes'] / 1024:.1f} KB (~{stats['estimated_tokens']} tokens)")
305
305
  print(f" Compression Ratio : {stats['compression_ratio_pct']:.1f}% reduction")
306
306
  print(f" Artifact Generated : {stats['output_file']}")
307
- print(f"======================================================================\n")
307
+ print("======================================================================\n")
308
308
 
309
309
  if args.print:
310
310
  with open(stats['output_file'], "r", encoding="utf-8") as f:
@@ -0,0 +1,32 @@
1
+ """
2
+ Synapse Hub — Reporting & Telemetry Engines (tools.report)
3
+ ----------------------------------------------------------
4
+ Domain package grouping all HTML dashboard, spreadsheet, JTL/Newman
5
+ metric parser, and visual reporting tools.
6
+ """
7
+
8
+ from tools import (
9
+ bug_reporter,
10
+ dashboard_generator,
11
+ generate_allure_report,
12
+ generate_test_reports,
13
+ jtl_analyzer,
14
+ md_to_xlsx,
15
+ newman_analyzer,
16
+ output_formatter,
17
+ telemetry_correlator,
18
+ welcome_page,
19
+ )
20
+
21
+ __all__ = [
22
+ "bug_reporter",
23
+ "dashboard_generator",
24
+ "generate_allure_report",
25
+ "generate_test_reports",
26
+ "jtl_analyzer",
27
+ "md_to_xlsx",
28
+ "newman_analyzer",
29
+ "output_formatter",
30
+ "telemetry_correlator",
31
+ "welcome_page",
32
+ ]
@@ -17,11 +17,10 @@ Reference:
17
17
 
18
18
  import os
19
19
  import sys
20
- import json
21
20
  import shlex
22
21
  import subprocess
23
22
  from dataclasses import dataclass, field
24
- from typing import List, Optional, Callable, Dict, Any
23
+ from typing import List, Optional, Dict, Any
25
24
 
26
25
  # Enforce UTF-8 stdout encoding for Windows console compatibility
27
26
  if hasattr(sys.stdout, 'reconfigure'):
@@ -61,7 +60,7 @@ class ExecutionSandbox:
61
60
  """Executes a forward shell command and immediately records its reversible undo counterpart."""
62
61
  try:
63
62
  args = shlex.split(forward_cmd) if isinstance(forward_cmd, str) else forward_cmd
64
- res = subprocess.run(args, text=True, capture_output=True)
63
+ res = subprocess.run(args, text=True, capture_output=True, timeout=60.0)
65
64
  if res.returncode == 0:
66
65
  self.record_mutation(action_type, target_resource, undo_cmd, {"forward_cmd": forward_cmd})
67
66
  return True
@@ -98,7 +97,7 @@ class ExecutionSandbox:
98
97
  elif cmd:
99
98
  try:
100
99
  args = shlex.split(cmd) if isinstance(cmd, str) else cmd
101
- res = subprocess.run(args, text=True, capture_output=True)
100
+ res = subprocess.run(args, text=True, capture_output=True, timeout=60.0)
102
101
  success = (res.returncode == 0)
103
102
  if not success:
104
103
  err_msg = res.stderr.strip()
@@ -245,7 +245,7 @@ def detect_action_loop(action_history: List[str], repeat_threshold: int = 4) ->
245
245
  """
246
246
  Detects infinite action loops and repeating cycle patterns among agent tool calls.
247
247
  Inspired by agentblazo/blazo deterministic detection rules.
248
-
248
+
249
249
  Checks:
250
250
  1. Consecutive identical actions (>= repeat_threshold)
251
251
  2. 2-action repeating cycle: [A, B, A, B]
@@ -411,7 +411,7 @@ def main():
411
411
  if all_authorized:
412
412
  print(f"\n✅ All actions cleared by Safety Firewall{' (Dry-Run)' if args.dry_run else ''}.\n")
413
413
  else:
414
- print(f"\n❌ Pre-execution Blocked: Destructive actions intercepted without --force or ALLOW_DESTRUCTIVE=1.\n")
414
+ print("\n❌ Pre-execution Blocked: Destructive actions intercepted without --force or ALLOW_DESTRUCTIVE=1.\n")
415
415
 
416
416
  return 0 if all_authorized else 1
417
417
 
@@ -28,7 +28,7 @@ import re
28
28
  import json
29
29
  import subprocess
30
30
  import argparse
31
- from typing import Dict, List, Any, Optional, Tuple
31
+ from typing import Dict, List, Any, Tuple
32
32
 
33
33
  if hasattr(sys.stdout, 'reconfigure'):
34
34
  try:
@@ -80,10 +80,11 @@ def get_git_diff(repo_dir: str, target: str = "uncommitted") -> str:
80
80
  text=True,
81
81
  encoding="utf-8",
82
82
  errors="ignore",
83
- check=True
83
+ check=True,
84
+ timeout=30.0
84
85
  )
85
86
  return res.stdout
86
- except Exception as e:
87
+ except Exception:
87
88
  return ""
88
89
 
89
90
 
@@ -24,7 +24,6 @@ import json
24
24
  import shutil
25
25
  import argparse
26
26
  from pathlib import Path
27
- from datetime import datetime
28
27
 
29
28
  # Enforce UTF-8 stdout encoding for Windows console compatibility
30
29
  if hasattr(sys.stdout, 'reconfigure'):
@@ -39,9 +38,8 @@ HUB_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
39
38
  TEMPLATES_DIR = os.path.join(HUB_ROOT, "templates")
40
39
  sys.path.insert(0, SCRIPT_DIR)
41
40
 
42
- import analyze_project
43
- import verify_all
44
- import project_state
41
+ import analyze_project # noqa: E402
42
+ import project_state # noqa: E402
45
43
 
46
44
  GREEN = "\033[92m"
47
45
  RED = "\033[91m"
@@ -412,7 +410,7 @@ def inject_antigravity_global_plugin():
412
410
  try:
413
411
  os.unlink(dst)
414
412
  except OSError:
415
- unlinked = False
413
+ _ = False
416
414
  if sys.platform == "win32":
417
415
  try:
418
416
  import _winapi
@@ -420,7 +418,7 @@ def inject_antigravity_global_plugin():
420
418
  _winapi.CreateJunction(src, dst)
421
419
  return
422
420
  except (OSError, ImportError, AttributeError):
423
- junction_created = False
421
+ _ = False
424
422
  shutil.copytree(src, dst, dirs_exist_ok=True, ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.tmp"))
425
423
 
426
424
  sync_dir(os.path.join(HUB_ROOT, ".agents", "skills"), os.path.join(target_plugin_dir, "skills"))
@@ -612,26 +610,26 @@ def run_setup(target_repo, dry_run=False, profile="auto", no_global=True, no_git
612
610
  if dry_run:
613
611
  print(f"Operational Mode : {YELLOW}DRY-RUN (Simulating without modifying files on disk){RESET}\n")
614
612
  else:
615
- print(f"Operational Mode : APPLY CHANGES\n")
613
+ print("Operational Mode : APPLY CHANGES\n")
616
614
 
617
615
  if dry_run:
618
616
  print(f"{YELLOW}[DRY-RUN PREVIEW]{RESET}")
619
617
  print(f" • Profile: {profile.upper()}")
620
618
  print(f" • Would generate: {os.path.join(target_repo, 'PROJECT_AI_GUIDE.md')}")
621
- print(f" • Would deploy AI bridges: .cursor/rules/synapse-qa.mdc, .github/copilot-instructions.md, .windsurfrules")
619
+ print(" • Would deploy AI bridges: .cursor/rules/synapse-qa.mdc, .github/copilot-instructions.md, .windsurfrules")
622
620
  print(f" • Would install pre-push hook: {os.path.join(target_repo, '.git', 'hooks', 'pre-push')}")
623
- print(f" • Would initialize: Central Agent Memory (~/.agent_memory/global_cache.db)")
621
+ print(" • Would initialize: Central Agent Memory (~/.agent_memory/global_cache.db)")
624
622
  print(f"\n{GREEN}✅ Dry-run completed successfully. Zero changes written to disk.{RESET}\n")
625
623
  return 0
626
624
 
627
625
  # 2. Ensure PROJECT_AI_GUIDE.md & PROJECT_STATE.md
628
626
  print(f"{BOLD}[1/5] Checking Project AI Guide & State Anchor...{RESET}")
629
- guide_created = ensure_project_ai_guide(target_repo, stack_info, profile=profile)
627
+ ensure_project_ai_guide(target_repo, stack_info, profile=profile)
630
628
  project_state.init_state(target_repo)
631
629
 
632
630
  # 3. Deploy Universal AI Bridges (Cursor, Claude Code, GitHub Copilot, Windsurf)
633
631
  print(f"\n{BOLD}[2/5] Deploying Universal AI Bridges (Cursor, Claude, Copilot, Windsurf)...{RESET}")
634
- bridges_deployed = install_universal_ai_bridges(target_repo)
632
+ install_universal_ai_bridges(target_repo)
635
633
 
636
634
  # 4. Inject Antigravity Global Plugin
637
635
  plugin_injected = False
@@ -687,7 +685,7 @@ def run_setup(target_repo, dry_run=False, profile="auto", no_global=True, no_git
687
685
  print(f" Target Framework : {stack_info['framework']}")
688
686
  print(f" Active Profile : {profile.upper()}")
689
687
  print(f" AI Project Guide : {os.path.join(target_repo, 'PROJECT_AI_GUIDE.md')}")
690
- print(f" Universal Bridges : Cursor (.cursor/rules), Claude Code, Copilot, Windsurf")
688
+ print(" Universal Bridges : Cursor (.cursor/rules), Claude Code, Copilot, Windsurf")
691
689
  print(f" Antigravity Plugin : {'ACTIVE (~/.gemini/config/plugins/synapse-hub)' if plugin_injected else 'STANDALONE'}")
692
690
  print(f" AST Method Map : {os.path.join(target_repo, 'repo_map.txt')}")
693
691
  print(f" Health Report : {os.path.join(target_repo, 'PROJECT_HEALTH_REPORT.md')}")
@@ -23,8 +23,8 @@ import sys
23
23
  import re
24
24
  import math
25
25
  import json
26
+ from typing import Dict, List, Tuple, Any
26
27
  import argparse
27
- from typing import Dict, List, Tuple
28
28
  from collections import Counter
29
29
 
30
30
  # Enforce UTF-8 stdout encoding for Windows console compatibility
@@ -136,7 +136,7 @@ class SkillCollisionGuard:
136
136
  fallback_name = os.path.basename(os.path.dirname(path))
137
137
  return fallback_name, ""
138
138
 
139
- def audit(self) -> Tuple[bool, List[Dict[str, any]]]:
139
+ def audit(self) -> Tuple[bool, List[Dict[str, Any]]]:
140
140
  """
141
141
  Computes pairwise similarity.
142
142
  Returns (is_clean, report_items).
@@ -211,7 +211,7 @@ def run_guard(skills_dir: str = ".agents/skills", threshold: float = 0.75, warn_
211
211
  print("=" * 70 + "\n")
212
212
  return 1
213
213
 
214
- print(f"\n✅ [PASS] 0 routing collisions detected. All skill descriptions uniquely segmented.\n")
214
+ print("\n✅ [PASS] 0 routing collisions detected. All skill descriptions uniquely segmented.\n")
215
215
  print("=" * 70 + "\n")
216
216
  return 0
217
217
 
@@ -22,7 +22,7 @@ import json
22
22
  import re
23
23
  import difflib
24
24
  import argparse
25
- from dataclasses import dataclass, asdict
25
+ from dataclasses import dataclass
26
26
  from typing import List, Optional, Dict, Any, Set
27
27
 
28
28
  # Enforce UTF-8 stdout encoding for Windows console compatibility
@@ -50,11 +50,25 @@ QA_SYNONYMS: Dict[str, Set[str]] = {
50
50
  "dropdown": {"select", "combobox", "option", "menu", "picker", "choice"},
51
51
  "invoice": {"billing", "order", "receipt", "payment", "charge", "reconcile"},
52
52
  "cart": {"checkout", "basket", "purchase", "item", "order"},
53
- "upload": {"attachment", "file", "import", "choose-file", "document"},
53
+ "upload": {"attachment", "file", "import", "choose-file", "document", "dropzone"},
54
+ "export": {"export", "download", "csv", "excel", "pdf", "report"},
55
+ "toast": {"toast", "notification", "snackbar", "alert", "message", "banner", "popup"},
56
+ "spinner": {"spinner", "loader", "loading", "shimmer", "skeleton", "progress", "wait"},
57
+ "drawer": {"drawer", "sidebar", "panel", "modal", "aside", "sheet"},
58
+ "tab": {"tab", "tabs", "tabpanel", "nav", "segment"},
54
59
  "wait": {"detach", "detached", "sleep", "loading", "spinner", "ready", "networkidle", "smartwait"},
55
60
  }
56
61
 
57
62
 
63
+ def split_identifier(identifier: str) -> List[str]:
64
+ """Splits camelCase, PascalCase, and snake_case tokens into lowercase sub-tokens."""
65
+ tokens = re.sub(r'([A-Z]+)', r' \1', identifier).split()
66
+ sub_tokens = []
67
+ for t in tokens:
68
+ sub_tokens.extend(re.split(r'[_\\-]+', t))
69
+ return [t.lower() for t in sub_tokens if len(t) >= 2]
70
+
71
+
58
72
  def _expand_tokens_with_synonyms(tokens: List[str]) -> Set[str]:
59
73
  """Expands query tokens using the QA domain synonym clusters."""
60
74
  expanded = set(tokens)
@@ -101,6 +115,15 @@ class PageClassPrimitive:
101
115
  tags: str = "" # Comma-separated domain tags, e.g. "auth,sso,login"
102
116
 
103
117
 
118
+ class ManagedConnection(sqlite3.Connection):
119
+ """SQLite connection that automatically commits/rollbacks and closes on context exit."""
120
+ def __exit__(self, exc_type, exc_val, exc_tb):
121
+ try:
122
+ super().__exit__(exc_type, exc_val, exc_tb)
123
+ finally:
124
+ self.close()
125
+
126
+
104
127
  class SkillVault:
105
128
  """Persistent library of verified, self-contained Page Class methods."""
106
129
 
@@ -113,8 +136,8 @@ class SkillVault:
113
136
  self.db_path = db_path
114
137
  self._init_schema()
115
138
 
116
- def _get_connection(self) -> sqlite3.Connection:
117
- conn = sqlite3.connect(self.db_path)
139
+ def _get_connection(self) -> ManagedConnection:
140
+ conn = sqlite3.connect(self.db_path, factory=ManagedConnection)
118
141
  conn.row_factory = sqlite3.Row
119
142
  return conn
120
143
 
@@ -176,7 +199,13 @@ class SkillVault:
176
199
  Queries verified primitives using intelligent fuzzy semantic matching:
177
200
  combines exact tokens, synonym expansion, n-gram substring match, and SequenceMatcher similarity.
178
201
  """
179
- raw_tokens = [t.lower() for t in re.findall(r'[a-zA-Z0-9_-]+', query) if len(t) >= 2]
202
+ words = re.findall(r'[a-zA-Z0-9_-]+', query)
203
+ raw_tokens = []
204
+ for w in words:
205
+ sub = split_identifier(w)
206
+ raw_tokens.extend(sub if sub else [w.lower()])
207
+ raw_tokens = list(dict.fromkeys(raw_tokens))
208
+
180
209
  if not raw_tokens and not tag:
181
210
  return self.list_primitives(limit=top_k)
182
211
 
@@ -194,7 +223,12 @@ class SkillVault:
194
223
  scored = []
195
224
  for r in rows:
196
225
  corpus_text = f"{r['page_class']} {r['method_signature']} {r['semantic_docstring'] or ''} {r['tags'] or ''}".lower()
197
- corpus_tokens = [t.lower() for t in re.findall(r'[a-zA-Z0-9_-]+', corpus_text) if len(t) >= 2]
226
+ corpus_words = re.findall(r'[a-zA-Z0-9_-]+', corpus_text)
227
+ corpus_tokens = []
228
+ for cw in corpus_words:
229
+ sub = split_identifier(cw)
230
+ corpus_tokens.extend(sub if sub else [cw.lower()])
231
+ corpus_tokens = list(dict.fromkeys(corpus_tokens))
198
232
 
199
233
  # Calculate individual query token matches
200
234
  token_scores = [_calculate_token_match_score(qt, corpus_tokens) for qt in raw_tokens]
@@ -220,6 +254,54 @@ class SkillVault:
220
254
  scored.sort(key=lambda x: (x["relevance_score"], x["mutation_score"]), reverse=True)
221
255
  return scored[:top_k]
222
256
 
257
+ def harvest_page_classes(self, dir_path: str) -> int:
258
+ """
259
+ Scans a directory for Page Classes across TS, JS, Python, and Groovy,
260
+ extracts method signatures, and registers them into SkillVault.
261
+ """
262
+ if not os.path.isdir(dir_path):
263
+ return 0
264
+ count = 0
265
+ for root, _, files in os.walk(dir_path):
266
+ for f in files:
267
+ ext = os.path.splitext(f)[1].lower()
268
+ if ext not in (".py", ".ts", ".js", ".groovy", ".java"):
269
+ continue
270
+ file_path = os.path.join(root, f)
271
+ try:
272
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as fp:
273
+ content = fp.read()
274
+ except OSError:
275
+ continue
276
+
277
+ page_class = os.path.splitext(f)[0]
278
+ method_matches = re.finditer(r'(?:def\s+([a-zA-Z_]\w*)\s*\(([^)]*)\)|(?:async\s+)?([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(?::\s*[^\{]+)?\s*\{)', content)
279
+ for mm in method_matches:
280
+ m_name = mm.group(1) or mm.group(3)
281
+ m_args = mm.group(2) if mm.group(1) else mm.group(4)
282
+ if not m_name or m_name.startswith("_") or m_name in ("constructor", "constructor__"):
283
+ continue
284
+ skill_id = f"SKILL_{page_class.upper()}_{m_name.upper()}"
285
+ sig = f"{m_name}({m_args.strip() if m_args else ''})"
286
+ doc_m = re.search(rf'{re.escape(m_name)}[^\n]*\n\s*[\'\""""/]+([^\'\""""\n*]+)', content)
287
+ doc = doc_m.group(1).strip() if doc_m else f"Page method for {m_name} on {page_class}"
288
+ tags = ",".join(split_identifier(page_class) + split_identifier(m_name))
289
+
290
+ prim = PageClassPrimitive(
291
+ skill_id=skill_id,
292
+ page_class=page_class,
293
+ method_signature=sig,
294
+ method_code=f"// Harvested from {f}\n{sig}",
295
+ locator_tier=1,
296
+ semantic_docstring=doc,
297
+ verified_on_url=f"local://{f}",
298
+ mutation_score=85.0,
299
+ tags=tags
300
+ )
301
+ if self.register_primitive(prim):
302
+ count += 1
303
+ return count
304
+
223
305
  def list_primitives(self, limit: int = 50) -> List[Dict[str, Any]]:
224
306
  with self._get_connection() as conn:
225
307
  rows = conn.execute("SELECT * FROM page_primitives ORDER BY created_at DESC LIMIT ?", (limit,)).fetchall()
@@ -230,12 +312,18 @@ def main():
230
312
  parser = argparse.ArgumentParser(description="SkillVault Lifelong POM Library")
231
313
  parser.add_argument("--query", default=None, help="Query verified primitives by keyword (fuzzy semantic search)")
232
314
  parser.add_argument("--tag", default=None, help="Filter primitives by domain tag (e.g. auth, table, invoice)")
315
+ parser.add_argument("--harvest", default=None, help="Directory path to harvest Page Classes from")
233
316
  parser.add_argument("--list", action="store_true", help="List all stored primitives")
234
317
  parser.add_argument("--seed-samples", action="store_true", help="Seed initial verified POM primitives")
235
318
  args = parser.parse_args()
236
319
 
237
320
  vault = SkillVault()
238
321
 
322
+ if args.harvest:
323
+ num = vault.harvest_page_classes(args.harvest)
324
+ print(f"✅ Harvested {num} Page Class primitives from '{args.harvest}'.")
325
+ return 0
326
+
239
327
  if args.seed_samples:
240
328
  samples = [
241
329
  PageClassPrimitive(
@@ -42,17 +42,15 @@ Usage:
42
42
  from __future__ import annotations
43
43
 
44
44
  import ast
45
- import fnmatch
46
45
  import hashlib
47
46
  import json
48
47
  import os
49
48
  import re
50
49
  import sys
51
- import unicodedata
52
50
  from dataclasses import dataclass, field
53
51
  from enum import Enum
54
52
  from pathlib import Path
55
- from typing import Any, Dict, List, Optional, Set, Tuple
53
+ from typing import Any, Dict, List, Tuple
56
54
 
57
55
  # Enforce UTF-8 stdout encoding for Windows console compatibility
58
56
  if hasattr(sys.stdout, "reconfigure"):
@@ -529,9 +527,9 @@ def scan_text_patterns(file_path: str, content: str) -> List[Finding]:
529
527
  # Line number offset indexing
530
528
  line_offsets = []
531
529
  curr = 0
532
- for l in lines:
530
+ for line_str in lines:
533
531
  line_offsets.append(curr)
534
- curr += len(l)
532
+ curr += len(line_str)
535
533
 
536
534
  def get_line_num(char_idx: int) -> int:
537
535
  for idx, offset in enumerate(line_offsets):
@@ -925,7 +923,7 @@ def scan_file(file_path: str) -> List[Finding]:
925
923
  try:
926
924
  with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
927
925
  content = f.read()
928
- except Exception as e:
926
+ except Exception:
929
927
  return []
930
928
 
931
929
  findings: List[Finding] = []
@@ -958,7 +956,11 @@ def scan_target(target_path: str) -> ScanReport:
958
956
  all_findings: List[Finding] = []
959
957
  scanned_count = 0
960
958
 
961
- SKIP_DIRS = {".git", ".venv", "venv", "node_modules", "__pycache__", ".pytest_cache", "dist", "build"}
959
+ SKIP_DIRS = {
960
+ ".git", ".venv", "venv", "node_modules", "__pycache__", ".pytest_cache",
961
+ "dist", "build", "claude-ecosystem-repos", "graphify-out", ".synapse",
962
+ ".mypy_cache", ".ruff_cache", ".sandbox_snapshots", "reports"
963
+ }
962
964
  SKIP_FILES = {"skillspector.py"}
963
965
  SCANNABLE_EXTS = {".md", ".py", ".json", ".yaml", ".yml", ".sh", ".ps1", ".txt", ".ts", ".js"}
964
966
 
@@ -1013,7 +1015,7 @@ def format_terminal(report: ScanReport) -> str:
1013
1015
  out.append(f"{BOLD}{CYAN}======================================================================{RESET}")
1014
1016
  out.append(f"📁 Target Scope : {report.target_path}")
1015
1017
  out.append(f"📄 Scanned Files : {report.scanned_files}")
1016
- out.append(f"🛡️ Engine Source : NVIDIA/SkillSpector Spec (71 Patterns, 17 Categories)")
1018
+ out.append("🛡️ Engine Source : NVIDIA/SkillSpector Spec (71 Patterns, 17 Categories)")
1017
1019
 
1018
1020
  rec_color = GREEN if report.recommendation == Recommendation.SAFE else (YELLOW if report.recommendation == Recommendation.CAUTION else RED)
1019
1021
  out.append(f"\n{BOLD}🎯 AUDIT VERDICT :{RESET} {rec_color}{BOLD}{report.recommendation.value}{RESET}")