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.
- package/.agents/agents/live-explorer.md +2 -0
- package/.agents/agents/pom-crafter.md +2 -0
- package/.agents/agents/test-runner.md +3 -0
- package/.agents/rules/communication-style.md +4 -0
- package/.agents/rules/context-engineering.md +1 -0
- package/.agents/rules/git-push-protocol.md +17 -0
- package/.agents/skills/agentless-qa-repair/examples/three_phase_repair_workflow.md +40 -0
- package/.agents/skills/antigravity-test-sentinel/examples/sprint_dod_enforcement.md +47 -0
- package/.agents/skills/auth-session-and-cookie-manager/examples/multi_role_auth_matrix.md +70 -0
- package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_playwright_pom.ts +52 -0
- package/.agents/skills/failure-autopsy-and-healing/examples/closed_loop_healing_walkthrough.md +48 -0
- package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
- package/.agents/skills/lean-impact-scoreboard/examples/sample_scoreboard.md +1 -1
- package/.agents/skills/mipro-prompt-compiler/examples/dspy_assertion_optimizer_case.md +46 -0
- package/.agents/skills/persistent-planning-files/SKILL.md +1 -1
- package/.agents/skills/qa-aci-interface/examples/surgical_code_window_walkthrough.md +40 -0
- package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
- package/.agents/skills/review-spec/SKILL.md +1 -1
- package/.agents/skills/review-spec/examples/pocock_grilling_decision_rubric.md +40 -0
- package/.agents/skills/senior-code-review/SKILL.md +1 -1
- package/.agents/skills/skillspector-security-guard/examples/audit_threat_vectors_report.json +35 -0
- package/.agents/skills/surgical-coding-principles/SKILL.md +1 -1
- package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
- package/.agents/skills/tot-exploratory-testing/examples/payment_gateway_tot_tree.json +62 -0
- package/.agents/skills/verification-before-completion/SKILL.md +2 -2
- package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +2 -2
- package/.agents/skills/web-agent-gym-benchmarking/examples/enterprise_tasks_suite.json +38 -0
- package/.agents/skills/web-vitals-and-performance-profiler/examples/playwright_cwv_profiler_fixture.ts +61 -0
- package/AGENTS.md +6 -4
- package/CLAUDE.md +2 -1
- package/README.en.md +71 -292
- package/README.md +69 -304
- package/action.yml +1 -1
- package/bin/agent-qa.js +2 -2
- package/docs/ARCHITECTURE.en.md +192 -0
- package/docs/ARCHITECTURE.md +135 -88
- package/docs/CI_CD_INTEGRATION.en.md +96 -0
- package/docs/CI_CD_INTEGRATION.md +24 -22
- package/docs/CLI_REFERENCE.en.md +174 -0
- package/docs/CLI_REFERENCE.md +120 -115
- package/docs/CONTRIBUTING.en.md +49 -0
- package/docs/CONTRIBUTING.md +26 -68
- package/docs/QUALITY_GATES.en.md +115 -0
- package/docs/QUALITY_GATES.md +115 -0
- package/docs/research/agent_skill_training_analysis.md +1 -1
- package/package.json +1 -1
- package/scripts/package_dist.py +2 -1
- package/skills_manifest.json +6 -6
- package/synapse.config.json +1 -1
- package/templates/ai_bridges/CLAUDE.md +2 -1
- package/templates/ai_bridges/copilot-instructions.md +2 -2
- package/tools/__init__.py +18 -1
- package/tools/_openapi_parser.py +1 -1
- package/tools/agent_canary.py +42 -30
- package/tools/agentless_patcher.py +2 -2
- package/tools/analyze_project.py +3 -5
- package/tools/api_drift_sentinel.py +4 -4
- package/tools/api_fuzzer.py +8 -8
- package/tools/auto_heal.py +4 -5
- package/tools/auto_heal_reflexion.py +10 -2
- package/tools/autogen_qa_broker.py +2 -2
- package/tools/browser_helpers.js +2 -2
- package/tools/browsergym_eval.py +2 -3
- package/tools/bug_reporter.py +11 -13
- package/tools/bump_version.py +90 -19
- package/tools/cache_resolver.py +78 -83
- package/tools/check_env.py +9 -1
- package/tools/check_version_sync.py +58 -4
- package/tools/ci_healing_hook.py +4 -6
- package/tools/cli.py +5 -7
- package/tools/config_decoupler.py +2 -3
- package/tools/contract_validator.py +141 -0
- package/tools/core/__init__.py +52 -0
- package/tools/count_tokens.py +22 -20
- package/tools/dashboard_generator.py +629 -1000
- package/tools/db_sandbox.py +87 -62
- package/tools/debt_harvester.py +3 -3
- package/tools/distill_log.py +3 -3
- package/tools/dspy_qa_signatures.py +3 -4
- package/tools/export_postman_collection.py +8 -8
- package/tools/flaky_detector.py +2 -2
- package/tools/fuzz_payloads.py +5 -5
- package/tools/generate_allure_report.py +1 -1
- package/tools/generate_jmx_plan.py +4 -4
- package/tools/gherkin_suite.py +1 -1
- package/tools/git_pre_commit_guard.py +2 -2
- package/tools/goldfish_review.py +2 -2
- package/tools/heal/__init__.py +28 -0
- package/tools/heal_pr_automator.py +6 -6
- package/tools/heuristic_locator_matcher.py +2 -2
- package/tools/infer_linter.py +143 -29
- package/tools/interview_story.py +0 -1
- package/tools/jtl_analyzer.py +11 -11
- package/tools/lint/__init__.py +30 -0
- package/tools/lint_ai_docs.py +42 -0
- package/tools/lookup_tc.py +1 -1
- package/tools/matrix_sharder.py +15 -13
- package/tools/mcp/__init__.py +16 -0
- package/tools/mcp_excel_server.py +2 -2
- package/tools/mcp_server.py +9 -7
- package/tools/md_to_xlsx.py +26 -26
- package/tools/micro_mutator.py +32 -15
- package/tools/network_mock_shield.py +27 -27
- package/tools/newman_analyzer.py +2 -3
- package/tools/omni_emitter.py +5 -8
- package/tools/omni_parser.py +5 -5
- package/tools/omni_transpiler.py +5 -5
- package/tools/output_formatter.py +19 -0
- package/tools/perf/__init__.py +24 -0
- package/tools/pinpoint_failure.py +1 -1
- package/tools/project_state.py +0 -2
- package/tools/qa_event_stream.py +1 -1
- package/tools/repo_map.py +8 -8
- package/tools/report/__init__.py +32 -0
- package/tools/reversible_sandbox.py +3 -4
- package/tools/safety_firewall.py +2 -2
- package/tools/senior_code_review.py +4 -3
- package/tools/setup_project.py +10 -12
- package/tools/skill_collision_guard.py +3 -3
- package/tools/skill_vault.py +94 -6
- package/tools/skillspector.py +10 -8
- package/tools/smart_visual_masker.py +4 -6
- package/tools/spec_drift_sentinel.py +1 -2
- package/tools/swe_qa_aci.py +2 -2
- package/tools/synapse_qa_bench.py +3 -3
- package/tools/sync_agent_rules.py +18 -3
- package/tools/telemetry_correlator.py +3 -2
- package/tools/test_impact.py +2 -1
- package/tools/test_memory_os.py +15 -5
- package/tools/textgrad_mutation_optimizer.py +3 -3
- package/tools/tms_sync_bridge.py +5 -5
- package/tools/tot_exploratory_tester.py +5 -6
- package/tools/traffic_session_converter.py +9 -9
- package/tools/universal_ir.py +2 -3
- package/tools/update_sentinel.py +6 -9
- package/tools/verify_all.py +12 -2
- package/tools/visual_diff_canvas.py +4 -4
- package/tools/visual_som_helper.py +15 -13
- package/tools/watch_mode.py +1 -1
- package/tools/wcag_audit.py +22 -1
- 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 = [
|
|
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
|
package/tools/project_state.py
CHANGED
|
@@ -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'):
|
package/tools/qa_event_stream.py
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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(
|
|
298
|
-
print(
|
|
299
|
-
print(
|
|
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(
|
|
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,
|
|
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()
|
package/tools/safety_firewall.py
CHANGED
|
@@ -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(
|
|
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,
|
|
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
|
|
87
|
+
except Exception:
|
|
87
88
|
return ""
|
|
88
89
|
|
|
89
90
|
|
package/tools/setup_project.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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(
|
|
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
|
|
package/tools/skill_vault.py
CHANGED
|
@@ -22,7 +22,7 @@ import json
|
|
|
22
22
|
import re
|
|
23
23
|
import difflib
|
|
24
24
|
import argparse
|
|
25
|
-
from dataclasses import dataclass
|
|
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) ->
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
package/tools/skillspector.py
CHANGED
|
@@ -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,
|
|
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
|
|
530
|
+
for line_str in lines:
|
|
533
531
|
line_offsets.append(curr)
|
|
534
|
-
curr += len(
|
|
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
|
|
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 = {
|
|
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(
|
|
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}")
|