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
|
@@ -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,27 +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"
|
|
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)",
|
|
44
55
|
]
|
|
45
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
|
+
|
|
46
69
|
DANGEROUS_SELECTOR_PATTERNS = [
|
|
47
|
-
r"\[data-action\*=['\"]?delete['\"]?\]",
|
|
48
|
-
r"\[data-action\*=['\"]?pay['\"]?\]",
|
|
70
|
+
r"\[data-action\*=['\"]?(?:delete|pay|confirm)['\"]?\]",
|
|
49
71
|
r"\.btn-danger\b",
|
|
50
72
|
r"\.p-button-danger\b",
|
|
51
|
-
r"
|
|
52
|
-
r"
|
|
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",
|
|
53
78
|
]
|
|
54
79
|
|
|
55
80
|
COMPILED_TEXT_REGEX = re.compile("|".join(DANGEROUS_ACTION_KEYWORDS), re.IGNORECASE)
|
|
@@ -57,16 +82,20 @@ COMPILED_SEL_REGEX = re.compile("|".join(DANGEROUS_SELECTOR_PATTERNS), re.IGNORE
|
|
|
57
82
|
|
|
58
83
|
# Pattern to extract interaction statements from Playwright / Selenium / Groovy
|
|
59
84
|
INTERACTION_CODE_PATTERNS = [
|
|
60
|
-
# page.click('selector'), page.fill('selector', 'val')
|
|
61
|
-
re.compile(r"""(?:page|frame)\.(?P<act>click|dblclick|fill|type|press|check)\s*\(\s*['"](?P<sel>
|
|
62
|
-
# page.locator('selector').click()
|
|
63
|
-
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),
|
|
64
89
|
# page.getByText('text').click()
|
|
65
|
-
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),
|
|
66
91
|
# page.getByRole('button', { name: 'Delete' }).click()
|
|
67
92
|
re.compile(r"""(?:page|frame)\.getByRole\s*\(\s*['"](?P<role>[^'"]+)['"]\s*,\s*\{[^}]*name:\s*['"](?P<text>[^'"]+)['"]""", re.IGNORECASE),
|
|
68
93
|
# WebUI.click(findTestObject(...)) or WebUI.clickOffset
|
|
69
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),
|
|
70
99
|
]
|
|
71
100
|
|
|
72
101
|
|
|
@@ -77,19 +106,36 @@ def is_action_dangerous(action_type: str, text: str = "", selector: str = "") ->
|
|
|
77
106
|
"""
|
|
78
107
|
text_clean = text.strip() if text else ""
|
|
79
108
|
sel_clean = selector.strip() if selector else ""
|
|
109
|
+
act_clean = action_type.strip() if action_type else "click"
|
|
80
110
|
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return True, f"Element text contains dangerous keyword: '{match}'"
|
|
85
|
-
|
|
86
|
-
# Check selector
|
|
87
|
-
if sel_clean and COMPILED_SEL_REGEX.search(sel_clean):
|
|
88
|
-
return True, f"Element selector matches destructive pattern: '{sel_clean}'"
|
|
89
|
-
|
|
90
|
-
# Specific dangerous action types
|
|
91
|
-
if action_type.lower() in ("drop_database", "wipe_environment", "execute_payment"):
|
|
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"):
|
|
92
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)}'"
|
|
129
|
+
|
|
130
|
+
# 3. Check selector
|
|
131
|
+
if sel_clean:
|
|
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}'"
|
|
93
139
|
|
|
94
140
|
return False, "Action passed safety firewall inspection"
|
|
95
141
|
|
|
@@ -117,38 +163,132 @@ def inspect_and_authorize(action_type: str, text: str = "", selector: str = "",
|
|
|
117
163
|
def inspect_code_snippet(code: str, force: bool = False) -> List[Dict[str, Any]]:
|
|
118
164
|
"""
|
|
119
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.
|
|
120
167
|
"""
|
|
121
168
|
inspections = []
|
|
122
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
|
+
)
|
|
123
176
|
|
|
124
177
|
for line_idx, line in enumerate(lines, start=1):
|
|
125
178
|
stripped = line.strip()
|
|
126
179
|
if not stripped or stripped.startswith(("//", "#", "/*", "*")):
|
|
127
180
|
continue
|
|
128
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
|
+
|
|
129
196
|
matched = False
|
|
130
197
|
for pattern in INTERACTION_CODE_PATTERNS:
|
|
198
|
+
found_any = False
|
|
131
199
|
for match in pattern.finditer(line):
|
|
132
|
-
matched = True
|
|
133
200
|
groups = match.groupdict()
|
|
134
201
|
act = groups.get("act") or "click"
|
|
135
202
|
sel = groups.get("sel") or ""
|
|
136
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
|
+
|
|
137
218
|
auth = inspect_and_authorize(act, text=text, selector=sel, force=force)
|
|
138
219
|
auth["line"] = line_idx
|
|
139
220
|
auth["code"] = stripped
|
|
140
221
|
inspections.append(auth)
|
|
222
|
+
found_any = True
|
|
223
|
+
if found_any:
|
|
224
|
+
matched = True
|
|
225
|
+
break
|
|
141
226
|
|
|
142
|
-
# Fallback: line itself contains dangerous keyword and an action verb
|
|
143
|
-
if not matched and
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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)
|
|
148
235
|
|
|
149
236
|
return inspections
|
|
150
237
|
|
|
151
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
|
+
|
|
152
292
|
def main():
|
|
153
293
|
parser = argparse.ArgumentParser(description="Action Safety Firewall & Blast Radius Containment Engine")
|
|
154
294
|
parser.add_argument("--action", default="click", help="Action type (click, fill, submit)")
|
|
@@ -160,8 +300,32 @@ def main():
|
|
|
160
300
|
parser.add_argument("--dry-run", action="store_true", help="Execute in pre-execution dry-run mode (outputs validation report)")
|
|
161
301
|
parser.add_argument("--force", action="store_true", help="Force bypass safety firewall")
|
|
162
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")
|
|
163
305
|
args = parser.parse_args()
|
|
164
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
|
+
|
|
165
329
|
# Determine input source: explicit stdin flag/arg, --file, --code, or parameters
|
|
166
330
|
code_content = ""
|
|
167
331
|
if args.code and args.code != "-":
|