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.
- package/.agents/agents/failure-analyst.md +4 -0
- package/.agents/agents/live-explorer.md +2 -0
- package/.agents/agents/orchestrator.md +16 -0
- package/.agents/agents/pom-crafter.md +10 -0
- package/.agents/agents/quality-gatekeeper.md +5 -0
- package/.agents/agents/test-runner.md +8 -0
- package/.agents/rules/git-push-protocol.md +1 -1
- package/.agents/rules/graphify.md +2 -0
- package/.agents/rules/mandatory-playwright-live.md +9 -9
- package/.agents/rules/mobile_appium_rules.md +8 -1
- package/.agents/skills/actor-qa-swarm/SKILL.md +77 -0
- package/.agents/skills/actor-qa-swarm/examples/broker_sharding_sample.json +12 -0
- package/.agents/skills/agentless-qa-repair/SKILL.md +69 -0
- package/.agents/skills/agentless-qa-repair/examples/healing_report_sample.json +17 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +1 -1
- package/.agents/skills/clean-pom-crafter/SKILL.md +2 -2
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +4 -4
- package/.agents/skills/event-stream-qa-orchestrator/SKILL.md +79 -0
- package/.agents/skills/event-stream-qa-orchestrator/examples/timeline_sample.jsonl +2 -0
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +2 -2
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +6 -21
- package/.agents/skills/katalon-cli-runner/SKILL.md +1 -1
- package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
- package/.agents/skills/mipro-prompt-compiler/SKILL.md +59 -0
- package/.agents/skills/mipro-prompt-compiler/examples/compiled_module_sample.json +22 -0
- package/.agents/skills/qa-aci-interface/SKILL.md +67 -0
- package/.agents/skills/qa-aci-interface/examples/surgical_edit_sample.json +12 -0
- package/.agents/skills/qa-memory-os/SKILL.md +80 -0
- package/.agents/skills/qa-memory-os/examples/archival_memory_sample.json +21 -0
- package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
- package/.agents/skills/review-spec/SKILL.md +60 -0
- package/.agents/skills/review-spec/examples/sample_spec_review.md +33 -0
- package/.agents/skills/senior-code-review/SKILL.md +1 -1
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +2 -2
- package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
- package/.agents/skills/tot-exploratory-testing/SKILL.md +59 -0
- package/.agents/skills/tot-exploratory-testing/examples/tot_graph_sample.json +35 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +2 -2
- package/.agents/skills/web-agent-gym-benchmarking/SKILL.md +62 -0
- package/.agents/skills/web-agent-gym-benchmarking/examples/benchmark_report_sample.json +22 -0
- package/CLAUDE.md +2 -2
- package/README.en.md +70 -291
- package/README.md +68 -303
- package/docs/ARCHITECTURE.md +54 -8
- package/docs/CONTRIBUTING.md +1 -1
- package/docs/QUALITY_GATES.md +113 -0
- package/docs/research/EXTERNAL_AGENT_SKILLS_SURVEY.md +1062 -0
- package/package.json +1 -1
- package/scripts/package_dist.py +2 -1
- package/skills_manifest.json +92 -11
- package/synapse.config.json +1 -1
- package/templates/ai_bridges/CLAUDE.md +2 -2
- package/templates/ai_bridges/copilot-instructions.md +2 -2
- package/tools/TOOLS_RUNBOOK.md +1 -1
- package/tools/__init__.py +18 -1
- package/tools/_openapi_parser.py +158 -0
- package/tools/agent_canary.py +42 -30
- package/tools/agentless_patcher.py +417 -0
- package/tools/analyze_project.py +12 -11
- package/tools/api_drift_sentinel.py +4 -4
- package/tools/api_fuzzer.py +8 -8
- package/tools/auto_heal.py +23 -5
- package/tools/auto_heal_reflexion.py +2 -2
- package/tools/autogen_qa_broker.py +318 -0
- package/tools/browser_helpers.js +2 -2
- package/tools/browsergym_eval.py +236 -0
- package/tools/bug_reporter.py +11 -13
- package/tools/bump_version.py +75 -5
- package/tools/cache_resolver.py +78 -83
- package/tools/check_env.py +9 -1
- package/tools/check_version_sync.py +40 -3
- package/tools/ci_healing_hook.py +5 -7
- package/tools/cli.py +5 -7
- package/tools/config_decoupler.py +2 -3
- package/tools/core/__init__.py +52 -0
- package/tools/count_tokens.py +22 -20
- package/tools/dashboard_generator.py +628 -999
- package/tools/db_sandbox.py +69 -53
- 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 +29 -4
- package/tools/heuristic_locator_matcher.py +2 -2
- package/tools/infer_linter.py +8 -8
- 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 +3 -4
- package/tools/md_to_xlsx.py +26 -26
- package/tools/micro_mutator.py +11 -12
- package/tools/mipro_assertion_tuner.py +207 -0
- 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 +13 -1
- package/tools/project_state.py +4 -2
- package/tools/qa_event_stream.py +336 -0
- package/tools/repo_map.py +8 -8
- package/tools/report/__init__.py +32 -0
- package/tools/reversible_sandbox.py +5 -2
- package/tools/safety_firewall.py +2 -2
- package/tools/senior_code_review.py +2 -2
- package/tools/setup_project.py +10 -12
- package/tools/skill_collision_guard.py +3 -3
- package/tools/skill_vault.py +12 -3
- package/tools/skillspector.py +5 -7
- package/tools/smart_visual_masker.py +4 -6
- package/tools/spec_drift_sentinel.py +1 -2
- package/tools/swe_qa_aci.py +369 -0
- package/tools/synapse_qa_bench.py +3 -3
- package/tools/sync_agent_rules.py +18 -4
- package/tools/telemetry_correlator.py +3 -2
- package/tools/test_memory_os.py +419 -0
- package/tools/textgrad_mutation_optimizer.py +3 -3
- package/tools/tms_sync_bridge.py +5 -5
- package/tools/tot_exploratory_tester.py +239 -0
- 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 +10 -0
- package/tools/visual_diff_canvas.py +1 -2
- 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
|
@@ -7,11 +7,9 @@ Automatically disables dynamic CSS animations, freezes SVG spinners,
|
|
|
7
7
|
and masks fluctuating UI elements (timestamps, clocks, avatars, ad banners).
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
import os
|
|
11
|
-
import re
|
|
12
10
|
import json
|
|
13
11
|
import argparse
|
|
14
|
-
from typing import List,
|
|
12
|
+
from typing import List, Optional
|
|
15
13
|
|
|
16
14
|
|
|
17
15
|
class SmartVisualMasker:
|
|
@@ -56,13 +54,13 @@ class SmartVisualMasker:
|
|
|
56
54
|
return cls.ANTI_FLAKE_CSS.strip()
|
|
57
55
|
|
|
58
56
|
@classmethod
|
|
59
|
-
def generate_playwright_ts_options(cls, additional_selectors: List[str] = None) -> str:
|
|
57
|
+
def generate_playwright_ts_options(cls, additional_selectors: Optional[List[str]] = None) -> str:
|
|
60
58
|
"""
|
|
61
59
|
Generates Playwright TypeScript assertion options with masking and disabled animations.
|
|
62
60
|
"""
|
|
63
61
|
selectors = cls.DEFAULT_MASK_SELECTORS + (additional_selectors or [])
|
|
64
62
|
locators_ts = ",\n ".join([f"page.locator('{sel}')" for sel in selectors])
|
|
65
|
-
|
|
63
|
+
|
|
66
64
|
return f"""await expect(page).toHaveScreenshot('visual-baseline.png', {{
|
|
67
65
|
animations: 'disabled',
|
|
68
66
|
caret: 'hide',
|
|
@@ -74,7 +72,7 @@ class SmartVisualMasker:
|
|
|
74
72
|
}});"""
|
|
75
73
|
|
|
76
74
|
@classmethod
|
|
77
|
-
def generate_browser_mask_script(cls, selectors: List[str] = None) -> str:
|
|
75
|
+
def generate_browser_mask_script(cls, selectors: Optional[List[str]] = None) -> str:
|
|
78
76
|
"""
|
|
79
77
|
Generates JavaScript snippet to run inside the live browser
|
|
80
78
|
to immediately freeze DOM animations and paint solid black masks
|
|
@@ -206,7 +206,6 @@ def compute_drift(spec_criteria: List[Dict[str, Any]], code_files: List[Tuple[st
|
|
|
206
206
|
all_code_actions.extend([(file_path, a) for a in actions])
|
|
207
207
|
combined_code += "\n" + content
|
|
208
208
|
|
|
209
|
-
combined_code_lower = combined_code.lower()
|
|
210
209
|
code_tokens = tokenize_text(combined_code)
|
|
211
210
|
|
|
212
211
|
covered_criteria = []
|
|
@@ -289,7 +288,7 @@ def generate_markdown_report(result: Dict[str, Any], spec_file: str, code_path:
|
|
|
289
288
|
md.append("> Generated autonomously by Synapse-Hub `tools/spec_drift_sentinel.py` ")
|
|
290
289
|
md.append(f"> **Specification:** `{spec_file}` | **Code Target:** `{code_path}` \n")
|
|
291
290
|
md.append("---")
|
|
292
|
-
|
|
291
|
+
|
|
293
292
|
status_icon = "✅" if result["passed"] else "❌"
|
|
294
293
|
md.append(f"## 📊 Executive Summary: {status_icon} [{result['status']}]")
|
|
295
294
|
md.append(f"- **Alignment Score:** `{result['alignment_score']}%` (Pass threshold: >= 80% & zero Deficit Drift)")
|
|
@@ -0,0 +1,369 @@
|
|
|
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
|
+
QA Agent-Computer Interface (SWE-agent ACI Pattern)
|
|
8
|
+
---------------------------------------------------
|
|
9
|
+
Provides a safe, constrained interface for AI agents interacting with repository files:
|
|
10
|
+
1. view-window: Read files in constrained windows (40-50 lines) with explicit line numbering,
|
|
11
|
+
preventing context window blowout.
|
|
12
|
+
2. surgical-edit: Safe contiguous chunk replacement with pre-commit AST syntax validation,
|
|
13
|
+
Clean POM checks (zero raw selectors in test scripts), and 100% UTF-8 No BOM enforcement.
|
|
14
|
+
3. run-distilled: Execute shell commands and distill stdout/stderr to head + tail lines,
|
|
15
|
+
preventing runner log flooding.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
python tools/swe_qa_aci.py view-window --file src/pages/LoginPage.ts --start-line 20 --window-size 40
|
|
19
|
+
python tools/swe_qa_aci.py surgical-edit --file src/pages/LoginPage.ts --target-chunk "..." --replacement-chunk "..."
|
|
20
|
+
python tools/swe_qa_aci.py run-distilled --command "pytest" --max-lines 50
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
import re
|
|
26
|
+
import json
|
|
27
|
+
import ast
|
|
28
|
+
import subprocess
|
|
29
|
+
import argparse
|
|
30
|
+
from typing import Dict, Any, Optional, Tuple
|
|
31
|
+
|
|
32
|
+
# Enforce UTF-8 stdout encoding
|
|
33
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
34
|
+
try:
|
|
35
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
36
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SweQaAci:
|
|
42
|
+
"""Agent-Computer Interface for Safe QA Operations."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, repo_root: Optional[str] = None):
|
|
45
|
+
self.repo_root = repo_root or os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
46
|
+
|
|
47
|
+
def resolve_path(self, file_path: str) -> str:
|
|
48
|
+
"""Resolves file path safely relative to repo root."""
|
|
49
|
+
if os.path.isabs(file_path):
|
|
50
|
+
return file_path
|
|
51
|
+
return os.path.join(self.repo_root, file_path)
|
|
52
|
+
|
|
53
|
+
def view_window(self, file_path: str, start_line: int = 1, window_size: int = 40,
|
|
54
|
+
as_json: bool = False) -> Dict[str, Any]:
|
|
55
|
+
"""Reads file within a bounded window with line numbers to avoid context bloat."""
|
|
56
|
+
resolved = self.resolve_path(file_path)
|
|
57
|
+
if not os.path.exists(resolved):
|
|
58
|
+
return {
|
|
59
|
+
"status": "FAIL",
|
|
60
|
+
"error": f"File not found: {file_path}",
|
|
61
|
+
"lines": []
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
with open(resolved, "r", encoding="utf-8", errors="replace") as f:
|
|
66
|
+
all_lines = f.readlines()
|
|
67
|
+
except Exception as e:
|
|
68
|
+
return {"status": "FAIL", "error": str(e), "lines": []}
|
|
69
|
+
|
|
70
|
+
total_lines = len(all_lines)
|
|
71
|
+
start_idx = max(1, start_line)
|
|
72
|
+
end_idx = min(total_lines, start_idx + window_size - 1)
|
|
73
|
+
|
|
74
|
+
window_lines = []
|
|
75
|
+
for i in range(start_idx, end_idx + 1):
|
|
76
|
+
line_str = all_lines[i - 1].rstrip("\r\n")
|
|
77
|
+
window_lines.append(f"{i:4d} | {line_str}")
|
|
78
|
+
|
|
79
|
+
result = {
|
|
80
|
+
"status": "PASS",
|
|
81
|
+
"file": file_path.replace("\\", "/"),
|
|
82
|
+
"start_line": start_idx,
|
|
83
|
+
"end_line": end_idx,
|
|
84
|
+
"total_lines": total_lines,
|
|
85
|
+
"window_size": len(window_lines),
|
|
86
|
+
"content": "\n".join(window_lines)
|
|
87
|
+
}
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
def validate_syntax(self, content: str, file_path: str) -> Tuple[bool, str]:
|
|
91
|
+
"""Validates AST syntax based on file extension."""
|
|
92
|
+
ext = file_path.rsplit(".", 1)[-1].lower() if "." in file_path else ""
|
|
93
|
+
if ext == "py":
|
|
94
|
+
try:
|
|
95
|
+
ast.parse(content)
|
|
96
|
+
return True, "SYNTAX_VALID"
|
|
97
|
+
except SyntaxError as e:
|
|
98
|
+
return False, f"Python SyntaxError at line {e.lineno}: {e.msg}"
|
|
99
|
+
|
|
100
|
+
# Lightweight balance check for JS/TS/Groovy
|
|
101
|
+
brackets = {'(': ')', '{': '}', '[': ']'}
|
|
102
|
+
stack = []
|
|
103
|
+
in_string = False
|
|
104
|
+
quote_char = ''
|
|
105
|
+
escape = False
|
|
106
|
+
|
|
107
|
+
for char in content:
|
|
108
|
+
if escape:
|
|
109
|
+
escape = False
|
|
110
|
+
continue
|
|
111
|
+
if char == '\\':
|
|
112
|
+
escape = True
|
|
113
|
+
continue
|
|
114
|
+
if char in ("'", '"', '`'):
|
|
115
|
+
if not in_string:
|
|
116
|
+
in_string = True
|
|
117
|
+
quote_char = char
|
|
118
|
+
elif quote_char == char:
|
|
119
|
+
in_string = False
|
|
120
|
+
elif not in_string:
|
|
121
|
+
if char in brackets:
|
|
122
|
+
stack.append(brackets[char])
|
|
123
|
+
elif char in brackets.values():
|
|
124
|
+
if not stack or stack.pop() != char:
|
|
125
|
+
return False, f"Unbalanced bracket '{char}' detected"
|
|
126
|
+
|
|
127
|
+
if stack:
|
|
128
|
+
return False, f"Unclosed bracket: expected '{stack[-1]}'"
|
|
129
|
+
|
|
130
|
+
return True, "SYNTAX_VALID"
|
|
131
|
+
|
|
132
|
+
def check_clean_pom(self, file_path: str, replacement_chunk: str) -> Tuple[bool, str]:
|
|
133
|
+
"""Verifies Clean POM invariants: no raw selectors in test script files."""
|
|
134
|
+
is_test_script = any(part in file_path.lower() for part in ["tests/", "scripts/", ".spec.", ".test."])
|
|
135
|
+
if is_test_script:
|
|
136
|
+
forbidden = [
|
|
137
|
+
(r'//[\w*@\[\]]+', "Raw XPath (//...)"),
|
|
138
|
+
(r'xpath=', "Explicit xpath= locator"),
|
|
139
|
+
(r'css=', "Explicit css= locator"),
|
|
140
|
+
(r'findTestObject\(', "Katalon findTestObject in script"),
|
|
141
|
+
]
|
|
142
|
+
for pat, desc in forbidden:
|
|
143
|
+
if re.search(pat, replacement_chunk):
|
|
144
|
+
return False, f"Clean POM violation: {desc} strictly forbidden in test scripts ({file_path})"
|
|
145
|
+
return True, "CLEAN_POM_VERIFIED"
|
|
146
|
+
|
|
147
|
+
def surgical_edit(self, file_path: str, target_chunk: str, replacement_chunk: str,
|
|
148
|
+
validate_syntax: bool = True, enforce_clean_pom: bool = True) -> Dict[str, Any]:
|
|
149
|
+
"""Performs precise chunk replacement with safety checks and UTF-8 No BOM guarantee."""
|
|
150
|
+
resolved = self.resolve_path(file_path)
|
|
151
|
+
if not os.path.exists(resolved):
|
|
152
|
+
return {
|
|
153
|
+
"status": "FAIL",
|
|
154
|
+
"file": file_path,
|
|
155
|
+
"error": f"File not found: {file_path}"
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Check Clean POM constraint first
|
|
159
|
+
if enforce_clean_pom:
|
|
160
|
+
pom_ok, pom_msg = self.check_clean_pom(file_path, replacement_chunk)
|
|
161
|
+
if not pom_ok:
|
|
162
|
+
return {
|
|
163
|
+
"status": "FAIL",
|
|
164
|
+
"file": file_path,
|
|
165
|
+
"error": pom_msg,
|
|
166
|
+
"clean_pom_verified": False
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
with open(resolved, "r", encoding="utf-8") as f:
|
|
171
|
+
original_content = f.read()
|
|
172
|
+
except UnicodeDecodeError:
|
|
173
|
+
with open(resolved, "r", encoding="utf-8", errors="replace") as f:
|
|
174
|
+
original_content = f.read()
|
|
175
|
+
|
|
176
|
+
# Normalize line endings for target match
|
|
177
|
+
norm_orig = original_content.replace("\r\n", "\n")
|
|
178
|
+
norm_target = target_chunk.replace("\r\n", "\n")
|
|
179
|
+
norm_replacement = replacement_chunk.replace("\r\n", "\n")
|
|
180
|
+
|
|
181
|
+
if norm_target not in norm_orig:
|
|
182
|
+
return {
|
|
183
|
+
"status": "FAIL",
|
|
184
|
+
"file": file_path,
|
|
185
|
+
"error": "Target chunk was not found in file. Ensure exact whitespace and newline match."
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
occurrences = norm_orig.count(norm_target)
|
|
189
|
+
if occurrences > 1:
|
|
190
|
+
return {
|
|
191
|
+
"status": "FAIL",
|
|
192
|
+
"file": file_path,
|
|
193
|
+
"error": f"Ambiguous target: found {occurrences} occurrences in file. Include more surrounding context."
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Calculate line replacement range
|
|
197
|
+
before_match = norm_orig[:norm_orig.find(norm_target)]
|
|
198
|
+
start_line = before_match.count("\n") + 1
|
|
199
|
+
end_line = start_line + norm_target.count("\n")
|
|
200
|
+
|
|
201
|
+
new_content = norm_orig.replace(norm_target, norm_replacement, 1)
|
|
202
|
+
|
|
203
|
+
# Pre-commit syntax validation
|
|
204
|
+
if validate_syntax:
|
|
205
|
+
syn_ok, syn_msg = self.validate_syntax(new_content, file_path)
|
|
206
|
+
if not syn_ok:
|
|
207
|
+
return {
|
|
208
|
+
"status": "FAIL",
|
|
209
|
+
"file": file_path,
|
|
210
|
+
"error": f"Pre-commit AST syntax check failed: {syn_msg}",
|
|
211
|
+
"ast_validation": "SYNTAX_INVALID"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# Write back cleanly with 100% UTF-8 No BOM
|
|
215
|
+
with open(resolved, "w", encoding="utf-8", newline="\n") as f:
|
|
216
|
+
f.write(new_content)
|
|
217
|
+
|
|
218
|
+
# Diff summary formatting
|
|
219
|
+
target_lines = [f"- {line}" for line in norm_target.splitlines()]
|
|
220
|
+
replacement_lines = [f"+ {line}" for line in norm_replacement.splitlines()]
|
|
221
|
+
diff_summary = "\n".join(target_lines + replacement_lines)
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
"status": "PASS",
|
|
225
|
+
"file": file_path.replace("\\", "/"),
|
|
226
|
+
"lines_replaced": {
|
|
227
|
+
"start": start_line,
|
|
228
|
+
"end": end_line
|
|
229
|
+
},
|
|
230
|
+
"ast_validation": "SYNTAX_VALID",
|
|
231
|
+
"utf8_nobom_verified": True,
|
|
232
|
+
"clean_pom_verified": True,
|
|
233
|
+
"diff_summary": diff_summary
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
def run_distilled(self, command: str, max_lines: int = 50, timeout_sec: float = 60.0) -> Dict[str, Any]:
|
|
237
|
+
"""Runs command and distills output to head + tail lines, preventing context flood."""
|
|
238
|
+
try:
|
|
239
|
+
import shlex
|
|
240
|
+
if isinstance(command, str):
|
|
241
|
+
if sys.platform == "win32":
|
|
242
|
+
raw_args = shlex.split(command, posix=False)
|
|
243
|
+
cmd_args = []
|
|
244
|
+
for a in raw_args:
|
|
245
|
+
if (a.startswith('"') and a.endswith('"')) or (a.startswith("'") and a.endswith("'")):
|
|
246
|
+
cmd_args.append(a[1:-1])
|
|
247
|
+
else:
|
|
248
|
+
cmd_args.append(a)
|
|
249
|
+
else:
|
|
250
|
+
cmd_args = shlex.split(command)
|
|
251
|
+
else:
|
|
252
|
+
cmd_args = list(command)
|
|
253
|
+
|
|
254
|
+
res = subprocess.run(
|
|
255
|
+
cmd_args,
|
|
256
|
+
cwd=self.repo_root,
|
|
257
|
+
stdout=subprocess.PIPE,
|
|
258
|
+
stderr=subprocess.PIPE,
|
|
259
|
+
text=True,
|
|
260
|
+
encoding="utf-8",
|
|
261
|
+
errors="replace",
|
|
262
|
+
timeout=timeout_sec
|
|
263
|
+
)
|
|
264
|
+
raw_stdout = res.stdout or ""
|
|
265
|
+
raw_stderr = res.stderr or ""
|
|
266
|
+
combined = raw_stdout + ("\n--- STDERR ---\n" + raw_stderr if raw_stderr.strip() else "")
|
|
267
|
+
except subprocess.TimeoutExpired:
|
|
268
|
+
return {
|
|
269
|
+
"status": "TIMEOUT",
|
|
270
|
+
"command": command,
|
|
271
|
+
"exit_code": -1,
|
|
272
|
+
"distilled_output": f"Command timed out after {timeout_sec}s"
|
|
273
|
+
}
|
|
274
|
+
except Exception as e:
|
|
275
|
+
return {
|
|
276
|
+
"status": "ERROR",
|
|
277
|
+
"command": command,
|
|
278
|
+
"exit_code": -1,
|
|
279
|
+
"distilled_output": str(e)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
lines = combined.splitlines()
|
|
283
|
+
total = len(lines)
|
|
284
|
+
if total <= max_lines:
|
|
285
|
+
distilled = "\n".join(lines)
|
|
286
|
+
else:
|
|
287
|
+
head_count = max_lines // 2
|
|
288
|
+
tail_count = max_lines - head_count
|
|
289
|
+
distilled_lines = (
|
|
290
|
+
lines[:head_count]
|
|
291
|
+
+ [f"\n... [TRUNCATED {total - max_lines} NOISE LINES] ...\n"]
|
|
292
|
+
+ lines[-tail_count:]
|
|
293
|
+
)
|
|
294
|
+
distilled = "\n".join(distilled_lines)
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
"status": "PASS" if res.returncode == 0 else "FAIL",
|
|
298
|
+
"command": command,
|
|
299
|
+
"exit_code": res.returncode,
|
|
300
|
+
"total_lines": total,
|
|
301
|
+
"distilled_lines": min(total, max_lines),
|
|
302
|
+
"distilled_output": distilled
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def main():
|
|
307
|
+
parser = argparse.ArgumentParser(description="QA Agent-Computer Interface (SWE-agent ACI)")
|
|
308
|
+
subparsers = parser.add_subparsers(dest="subcommand", help="Subcommand to execute")
|
|
309
|
+
|
|
310
|
+
# view-window
|
|
311
|
+
vw_parser = subparsers.add_parser("view-window", help="View a file in bounded window")
|
|
312
|
+
vw_parser.add_argument("--file", required=True, help="Target file path")
|
|
313
|
+
vw_parser.add_argument("--start-line", type=int, default=1, help="Start line number (1-indexed)")
|
|
314
|
+
vw_parser.add_argument("--window-size", type=int, default=40, help="Window line size (default 40)")
|
|
315
|
+
vw_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
316
|
+
|
|
317
|
+
# surgical-edit
|
|
318
|
+
se_parser = subparsers.add_parser("surgical-edit", help="Surgically edit file with pre-commit checks")
|
|
319
|
+
se_parser.add_argument("--file", required=True, help="Target file path")
|
|
320
|
+
se_parser.add_argument("--target-chunk", required=True, help="Exact code chunk to replace")
|
|
321
|
+
se_parser.add_argument("--replacement-chunk", required=True, help="New code chunk")
|
|
322
|
+
se_parser.add_argument("--validate-syntax", action="store_true", default=True, help="Check AST syntax before writing")
|
|
323
|
+
se_parser.add_argument("--no-validate-syntax", dest="validate_syntax", action="store_false")
|
|
324
|
+
se_parser.add_argument("--enforce-clean-pom", action="store_true", default=True, help="Enforce Clean POM rules")
|
|
325
|
+
|
|
326
|
+
# run-distilled
|
|
327
|
+
rd_parser = subparsers.add_parser("run-distilled", help="Run shell command with distilled output")
|
|
328
|
+
rd_parser.add_argument("--command", required=True, help="Shell command string")
|
|
329
|
+
rd_parser.add_argument("--max-lines", type=int, default=50, help="Max lines to retain")
|
|
330
|
+
rd_parser.add_argument("--timeout-sec", type=float, default=60.0, help="Timeout in seconds")
|
|
331
|
+
|
|
332
|
+
args = parser.parse_args()
|
|
333
|
+
aci = SweQaAci()
|
|
334
|
+
|
|
335
|
+
if args.subcommand == "view-window":
|
|
336
|
+
res = aci.view_window(args.file, start_line=args.start_line, window_size=args.window_size)
|
|
337
|
+
if args.json:
|
|
338
|
+
print(json.dumps(res, indent=2))
|
|
339
|
+
else:
|
|
340
|
+
if res["status"] == "PASS":
|
|
341
|
+
print(f"--- Viewing {res['file']} (Lines {res['start_line']} - {res['end_line']} of {res['total_lines']}) ---")
|
|
342
|
+
print(res["content"])
|
|
343
|
+
else:
|
|
344
|
+
print(f"[ERROR] {res.get('error')}")
|
|
345
|
+
sys.exit(1)
|
|
346
|
+
|
|
347
|
+
elif args.subcommand == "surgical-edit":
|
|
348
|
+
res = aci.surgical_edit(
|
|
349
|
+
file_path=args.file,
|
|
350
|
+
target_chunk=args.target_chunk,
|
|
351
|
+
replacement_chunk=args.replacement_chunk,
|
|
352
|
+
validate_syntax=args.validate_syntax,
|
|
353
|
+
enforce_clean_pom=args.enforce_clean_pom
|
|
354
|
+
)
|
|
355
|
+
print(json.dumps(res, indent=2))
|
|
356
|
+
sys.exit(0 if res["status"] == "PASS" else 1)
|
|
357
|
+
|
|
358
|
+
elif args.subcommand == "run-distilled":
|
|
359
|
+
res = aci.run_distilled(command=args.command, max_lines=args.max_lines, timeout_sec=args.timeout_sec)
|
|
360
|
+
print(json.dumps(res, indent=2))
|
|
361
|
+
sys.exit(0 if res["status"] == "PASS" else 1)
|
|
362
|
+
|
|
363
|
+
else:
|
|
364
|
+
parser.print_help()
|
|
365
|
+
sys.exit(1)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
if __name__ == "__main__":
|
|
369
|
+
main()
|
|
@@ -19,7 +19,7 @@ import os
|
|
|
19
19
|
import sys
|
|
20
20
|
import json
|
|
21
21
|
import argparse
|
|
22
|
-
from typing import List, Dict, Any,
|
|
22
|
+
from typing import List, Dict, Any, Optional
|
|
23
23
|
|
|
24
24
|
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
25
25
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
@@ -96,7 +96,7 @@ BENCHMARK_CASES = [
|
|
|
96
96
|
class SynapseQABench:
|
|
97
97
|
"""Evaluates agent tool-calling capabilities against standardized QA benchmark cases."""
|
|
98
98
|
|
|
99
|
-
def __init__(self, cases: List[Dict[str, Any]] = None):
|
|
99
|
+
def __init__(self, cases: Optional[List[Dict[str, Any]]] = None):
|
|
100
100
|
self.cases = cases or BENCHMARK_CASES
|
|
101
101
|
|
|
102
102
|
def run_benchmark(self, tool_resolver_fn=None) -> Dict[str, Any]:
|
|
@@ -112,7 +112,7 @@ class SynapseQABench:
|
|
|
112
112
|
details = []
|
|
113
113
|
|
|
114
114
|
for case in self.cases:
|
|
115
|
-
expected_tool = case["expected_tool"]
|
|
115
|
+
expected_tool = str(case["expected_tool"])
|
|
116
116
|
# Baseline keyword resolver simulation
|
|
117
117
|
selected_tool = expected_tool if tool_resolver_fn is None else tool_resolver_fn(case["intent"])
|
|
118
118
|
|
|
@@ -47,9 +47,10 @@ HEADER_HASH_TEMPLATE = """# ====================================================
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def generate_claude_md():
|
|
50
|
+
"""Generates the unified CLAUDE.md file compiling agent rules and standards."""
|
|
50
51
|
return f"""# CLAUDE.md — Synapse-Hub Guidelines for Claude Code
|
|
51
52
|
|
|
52
|
-
> **Author & Creator:** Thế Vị (@TeeVee998) <https://github.com/TeeVee998/Synapse-Hub>
|
|
53
|
+
> **Author & Creator:** Thế Vị (@TeeVee998) <https://github.com/TeeVee998/Synapse-Hub>
|
|
53
54
|
> **License:** MIT License (Mandatory Attribution Preservation)
|
|
54
55
|
|
|
55
56
|
{HEADER_TEMPLATE}
|
|
@@ -62,7 +63,7 @@ In accordance with the MIT License, the original copyright notice crediting **Th
|
|
|
62
63
|
---
|
|
63
64
|
|
|
64
65
|
## ⚡ Quick Commands
|
|
65
|
-
- Verify
|
|
66
|
+
- Verify 10 Quality Gates: `npx synapse-hub verify` (or `python tools/verify_all.py`)
|
|
66
67
|
- Project Health Audit: `npx synapse-hub analyze` (or `python tools/analyze_project.py`)
|
|
67
68
|
- Code Linter & Fake-Pass Sentinel: `npx synapse-hub lint` (or `python tools/infer_linter.py`)
|
|
68
69
|
- Verify Author Attribution: `python tools/author_integrity.py`
|
|
@@ -100,6 +101,7 @@ In accordance with the MIT License, the original copyright notice crediting **Th
|
|
|
100
101
|
|
|
101
102
|
|
|
102
103
|
def generate_cursorrules():
|
|
104
|
+
"""Generates the .cursorrules file compiling agent rules for Cursor AI."""
|
|
103
105
|
return f"""# .cursorrules — Synapse-Hub Guidelines for Cursor AI
|
|
104
106
|
# Author & Creator: Thế Vị (@TeeVee998) <https://github.com/TeeVee998/Synapse-Hub>
|
|
105
107
|
# License: MIT License (Mandatory Attribution Preservation)
|
|
@@ -127,6 +129,7 @@ def generate_cursorrules():
|
|
|
127
129
|
|
|
128
130
|
|
|
129
131
|
def generate_windsurfrules():
|
|
132
|
+
"""Generates the .windsurfrules configuration for Windsurf AI."""
|
|
130
133
|
return f"""# .windsurfrules — Synapse-Hub Guidelines for Windsurf AI
|
|
131
134
|
# Author & Creator: Thế Vị (@TeeVee998) <https://github.com/TeeVee998/Synapse-Hub>
|
|
132
135
|
# License: MIT License (Mandatory Attribution Preservation)
|
|
@@ -153,6 +156,7 @@ def generate_windsurfrules():
|
|
|
153
156
|
|
|
154
157
|
|
|
155
158
|
def generate_cursor_mdc():
|
|
159
|
+
"""Generates the .cursor/rules/synapse-qa.mdc file for Cursor IDE."""
|
|
156
160
|
return f"""---
|
|
157
161
|
description: Synapse-Hub Enterprise QA Automation & Testing Rules
|
|
158
162
|
globs: **/*
|
|
@@ -177,12 +181,13 @@ Attribution notices must be preserved.
|
|
|
177
181
|
|
|
178
182
|
|
|
179
183
|
def generate_copilot_instructions():
|
|
184
|
+
"""Generates the .github/copilot-instructions.md file for GitHub Copilot."""
|
|
180
185
|
return f"""# GitHub Copilot Custom Instructions for Synapse-Hub
|
|
181
186
|
|
|
182
187
|
{HEADER_TEMPLATE}
|
|
183
188
|
|
|
184
|
-
> **Project:** Synapse-Hub Enterprise QA Automation
|
|
185
|
-
> **Author:** Thế Vị (@TeeVee998) — MIT License
|
|
189
|
+
> **Project:** Synapse-Hub Enterprise QA Automation
|
|
190
|
+
> **Author:** Thế Vị (@TeeVee998) — MIT License
|
|
186
191
|
|
|
187
192
|
## Coding Invariants
|
|
188
193
|
- Enforce Clean Page Object Model (POM) with Flat Architecture.
|
|
@@ -210,6 +215,14 @@ TARGET_GENERATORS = [
|
|
|
210
215
|
|
|
211
216
|
|
|
212
217
|
def sync_all(check_only=False):
|
|
218
|
+
"""Synchronizes AGENTS.md rules across all IDE and assistant bridge files.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
check_only: If True, only checks for rule drift without modifying files.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
0 if clean or synchronized, 1 if drift was detected in check_only mode.
|
|
225
|
+
"""
|
|
213
226
|
drift_detected = False
|
|
214
227
|
for rel_path, gen_fn in TARGET_GENERATORS:
|
|
215
228
|
full_path = os.path.join(REPO_ROOT, rel_path)
|
|
@@ -249,6 +262,7 @@ def sync_all(check_only=False):
|
|
|
249
262
|
|
|
250
263
|
|
|
251
264
|
def main():
|
|
265
|
+
"""CLI entrypoint for synchronizing AI agent rules."""
|
|
252
266
|
parser = argparse.ArgumentParser(description="Synchronize AGENTS.md to all AI IDE bridges")
|
|
253
267
|
parser.add_argument("--check", action="store_true", help="Check for drift without modifying files")
|
|
254
268
|
args = parser.parse_args()
|
|
@@ -36,7 +36,7 @@ import csv
|
|
|
36
36
|
import json
|
|
37
37
|
import math
|
|
38
38
|
import argparse
|
|
39
|
-
from typing import Dict, List, Any
|
|
39
|
+
from typing import Dict, List, Any
|
|
40
40
|
|
|
41
41
|
# Enforce UTF-8 stdout encoding for Windows/macOS/Linux console compatibility
|
|
42
42
|
if hasattr(sys.stdout, "reconfigure"):
|
|
@@ -286,7 +286,8 @@ def main():
|
|
|
286
286
|
args = parser.parse_args()
|
|
287
287
|
|
|
288
288
|
if args.sample or not args.jtl or not args.server_metrics:
|
|
289
|
-
import tempfile
|
|
289
|
+
import tempfile
|
|
290
|
+
import time
|
|
290
291
|
temp_dir = tempfile.gettempdir()
|
|
291
292
|
temp_jtl = os.path.join(temp_dir, "sample_corr_jmeter.jtl")
|
|
292
293
|
temp_srv = os.path.join(temp_dir, "sample_corr_server.csv")
|