synapse-hub 2.7.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 (108) 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/skills/lean-impact-scoreboard/SKILL.md +1 -1
  5. package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
  6. package/.agents/skills/review-spec/SKILL.md +1 -1
  7. package/.agents/skills/senior-code-review/SKILL.md +1 -1
  8. package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
  9. package/CLAUDE.md +1 -1
  10. package/README.en.md +70 -291
  11. package/README.md +68 -303
  12. package/docs/ARCHITECTURE.md +48 -2
  13. package/docs/CONTRIBUTING.md +1 -1
  14. package/docs/QUALITY_GATES.md +113 -0
  15. package/package.json +1 -1
  16. package/scripts/package_dist.py +2 -1
  17. package/skills_manifest.json +6 -6
  18. package/synapse.config.json +1 -1
  19. package/templates/ai_bridges/CLAUDE.md +1 -1
  20. package/templates/ai_bridges/copilot-instructions.md +2 -2
  21. package/tools/__init__.py +18 -1
  22. package/tools/_openapi_parser.py +1 -1
  23. package/tools/agent_canary.py +42 -30
  24. package/tools/agentless_patcher.py +2 -2
  25. package/tools/analyze_project.py +3 -5
  26. package/tools/api_drift_sentinel.py +4 -4
  27. package/tools/api_fuzzer.py +8 -8
  28. package/tools/auto_heal.py +4 -5
  29. package/tools/auto_heal_reflexion.py +2 -2
  30. package/tools/autogen_qa_broker.py +2 -2
  31. package/tools/browser_helpers.js +2 -2
  32. package/tools/browsergym_eval.py +2 -3
  33. package/tools/bug_reporter.py +11 -13
  34. package/tools/bump_version.py +75 -5
  35. package/tools/cache_resolver.py +78 -83
  36. package/tools/check_env.py +9 -1
  37. package/tools/check_version_sync.py +40 -3
  38. package/tools/ci_healing_hook.py +4 -6
  39. package/tools/cli.py +5 -7
  40. package/tools/config_decoupler.py +2 -3
  41. package/tools/core/__init__.py +52 -0
  42. package/tools/count_tokens.py +22 -20
  43. package/tools/dashboard_generator.py +627 -998
  44. package/tools/db_sandbox.py +65 -53
  45. package/tools/debt_harvester.py +3 -3
  46. package/tools/distill_log.py +3 -3
  47. package/tools/dspy_qa_signatures.py +3 -4
  48. package/tools/export_postman_collection.py +8 -8
  49. package/tools/flaky_detector.py +2 -2
  50. package/tools/fuzz_payloads.py +5 -5
  51. package/tools/generate_allure_report.py +1 -1
  52. package/tools/generate_jmx_plan.py +4 -4
  53. package/tools/gherkin_suite.py +1 -1
  54. package/tools/git_pre_commit_guard.py +2 -2
  55. package/tools/goldfish_review.py +2 -2
  56. package/tools/heal/__init__.py +28 -0
  57. package/tools/heal_pr_automator.py +6 -6
  58. package/tools/heuristic_locator_matcher.py +2 -2
  59. package/tools/infer_linter.py +8 -8
  60. package/tools/interview_story.py +0 -1
  61. package/tools/jtl_analyzer.py +11 -11
  62. package/tools/lint/__init__.py +30 -0
  63. package/tools/lint_ai_docs.py +42 -0
  64. package/tools/lookup_tc.py +1 -1
  65. package/tools/matrix_sharder.py +15 -13
  66. package/tools/mcp/__init__.py +16 -0
  67. package/tools/mcp_excel_server.py +2 -2
  68. package/tools/mcp_server.py +3 -4
  69. package/tools/md_to_xlsx.py +26 -26
  70. package/tools/micro_mutator.py +11 -12
  71. package/tools/network_mock_shield.py +27 -27
  72. package/tools/newman_analyzer.py +2 -3
  73. package/tools/omni_emitter.py +5 -8
  74. package/tools/omni_parser.py +5 -5
  75. package/tools/omni_transpiler.py +5 -5
  76. package/tools/output_formatter.py +19 -0
  77. package/tools/perf/__init__.py +24 -0
  78. package/tools/pinpoint_failure.py +1 -1
  79. package/tools/project_state.py +0 -2
  80. package/tools/qa_event_stream.py +1 -1
  81. package/tools/repo_map.py +8 -8
  82. package/tools/report/__init__.py +32 -0
  83. package/tools/reversible_sandbox.py +1 -2
  84. package/tools/safety_firewall.py +2 -2
  85. package/tools/senior_code_review.py +2 -2
  86. package/tools/setup_project.py +10 -12
  87. package/tools/skill_collision_guard.py +3 -3
  88. package/tools/skill_vault.py +12 -3
  89. package/tools/skillspector.py +5 -7
  90. package/tools/smart_visual_masker.py +4 -6
  91. package/tools/spec_drift_sentinel.py +1 -2
  92. package/tools/swe_qa_aci.py +2 -2
  93. package/tools/synapse_qa_bench.py +3 -3
  94. package/tools/sync_agent_rules.py +17 -3
  95. package/tools/telemetry_correlator.py +3 -2
  96. package/tools/test_memory_os.py +15 -5
  97. package/tools/textgrad_mutation_optimizer.py +3 -3
  98. package/tools/tms_sync_bridge.py +5 -5
  99. package/tools/tot_exploratory_tester.py +5 -6
  100. package/tools/traffic_session_converter.py +9 -9
  101. package/tools/universal_ir.py +2 -3
  102. package/tools/update_sentinel.py +6 -9
  103. package/tools/verify_all.py +10 -0
  104. package/tools/visual_diff_canvas.py +1 -2
  105. package/tools/visual_som_helper.py +15 -13
  106. package/tools/watch_mode.py +1 -1
  107. package/tools/wcag_audit.py +22 -1
  108. package/tools/welcome_page.py +5 -6
