synapse-hub 2.2.3 → 2.4.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.
- package/.agents/agents/failure-analyst.md +11 -0
- package/.agents/agents/live-explorer.md +28 -0
- package/.agents/agents/orchestrator.md +20 -4
- package/.agents/agents/pom-crafter.md +12 -0
- package/.agents/agents/quality-gatekeeper.md +19 -4
- package/.agents/agents/test-runner.md +11 -0
- package/.agents/rules/communication-style.md +40 -0
- package/.agents/rules/context-engineering.md +23 -0
- package/.agents/rules/git-push-protocol.md +11 -17
- package/.agents/rules/graphify.md +14 -14
- package/.agents/rules/lessons-learned.md +3 -3
- package/.agents/rules/mandatory-playwright-live.md +29 -29
- package/.agents/rules/mobile_appium_rules.md +132 -0
- package/.agents/rules/performance_testing_rules.md +74 -0
- package/.agents/rules/skill-precedence.md +1 -1
- package/.agents/rules/systematic-debugging.md +6 -6
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +64 -215
- package/.agents/skills/agent-orchestration-multi-agent-optimize/examples/swarm_workload_config.json +12 -0
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +3 -3
- package/.agents/skills/antigravity-test-sentinel/examples/dod_gate_policy.json +11 -0
- package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
- package/.agents/skills/api-network-mocking-interception/examples/network_route_stub.json +9 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +9 -9
- package/.agents/skills/api-spec-to-e2e-matrix/examples/sample_openapi.json +19 -0
- package/.agents/skills/architecture-decision-records/SKILL.md +35 -412
- package/.agents/skills/architecture-decision-records/examples/0001-record-architecture-decisions.md +12 -0
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +4 -1
- package/.agents/skills/auth-session-and-cookie-manager/examples/storage_state_sample.json +11 -0
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +80 -186
- package/.agents/skills/backend-load-and-stress-patterns/examples/k6_load_test_sample.js +13 -0
- package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/bats-testing-patterns/examples/cli_runner_test.bats +6 -0
- package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -614
- package/.agents/skills/clean-pom-crafter/SKILL.md +21 -3
- package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -28
- package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -138
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +2 -2
- package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_page_sample.groovy +9 -0
- package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +9 -7
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +5 -5
- package/.agents/skills/db-state-verifier-and-seeder/examples/db_fixture_seed.json +12 -0
- package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/e2e-testing-patterns/examples/playwright_sharded_config.ts +12 -0
- package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -522
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +17 -3
- package/.agents/skills/failure-autopsy-and-healing/examples/sample_junit_failure.xml +8 -0
- package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -64
- package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -56
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -98
- package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
- package/.agents/skills/file-and-report-validator/examples/sample_export_schema.json +14 -0
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +3 -3
- package/.agents/skills/flaky-test-sentinel-and-quarantine/examples/quarantine_registry.json +11 -0
- package/.agents/skills/gherkin-to-suite/SKILL.md +103 -0
- package/.agents/skills/gherkin-to-suite/examples/checkout.feature +39 -0
- package/.agents/skills/gherkin-to-suite/examples/suite_report_sample.json +334 -0
- package/.agents/skills/github-actions-templates/SKILL.md +47 -304
- package/.agents/skills/github-actions-templates/examples/matrix_test_pipeline.yml +8 -0
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +6 -6
- package/.agents/skills/graphify-impact-analyzer/examples/dependency_blast_radius.json +8 -0
- package/.agents/skills/katalon-cli-runner/SKILL.md +2 -2
- package/.agents/skills/katalon-cli-runner/examples/headless_kre_exec.sh +3 -0
- package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -209
- package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -155
- package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -67
- package/.agents/skills/lam-script-generator/SKILL.md +42 -1
- package/.agents/skills/lam-script-generator/examples/user_story_sample.md +8 -0
- package/.agents/skills/lean-code-ladder/SKILL.md +83 -0
- package/.agents/skills/lean-code-ladder/examples/ladder_walkthrough.md +37 -0
- package/.agents/skills/meta-prompt-crafter/SKILL.md +107 -0
- package/.agents/skills/meta-prompt-crafter/examples/qa_prompt_template.md +35 -0
- package/.agents/skills/persistent-planning-files/SKILL.md +108 -0
- package/.agents/skills/persistent-planning-files/examples/task_plan_example.md +56 -0
- package/.agents/skills/playwright-live-interaction/SKILL.md +49 -1
- package/.agents/skills/playwright-live-interaction/examples/live_probe_script.js +5 -0
- package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -160
- package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -75
- package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -388
- package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -134
- package/.agents/skills/playwright-webapp-testing/SKILL.md +93 -0
- package/.agents/skills/playwright-webapp-testing/examples/server_lifecycle_playwright.py +63 -0
- package/.agents/skills/rbt-manual-testing/SKILL.md +150 -0
- package/.agents/skills/rbt-manual-testing/examples/checkout_rbt_manual_suite.md +25 -0
- package/.agents/skills/security-dast-sentinel/SKILL.md +57 -135
- package/.agents/skills/security-dast-sentinel/examples/dast_scan_config.json +10 -0
- package/.agents/skills/self-healing-locator/SKILL.md +24 -10
- package/.agents/skills/self-healing-locator/examples/aom_diff_sample.json +16 -0
- package/.agents/skills/skillspector-security-guard/SKILL.md +32 -90
- package/.agents/skills/skillspector-security-guard/examples/safe_mcp_config.json +12 -0
- package/.agents/skills/surgical-coding-principles/SKILL.md +89 -0
- package/.agents/skills/surgical-coding-principles/examples/surgical_vs_scope_creep.md +46 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +3 -3
- package/.agents/skills/synthetic-test-data-generator/examples/idempotent_data_recipe.json +9 -0
- package/.agents/skills/systematic-debugging/SKILL.md +100 -0
- package/.agents/skills/systematic-debugging/examples/four_phase_case.md +44 -0
- package/.agents/skills/test-driven-development/SKILL.md +114 -0
- package/.agents/skills/test-driven-development/examples/red_green_refactor_qa.md +78 -0
- package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
- package/.agents/skills/token-and-context-optimizer/examples/distilled_log_sample.txt +3 -0
- package/.agents/skills/verification-before-completion/SKILL.md +100 -0
- package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +46 -0
- package/.agents/skills/visual-regression-sentinel/SKILL.md +21 -1
- package/.agents/skills/visual-regression-sentinel/examples/visual_mask_rules.json +9 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +3 -3
- package/.agents/skills/wcag-audit-patterns/examples/wcag_compliance_checklist.json +10 -0
- package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -541
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
- package/.agents/skills/web-vitals-and-performance-profiler/examples/cwv_budget_policy.json +6 -0
- package/.agents/templates/completion_report.md +57 -57
- package/.agents/workflows/analyze_requirement_document.md +18 -0
- package/.agents/workflows/export_testcases_excel.md +32 -0
- package/.agents/workflows/generate_combinatorial_test_data.md +28 -0
- package/.agents/workflows/generate_cross_module_test_plan.md +37 -0
- package/.agents/workflows/generate_manual_testcases_rbt.md +56 -0
- package/.agents/workflows/generate_requirements_from_website.md +25 -0
- package/.agents/workflows/generate_testcases_from_requirements.md +21 -0
- package/.agents/workflows/graphify.md +10 -10
- package/.agents/workflows/verify.md +2 -2
- package/.cursor/rules/language.mdc +7 -7
- package/.cursor/rules/synapse-qa.mdc +23 -0
- package/.cursorrules +7 -0
- package/.pre-commit-hooks.yaml +14 -0
- package/.windsurfrules +7 -0
- package/AGENTS.md +54 -15
- package/CLAUDE.md +35 -12
- package/LICENSE +30 -0
- package/NOTICE +180 -0
- package/README.en.md +325 -0
- package/README.md +247 -512
- package/action.yml +51 -0
- package/benchmarks/qa_eval/README.md +71 -0
- package/benchmarks/qa_eval/build_dataset.py +677 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +23 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/eval_runner.py +418 -0
- package/benchmarks/qa_eval/reports/AB_METHODOLOGY_REPORT.md +83 -0
- package/benchmarks/qa_eval/reports/BENCHMARK_REPORT.md +69 -0
- package/benchmarks/qa_eval/reports/eval_results.json +105 -0
- package/bin/agent-qa.js +10 -3
- package/docs/ARCHITECTURE.md +107 -0
- package/docs/CI_CD_INTEGRATION.md +94 -0
- package/docs/CLI_REFERENCE.md +157 -0
- package/docs/CONTRIBUTING.md +91 -0
- package/docs/PLUGIN_DEVELOPMENT_GUIDE.md +126 -0
- package/docs/TOKEN_QUOTA_PLAYBOOK.md +41 -0
- package/docs/adopted-ecosystem-tools.md +87 -0
- package/docs/schemas/synapse.config.schema.json +64 -0
- package/examples/cypress-ecommerce-suite/README.md +45 -0
- package/examples/cypress-ecommerce-suite/cypress/e2e/checkout.cy.js +17 -0
- package/examples/cypress-ecommerce-suite/cypress/pages/BasePage.js +29 -0
- package/examples/cypress-ecommerce-suite/cypress/pages/CartPage.js +51 -0
- package/examples/cypress-ecommerce-suite/cypress.config.js +13 -0
- package/examples/cypress-ecommerce-suite/package.json +13 -0
- package/examples/katalon-suite/Keywords/pages/BasePage.groovy +19 -0
- package/examples/katalon-suite/Keywords/pages/DashboardPage.groovy +29 -0
- package/examples/katalon-suite/Keywords/pages/LoginPage.groovy +45 -0
- package/examples/katalon-suite/Profiles/default.glbl +12 -0
- package/examples/katalon-suite/README.md +18 -0
- package/examples/katalon-suite/Scripts/Auth/TC_LoginSuccess/Script1234567890.groovy +12 -0
- package/examples/katalon-suite/Scripts/Dashboard/TC_ViewSummary/Script1234567891.groovy +15 -0
- package/examples/katalon-suite/katalon_suite.prj +21 -0
- package/examples/playwright-crm-suite/README.md +40 -0
- package/examples/playwright-crm-suite/package.json +14 -0
- package/examples/playwright-crm-suite/pages/BasePage.ts +37 -0
- package/examples/playwright-crm-suite/pages/CustomerPage.ts +57 -0
- package/examples/playwright-crm-suite/pages/LoginPage.ts +32 -0
- package/examples/playwright-crm-suite/playwright.config.ts +27 -0
- package/examples/playwright-crm-suite/tests/auth.spec.ts +22 -0
- package/examples/playwright-crm-suite/tests/customers.spec.ts +33 -0
- package/examples/plugins/sample-custom-plugin/README.md +14 -0
- package/examples/plugins/sample-custom-plugin/plugin.json +16 -0
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/SKILL.md +29 -0
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/examples/audit_fixture.json +8 -0
- package/examples/plugins/sample-custom-plugin/tools/custom_security_headers.py +125 -0
- package/examples/practices/README.md +22 -0
- package/examples/practices/requirements/crm/requirements_contracts.md +292 -0
- package/examples/practices/requirements/crm/requirements_customers.md +462 -0
- package/examples/practices/requirements/crm/requirements_login.md +202 -0
- package/examples/practices/requirements/crm/requirements_projects.md +396 -0
- package/examples/practices/requirements/crm/requirements_tasks.md +343 -0
- package/examples/practices/requirements/ecommerce/requirements_category_brand.txt +402 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/2026-04-03_04-28-27.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/KAN-1_requirement.md +52 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-19.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-57.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/KAN-4_requirement.md +119 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-5_overview.md +9 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-08.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-23.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/KAN-6_requirement.md +32 -0
- package/examples/practices/testcases/api/book_management_api_test_cases.md +144 -0
- package/examples/practices/testcases/api/user_management_api_test_cases.md +139 -0
- package/examples/practices/testcases/crm/test_cases_crm_login.md +148 -0
- package/examples/practices/testcases/crm/test_cases_customers.md +156 -0
- package/examples/pytest-api-suite/README.md +45 -0
- package/examples/pytest-api-suite/conftest.py +31 -0
- package/examples/pytest-api-suite/pages/user_api_client.py +36 -0
- package/examples/pytest-api-suite/requirements.txt +2 -0
- package/examples/pytest-api-suite/tests/test_user_api.py +44 -0
- package/examples/selenium-suite/README.md +18 -0
- package/examples/selenium-suite/pages/base_page.py +27 -0
- package/examples/selenium-suite/pages/login_page.py +29 -0
- package/examples/selenium-suite/pages/order_page.py +25 -0
- package/examples/selenium-suite/pytest.ini +6 -0
- package/examples/selenium-suite/requirements.txt +2 -0
- package/examples/selenium-suite/tests/test_login.py +10 -0
- package/examples/selenium-suite/tests/test_order.py +10 -0
- package/package.json +22 -7
- package/scripts/generate_skill_examples.py +244 -0
- package/scripts/install.ps1 +112 -0
- package/scripts/install_global_plugin.ps1 +94 -0
- package/scripts/install_global_plugin.sh +49 -0
- package/scripts/lint_skills.ps1 +57 -0
- package/scripts/package_dist.py +323 -0
- package/scripts/setup.sh +59 -0
- package/scripts/update_hub.ps1 +19 -0
- package/scripts/update_hub.sh +15 -0
- package/skills_manifest.json +209 -6
- package/synapse.config.json +30 -0
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +7 -7
- package/templates/PROJECT_STATE_TEMPLATE.md +1 -1
- package/templates/ai_bridges/CLAUDE.md +33 -16
- package/templates/ai_bridges/copilot-instructions.md +14 -21
- package/templates/ai_bridges/cursor_synapse_qa.mdc +17 -20
- package/templates/ai_bridges/windsurfrules +21 -21
- package/templates/ci/github-actions-synapse.yml +71 -4
- package/templates/welcome.html.tmpl +1674 -0
- package/tools/.infer_baseline.json +1102 -0
- package/tools/TOOLS_RUNBOOK.md +92 -130
- package/tools/__init__.py +26 -0
- package/tools/agent_canary.py +501 -501
- package/tools/analyze_project.py +31 -17
- package/tools/api_fuzzer.py +278 -6
- package/tools/author_integrity.py +6 -6
- package/tools/auto_heal.py +1 -1
- package/tools/browser_helpers.js +105 -7
- package/tools/bug_reporter.py +31 -1
- package/tools/bump_version.py +23 -3
- package/tools/cache_resolver.py +248 -14
- package/tools/check_env.py +55 -14
- package/tools/check_version_sync.py +153 -0
- package/tools/ci_healing_hook.py +1 -1
- package/tools/cli.py +263 -13
- package/tools/count_tokens.py +1257 -0
- package/tools/dashboard_generator.py +2638 -0
- package/tools/db_sandbox.py +198 -20
- package/tools/e2e_flow_prober.ps1 +12 -64
- package/tools/e2e_flow_prober.py +99 -0
- package/tools/e2e_flow_prober.sh +4 -0
- package/tools/export_postman_collection.py +29 -7
- package/tools/flaky_detector.py +4 -3
- package/tools/generate_allure_report.ps1 +12 -29
- package/tools/generate_allure_report.py +77 -0
- package/tools/generate_allure_report.sh +4 -0
- package/tools/generate_jmx_plan.py +299 -68
- package/tools/generate_test_reports.ps1 +12 -134
- package/tools/generate_test_reports.py +133 -0
- package/tools/generate_test_reports.sh +4 -0
- package/tools/gherkin_suite.py +758 -0
- package/tools/git_pre_commit_guard.py +12 -6
- package/tools/goldfish_review.py +366 -0
- package/tools/heal_pr_automator.py +1 -1
- package/tools/heuristic_locator_matcher.py +170 -21
- package/tools/infer_linter.py +612 -70
- package/tools/jtl_analyzer.py +1 -1
- package/tools/lint_ai_docs.py +64 -0
- package/tools/matrix_sharder.py +1 -1
- package/tools/mcp_excel_server.ps1 +12 -250
- package/tools/mcp_excel_server.py +249 -0
- package/tools/mcp_excel_server.sh +4 -0
- package/tools/mcp_server.py +368 -144
- package/tools/md_to_xlsx.py +307 -0
- package/tools/micro_mutator.py +300 -69
- package/tools/omni_transpiler.py +2 -2
- package/tools/output_formatter.py +128 -0
- package/tools/pinpoint_failure.py +88 -88
- package/tools/project_state.py +2 -2
- package/tools/repo_map.py +1 -1
- package/tools/safety_firewall.py +197 -33
- package/tools/setup_project.py +294 -30
- package/tools/skillspector.py +8 -8
- package/tools/spec_drift_sentinel.py +439 -0
- package/tools/sync_agent_rules.py +248 -0
- package/tools/telemetry_correlator.py +45 -0
- package/tools/test_impact.py +51 -12
- package/tools/tms_sync_bridge.py +21 -5
- package/tools/universal_ir.py +3 -0
- package/tools/update_sentinel.py +87 -15
- package/tools/verify_all.ps1 +13 -0
- package/tools/verify_all.py +463 -79
- package/tools/visual_diff_canvas.py +1 -0
- package/tools/visual_som_helper.py +1 -1
- package/tools/watch_mode.py +305 -0
- package/tools/welcome_page.py +291 -0
package/tools/bug_reporter.py
CHANGED
|
@@ -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
|
|
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",
|
package/tools/bump_version.py
CHANGED
|
@@ -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
|
-
|
|
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("✅
|
|
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
|
|
|
@@ -189,10 +198,21 @@ def main():
|
|
|
189
198
|
# 8. docs/ARCHITECTURE.md
|
|
190
199
|
update_file("docs/ARCHITECTURE.md", r'(>\s*\*\*Version:\*\*\s*`)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
|
|
191
200
|
|
|
192
|
-
# 9. README.md (Documentation headings and
|
|
201
|
+
# 9. README.md & README.en.md (Documentation headings and badges)
|
|
193
202
|
update_file("README.md", r'(Phiên bản Hub:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
|
|
194
203
|
update_file("README.md", r'(Hub Version:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
|
|
195
204
|
update_file("README.md", r'(Intelligence Core v)[^")\]]+', rf"\g<1>{new_ver}", args.dry_run)
|
|
205
|
+
update_file("README.en.md", r'(Hub Version:\*\*\s*`v)[^`]+(`)', rf"\g<1>{new_ver}\g<2>", args.dry_run)
|
|
206
|
+
update_file("README.en.md", r'(Intelligence Core v)[^")\]]+', rf"\g<1>{new_ver}", args.dry_run)
|
|
207
|
+
|
|
208
|
+
# 10. tools/__init__.py
|
|
209
|
+
update_file("tools/__init__.py", r'(__version__\s*=\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
|
|
210
|
+
|
|
211
|
+
# 11. tools/welcome_page.py
|
|
212
|
+
update_file("tools/welcome_page.py", r'("version":\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
|
|
213
|
+
|
|
214
|
+
# 12. tools/dashboard_generator.py
|
|
215
|
+
update_file("tools/dashboard_generator.py", r'(Synapse-Hub v)[0-9]+\.[0-9]+\.[0-9]+', rf"\g<1>{new_ver}", args.dry_run)
|
|
196
216
|
|
|
197
217
|
if args.dry_run:
|
|
198
218
|
print("\n[INFO] Dry-run completed. No files modified on disk.")
|
package/tools/cache_resolver.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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": "((
|
|
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
|
|
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
|
|
170
|
-
target = db_path or
|
|
171
|
-
|
|
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
|
|
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}")
|
package/tools/check_env.py
CHANGED
|
@@ -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
|
-
|
|
104
|
-
|
|
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
|
|
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
|
-
|
|
158
|
-
|
|
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.
|
|
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
|
-
|
|
230
|
-
|
|
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
|
|