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
@@ -1,22 +1,29 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Enterprise AST Micro-Mutation Testing Sentinel
4
- -----------------------------------------------
5
- Evaluates the true defect-catching efficacy of AI-generated tests by injecting
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 AST/syntactic fault injection across Groovy, Python, TypeScript, JavaScript
11
- - Static assertion sensitivity analysis (killing mutants when valid assertions exist)
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 hard threshold enforcement (--min-score, default: 80.0%)
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|write)\s*\(',
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 & docstrings.
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
- in_docstring = False
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 python / groovy multiline docstrings
117
- if stripped.startswith(('"""', "'''")):
118
- if stripped.count('"""') % 2 == 1 or stripped.count("'''") % 2 == 1:
119
- in_docstring = not in_docstring
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
- if in_docstring:
122
- if '"""' in stripped or "'''" in stripped:
123
- in_docstring = False
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
- mutated_line = f"{indent}// MUTANT_DELETED: {stripped}\n"
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" # Evaluated by analyze_and_score_mutants
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
- valid_assertion_lines = []
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
- valid_assertion_lines.append(idx)
279
+ assertion_entries.append((idx, s))
216
280
 
217
281
  # Zero valid assertions across code -> all mutants survive (Fake Pass!)
218
- if not valid_assertion_lines:
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
- downstream_assertions = [a_line for a_line in valid_assertion_lines if a_line >= m_line]
238
- if downstream_assertions and not has_empty_catch:
239
- # Check if within reasonable proximity (same block/method, e.g. within 30 lines)
240
- if any(a_line - m_line <= 30 for a_line in downstream_assertions):
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 at line {downstream_assertions[0]}"
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 verified this logic branch"
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
- test_cmd,
275
- shell=True,
409
+ cmd_args,
276
410
  capture_output=True,
277
- timeout=30,
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
- m["status"] = "KILLED"
289
- m["reason"] = "Mutant triggered execution timeout (> 30s): caught by runtime guard"
290
- killed_count += 1
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
- with open(abs_path, "w", encoding="utf-8") as f:
296
- f.write(code)
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
- evaluated_mutants, score = evaluate_mutants_statically(code, mutants)
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 test_cmd:
353
- eval_m, _ = evaluate_mutants_dynamically(file_full, code, f_mutants, test_cmd)
354
- else:
355
- eval_m, _ = evaluate_mutants_statically(code, f_mutants)
356
- all_mutants.extend(eval_m)
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
- return {
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,121 @@ 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
- return {
380
- "target": target,
381
- "total_mutants": len(all_mutants),
382
- "killed": killed,
383
- "survived": survived,
384
- "mutation_score": score,
385
- "min_score": min_score,
386
- "passed": passed,
387
- "mutants": all_mutants
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="AST Micro-Mutation Testing Sentinel & Quality Gate")
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 mutation score percentage required to pass Quality Gate (default: 80.0)")
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=True, help="Scan and list mutants without modifying files")
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
- report = evaluate_target(args.target, min_score=args.min_score, test_cmd=args.test_cmd)
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 args.json:
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:
636
+ mode_tag = "LIVE DYNAMIC RUNNER" if report.get("mode") == "DYNAMIC" else "STATIC HEURISTIC"
637
+ mode_desc = f"LIVE DYNAMIC RUNNER (cmd: '{args.test_cmd}')" if report.get("mode") == "DYNAMIC" else "STATIC HEURISTIC (fast 0.1s AST; pass --test-cmd for dynamic run)"
406
638
  print(f"\n======================================================================")
407
- print(f" ENTERPRISE AST MICRO-MUTATION SENTINEL (ANTIDOTE TO FAKE PASS) ")
639
+ print(f" ASSERTION SENSITIVITY & SYNTACTIC MUTATION SENTINEL [{mode_tag}] ")
408
640
  print(f"======================================================================")
409
- print(f" Target Scope : {report['target']}")
410
- print(f" Mutants Injected : {report['total_mutants']}")
411
- print(f" Mutants Killed : {report['killed']}")
412
- print(f" Mutants Survived : {report['survived']}")
413
- print(f" Mutation Score : {report['mutation_score']}% (Required: >={report['min_score']}%)")
641
+ print(f" Target Scope : {report['target']}")
642
+ print(f" Evaluation Mode : {mode_desc}")
643
+ print(f" Mutants Injected : {report['total_mutants']}")
644
+ print(f" Mutants Caught/Killed : {report['killed']}")
645
+ print(f" Mutants Survived : {report['survived']}")
646
+ print(f" Assertion Sensitivity : {report['mutation_score']}% (Required: >={report['min_score']}%)")
414
647
  print(f"======================================================================\n")
415
648
 
416
649
  for m in report['mutants'][:6]:
@@ -424,9 +657,9 @@ def main():
424
657
  print(f"... and {len(report['mutants']) - 6} more mutants evaluated.\n")
425
658
 
426
659
  if report["passed"]:
427
- print(f"✅ [QUALITY GATE PASSED] Mutation Score {report['mutation_score']}% >= {report['min_score']}% threshold.")
660
+ print(f"✅ [QUALITY GATE PASSED] Assertion Sensitivity {report['mutation_score']}% >= {report['min_score']}% threshold.")
428
661
  else:
429
- print(f"❌ [REJECTED BY QUALITY GATEKEEPER] Mutation Score {report['mutation_score']}% < {report['min_score']}%.")
662
+ print(f"❌ [REJECTED BY QUALITY GATEKEEPER] Assertion Sensitivity {report['mutation_score']}% < {report['min_score']}%.")
430
663
  print(" 👉 Increase test assertion sensitivity or eliminate tautological/unasserted blocks.")
431
664
 
432
665
  return 0 if report["passed"] else 1
@@ -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"] += len(p.locators)
105
- inventory["total_methods"] += len(p.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({