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.
- package/.agents/agents/failure-analyst.md +11 -0
- package/.agents/agents/live-explorer.md +28 -0
- package/.agents/agents/orchestrator.md +23 -4
- package/.agents/agents/pom-crafter.md +15 -0
- package/.agents/agents/quality-gatekeeper.md +20 -4
- package/.agents/agents/test-runner.md +12 -0
- package/.agents/rules/communication-style.md +40 -0
- package/.agents/rules/context-engineering.md +23 -0
- package/.agents/rules/git-push-protocol.md +10 -16
- package/.agents/rules/graphify.md +14 -14
- 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/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 +1 -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/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 +54 -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 +331 -0
- package/README.md +252 -515
- 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 +16 -4
- 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 +27 -12
- 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 +98 -131
- 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 +30 -3
- package/tools/cache_resolver.py +248 -14
- package/tools/check_env.py +55 -14
- package/tools/check_version_sync.py +171 -0
- package/tools/ci_healing_hook.py +1 -1
- package/tools/cli.py +454 -42
- 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/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 +257 -58
- 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/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 +302 -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 +196 -45
- package/tools/setup_project.py +295 -31
- package/tools/skillspector.py +31 -0
- 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 +479 -85
- 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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Enterprise Standardized Output Formatter & Exit Code Specification
|
|
4
|
+
------------------------------------------------------------------
|
|
5
|
+
Provides centralized terminal styling, NO_COLOR standard compliance,
|
|
6
|
+
and deterministic exit codes across all Synapse Hub CLI tools.
|
|
7
|
+
|
|
8
|
+
Exit Code Standards:
|
|
9
|
+
0: EXIT_SUCCESS (Operation passed, DoD compliant)
|
|
10
|
+
1: EXIT_FAILURE (Test failure, linter defect, gate rejection)
|
|
11
|
+
2: EXIT_USAGE_ERROR (Invalid CLI arguments, syntax error)
|
|
12
|
+
3: EXIT_SECURITY_BLOCK (Destructive action blocked, secret leaked, MIT license altered)
|
|
13
|
+
4: EXIT_ENVIRONMENT_ERROR (Missing required tool, incompatible runtime)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import json
|
|
19
|
+
from typing import Any, Dict, Optional
|
|
20
|
+
|
|
21
|
+
# Standard Exit Codes
|
|
22
|
+
EXIT_SUCCESS = 0
|
|
23
|
+
EXIT_FAILURE = 1
|
|
24
|
+
EXIT_USAGE_ERROR = 2
|
|
25
|
+
EXIT_SECURITY_BLOCK = 3
|
|
26
|
+
EXIT_ENVIRONMENT_ERROR = 4
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_color_enabled() -> bool:
|
|
30
|
+
"""
|
|
31
|
+
Determines if ANSI color codes should be rendered.
|
|
32
|
+
Honors https://no-color.org specification (NO_COLOR env var).
|
|
33
|
+
"""
|
|
34
|
+
if os.environ.get("NO_COLOR") is not None:
|
|
35
|
+
return False
|
|
36
|
+
if os.environ.get("TERM") == "dumb":
|
|
37
|
+
return False
|
|
38
|
+
if os.environ.get("FORCE_COLOR") in ("1", "true", "TRUE"):
|
|
39
|
+
return True
|
|
40
|
+
return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Colors:
|
|
44
|
+
"""ANSI color codes with dynamic NO_COLOR enforcement."""
|
|
45
|
+
ESC = "\033"
|
|
46
|
+
CYAN = "\033[96m"
|
|
47
|
+
GREEN = "\033[92m"
|
|
48
|
+
YELLOW = "\033[93m"
|
|
49
|
+
RED = "\033[91m"
|
|
50
|
+
BOLD = "\033[1m"
|
|
51
|
+
DIM = "\033[2m"
|
|
52
|
+
RESET = "\033[0m"
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _c(code: str) -> str:
|
|
56
|
+
return code if is_color_enabled() else ""
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def cyan(cls, text: str) -> str:
|
|
60
|
+
return f"{cls._c(cls.CYAN)}{text}{cls._c(cls.RESET)}"
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def green(cls, text: str) -> str:
|
|
64
|
+
return f"{cls._c(cls.GREEN)}{text}{cls._c(cls.RESET)}"
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def yellow(cls, text: str) -> str:
|
|
68
|
+
return f"{cls._c(cls.YELLOW)}{text}{cls._c(cls.RESET)}"
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def red(cls, text: str) -> str:
|
|
72
|
+
return f"{cls._c(cls.RED)}{text}{cls._c(cls.RESET)}"
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def bold(cls, text: str) -> str:
|
|
76
|
+
return f"{cls._c(cls.BOLD)}{text}{cls._c(cls.RESET)}"
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def dim(cls, text: str) -> str:
|
|
80
|
+
return f"{cls._c(cls.DIM)}{text}{cls._c(cls.RESET)}"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class OutputFormatter:
|
|
84
|
+
"""Standardized console & machine-readable output renderer."""
|
|
85
|
+
|
|
86
|
+
def __init__(self, quiet: bool = False, json_mode: bool = False):
|
|
87
|
+
self.quiet = quiet
|
|
88
|
+
self.json_mode = json_mode
|
|
89
|
+
|
|
90
|
+
def header(self, title: str):
|
|
91
|
+
if not self.quiet and not self.json_mode:
|
|
92
|
+
border = "=" * 70
|
|
93
|
+
print(f"\n{Colors.cyan(border)}")
|
|
94
|
+
print(f" {Colors.bold(title)}")
|
|
95
|
+
print(f"{Colors.cyan(border)}\n")
|
|
96
|
+
|
|
97
|
+
def step(self, current: int, total: int, title: str):
|
|
98
|
+
if not self.quiet and not self.json_mode:
|
|
99
|
+
print(f"{Colors.bold(f'[{current}/{total}]')} {title}")
|
|
100
|
+
|
|
101
|
+
def pass_msg(self, message: str):
|
|
102
|
+
if not self.json_mode:
|
|
103
|
+
print(f"{Colors.green('✅ [PASS]')} {message}")
|
|
104
|
+
|
|
105
|
+
def fail_msg(self, message: str):
|
|
106
|
+
if not self.json_mode:
|
|
107
|
+
print(f"{Colors.red('❌ [FAIL]')} {message}")
|
|
108
|
+
|
|
109
|
+
def warn_msg(self, message: str):
|
|
110
|
+
if not self.quiet and not self.json_mode:
|
|
111
|
+
print(f"{Colors.yellow('⚠️ [WARN]')} {message}")
|
|
112
|
+
|
|
113
|
+
def info_msg(self, message: str):
|
|
114
|
+
if not self.quiet and not self.json_mode:
|
|
115
|
+
print(f"{Colors.cyan('ℹ️ [INFO]')} {message}")
|
|
116
|
+
|
|
117
|
+
def print_data(self, data: Any, format_type: str = "text"):
|
|
118
|
+
if format_type == "json" or self.json_mode:
|
|
119
|
+
if isinstance(data, (dict, list)):
|
|
120
|
+
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
121
|
+
else:
|
|
122
|
+
print(str(data))
|
|
123
|
+
else:
|
|
124
|
+
print(str(data))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# Global default instance
|
|
128
|
+
console = OutputFormatter()
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Fast Zero-Token Failure Pinpointer for Katalon & Jenkins Reports.
|
|
4
|
-
Scans the latest Reports/**/JUnit_Report.xml, extracts failed Test Cases,
|
|
5
|
-
exact failed lines in Groovy/Script, and the root-cause exception.
|
|
6
|
-
Outputs < 100 tokens.
|
|
7
|
-
|
|
8
|
-
Usage:
|
|
9
|
-
python3 tools/pinpoint_failure.py
|
|
10
|
-
python3 tools/pinpoint_failure.py path/to/JUnit_Report.xml
|
|
11
|
-
"""
|
|
12
|
-
import os
|
|
13
|
-
import sys
|
|
14
|
-
import glob
|
|
15
|
-
import xml.etree.ElementTree as ET
|
|
16
|
-
import re
|
|
17
|
-
|
|
18
|
-
def find_latest_report():
|
|
19
|
-
reports = glob.glob("Reports/**/JUnit_Report.xml", recursive=True)
|
|
20
|
-
if not reports:
|
|
21
|
-
return None
|
|
22
|
-
reports.sort(key=lambda x: os.path.getmtime(x), reverse=True)
|
|
23
|
-
return reports[0]
|
|
24
|
-
|
|
25
|
-
def parse_failures(xml_path):
|
|
26
|
-
if not os.path.exists(xml_path):
|
|
27
|
-
print(f"Error: {xml_path} does not exist.")
|
|
28
|
-
return []
|
|
29
|
-
|
|
30
|
-
tree = ET.parse(xml_path)
|
|
31
|
-
root = tree.getroot()
|
|
32
|
-
failures = []
|
|
33
|
-
|
|
34
|
-
suites = [root] if root.tag == "testsuite" else root.findall("testsuite")
|
|
35
|
-
|
|
36
|
-
for suite in suites:
|
|
37
|
-
for tc in suite.findall("testcase"):
|
|
38
|
-
tc_name = tc.get("name", "Unknown")
|
|
39
|
-
tc_time = tc.get("time", "0")
|
|
40
|
-
fail = tc.find("failure")
|
|
41
|
-
err = tc.find("error")
|
|
42
|
-
target = fail if fail is not None else err
|
|
43
|
-
|
|
44
|
-
if target is not None:
|
|
45
|
-
msg = target.get("message", "")
|
|
46
|
-
text = target.text or ""
|
|
47
|
-
combined = (msg + "\n" + text).replace("
", "\n")
|
|
48
|
-
|
|
49
|
-
# Extract Root Cause Reason
|
|
50
|
-
reason_match = re.search(r"Reason:\s*(.+?)(?=\n\s*at|\Z)", combined, re.DOTALL)
|
|
51
|
-
if reason_match:
|
|
52
|
-
reason = reason_match.group(1).strip()
|
|
53
|
-
else:
|
|
54
|
-
lines = [l.strip() for l in combined.split("\n") if l.strip()]
|
|
55
|
-
reason = lines[0] if lines else "Unknown Error"
|
|
56
|
-
|
|
57
|
-
# Extract Failed Line in Groovy Page / Script
|
|
58
|
-
Enterprise_lines = re.findall(r"(?:Enterprise\.pages\.[\w]+|TC-[\w\s\u00C0-\u1EF9]+|Script\d+)\.groovy:\d+|(?:TC-[\w\s\u00C0-\u1EF9]+:\d+)", combined)
|
|
59
|
-
|
|
60
|
-
failures.append({
|
|
61
|
-
"tc": tc_name.split("/")[-1],
|
|
62
|
-
"time": f"{float(tc_time):.1f}s",
|
|
63
|
-
"reason": reason.split("\n")[0][:180],
|
|
64
|
-
"failed_at": Enterprise_lines[:2] if Enterprise_lines else ["See JUnit report"]
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
return failures
|
|
68
|
-
|
|
69
|
-
def main():
|
|
70
|
-
xml_path = sys.argv[1] if len(sys.argv) > 1 else find_latest_report()
|
|
71
|
-
if not xml_path:
|
|
72
|
-
print("No JUnit_Report.xml found in Reports/.")
|
|
73
|
-
sys.exit(0)
|
|
74
|
-
|
|
75
|
-
print(f"=== Report: {xml_path} ===")
|
|
76
|
-
failures = parse_failures(xml_path)
|
|
77
|
-
if not failures:
|
|
78
|
-
print("✅ 100% PASSED: Zero failures found in report.")
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
print(f"❌ Found {len(failures)} Failed Test Case(s):")
|
|
82
|
-
for i, f in enumerate(failures, 1):
|
|
83
|
-
print(f"[{i}] {f['tc']} ({f['time']})")
|
|
84
|
-
print(f" Cause: {f['reason']}")
|
|
85
|
-
print(f" Line: {', '.join(f['failed_at'])}")
|
|
86
|
-
|
|
87
|
-
if __name__ == "__main__":
|
|
88
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Fast Zero-Token Failure Pinpointer for Katalon & Jenkins Reports.
|
|
4
|
+
Scans the latest Reports/**/JUnit_Report.xml, extracts failed Test Cases,
|
|
5
|
+
exact failed lines in Groovy/Script, and the root-cause exception.
|
|
6
|
+
Outputs < 100 tokens.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 tools/pinpoint_failure.py
|
|
10
|
+
python3 tools/pinpoint_failure.py path/to/JUnit_Report.xml
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import glob
|
|
15
|
+
import xml.etree.ElementTree as ET
|
|
16
|
+
import re
|
|
17
|
+
|
|
18
|
+
def find_latest_report():
|
|
19
|
+
reports = glob.glob("Reports/**/JUnit_Report.xml", recursive=True)
|
|
20
|
+
if not reports:
|
|
21
|
+
return None
|
|
22
|
+
reports.sort(key=lambda x: os.path.getmtime(x), reverse=True)
|
|
23
|
+
return reports[0]
|
|
24
|
+
|
|
25
|
+
def parse_failures(xml_path):
|
|
26
|
+
if not os.path.exists(xml_path):
|
|
27
|
+
print(f"Error: {xml_path} does not exist.")
|
|
28
|
+
return []
|
|
29
|
+
|
|
30
|
+
tree = ET.parse(xml_path)
|
|
31
|
+
root = tree.getroot()
|
|
32
|
+
failures = []
|
|
33
|
+
|
|
34
|
+
suites = [root] if root.tag == "testsuite" else root.findall("testsuite")
|
|
35
|
+
|
|
36
|
+
for suite in suites:
|
|
37
|
+
for tc in suite.findall("testcase"):
|
|
38
|
+
tc_name = tc.get("name", "Unknown")
|
|
39
|
+
tc_time = tc.get("time", "0")
|
|
40
|
+
fail = tc.find("failure")
|
|
41
|
+
err = tc.find("error")
|
|
42
|
+
target = fail if fail is not None else err
|
|
43
|
+
|
|
44
|
+
if target is not None:
|
|
45
|
+
msg = target.get("message", "")
|
|
46
|
+
text = target.text or ""
|
|
47
|
+
combined = (msg + "\n" + text).replace("
", "\n")
|
|
48
|
+
|
|
49
|
+
# Extract Root Cause Reason
|
|
50
|
+
reason_match = re.search(r"Reason:\s*(.+?)(?=\n\s*at|\Z)", combined, re.DOTALL)
|
|
51
|
+
if reason_match:
|
|
52
|
+
reason = reason_match.group(1).strip()
|
|
53
|
+
else:
|
|
54
|
+
lines = [l.strip() for l in combined.split("\n") if l.strip()]
|
|
55
|
+
reason = lines[0] if lines else "Unknown Error"
|
|
56
|
+
|
|
57
|
+
# Extract Failed Line in Groovy Page / Script
|
|
58
|
+
Enterprise_lines = re.findall(r"(?:Enterprise\.pages\.[\w]+|TC-[\w\s\u00C0-\u1EF9]+|Script\d+)\.groovy:\d+|(?:TC-[\w\s\u00C0-\u1EF9]+:\d+)", combined)
|
|
59
|
+
|
|
60
|
+
failures.append({
|
|
61
|
+
"tc": tc_name.split("/")[-1],
|
|
62
|
+
"time": f"{float(tc_time):.1f}s",
|
|
63
|
+
"reason": reason.split("\n")[0][:180],
|
|
64
|
+
"failed_at": Enterprise_lines[:2] if Enterprise_lines else ["See JUnit report"]
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return failures
|
|
68
|
+
|
|
69
|
+
def main():
|
|
70
|
+
xml_path = sys.argv[1] if len(sys.argv) > 1 else find_latest_report()
|
|
71
|
+
if not xml_path:
|
|
72
|
+
print("No JUnit_Report.xml found in Reports/.")
|
|
73
|
+
sys.exit(0)
|
|
74
|
+
|
|
75
|
+
print(f"=== Report: {xml_path} ===")
|
|
76
|
+
failures = parse_failures(xml_path)
|
|
77
|
+
if not failures:
|
|
78
|
+
print("✅ 100% PASSED: Zero failures found in report.")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
print(f"❌ Found {len(failures)} Failed Test Case(s):")
|
|
82
|
+
for i, f in enumerate(failures, 1):
|
|
83
|
+
print(f"[{i}] {f['tc']} ({f['time']})")
|
|
84
|
+
print(f" Cause: {f['reason']}")
|
|
85
|
+
print(f" Line: {', '.join(f['failed_at'])}")
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
main()
|
package/tools/project_state.py
CHANGED
|
@@ -56,7 +56,7 @@ def init_state(target_dir, goal="Initial Autonomous Setup"):
|
|
|
56
56
|
- Failing Locator / Line: `[None]`
|
|
57
57
|
|
|
58
58
|
## 4. Quality Verification
|
|
59
|
-
-
|
|
59
|
+
- 9 Quality Gates: `READY`
|
|
60
60
|
- Clean POM Integrity: `VERIFIED`
|
|
61
61
|
"""
|
|
62
62
|
with open(state_file, "w", encoding="utf-8") as f:
|
|
@@ -146,7 +146,7 @@ def update_state(target_dir, goal=None, completed_task=None, pending_task=None,
|
|
|
146
146
|
{watch_block}
|
|
147
147
|
|
|
148
148
|
## 4. Quality Verification
|
|
149
|
-
-
|
|
149
|
+
- 9 Quality Gates: `{current_gate}`
|
|
150
150
|
- Clean POM Integrity: `VERIFIED`
|
|
151
151
|
"""
|
|
152
152
|
with open(state_file, "w", encoding="utf-8") as f:
|
package/tools/repo_map.py
CHANGED
|
@@ -221,7 +221,7 @@ def build_repo_map(target_repo=None, output_txt=None, output_json=None):
|
|
|
221
221
|
|
|
222
222
|
rendered_text = "\n".join(out_lines)
|
|
223
223
|
compressed_bytes = len(rendered_text.encode("utf-8"))
|
|
224
|
-
ratio = (1.0 - (compressed_bytes /
|
|
224
|
+
ratio = (1.0 - (compressed_bytes / total_raw_bytes)) * 100 if total_raw_bytes > 0 else 0.0
|
|
225
225
|
est_tokens = len(rendered_text) // 4
|
|
226
226
|
|
|
227
227
|
# 1. Save to primary destination (tools/repo_map.txt in Hub)
|
package/tools/safety_firewall.py
CHANGED
|
@@ -29,36 +29,52 @@ if hasattr(sys.stdout, 'reconfigure'):
|
|
|
29
29
|
|
|
30
30
|
DANGEROUS_ACTION_KEYWORDS = [
|
|
31
31
|
r"\bdelete\b",
|
|
32
|
-
r"\bremove\b",
|
|
33
32
|
r"\bpurge\b",
|
|
34
33
|
r"\bwipe\b",
|
|
35
34
|
r"\bdrop\b",
|
|
36
35
|
r"\bpay\b",
|
|
37
36
|
r"\bpurchase\b",
|
|
38
|
-
r"\
|
|
39
|
-
r"\
|
|
37
|
+
r"\bplace\s+order\b",
|
|
38
|
+
r"\bconfirm\s+(?:payment|order|delete|deletion|purge|wipe|destroy)\b",
|
|
39
|
+
r"\bsubmit\s+order\b",
|
|
40
|
+
r"\barchive\s+(?:all|data|system|database|account)\b",
|
|
41
|
+
r"\brevoke\s+access\b",
|
|
42
|
+
r"\bcancel\s+subscription\b",
|
|
43
|
+
r"\berase\s+all\s+data\b",
|
|
44
|
+
r"\bterminate(?:\s+account)?\b",
|
|
40
45
|
r"\bdestroy\b",
|
|
41
|
-
r"\
|
|
42
|
-
|
|
43
|
-
r"
|
|
44
|
-
|
|
45
|
-
r"
|
|
46
|
-
r"
|
|
47
|
-
r"
|
|
48
|
-
r"
|
|
49
|
-
r"
|
|
50
|
-
r"n[aạáàảã]p\s+ti[eềéèẻẽẹ]n",
|
|
46
|
+
r"\bformat\s+(?:disk|drive|system|database|table|partition)\b",
|
|
47
|
+
# Multilingual & Vietnamese destructive keywords with strict word boundaries
|
|
48
|
+
r"(?<!\w)x[oóòỏõọ][aáàảãạ](?!\w)",
|
|
49
|
+
r"(?<!\w)x[oóòỏõọ]a\s+t[aáàảãạ]t\s+c[aảáàãạ](?!\w)",
|
|
50
|
+
r"(?<!\w)h[uủúùũụ]y\s+b[oỏóòõọ](?!\w)",
|
|
51
|
+
r"(?<!\w)h[uủúùũụ]y\s+t[aàáảãạ]i\s+kho[aảáàãạ]n(?!\w)",
|
|
52
|
+
r"(?<!\w)thanh\s+to[aáàảãạ]n(?!\w)",
|
|
53
|
+
r"(?<!\w)r[uúùủũụ]t\s+ti[eềéèẻẽẹ]n(?!\w)",
|
|
54
|
+
r"(?<!\w)n[aạáàảã]p\s+ti[eềéèẻẽẹ]n(?!\w)",
|
|
51
55
|
]
|
|
52
56
|
|
|
57
|
+
# Explicit whitelist of benign user-flow patterns that require exact fullmatch to authorize
|
|
58
|
+
BENIGN_ACTIONS_WHITELIST = [
|
|
59
|
+
re.compile(r"(?i)^\s*(?:checkout|proceed\s+to\s+checkout)\s*$"),
|
|
60
|
+
re.compile(r"(?i)^\s*(?:reset\s+password)\s*$"),
|
|
61
|
+
re.compile(r"(?i)^\s*(?:remove\s+from\s+cart)\s*$"),
|
|
62
|
+
re.compile(r"(?i)^\s*(?:xoay\s+[aảáàãạ]nh)\s*$"),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
def _split_words_and_camel(s: str) -> str:
|
|
66
|
+
"""Expands camelCase and PascalCase into separated words for keyword inspection."""
|
|
67
|
+
return re.sub(r'([a-z0-9])([A-Z])', r'\1 \2', s)
|
|
68
|
+
|
|
53
69
|
DANGEROUS_SELECTOR_PATTERNS = [
|
|
54
|
-
r"\[data-action\*=['\"]?delete['\"]?\]",
|
|
55
|
-
r"\[data-action\*=['\"]?pay['\"]?\]",
|
|
70
|
+
r"\[data-action\*=['\"]?(?:delete|pay|confirm)['\"]?\]",
|
|
56
71
|
r"\.btn-danger\b",
|
|
57
72
|
r"\.p-button-danger\b",
|
|
58
|
-
r"
|
|
59
|
-
r"
|
|
60
|
-
r"
|
|
61
|
-
r"text
|
|
73
|
+
r"#(?:btn-confirm|btn-delete|btn-purge)\b",
|
|
74
|
+
r"\[aria-label\*=['\"]?(?:delete|pay|confirm)['\"]?\]",
|
|
75
|
+
r"button[#\.\-_a-zA-Z0-9]*[_\-]*(?:del|delete|purge|wipe|drop|confirm|archive)",
|
|
76
|
+
r":(?:has-text|text)\s*\(\s*(['\"])[^'\"]*(?:delete|wipe|purge|pay|confirm|archive|x[oóòỏõọ][aáàảãạ]|h[uủúùũụ]y\s+b[oỏóòõọ]|thanh\s*to[aáàảãạ]n)[^'\"]*\1\s*\)",
|
|
77
|
+
r"text=(['\"])[^'\"]*(?:delete|wipe|purge|pay|confirm|archive|x[oóòỏõọ][aáàảãạ]|h[uủúùũụ]y\s+b[oỏóòõọ]|thanh\s*to[aáàảãạ]n)\1",
|
|
62
78
|
]
|
|
63
79
|
|
|
64
80
|
COMPILED_TEXT_REGEX = re.compile("|".join(DANGEROUS_ACTION_KEYWORDS), re.IGNORECASE)
|
|
@@ -66,16 +82,20 @@ COMPILED_SEL_REGEX = re.compile("|".join(DANGEROUS_SELECTOR_PATTERNS), re.IGNORE
|
|
|
66
82
|
|
|
67
83
|
# Pattern to extract interaction statements from Playwright / Selenium / Groovy
|
|
68
84
|
INTERACTION_CODE_PATTERNS = [
|
|
69
|
-
# page.click('selector'), page.fill('selector', 'val')
|
|
70
|
-
re.compile(r"""(?:page|frame)\.(?P<act>click|dblclick|fill|type|press|check)\s*\(\s*['"](?P<sel>
|
|
71
|
-
# page.locator('selector').click()
|
|
72
|
-
re.compile(r"""(?:page|frame)\.locator\s*\(\s*['"](?P<sel>[
|
|
85
|
+
# page.click('selector'), page.fill('selector', 'val') - supports nested quotes
|
|
86
|
+
re.compile(r"""(?:page|frame)\.(?P<act>click|dblclick|fill|type|press|check)\s*\(\s*(?P<q>['"])(?P<sel>(?:(?!(?P=q)|\\).|\\.)*)(?P=q)(?:\s*,\s*['"](?P<text>[^'"]*)['"])?""", re.IGNORECASE),
|
|
87
|
+
# page.locator('selector').click() or page.locator(VAR).click()
|
|
88
|
+
re.compile(r"""(?:page|frame)\.locator\s*\(\s*(?:(?P<q>['"])(?P<sel>(?:(?!(?P=q)|\\).|\\.)*)(?P=q)|(?P<var_arg>[a-zA-Z0-9_]+))\s*\)\.(?P<act>click|fill|press)""", re.IGNORECASE),
|
|
73
89
|
# page.getByText('text').click()
|
|
74
|
-
re.compile(r"""(?:page|frame)\.getByText\s*\(\s*['"](?P<text>
|
|
90
|
+
re.compile(r"""(?:page|frame)\.getByText\s*\(\s*(?P<q>['"])(?P<text>(?:(?!(?P=q)|\\).|\\.)*)(?P=q)\s*\)\.(?P<act>click)""", re.IGNORECASE),
|
|
75
91
|
# page.getByRole('button', { name: 'Delete' }).click()
|
|
76
92
|
re.compile(r"""(?:page|frame)\.getByRole\s*\(\s*['"](?P<role>[^'"]+)['"]\s*,\s*\{[^}]*name:\s*['"](?P<text>[^'"]+)['"]""", re.IGNORECASE),
|
|
77
93
|
# WebUI.click(findTestObject(...)) or WebUI.clickOffset
|
|
78
94
|
re.compile(r"""WebUI\.(?P<act>click|setText)\s*\([^)]*['"](?P<sel>[^'"]*)['"]""", re.IGNORECASE),
|
|
95
|
+
# Variable locator call: e.g. deleteBtn.click(), btn.click()
|
|
96
|
+
re.compile(r"""\b(?!(?:page|frame|WebUI)\b)(?P<var>[a-zA-Z0-9_]+)\.(?P<act>click|dblclick|fill|press|tap)\s*\(""", re.IGNORECASE),
|
|
97
|
+
# Clean POM method invocation: e.g. customerPage.deleteAllCustomers(), accountPO.purgeAccount(userId)
|
|
98
|
+
re.compile(r"""\b(?!(?:page|frame|WebUI)\b)(?P<page>[a-zA-Z0-9_]*(?:Page|PO|View|Service|Component|[a-z]))\.(?P<act>[a-zA-Z0-9_]+)\s*\((?P<args>[^)]*)\)""", re.IGNORECASE),
|
|
79
99
|
]
|
|
80
100
|
|
|
81
101
|
|
|
@@ -86,23 +106,36 @@ def is_action_dangerous(action_type: str, text: str = "", selector: str = "") ->
|
|
|
86
106
|
"""
|
|
87
107
|
text_clean = text.strip() if text else ""
|
|
88
108
|
sel_clean = selector.strip() if selector else ""
|
|
109
|
+
act_clean = action_type.strip() if action_type else "click"
|
|
89
110
|
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return True, f"
|
|
111
|
+
# 1. Action type verification
|
|
112
|
+
act_expanded = _split_words_and_camel(act_clean)
|
|
113
|
+
if act_clean.lower() in ("drop_database", "wipe_environment", "execute_payment"):
|
|
114
|
+
return True, f"Action type '{action_type}' is classified as irreversible"
|
|
115
|
+
match_act = COMPILED_TEXT_REGEX.search(act_clean) or COMPILED_TEXT_REGEX.search(act_expanded)
|
|
116
|
+
if match_act:
|
|
117
|
+
return True, f"Action method contains dangerous verb: '{match_act.group(0)}'"
|
|
118
|
+
|
|
119
|
+
# 2. Check text (Dangerous check runs FIRST; benign whitelist requires strict fullmatch)
|
|
120
|
+
if text_clean:
|
|
121
|
+
text_expanded = _split_words_and_camel(text_clean)
|
|
122
|
+
match = COMPILED_TEXT_REGEX.search(text_clean) or COMPILED_TEXT_REGEX.search(text_expanded)
|
|
123
|
+
if match:
|
|
124
|
+
# Check strict benign whitelist fullmatch before flagging
|
|
125
|
+
if any(p.fullmatch(text_clean) for p in BENIGN_ACTIONS_WHITELIST):
|
|
126
|
+
pass
|
|
127
|
+
else:
|
|
128
|
+
return True, f"Element text contains dangerous keyword: '{match.group(0)}'"
|
|
94
129
|
|
|
95
|
-
# Check selector
|
|
130
|
+
# 3. Check selector
|
|
96
131
|
if sel_clean:
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if action_type.lower() in ("drop_database", "wipe_environment", "execute_payment"):
|
|
105
|
-
return True, f"Action type '{action_type}' is classified as irreversible"
|
|
132
|
+
if not any(p.fullmatch(sel_clean) for p in BENIGN_ACTIONS_WHITELIST):
|
|
133
|
+
if COMPILED_SEL_REGEX.search(sel_clean):
|
|
134
|
+
return True, f"Element selector matches destructive pattern: '{sel_clean}'"
|
|
135
|
+
sel_expanded = _split_words_and_camel(sel_clean)
|
|
136
|
+
match = COMPILED_TEXT_REGEX.search(sel_clean) or COMPILED_TEXT_REGEX.search(sel_expanded)
|
|
137
|
+
if match:
|
|
138
|
+
return True, f"Element selector contains dangerous keyword: '{match.group(0)}' in '{sel_clean}'"
|
|
106
139
|
|
|
107
140
|
return False, "Action passed safety firewall inspection"
|
|
108
141
|
|
|
@@ -130,38 +163,132 @@ def inspect_and_authorize(action_type: str, text: str = "", selector: str = "",
|
|
|
130
163
|
def inspect_code_snippet(code: str, force: bool = False) -> List[Dict[str, Any]]:
|
|
131
164
|
"""
|
|
132
165
|
Parses a code snippet or script and checks all discovered UI interaction statements.
|
|
166
|
+
Tracks variable locator assignments across lines to detect chained and indirect invocations.
|
|
133
167
|
"""
|
|
134
168
|
inspections = []
|
|
135
169
|
lines = code.splitlines()
|
|
170
|
+
tracked_vars: Dict[str, str] = {}
|
|
171
|
+
|
|
172
|
+
var_assign_re = re.compile(
|
|
173
|
+
r"""(?:const|let|var|def|\$)?\s*(?P<var>[a-zA-Z0-9_]+)\s*=\s*(?:(?:page|frame)\.(?:locator|getByTestId|getByText)\s*\(\s*(?:(?P<q1>['"])(?P<val1>(?:(?!(?P=q1)|\\).|\\.)*)(?P=q1)|(?P<arg_var>[a-zA-Z0-9_]+))\s*\)|(?:page|frame)\.getByRole\s*\(\s*['"][^'"]+['"]\s*,\s*\{[^}]*name:\s*(?P<q_role>['"])(?P<val_role>(?:(?!(?P=q_role)|\\).|\\.)*)(?P=q_role)|(?P<q2>['"])(?P<val2>(?:(?!(?P=q2)|\\).|\\.)*)(?P=q2))""",
|
|
174
|
+
re.IGNORECASE
|
|
175
|
+
)
|
|
136
176
|
|
|
137
177
|
for line_idx, line in enumerate(lines, start=1):
|
|
138
178
|
stripped = line.strip()
|
|
139
179
|
if not stripped or stripped.startswith(("//", "#", "/*", "*")):
|
|
140
180
|
continue
|
|
141
181
|
|
|
182
|
+
# Track variable definitions
|
|
183
|
+
m_assign = var_assign_re.search(stripped)
|
|
184
|
+
if m_assign:
|
|
185
|
+
var_name = m_assign.group("var")
|
|
186
|
+
val = m_assign.group("val1") or m_assign.group("val2") or m_assign.group("val_role") or ""
|
|
187
|
+
if not val and m_assign.group("arg_var"):
|
|
188
|
+
arg_v = m_assign.group("arg_var")
|
|
189
|
+
val = tracked_vars.get(arg_v, arg_v)
|
|
190
|
+
# Recursively unwrap aliases
|
|
191
|
+
while val in tracked_vars:
|
|
192
|
+
val = tracked_vars[val]
|
|
193
|
+
if var_name and val:
|
|
194
|
+
tracked_vars[var_name] = val
|
|
195
|
+
|
|
142
196
|
matched = False
|
|
143
197
|
for pattern in INTERACTION_CODE_PATTERNS:
|
|
198
|
+
found_any = False
|
|
144
199
|
for match in pattern.finditer(line):
|
|
145
|
-
matched = True
|
|
146
200
|
groups = match.groupdict()
|
|
147
201
|
act = groups.get("act") or "click"
|
|
148
202
|
sel = groups.get("sel") or ""
|
|
149
203
|
text = groups.get("text") or ""
|
|
204
|
+
var_arg = groups.get("var_arg")
|
|
205
|
+
var_name = groups.get("var")
|
|
206
|
+
|
|
207
|
+
if var_arg:
|
|
208
|
+
sel = tracked_vars.get(var_arg, var_arg)
|
|
209
|
+
elif not sel and groups.get("page"):
|
|
210
|
+
sel = f"{groups.get('page')}.{act}"
|
|
211
|
+
elif not sel and var_name:
|
|
212
|
+
sel = tracked_vars.get(var_name, var_name)
|
|
213
|
+
|
|
214
|
+
# Recursively unwrap selector if it matches an alias
|
|
215
|
+
while sel in tracked_vars:
|
|
216
|
+
sel = tracked_vars[sel]
|
|
217
|
+
|
|
150
218
|
auth = inspect_and_authorize(act, text=text, selector=sel, force=force)
|
|
151
219
|
auth["line"] = line_idx
|
|
152
220
|
auth["code"] = stripped
|
|
153
221
|
inspections.append(auth)
|
|
222
|
+
found_any = True
|
|
223
|
+
if found_any:
|
|
224
|
+
matched = True
|
|
225
|
+
break
|
|
154
226
|
|
|
155
|
-
# Fallback: line itself contains dangerous keyword and an action verb
|
|
156
|
-
if not matched and
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
227
|
+
# Fallback: line itself contains dangerous keyword and an action verb (ignore pure assignments)
|
|
228
|
+
if not matched and not m_assign:
|
|
229
|
+
line_expanded = _split_words_and_camel(stripped)
|
|
230
|
+
if (COMPILED_TEXT_REGEX.search(stripped) or COMPILED_TEXT_REGEX.search(line_expanded)) and any(w in stripped.lower() for w in ("click", "press", "submit", "post", "delete", "purge", "wipe", "drop", "pay")):
|
|
231
|
+
auth = inspect_and_authorize("dispatch", text=stripped, selector="", force=force)
|
|
232
|
+
auth["line"] = line_idx
|
|
233
|
+
auth["code"] = stripped
|
|
234
|
+
inspections.append(auth)
|
|
161
235
|
|
|
162
236
|
return inspections
|
|
163
237
|
|
|
164
238
|
|
|
239
|
+
def detect_action_loop(action_history: List[str], repeat_threshold: int = 4) -> Dict[str, Any]:
|
|
240
|
+
"""
|
|
241
|
+
Detects infinite action loops and repeating cycle patterns among agent tool calls.
|
|
242
|
+
Inspired by agentblazo/blazo deterministic detection rules.
|
|
243
|
+
|
|
244
|
+
Checks:
|
|
245
|
+
1. Consecutive identical actions (>= repeat_threshold)
|
|
246
|
+
2. 2-action repeating cycle: [A, B, A, B]
|
|
247
|
+
3. 3-action repeating cycle: [A, B, C, A, B, C]
|
|
248
|
+
"""
|
|
249
|
+
if not action_history or len(action_history) < 2:
|
|
250
|
+
return {"loop_detected": False, "reason": "History too short", "severity": "none", "pattern": []}
|
|
251
|
+
|
|
252
|
+
names = [a.strip() for a in action_history if a.strip()]
|
|
253
|
+
|
|
254
|
+
# 1. Check consecutive identical repeats
|
|
255
|
+
i = 0
|
|
256
|
+
while i < len(names):
|
|
257
|
+
name = names[i]
|
|
258
|
+
end = i + 1
|
|
259
|
+
while end < len(names) and names[end] == name:
|
|
260
|
+
end += 1
|
|
261
|
+
length = end - i
|
|
262
|
+
if length >= repeat_threshold:
|
|
263
|
+
return {
|
|
264
|
+
"loop_detected": True,
|
|
265
|
+
"type": "consecutive_repeat",
|
|
266
|
+
"severity": "critical",
|
|
267
|
+
"message": f"Possible loop: action '{name}' repeated {length} times in a row",
|
|
268
|
+
"pattern": [name] * length
|
|
269
|
+
}
|
|
270
|
+
i = end
|
|
271
|
+
|
|
272
|
+
# 2. Check 2-action and 3-action trailing cycles
|
|
273
|
+
for block in (2, 3):
|
|
274
|
+
if len(names) < block * 2:
|
|
275
|
+
continue
|
|
276
|
+
start = len(names) - block * 2
|
|
277
|
+
left_block = names[start : start + block]
|
|
278
|
+
right_block = names[start + block : start + block * 2]
|
|
279
|
+
if left_block == right_block and len(set(left_block)) >= 2:
|
|
280
|
+
pattern_str = " -> ".join(left_block)
|
|
281
|
+
return {
|
|
282
|
+
"loop_detected": True,
|
|
283
|
+
"type": f"cycle_{block}",
|
|
284
|
+
"severity": "critical",
|
|
285
|
+
"message": f"Possible loop: cycle pattern [{pattern_str}] repeated",
|
|
286
|
+
"pattern": left_block
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {"loop_detected": False, "reason": "No loop patterns detected", "severity": "none", "pattern": []}
|
|
290
|
+
|
|
291
|
+
|
|
165
292
|
def main():
|
|
166
293
|
parser = argparse.ArgumentParser(description="Action Safety Firewall & Blast Radius Containment Engine")
|
|
167
294
|
parser.add_argument("--action", default="click", help="Action type (click, fill, submit)")
|
|
@@ -173,8 +300,32 @@ def main():
|
|
|
173
300
|
parser.add_argument("--dry-run", action="store_true", help="Execute in pre-execution dry-run mode (outputs validation report)")
|
|
174
301
|
parser.add_argument("--force", action="store_true", help="Force bypass safety firewall")
|
|
175
302
|
parser.add_argument("--json", action="store_true", help="Output results in JSON format")
|
|
303
|
+
parser.add_argument("--history", default="", help="Comma-separated or JSON list of historical action names to check for infinite loops")
|
|
304
|
+
parser.add_argument("--check-loop", action="store_true", help="Explicitly audit action history for cycle/infinite loops")
|
|
176
305
|
args = parser.parse_args()
|
|
177
306
|
|
|
307
|
+
# Loop check mode (Blazo-inspired)
|
|
308
|
+
if args.history or args.check_loop:
|
|
309
|
+
hist_items = []
|
|
310
|
+
if args.history:
|
|
311
|
+
if args.history.strip().startswith("[") and args.history.strip().endswith("]"):
|
|
312
|
+
try:
|
|
313
|
+
hist_items = json.loads(args.history)
|
|
314
|
+
except Exception:
|
|
315
|
+
hist_items = [h.strip() for h in args.history.split(",") if h.strip()]
|
|
316
|
+
else:
|
|
317
|
+
hist_items = [h.strip() for h in args.history.split(",") if h.strip()]
|
|
318
|
+
|
|
319
|
+
loop_res = detect_action_loop(hist_items)
|
|
320
|
+
if args.json:
|
|
321
|
+
print(json.dumps(loop_res, indent=2))
|
|
322
|
+
else:
|
|
323
|
+
if loop_res["loop_detected"]:
|
|
324
|
+
print(f"❌ [LOOP_DETECTED] {loop_res['message']} (Severity: {loop_res['severity'].upper()})")
|
|
325
|
+
else:
|
|
326
|
+
print("✅ [LOOP_CLEAR] No infinite loop or cycle pattern detected in action history.")
|
|
327
|
+
return 1 if loop_res["loop_detected"] else 0
|
|
328
|
+
|
|
178
329
|
# Determine input source: explicit stdin flag/arg, --file, --code, or parameters
|
|
179
330
|
code_content = ""
|
|
180
331
|
if args.code and args.code != "-":
|