synapse-hub 2.7.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/.agents/agents/live-explorer.md +2 -0
  2. package/.agents/agents/pom-crafter.md +2 -0
  3. package/.agents/agents/test-runner.md +3 -0
  4. package/.agents/rules/communication-style.md +4 -0
  5. package/.agents/rules/context-engineering.md +1 -0
  6. package/.agents/rules/git-push-protocol.md +17 -0
  7. package/.agents/skills/agentless-qa-repair/examples/three_phase_repair_workflow.md +40 -0
  8. package/.agents/skills/antigravity-test-sentinel/examples/sprint_dod_enforcement.md +47 -0
  9. package/.agents/skills/auth-session-and-cookie-manager/examples/multi_role_auth_matrix.md +70 -0
  10. package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_playwright_pom.ts +52 -0
  11. package/.agents/skills/failure-autopsy-and-healing/examples/closed_loop_healing_walkthrough.md +48 -0
  12. package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
  13. package/.agents/skills/lean-impact-scoreboard/examples/sample_scoreboard.md +1 -1
  14. package/.agents/skills/mipro-prompt-compiler/examples/dspy_assertion_optimizer_case.md +46 -0
  15. package/.agents/skills/persistent-planning-files/SKILL.md +1 -1
  16. package/.agents/skills/qa-aci-interface/examples/surgical_code_window_walkthrough.md +40 -0
  17. package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
  18. package/.agents/skills/review-spec/SKILL.md +1 -1
  19. package/.agents/skills/review-spec/examples/pocock_grilling_decision_rubric.md +40 -0
  20. package/.agents/skills/senior-code-review/SKILL.md +1 -1
  21. package/.agents/skills/skillspector-security-guard/examples/audit_threat_vectors_report.json +35 -0
  22. package/.agents/skills/surgical-coding-principles/SKILL.md +1 -1
  23. package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
  24. package/.agents/skills/tot-exploratory-testing/examples/payment_gateway_tot_tree.json +62 -0
  25. package/.agents/skills/verification-before-completion/SKILL.md +2 -2
  26. package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +2 -2
  27. package/.agents/skills/web-agent-gym-benchmarking/examples/enterprise_tasks_suite.json +38 -0
  28. package/.agents/skills/web-vitals-and-performance-profiler/examples/playwright_cwv_profiler_fixture.ts +61 -0
  29. package/AGENTS.md +6 -4
  30. package/CLAUDE.md +2 -1
  31. package/README.en.md +71 -292
  32. package/README.md +69 -304
  33. package/action.yml +1 -1
  34. package/bin/agent-qa.js +2 -2
  35. package/docs/ARCHITECTURE.en.md +192 -0
  36. package/docs/ARCHITECTURE.md +135 -88
  37. package/docs/CI_CD_INTEGRATION.en.md +96 -0
  38. package/docs/CI_CD_INTEGRATION.md +24 -22
  39. package/docs/CLI_REFERENCE.en.md +174 -0
  40. package/docs/CLI_REFERENCE.md +120 -115
  41. package/docs/CONTRIBUTING.en.md +49 -0
  42. package/docs/CONTRIBUTING.md +26 -68
  43. package/docs/QUALITY_GATES.en.md +115 -0
  44. package/docs/QUALITY_GATES.md +115 -0
  45. package/docs/research/agent_skill_training_analysis.md +1 -1
  46. package/package.json +1 -1
  47. package/scripts/package_dist.py +2 -1
  48. package/skills_manifest.json +6 -6
  49. package/synapse.config.json +1 -1
  50. package/templates/ai_bridges/CLAUDE.md +2 -1
  51. package/templates/ai_bridges/copilot-instructions.md +2 -2
  52. package/tools/__init__.py +18 -1
  53. package/tools/_openapi_parser.py +1 -1
  54. package/tools/agent_canary.py +42 -30
  55. package/tools/agentless_patcher.py +2 -2
  56. package/tools/analyze_project.py +3 -5
  57. package/tools/api_drift_sentinel.py +4 -4
  58. package/tools/api_fuzzer.py +8 -8
  59. package/tools/auto_heal.py +4 -5
  60. package/tools/auto_heal_reflexion.py +10 -2
  61. package/tools/autogen_qa_broker.py +2 -2
  62. package/tools/browser_helpers.js +2 -2
  63. package/tools/browsergym_eval.py +2 -3
  64. package/tools/bug_reporter.py +11 -13
  65. package/tools/bump_version.py +90 -19
  66. package/tools/cache_resolver.py +78 -83
  67. package/tools/check_env.py +9 -1
  68. package/tools/check_version_sync.py +58 -4
  69. package/tools/ci_healing_hook.py +4 -6
  70. package/tools/cli.py +5 -7
  71. package/tools/config_decoupler.py +2 -3
  72. package/tools/contract_validator.py +141 -0
  73. package/tools/core/__init__.py +52 -0
  74. package/tools/count_tokens.py +22 -20
  75. package/tools/dashboard_generator.py +629 -1000
  76. package/tools/db_sandbox.py +87 -62
  77. package/tools/debt_harvester.py +3 -3
  78. package/tools/distill_log.py +3 -3
  79. package/tools/dspy_qa_signatures.py +3 -4
  80. package/tools/export_postman_collection.py +8 -8
  81. package/tools/flaky_detector.py +2 -2
  82. package/tools/fuzz_payloads.py +5 -5
  83. package/tools/generate_allure_report.py +1 -1
  84. package/tools/generate_jmx_plan.py +4 -4
  85. package/tools/gherkin_suite.py +1 -1
  86. package/tools/git_pre_commit_guard.py +2 -2
  87. package/tools/goldfish_review.py +2 -2
  88. package/tools/heal/__init__.py +28 -0
  89. package/tools/heal_pr_automator.py +6 -6
  90. package/tools/heuristic_locator_matcher.py +2 -2
  91. package/tools/infer_linter.py +143 -29
  92. package/tools/interview_story.py +0 -1
  93. package/tools/jtl_analyzer.py +11 -11
  94. package/tools/lint/__init__.py +30 -0
  95. package/tools/lint_ai_docs.py +42 -0
  96. package/tools/lookup_tc.py +1 -1
  97. package/tools/matrix_sharder.py +15 -13
  98. package/tools/mcp/__init__.py +16 -0
  99. package/tools/mcp_excel_server.py +2 -2
  100. package/tools/mcp_server.py +9 -7
  101. package/tools/md_to_xlsx.py +26 -26
  102. package/tools/micro_mutator.py +32 -15
  103. package/tools/network_mock_shield.py +27 -27
  104. package/tools/newman_analyzer.py +2 -3
  105. package/tools/omni_emitter.py +5 -8
  106. package/tools/omni_parser.py +5 -5
  107. package/tools/omni_transpiler.py +5 -5
  108. package/tools/output_formatter.py +19 -0
  109. package/tools/perf/__init__.py +24 -0
  110. package/tools/pinpoint_failure.py +1 -1
  111. package/tools/project_state.py +0 -2
  112. package/tools/qa_event_stream.py +1 -1
  113. package/tools/repo_map.py +8 -8
  114. package/tools/report/__init__.py +32 -0
  115. package/tools/reversible_sandbox.py +3 -4
  116. package/tools/safety_firewall.py +2 -2
  117. package/tools/senior_code_review.py +4 -3
  118. package/tools/setup_project.py +10 -12
  119. package/tools/skill_collision_guard.py +3 -3
  120. package/tools/skill_vault.py +94 -6
  121. package/tools/skillspector.py +10 -8
  122. package/tools/smart_visual_masker.py +4 -6
  123. package/tools/spec_drift_sentinel.py +1 -2
  124. package/tools/swe_qa_aci.py +2 -2
  125. package/tools/synapse_qa_bench.py +3 -3
  126. package/tools/sync_agent_rules.py +18 -3
  127. package/tools/telemetry_correlator.py +3 -2
  128. package/tools/test_impact.py +2 -1
  129. package/tools/test_memory_os.py +15 -5
  130. package/tools/textgrad_mutation_optimizer.py +3 -3
  131. package/tools/tms_sync_bridge.py +5 -5
  132. package/tools/tot_exploratory_tester.py +5 -6
  133. package/tools/traffic_session_converter.py +9 -9
  134. package/tools/universal_ir.py +2 -3
  135. package/tools/update_sentinel.py +6 -9
  136. package/tools/verify_all.py +12 -2
  137. package/tools/visual_diff_canvas.py +4 -4
  138. package/tools/visual_som_helper.py +15 -13
  139. package/tools/watch_mode.py +1 -1
  140. package/tools/wcag_audit.py +22 -1
  141. package/tools/welcome_page.py +5 -6
