synapse-hub 2.2.3 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/failure-analyst.md +11 -0
- package/.agents/agents/live-explorer.md +28 -0
- package/.agents/agents/orchestrator.md +20 -4
- package/.agents/agents/pom-crafter.md +12 -0
- package/.agents/agents/quality-gatekeeper.md +19 -4
- package/.agents/agents/test-runner.md +11 -0
- package/.agents/rules/communication-style.md +40 -0
- package/.agents/rules/context-engineering.md +23 -0
- package/.agents/rules/git-push-protocol.md +11 -17
- package/.agents/rules/graphify.md +14 -14
- package/.agents/rules/lessons-learned.md +3 -3
- package/.agents/rules/mandatory-playwright-live.md +29 -29
- package/.agents/rules/mobile_appium_rules.md +132 -0
- package/.agents/rules/performance_testing_rules.md +74 -0
- package/.agents/rules/skill-precedence.md +1 -1
- package/.agents/rules/systematic-debugging.md +6 -6
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +64 -215
- package/.agents/skills/agent-orchestration-multi-agent-optimize/examples/swarm_workload_config.json +12 -0
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +3 -3
- package/.agents/skills/antigravity-test-sentinel/examples/dod_gate_policy.json +11 -0
- package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
- package/.agents/skills/api-network-mocking-interception/examples/network_route_stub.json +9 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +9 -9
- package/.agents/skills/api-spec-to-e2e-matrix/examples/sample_openapi.json +19 -0
- package/.agents/skills/architecture-decision-records/SKILL.md +35 -412
- package/.agents/skills/architecture-decision-records/examples/0001-record-architecture-decisions.md +12 -0
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +4 -1
- package/.agents/skills/auth-session-and-cookie-manager/examples/storage_state_sample.json +11 -0
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +80 -186
- package/.agents/skills/backend-load-and-stress-patterns/examples/k6_load_test_sample.js +13 -0
- package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/bats-testing-patterns/examples/cli_runner_test.bats +6 -0
- package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -614
- package/.agents/skills/clean-pom-crafter/SKILL.md +21 -3
- package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -28
- package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -138
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +2 -2
- package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_page_sample.groovy +9 -0
- package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +9 -7
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +5 -5
- package/.agents/skills/db-state-verifier-and-seeder/examples/db_fixture_seed.json +12 -0
- package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/e2e-testing-patterns/examples/playwright_sharded_config.ts +12 -0
- package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -522
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +17 -3
- package/.agents/skills/failure-autopsy-and-healing/examples/sample_junit_failure.xml +8 -0
- package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -64
- package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -56
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -98
- package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
- package/.agents/skills/file-and-report-validator/examples/sample_export_schema.json +14 -0
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +3 -3
- package/.agents/skills/flaky-test-sentinel-and-quarantine/examples/quarantine_registry.json +11 -0
- package/.agents/skills/gherkin-to-suite/SKILL.md +103 -0
- package/.agents/skills/gherkin-to-suite/examples/checkout.feature +39 -0
- package/.agents/skills/gherkin-to-suite/examples/suite_report_sample.json +334 -0
- package/.agents/skills/github-actions-templates/SKILL.md +47 -304
- package/.agents/skills/github-actions-templates/examples/matrix_test_pipeline.yml +8 -0
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +6 -6
- package/.agents/skills/graphify-impact-analyzer/examples/dependency_blast_radius.json +8 -0
- package/.agents/skills/katalon-cli-runner/SKILL.md +2 -2
- package/.agents/skills/katalon-cli-runner/examples/headless_kre_exec.sh +3 -0
- package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -209
- package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -155
- package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -67
- package/.agents/skills/lam-script-generator/SKILL.md +42 -1
- package/.agents/skills/lam-script-generator/examples/user_story_sample.md +8 -0
- package/.agents/skills/lean-code-ladder/SKILL.md +83 -0
- package/.agents/skills/lean-code-ladder/examples/ladder_walkthrough.md +37 -0
- package/.agents/skills/meta-prompt-crafter/SKILL.md +107 -0
- package/.agents/skills/meta-prompt-crafter/examples/qa_prompt_template.md +35 -0
- package/.agents/skills/persistent-planning-files/SKILL.md +108 -0
- package/.agents/skills/persistent-planning-files/examples/task_plan_example.md +56 -0
- package/.agents/skills/playwright-live-interaction/SKILL.md +49 -1
- package/.agents/skills/playwright-live-interaction/examples/live_probe_script.js +5 -0
- package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -160
- package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -75
- package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -388
- package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -134
- package/.agents/skills/playwright-webapp-testing/SKILL.md +93 -0
- package/.agents/skills/playwright-webapp-testing/examples/server_lifecycle_playwright.py +63 -0
- package/.agents/skills/rbt-manual-testing/SKILL.md +150 -0
- package/.agents/skills/rbt-manual-testing/examples/checkout_rbt_manual_suite.md +25 -0
- package/.agents/skills/security-dast-sentinel/SKILL.md +57 -135
- package/.agents/skills/security-dast-sentinel/examples/dast_scan_config.json +10 -0
- package/.agents/skills/self-healing-locator/SKILL.md +24 -10
- package/.agents/skills/self-healing-locator/examples/aom_diff_sample.json +16 -0
- package/.agents/skills/skillspector-security-guard/SKILL.md +32 -90
- package/.agents/skills/skillspector-security-guard/examples/safe_mcp_config.json +12 -0
- package/.agents/skills/surgical-coding-principles/SKILL.md +89 -0
- package/.agents/skills/surgical-coding-principles/examples/surgical_vs_scope_creep.md +46 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +3 -3
- package/.agents/skills/synthetic-test-data-generator/examples/idempotent_data_recipe.json +9 -0
- package/.agents/skills/systematic-debugging/SKILL.md +100 -0
- package/.agents/skills/systematic-debugging/examples/four_phase_case.md +44 -0
- package/.agents/skills/test-driven-development/SKILL.md +114 -0
- package/.agents/skills/test-driven-development/examples/red_green_refactor_qa.md +78 -0
- package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
- package/.agents/skills/token-and-context-optimizer/examples/distilled_log_sample.txt +3 -0
- package/.agents/skills/verification-before-completion/SKILL.md +100 -0
- package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +46 -0
- package/.agents/skills/visual-regression-sentinel/SKILL.md +21 -1
- package/.agents/skills/visual-regression-sentinel/examples/visual_mask_rules.json +9 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +3 -3
- package/.agents/skills/wcag-audit-patterns/examples/wcag_compliance_checklist.json +10 -0
- package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -541
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
- package/.agents/skills/web-vitals-and-performance-profiler/examples/cwv_budget_policy.json +6 -0
- package/.agents/templates/completion_report.md +57 -57
- package/.agents/workflows/analyze_requirement_document.md +18 -0
- package/.agents/workflows/export_testcases_excel.md +32 -0
- package/.agents/workflows/generate_combinatorial_test_data.md +28 -0
- package/.agents/workflows/generate_cross_module_test_plan.md +37 -0
- package/.agents/workflows/generate_manual_testcases_rbt.md +56 -0
- package/.agents/workflows/generate_requirements_from_website.md +25 -0
- package/.agents/workflows/generate_testcases_from_requirements.md +21 -0
- package/.agents/workflows/graphify.md +10 -10
- package/.agents/workflows/verify.md +2 -2
- package/.cursor/rules/language.mdc +7 -7
- package/.cursor/rules/synapse-qa.mdc +23 -0
- package/.cursorrules +7 -0
- package/.pre-commit-hooks.yaml +14 -0
- package/.windsurfrules +7 -0
- package/AGENTS.md +54 -15
- package/CLAUDE.md +35 -12
- package/LICENSE +30 -0
- package/NOTICE +180 -0
- package/README.en.md +325 -0
- package/README.md +247 -512
- package/action.yml +51 -0
- package/benchmarks/qa_eval/README.md +71 -0
- package/benchmarks/qa_eval/build_dataset.py +677 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +23 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/eval_runner.py +418 -0
- package/benchmarks/qa_eval/reports/AB_METHODOLOGY_REPORT.md +83 -0
- package/benchmarks/qa_eval/reports/BENCHMARK_REPORT.md +69 -0
- package/benchmarks/qa_eval/reports/eval_results.json +105 -0
- package/bin/agent-qa.js +10 -3
- package/docs/ARCHITECTURE.md +107 -0
- package/docs/CI_CD_INTEGRATION.md +94 -0
- package/docs/CLI_REFERENCE.md +157 -0
- package/docs/CONTRIBUTING.md +91 -0
- package/docs/PLUGIN_DEVELOPMENT_GUIDE.md +126 -0
- package/docs/TOKEN_QUOTA_PLAYBOOK.md +41 -0
- package/docs/adopted-ecosystem-tools.md +87 -0
- package/docs/schemas/synapse.config.schema.json +64 -0
- package/examples/cypress-ecommerce-suite/README.md +45 -0
- package/examples/cypress-ecommerce-suite/cypress/e2e/checkout.cy.js +17 -0
- package/examples/cypress-ecommerce-suite/cypress/pages/BasePage.js +29 -0
- package/examples/cypress-ecommerce-suite/cypress/pages/CartPage.js +51 -0
- package/examples/cypress-ecommerce-suite/cypress.config.js +13 -0
- package/examples/cypress-ecommerce-suite/package.json +13 -0
- package/examples/katalon-suite/Keywords/pages/BasePage.groovy +19 -0
- package/examples/katalon-suite/Keywords/pages/DashboardPage.groovy +29 -0
- package/examples/katalon-suite/Keywords/pages/LoginPage.groovy +45 -0
- package/examples/katalon-suite/Profiles/default.glbl +12 -0
- package/examples/katalon-suite/README.md +18 -0
- package/examples/katalon-suite/Scripts/Auth/TC_LoginSuccess/Script1234567890.groovy +12 -0
- package/examples/katalon-suite/Scripts/Dashboard/TC_ViewSummary/Script1234567891.groovy +15 -0
- package/examples/katalon-suite/katalon_suite.prj +21 -0
- package/examples/playwright-crm-suite/README.md +40 -0
- package/examples/playwright-crm-suite/package.json +14 -0
- package/examples/playwright-crm-suite/pages/BasePage.ts +37 -0
- package/examples/playwright-crm-suite/pages/CustomerPage.ts +57 -0
- package/examples/playwright-crm-suite/pages/LoginPage.ts +32 -0
- package/examples/playwright-crm-suite/playwright.config.ts +27 -0
- package/examples/playwright-crm-suite/tests/auth.spec.ts +22 -0
- package/examples/playwright-crm-suite/tests/customers.spec.ts +33 -0
- package/examples/plugins/sample-custom-plugin/README.md +14 -0
- package/examples/plugins/sample-custom-plugin/plugin.json +16 -0
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/SKILL.md +29 -0
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/examples/audit_fixture.json +8 -0
- package/examples/plugins/sample-custom-plugin/tools/custom_security_headers.py +125 -0
- package/examples/practices/README.md +22 -0
- package/examples/practices/requirements/crm/requirements_contracts.md +292 -0
- package/examples/practices/requirements/crm/requirements_customers.md +462 -0
- package/examples/practices/requirements/crm/requirements_login.md +202 -0
- package/examples/practices/requirements/crm/requirements_projects.md +396 -0
- package/examples/practices/requirements/crm/requirements_tasks.md +343 -0
- package/examples/practices/requirements/ecommerce/requirements_category_brand.txt +402 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/2026-04-03_04-28-27.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/KAN-1_requirement.md +52 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-19.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-57.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/KAN-4_requirement.md +119 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-5_overview.md +9 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-08.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-23.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/KAN-6_requirement.md +32 -0
- package/examples/practices/testcases/api/book_management_api_test_cases.md +144 -0
- package/examples/practices/testcases/api/user_management_api_test_cases.md +139 -0
- package/examples/practices/testcases/crm/test_cases_crm_login.md +148 -0
- package/examples/practices/testcases/crm/test_cases_customers.md +156 -0
- package/examples/pytest-api-suite/README.md +45 -0
- package/examples/pytest-api-suite/conftest.py +31 -0
- package/examples/pytest-api-suite/pages/user_api_client.py +36 -0
- package/examples/pytest-api-suite/requirements.txt +2 -0
- package/examples/pytest-api-suite/tests/test_user_api.py +44 -0
- package/examples/selenium-suite/README.md +18 -0
- package/examples/selenium-suite/pages/base_page.py +27 -0
- package/examples/selenium-suite/pages/login_page.py +29 -0
- package/examples/selenium-suite/pages/order_page.py +25 -0
- package/examples/selenium-suite/pytest.ini +6 -0
- package/examples/selenium-suite/requirements.txt +2 -0
- package/examples/selenium-suite/tests/test_login.py +10 -0
- package/examples/selenium-suite/tests/test_order.py +10 -0
- package/package.json +22 -7
- package/scripts/generate_skill_examples.py +244 -0
- package/scripts/install.ps1 +112 -0
- package/scripts/install_global_plugin.ps1 +94 -0
- package/scripts/install_global_plugin.sh +49 -0
- package/scripts/lint_skills.ps1 +57 -0
- package/scripts/package_dist.py +323 -0
- package/scripts/setup.sh +59 -0
- package/scripts/update_hub.ps1 +19 -0
- package/scripts/update_hub.sh +15 -0
- package/skills_manifest.json +209 -6
- package/synapse.config.json +30 -0
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +7 -7
- package/templates/PROJECT_STATE_TEMPLATE.md +1 -1
- package/templates/ai_bridges/CLAUDE.md +33 -16
- package/templates/ai_bridges/copilot-instructions.md +14 -21
- package/templates/ai_bridges/cursor_synapse_qa.mdc +17 -20
- package/templates/ai_bridges/windsurfrules +21 -21
- package/templates/ci/github-actions-synapse.yml +71 -4
- package/templates/welcome.html.tmpl +1674 -0
- package/tools/.infer_baseline.json +1102 -0
- package/tools/TOOLS_RUNBOOK.md +92 -130
- package/tools/__init__.py +26 -0
- package/tools/agent_canary.py +501 -501
- package/tools/analyze_project.py +31 -17
- package/tools/api_fuzzer.py +278 -6
- package/tools/author_integrity.py +6 -6
- package/tools/auto_heal.py +1 -1
- package/tools/browser_helpers.js +105 -7
- package/tools/bug_reporter.py +31 -1
- package/tools/bump_version.py +23 -3
- package/tools/cache_resolver.py +248 -14
- package/tools/check_env.py +55 -14
- package/tools/check_version_sync.py +153 -0
- package/tools/ci_healing_hook.py +1 -1
- package/tools/cli.py +263 -13
- package/tools/count_tokens.py +1257 -0
- package/tools/dashboard_generator.py +2638 -0
- package/tools/db_sandbox.py +198 -20
- package/tools/e2e_flow_prober.ps1 +12 -64
- package/tools/e2e_flow_prober.py +99 -0
- package/tools/e2e_flow_prober.sh +4 -0
- package/tools/export_postman_collection.py +29 -7
- package/tools/flaky_detector.py +4 -3
- package/tools/generate_allure_report.ps1 +12 -29
- package/tools/generate_allure_report.py +77 -0
- package/tools/generate_allure_report.sh +4 -0
- package/tools/generate_jmx_plan.py +299 -68
- package/tools/generate_test_reports.ps1 +12 -134
- package/tools/generate_test_reports.py +133 -0
- package/tools/generate_test_reports.sh +4 -0
- package/tools/gherkin_suite.py +758 -0
- package/tools/git_pre_commit_guard.py +12 -6
- package/tools/goldfish_review.py +366 -0
- package/tools/heal_pr_automator.py +1 -1
- package/tools/heuristic_locator_matcher.py +170 -21
- package/tools/infer_linter.py +612 -70
- package/tools/jtl_analyzer.py +1 -1
- package/tools/lint_ai_docs.py +64 -0
- package/tools/matrix_sharder.py +1 -1
- package/tools/mcp_excel_server.ps1 +12 -250
- package/tools/mcp_excel_server.py +249 -0
- package/tools/mcp_excel_server.sh +4 -0
- package/tools/mcp_server.py +368 -144
- package/tools/md_to_xlsx.py +307 -0
- package/tools/micro_mutator.py +300 -69
- package/tools/omni_transpiler.py +2 -2
- package/tools/output_formatter.py +128 -0
- package/tools/pinpoint_failure.py +88 -88
- package/tools/project_state.py +2 -2
- package/tools/repo_map.py +1 -1
- package/tools/safety_firewall.py +197 -33
- package/tools/setup_project.py +294 -30
- package/tools/skillspector.py +8 -8
- package/tools/spec_drift_sentinel.py +439 -0
- package/tools/sync_agent_rules.py +248 -0
- package/tools/telemetry_correlator.py +45 -0
- package/tools/test_impact.py +51 -12
- package/tools/tms_sync_bridge.py +21 -5
- package/tools/universal_ir.py +3 -0
- package/tools/update_sentinel.py +87 -15
- package/tools/verify_all.ps1 +13 -0
- package/tools/verify_all.py +463 -79
- package/tools/visual_diff_canvas.py +1 -0
- package/tools/visual_som_helper.py +1 -1
- package/tools/watch_mode.py +305 -0
- package/tools/welcome_page.py +291 -0
package/tools/micro_mutator.py
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Evaluates the true defect-catching efficacy of
|
|
3
|
+
Assertion Sensitivity & Syntactic Micro-Mutation Sentinel (Heuristic)
|
|
4
|
+
---------------------------------------------------------------------
|
|
5
|
+
Evaluates the true defect-catching efficacy of test suites by injecting
|
|
6
6
|
syntactic micro-mutants (inverting operators, flipping booleans, altering boundaries)
|
|
7
7
|
to eradicate 'Tautological Tests' (tests that pass unconditionally without valid assertions).
|
|
8
8
|
|
|
9
9
|
Features:
|
|
10
|
-
- Atomic
|
|
11
|
-
- Static assertion sensitivity analysis (killing mutants when valid assertions
|
|
10
|
+
- Atomic syntactic fault injection across Groovy, Python, TypeScript, JavaScript
|
|
11
|
+
- Static assertion sensitivity analysis (killing mutants when valid assertions verify downstream logic)
|
|
12
12
|
- Dynamic test execution mode (--test-cmd) for live mutant verification
|
|
13
|
-
- Quality Gatekeeper
|
|
13
|
+
- Quality Gatekeeper threshold enforcement (--min-score, default: 80.0%)
|
|
14
|
+
|
|
15
|
+
Implementation Note:
|
|
16
|
+
Static evaluation is a lightweight, heuristic assertion-sensitivity analysis
|
|
17
|
+
(fast, polyglot, token-free), not a compiler-grade mutation testing tool like Stryker or mutmut.
|
|
18
|
+
It measures whether test logic is bounded by active downstream assertions rather than passing unconditionally.
|
|
14
19
|
"""
|
|
15
20
|
|
|
16
21
|
import os
|
|
17
22
|
import re
|
|
18
23
|
import sys
|
|
19
24
|
import json
|
|
25
|
+
import shlex
|
|
26
|
+
import html as html_lib
|
|
20
27
|
import argparse
|
|
21
28
|
import subprocess
|
|
22
29
|
import tempfile
|
|
@@ -57,7 +64,7 @@ MUTATION_RULES = [
|
|
|
57
64
|
|
|
58
65
|
# Action statement pattern for Statement Deletion Mutants
|
|
59
66
|
ACTION_STATEMENT_PATTERN = re.compile(
|
|
60
|
-
r'\.(click|submit|save|update|delete|send|fill|press|type|execute
|
|
67
|
+
r'\.(click|submit|save|update|delete|send|fill|press|type|execute)\s*\(',
|
|
61
68
|
re.IGNORECASE
|
|
62
69
|
)
|
|
63
70
|
|
|
@@ -99,38 +106,95 @@ def is_inside_quotes(line: str, pos: int) -> bool:
|
|
|
99
106
|
return in_single or in_double
|
|
100
107
|
|
|
101
108
|
|
|
109
|
+
def mask_non_executable_contexts(code: str) -> str:
|
|
110
|
+
"""Replaces multiline strings, docstrings, comments and string fixtures with blank lines
|
|
111
|
+
to preserve exact line numbering while eliminating false positives during static analysis."""
|
|
112
|
+
lines = code.splitlines(keepends=True)
|
|
113
|
+
in_ml = False
|
|
114
|
+
ml_char = ""
|
|
115
|
+
masked = []
|
|
116
|
+
triple_d = '"""'
|
|
117
|
+
triple_s = "'''"
|
|
118
|
+
|
|
119
|
+
for line in lines:
|
|
120
|
+
stripped = line.strip()
|
|
121
|
+
if in_ml:
|
|
122
|
+
if ml_char in stripped:
|
|
123
|
+
in_ml = False
|
|
124
|
+
ml_char = ""
|
|
125
|
+
masked.append("\n")
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
for q in (triple_d, triple_s):
|
|
129
|
+
if q in stripped and stripped.count(q) % 2 == 1:
|
|
130
|
+
in_ml = True
|
|
131
|
+
ml_char = q
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
if in_ml:
|
|
135
|
+
masked.append("\n")
|
|
136
|
+
else:
|
|
137
|
+
# Mask string fixtures containing catch blocks or test code samples
|
|
138
|
+
if "catch" in stripped and ('"' in stripped or "'" in stripped):
|
|
139
|
+
masked.append("\n")
|
|
140
|
+
else:
|
|
141
|
+
masked.append(line)
|
|
142
|
+
|
|
143
|
+
return "".join(masked)
|
|
144
|
+
|
|
145
|
+
|
|
102
146
|
def generate_mutants_for_code(code: str, file_path: str = "") -> List[Dict[str, Any]]:
|
|
103
147
|
"""
|
|
104
148
|
Generates single-point syntactic mutants from source code string.
|
|
105
149
|
Each mutant represents exactly one atomic fault injection.
|
|
106
|
-
Only generates mutants on active executable lines, ignoring comments &
|
|
150
|
+
Only generates mutants on active executable lines, ignoring comments, multiline strings & runners.
|
|
107
151
|
"""
|
|
108
152
|
lines = code.splitlines(keepends=True)
|
|
109
153
|
mutants = []
|
|
110
154
|
mutant_id = 1
|
|
111
|
-
|
|
155
|
+
in_multiline = False
|
|
156
|
+
ml_char = ""
|
|
157
|
+
triple_d = '"""'
|
|
158
|
+
triple_s = "'''"
|
|
112
159
|
|
|
113
160
|
for line_idx, line in enumerate(lines):
|
|
114
161
|
stripped = line.strip()
|
|
115
162
|
|
|
116
|
-
# Track
|
|
117
|
-
if
|
|
118
|
-
if
|
|
119
|
-
|
|
163
|
+
# Track multiline docstrings and string assignments
|
|
164
|
+
if in_multiline:
|
|
165
|
+
if ml_char in stripped:
|
|
166
|
+
in_multiline = False
|
|
167
|
+
ml_char = ""
|
|
120
168
|
continue
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
169
|
+
|
|
170
|
+
for q in (triple_d, triple_s):
|
|
171
|
+
if q in stripped and stripped.count(q) % 2 == 1:
|
|
172
|
+
in_multiline = True
|
|
173
|
+
ml_char = q
|
|
174
|
+
break
|
|
175
|
+
|
|
176
|
+
if in_multiline:
|
|
124
177
|
continue
|
|
125
178
|
|
|
126
179
|
# Skip pure comments or imports
|
|
127
180
|
if stripped.startswith(("//", "#", "/*", "*", "import", "package", "from")):
|
|
128
181
|
continue
|
|
129
182
|
|
|
183
|
+
# Skip test runner boilerplate, entry points, and file writing harness logic
|
|
184
|
+
if re.search(r"\b(if\s+__name__\s*==|unittest\.main\(\)|sys\.exit\(|\.write\()", stripped):
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
# Skip tuples/lists of mock code templates in data fixtures
|
|
188
|
+
if stripped.startswith("(") and ('"' in stripped or "'" in stripped):
|
|
189
|
+
continue
|
|
190
|
+
|
|
130
191
|
# Statement Deletion Mutant (Simulates omitted UI action or bypassed step)
|
|
131
192
|
if ACTION_STATEMENT_PATTERN.search(stripped) and not ASSERTION_PATTERN.search(stripped):
|
|
132
193
|
indent = line[:len(line) - len(line.lstrip())]
|
|
133
|
-
|
|
194
|
+
if file_path.endswith((".py", ".pyw")):
|
|
195
|
+
mutated_line = f"{indent}pass # MUTANT_DELETED: {stripped}\n"
|
|
196
|
+
else:
|
|
197
|
+
mutated_line = f"{indent}// MUTANT_DELETED: {stripped}\n"
|
|
134
198
|
mutated_lines = list(lines)
|
|
135
199
|
mutated_lines[line_idx] = mutated_line
|
|
136
200
|
mutants.append({
|
|
@@ -168,7 +232,7 @@ def generate_mutants_for_code(code: str, file_path: str = "") -> List[Dict[str,
|
|
|
168
232
|
"mutated_snippet": mutated_line.strip(),
|
|
169
233
|
"description": desc,
|
|
170
234
|
"mutated_code": mutated_code,
|
|
171
|
-
"status": "SURVIVED"
|
|
235
|
+
"status": "SURVIVED"
|
|
172
236
|
})
|
|
173
237
|
mutant_id += 1
|
|
174
238
|
|
|
@@ -206,21 +270,31 @@ def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tup
|
|
|
206
270
|
has_empty_catch = bool(EMPTY_CATCH_PATTERN.search(code))
|
|
207
271
|
|
|
208
272
|
# Identify lines containing assertions
|
|
209
|
-
|
|
273
|
+
assertion_entries = []
|
|
210
274
|
for idx, l in enumerate(lines, start=1):
|
|
211
275
|
s = l.strip()
|
|
212
276
|
if s.startswith(("//", "#", "/*")):
|
|
213
277
|
continue
|
|
214
278
|
if ASSERTION_PATTERN.search(s) and not TAUTOLOGY_PATTERN.search(s):
|
|
215
|
-
|
|
279
|
+
assertion_entries.append((idx, s))
|
|
216
280
|
|
|
217
281
|
# Zero valid assertions across code -> all mutants survive (Fake Pass!)
|
|
218
|
-
if not
|
|
282
|
+
if not assertion_entries:
|
|
219
283
|
for m in mutants:
|
|
220
284
|
m["status"] = "SURVIVED"
|
|
221
285
|
m["reason"] = "No valid assertions found in test script (Fake-Pass Trap)"
|
|
222
286
|
return mutants, 0.0
|
|
223
287
|
|
|
288
|
+
COMMON_KEYWORDS = {
|
|
289
|
+
"if", "else", "def", "return", "for", "in", "and", "or", "not", "val", "var",
|
|
290
|
+
"let", "const", "True", "False", "None", "null", "int", "str", "float", "bool",
|
|
291
|
+
"function", "class", "this", "self"
|
|
292
|
+
}
|
|
293
|
+
DOMAIN_TEST_NOISE = {
|
|
294
|
+
"page", "await", "expect", "browser", "driver", "window", "document",
|
|
295
|
+
"locator", "WebUI", "findTestObject", "cy", "playwright"
|
|
296
|
+
}
|
|
297
|
+
|
|
224
298
|
killed_count = 0
|
|
225
299
|
for m in mutants:
|
|
226
300
|
m_line = m["line"]
|
|
@@ -233,24 +307,41 @@ def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tup
|
|
|
233
307
|
killed_count += 1
|
|
234
308
|
continue
|
|
235
309
|
|
|
236
|
-
# Case 2: Mutation in logic followed by downstream assertions
|
|
237
|
-
|
|
238
|
-
if
|
|
239
|
-
|
|
240
|
-
|
|
310
|
+
# Case 2: Mutation in logic followed by downstream assertions with variable dataflow linkage
|
|
311
|
+
tokens = (set(re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', snippet)) - COMMON_KEYWORDS - DOMAIN_TEST_NOISE)
|
|
312
|
+
tokens = {t for t in tokens if len(t) > 1}
|
|
313
|
+
downstream = [
|
|
314
|
+
(a_line, a_text) for a_line, a_text in assertion_entries
|
|
315
|
+
if a_line >= m_line and a_line - m_line <= 30
|
|
316
|
+
]
|
|
317
|
+
if downstream and not has_empty_catch and tokens:
|
|
318
|
+
# 1-hop dataflow propagation across intermediate lines between m_line and downstream assertions
|
|
319
|
+
max_line = min(len(lines), m_line + 30)
|
|
320
|
+
dataflow_tokens = set(tokens)
|
|
321
|
+
for idx in range(m_line, max_line):
|
|
322
|
+
l_code = lines[idx]
|
|
323
|
+
asgn = re.match(r'^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*=', l_code)
|
|
324
|
+
if asgn:
|
|
325
|
+
tgt = asgn.group(1)
|
|
326
|
+
if any(re.search(rf'\b{re.escape(t)}\b', l_code) for t in dataflow_tokens):
|
|
327
|
+
dataflow_tokens.add(tgt)
|
|
328
|
+
|
|
329
|
+
# Check if any assertion references the variables mutated in this logic branch or downstream dataflow
|
|
330
|
+
has_variable_link = any(any(re.search(rf'\b{re.escape(tok)}\b', a_text) for tok in dataflow_tokens) for _, a_text in downstream)
|
|
331
|
+
if has_variable_link:
|
|
241
332
|
m["status"] = "KILLED"
|
|
242
|
-
m["reason"] = f"Logic mutant caught by downstream assertion
|
|
333
|
+
m["reason"] = f"Logic mutant caught by downstream assertion referencing mutated state (line {downstream[0][0]})"
|
|
243
334
|
killed_count += 1
|
|
244
335
|
continue
|
|
245
336
|
|
|
246
337
|
m["status"] = "SURVIVED"
|
|
247
|
-
m["reason"] = "Mutant survived: no assertion
|
|
338
|
+
m["reason"] = "Mutant survived: no downstream assertion validates the mutated variables"
|
|
248
339
|
|
|
249
340
|
score = evaluate_mutation_score(killed_count, len(mutants))
|
|
250
341
|
return mutants, score
|
|
251
342
|
|
|
252
343
|
|
|
253
|
-
def evaluate_mutants_dynamically(file_path: str, code: str, mutants: List[Dict[str, Any]], test_cmd: str) -> Tuple[List[Dict[str, Any]], float]:
|
|
344
|
+
def evaluate_mutants_dynamically(file_path: str, code: str, mutants: List[Dict[str, Any]], test_cmd: str, timeout: float = 60.0) -> Tuple[List[Dict[str, Any]], float]:
|
|
254
345
|
"""
|
|
255
346
|
Executes live mutation testing by injecting mutants into disk file and executing test_cmd.
|
|
256
347
|
If the test command fails (exit code != 0), the mutant is KILLED by the test suite.
|
|
@@ -260,21 +351,64 @@ def evaluate_mutants_dynamically(file_path: str, code: str, mutants: List[Dict[s
|
|
|
260
351
|
if not mutants:
|
|
261
352
|
return mutants, 0.0
|
|
262
353
|
|
|
354
|
+
cmd_args = shlex.split(test_cmd, posix=sys.platform != "win32") if isinstance(test_cmd, str) else test_cmd
|
|
355
|
+
|
|
356
|
+
# F15: Baseline execution check - test suite must be GREEN on pristine code
|
|
357
|
+
try:
|
|
358
|
+
baseline_proc = subprocess.run(
|
|
359
|
+
cmd_args,
|
|
360
|
+
capture_output=True,
|
|
361
|
+
timeout=int(timeout),
|
|
362
|
+
text=True
|
|
363
|
+
)
|
|
364
|
+
if baseline_proc.returncode != 0:
|
|
365
|
+
for m in mutants:
|
|
366
|
+
m["status"] = "SURVIVED"
|
|
367
|
+
m["reason"] = f"Baseline test run failed (exit code {baseline_proc.returncode}). Tests must pass on clean code before mutation."
|
|
368
|
+
return mutants, 0.0
|
|
369
|
+
except subprocess.TimeoutExpired:
|
|
370
|
+
for m in mutants:
|
|
371
|
+
m["status"] = "SURVIVED"
|
|
372
|
+
m["reason"] = f"Baseline execution error: timed out after {int(timeout)} seconds"
|
|
373
|
+
return mutants, 0.0
|
|
374
|
+
except Exception as e:
|
|
375
|
+
for m in mutants:
|
|
376
|
+
m["status"] = "SURVIVED"
|
|
377
|
+
m["reason"] = f"Baseline execution error: {e}"
|
|
378
|
+
return mutants, 0.0
|
|
379
|
+
|
|
263
380
|
killed_count = 0
|
|
381
|
+
valid_mutants_count = 0
|
|
264
382
|
abs_path = os.path.abspath(file_path)
|
|
383
|
+
backup_path = abs_path + ".synapse_mutator_bak"
|
|
265
384
|
|
|
385
|
+
# F3 Safety Discipline: Create a dedicated atomic backup file
|
|
386
|
+
# and guarantee recovery via try/finally and os.replace
|
|
266
387
|
try:
|
|
388
|
+
with open(backup_path, "w", encoding="utf-8") as bf:
|
|
389
|
+
bf.write(code)
|
|
390
|
+
|
|
267
391
|
for m in mutants:
|
|
268
392
|
mutated_code = m["mutated_code"]
|
|
393
|
+
|
|
394
|
+
# F14: Syntax validation for Python - discard invalid syntactic mutants
|
|
395
|
+
if file_path.endswith((".py", ".pyw")):
|
|
396
|
+
try:
|
|
397
|
+
compile(mutated_code, file_path, "exec")
|
|
398
|
+
except SyntaxError:
|
|
399
|
+
m["status"] = "INVALID_SYNTAX"
|
|
400
|
+
m["reason"] = "Mutant generated invalid Python syntax; discarded to prevent false killed verdict"
|
|
401
|
+
continue
|
|
402
|
+
|
|
403
|
+
valid_mutants_count += 1
|
|
269
404
|
with open(abs_path, "w", encoding="utf-8") as f:
|
|
270
405
|
f.write(mutated_code)
|
|
271
406
|
|
|
272
407
|
try:
|
|
273
408
|
proc = subprocess.run(
|
|
274
|
-
|
|
275
|
-
shell=True,
|
|
409
|
+
cmd_args,
|
|
276
410
|
capture_output=True,
|
|
277
|
-
timeout=
|
|
411
|
+
timeout=int(timeout),
|
|
278
412
|
text=True
|
|
279
413
|
)
|
|
280
414
|
if proc.returncode != 0:
|
|
@@ -285,15 +419,26 @@ def evaluate_mutants_dynamically(file_path: str, code: str, mutants: List[Dict[s
|
|
|
285
419
|
m["status"] = "SURVIVED"
|
|
286
420
|
m["reason"] = "Dynamic test suite passed unconditionally: missing assertion for this mutant."
|
|
287
421
|
except subprocess.TimeoutExpired:
|
|
288
|
-
|
|
289
|
-
m["
|
|
290
|
-
|
|
422
|
+
# F16: Inconclusive timeout does not count as killed
|
|
423
|
+
m["status"] = "TIMED_OUT"
|
|
424
|
+
m["reason"] = f"Mutant execution timed out (> {int(timeout)}s): inconclusive assertion sensitivity"
|
|
291
425
|
except Exception as e:
|
|
292
426
|
m["status"] = "SURVIVED"
|
|
293
427
|
m["reason"] = f"Test execution error: {e}"
|
|
294
428
|
finally:
|
|
295
|
-
|
|
296
|
-
|
|
429
|
+
# Atomic restore guaranteed from backup file
|
|
430
|
+
if os.path.exists(backup_path):
|
|
431
|
+
with open(backup_path, "r", encoding="utf-8") as bf:
|
|
432
|
+
clean_content = bf.read()
|
|
433
|
+
with open(abs_path, "w", encoding="utf-8") as f:
|
|
434
|
+
f.write(clean_content)
|
|
435
|
+
try:
|
|
436
|
+
os.remove(backup_path)
|
|
437
|
+
except Exception:
|
|
438
|
+
pass
|
|
439
|
+
else:
|
|
440
|
+
with open(abs_path, "w", encoding="utf-8") as f:
|
|
441
|
+
f.write(code)
|
|
297
442
|
|
|
298
443
|
score = evaluate_mutation_score(killed_count, len(mutants))
|
|
299
444
|
return mutants, score
|
|
@@ -320,7 +465,7 @@ def scan_directory_for_mutants(target_dir: str) -> List[Dict[str, Any]]:
|
|
|
320
465
|
return all_mutants
|
|
321
466
|
|
|
322
467
|
|
|
323
|
-
def evaluate_target(target_path: str, min_score: float = 80.0, test_cmd: str = "") -> Dict[str, Any]:
|
|
468
|
+
def evaluate_target(target_path: str, min_score: float = 80.0, test_cmd: str = "", timeout: float = 60.0, min_file_score: float = 0.0) -> Dict[str, Any]:
|
|
324
469
|
"""
|
|
325
470
|
Evaluates target file or directory for mutation robustness.
|
|
326
471
|
Dispatches to live test execution if test_cmd is provided, or static heuristic otherwise.
|
|
@@ -334,12 +479,15 @@ def evaluate_target(target_path: str, min_score: float = 80.0, test_cmd: str = "
|
|
|
334
479
|
code = f.read()
|
|
335
480
|
mutants = generate_mutants_for_code(code, file_path=os.path.basename(target))
|
|
336
481
|
if test_cmd:
|
|
337
|
-
evaluated_mutants, score = evaluate_mutants_dynamically(target, code, mutants, test_cmd)
|
|
482
|
+
evaluated_mutants, score = evaluate_mutants_dynamically(target, code, mutants, test_cmd, timeout=timeout)
|
|
338
483
|
else:
|
|
339
|
-
|
|
484
|
+
masked_code = mask_non_executable_contexts(code)
|
|
485
|
+
evaluated_mutants, score = evaluate_mutants_statically(masked_code, mutants)
|
|
340
486
|
all_mutants.extend(evaluated_mutants)
|
|
487
|
+
file_scores = {os.path.basename(target): score}
|
|
341
488
|
else:
|
|
342
489
|
valid_exts = {".groovy", ".py", ".ts", ".js"}
|
|
490
|
+
file_scores = {}
|
|
343
491
|
for root, _, files in os.walk(target):
|
|
344
492
|
for f in files:
|
|
345
493
|
if any(f.endswith(ext) for ext in valid_exts):
|
|
@@ -349,11 +497,14 @@ def evaluate_target(target_path: str, min_score: float = 80.0, test_cmd: str = "
|
|
|
349
497
|
code = fp.read()
|
|
350
498
|
rel_path = os.path.relpath(file_full, target)
|
|
351
499
|
f_mutants = generate_mutants_for_code(code, file_path=rel_path)
|
|
352
|
-
if
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
500
|
+
if f_mutants:
|
|
501
|
+
if test_cmd:
|
|
502
|
+
eval_m, f_score = evaluate_mutants_dynamically(file_full, code, f_mutants, test_cmd, timeout=timeout)
|
|
503
|
+
else:
|
|
504
|
+
masked_code = mask_non_executable_contexts(code)
|
|
505
|
+
eval_m, f_score = evaluate_mutants_statically(masked_code, f_mutants)
|
|
506
|
+
all_mutants.extend(eval_m)
|
|
507
|
+
file_scores[rel_path] = f_score
|
|
357
508
|
except Exception:
|
|
358
509
|
pass
|
|
359
510
|
|
|
@@ -364,7 +515,13 @@ def evaluate_target(target_path: str, min_score: float = 80.0, test_cmd: str = "
|
|
|
364
515
|
survived = sum(1 for m in all_mutants if m["status"] == "SURVIVED")
|
|
365
516
|
passed = score >= min_score
|
|
366
517
|
|
|
367
|
-
|
|
518
|
+
failed_files = {}
|
|
519
|
+
if not os.path.isfile(target) and min_file_score > 0.0:
|
|
520
|
+
failed_files = {f: s for f, s in file_scores.items() if s < min_file_score}
|
|
521
|
+
if failed_files:
|
|
522
|
+
passed = False
|
|
523
|
+
|
|
524
|
+
report_res = {
|
|
368
525
|
"target": target,
|
|
369
526
|
"mode": exec_mode,
|
|
370
527
|
"total_mutants": len(all_mutants),
|
|
@@ -372,45 +529,119 @@ def evaluate_target(target_path: str, min_score: float = 80.0, test_cmd: str = "
|
|
|
372
529
|
"survived": survived,
|
|
373
530
|
"mutation_score": score,
|
|
374
531
|
"min_score": min_score,
|
|
532
|
+
"min_file_score": min_file_score,
|
|
375
533
|
"passed": passed,
|
|
376
534
|
"mutants": all_mutants
|
|
377
535
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
536
|
+
if not os.path.isfile(target):
|
|
537
|
+
report_res["file_scores"] = file_scores
|
|
538
|
+
if failed_files:
|
|
539
|
+
report_res["failed_files"] = failed_files
|
|
540
|
+
return report_res
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def format_sarif(report: Dict[str, Any]) -> str:
|
|
544
|
+
"""Formats survived mutants in SARIF 2.1.0 format for GitHub Code Scanning."""
|
|
545
|
+
results = []
|
|
546
|
+
rules_dict = {}
|
|
547
|
+
for m in report.get("mutants", []):
|
|
548
|
+
if m.get("status") == "SURVIVED":
|
|
549
|
+
rule_id = "SYNAPSE_MUTANT_SURVIVED"
|
|
550
|
+
if rule_id not in rules_dict:
|
|
551
|
+
rules_dict[rule_id] = {
|
|
552
|
+
"id": rule_id,
|
|
553
|
+
"shortDescription": {"text": "Syntactic micro-mutant survived downstream assertion check"},
|
|
554
|
+
"defaultConfiguration": {"level": "error"}
|
|
555
|
+
}
|
|
556
|
+
results.append({
|
|
557
|
+
"ruleId": rule_id,
|
|
558
|
+
"level": "error",
|
|
559
|
+
"message": {"text": f"Mutant survived: {m.get('description', '')} ({m.get('original_snippet', '')} -> {m.get('mutated_snippet', '')})"},
|
|
560
|
+
"locations": [{
|
|
561
|
+
"physicalLocation": {
|
|
562
|
+
"artifactLocation": {"uri": m.get("file", report.get("target", ""))},
|
|
563
|
+
"region": {"startLine": max(1, m.get("line", 1))}
|
|
564
|
+
}
|
|
565
|
+
}]
|
|
566
|
+
})
|
|
567
|
+
sarif_doc = {
|
|
568
|
+
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
|
|
569
|
+
"version": "2.1.0",
|
|
570
|
+
"runs": [{
|
|
571
|
+
"tool": {
|
|
572
|
+
"driver": {
|
|
573
|
+
"name": "synapse-micro-mutator",
|
|
574
|
+
"version": "2.3.0",
|
|
575
|
+
"rules": list(rules_dict.values())
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
"results": results
|
|
579
|
+
}]
|
|
388
580
|
}
|
|
581
|
+
return json.dumps(sarif_doc, indent=2, ensure_ascii=False)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def format_junit(report: Dict[str, Any]) -> str:
|
|
585
|
+
"""Formats mutation evaluation in standard JUnit XML format."""
|
|
586
|
+
total = max(1, report.get("total_mutants", 0))
|
|
587
|
+
survived = report.get("survived", 0)
|
|
588
|
+
lines = [
|
|
589
|
+
'<?xml version="1.0" encoding="utf-8"?>',
|
|
590
|
+
f'<testsuite name="synapse-micro-mutator" tests="{total}" failures="{survived}" errors="0">'
|
|
591
|
+
]
|
|
592
|
+
for m in report.get("mutants", []):
|
|
593
|
+
cname = (m.get("file") or report.get("target", "")).replace("/", ".").replace("\\", ".")
|
|
594
|
+
clean_desc = re.sub(r'[^a-zA-Z0-9_]', '_', m.get('description', 'mutant'))
|
|
595
|
+
tname = f"Mutant_L{m.get('line', 1)}_{clean_desc}"
|
|
596
|
+
lines.append(f' <testcase classname="{cname}" name="{tname}">')
|
|
597
|
+
if m.get("status") == "SURVIVED":
|
|
598
|
+
desc = html_lib.escape(m.get("description", ""))
|
|
599
|
+
orig = html_lib.escape(m.get("original_snippet", ""))
|
|
600
|
+
mut = html_lib.escape(m.get("mutated_snippet", ""))
|
|
601
|
+
reason = html_lib.escape(m.get("reason", "Downstream assertions failed to kill mutant"))
|
|
602
|
+
lines.append(f' <failure message="Mutant survived: {desc}" type="MUTANT_SURVIVED">Line {m.get("line", 1)}: {orig} -> {mut}. Reason: {reason}</failure>')
|
|
603
|
+
lines.append(' </testcase>')
|
|
604
|
+
if not report.get("mutants"):
|
|
605
|
+
lines.append(' <testcase classname="synapse.mutator" name="clean_run"/>')
|
|
606
|
+
lines.append('</testsuite>')
|
|
607
|
+
return "\n".join(lines)
|
|
389
608
|
|
|
390
609
|
|
|
391
610
|
def main():
|
|
392
|
-
parser = argparse.ArgumentParser(description="
|
|
611
|
+
parser = argparse.ArgumentParser(description="Assertion Sensitivity & Syntactic Micro-Mutation Sentinel (Heuristic)")
|
|
393
612
|
parser.add_argument("target", nargs="?", default=os.getcwd(), help="Target script or directory to mutate")
|
|
394
613
|
parser.add_argument("--min-score", "--threshold", type=float, default=80.0, dest="min_score",
|
|
395
|
-
help="Minimum
|
|
614
|
+
help="Minimum assertion sensitivity percentage required to pass Quality Gate (default: 80.0)")
|
|
615
|
+
parser.add_argument("--min-file-score", type=float, default=0.0, help="Minimum assertion sensitivity percentage required per file (default: 0.0)")
|
|
616
|
+
parser.add_argument("--timeout", type=float, default=60.0, help="Per-mutant test execution timeout in seconds (default: 60.0)")
|
|
396
617
|
parser.add_argument("--test-cmd", default="", help="Optional dynamic test execution command to verify mutants")
|
|
397
|
-
parser.add_argument("--dry-run", action="store_true", default=
|
|
618
|
+
parser.add_argument("--dry-run", action="store_true", default=False, help="Scan and list mutants without modifying files")
|
|
398
619
|
parser.add_argument("--json", action="store_true", help="Output results in JSON format")
|
|
620
|
+
parser.add_argument("--format", choices=["text", "json", "sarif", "junit"], default="text",
|
|
621
|
+
help="Output format: text (default), json, sarif, or junit")
|
|
399
622
|
args = parser.parse_args()
|
|
400
623
|
|
|
401
|
-
|
|
624
|
+
effective_test_cmd = "" if args.dry_run else args.test_cmd
|
|
625
|
+
report = evaluate_target(args.target, min_score=args.min_score, test_cmd=effective_test_cmd, timeout=args.timeout, min_file_score=args.min_file_score)
|
|
626
|
+
|
|
627
|
+
output_format = "json" if args.json else args.format
|
|
402
628
|
|
|
403
|
-
if
|
|
629
|
+
if output_format == "json":
|
|
404
630
|
print(json.dumps(report, indent=2))
|
|
631
|
+
elif output_format == "sarif":
|
|
632
|
+
print(format_sarif(report))
|
|
633
|
+
elif output_format == "junit":
|
|
634
|
+
print(format_junit(report))
|
|
405
635
|
else:
|
|
406
636
|
print(f"\n======================================================================")
|
|
407
|
-
print(f"
|
|
637
|
+
print(f" ASSERTION SENSITIVITY & SYNTACTIC MUTATION SENTINEL (HEURISTIC) ")
|
|
408
638
|
print(f"======================================================================")
|
|
409
|
-
print(f" Target Scope
|
|
410
|
-
print(f"
|
|
411
|
-
print(f" Mutants
|
|
412
|
-
print(f" Mutants
|
|
413
|
-
print(f"
|
|
639
|
+
print(f" Target Scope : {report['target']}")
|
|
640
|
+
print(f" Evaluation Mode : {report['mode']}")
|
|
641
|
+
print(f" Mutants Injected : {report['total_mutants']}")
|
|
642
|
+
print(f" Mutants Caught/Killed : {report['killed']}")
|
|
643
|
+
print(f" Mutants Survived : {report['survived']}")
|
|
644
|
+
print(f" Assertion Sensitivity : {report['mutation_score']}% (Required: >={report['min_score']}%)")
|
|
414
645
|
print(f"======================================================================\n")
|
|
415
646
|
|
|
416
647
|
for m in report['mutants'][:6]:
|
|
@@ -424,9 +655,9 @@ def main():
|
|
|
424
655
|
print(f"... and {len(report['mutants']) - 6} more mutants evaluated.\n")
|
|
425
656
|
|
|
426
657
|
if report["passed"]:
|
|
427
|
-
print(f"✅ [QUALITY GATE PASSED]
|
|
658
|
+
print(f"✅ [QUALITY GATE PASSED] Assertion Sensitivity {report['mutation_score']}% >= {report['min_score']}% threshold.")
|
|
428
659
|
else:
|
|
429
|
-
print(f"❌ [REJECTED BY QUALITY GATEKEEPER]
|
|
660
|
+
print(f"❌ [REJECTED BY QUALITY GATEKEEPER] Assertion Sensitivity {report['mutation_score']}% < {report['min_score']}%.")
|
|
430
661
|
print(" 👉 Increase test assertion sensitivity or eliminate tautological/unasserted blocks.")
|
|
431
662
|
|
|
432
663
|
return 0 if report["passed"] else 1
|
package/tools/omni_transpiler.py
CHANGED
|
@@ -101,8 +101,8 @@ def scan_repository(repo_path: str) -> Dict[str, Any]:
|
|
|
101
101
|
"locators_count": len(p.locators),
|
|
102
102
|
"methods_count": len(p.methods)
|
|
103
103
|
})
|
|
104
|
-
inventory["total_locators"]
|
|
105
|
-
inventory["total_methods"]
|
|
104
|
+
inventory["total_locators"] = int(inventory.get("total_locators", 0)) + len(p.locators)
|
|
105
|
+
inventory["total_methods"] = int(inventory.get("total_methods", 0)) + len(p.methods)
|
|
106
106
|
|
|
107
107
|
for tc in p_ir.test_cases:
|
|
108
108
|
inventory["test_cases"].append({
|