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
@@ -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}")
@@ -102,7 +102,7 @@ def extract_versions(repo_root: str) -> Dict[str, str]:
102
102
  if m:
103
103
  versions["tools/mcp_excel_server.py"] = m.group(1)
104
104
 
105
- # 10. docs/ARCHITECTURE.md
105
+ # 10. docs/ARCHITECTURE.md & docs/ARCHITECTURE.en.md
106
106
  arch_path = os.path.join(repo_root, "docs", "ARCHITECTURE.md")
107
107
  if os.path.exists(arch_path):
108
108
  with open(arch_path, "r", encoding="utf-8") as f:
@@ -110,6 +110,50 @@ def extract_versions(repo_root: str) -> Dict[str, str]:
110
110
  m = re.search(r'>\s*\*\*Version:\*\*\s*`([^`]+)`', content)
111
111
  if m:
112
112
  versions["docs/ARCHITECTURE.md"] = m.group(1)
113
+ arch_en_path = os.path.join(repo_root, "docs", "ARCHITECTURE.en.md")
114
+ if os.path.exists(arch_en_path):
115
+ with open(arch_en_path, "r", encoding="utf-8") as f:
116
+ content = f.read()
117
+ m = re.search(r'>\s*\*\*Version:\*\*\s*`([^`]+)`', content)
118
+ if m:
119
+ versions["docs/ARCHITECTURE.en.md"] = m.group(1)
120
+
121
+ # 11. README.md
122
+ readme_path = os.path.join(repo_root, "README.md")
123
+ if os.path.exists(readme_path):
124
+ with open(readme_path, "r", encoding="utf-8") as f:
125
+ content = f.read()
126
+ m_badge = re.search(r'Phiên bản(?: Hub)?:\*\*\s*`v([0-9]+\.[0-9]+\.[0-9]+)', content)
127
+ m_rev = re.search(r'rev:\s*v([0-9]+\.[0-9]+\.[0-9]+)', content)
128
+ if m_badge:
129
+ versions["README.md"] = m_badge.group(1)
130
+ elif m_rev:
131
+ versions["README.md"] = m_rev.group(1)
132
+ if m_badge and m_rev:
133
+ versions["README.md (rev)"] = m_rev.group(1)
134
+
135
+ # 12. README.en.md
136
+ readme_en_path = os.path.join(repo_root, "README.en.md")
137
+ if os.path.exists(readme_en_path):
138
+ with open(readme_en_path, "r", encoding="utf-8") as f:
139
+ content = f.read()
140
+ m_badge = re.search(r'(?:Hub )?Version:\*\*\s*`v([0-9]+\.[0-9]+\.[0-9]+)', content)
141
+ m_rev = re.search(r'rev:\s*v([0-9]+\.[0-9]+\.[0-9]+)', content)
142
+ if m_badge:
143
+ versions["README.en.md"] = m_badge.group(1)
144
+ elif m_rev:
145
+ versions["README.en.md"] = m_rev.group(1)
146
+ if m_badge and m_rev:
147
+ versions["README.en.md (rev)"] = m_rev.group(1)
148
+
149
+ # 13. SECURITY.md
150
+ sec_path = os.path.join(repo_root, "SECURITY.md")
151
+ if os.path.exists(sec_path):
152
+ with open(sec_path, "r", encoding="utf-8") as f:
153
+ content = f.read()
154
+ m = re.search(r'\|\s*`([0-9]+\.[0-9]+\.x)`\s*\|\s*✅', content)
155
+ if m:
156
+ versions["SECURITY.md"] = m.group(1)
113
157
 
114
158
  return versions
115
159
 