@@ -24,7 +24,7 @@ import urllib.request
24
24
  import urllib.error
25
25
  import urllib.parse
26
26
  import argparse
27
- from typing import Dict, Any, List, Optional, Tuple
27
+ from typing import Dict, Any, List, Optional
28
28
 
29
29
  # Enforce UTF-8 stdout encoding for Windows/macOS/Linux compatibility
30
30
  if hasattr(sys.stdout, 'reconfigure'):
@@ -578,13 +578,13 @@ class ApiFuzzer:
578
578
  print(f" Verdict : {GREEN}[PASS] API resilient against input fuzzing & chaos.{RESET}")
579
579
  else:
580
580
  print(f" Verdict : {RED}[FAIL] {total_failures} defect alarms detected. Backend fixes required!{RESET}")
581
- print(f"======================================================================\n")
581
+ print("======================================================================\n")
582
582
 
583
583
  # Save Report
584
584
  os.makedirs(REPORTS_DIR, exist_ok=True)
585
585
  report_path = os.path.join(REPORTS_DIR, "api_fuzz_report.json")
586
- with open(report_path, "w", encoding="utf-8") as f:
587
- json.dump(scorecard, f, indent=2)
586
+ with open(report_path, "w", encoding="utf-8") as out_file:
587
+ json.dump(scorecard, out_file, indent=2)
588
588
  print(f"Full telemetry report saved to: {report_path}")
