synapse-hub 2.6.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/.agents/agents/failure-analyst.md +4 -0
  2. package/.agents/agents/live-explorer.md +2 -0
  3. package/.agents/agents/orchestrator.md +16 -0
  4. package/.agents/agents/pom-crafter.md +10 -0
  5. package/.agents/agents/quality-gatekeeper.md +5 -0
  6. package/.agents/agents/test-runner.md +8 -0
  7. package/.agents/rules/git-push-protocol.md +1 -1
  8. package/.agents/rules/graphify.md +2 -0
  9. package/.agents/rules/mandatory-playwright-live.md +9 -9
  10. package/.agents/rules/mobile_appium_rules.md +8 -1
  11. package/.agents/skills/actor-qa-swarm/SKILL.md +77 -0
  12. package/.agents/skills/actor-qa-swarm/examples/broker_sharding_sample.json +12 -0
  13. package/.agents/skills/agentless-qa-repair/SKILL.md +69 -0
  14. package/.agents/skills/agentless-qa-repair/examples/healing_report_sample.json +17 -0
  15. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +1 -1
  16. package/.agents/skills/clean-pom-crafter/SKILL.md +2 -2
  17. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +4 -4
  18. package/.agents/skills/event-stream-qa-orchestrator/SKILL.md +79 -0
  19. package/.agents/skills/event-stream-qa-orchestrator/examples/timeline_sample.jsonl +2 -0
  20. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +2 -2
  21. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +6 -21
  22. package/.agents/skills/katalon-cli-runner/SKILL.md +1 -1
  23. package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
  24. package/.agents/skills/mipro-prompt-compiler/SKILL.md +59 -0
  25. package/.agents/skills/mipro-prompt-compiler/examples/compiled_module_sample.json +22 -0
  26. package/.agents/skills/qa-aci-interface/SKILL.md +67 -0
  27. package/.agents/skills/qa-aci-interface/examples/surgical_edit_sample.json +12 -0
  28. package/.agents/skills/qa-memory-os/SKILL.md +80 -0
  29. package/.agents/skills/qa-memory-os/examples/archival_memory_sample.json +21 -0
  30. package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
  31. package/.agents/skills/review-spec/SKILL.md +60 -0
  32. package/.agents/skills/review-spec/examples/sample_spec_review.md +33 -0
  33. package/.agents/skills/senior-code-review/SKILL.md +1 -1
  34. package/.agents/skills/synthetic-test-data-generator/SKILL.md +2 -2
  35. package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
  36. package/.agents/skills/tot-exploratory-testing/SKILL.md +59 -0
  37. package/.agents/skills/tot-exploratory-testing/examples/tot_graph_sample.json +35 -0
  38. package/.agents/skills/wcag-audit-patterns/SKILL.md +2 -2
  39. package/.agents/skills/web-agent-gym-benchmarking/SKILL.md +62 -0
  40. package/.agents/skills/web-agent-gym-benchmarking/examples/benchmark_report_sample.json +22 -0
  41. package/CLAUDE.md +2 -2
  42. package/README.en.md +70 -291
  43. package/README.md +68 -303
  44. package/docs/ARCHITECTURE.md +54 -8
  45. package/docs/CONTRIBUTING.md +1 -1
  46. package/docs/QUALITY_GATES.md +113 -0
  47. package/docs/research/EXTERNAL_AGENT_SKILLS_SURVEY.md +1062 -0
  48. package/package.json +1 -1
  49. package/scripts/package_dist.py +2 -1
  50. package/skills_manifest.json +92 -11
  51. package/synapse.config.json +1 -1
  52. package/templates/ai_bridges/CLAUDE.md +2 -2
  53. package/templates/ai_bridges/copilot-instructions.md +2 -2
  54. package/tools/TOOLS_RUNBOOK.md +1 -1
  55. package/tools/__init__.py +18 -1
  56. package/tools/_openapi_parser.py +158 -0
  57. package/tools/agent_canary.py +42 -30
  58. package/tools/agentless_patcher.py +417 -0
  59. package/tools/analyze_project.py +12 -11
  60. package/tools/api_drift_sentinel.py +4 -4
  61. package/tools/api_fuzzer.py +8 -8
  62. package/tools/auto_heal.py +23 -5
  63. package/tools/auto_heal_reflexion.py +2 -2
  64. package/tools/autogen_qa_broker.py +318 -0
  65. package/tools/browser_helpers.js +2 -2
  66. package/tools/browsergym_eval.py +236 -0
  67. package/tools/bug_reporter.py +11 -13
  68. package/tools/bump_version.py +75 -5
  69. package/tools/cache_resolver.py +78 -83
  70. package/tools/check_env.py +9 -1
  71. package/tools/check_version_sync.py +40 -3
  72. package/tools/ci_healing_hook.py +5 -7
  73. package/tools/cli.py +5 -7
  74. package/tools/config_decoupler.py +2 -3
  75. package/tools/core/__init__.py +52 -0
  76. package/tools/count_tokens.py +22 -20
  77. package/tools/dashboard_generator.py +628 -999
  78. package/tools/db_sandbox.py +69 -53
  79. package/tools/debt_harvester.py +3 -3
  80. package/tools/distill_log.py +3 -3
  81. package/tools/dspy_qa_signatures.py +3 -4
  82. package/tools/export_postman_collection.py +8 -8
  83. package/tools/flaky_detector.py +2 -2
  84. package/tools/fuzz_payloads.py +5 -5
  85. package/tools/generate_allure_report.py +1 -1
  86. package/tools/generate_jmx_plan.py +4 -4
  87. package/tools/gherkin_suite.py +1 -1
  88. package/tools/git_pre_commit_guard.py +2 -2
  89. package/tools/goldfish_review.py +2 -2
  90. package/tools/heal/__init__.py +28 -0
  91. package/tools/heal_pr_automator.py +29 -4
  92. package/tools/heuristic_locator_matcher.py +2 -2
  93. package/tools/infer_linter.py +8 -8
  94. package/tools/interview_story.py +0 -1
  95. package/tools/jtl_analyzer.py +11 -11
  96. package/tools/lint/__init__.py +30 -0
  97. package/tools/lint_ai_docs.py +42 -0
  98. package/tools/lookup_tc.py +1 -1
  99. package/tools/matrix_sharder.py +15 -13
  100. package/tools/mcp/__init__.py +16 -0
  101. package/tools/mcp_excel_server.py +2 -2
  102. package/tools/mcp_server.py +3 -4
  103. package/tools/md_to_xlsx.py +26 -26
  104. package/tools/micro_mutator.py +11 -12
  105. package/tools/mipro_assertion_tuner.py +207 -0
  106. package/tools/network_mock_shield.py +27 -27
  107. package/tools/newman_analyzer.py +2 -3
  108. package/tools/omni_emitter.py +5 -8
  109. package/tools/omni_parser.py +5 -5
  110. package/tools/omni_transpiler.py +5 -5
  111. package/tools/output_formatter.py +19 -0
  112. package/tools/perf/__init__.py +24 -0
  113. package/tools/pinpoint_failure.py +13 -1
  114. package/tools/project_state.py +4 -2
  115. package/tools/qa_event_stream.py +336 -0
  116. package/tools/repo_map.py +8 -8
  117. package/tools/report/__init__.py +32 -0
  118. package/tools/reversible_sandbox.py +5 -2
  119. package/tools/safety_firewall.py +2 -2
  120. package/tools/senior_code_review.py +2 -2
  121. package/tools/setup_project.py +10 -12
  122. package/tools/skill_collision_guard.py +3 -3
  123. package/tools/skill_vault.py +12 -3
  124. package/tools/skillspector.py +5 -7
  125. package/tools/smart_visual_masker.py +4 -6
  126. package/tools/spec_drift_sentinel.py +1 -2
  127. package/tools/swe_qa_aci.py +369 -0
  128. package/tools/synapse_qa_bench.py +3 -3
  129. package/tools/sync_agent_rules.py +18 -4
  130. package/tools/telemetry_correlator.py +3 -2
  131. package/tools/test_memory_os.py +419 -0
  132. package/tools/textgrad_mutation_optimizer.py +3 -3
  133. package/tools/tms_sync_bridge.py +5 -5
  134. package/tools/tot_exploratory_tester.py +239 -0
  135. package/tools/traffic_session_converter.py +9 -9
  136. package/tools/universal_ir.py +2 -3
  137. package/tools/update_sentinel.py +6 -9
  138. package/tools/verify_all.py +10 -0
  139. package/tools/visual_diff_canvas.py +1 -2
  140. package/tools/visual_som_helper.py +15 -13
  141. package/tools/watch_mode.py +1 -1
  142. package/tools/wcag_audit.py +22 -1
  143. package/tools/welcome_page.py +5 -6
@@ -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, Dict, Any, Tuple
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, Tuple
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 9 Quality Gates: `npx synapse-hub verify` (or `python tools/verify_all.py`)
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, Optional, Tuple
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, time
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")