@@ -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,10 +184,16 @@ 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
198
  subprocess.run(["git", "add", "-A"], cwd=REPO_ROOT, check=True)
139
199
  subprocess.run(["git", "commit", "-m", f"chore(release): bump version to v{version}"], cwd=REPO_ROOT, check=True)
@@ -141,7 +201,7 @@ def git_commit_and_tag(version: str, push: bool = False):
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...")
204
+ print("🚀 Pushing commit and tags to remote origin...")
145
205
  res = subprocess.run(["git", "branch", "--show-current"], cwd=REPO_ROOT, stdout=subprocess.PIPE, text=True)
146
206
  cur_branch = res.stdout.strip()
147
207
  if cur_branch == "develop":
@@ -154,9 +214,10 @@ def git_commit_and_tag(version: str, push: bool = False):
154
214
  subprocess.run(["git", "push", "origin", "HEAD", "--tags"], cwd=REPO_ROOT, check=True)
155
215
  else:
156
216
  subprocess.run(["git", "push", "origin", "HEAD", "--tags"], cwd=REPO_ROOT, check=True)
157
- print(f"✅ Remote origin synchronized.")
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")
@@ -217,8 +278,17 @@ def main():
217
278
  update_file("README.md", r'(Phiên bản Hub:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
218
279
  update_file("README.md", r'(Hub Version:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
219
280
  update_file("README.md", r'(Intelligence Core v)[^")\]]+', rf"\g<1>{new_ver}", args.dry_run)
281
+ update_file("README.md", r'(rev:\s*v)[0-9]+\.[0-9]+\.[0-9]+', rf"\g<1>{new_ver}", args.dry_run)
282
+ 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)
220
283
  update_file("README.en.md", r'(Hub Version:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
221
284
  update_file("README.en.md", r'(Intelligence Core v)[^")\]]+', rf"\g<1>{new_ver}", args.dry_run)
285
+ update_file("README.en.md", r'(rev:\s*v)[0-9]+\.[0-9]+\.[0-9]+', rf"\g<1>{new_ver}", args.dry_run)
286
+ 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)
287
+
288
+ # 9b. SECURITY.md
289
+ major, minor, _, _ = parse_semver(new_ver)
290
+ current_branch = f"{major}.{minor}.x"
291
+ update_file("SECURITY.md", r'(\|\s*`)[0-9]+\.[0-9]+\.x(`\s*\|\s*✅)', rf"\g<1>{current_branch}\g<2>", args.dry_run)
222
292
 
223
293
  # 10. tools/__init__.py
224
294
  update_file("tools/__init__.py", r'(__version__\s*=\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
@@ -267,8 +337,8 @@ def main():
267
337
  print(f"🎉 VERSION BUMP COMPLETE: v{current_ver} ➔ v{new_ver}")
268
338
  print("======================================================================")
269
339
  print("Next steps:")
270
- print(f" • Publish to NPM : npm publish")
271
- print(f" • Or Git Push : git push origin HEAD --tags")
340
+ print(" • Publish to NPM : npm publish")
341
+ print(" • Or Git Push : git push origin HEAD --tags")
272
342
  print("======================================================================")
273
343
 
274
344
  if __name__ == "__main__":
@@ -313,7 +313,7 @@ def init_db(seed=True, target_db=None):
313
313
  try:
314
314
  cursor.execute("ALTER TABLE healing_cache ADD COLUMN code_patch TEXT DEFAULT ''")
315
315
  except sqlite3.OperationalError:
316
- column_exists = True
316
+ _ = True
317
317
 
318
318
  # 2. Negative Memory Table (Blacklist of Failed Fix Attempts)
319
319
  cursor.execute("""
@@ -362,30 +362,29 @@ def init_db(seed=True, target_db=None):
362
362
  # Migrate existing solutions from local DB if initializing global DB for the first time
363
363
  if current_db == GLOBAL_DB and os.path.exists(LOCAL_DB):
364
364
  try:
365
- local_conn = get_db(LOCAL_DB)
366
- rows = local_conn.execute("SELECT * FROM healing_cache").fetchall()
367
- for r in rows:
368
- cursor.execute("""
369
- INSERT OR IGNORE INTO healing_cache
370
- (error_signature, module_name, symptom, root_cause, groovy_patch, hit_count)
371
- VALUES (?, ?, ?, ?, ?, ?)
372
- """, (r["error_signature"], r["module_name"], r["symptom"], r["root_cause"], r["groovy_patch"], r["hit_count"]))
373
- conn.commit()
374
- local_conn.close()
365
+ with get_db(LOCAL_DB) as local_conn:
366
+ rows = local_conn.execute("SELECT * FROM healing_cache").fetchall()
367
+ for r in rows:
368
+ cursor.execute("""
369
+ INSERT OR IGNORE INTO healing_cache
370
+ (error_signature, module_name, symptom, root_cause, groovy_patch, hit_count)
371
+ VALUES (?, ?, ?, ?, ?, ?)
372
+ """, (r["error_signature"], r["module_name"], r["symptom"], r["root_cause"], r["groovy_patch"], r["hit_count"]))
373
+ conn.commit()
375
374
  except (sqlite3.Error, OSError) as e:
376
375
  sys.stderr.write(f"Notice: Local cache migration skipped: {e}\n")
377
376
 
378
377
  if seed:
379
378
  for s in SEED_SOLUTIONS:
380
379
  cursor.execute("""
381
- INSERT OR IGNORE INTO healing_cache
380
+ INSERT OR IGNORE INTO healing_cache
382
381
  (error_signature, module_name, symptom, root_cause, groovy_patch)
383
382
  VALUES (?, ?, ?, ?, ?)
384
383
  """, (s["error_signature"], s["module_name"], s["symptom"], s["root_cause"], s["groovy_patch"]))
385
384
 
386
385
  for f in SEED_FAILED_FIXES:
387
386
  cursor.execute("""
388
- SELECT COUNT(*) as cnt FROM failed_fixes
387
+ SELECT COUNT(*) as cnt FROM failed_fixes
389
388
  WHERE error_signature = ? AND attempted_patch = ?
390
389
  """, (f["error_signature"], f["attempted_patch"]))
391
390
  if cursor.fetchone()["cnt"] == 0:
@@ -394,17 +393,17 @@ def init_db(seed=True, target_db=None):
394
393
  VALUES (?, ?, ?, ?)
395
394
  """, (f["error_signature"], f["attempted_patch"], f["failure_reason"], f["agent_role"]))
396
395
 
397
- for l in SEED_LESSONS_LEARNED:
396
+ for les in SEED_LESSONS_LEARNED:
398
397
  cursor.execute("""
399
- SELECT COUNT(*) as cnt FROM lessons_learned
398
+ SELECT COUNT(*) as cnt FROM lessons_learned
400
399
  WHERE error_signature = ? AND page_context = ?
401
- """, (l["error_signature"], l["page_context"]))
400
+ """, (les["error_signature"], les["page_context"]))
402
401
  if cursor.fetchone()["cnt"] == 0:
403
402
  cursor.execute("""
404
- INSERT INTO lessons_learned
403
+ INSERT INTO lessons_learned
405
404
  (page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary)
406
405
  VALUES (?, ?, ?, ?, ?, ?)
407
- """, (l["page_context"], l["error_signature"], l["root_cause"], l["failed_attempt"], l["successful_fix"], l["lesson_summary"]))
406
+ """, (les["page_context"], les["error_signature"], les["root_cause"], les["failed_attempt"], les["successful_fix"], les["lesson_summary"]))
408
407
  conn.commit()
409
408
 
410
409
 
@@ -552,7 +551,7 @@ def get_failed_fixes(error_signature):
552
551
  with get_db() as conn:
553
552
  cursor = conn.cursor()
554
553
  cursor.execute("""
555
- SELECT * FROM failed_fixes
554
+ SELECT * FROM failed_fixes
556
555
  WHERE error_signature = ? OR error_signature LIKE ?
557
556
  ORDER BY created_at DESC
558
557
  """, (error_signature, f"%{error_signature}%"))
@@ -587,7 +586,7 @@ def query_solution(error_signature, module_name="GLOBAL", threshold=0.35):
587
586
  cursor = conn.cursor()
588
587
  # 1. Exact match with module
589
588
  cursor.execute("""
590
- SELECT * FROM healing_cache
589
+ SELECT * FROM healing_cache
591
590
  WHERE error_signature = ? AND module_name = ?
592
591
  """, (error_signature, module_name))
593
592
  row = cursor.fetchone()
@@ -596,7 +595,7 @@ def query_solution(error_signature, module_name="GLOBAL", threshold=0.35):
596
595
  # 2. Fallback to GLOBAL match
597
596
  if not row:
598
597
  cursor.execute("""
599
- SELECT * FROM healing_cache
598
+ SELECT * FROM healing_cache
600
599
  WHERE error_signature = ? AND module_name = 'GLOBAL'
601
600
  """, (error_signature,))
602
601
  row = cursor.fetchone()
@@ -605,7 +604,7 @@ def query_solution(error_signature, module_name="GLOBAL", threshold=0.35):
605
604
  # 3. Fuzzy search on symptom/error
606
605
  if not row:
607
606
  cursor.execute("""
608
- SELECT * FROM healing_cache
607
+ SELECT * FROM healing_cache
609
608
  WHERE error_signature LIKE ? OR symptom LIKE ?
610
609
  """, (f"%{error_signature}%", f"%{error_signature}%"))
611
610
  row = cursor.fetchone()
@@ -639,8 +638,8 @@ def query_solution(error_signature, module_name="GLOBAL", threshold=0.35):
639
638
 
640
639
  if row:
641
640
  cursor.execute("""
642
- UPDATE healing_cache
643
- SET hit_count = hit_count + 1
641
+ UPDATE healing_cache
642
+ SET hit_count = hit_count + 1
644
643
  WHERE id = ?
645
644
  """, (row["id"],))
646
645
  conn.commit()
@@ -700,57 +699,53 @@ def sync_db():
700
699
  init_db(seed=True, target_db=GLOBAL_DB)
701
700
  DB_FILE = resolve_db_path(use_local=True)
702
701
  init_db(seed=False, target_db=LOCAL_DB)
703
-
704
- g_conn = get_db(GLOBAL_DB)
705
- l_conn = get_db(LOCAL_DB)
706
-
707
- # 1. Sync positive cache
708
- g_rows = g_conn.execute("SELECT * FROM healing_cache").fetchall()
709
- for r in g_rows:
710
- l_conn.execute("""
711
- INSERT INTO healing_cache (error_signature, module_name, symptom, root_cause, groovy_patch, hit_count)
712
- VALUES (?, ?, ?, ?, ?, ?)
713
- ON CONFLICT(error_signature, module_name) DO UPDATE SET
714
- symptom = excluded.symptom,
715
- root_cause = excluded.root_cause,
716
- groovy_patch = excluded.groovy_patch
717
- """, (r["error_signature"], r["module_name"], r["symptom"], r["root_cause"], r["groovy_patch"], r["hit_count"]))
718
- l_conn.commit()
719
-
720
- l_rows = l_conn.execute("SELECT * FROM healing_cache").fetchall()
721
- for r in l_rows:
722
- g_conn.execute("""
723
- INSERT INTO healing_cache (error_signature, module_name, symptom, root_cause, groovy_patch, hit_count)
724
- VALUES (?, ?, ?, ?, ?, ?)
725
- ON CONFLICT(error_signature, module_name) DO UPDATE SET
726
- symptom = excluded.symptom,
727
- root_cause = excluded.root_cause,
728
- groovy_patch = excluded.groovy_patch
729
- """, (r["error_signature"], r["module_name"], r["symptom"], r["root_cause"], r["groovy_patch"], r["hit_count"]))
730
- g_conn.commit()
731
-
732
- # 2. Sync negative failed fixes
733
- g_failed = g_conn.execute("SELECT * FROM failed_fixes").fetchall()
734
- for f in g_failed:
735
- l_conn.execute("""
736
- INSERT OR IGNORE INTO failed_fixes (error_signature, attempted_patch, failure_reason, agent_role)
737
- VALUES (?, ?, ?, ?)
738
- """, (f["error_signature"], f["attempted_patch"], f["failure_reason"], f["agent_role"]))
739
- l_conn.commit()
740
-
741
- # 3. Sync lessons learned
742
- g_lessons = g_conn.execute("SELECT * FROM lessons_learned").fetchall()
743
- for r in g_lessons:
744
- l_conn.execute("""
745
- INSERT OR IGNORE INTO lessons_learned
746
- (page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary, hit_count)
747
- VALUES (?, ?, ?, ?, ?, ?, ?)
748
- """, (r["page_context"], r["error_signature"], r["root_cause"], r["failed_attempt"], r["successful_fix"], r["lesson_summary"], r["hit_count"]))
749
- l_conn.commit()
750
702
 
751
- l_conn.close()
752
- g_conn.close()
753
- return len(g_rows)
703
+ with get_db(GLOBAL_DB) as g_conn, get_db(LOCAL_DB) as l_conn:
704
+ # 1. Sync positive cache
705
+ g_rows = g_conn.execute("SELECT * FROM healing_cache").fetchall()
706
+ for r in g_rows:
707
+ l_conn.execute("""
708
+ INSERT INTO healing_cache (error_signature, module_name, symptom, root_cause, groovy_patch, hit_count)
709
+ VALUES (?, ?, ?, ?, ?, ?)
710
+ ON CONFLICT(error_signature, module_name) DO UPDATE SET
711
+ symptom = excluded.symptom,
712
+ root_cause = excluded.root_cause,
713
+ groovy_patch = excluded.groovy_patch
714
+ """, (r["error_signature"], r["module_name"], r["symptom"], r["root_cause"], r["groovy_patch"], r["hit_count"]))
715
+ l_conn.commit()
716
+
717
+ l_rows = l_conn.execute("SELECT * FROM healing_cache").fetchall()
718
+ for r in l_rows:
719
+ g_conn.execute("""
720
+ INSERT INTO healing_cache (error_signature, module_name, symptom, root_cause, groovy_patch, hit_count)
721
+ VALUES (?, ?, ?, ?, ?, ?)
722
+ ON CONFLICT(error_signature, module_name) DO UPDATE SET
723
+ symptom = excluded.symptom,
724
+ root_cause = excluded.root_cause,
725
+ groovy_patch = excluded.groovy_patch
726
+ """, (r["error_signature"], r["module_name"], r["symptom"], r["root_cause"], r["groovy_patch"], r["hit_count"]))
727
+ g_conn.commit()
728
+
729
+ # 2. Sync negative failed fixes
730
+ g_failed = g_conn.execute("SELECT * FROM failed_fixes").fetchall()
731
+ for f in g_failed:
732
+ l_conn.execute("""
733
+ INSERT OR IGNORE INTO failed_fixes (error_signature, attempted_patch, failure_reason, agent_role)
734
+ VALUES (?, ?, ?, ?)
735
+ """, (f["error_signature"], f["attempted_patch"], f["failure_reason"], f["agent_role"]))
736
+ l_conn.commit()
737
+
738
+ # 3. Sync lessons learned
739
+ g_lessons = g_conn.execute("SELECT * FROM lessons_learned").fetchall()
740
+ for r in g_lessons:
741
+ l_conn.execute("""
742
+ INSERT OR IGNORE INTO lessons_learned
743
+ (page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary, hit_count)
744
+ VALUES (?, ?, ?, ?, ?, ?, ?)
745
+ """, (r["page_context"], r["error_signature"], r["root_cause"], r["failed_attempt"], r["successful_fix"], r["lesson_summary"], r["hit_count"]))
746
+ l_conn.commit()
747
+
748
+ return len(g_rows)
754
749
 
755
750
 
756
751
  @retry_on_locked()
@@ -769,7 +764,7 @@ def record_lesson(page_context, error_signature, root_cause, failed_attempt, suc
769
764
  with get_db() as conn:
770
765
  cursor = conn.cursor()
771
766
  cursor.execute("""
772
- INSERT INTO lessons_learned
767
+ INSERT INTO lessons_learned
773
768
  (page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary)
774
769
  VALUES (?, ?, ?, ?, ?, ?)
775
770
  """, (page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary))
@@ -847,7 +842,7 @@ def get_flaky_telemetry(days=30, min_runs=2, threshold=0.15, target_db=None):
847
842
  with get_db(current_db) as conn:
848
843
  cursor = conn.cursor()
849
844
  cursor.execute("""
850
- SELECT
845
+ SELECT
851
846
  test_id,
852
847
  COUNT(*) as total_runs,
853
848
  SUM(CASE WHEN status = 'PASS' THEN 1 ELSE 0 END) as pass_count,
@@ -1051,7 +1046,7 @@ def main():
1051
1046
  if sol:
1052
1047
  print("==================================================")
1053
1048
  if sol.get("groovy_patch"):
1054
- print(f"✅ [CACHE HIT - 0 TOKENS] Found pre-tested solution!")
1049
+ print("✅ [CACHE HIT - 0 TOKENS] Found pre-tested solution!")
1055
1050
  print(f"Database Scope : {'LOCAL' if args.local else 'GLOBAL CENTRAL (~/.agent_memory)'}")
1056
1051
  print(f"Match Strategy : {sol.get('match_type', 'EXACT')}")
1057
1052
  print(f"Error Signature : {sol['error_signature']}")
@@ -1104,12 +1099,12 @@ def main():
1104
1099
  if not lessons:
1105
1100
  print(" (Zero relevant lessons in cache yet. Proceeding with clean reasoning.)")
1106
1101
  else:
1107
- for idx, l in enumerate(lessons, 1):
1108
- print(f"📌 [Lesson {idx}] Context: {l['page_context']} | Trigger: {l['error_signature']}")
1109
- print(f" 💡 Rule: {l['lesson_summary']}")
1110
- if l.get("failed_attempt") or l.get("successful_fix"):
1111
- print(f" ⚠️ Anti-Pattern: {l.get('failed_attempt') or 'N/A'}")
1112
- print(f" ✅ Proven Fix : {l.get('successful_fix') or 'N/A'}")
1102
+ for idx, les in enumerate(lessons, 1):
1103
+ print(f"📌 [Lesson {idx}] Context: {les['page_context']} | Trigger: {les['error_signature']}")
1104
+ print(f" 💡 Rule: {les['lesson_summary']}")
1105
+ if les.get("failed_attempt") or les.get("successful_fix"):
1106
+ print(f" ⚠️ Anti-Pattern: {les.get('failed_attempt') or 'N/A'}")
1107
+ print(f" ✅ Proven Fix : {les.get('successful_fix') or 'N/A'}")
1113
1108
  print("==================================================")
1114
1109
 
1115
1110
  elif args.command == "record-telemetry":
@@ -12,7 +12,6 @@ Autonomously detects Target Project or QA Hub (CLI, CWD, ENV).
12
12
 
13
13
  import os
14
14
  import sys
15
- import shutil
16
15
  import shlex
17
16
  import platform
18
17
  import subprocess
@@ -101,12 +100,14 @@ class DiagnosticRunner:
101
100
  return False, str(e)
102
101
 
103
102
  def check_os(self):
103
+ """Verifies operating system platform, kernel version, and architecture."""
104
104
  os_name = platform.system()
105
105
  os_ver = platform.release()
106
106
  arch = platform.machine()
107
107
  self.log_check("Operating System", "PASS", f"{os_name} {os_ver} ({arch})")
108
108
 
109
109
  def check_chrome(self):
110
+ """Checks for Google Chrome, Chromium, Brave, or Playwright browser runtimes."""
110
111
  # 1. Search via 'where' (Windows) or 'which' (Unix)
111
112
  cmd = "where chrome" if platform.system() == "Windows" else "which google-chrome google-chrome-stable chromium chromium-browser"
112
113
  found, out = self.run_command(cmd)
@@ -149,6 +150,7 @@ class DiagnosticRunner:
149
150
  "Download and install Google Chrome from https://www.google.com/chrome/")
150
151
 
151
152
  def check_java(self):
153
+ """Verifies presence of Java JRE/JDK runtime via CLI or JAVA_HOME."""
152
154
  found, out = self.run_command("java -version")
153
155
  if found or "version" in out:
154
156
  ver = out.splitlines()[0] if out else "Installed"
@@ -162,6 +164,7 @@ class DiagnosticRunner:
162
164
  "Katalon includes embedded JRE, but for Gradle/Jenkins CLI, install OpenJDK 17.")
163
165
 
164
166
  def check_katalon(self):
167
+ """Verifies Katalon Studio / Katalon Runtime Engine installation if applicable."""
165
168
  # Context-aware: check if current target is actually a Katalon project
166
169
  is_katalon_project = (
167
170
  any(self.target_dir.glob("*.prj"))
@@ -196,6 +199,7 @@ class DiagnosticRunner:
196
199
  self.log_check("Katalon Studio IDE", "PASS", "Not installed (Optional - not required for Playwright/Cypress/Pytest)")
197
200
 
198
201
  def check_node_allure(self):
202
+ """Verifies presence of Node.js runtime for test runners and Allure reports."""
199
203
  found_node, node_ver = self.run_command("node -v")
200
204
  if found_node:
201
205
  self.log_check("Node.js Runtime", "PASS", f"Node.js {node_ver}")
@@ -204,6 +208,7 @@ class DiagnosticRunner:
204
208
  "Install Node.js from https://nodejs.org/ to execute './tools/generate_allure_report.sh serve'")
205
209
 
206
210
  def check_python_runner(self):
211
+ """Verifies active Python environment and optional virtual environment presence."""
207
212
  py_ver = sys.version.split()[0]
208
213
  venv_path = self.target_dir / ".venv"
209
214
 
@@ -213,6 +218,7 @@ class DiagnosticRunner:
213
218
  self.log_check("Python Virtual Environment", "PASS", f"Python {py_ver} (Optional: create .venv if using auxiliary runners)")
214
219
 
215
220
  def check_project_structure(self):
221
+ """Audits folder layout and configuration integrity for Hub or Spoke projects."""
216
222
  # 1. QA Hub Repository Check
217
223
  is_hub = (self.target_dir / "skills_manifest.json").exists() or (self.target_dir / ".agents" / "skills").is_dir()
218
224
  if is_hub:
@@ -301,6 +307,7 @@ class DiagnosticRunner:
301
307
  "Run 'agent-qa setup .' to initialize Clean POM / Playwright configuration for this project.")
302
308
 
303
309
  def check_network(self):
310
+ """Audits AUT network reachability via BASE_URL configured in environment."""
304
311
  env_file = self.target_dir / ".env"
305
312
  if env_file.exists():
306
313
  try:
@@ -345,6 +352,7 @@ class DiagnosticRunner:
345
352
  "Check internet connection or enable VPN if corporate network required.")
346
353
 
347
354
  def run_all(self):
355
+ """Runs the complete diagnostic suite and returns 0 if all prerequisites pass."""
348
356
  print(f"\n{BOLD}{'='*70}{RESET}")
349
357
  print(f"{BOLD}{CYAN} ENTERPRISE AUTOMATION - ENVIRONMENT & ONBOARDING DIAGNOSTIC{RESET}")
350
358
  print(f"{BOLD}{'='*70}{RESET}")
@@ -111,6 +111,33 @@ def extract_versions(repo_root: str) -> Dict[str, str]:
111
111
  if m:
112
112
  versions["docs/ARCHITECTURE.md"] = m.group(1)
113
113
 
114
+ # 11. README.md
115
+ readme_path = os.path.join(repo_root, "README.md")
116
+ if os.path.exists(readme_path):
117
+ with open(readme_path, "r", encoding="utf-8") as f:
118
+ content = f.read()
119
+ m = re.search(r'rev:\s*v([0-9]+\.[0-9]+\.[0-9]+)', content)
120
+ if m:
121
+ versions["README.md"] = m.group(1)
122
+
123
+ # 12. README.en.md
124
+ readme_en_path = os.path.join(repo_root, "README.en.md")
125
+ if os.path.exists(readme_en_path):
126
+ with open(readme_en_path, "r", encoding="utf-8") as f:
127
+ content = f.read()
128
+ m = re.search(r'rev:\s*v([0-9]+\.[0-9]+\.[0-9]+)', content)
129
+ if m:
130
+ versions["README.en.md"] = m.group(1)
131
+
132
+ # 13. SECURITY.md
133
+ sec_path = os.path.join(repo_root, "SECURITY.md")
134
+ if os.path.exists(sec_path):
135
+ with open(sec_path, "r", encoding="utf-8") as f:
136
+ content = f.read()
137
+ m = re.search(r'\|\s*`([0-9]+\.[0-9]+\.x)`\s*\|\s*✅', content)
138
+ if m:
139
+ versions["SECURITY.md"] = m.group(1)
140
+
114
141
  return versions
115
142
 
116
143
 
@@ -127,7 +154,15 @@ def check_sync(repo_root: str, expected_version: Optional[str] = None) -> Tuple[
127
154
  if not target_ver:
128
155
  target_ver = next(iter(versions.values()))
129
156
 
130
- mismatches = {path: ver for path, ver in versions.items() if ver != target_ver}
157
+ target_prefix = ".".join(target_ver.split(".")[:2]) + ".x"
158
+ mismatches = {}
159
+ for path, ver in versions.items():
160
+ if path == "SECURITY.md":
161
+ if ver != target_prefix:
162
+ mismatches[path] = ver
163
+ elif ver != target_ver:
164
+ mismatches[path] = ver
165
+
131
166
  if mismatches:
132
167
  return False, versions, target_ver
133
168
  return True, versions, target_ver
@@ -151,10 +186,12 @@ def main() -> int:
151
186
  print(f"Target Version: v{canonical}")
152
187
  print("----------------------------------------------------------------------")
153
188
 
189
+ target_prefix = ".".join(canonical.split(".")[:2]) + ".x"
154
190
  all_matched = True
155
191
  for file_path, ver in sorted(versions.items()):
156
- status = "[MATCH]" if ver == canonical else "[MISMATCH]"
157
- if ver != canonical:
192
+ is_match = (ver == target_prefix) if file_path == "SECURITY.md" else (ver == canonical)
193
+ status = "[MATCH]" if is_match else "[MISMATCH]"
194
+ if not is_match:
158
195
  all_matched = False
159
196
  print(f" {status:<11} {file_path:<26} -> {ver}")
160
197