589
589
 
590
590
  return scorecard
@@ -622,7 +622,7 @@ class ApiFuzzer:
622
622
  if not is_protected and method.lower() in ("post", "put", "patch", "delete"):
623
623
  severity = "HIGH" if re.search(r"admin|internal|user", path, re.IGNORECASE) else "MEDIUM"
624
624
  findings.append({
625
- "finding": f"AUTH_NOT_DECLARED: Mutating endpoint declares no security requirement",
625
+ "finding": "AUTH_NOT_DECLARED: Mutating endpoint declares no security requirement",
626
626
  "severity": severity,
627
627
  "target": label,
628
628
  "recommendation": "Declare securitySchemes/security for this operation or document why it is public.",
@@ -678,7 +678,7 @@ class ApiFuzzer:
678
678
  print(f" Verdict : {RED}[FAIL] {blocking} HIGH/CRITICAL pre-flight finding(s).{RESET}")
679
679
  else:
680
680
  print(f" Verdict : {GREEN}[PASS] No HIGH/CRITICAL spec-level finding.{RESET}")
681
- print(f"======================================================================\n")
681
+ print("======================================================================\n")
682
682
  for f in findings:
683
683
  print(f" - [{f['severity']}] {f['target']} - {f['finding']}")
684
684
  print(f" 👉 {f['recommendation']}")
@@ -694,8 +694,8 @@ class ApiFuzzer:
694
694
  "severity_summary": severity_summary,
695
695
  "findings": findings,
696
696
  }
697
- with open(report_path, "w", encoding="utf-8") as f:
698
- json.dump(scorecard, f, indent=2)
697
+ with open(report_path, "w", encoding="utf-8") as out_file:
698
+ json.dump(scorecard, out_file, indent=2)
699
699
  print(f"\nFull telemetry report saved to: {report_path}")
700
700
 
701
701
  return scorecard
@@ -2,7 +2,7 @@
2
2
  """
3
3
  Enterprise Closed-Loop Autonomous Test Healer Engine
4
4
  -----------------------------------------------------
5
- Diagnoses test failures in 0.1s from JUnit XML and terminal logs, queries the
5
+ Diagnoses test failures in 0.1s from JUnit XML and terminal logs, queries the
6
6
  semantic memory cache for proven patches, and orchestrates targeted re-tests.
7
7
  Dispatched automatically by AI Agents when test runs encounter failures.
8
8
  """
@@ -12,7 +12,6 @@ import sys
12
12
  import re
13
13
  import json
14
14
  import argparse
15
- from pathlib import Path
16
15
 
17
16
  # Enforce UTF-8 stdout encoding for Windows console compatibility
18
17
  if hasattr(sys.stdout, 'reconfigure'):
@@ -103,7 +102,7 @@ def diagnose_and_plan_heal(target_dir, junit_xml_path=None):
103
102
  "heuristic_match": None,
104
103
  "recommended_patch": None,
105
104
  "verbal_reflection": None,
106
- "episodic_lessons": [l["lesson_summary"] for l in lessons]
105
+ "episodic_lessons": [les["lesson_summary"] for les in lessons]
107
106
  }
