synapse-hub 2.2.4 → 2.4.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 (312) hide show
  1. package/.agents/agents/failure-analyst.md +11 -0
  2. package/.agents/agents/live-explorer.md +28 -0
  3. package/.agents/agents/orchestrator.md +23 -4
  4. package/.agents/agents/pom-crafter.md +15 -0
  5. package/.agents/agents/quality-gatekeeper.md +20 -4
  6. package/.agents/agents/test-runner.md +12 -0
  7. package/.agents/rules/communication-style.md +40 -0
  8. package/.agents/rules/context-engineering.md +23 -0
  9. package/.agents/rules/git-push-protocol.md +10 -16
  10. package/.agents/rules/graphify.md +14 -14
  11. package/.agents/rules/mandatory-playwright-live.md +29 -29
  12. package/.agents/rules/mobile_appium_rules.md +132 -0
  13. package/.agents/rules/performance_testing_rules.md +74 -0
  14. package/.agents/rules/systematic-debugging.md +6 -6
  15. package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +64 -215
  16. package/.agents/skills/agent-orchestration-multi-agent-optimize/examples/swarm_workload_config.json +12 -0
  17. package/.agents/skills/antigravity-test-sentinel/SKILL.md +3 -3
  18. package/.agents/skills/antigravity-test-sentinel/examples/dod_gate_policy.json +11 -0
  19. package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
  20. package/.agents/skills/api-network-mocking-interception/examples/network_route_stub.json +9 -0
  21. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +9 -9
  22. package/.agents/skills/api-spec-to-e2e-matrix/examples/sample_openapi.json +19 -0
  23. package/.agents/skills/architecture-decision-records/SKILL.md +35 -412
  24. package/.agents/skills/architecture-decision-records/examples/0001-record-architecture-decisions.md +12 -0
  25. package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +1 -1
  26. package/.agents/skills/auth-session-and-cookie-manager/examples/storage_state_sample.json +11 -0
  27. package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +80 -186
  28. package/.agents/skills/backend-load-and-stress-patterns/examples/k6_load_test_sample.js +13 -0
  29. package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
  30. package/.agents/skills/bats-testing-patterns/examples/cli_runner_test.bats +6 -0
  31. package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -614
  32. package/.agents/skills/clean-pom-crafter/SKILL.md +21 -3
  33. package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -28
  34. package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -138
  35. package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +2 -2
  36. package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_page_sample.groovy +9 -0
  37. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +5 -5
  38. package/.agents/skills/db-state-verifier-and-seeder/examples/db_fixture_seed.json +12 -0
  39. package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
  40. package/.agents/skills/e2e-testing-patterns/examples/playwright_sharded_config.ts +12 -0
  41. package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -522
  42. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +17 -3
  43. package/.agents/skills/failure-autopsy-and-healing/examples/sample_junit_failure.xml +8 -0
  44. package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -64
  45. package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -56
  46. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -98
  47. package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
  48. package/.agents/skills/file-and-report-validator/examples/sample_export_schema.json +14 -0
  49. package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +3 -3
  50. package/.agents/skills/flaky-test-sentinel-and-quarantine/examples/quarantine_registry.json +11 -0
  51. package/.agents/skills/gherkin-to-suite/SKILL.md +103 -0
  52. package/.agents/skills/gherkin-to-suite/examples/checkout.feature +39 -0
  53. package/.agents/skills/gherkin-to-suite/examples/suite_report_sample.json +334 -0
  54. package/.agents/skills/github-actions-templates/SKILL.md +47 -304
  55. package/.agents/skills/github-actions-templates/examples/matrix_test_pipeline.yml +8 -0
  56. package/.agents/skills/graphify-impact-analyzer/SKILL.md +6 -6
  57. package/.agents/skills/graphify-impact-analyzer/examples/dependency_blast_radius.json +8 -0
  58. package/.agents/skills/katalon-cli-runner/SKILL.md +2 -2
  59. package/.agents/skills/katalon-cli-runner/examples/headless_kre_exec.sh +3 -0
  60. package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -209
  61. package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -155
  62. package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -67
  63. package/.agents/skills/lam-script-generator/SKILL.md +42 -1
  64. package/.agents/skills/lam-script-generator/examples/user_story_sample.md +8 -0
  65. package/.agents/skills/lean-code-ladder/SKILL.md +83 -0
  66. package/.agents/skills/lean-code-ladder/examples/ladder_walkthrough.md +37 -0
  67. package/.agents/skills/meta-prompt-crafter/SKILL.md +107 -0
  68. package/.agents/skills/meta-prompt-crafter/examples/qa_prompt_template.md +35 -0
  69. package/.agents/skills/persistent-planning-files/SKILL.md +108 -0
  70. package/.agents/skills/persistent-planning-files/examples/task_plan_example.md +56 -0
  71. package/.agents/skills/playwright-live-interaction/SKILL.md +49 -1
  72. package/.agents/skills/playwright-live-interaction/examples/live_probe_script.js +5 -0
  73. package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -160
  74. package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -75
  75. package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -388
  76. package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -134
  77. package/.agents/skills/playwright-webapp-testing/SKILL.md +93 -0
  78. package/.agents/skills/playwright-webapp-testing/examples/server_lifecycle_playwright.py +63 -0
  79. package/.agents/skills/rbt-manual-testing/SKILL.md +150 -0
  80. package/.agents/skills/rbt-manual-testing/examples/checkout_rbt_manual_suite.md +25 -0
  81. package/.agents/skills/security-dast-sentinel/SKILL.md +54 -135
  82. package/.agents/skills/security-dast-sentinel/examples/dast_scan_config.json +10 -0
  83. package/.agents/skills/self-healing-locator/SKILL.md +24 -10
  84. package/.agents/skills/self-healing-locator/examples/aom_diff_sample.json +16 -0
  85. package/.agents/skills/skillspector-security-guard/SKILL.md +32 -90
  86. package/.agents/skills/skillspector-security-guard/examples/safe_mcp_config.json +12 -0
  87. package/.agents/skills/surgical-coding-principles/SKILL.md +89 -0
  88. package/.agents/skills/surgical-coding-principles/examples/surgical_vs_scope_creep.md +46 -0
  89. package/.agents/skills/synthetic-test-data-generator/SKILL.md +3 -3
  90. package/.agents/skills/synthetic-test-data-generator/examples/idempotent_data_recipe.json +9 -0
  91. package/.agents/skills/systematic-debugging/SKILL.md +100 -0
  92. package/.agents/skills/systematic-debugging/examples/four_phase_case.md +44 -0
  93. package/.agents/skills/test-driven-development/SKILL.md +114 -0
  94. package/.agents/skills/test-driven-development/examples/red_green_refactor_qa.md +78 -0
  95. package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
  96. package/.agents/skills/token-and-context-optimizer/examples/distilled_log_sample.txt +3 -0
  97. package/.agents/skills/verification-before-completion/SKILL.md +100 -0
  98. package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +46 -0
  99. package/.agents/skills/visual-regression-sentinel/SKILL.md +21 -1
  100. package/.agents/skills/visual-regression-sentinel/examples/visual_mask_rules.json +9 -0
  101. package/.agents/skills/wcag-audit-patterns/SKILL.md +3 -3
  102. package/.agents/skills/wcag-audit-patterns/examples/wcag_compliance_checklist.json +10 -0
  103. package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -541
  104. package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
  105. package/.agents/skills/web-vitals-and-performance-profiler/examples/cwv_budget_policy.json +6 -0
  106. package/.agents/templates/completion_report.md +57 -57
  107. package/.agents/workflows/analyze_requirement_document.md +18 -0
  108. package/.agents/workflows/export_testcases_excel.md +32 -0
  109. package/.agents/workflows/generate_combinatorial_test_data.md +28 -0
  110. package/.agents/workflows/generate_cross_module_test_plan.md +37 -0
  111. package/.agents/workflows/generate_manual_testcases_rbt.md +56 -0
  112. package/.agents/workflows/generate_requirements_from_website.md +25 -0
  113. package/.agents/workflows/generate_testcases_from_requirements.md +21 -0
  114. package/.agents/workflows/graphify.md +10 -10
  115. package/.agents/workflows/verify.md +2 -2
  116. package/.cursor/rules/language.mdc +7 -7
  117. package/.cursor/rules/synapse-qa.mdc +23 -0
  118. package/.cursorrules +7 -0
  119. package/.pre-commit-hooks.yaml +14 -0
  120. package/.windsurfrules +7 -0
  121. package/AGENTS.md +54 -15
  122. package/CLAUDE.md +35 -12
  123. package/LICENSE +30 -0
  124. package/NOTICE +180 -0
  125. package/README.en.md +331 -0
  126. package/README.md +252 -515
  127. package/action.yml +51 -0
  128. package/benchmarks/qa_eval/README.md +71 -0
  129. package/benchmarks/qa_eval/build_dataset.py +677 -0
  130. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +22 -0
  131. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +22 -0
  132. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +22 -0
  133. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +22 -0
  134. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +22 -0
  135. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +21 -0
  136. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +21 -0
  137. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +21 -0
  138. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +21 -0
  139. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +21 -0
  140. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +23 -0
  141. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +23 -0
  142. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +23 -0
  143. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +23 -0
  144. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +23 -0
  145. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +22 -0
  146. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +22 -0
  147. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +22 -0
  148. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +22 -0
  149. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +23 -0
  150. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +22 -0
  151. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +22 -0
  152. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +22 -0
  153. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +22 -0
  154. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +22 -0
  155. package/benchmarks/qa_eval/eval_runner.py +418 -0
  156. package/benchmarks/qa_eval/reports/AB_METHODOLOGY_REPORT.md +83 -0
  157. package/benchmarks/qa_eval/reports/BENCHMARK_REPORT.md +69 -0
  158. package/benchmarks/qa_eval/reports/eval_results.json +105 -0
  159. package/bin/agent-qa.js +16 -4
  160. package/docs/ARCHITECTURE.md +107 -0
  161. package/docs/CI_CD_INTEGRATION.md +94 -0
  162. package/docs/CLI_REFERENCE.md +157 -0
  163. package/docs/CONTRIBUTING.md +91 -0
  164. package/docs/PLUGIN_DEVELOPMENT_GUIDE.md +126 -0
  165. package/docs/TOKEN_QUOTA_PLAYBOOK.md +41 -0
  166. package/docs/adopted-ecosystem-tools.md +87 -0
  167. package/docs/schemas/synapse.config.schema.json +64 -0
  168. package/examples/cypress-ecommerce-suite/README.md +45 -0
  169. package/examples/cypress-ecommerce-suite/cypress/e2e/checkout.cy.js +17 -0
  170. package/examples/cypress-ecommerce-suite/cypress/pages/BasePage.js +29 -0
  171. package/examples/cypress-ecommerce-suite/cypress/pages/CartPage.js +51 -0
  172. package/examples/cypress-ecommerce-suite/cypress.config.js +13 -0
  173. package/examples/cypress-ecommerce-suite/package.json +13 -0
  174. package/examples/katalon-suite/Keywords/pages/BasePage.groovy +19 -0
  175. package/examples/katalon-suite/Keywords/pages/DashboardPage.groovy +29 -0
  176. package/examples/katalon-suite/Keywords/pages/LoginPage.groovy +45 -0
  177. package/examples/katalon-suite/Profiles/default.glbl +12 -0
  178. package/examples/katalon-suite/README.md +18 -0
  179. package/examples/katalon-suite/Scripts/Auth/TC_LoginSuccess/Script1234567890.groovy +12 -0
  180. package/examples/katalon-suite/Scripts/Dashboard/TC_ViewSummary/Script1234567891.groovy +15 -0
  181. package/examples/katalon-suite/katalon_suite.prj +21 -0
  182. package/examples/playwright-crm-suite/README.md +40 -0
  183. package/examples/playwright-crm-suite/package.json +14 -0
  184. package/examples/playwright-crm-suite/pages/BasePage.ts +37 -0
  185. package/examples/playwright-crm-suite/pages/CustomerPage.ts +57 -0
  186. package/examples/playwright-crm-suite/pages/LoginPage.ts +32 -0
  187. package/examples/playwright-crm-suite/playwright.config.ts +27 -0
  188. package/examples/playwright-crm-suite/tests/auth.spec.ts +22 -0
  189. package/examples/playwright-crm-suite/tests/customers.spec.ts +33 -0
  190. package/examples/plugins/sample-custom-plugin/README.md +14 -0
  191. package/examples/plugins/sample-custom-plugin/plugin.json +16 -0
  192. package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/SKILL.md +29 -0
  193. package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/examples/audit_fixture.json +8 -0
  194. package/examples/plugins/sample-custom-plugin/tools/custom_security_headers.py +125 -0
  195. package/examples/practices/README.md +22 -0
  196. package/examples/practices/requirements/crm/requirements_contracts.md +292 -0
  197. package/examples/practices/requirements/crm/requirements_customers.md +462 -0
  198. package/examples/practices/requirements/crm/requirements_login.md +202 -0
  199. package/examples/practices/requirements/crm/requirements_projects.md +396 -0
  200. package/examples/practices/requirements/crm/requirements_tasks.md +343 -0
  201. package/examples/practices/requirements/ecommerce/requirements_category_brand.txt +402 -0
  202. package/examples/practices/requirements/jira/KAN-5/KAN-1/2026-04-03_04-28-27.png +0 -0
  203. package/examples/practices/requirements/jira/KAN-5/KAN-1/KAN-1_requirement.md +52 -0
  204. package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-19.png +0 -0
  205. package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-57.png +0 -0
  206. package/examples/practices/requirements/jira/KAN-5/KAN-4/KAN-4_requirement.md +119 -0
  207. package/examples/practices/requirements/jira/KAN-5/KAN-5_overview.md +9 -0
  208. package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-08.png +0 -0
  209. package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-23.png +0 -0
  210. package/examples/practices/requirements/jira/KAN-5/KAN-6/KAN-6_requirement.md +32 -0
  211. package/examples/practices/testcases/api/book_management_api_test_cases.md +144 -0
  212. package/examples/practices/testcases/api/user_management_api_test_cases.md +139 -0
  213. package/examples/practices/testcases/crm/test_cases_crm_login.md +148 -0
  214. package/examples/practices/testcases/crm/test_cases_customers.md +156 -0
  215. package/examples/pytest-api-suite/README.md +45 -0
  216. package/examples/pytest-api-suite/conftest.py +31 -0
  217. package/examples/pytest-api-suite/pages/user_api_client.py +36 -0
  218. package/examples/pytest-api-suite/requirements.txt +2 -0
  219. package/examples/pytest-api-suite/tests/test_user_api.py +44 -0
  220. package/examples/selenium-suite/README.md +18 -0
  221. package/examples/selenium-suite/pages/base_page.py +27 -0
  222. package/examples/selenium-suite/pages/login_page.py +29 -0
  223. package/examples/selenium-suite/pages/order_page.py +25 -0
  224. package/examples/selenium-suite/pytest.ini +6 -0
  225. package/examples/selenium-suite/requirements.txt +2 -0
  226. package/examples/selenium-suite/tests/test_login.py +10 -0
  227. package/examples/selenium-suite/tests/test_order.py +10 -0
  228. package/package.json +27 -12
  229. package/scripts/generate_skill_examples.py +244 -0
  230. package/scripts/install.ps1 +112 -0
  231. package/scripts/install_global_plugin.ps1 +94 -0
  232. package/scripts/install_global_plugin.sh +49 -0
  233. package/scripts/lint_skills.ps1 +57 -0
  234. package/scripts/package_dist.py +323 -0
  235. package/scripts/setup.sh +59 -0
  236. package/scripts/update_hub.ps1 +19 -0
  237. package/scripts/update_hub.sh +15 -0
  238. package/skills_manifest.json +209 -6
  239. package/synapse.config.json +30 -0
  240. package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +7 -7
  241. package/templates/PROJECT_STATE_TEMPLATE.md +1 -1
  242. package/templates/ai_bridges/CLAUDE.md +33 -16
  243. package/templates/ai_bridges/copilot-instructions.md +14 -21
  244. package/templates/ai_bridges/cursor_synapse_qa.mdc +17 -20
  245. package/templates/ai_bridges/windsurfrules +21 -21
  246. package/templates/ci/github-actions-synapse.yml +71 -4
  247. package/templates/welcome.html.tmpl +1674 -0
  248. package/tools/.infer_baseline.json +1102 -0
  249. package/tools/TOOLS_RUNBOOK.md +98 -131
  250. package/tools/__init__.py +26 -0
  251. package/tools/agent_canary.py +501 -501
  252. package/tools/analyze_project.py +31 -17
  253. package/tools/api_fuzzer.py +278 -6
  254. package/tools/author_integrity.py +6 -6
  255. package/tools/auto_heal.py +1 -1
  256. package/tools/browser_helpers.js +105 -7
  257. package/tools/bug_reporter.py +31 -1
  258. package/tools/bump_version.py +30 -3
  259. package/tools/cache_resolver.py +248 -14
  260. package/tools/check_env.py +55 -14
  261. package/tools/check_version_sync.py +171 -0
  262. package/tools/ci_healing_hook.py +1 -1
  263. package/tools/cli.py +454 -42
  264. package/tools/count_tokens.py +1257 -0
  265. package/tools/dashboard_generator.py +2638 -0
  266. package/tools/db_sandbox.py +198 -20
  267. package/tools/e2e_flow_prober.ps1 +12 -64
  268. package/tools/e2e_flow_prober.py +99 -0
  269. package/tools/e2e_flow_prober.sh +4 -0
  270. package/tools/flaky_detector.py +4 -3
  271. package/tools/generate_allure_report.ps1 +12 -29
  272. package/tools/generate_allure_report.py +77 -0
  273. package/tools/generate_allure_report.sh +4 -0
  274. package/tools/generate_jmx_plan.py +257 -58
  275. package/tools/generate_test_reports.ps1 +12 -134
  276. package/tools/generate_test_reports.py +133 -0
  277. package/tools/generate_test_reports.sh +4 -0
  278. package/tools/gherkin_suite.py +758 -0
  279. package/tools/git_pre_commit_guard.py +12 -6
  280. package/tools/goldfish_review.py +366 -0
  281. package/tools/heal_pr_automator.py +1 -1
  282. package/tools/heuristic_locator_matcher.py +170 -21
  283. package/tools/infer_linter.py +612 -70
  284. package/tools/lint_ai_docs.py +64 -0
  285. package/tools/matrix_sharder.py +1 -1
  286. package/tools/mcp_excel_server.ps1 +12 -250
  287. package/tools/mcp_excel_server.py +249 -0
  288. package/tools/mcp_excel_server.sh +4 -0
  289. package/tools/mcp_server.py +368 -144
  290. package/tools/md_to_xlsx.py +307 -0
  291. package/tools/micro_mutator.py +302 -69
  292. package/tools/omni_transpiler.py +2 -2
  293. package/tools/output_formatter.py +128 -0
  294. package/tools/pinpoint_failure.py +88 -88
  295. package/tools/project_state.py +2 -2
  296. package/tools/repo_map.py +1 -1
  297. package/tools/safety_firewall.py +196 -45
  298. package/tools/setup_project.py +295 -31
  299. package/tools/skillspector.py +31 -0
  300. package/tools/spec_drift_sentinel.py +439 -0
  301. package/tools/sync_agent_rules.py +248 -0
  302. package/tools/telemetry_correlator.py +45 -0
  303. package/tools/test_impact.py +51 -12
  304. package/tools/tms_sync_bridge.py +21 -5
  305. package/tools/universal_ir.py +3 -0
  306. package/tools/update_sentinel.py +87 -15
  307. package/tools/verify_all.ps1 +13 -0
  308. package/tools/verify_all.py +479 -85
  309. package/tools/visual_diff_canvas.py +1 -0
  310. package/tools/visual_som_helper.py +1 -1
  311. package/tools/watch_mode.py +305 -0
  312. package/tools/welcome_page.py +291 -0