@@ -127,7 +171,15 @@ def check_sync(repo_root: str, expected_version: Optional[str] = None) -> Tuple[
127
171
  if not target_ver:
128
172
  target_ver = next(iter(versions.values()))
129
173
 
130
- mismatches = {path: ver for path, ver in versions.items() if ver != target_ver}
174
+ target_prefix = ".".join(target_ver.split(".")[:2]) + ".x"
175
+ mismatches = {}
176
+ for path, ver in versions.items():
177
+ if path == "SECURITY.md":
178
+ if ver != target_prefix:
179
+ mismatches[path] = ver
180
+ elif ver != target_ver:
181
+ mismatches[path] = ver
182
+
131
183
  if mismatches:
132
184
  return False, versions, target_ver
133
185
  return True, versions, target_ver
@@ -151,10 +203,12 @@ def main() -> int:
151
203
  print(f"Target Version: v{canonical}")
152
204
  print("----------------------------------------------------------------------")
153
205
 
206
+ target_prefix = ".".join(canonical.split(".")[:2]) + ".x"
154
207
  all_matched = True
155
208
  for file_path, ver in sorted(versions.items()):
156
- status = "[MATCH]" if ver == canonical else "[MISMATCH]"
157
- if ver != canonical:
209
+ is_match = (ver == target_prefix) if file_path == "SECURITY.md" else (ver == canonical)
210
+ status = "[MATCH]" if is_match else "[MISMATCH]"
211
+ if not is_match:
158
212
  all_matched = False
159
213
  print(f" {status:<11} {file_path:<26} -> {ver}")
160
214
 
@@ -10,10 +10,8 @@ When a test fails due to a broken locator:
10
10
  """
11
11
 
12
12
  import os
13
- import sys
14
- import json
15
13
  import argparse
16
- from typing import Dict, Any, Optional
14
+ from typing import Dict, Any
17
15
  from datetime import datetime
18
16
 
19
17
 
@@ -35,11 +33,11 @@ class CIHealingHook:
35
33
  """
36
34
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
37
35
  branch_name = f"auto-heal/{test_id.lower()}_{timestamp}"
38
-
36
+
39
37
  pr_description = f"""## 🤖 Autonomous CI/CD Self-Healing Report
40
38
 
41
- > **Target File:** `{file_path}`
42
- > **Confidence Score:** `{confidence * 100:.1f}%`
39
+ > **Target File:** `{file_path}`
40
+ > **Confidence Score:** `{confidence * 100:.1f}%`
43
41
  > **Timestamp:** `{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}`
44
42
 
45
43
  ### 📝 Summary of Changes
package/tools/cli.py CHANGED
@@ -41,7 +41,7 @@ RESET = "\033[0m"
41
41
  def cmd_version(args):
42
42
  """Displays the current hub version, skill catalog count, and license information."""
43
43
  manifest_path = os.path.join(REPO_ROOT, "skills_manifest.json")
44
- version = "2.7.0"
44
+ version = "3.0.1"
45
45
  if os.path.exists(manifest_path):
46
46
  try:
47
47
  import json
@@ -145,7 +145,7 @@ def cmd_clean(args):
145
145
  os.remove(os.path.join(root, f))
146
146
  removed_count += 1
147
147
  except OSError as err:
148
- failed_files = getattr(args, "verbose", False) and print(f"Notice: Could not remove {f}: {err}")
148
+ getattr(args, "verbose", False) and print(f"Notice: Could not remove {f}: {err}")
149
149
  print(f"✅ Cleanup finished: {removed_count} build artifacts and caches removed.")
150
150
 
151
151
 
@@ -255,7 +255,6 @@ def cmd_transpile(args):
255
255
  def cmd_omni_scan(args):
256
256
  from omni_transpiler import scan_repository
257
257
  inv = scan_repository(args.path)
258
- import json
259
258
  if getattr(args, "json", False):
260
259
  print(json.dumps(inv, indent=2))
261
260
  else:
@@ -542,21 +541,20 @@ def cmd_release(args):
542
541
 
543
542
  def _hub_version():
544
543
  manifest_path = os.path.join(REPO_ROOT, "skills_manifest.json")
545
- version = "2.7.0"
544
+ version = "3.0.1"
546
545
  if os.path.exists(manifest_path):
547
546
  try:
548
547
  import json
549
548
  with open(manifest_path, "r", encoding="utf-8") as f:
550
549
  version = json.load(f).get("version", version)
551
550
  except (OSError, json.JSONDecodeError):
552
- version = "2.7.0"
551
+ version = "3.0.1"
553
552
  return version
554
553
 
555
554
 
556
555
  class SynapseArgumentParser(argparse.ArgumentParser):
557
556
  """Enhanced ArgumentParser providing fuzzy Did-You-Mean suggestions on command typos."""
558
557
  def error(self, message):
559
- import difflib
560
558
  m = re.search(r"invalid choice: '([^']+)'", message)
561
559
  if m:
562
560
  bad_choice = m.group(1)
@@ -1008,7 +1006,7 @@ def main():
1008
1006
  import update_sentinel
1009
1007
  update_sentinel.check_for_updates(force=False, notify=True)
1010
1008
  except (ImportError, OSError, TimeoutError):
1011
- update_status = None
1009
+ _ = False
1012
1010
 
1013
1011
  # Dispatch subcommand
1014
1012
  args.func(args)
@@ -15,8 +15,7 @@ import re
15
15
  import sys
16
16
  import json
17
17
  import argparse
18
- from pathlib import Path
19
- from typing import Dict, List, Any, Tuple
18
+ from typing import Dict, List, Any, Tuple, Optional
20
19
 
21
20
  # Enforce UTF-8 stdout encoding for Windows console compatibility
22
21
  if hasattr(sys.stdout, 'reconfigure'):
@@ -129,7 +128,7 @@ class ConfigDecoupler:
129
128
  "replacement": f"process.env.{key}"
130
129
  })
131
130
 
132
- def export_configs(self, output_dir: str = None) -> Tuple[str, str]:
131
+ def export_configs(self, output_dir: Optional[str] = None) -> Tuple[str, str]:
133
132
  """Generates env.default.json and .env.example configuration files."""
134
133
  out_base = output_dir or os.path.join(self.target_dir, "config", "profiles")
135
134
  os.makedirs(out_base, exist_ok=True)