108
107
 
109
108
  # 2b. Reflexion Verbal Diagnosis
@@ -113,8 +112,8 @@ def diagnose_and_plan_heal(target_dir, junit_xml_path=None):
113
112
 
114
113
  if lessons:
115
114
  print(f" {CYAN}🧠 [EPISODIC REFLEXION] Top Historical Lessons for Context:{RESET}")
116
- for idx, l in enumerate(lessons, 1):
117
- print(f" {idx}. [{l['page_context']}]: {l['lesson_summary']}")
115
+ for idx, les in enumerate(lessons, 1):
116
+ print(f" {idx}. [{les['page_context']}]: {les['lesson_summary']}")
118
117
  print(f" {CYAN}💡 [REFLEXION DIAGNOSIS] {verbal_reflection}{RESET}")
119
118
 
120
119
  if cache_solution:
@@ -24,8 +24,8 @@ import os
24
24
  import sys
25
25
  import json
26
26
  import argparse
27
- from dataclasses import dataclass, asdict
28
- from typing import List, Optional, Callable, Dict, Any, Tuple
27
+ from dataclasses import dataclass
28
+ from typing import List, Optional, Callable, Tuple
29
29
 
30
30
  # Enforce UTF-8 stdout encoding for Windows console compatibility
31
31
  if hasattr(sys.stdout, 'reconfigure'):
@@ -67,6 +67,14 @@ class ClosedLoopHealer:
67
67
  return "Reflection: DOM node re-rendered asynchronously after API call. Adjustment: Must wait for loader/backdrop detachment before re-querying element."
68
68
  elif "intercepted" in err_lower or "click" in err_lower:
69
69
  return "Reflection: Click intercepted by fading overlay/spinner animation. Adjustment: Use smart wait for overlay detachment or bypass via safe JS click."
70
+ elif "hydration" in err_lower or "did not match" in err_lower:
71
+ return "Reflection: React/Next.js hydration mismatch caused DOM element to re-render. Adjustment: Ensure DOM is interactive and wait for loadState('domcontentloaded') before query."
72
+ elif "shadow" in err_lower or "shadowroot" in err_lower:
73
+ return "Reflection: Target element encapsulated inside closed Shadow DOM boundary. Adjustment: Pierce Shadow DOM via locator piercing syntax or evaluate via CDP."
74
+ elif "iframe" in err_lower or "frame" in err_lower:
75
+ return "Reflection: Target element resides inside cross-origin iframe container. Adjustment: Switch context to frameLocator('iframe#...') before clicking."
76
+ elif any(auth_err in err_lower for auth_err in ("401", "403", "unauthorized", "forbidden", "token expired")):
77
+ return "Reflection: Session auth token or cookie expired mid-test. Adjustment: Refresh storageState or trigger silent token re-authentication via API fixture."
70
78
  elif "timeout" in err_lower or "nosuchelement" in err_lower:
71
79
  return "Reflection: Static selector did not appear within timeout window. Adjustment: Fallback to Accessibility role/label or dynamic data-testid attribute."
72
80
  elif "assert" in err_lower:
@@ -25,7 +25,7 @@ import json
25
25
  import time
26
26
  import argparse
27
27
  from datetime import datetime, timezone
28
- from typing import Dict, Any, List, Optional, Tuple
28
+ from typing import Dict, Any, List, Optional
29
29
 
30
30
  # Enforce UTF-8 stdout encoding
31
31
  if hasattr(sys.stdout, 'reconfigure'):
@@ -174,7 +174,7 @@ class ActorQaBroker:
174
174
  if isinstance(data, list):
175
175
  test_items = data
176
176
  elif isinstance(data, dict):
177
- test_items = data.get("tests", data.get("test_cases", []))
177
+ test_items = data.get("tests") or data.get("test_cases") or []
178
178
  except Exception as e:
179
179
  sys.stderr.write(f"Notice: Could not parse manifest ({e}), using default mock items.\n")
180
180
 
