synapse-hub 2.2.4 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/.agents/agents/failure-analyst.md +11 -0
  2. package/.agents/agents/live-explorer.md +28 -0
  3. package/.agents/agents/orchestrator.md +23 -4
  4. package/.agents/agents/pom-crafter.md +15 -0
  5. package/.agents/agents/quality-gatekeeper.md +20 -4
  6. package/.agents/agents/test-runner.md +12 -0
  7. package/.agents/rules/communication-style.md +40 -0
  8. package/.agents/rules/context-engineering.md +23 -0
  9. package/.agents/rules/git-push-protocol.md +10 -16
  10. package/.agents/rules/graphify.md +14 -14
  11. package/.agents/rules/mandatory-playwright-live.md +29 -29
  12. package/.agents/rules/mobile_appium_rules.md +132 -0
  13. package/.agents/rules/performance_testing_rules.md +74 -0
  14. package/.agents/rules/systematic-debugging.md +6 -6
  15. package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +64 -215
  16. package/.agents/skills/agent-orchestration-multi-agent-optimize/examples/swarm_workload_config.json +12 -0
  17. package/.agents/skills/antigravity-test-sentinel/SKILL.md +3 -3
  18. package/.agents/skills/antigravity-test-sentinel/examples/dod_gate_policy.json +11 -0
  19. package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
  20. package/.agents/skills/api-network-mocking-interception/examples/network_route_stub.json +9 -0
  21. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +9 -9
  22. package/.agents/skills/api-spec-to-e2e-matrix/examples/sample_openapi.json +19 -0
  23. package/.agents/skills/architecture-decision-records/SKILL.md +35 -412
  24. package/.agents/skills/architecture-decision-records/examples/0001-record-architecture-decisions.md +12 -0
  25. package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +1 -1
  26. package/.agents/skills/auth-session-and-cookie-manager/examples/storage_state_sample.json +11 -0
  27. package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +80 -186
  28. package/.agents/skills/backend-load-and-stress-patterns/examples/k6_load_test_sample.js +13 -0
  29. package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
  30. package/.agents/skills/bats-testing-patterns/examples/cli_runner_test.bats +6 -0
  31. package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -614
  32. package/.agents/skills/clean-pom-crafter/SKILL.md +21 -3
  33. package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -28
  34. package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -138
  35. package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +2 -2
  36. package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_page_sample.groovy +9 -0
  37. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +5 -5
  38. package/.agents/skills/db-state-verifier-and-seeder/examples/db_fixture_seed.json +12 -0
  39. package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
  40. package/.agents/skills/e2e-testing-patterns/examples/playwright_sharded_config.ts +12 -0
  41. package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -522
  42. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +17 -3
  43. package/.agents/skills/failure-autopsy-and-healing/examples/sample_junit_failure.xml +8 -0
  44. package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -64
  45. package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -56
  46. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -98
  47. package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
  48. package/.agents/skills/file-and-report-validator/examples/sample_export_schema.json +14 -0
  49. package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +3 -3
  50. package/.agents/skills/flaky-test-sentinel-and-quarantine/examples/quarantine_registry.json +11 -0
  51. package/.agents/skills/gherkin-to-suite/SKILL.md +103 -0
  52. package/.agents/skills/gherkin-to-suite/examples/checkout.feature +39 -0
  53. package/.agents/skills/gherkin-to-suite/examples/suite_report_sample.json +334 -0
  54. package/.agents/skills/github-actions-templates/SKILL.md +47 -304
  55. package/.agents/skills/github-actions-templates/examples/matrix_test_pipeline.yml +8 -0
  56. package/.agents/skills/graphify-impact-analyzer/SKILL.md +6 -6
  57. package/.agents/skills/graphify-impact-analyzer/examples/dependency_blast_radius.json +8 -0
  58. package/.agents/skills/katalon-cli-runner/SKILL.md +2 -2
  59. package/.agents/skills/katalon-cli-runner/examples/headless_kre_exec.sh +3 -0
  60. package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -209
  61. package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -155
  62. package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -67
  63. package/.agents/skills/lam-script-generator/SKILL.md +42 -1
  64. package/.agents/skills/lam-script-generator/examples/user_story_sample.md +8 -0
  65. package/.agents/skills/lean-code-ladder/SKILL.md +83 -0
  66. package/.agents/skills/lean-code-ladder/examples/ladder_walkthrough.md +37 -0
  67. package/.agents/skills/meta-prompt-crafter/SKILL.md +107 -0
  68. package/.agents/skills/meta-prompt-crafter/examples/qa_prompt_template.md +35 -0
  69. package/.agents/skills/persistent-planning-files/SKILL.md +108 -0
  70. package/.agents/skills/persistent-planning-files/examples/task_plan_example.md +56 -0
  71. package/.agents/skills/playwright-live-interaction/SKILL.md +49 -1
  72. package/.agents/skills/playwright-live-interaction/examples/live_probe_script.js +5 -0
  73. package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -160
  74. package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -75
  75. package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -388
  76. package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -134
  77. package/.agents/skills/playwright-webapp-testing/SKILL.md +93 -0
  78. package/.agents/skills/playwright-webapp-testing/examples/server_lifecycle_playwright.py +63 -0
  79. package/.agents/skills/rbt-manual-testing/SKILL.md +150 -0
  80. package/.agents/skills/rbt-manual-testing/examples/checkout_rbt_manual_suite.md +25 -0
  81. package/.agents/skills/security-dast-sentinel/SKILL.md +54 -135
  82. package/.agents/skills/security-dast-sentinel/examples/dast_scan_config.json +10 -0
  83. package/.agents/skills/self-healing-locator/SKILL.md +24 -10
  84. package/.agents/skills/self-healing-locator/examples/aom_diff_sample.json +16 -0
  85. package/.agents/skills/skillspector-security-guard/SKILL.md +32 -90
  86. package/.agents/skills/skillspector-security-guard/examples/safe_mcp_config.json +12 -0
  87. package/.agents/skills/surgical-coding-principles/SKILL.md +89 -0
  88. package/.agents/skills/surgical-coding-principles/examples/surgical_vs_scope_creep.md +46 -0
  89. package/.agents/skills/synthetic-test-data-generator/SKILL.md +3 -3
  90. package/.agents/skills/synthetic-test-data-generator/examples/idempotent_data_recipe.json +9 -0
  91. package/.agents/skills/systematic-debugging/SKILL.md +100 -0
  92. package/.agents/skills/systematic-debugging/examples/four_phase_case.md +44 -0
  93. package/.agents/skills/test-driven-development/SKILL.md +114 -0
  94. package/.agents/skills/test-driven-development/examples/red_green_refactor_qa.md +78 -0
  95. package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
  96. package/.agents/skills/token-and-context-optimizer/examples/distilled_log_sample.txt +3 -0
  97. package/.agents/skills/verification-before-completion/SKILL.md +100 -0
  98. package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +46 -0
  99. package/.agents/skills/visual-regression-sentinel/SKILL.md +21 -1
  100. package/.agents/skills/visual-regression-sentinel/examples/visual_mask_rules.json +9 -0
  101. package/.agents/skills/wcag-audit-patterns/SKILL.md +3 -3
  102. package/.agents/skills/wcag-audit-patterns/examples/wcag_compliance_checklist.json +10 -0
  103. package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -541
  104. package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
  105. package/.agents/skills/web-vitals-and-performance-profiler/examples/cwv_budget_policy.json +6 -0
  106. package/.agents/templates/completion_report.md +57 -57
  107. package/.agents/workflows/analyze_requirement_document.md +18 -0
  108. package/.agents/workflows/export_testcases_excel.md +32 -0
  109. package/.agents/workflows/generate_combinatorial_test_data.md +28 -0
  110. package/.agents/workflows/generate_cross_module_test_plan.md +37 -0
  111. package/.agents/workflows/generate_manual_testcases_rbt.md +56 -0
  112. package/.agents/workflows/generate_requirements_from_website.md +25 -0
  113. package/.agents/workflows/generate_testcases_from_requirements.md +21 -0
  114. package/.agents/workflows/graphify.md +10 -10
  115. package/.agents/workflows/verify.md +2 -2
  116. package/.cursor/rules/language.mdc +7 -7
  117. package/.cursor/rules/synapse-qa.mdc +23 -0
  118. package/.cursorrules +7 -0
  119. package/.pre-commit-hooks.yaml +14 -0
  120. package/.windsurfrules +7 -0
  121. package/AGENTS.md +54 -15
  122. package/CLAUDE.md +35 -12
  123. package/LICENSE +30 -0
  124. package/NOTICE +180 -0
  125. package/README.en.md +331 -0
  126. package/README.md +252 -515
  127. package/action.yml +51 -0
  128. package/benchmarks/qa_eval/README.md +71 -0
  129. package/benchmarks/qa_eval/build_dataset.py +677 -0
  130. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +22 -0
  131. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +22 -0
  132. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +22 -0
  133. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +22 -0
  134. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +22 -0
  135. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +21 -0
  136. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +21 -0
  137. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +21 -0
  138. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +21 -0
  139. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +21 -0
  140. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +23 -0
  141. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +23 -0
  142. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +23 -0
  143. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +23 -0
  144. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +23 -0
  145. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +22 -0
  146. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +22 -0
  147. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +22 -0
  148. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +22 -0
  149. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +23 -0
  150. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +22 -0
  151. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +22 -0
  152. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +22 -0
  153. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +22 -0
  154. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +22 -0
  155. package/benchmarks/qa_eval/eval_runner.py +418 -0
  156. package/benchmarks/qa_eval/reports/AB_METHODOLOGY_REPORT.md +83 -0
  157. package/benchmarks/qa_eval/reports/BENCHMARK_REPORT.md +69 -0
  158. package/benchmarks/qa_eval/reports/eval_results.json +105 -0
  159. package/bin/agent-qa.js +16 -4
  160. package/docs/ARCHITECTURE.md +107 -0
  161. package/docs/CI_CD_INTEGRATION.md +94 -0
  162. package/docs/CLI_REFERENCE.md +157 -0
  163. package/docs/CONTRIBUTING.md +91 -0
  164. package/docs/PLUGIN_DEVELOPMENT_GUIDE.md +126 -0
  165. package/docs/TOKEN_QUOTA_PLAYBOOK.md +41 -0
  166. package/docs/adopted-ecosystem-tools.md +87 -0
  167. package/docs/schemas/synapse.config.schema.json +64 -0
  168. package/examples/cypress-ecommerce-suite/README.md +45 -0
  169. package/examples/cypress-ecommerce-suite/cypress/e2e/checkout.cy.js +17 -0
  170. package/examples/cypress-ecommerce-suite/cypress/pages/BasePage.js +29 -0
  171. package/examples/cypress-ecommerce-suite/cypress/pages/CartPage.js +51 -0
  172. package/examples/cypress-ecommerce-suite/cypress.config.js +13 -0
  173. package/examples/cypress-ecommerce-suite/package.json +13 -0
  174. package/examples/katalon-suite/Keywords/pages/BasePage.groovy +19 -0
  175. package/examples/katalon-suite/Keywords/pages/DashboardPage.groovy +29 -0
  176. package/examples/katalon-suite/Keywords/pages/LoginPage.groovy +45 -0
  177. package/examples/katalon-suite/Profiles/default.glbl +12 -0
  178. package/examples/katalon-suite/README.md +18 -0
  179. package/examples/katalon-suite/Scripts/Auth/TC_LoginSuccess/Script1234567890.groovy +12 -0
  180. package/examples/katalon-suite/Scripts/Dashboard/TC_ViewSummary/Script1234567891.groovy +15 -0
  181. package/examples/katalon-suite/katalon_suite.prj +21 -0
  182. package/examples/playwright-crm-suite/README.md +40 -0
  183. package/examples/playwright-crm-suite/package.json +14 -0
  184. package/examples/playwright-crm-suite/pages/BasePage.ts +37 -0
  185. package/examples/playwright-crm-suite/pages/CustomerPage.ts +57 -0
  186. package/examples/playwright-crm-suite/pages/LoginPage.ts +32 -0
  187. package/examples/playwright-crm-suite/playwright.config.ts +27 -0
  188. package/examples/playwright-crm-suite/tests/auth.spec.ts +22 -0
  189. package/examples/playwright-crm-suite/tests/customers.spec.ts +33 -0
  190. package/examples/plugins/sample-custom-plugin/README.md +14 -0
  191. package/examples/plugins/sample-custom-plugin/plugin.json +16 -0
  192. package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/SKILL.md +29 -0
  193. package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/examples/audit_fixture.json +8 -0
  194. package/examples/plugins/sample-custom-plugin/tools/custom_security_headers.py +125 -0
  195. package/examples/practices/README.md +22 -0
  196. package/examples/practices/requirements/crm/requirements_contracts.md +292 -0
  197. package/examples/practices/requirements/crm/requirements_customers.md +462 -0
  198. package/examples/practices/requirements/crm/requirements_login.md +202 -0
  199. package/examples/practices/requirements/crm/requirements_projects.md +396 -0
  200. package/examples/practices/requirements/crm/requirements_tasks.md +343 -0
  201. package/examples/practices/requirements/ecommerce/requirements_category_brand.txt +402 -0
  202. package/examples/practices/requirements/jira/KAN-5/KAN-1/2026-04-03_04-28-27.png +0 -0
  203. package/examples/practices/requirements/jira/KAN-5/KAN-1/KAN-1_requirement.md +52 -0
  204. package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-19.png +0 -0
  205. package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-57.png +0 -0
  206. package/examples/practices/requirements/jira/KAN-5/KAN-4/KAN-4_requirement.md +119 -0
  207. package/examples/practices/requirements/jira/KAN-5/KAN-5_overview.md +9 -0
  208. package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-08.png +0 -0
  209. package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-23.png +0 -0
  210. package/examples/practices/requirements/jira/KAN-5/KAN-6/KAN-6_requirement.md +32 -0
  211. package/examples/practices/testcases/api/book_management_api_test_cases.md +144 -0
  212. package/examples/practices/testcases/api/user_management_api_test_cases.md +139 -0
  213. package/examples/practices/testcases/crm/test_cases_crm_login.md +148 -0
  214. package/examples/practices/testcases/crm/test_cases_customers.md +156 -0
  215. package/examples/pytest-api-suite/README.md +45 -0
  216. package/examples/pytest-api-suite/conftest.py +31 -0
  217. package/examples/pytest-api-suite/pages/user_api_client.py +36 -0
  218. package/examples/pytest-api-suite/requirements.txt +2 -0
  219. package/examples/pytest-api-suite/tests/test_user_api.py +44 -0
  220. package/examples/selenium-suite/README.md +18 -0
  221. package/examples/selenium-suite/pages/base_page.py +27 -0
  222. package/examples/selenium-suite/pages/login_page.py +29 -0
  223. package/examples/selenium-suite/pages/order_page.py +25 -0
  224. package/examples/selenium-suite/pytest.ini +6 -0
  225. package/examples/selenium-suite/requirements.txt +2 -0
  226. package/examples/selenium-suite/tests/test_login.py +10 -0
  227. package/examples/selenium-suite/tests/test_order.py +10 -0
  228. package/package.json +27 -12
  229. package/scripts/generate_skill_examples.py +244 -0
  230. package/scripts/install.ps1 +112 -0
  231. package/scripts/install_global_plugin.ps1 +94 -0
  232. package/scripts/install_global_plugin.sh +49 -0
  233. package/scripts/lint_skills.ps1 +57 -0
  234. package/scripts/package_dist.py +323 -0
  235. package/scripts/setup.sh +59 -0
  236. package/scripts/update_hub.ps1 +19 -0
  237. package/scripts/update_hub.sh +15 -0
  238. package/skills_manifest.json +209 -6
  239. package/synapse.config.json +30 -0
  240. package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +7 -7
  241. package/templates/PROJECT_STATE_TEMPLATE.md +1 -1
  242. package/templates/ai_bridges/CLAUDE.md +33 -16
  243. package/templates/ai_bridges/copilot-instructions.md +14 -21
  244. package/templates/ai_bridges/cursor_synapse_qa.mdc +17 -20
  245. package/templates/ai_bridges/windsurfrules +21 -21
  246. package/templates/ci/github-actions-synapse.yml +71 -4
  247. package/templates/welcome.html.tmpl +1674 -0
  248. package/tools/.infer_baseline.json +1102 -0
  249. package/tools/TOOLS_RUNBOOK.md +98 -131
  250. package/tools/__init__.py +26 -0
  251. package/tools/agent_canary.py +501 -501
  252. package/tools/analyze_project.py +31 -17
  253. package/tools/api_fuzzer.py +278 -6
  254. package/tools/author_integrity.py +6 -6
  255. package/tools/auto_heal.py +1 -1
  256. package/tools/browser_helpers.js +105 -7
  257. package/tools/bug_reporter.py +31 -1
  258. package/tools/bump_version.py +30 -3
  259. package/tools/cache_resolver.py +248 -14
  260. package/tools/check_env.py +55 -14
  261. package/tools/check_version_sync.py +171 -0
  262. package/tools/ci_healing_hook.py +1 -1
  263. package/tools/cli.py +454 -42
  264. package/tools/count_tokens.py +1257 -0
  265. package/tools/dashboard_generator.py +2638 -0
  266. package/tools/db_sandbox.py +198 -20
  267. package/tools/e2e_flow_prober.ps1 +12 -64
  268. package/tools/e2e_flow_prober.py +99 -0
  269. package/tools/e2e_flow_prober.sh +4 -0
  270. package/tools/flaky_detector.py +4 -3
  271. package/tools/generate_allure_report.ps1 +12 -29
  272. package/tools/generate_allure_report.py +77 -0
  273. package/tools/generate_allure_report.sh +4 -0
  274. package/tools/generate_jmx_plan.py +257 -58
  275. package/tools/generate_test_reports.ps1 +12 -134
  276. package/tools/generate_test_reports.py +133 -0
  277. package/tools/generate_test_reports.sh +4 -0
  278. package/tools/gherkin_suite.py +758 -0
  279. package/tools/git_pre_commit_guard.py +12 -6
  280. package/tools/goldfish_review.py +366 -0
  281. package/tools/heal_pr_automator.py +1 -1
  282. package/tools/heuristic_locator_matcher.py +170 -21
  283. package/tools/infer_linter.py +612 -70
  284. package/tools/lint_ai_docs.py +64 -0
  285. package/tools/matrix_sharder.py +1 -1
  286. package/tools/mcp_excel_server.ps1 +12 -250
  287. package/tools/mcp_excel_server.py +249 -0
  288. package/tools/mcp_excel_server.sh +4 -0
  289. package/tools/mcp_server.py +368 -144
  290. package/tools/md_to_xlsx.py +307 -0
  291. package/tools/micro_mutator.py +302 -69
  292. package/tools/omni_transpiler.py +2 -2
  293. package/tools/output_formatter.py +128 -0
  294. package/tools/pinpoint_failure.py +88 -88
  295. package/tools/project_state.py +2 -2
  296. package/tools/repo_map.py +1 -1
  297. package/tools/safety_firewall.py +196 -45
  298. package/tools/setup_project.py +295 -31
  299. package/tools/skillspector.py +31 -0
  300. package/tools/spec_drift_sentinel.py +439 -0
  301. package/tools/sync_agent_rules.py +248 -0
  302. package/tools/telemetry_correlator.py +45 -0
  303. package/tools/test_impact.py +51 -12
  304. package/tools/tms_sync_bridge.py +21 -5
  305. package/tools/universal_ir.py +3 -0
  306. package/tools/update_sentinel.py +87 -15
  307. package/tools/verify_all.ps1 +13 -0
  308. package/tools/verify_all.py +479 -85
  309. package/tools/visual_diff_canvas.py +1 -0
  310. package/tools/visual_som_helper.py +1 -1
  311. package/tools/watch_mode.py +305 -0
  312. package/tools/welcome_page.py +291 -0