@@ -16,6 +16,19 @@ import argparse
16
16
  from typing import Dict, List, Optional, Any
17
17
  from datetime import datetime
18
18
 
19
+ try:
20
+ from tools.cache_resolver import compute_error_fingerprint
21
+ except ImportError:
22
+ try:
23
+ from cache_resolver import compute_error_fingerprint
24
+ 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]}"
27
+ h = 5381
28
+ for b in raw.encode("utf-8", errors="ignore"):
29
+ h = (((h << 5) + h) + b) & 0xFFFFFFFF
30
+ return f"fp_{h:08x}"
31
+
19
32
 
20
33
  class BugReporter:
21
34
  """Enterprise Automated Bug Ticket & RCA Generator."""
@@ -104,6 +117,8 @@ class BugReporter:
104
117
  rca = cls.classify_root_cause(error_message, stack_trace)
105
118
  distilled_stack = cls.distill_stack_trace(stack_trace)
106
119
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
120
+ anchor = distilled_stack.splitlines()[0].strip() if distilled_stack else ""
121
+ fingerprint = compute_error_fingerprint(rca["type"], error_message, anchor)
107
122
 
108
123
  steps = steps_executed or [
109
124
  f"1. Navigate to target application under test",
@@ -111,11 +126,26 @@ class BugReporter:
111
126
  f"3. Trigger operation causing failure: {error_message[:80]}"
112
127
  ]