@@ -13,7 +13,7 @@
13
13
  (function () {
14
14
  'use strict';
15
15
 
16
- if (window.__qa && window.__qa.__version === '2.7.0') {
16
+ if (window.__qa && window.__qa.__version === '3.0.1') {
17
17
  return window.__qa;
18
18
  }
19
19
 
@@ -47,7 +47,7 @@
47
47
  }
48
48
 
49
49
  const qa = {
50
- __version: '2.7.0',
50
+ __version: '3.0.1',
51
51
 
52
52
  /**
53
53
  * 1. Smart Action Batching
@@ -22,9 +22,8 @@ import sys
22
22
  import re
23
23
  import json
24
24
  import time
25
- import ast
26
25
  import argparse
27
- from typing import Dict, Any, List, Optional, Tuple
26
+ from typing import Dict, Any, List, Optional
28
27
 
29
28
  # Enforce UTF-8 stdout encoding
30
29
  if hasattr(sys.stdout, 'reconfigure'):
@@ -35,7 +34,7 @@ if hasattr(sys.stdout, 'reconfigure'):
35
34
  pass
36
35
 
37
36
 
38
- DEFAULT_BENCHMARK_TASKS = {
37
+ DEFAULT_BENCHMARK_TASKS: Dict[str, Any] = {
39
38
  "ecommerce_order": {
40
39
  "name": "E-Commerce Checkout & Order Generation",
41
40
  "steps": [
@@ -9,11 +9,9 @@ reproduction steps, distills stacktraces (0 noise), and attaches evidence.
9
9
  """
10
10
 
11
11
  import os
12
- import re
13
- import sys
14
12
  import json
15
13
  import argparse
16
- from typing import Dict, List, Optional, Any
14
+ from typing import Dict, List, Optional
17
15
  from datetime import datetime
18
16
 
19
17
  try:
@@ -22,8 +20,8 @@ except ImportError:
22
20
  try:
23
21
  from cache_resolver import compute_error_fingerprint
24
22
  except ImportError:
25
- def compute_error_fingerprint(err_type: str, err_msg: str, stack_anchor: str = "") -> str:
26
- raw = f"{(err_type or '').strip().lower()}:{(err_msg or '').strip()[:200]}"
23
+ def compute_error_fingerprint(error_type: str, error_msg: str, stack_anchor: str = "") -> str:
24
+ raw = f"{(error_type or '').strip().lower()}:{(error_msg or '').strip()[:200]}"
27
25
  h = 5381
28
26
  for b in raw.encode("utf-8", errors="ignore"):
29
27
  h = (((h << 5) + h) + b) & 0xFFFFFFFF
@@ -90,13 +88,13 @@ class BugReporter:
90
88
  @staticmethod
91
89
  def distill_stack_trace(raw_stack: str, max_lines: int = 5) -> str:
92
90
  """Filters noisy framework stack trace down to actionable lines."""
93
- lines = [l.strip() for l in raw_stack.splitlines() if l.strip()]
91
+ lines = [line.strip() for line in raw_stack.splitlines() if line.strip()]
94
92
  actionable = []
95
- for l in lines:
93
+ for line in lines:
96
94
  # Filter standard framework internals
97
- if any(internal in l for internal in ["at Module.", "node_modules", "org.gradle", "sun.reflect", "jdk.internal"]):
95
+ if any(internal in line for internal in ["at Module.", "node_modules", "org.gradle", "sun.reflect", "jdk.internal"]):
98
96
  continue
99
- actionable.append(l)
97
+ actionable.append(line)
100
98
  if len(actionable) >= max_lines:
101
99
  break
102
100
  return "\n".join(actionable) if actionable else raw_stack[:300]
@@ -121,7 +119,7 @@ class BugReporter:
121
119
  fingerprint = compute_error_fingerprint(rca["type"], error_message, anchor)
122
120
 
123
121
  steps = steps_executed or [
124
- f"1. Navigate to target application under test",
122
+ "1. Navigate to target application under test",
125
123
  f"2. Execute test workflow for {test_title}",
126
124
  f"3. Trigger operation causing failure: {error_message[:80]}"
127
125
  ]
@@ -144,7 +142,7 @@ class BugReporter:
144
142
  report = [
145
143
  f"h2. [BUG][{rca['severity'].upper()}] {test_id}: {test_title} - {error_message[:90]}",
146
144
  "",
147
- f"* *Issue Type:* Bug",
145
+ "* *Issue Type:* Bug",
148
146
  f"* *Error Fingerprint:* {fingerprint}",
149
147
  f"* *Root Cause Category:* {rca['label']}",
150
148
  f"* *Severity / Priority:* {rca['severity']}",
@@ -160,7 +158,7 @@ class BugReporter:
160
158
  report.extend([
161
159
  "",
162
160
  "h3. 2. Expected Result",
163
- f"Operation completes successfully without errors, assertions pass.",
161
+ "Operation completes successfully without errors, assertions pass.",
164
162
  "",
165
163
  "h3. 3. Actual Result",
166
164
  f"{{color:red}}*Failure:* {error_message}{{color}}",
@@ -199,7 +197,7 @@ class BugReporter:
199
197
  "Application executes workflow smoothly and passes all validations.",
200
198
  "",
201
199
  "## ❌ 3. Actual Result",
202
- f"**Error Encountered:**",
200
+ "**Error Encountered:**",
203
201
  f"```text\n{error_message}\n```",
204
202
  "",
205
203
  "## 🔍 4. Distilled Stack Trace (0-Noise)",
@@ -42,12 +42,32 @@ if hasattr(sys.stdout, 'reconfigure'):
42
42
  REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
43
43
 
44
44
  def parse_semver(v_str: str):
45
+ """Parses a semantic version string into major, minor, patch integers and optional suffix.
46
+
47
+ Args:
48
+ v_str: The version string to parse (e.g. '2.9.0' or '2.9.0-rc1').
49
+
50
+ Returns:
51
+ A tuple of (major, minor, patch, suffix).
52
+
53
+ Raises:
54
+ ValueError: If the string does not follow semantic versioning.
55
+ """
45
56
  m = re.match(r"^(\d+)\.(\d+)\.(\d+)(.*)$", v_str.strip())
46
57
  if not m:
47
58
  raise ValueError(f"Invalid semver: '{v_str}'. Must follow major.minor.patch[-suffix]")
48
59
  return int(m.group(1)), int(m.group(2)), int(m.group(3)), m.group(4)
49
60
 
50
61
  def calculate_next_version(current: str, action: str) -> str:
62
+ """Computes the target version based on an action or explicit semver string.
63
+
64
+ Args:
65
+ current: Current semver version string.
66
+ action: 'patch', 'minor', 'major', or an explicit target version string.
67
+
68
+ Returns:
69
+ The computed next semver version string.
70
+ """
51
71
  major, minor, patch, suffix = parse_semver(current)
52
72
  action = action.lower()
53
73
  if action == "patch":
@@ -62,6 +82,14 @@ def calculate_next_version(current: str, action: str) -> str:
62
82
  return action
63
83
 
64
84
  def get_current_version() -> str:
85
+ """Reads the current canonical version from package.json in the repository root.
86
+
87
+ Returns:
88
+ The version string.
89
+
90
+ Raises:
91
+ ValueError: If package.json is missing or lacks a valid version field.
92
+ """
65
93
  pkg_file = os.path.join(REPO_ROOT, "package.json")
66
94
  with open(pkg_file, "r", encoding="utf-8") as f:
67
95
  data = json.load(f)
@@ -71,6 +99,16 @@ def get_current_version() -> str:
71
99
  return version
72
100
 
73
101
  def update_json_root_version(rel_path: str, new_ver: str, dry_run: bool = False) -> bool:
102
+ """Updates the top-level 'version' property in a JSON manifest file.
103
+
104
+ Args:
105
+ rel_path: Relative path to the target JSON file from repo root.
106
+ new_ver: Target version string.
107
+ dry_run: If True, previews changes without writing to disk.
108
+
109
+ Returns:
110
+ True if the file was modified or would be updated.
111
+ """
74
112
  full_path = os.path.join(REPO_ROOT, rel_path)
75
113
  if not os.path.exists(full_path):
76
114
  print(f" [WARN] File not found: {rel_path}")
@@ -95,6 +133,17 @@ def update_json_root_version(rel_path: str, new_ver: str, dry_run: bool = False)
95
133
  return True
96
134
 
97
135
  def update_file(rel_path: str, pattern: str, replacement: str, dry_run: bool = False) -> bool:
136
+ """Performs regex replacement within a target file to update version strings.
137
+
138
+ Args:
139
+ rel_path: Relative path to the target file from repo root.
140
+ pattern: Regular expression pattern matching existing version occurrences.
141
+ replacement: String replacement containing the new version.
142
+ dry_run: If True, previews changes without writing to disk.
143
+
144
+ Returns:
145
+ True if matches were found and updated.
146
+ """
98
147
  full_path = os.path.join(REPO_ROOT, rel_path)
99
148
  if not os.path.exists(full_path):
100
149
  print(f" [WARN] File not found: {rel_path}")
@@ -117,6 +166,11 @@ def update_file(rel_path: str, pattern: str, replacement: str, dry_run: bool = F
117
166
  return True
118
167
 
119
168
  def run_tests() -> bool:
169
+ """Executes the test suite and 10 DoD Quality Gates before release.
170
+
171
+ Returns:
172
+ True if all tests and gates pass.
173
+ """
120
174
  print("\n🔍 Running verification suite...")
121
175
  cmd = [sys.executable, "-m", "unittest", "discover", "tests", "-p", "test_*.py"]
122
176
  res = subprocess.run(cmd, cwd=REPO_ROOT)
@@ -130,33 +184,40 @@ def run_tests() -> bool:
130
184
  if res_verify.returncode != 0:
131
185
  print("❌ Quality Gates failed! Aborting release.")
132
186
  return False
133
- print("✅ 9 Quality Gates DoD PASSED.")
187
+ print("✅ 10 Quality Gates DoD PASSED.")
134
188
  return True
135
189
 
136
190
  def git_commit_and_tag(version: str, push: bool = False):
191
+ """Creates a conventional Git release commit and tags the repository.
192
+
193
+ Args:
194
+ version: Target version string.
195
+ push: If True, pushes the commit and tag to git remote origin.
196
+ """
137
197
  print(f"\n📦 Creating Git commit and tag v{version}...")
138
- subprocess.run(["git", "add", "-A"], cwd=REPO_ROOT, check=True)
139
- subprocess.run(["git", "commit", "-m", f"chore(release): bump version to v{version}"], cwd=REPO_ROOT, check=True)
140
- subprocess.run(["git", "tag", f"v{version}"], cwd=REPO_ROOT, check=True)
198
+ subprocess.run(["git", "add", "-u"], cwd=REPO_ROOT, check=True, timeout=30.0)
199
+ subprocess.run(["git", "commit", "-m", f"chore(release): bump version to v{version}"], cwd=REPO_ROOT, check=True, timeout=30.0)
200
+ subprocess.run(["git", "tag", f"v{version}"], cwd=REPO_ROOT, check=True, timeout=30.0)
141
201
  print(f"✅ Git commit and tag v{version} created.")
142
202
 
143
203
  if push:
144
- print(f"🚀 Pushing commit and tags to remote origin...")
145
- res = subprocess.run(["git", "branch", "--show-current"], cwd=REPO_ROOT, stdout=subprocess.PIPE, text=True)
204
+ print("🚀 Pushing commit and tags to remote origin...")
205
+ res = subprocess.run(["git", "branch", "--show-current"], cwd=REPO_ROOT, stdout=subprocess.PIPE, text=True, timeout=30.0)
146
206
  cur_branch = res.stdout.strip()
147
207
  if cur_branch == "develop":
148
208
  try:
149
- subprocess.run(["git", "checkout", "main"], cwd=REPO_ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
150
- subprocess.run(["git", "merge", "develop", "--ff-only"], cwd=REPO_ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
151
- subprocess.run(["git", "checkout", "develop"], cwd=REPO_ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
152
- subprocess.run(["git", "push", "origin", "develop", "main", "--tags"], cwd=REPO_ROOT, check=True)
209
+ subprocess.run(["git", "checkout", "main"], cwd=REPO_ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30.0)
210
+ subprocess.run(["git", "merge", "develop", "--ff-only"], cwd=REPO_ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30.0)
211
+ subprocess.run(["git", "checkout", "develop"], cwd=REPO_ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30.0)
212
+ subprocess.run(["git", "push", "origin", "develop", "main", "--tags"], cwd=REPO_ROOT, check=True, timeout=60.0)
153
213
  except Exception:
154
- subprocess.run(["git", "push", "origin", "HEAD", "--tags"], cwd=REPO_ROOT, check=True)
214
+ subprocess.run(["git", "push", "origin", "HEAD", "--tags"], cwd=REPO_ROOT, check=True, timeout=60.0)
155
215
  else:
156
- subprocess.run(["git", "push", "origin", "HEAD", "--tags"], cwd=REPO_ROOT, check=True)
157
- print(f"✅ Remote origin synchronized.")
216
+ subprocess.run(["git", "push", "origin", "HEAD", "--tags"], cwd=REPO_ROOT, check=True, timeout=60.0)
217
+ print("✅ Remote origin synchronized.")
158
218
 
159
219
  def main():
220
+ """CLI entrypoint for bumping Synapse Hub version synchronously across manifests."""
160
221
  parser = argparse.ArgumentParser(description="Atomic Universal Version Bumper for Synapse Hub")
161
222
  parser.add_argument("action", nargs="?", default="patch", help="patch | minor | major | <explicit_version> (default: patch)")
162
223
  parser.add_argument("--dry-run", action="store_true", help="Preview version changes without writing files")
@@ -210,15 +271,25 @@ def main():
210
271
  update_file("tools/mcp_excel_server.py", r'(SERVER_VERSION\s*=\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
211
272
  update_file("tools/mcp_excel_server.py", r'("serverInfo":\s*\{[^}]*"version":\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
212
273
 
213
- # 8. docs/ARCHITECTURE.md
274
+ # 8. docs/ARCHITECTURE.md & docs/ARCHITECTURE.en.md
214
275
  update_file("docs/ARCHITECTURE.md", r'(>\s*\*\*Version:\*\*\s*`)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
276
+ update_file("docs/ARCHITECTURE.en.md", r'(>\s*\*\*Version:\*\*\s*`)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
215
277
 
216
278
  # 9. README.md & README.en.md (Documentation headings and badges)
217
- update_file("README.md", r'(Phiên bản Hub:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
218
- update_file("README.md", r'(Hub Version:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
279
+ update_file("README.md", r'(Phiên bản(?: Hub)?:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
280
+ update_file("README.md", r'((?:Hub )?Version:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
219
281
  update_file("README.md", r'(Intelligence Core v)[^")\]]+', rf"\g<1>{new_ver}", args.dry_run)
220
- update_file("README.en.md", r'(Hub Version:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
282
+ update_file("README.md", r'(rev:\s*v)[0-9]+\.[0-9]+\.[0-9]+', rf"\g<1>{new_ver}", args.dry_run)
283
+ update_file("README.md", r'(Synapse Hub \(Core v)[0-9]+\.[0-9]+\.[0-9]+(\))', rf"\g<1>{new_ver}\g<2>", args.dry_run)
284
+ update_file("README.en.md", r'((?:Hub )?Version:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
221
285
  update_file("README.en.md", r'(Intelligence Core v)[^")\]]+', rf"\g<1>{new_ver}", args.dry_run)
286
+ update_file("README.en.md", r'(rev:\s*v)[0-9]+\.[0-9]+\.[0-9]+', rf"\g<1>{new_ver}", args.dry_run)
287
+ update_file("README.en.md", r'(Synapse Hub \(Core v)[0-9]+\.[0-9]+\.[0-9]+(\))', rf"\g<1>{new_ver}\g<2>", args.dry_run)
288
+
289
+ # 9b. SECURITY.md
290
+ major, minor, _, _ = parse_semver(new_ver)
291
+ current_branch = f"{major}.{minor}.x"
292
+ update_file("SECURITY.md", r'(\|\s*`)[0-9]+\.[0-9]+\.x(`\s*\|\s*✅)', rf"\g<1>{current_branch}\g<2>", args.dry_run)
222
293
 
223
294
  # 10. tools/__init__.py
224
295
  update_file("tools/__init__.py", r'(__version__\s*=\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
@@ -267,8 +338,8 @@ def main():
267
338
  print(f"🎉 VERSION BUMP COMPLETE: v{current_ver} ➔ v{new_ver}")
268
339
  print("======================================================================")
269
340
  print("Next steps:")
270
- print(f" • Publish to NPM : npm publish")
271
- print(f" • Or Git Push : git push origin HEAD --tags")
341
+ print(" • Publish to NPM : npm publish")
342
+ print(" • Or Git Push : git push origin HEAD --tags")
272
343
  print("======================================================================")
273
344
 
274
345
  if __name__ == "__main__":