@@ -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("&#xa;", "\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("&#xa;", "\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()
@@ -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
- - 7 Quality Gates: `READY`
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
- - 7 Quality Gates: `{current_gate}`
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 / max(1, total_raw_bytes))) * 100
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)
@@ -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"\bcheckout\b",
39
- r"\bterminate\b",
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"\breset\b",
42
- r"\btransfer\b",
43
- r"\bformat\b",
44
- # Multilingual & Vietnamese destructive keywords
45
- r"x[oóòỏõọ][aáàảãạ]",
46
- r"x[oóòỏõọ]a\s+t[aáàảãạ]t\s+c[aảáàãạ]",
47
- r"h[uủúùũụ]y",
48
- r"thanh\s+to[aáàảãạ]n",
49
- r"r[uúùủũụ]t\s+ti[eềéèẻẽẹ]n",
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"\[aria-label\*=['\"]?delete['\"]?\]",
59
- r"button[#\.\-_a-zA-Z0-9]*[_\-]*(?:del|delete|purge|remove|wipe|drop)",
60
- r":(?:has-text|text)\s*\(\s*['\"][^'\"]*(?:delete|remove|wipe|purge|pay|x[oóòỏõọ][aáàảãạ]|h[uủúùũụ]y|thanh\s*to[aáàảãạ]n)[^'\"]*['\"]\s*\)",
61
- r"text=['\"][^'\"]*(?:delete|remove|wipe|purge|pay|x[oóòỏõọ][aáàảãạ]|h[uủúùũụ]y|thanh\s*to[aáàảãạ]n)",
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>[^'"]+)['"](?:\s*,\s*['"](?P<text>[^'"]*)['"])?""", re.IGNORECASE),
71
- # page.locator('selector').click()
72
- re.compile(r"""(?:page|frame)\.locator\s*\(\s*['"](?P<sel>[^'"]+)['"]\s*\)\.(?P<act>click|fill|press)""", re.IGNORECASE),
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>[^'"]+)['"]\s*\)\.(?P<act>click)""", re.IGNORECASE),
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
- # Check text content
91
- if text_clean and COMPILED_TEXT_REGEX.search(text_clean):
92
- match = COMPILED_TEXT_REGEX.search(text_clean).group(0)
93
- return True, f"Element text contains dangerous keyword: '{match}'"
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 COMPILED_SEL_REGEX.search(sel_clean):
98
- return True, f"Element selector matches destructive pattern: '{sel_clean}'"
99
- if COMPILED_TEXT_REGEX.search(sel_clean):
100
- match = COMPILED_TEXT_REGEX.search(sel_clean).group(0)
101
- return True, f"Element selector contains dangerous keyword: '{match}' in '{sel_clean}'"
102
-
103
- # Specific dangerous action types
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 COMPILED_TEXT_REGEX.search(stripped) and any(w in stripped.lower() for w in ("click", "press", "submit", "post", "delete")):
157
- auth = inspect_and_authorize("dispatch", text=stripped, selector="", force=force)
158
- auth["line"] = line_idx
159
- auth["code"] = stripped
160
- inspections.append(auth)
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 != "-":