113
128
 
129
+ if format_type.lower() == "json":
130
+ return json.dumps({
131
+ "test_id": test_id,
132
+ "title": test_title,
133
+ "fingerprint": fingerprint,
134
+ "rca": rca,
135
+ "error_message": error_message,
136
+ "distilled_stack": distilled_stack,
137
+ "screenshot": screenshot_path,
138
+ "steps": steps,
139
+ "environment": environment,
140
+ "timestamp": timestamp
141
+ }, indent=2)
142
+
114
143
  if format_type.lower() == "jira":
115
144
  report = [
116
145
  f"h2. [BUG][{rca['severity'].upper()}] {test_id}: {test_title} - {error_message[:90]}",
117
146
  "",
118
147
  f"* *Issue Type:* Bug",
148
+ f"* *Error Fingerprint:* {fingerprint}",
119
149
  f"* *Root Cause Category:* {rca['label']}",
120
150
  f"* *Severity / Priority:* {rca['severity']}",
121
151
  f"* *Assigned Team:* {rca['suggested_team']}",
@@ -155,7 +185,7 @@ class BugReporter:
155
185
  md = [
156
186
  f"# [BUG][{rca['severity'].upper()}] {test_id}: {test_title}",
157
187
  "",
158
- f"> **Root Cause Analysis:** `{rca['label']}` ",
188
+ f"> **Fingerprint:** `{fingerprint}` | **Root Cause Category:** `{rca['label']}` ",
159
189
  f"> **Severity:** **{rca['severity']}** | **Assigned To:** `{rca['suggested_team']}` | **Detected:** {timestamp}",
160
190
  "",
161
191
  "## 📌 1. Steps to Reproduce",
@@ -65,7 +65,10 @@ def get_current_version() -> str:
65
65
  pkg_file = os.path.join(REPO_ROOT, "package.json")
66
66
  with open(pkg_file, "r", encoding="utf-8") as f:
67
67
  data = json.load(f)
68
- return data.get("version", "2.0.0")
68
+ version = data.get("version")
69
+ if not version:
70
+ raise ValueError("package.json does not contain a valid 'version' field.")
71
+ return version
69
72
 
70
73
  def update_json_root_version(rel_path: str, new_ver: str, dry_run: bool = False) -> bool:
71
74
  full_path = os.path.join(REPO_ROOT, rel_path)
@@ -127,7 +130,7 @@ def run_tests() -> bool:
127
130
  if res_verify.returncode != 0:
128
131
  print("❌ Quality Gates failed! Aborting release.")
129
132
  return False
130
- print("✅ 8 Quality Gates DoD PASSED.")
133
+ print("✅ 9 Quality Gates DoD PASSED.")
131
134
  return True
132
135
 
133
136
  def git_commit_and_tag(version: str, push: bool = False):
@@ -167,12 +170,18 @@ def main():
167
170
  # 1. package.json
168
171
  update_json_root_version("package.json", new_ver, args.dry_run)
169
172
 
173
+ # 1b. plugin.json
174
+ update_json_root_version("plugin.json", new_ver, args.dry_run)
175
+
170
176
  # 2. pyproject.toml
171
177
  update_file("pyproject.toml", r'(version\s*=\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
172
178
 
173
179
  # 3. skills_manifest.json
174
180
  update_json_root_version("skills_manifest.json", new_ver, args.dry_run)
175
181
 
182
+ # 3b. synapse.config.json
183
+ update_json_root_version("synapse.config.json", new_ver, args.dry_run)
184
+
176
185
  # 4. tools/cli.py
177
186
  update_file("tools/cli.py", r'(version\s*=\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
178
187
 
@@ -186,13 +195,31 @@ def main():
186
195
  # 7. tools/mcp_excel_server.ps1
187
196
  update_file("tools/mcp_excel_server.ps1", r'(version\s*=\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
188
197
 
198
+ # 7b. tools/mcp_excel_server.py
199
+ update_file("tools/mcp_excel_server.py", r'("serverInfo":\s*\{[^}]*"version":\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
200
+
189
201
  # 8. docs/ARCHITECTURE.md
190
202
  update_file("docs/ARCHITECTURE.md", r'(>\s*\*\*Version:\*\*\s*`)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
191
203
 
192
- # 9. README.md (Documentation headings and mermaid badges)
204
+ # 9. README.md & README.en.md (Documentation headings and badges)
193
205
  update_file("README.md", r'(Phiên bản Hub:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
194
206
  update_file("README.md", r'(Hub Version:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
195
207
  update_file("README.md", r'(Intelligence Core v)[^")\]]+', rf"\g<1>{new_ver}", args.dry_run)
208
+ update_file("README.en.md", r'(Hub Version:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
209
+ update_file("README.en.md", r'(Intelligence Core v)[^")\]]+', rf"\g<1>{new_ver}", args.dry_run)
210
+
211
+ # 10. tools/__init__.py
212
+ update_file("tools/__init__.py", r'(__version__\s*=\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
213
+
214
+ # 11. tools/welcome_page.py
215
+ update_file("tools/welcome_page.py", r'("version":\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
216
+
217
+ # 12. tools/dashboard_generator.py
218
+ update_file("tools/dashboard_generator.py", r'(Synapse-Hub v)[0-9]+\.[0-9]+\.[0-9]+', rf"\g<1>{new_ver}", args.dry_run)
219
+
220
+ # 13. tools/update_sentinel.py
221
+ update_file("tools/update_sentinel.py", r'(\.get\("version",\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
222
+ update_file("tools/update_sentinel.py", r'(return\s*")[0-9]+\.[0-9]+\.[0-9]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
196
223
 
197
224
  if args.dry_run:
198
225
  print("\n[INFO] Dry-run completed. No files modified on disk.")
@@ -32,28 +32,127 @@ if hasattr(sys.stdout, 'reconfigure'):
32
32
  except Exception:
33
33
  pass
34
34
 
35
+ def _find_project_root():
36
+ """Traverses up from CWD or script directory to find the target project root."""
37
+ curr = os.path.abspath(os.getcwd())
38
+ while True:
39
+ if any(os.path.exists(os.path.join(curr, m)) for m in ("package.json", "PROJECT_AI_GUIDE.md", ".git", "pyproject.toml")):
40
+ return curr
41
+ parent = os.path.dirname(curr)
42
+ if parent == curr:
43
+ break
44
+ curr = parent
45
+ return os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
46
+
47
+
35
48
  GLOBAL_DIR = os.path.expanduser("~/.agent_memory")
36
49
  GLOBAL_DB = os.path.join(GLOBAL_DIR, "global_cache.db")
37
- LOCAL_DB = os.path.join(os.path.dirname(os.path.abspath(__file__)), "memory_cache.db")
50
+ LOCAL_DIR = os.path.join(_find_project_root(), ".synapse")
51
+ LOCAL_DB = os.path.join(LOCAL_DIR, "cache.db")
38
52
 
39
53
 
40
54
  def resolve_db_path(use_local=False):
41
- """Returns the SQLite database path. Defaults to Global Central Memory."""
42
- if use_local:
43
- return LOCAL_DB
44
- os.makedirs(GLOBAL_DIR, exist_ok=True)
45
- return GLOBAL_DB
55
+ """
56
+ Returns the SQLite database path.
57
+ Defaults to project-local store (.synapse/cache.db) to prevent cross-project NDA/IP leakage.
58
+ Global memory (~/.agent_memory/global_cache.db) is only used when SYNAPSE_GLOBAL_CACHE=1
59
+ or when explicit use_local=False is called with SYNAPSE_GLOBAL_CACHE enabled.
60
+ Zero filesystem mutation on module import: directories are lazy-created upon connection.
61
+ """
62
+ if os.environ.get("SYNAPSE_CACHE_DB"):
63
+ return os.environ.get("SYNAPSE_CACHE_DB")
64
+ if os.environ.get("SYNAPSE_GLOBAL_CACHE", "").lower() in ("1", "true", "yes") and not use_local:
65
+ return GLOBAL_DB
66
+ return LOCAL_DB
46
67
 
47
68
 
48
69
  DB_FILE = resolve_db_path()
49
70
 
71
+ SECRET_PATTERNS = [
72
+ (re.compile(r'(?i)(api[_-]?key|secret|token|password|auth|passwd|credential)[\s:=]+["\']?([a-zA-Z0-9_\-\.]{8,})["\']?'), r'\1="[REDACTED]"'),
73
+ (re.compile(r'-----BEGIN[ A-Z_-]+PRIVATE KEY-----[\s\S]+?-----END[ A-Z_-]+PRIVATE KEY-----'), '[REDACTED_PRIVATE_KEY]'),
74
+ (re.compile(r'https?://([^:]+):([^@]+)@'), r'https://\1:[REDACTED]@'),
75
+ (re.compile(r'Bearer\s+[a-zA-Z0-9_\-\.]{15,}'), 'Bearer [REDACTED_TOKEN]'),
76
+ ]
77
+
78
+
79
+ def sanitize_text(text):
80
+ """Redacts passwords, API tokens, and private keys before persisting to SQLite memory."""
81
+ if not text or not isinstance(text, str):
82
+ return text or ""
83
+ sanitized = text
84
+ for pattern, repl in SECRET_PATTERNS:
85
+ sanitized = pattern.sub(repl, sanitized)
86
+ return sanitized
87
+
88
+
89
+ def djb2_hash(s: str) -> str:
90
+ """Computes a 32-bit unsigned DJB2 hash formatted as an 8-char hex string."""
91
+ h = 5381
92
+ for byte in s.encode('utf-8', errors='ignore'):
93
+ h = (((h << 5) + h) + byte) & 0xFFFFFFFF
94
+ return f"{h:08x}"
95
+
96
+
97
+ def compute_error_fingerprint(error_type: str, error_msg: str, stack_anchor: str = "") -> str:
98
+ """
99
+ Computes a deterministic, normalized fingerprint hash for an error.
100
+ Strips volatile elements (timestamps, hex addresses, line numbers) so semantically
101
+ identical errors across test runs produce the identical fingerprint.
102
+ """
103
+ norm_type = (error_type or "").strip().lower()
104
+ msg = error_msg or ""
105
+ # Strip hex memory addresses
106
+ msg = re.sub(r'0x[0-9a-fA-F]+', '0xADDR', msg)
107
+ # Strip ISO/standard timestamps
108
+ msg = re.sub(r'\b\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\b', 'TIMESTAMP', msg)
109
+ # Strip variable line numbers in filenames like "file.py:123"
110
+ msg = re.sub(r':\d+\b', ':LINE', msg)
111
+ # Collapse whitespace
112
+ msg = re.sub(r'\s+', ' ', msg).strip()[:200]
113
+
114
+ anchor = (stack_anchor or "").strip().lower()
115
+ if anchor:
116
+ anchor = re.sub(r':\d+\b', ':LINE', anchor)
117
+ raw = f"{norm_type}:{msg}@{anchor}"
118
+ else:
119
+ raw = f"{norm_type}:{msg}"
120
+ return f"fp_{djb2_hash(raw)}"
121
+
122
+
123
+ def group_errors_by_fingerprint(error_list: list, repeat_threshold: int = 3) -> dict:
124
+ """
125
+ Groups error events by fingerprint and detects repeated errors (exceeding repeat_threshold).
126
+ """
127
+ grouped = {}
128
+ for item in error_list:
129
+ err_type = item.get("error_type") or item.get("type") or "UnknownError"
130
+ err_msg = item.get("error_message") or item.get("message") or item.get("symptom") or ""
131
+ anchor = item.get("stack_anchor") or item.get("file") or ""
132
+ fp = compute_error_fingerprint(err_type, err_msg, anchor)
133
+ if fp not in grouped:
134
+ grouped[fp] = {
135
+ "fingerprint": fp,
136
+ "count": 0,
137
+ "is_repeated": False,
138
+ "error_type": err_type,
139
+ "sample_message": err_msg,
140
+ "items": []
141
+ }
142
+ grouped[fp]["count"] += 1
143
+ if grouped[fp]["count"] >= repeat_threshold:
144
+ grouped[fp]["is_repeated"] = True
145
+ grouped[fp]["items"].append(item)
146
+ return grouped
147
+
148
+
50
149
  SEED_SOLUTIONS = [
51
150
  {
52
151
  "error_signature": "ElementClickInterceptedException",
53
152
  "module_name": "GLOBAL",
54
153
  "symptom": "Modal backdrop or PrimeNG overlay (.p-component-overlay) blocks button click.",
55
154
  "root_cause": "Angular PrimeNG animation fade-out latency.",
56
- "groovy_patch": "((JavascriptExecutor) driver).executeScript(\"arguments[0].click();\", element);"
155
+ "groovy_patch": "waitForElementNotVisible(By.cssSelector(\".p-component-overlay, .modal-backdrop\"), 5);\nfindElement(by).click();"
57
156
  },
58
157
  {
59
158
  "error_signature": "Save_Button_Disabled",
@@ -67,7 +166,7 @@ SEED_SOLUTIONS = [
67
166
  "module_name": "GLOBAL",
68
167
  "symptom": "Element is not attached to the DOM.",
69
168
  "root_cause": "Angular re-rendered DOM node after async API response.",
70
- "groovy_patch": "for (int i = 0; i < 3; i++) {\n try {\n findElement(by).click();\n break;\n } catch (StaleElementReferenceException e) {\n sleep(500);\n }\n}"
169
+ "groovy_patch": "for (int i = 0; i < 3; i++) {\n try {\n waitForElementVisible(by, 5);\n findElement(by).click();\n break;\n } catch (StaleElementReferenceException e) {\n waitForPageLoaded(5);\n }\n}"
71
170
  },
72
171
  {
73
172
  "error_signature": "Duplicate_Code_Error",
@@ -165,10 +264,22 @@ def retry_on_locked(max_retries=5, initial_delay=0.05, backoff=2.0):
165
264
  return decorator
166
265
 
167
266
 
267
+ class ManagedConnection(sqlite3.Connection):
268
+ """Resilient SQLite connection that automatically commits/rollbacks and closes on context exit."""
269
+ def __exit__(self, exc_type, exc_val, exc_tb):
270
+ try:
271
+ super().__exit__(exc_type, exc_val, exc_tb)
272
+ finally:
273
+ self.close()
274
+
275
+
168
276
  def get_db(db_path=None, timeout=30.0):
169
- """Returns a resilient SQLite connection configured with WAL mode and busy timeout."""
170
- target = db_path or DB_FILE
171
- conn = sqlite3.connect(target, timeout=timeout)
277
+ """Returns a resilient SQLite connection configured with WAL mode, busy timeout, and auto-close."""
278
+ target = db_path or resolve_db_path()
279
+ parent_dir = os.path.dirname(os.path.abspath(target))
280
+ if parent_dir and not os.path.exists(parent_dir):
281
+ os.makedirs(parent_dir, exist_ok=True)
282
+ conn = sqlite3.connect(target, timeout=timeout, factory=ManagedConnection)
172
283
  conn.row_factory = sqlite3.Row
173
284
  try:
174
285
  conn.execute("PRAGMA journal_mode=WAL;")
@@ -451,6 +562,9 @@ def get_failed_fixes(error_signature):
451
562
  @retry_on_locked()
452
563
  def record_failed_fix(error_signature, attempted_patch, failure_reason, agent_role="pom-crafter"):
453
564
  """Records a failed bug fix attempt into negative memory so AI agents will never repeat it."""
565
+ error_signature = sanitize_text(error_signature)
566
+ attempted_patch = sanitize_text(attempted_patch)
567
+ failure_reason = sanitize_text(failure_reason)
454
568
  init_db(seed=True)
455
569
  with get_db() as conn:
456
570
  cursor = conn.cursor()
@@ -557,7 +671,10 @@ def query_solution(error_signature, module_name="GLOBAL", threshold=0.35):
557
671
  @retry_on_locked()
558
672
  def learn_solution(error_signature, module_name, symptom, root_cause, groovy_patch, code_patch=None):
559
673
  """Saves a verified solution into positive memory."""
560
- patch = code_patch or groovy_patch
674
+ patch = sanitize_text(code_patch or groovy_patch)
675
+ error_signature = sanitize_text(error_signature)
676
+ symptom = sanitize_text(symptom)
677
+ root_cause = sanitize_text(root_cause)
561
678
  init_db(seed=True)
562
679
  with get_db() as conn:
563
680
  cursor = conn.cursor()
@@ -640,16 +757,22 @@ def sync_db():
640
757
  def record_lesson(page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary):
641
758
  """
642
759
  Episodic Reflexion Memory:
643
- Records a concise lesson learned (maximum 2 lines) into SQLite for cross-session reflection.
760
+ Records a concise lesson learned (maximum 2 lines) into SQLite for cross-session reflection after secret sanitization.
644
761
  """
645
762
  init_db(seed=True)
763
+ page_context = sanitize_text(page_context or "GLOBAL")
764
+ error_signature = sanitize_text(error_signature)
765
+ root_cause = sanitize_text(root_cause)
766
+ failed_attempt = sanitize_text(failed_attempt)
767
+ successful_fix = sanitize_text(successful_fix)
768
+ lesson_summary = sanitize_text(lesson_summary.strip())
646
769
  with get_db() as conn:
647
770
  cursor = conn.cursor()
648
771
  cursor.execute("""
649
772
  INSERT INTO lessons_learned
650
773
  (page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary)
651
774
  VALUES (?, ?, ?, ?, ?, ?)
652
- """, (page_context or "GLOBAL", error_signature, root_cause, failed_attempt, successful_fix, lesson_summary.strip()))
775
+ """, (page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary))
653
776
  conn.commit()
654
777
  return True
655
778
 
@@ -700,6 +823,8 @@ def record_test_telemetry(test_id, status, is_retry=0, duration_ms=0.0, error_si
700
823
  Records an individual test execution into persistent SQLite telemetry.
701
824
  Used for computing 30-day historical flakiness index and auto-quarantine decisions.
702
825
  """
826
+ error_signature = sanitize_text(error_signature)
827
+ context = sanitize_text(context)
703
828
  init_db(seed=True, target_db=target_db)
704
829
  current_db = target_db or DB_FILE
705
830
  with get_db(current_db) as conn:
@@ -793,6 +918,45 @@ def get_stats():
793
918
  }
794
919
 
795
920
 
921
+ @retry_on_locked()
922
+ def purge_cache(keep_seeds=True, target_db=None):
923
+ """Clears SQLite memory cache, preserving default seed solutions if keep_seeds=True."""
924
+ target = target_db or DB_FILE
925
+ init_db(seed=False, target_db=target)
926
+ with get_db(target) as conn:
927
+ cursor = conn.cursor()
928
+ cursor.execute("DELETE FROM failed_fixes")
929
+ cursor.execute("DELETE FROM lessons_learned")
930
+ cursor.execute("DELETE FROM test_telemetry")
931
+ if keep_seeds:
932
+ cursor.execute("DELETE FROM healing_cache WHERE module_name != 'GLOBAL'")
933
+ else:
934
+ cursor.execute("DELETE FROM healing_cache")
935
+ conn.commit()
936
+ if keep_seeds:
937
+ init_db(seed=True, target_db=target)
938
+ return True
939
+
940
+
941
+ def export_cache(target_db=None):
942
+ """Exports all positive solutions, negative failed fixes, and lessons learned to a structured dictionary."""
943
+ target = target_db or DB_FILE
944
+ init_db(seed=True, target_db=target)
945
+ with get_db(target) as conn:
946
+ healing = [dict(r) for r in conn.execute("SELECT * FROM healing_cache").fetchall()]
947
+ failed = [dict(r) for r in conn.execute("SELECT * FROM failed_fixes").fetchall()]
948
+ lessons = [dict(r) for r in conn.execute("SELECT * FROM lessons_learned").fetchall()]
949
+ telemetry = [dict(r) for r in conn.execute("SELECT * FROM test_telemetry").fetchall()]
950
+ return {
951
+ "exported_at": datetime.now().isoformat(),
952
+ "database": target,
953
+ "healing_cache": healing,
954
+ "failed_fixes": failed,
955
+ "lessons_learned": lessons,
956
+ "test_telemetry": telemetry,
957
+ }
958
+
959
+
796
960
  def main():
797
961
  global DB_FILE
798
962
  parser = argparse.ArgumentParser(description="Enterprise Central Agent Memory & Self-Healing Resolver")
@@ -851,9 +1015,28 @@ def main():
851
1015
  # Stats command
852
1016
  subparsers.add_parser("stats", help="View memory cache statistics (Positive & Negative)")
853
1017
 
1018
+ # Fingerprint command (Deterministic Error Signature Hashing)
1019
+ fp_parser = subparsers.add_parser("fingerprint", help="Generate deterministic DJB2 error fingerprint")
1020
+ fp_parser.add_argument("--type", default="UnknownError", help="Error type/class (e.g. AssertionError)")
1021
+ fp_parser.add_argument("--msg", required=True, help="Error message / symptom")
1022
+ fp_parser.add_argument("--stack", default="", help="Optional stack anchor or file:function")
1023
+
1024
+ # Detect-Repeated command (Group errors and flag repeated errors)
1025
+ rep_parser = subparsers.add_parser("detect-repeated", help="Group errors by fingerprint and flag repeated occurrences")
1026
+ rep_parser.add_argument("--file", required=True, help="Path to JSON file containing list of error objects")
1027
+ rep_parser.add_argument("--threshold", type=int, default=3, help="Repeat threshold (default: 3)")
1028
+
854
1029
  # Sync command
855
1030
  subparsers.add_parser("sync", help="Synchronize Global Central Memory with Local Project Cache")
856
1031
 
1032
+ # Purge command
1033
+ purge_parser = subparsers.add_parser("purge", help="Clear SQLite memory cache")
1034
+ purge_parser.add_argument("--all", action="store_true", help="Clear all entries including default seed solutions")
1035
+
1036
+ # Export command
1037
+ export_parser = subparsers.add_parser("export", help="Export memory cache and lessons to JSON")
1038
+ export_parser.add_argument("-o", "--output", help="Output file path (default: stdout)")
1039
+
857
1040
  # Init command
858
1041
  subparsers.add_parser("init", help="Initialize and seed database")
859
1042
 
@@ -976,6 +1159,57 @@ def main():
976
1159
  count = sync_db()
977
1160
  print(f"✅ [MEMORY SYNC] Bidirectional sync completed ({count} solutions synchronized between Global & Local).")
978
1161
 
1162
+ elif args.command == "purge":
1163
+ all_entries = getattr(args, "all", False)
1164
+ purge_cache(keep_seeds=not all_entries)
1165
+ scope_str = "All memory entries cleared (including default seeds)" if all_entries else "User entries cleared (seed solutions preserved)"
1166
+ print(f"✅ [MEMORY PURGE] SQLite cache purged: {scope_str} at {DB_FILE}")
1167
+
1168
+ elif args.command == "export":
1169
+ data = export_cache()
1170
+ if getattr(args, "output", None):
1171
+ with open(args.output, "w", encoding="utf-8") as f:
1172
+ json.dump(data, f, indent=2)
1173
+ print(f"✅ [MEMORY EXPORT] Exported memory cache to: {args.output}")
1174
+ else:
1175
+ print(json.dumps(data, indent=2))
1176
+
1177
+ elif args.command == "fingerprint":
1178
+ fp = compute_error_fingerprint(args.type, args.msg, args.stack)
1179
+ print(f"🔑 Error Fingerprint: {fp}")
1180
+ print(f" Normalized Type : {args.type.strip().lower()}")
1181
+ print(f" Raw Message : {args.msg[:120]}")
1182
+ if args.stack:
1183
+ print(f" Stack Anchor : {args.stack}")
1184
+
1185
+ elif args.command == "detect-repeated":
1186
+ if not os.path.exists(args.file):
1187
+ print(f"❌ Error file not found: {args.file}")
1188
+ sys.exit(1)
1189
+ with open(args.file, "r", encoding="utf-8", errors="replace") as f:
1190
+ error_data = json.load(f)
1191
+ if isinstance(error_data, dict) and "errors" in error_data:
1192
+ error_data = error_data["errors"]
1193
+ elif not isinstance(error_data, list):
1194
+ error_data = [error_data]
1195
+ grouped = group_errors_by_fingerprint(error_data, repeat_threshold=args.threshold)
1196
+ print("==================================================")
1197
+ print(f"🔍 [ERROR FINGERPRINT GROUPING] Processed {len(error_data)} errors -> {len(grouped)} unique groups")
1198
+ print("==================================================")
1199
+ repeated_count = 0
1200
+ for fp, group in grouped.items():
1201
+ status_tag = f"🚨 [REPEATED ERROR >= {args.threshold}x]" if group["is_repeated"] else "ℹ️ [OBSERVE]"
1202
+ if group["is_repeated"]:
1203
+ repeated_count += 1
1204
+ print(f"{status_tag} {fp} ({group['count']}x)")
1205
+ print(f" Type: {group['error_type']} | Sample: {group['sample_message'][:100]}")
1206
+ print("==================================================")
1207
+ if repeated_count > 0:
1208
+ print(f"⚠️ Alert: {repeated_count} error fingerprints exceeded repeat threshold ({args.threshold}x).")
1209
+ sys.exit(2)
1210
+ else:
1211
+ print("✅ Zero repeated errors exceeded threshold.")
1212
+
979
1213
  elif args.command == "init" or args.command is None:
980
1214
  init_db(seed=True)
981
1215
  print(f"✅ Central Agent Memory initialized at: {DB_FILE}")
@@ -69,8 +69,9 @@ def resolve_target_repo(cli_target=None, cli_repo=None):
69
69
 
70
70
 
71
71
  class DiagnosticRunner:
72
- def __init__(self, target_dir=None):
72
+ def __init__(self, target_dir=None, insecure: bool = False):
73
73
  self.target_dir = Path(target_dir or HUB_ROOT).resolve()
74
+ self.insecure = insecure
74
75
  self.results = []
75
76
  self.warnings = []
76
77
  self.errors = []
@@ -85,9 +86,9 @@ class DiagnosticRunner:
85
86
  else:
86
87
  self.warnings.append((name, fix_hint))
87
88
 
88
- def run_command(self, cmd):
89
+ def run_command(self, cmd, timeout: float = 15.0):
89
90
  try:
90
- res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=isinstance(cmd, str))
91
+ res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=isinstance(cmd, str), timeout=timeout)
91
92
  return res.returncode == 0, res.stdout.strip()
92
93
  except Exception as e:
93
94
  return False, str(e)
@@ -99,13 +100,33 @@ class DiagnosticRunner:
99
100
  self.log_check("Operating System", "PASS", f"{os_name} {os_ver} ({arch})")
100
101
 
101
102
  def check_chrome(self):
102
- # 1. Search via 'where' or 'which'
103
- found, out = self.run_command("where chrome" if platform.system() == "Windows" else "which google-chrome")
104
- if found:
103
+ # 1. Search via 'where' (Windows) or 'which' (Unix)
104
+ cmd = "where chrome" if platform.system() == "Windows" else "which google-chrome google-chrome-stable chromium chromium-browser"
105
+ found, out = self.run_command(cmd)
106
+ if found and out:
105
107
  self.log_check("Google Chrome (Browser)", "PASS", f"Installed at {out.splitlines()[0]}")
106
108
  return
107
109
 
108
- # 2. Search standard installation paths on Windows
110
+ # 2. Search macOS standard application bundles
111
+ if platform.system() == "Darwin":
112
+ mac_paths = [
113
+ Path("/Applications/Google Chrome.app"),
114
+ Path.home() / "Applications/Google Chrome.app",
115
+ Path("/Applications/Chromium.app"),
116
+ Path("/Applications/Brave Browser.app"),
117
+ Path("/Applications/Microsoft Edge.app")
118
+ ]
119
+ for p in mac_paths:
120
+ if p.exists():
121
+ self.log_check("Google Chrome (Browser)", "PASS", f"Installed at {p}")
122
+ return
123
+ # Also check Playwright managed browser installations
124
+ pw_cache = Path.home() / "Library/Caches/ms-playwright"
125
+ if pw_cache.exists() and any(pw_cache.glob("chromium-*")):
126
+ self.log_check("Google Chrome (Browser)", "PASS", f"Playwright Chromium runtime verified at {pw_cache}")
127
+ return
128
+
129
+ # 3. Search Windows standard installation paths
109
130
  if platform.system() == "Windows":
110
131
  paths = [
111
132
  os.path.expandvars(r"%ProgramFiles%\Google\Chrome\Application\chrome.exe"),
@@ -134,8 +155,15 @@ class DiagnosticRunner:
134
155
  "Katalon includes embedded JRE, but for Gradle/Jenkins CLI, install OpenJDK 17.")
135
156
 
136
157
  def check_katalon(self):
158
+ # Context-aware: check if current target is actually a Katalon project
159
+ is_katalon_project = (
160
+ any(self.target_dir.glob("*.prj"))
161
+ or (self.target_dir / "Keywords").exists()
162
+ or ((self.target_dir / "Scripts").exists() and any(self.target_dir.glob("Scripts/**/*.groovy")))
163
+ )
164
+
137
165
  found, out = self.run_command("where katalonc" if platform.system() == "Windows" else "which katalonc")
138
- if found:
166
+ if found and out:
139
167
  self.log_check("Katalon Runtime Engine (KRE)", "PASS", f"Found katalonc at {out.splitlines()[0]}")
140
168
  return
141
169
 
@@ -154,8 +182,11 @@ class DiagnosticRunner:
154
182
  self.log_check("Katalon Studio IDE", "PASS", f"Installation directory located at {os.path.join(c, item)}")
155
183
  return
156
184
 
157
- self.log_check("Katalon Studio IDE", "WARN", "Katalon Studio installation not found in standard paths",
158
- "Download and install Katalon Studio (v8.x/v9.x/v10.x) from https://katalon.com/download")
185
+ if is_katalon_project:
186
+ self.log_check("Katalon Studio IDE", "WARN", "Katalon Studio installation not found in standard paths",
187
+ "Download and install Katalon Studio from https://katalon.com/download")
188
+ else:
189
+ self.log_check("Katalon Studio IDE", "PASS", "Not installed (Optional - not required for Playwright/Cypress/Pytest)")
159
190
 
160
191
  def check_node_allure(self):
161
192
  found_node, node_ver = self.run_command("node -v")
@@ -181,7 +212,9 @@ class DiagnosticRunner:
181
212
  required_dirs = [".agents/skills", ".agents/rules", "tools", "tests"]
182
213
  missing = [rd for rd in required_dirs if not (self.target_dir / rd).exists()]
183
214
  if not missing:
184
- self.log_check("Project Structure (QA Hub)", "PASS", "QA Hub integrity verified (27 Skills, Rules, Tools, Unit Tests)")
215
+ skills_dir = self.target_dir / ".agents" / "skills"
216
+ skill_count = len([d for d in skills_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]) if skills_dir.exists() else 0
217
+ self.log_check("Project Structure (QA Hub)", "PASS", f"QA Hub integrity verified ({skill_count} Skills, Rules, Tools, Unit Tests)")
185
218
  else:
186
219
  self.log_check("Project Structure (QA Hub)", "FAIL", f"Missing directories: {', '.join(missing)}",
187
220
  "Restore from official agent-skills Git repository.")
@@ -225,9 +258,16 @@ class DiagnosticRunner:
225
258
  return
226
259
  test_url = f"{base}/login"
227
260
  try:
261
+ # Respect explicit insecure flag or environment variable; default to secure TLS
262
+ allow_insecure = bool(
263
+ getattr(self, "insecure", False)
264
+ or os.environ.get("SYNAPSE_INSECURE_SSL") == "1"
265
+ )
228
266
  ctx = ssl.create_default_context()
229
- ctx.check_hostname = False
230
- ctx.verify_mode = ssl.CERT_NONE
267
+ if allow_insecure:
268
+ print(" ⚠️ [SECURITY NOTICE] TLS certificate verification is bypassed (--insecure / SYNAPSE_INSECURE_SSL).")
269
+ ctx.check_hostname = False
270
+ ctx.verify_mode = ssl.CERT_NONE
231
271
 
232
272
  req = urllib.request.Request(test_url, headers={'User-Agent': 'Mozilla/5.0'})
233
273
  with urllib.request.urlopen(req, timeout=8, context=ctx) as resp:
@@ -272,6 +312,7 @@ def main():
272
312
  parser = argparse.ArgumentParser(description="Enterprise Environment Diagnostic Tool")
273
313
  parser.add_argument("target", nargs="?", default=None, help="Target repository path to diagnose")
274
314
  parser.add_argument("--repo", "--path", dest="repo_path", default=None, help="Target repository path")
315
+ parser.add_argument("--insecure", action="store_true", help="Allow unverified SSL certificates during connectivity checks")
275
316
  args = parser.parse_args()
276
317
 
277
318
  try:
@@ -280,7 +321,7 @@ def main():
280
321
  print(f"❌ Error: {e}")
281
322
  return 1
282
323
 
283
- runner = DiagnosticRunner(target_dir=target_dir)
324
+ runner = DiagnosticRunner(target_dir=target_dir, insecure=args.insecure)
284
325
  return runner.run_all()
285
326
 
286
327