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
@@ -17,7 +17,7 @@ Verifies:
17
17
  Gate 5: Cleanup Blacklist: No temporary properties or debug dump files on disk.
18
18
  Gate 6: Credential Security: No plaintext passwords in Profiles/default.glbl or repository.
19
19
  Gate 7: Static Defect Sentinel: Meta Infer-style AST scan for fake passes and empty catches.
20
- Gate 8: AST Micro-Mutation Sentinel: Injects syntactic faults to eliminate tautological tests (Mutation Score >= 80%).
20
+ Gate 8: Syntactic Micro-Mutation Sentinel: Injects syntactic faults to eliminate tautological tests (Mutation Score >= 80%).
21
21
 
22
22
  Supports:
23
23
  - Hub Mode: Verifies the centralized QA Hub / skills repository integrity.
@@ -32,6 +32,11 @@ import sys
32
32
  import subprocess
33
33
  import glob
34
34
  import re
35
+ import json
36
+ import io
37
+ from datetime import datetime
38
+ import ast
39
+ import builtins
35
40
  import argparse
36
41
 
37
42
  # Enforce UTF-8 stdout encoding for Windows console compatibility
@@ -130,7 +135,7 @@ def resolve_target_repo(cli_target=None, cli_repo=None):
130
135
 
131
136
 
132
137
  def gate_0_author_integrity(repo_root, mode="AUTO"):
133
- print("\n[GATE 0] Running Author Attribution & Anti-Tamper Integrity Guard...")
138
+ print("\n[GATE 0] Verifying Author Attribution & MIT License Integrity...")
134
139
  try:
135
140
  from author_integrity import (
136
141
  verify_author_attribution,
@@ -148,47 +153,89 @@ def gate_0_author_integrity(repo_root, mode="AUTO"):
148
153
  DEFAULT_REPO_ROOT,
149
154
  )
150
155
 
151
- # 1. Cryptographic SHA-256 fingerprint verification
156
+ # 1. Author metadata hash verification
152
157
  if not verify_author_fingerprint():
153
- print("[FAIL] Cryptographic author fingerprint mismatch! Original creator ('Thế Vị @TeeVee998') has been tampered with.")
158
+ print("[FAIL] Author attribution reference mismatch! Original creator ('Thế Vị @TeeVee998') has been altered.")
154
159
  return False
155
160
 
156
161
  # 2. In HUB mode, verify the Hub repository itself.
157
162
  # In TARGET mode, verify the Hub tools root to guarantee engine integrity.
158
163
  check_root = repo_root if mode == "HUB" else DEFAULT_REPO_ROOT
159
164
  valid, violations = verify_author_attribution(check_root)
160
- if not valid:
161
- print("[FAIL] Author Attribution & Copyright Integrity Violated:")
162
- for v in violations:
165
+ license_violations = [v for v in violations if "LICENSE" in v]
166
+ if license_violations:
167
+ print("[FAIL] Author Attribution & MIT License Violated:")
168
+ for v in license_violations:
163
169
  print(f" • {v}")
164
- print(" Under MIT License, original author attribution to 'Thế Vị (@TeeVee998)' must be preserved.")
170
+ print(" Under MIT License, original author copyright notice to 'Thế Vị (@TeeVee998)' must be preserved in LICENSE.")
165
171
  return False
166
- print(f"[PASS] Author Attribution & Cryptographic Provenance verified intact: {get_canonical_watermark()}")
172
+ elif not valid:
173
+ # Informational notices for downstream forks with customized package.json / README
174
+ print("[INFO] Downstream attribution notice:")
175
+ for v in violations:
176
+ print(f" • {v}")
177
+ print(f"[PASS] Author Attribution & MIT License Provenance verified intact: {get_canonical_watermark()}")
167
178
  return True
168
179
 
169
180
 
181
+ MOJIBAKE_INDICATORS = (
182
+ "\u00c3\u00a1", # á
183
+ "\u00c3\u00a3", # ã
184
+ "\u00c3\u00a9", # é
185
+ "\u00c3\u00a8", # è
186
+ "\u00c3\u00b3", # ó
187
+ "\u00c3\u00b2", # ò
188
+ "\ufffd", # replacement char
189
+ "\u00e2\u20ac\u2014", # double encoded em-dash
190
+ "\u00e2\u20ac\u0153", # double encoded open quote
191
+ "\u00e2\u20ac\u009d", # double encoded close quote
192
+ )
193
+
194
+
170
195
  def gate_1_bom_check(repo_root, mode="AUTO"):
171
- print("\n[GATE 1] Scanning UTF-8 No BOM across repository...")
172
- violations = []
196
+ print("\n[GATE 1] Scanning UTF-8 No BOM and Encoding Integrity across repository...")
197
+ ignored_dirs = {".git", ".gradle", "node_modules", "allure-results", "graphify-out", "__pycache__", "build", "dist", "claude-ecosystem-repos"}
198
+ ignored_files = {"verify_all.py", "test_tools.py"}
199
+ bom_violations = []
200
+ mojibake_violations = []
173
201
  for root, dirs, files in os.walk(repo_root):
174
- if any(ignored in root for ignored in [".git", ".gradle", "node_modules", "allure-results", "graphify-out"]):
175
- continue
202
+ dirs[:] = [d for d in dirs if d not in ignored_dirs]
176
203
  for f in files:
177
204
  if f.endswith(BOM_EXTENSIONS):
178
205
  full_path = os.path.join(root, f)
206
+ rel_path = os.path.relpath(full_path, repo_root)
207
+ if f in ignored_files:
208
+ continue
179
209
  try:
180
210
  with open(full_path, "rb") as fp:
181
- if fp.read(3) == b"\xef\xbb\xbf":
182
- violations.append(os.path.relpath(full_path, repo_root))
183
- except Exception:
211
+ raw = fp.read()
212
+ if raw[:3] == b"\xef\xbb\xbf":
213
+ bom_violations.append(rel_path)
214
+ try:
215
+ text = raw.decode("utf-8")
216
+ if any(ind in text for ind in MOJIBAKE_INDICATORS):
217
+ mojibake_violations.append(rel_path)
218
+ except UnicodeDecodeError:
219
+ mojibake_violations.append(f"{rel_path} (Invalid UTF-8 decode)")
220
+ except (OSError, PermissionError):
184
221
  pass
185
222
 
186
- if violations:
187
- print(f"[FAIL] {len(violations)} files contain UTF-8 BOM bytes:")
188
- for v in violations:
223
+ failed = False
224
+ if bom_violations:
225
+ print(f"[FAIL] {len(bom_violations)} files contain UTF-8 BOM bytes:")
226
+ for v in bom_violations:
227
+ print(f" - {v}")
228
+ failed = True
229
+ if mojibake_violations:
230
+ print(f"[FAIL] {len(mojibake_violations)} files contain Mojibake / Corrupted Encoding:")
231
+ for v in mojibake_violations:
189
232
  print(f" - {v}")
233
+ failed = True
234
+
235
+ if failed:
190
236
  return False
191
- print("[PASS] 100% UTF-8 No BOM verified across all source files.")
237
+
238
+ print("[PASS] 100% UTF-8 No BOM & Clean Encoding verified across all source files.")
192
239
  return True
193
240
 
194
241
 
@@ -197,11 +244,12 @@ def gate_2_path_portability(repo_root, mode="AUTO"):
197
244
  ignored_dirs = {
198
245
  ".git", ".gradle", "node_modules", "allure-results", "Screenshots",
199
246
  "Reports", "reports", "TestReports", "testreports", "bin", "Libs", ".settings", "graphify-out",
200
- "__pycache__", "agent_qa.egg-info", "dist", "build"
247
+ "__pycache__", "agent_qa.egg-info", "dist", "build", "claude-ecosystem-repos"
201
248
  }
202
249
  ignored_files = {
203
250
  ".project", ".classpath", ".DS_Store", "memory_cache.db",
204
- "verify_all.py", "git_pre_commit_guard.py", "agent_canary.py", "repo_map.txt", "repo_map.json"
251
+ "verify_all.py", "git_pre_commit_guard.py", "agent_canary.py", "repo_map.txt", "repo_map.json",
252
+ "PROJECT_HEALTH_REPORT.md", "PROJECT_STATE.md"
205
253
  }
206
254
  violations = []
207
255
  for root, dirs, files in os.walk(repo_root):
@@ -224,7 +272,7 @@ def gate_2_path_portability(repo_root, mode="AUTO"):
224
272
  for pattern, desc in FORBIDDEN_PATH_PATTERNS:
225
273
  if pattern.search(line):
226
274
  violations.append((rel_path, line_no, desc))
227
- except Exception:
275
+ except (OSError, PermissionError):
228
276
  pass
229
277
 
230
278
  if violations:
@@ -240,7 +288,7 @@ def gate_3_clean_pom(repo_root, mode="AUTO"):
240
288
  print("\n[GATE 3] Checking Clean POM & Flat Architecture in Test Scripts...")
241
289
  effective_mode = detect_repo_mode(repo_root) if mode == "AUTO" else mode
242
290
  if effective_mode == "HUB":
243
- print("[PASS] Hub Mode: Repository contains QA Hub assets; Clean POM applies to target test suites.")
291
+ print("[N/A] Hub Mode: Not Applicable (Clean POM rules apply to target test suites in Spokes).")
244
292
  return True
245
293
 
246
294
  test_dirs = ["Scripts", "tests", "test", "e2e", "specs", "cypress"]
@@ -252,9 +300,14 @@ def gate_3_clean_pom(repo_root, mode="AUTO"):
252
300
  test_files.extend(glob.glob(os.path.join(d, "**", ext), recursive=True))
253
301
 
254
302
  if not test_files:
255
- print("[SKIP] No test scripts present in candidate directories (Scripts/, tests/, e2e/).")
303
+ print("[N/A] No test scripts present in candidate directories (Scripts/, tests/, e2e/).")
256
304
  return True
257
305
 
306
+ LOCATOR_CALL_PATTERN = re.compile(
307
+ r'(?:\.(?:click|dblclick|fill|type|press|check|locator|waitForSelector|hover|selectOption)|cy\.(?:get|find)|driver\.find[A-Za-z]*|WebUI\.[a-zA-Z]+)\s*\(\s*[\x22\x27](?P<sel>[^\x22\x27]+)[\x22\x27]'
308
+ )
309
+ RAW_XPATH_PATTERN = re.compile(r'[\x22\x27](?:xpath=|\/{2}[a-zA-Z*@\[])[^\x22\x27]*[\x22\x27]')
310
+
258
311
  violations = []
259
312
  for full_path in sorted(set(test_files)):
260
313
  rel_path = os.path.relpath(full_path, repo_root)
@@ -264,11 +317,22 @@ def gate_3_clean_pom(repo_root, mode="AUTO"):
264
317
  stripped = line.strip()
265
318
  if stripped.startswith("//") or stripped.startswith("/*") or stripped.startswith("#"):
266
319
  continue
320
+ # Ignore standard navigation statements to prevent false-positive XPath violations on URLs
321
+ if re.search(r'\.(?:goto|get|open|navigate)\s*\(\s*[\x22\x27]https?:\/\/', stripped):
322
+ continue
267
323
  if "findTestObject" in line:
268
324
  violations.append((rel_path, line_no, "Raw findTestObject"))
269
- if "//" in line and ("//table" in line or "//input" in line or "//button" in line):
270
- violations.append((rel_path, line_no, "Raw XPath"))
271
- except Exception:
325
+ continue
326
+ # Check locator calls taking raw CSS/XPath selectors
327
+ m = LOCATOR_CALL_PATTERN.search(stripped)
328
+ if m:
329
+ sel = m.group('sel').strip()
330
+ if any(sel.startswith(p) for p in ('.', '#', '//', 'xpath=', '[')) or ' > ' in sel:
331
+ violations.append((rel_path, line_no, f"Raw CSS/XPath Selector in Test Script: '{sel}'"))
332
+ continue
333
+ if RAW_XPATH_PATTERN.search(stripped):
334
+ violations.append((rel_path, line_no, "Raw XPath literal in Test Script"))
335
+ except (OSError, PermissionError):
272
336
  pass
273
337
 
274
338
  if violations:
@@ -289,7 +353,11 @@ def gate_4_ai_docs_lint(repo_root, mode="AUTO"):
289
353
  print("[FAIL] lint_ai_docs.py not found in tools/!")
290
354
  return False
291
355
 
292
- res = subprocess.run([sys.executable, linter_script, repo_root], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
356
+ try:
357
+ res = subprocess.run([sys.executable, linter_script, repo_root], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=60.0)
358
+ except subprocess.TimeoutExpired:
359
+ print("[FAIL] lint_ai_docs.py timed out after 60s!")
360
+ return False
293
361
  if res.returncode != 0:
294
362
  print(res.stdout)
295
363
  print(res.stderr)
@@ -352,7 +420,11 @@ def gate_6_credential_security(repo_root, mode="AUTO"):
352
420
  # 2. Universal Repository Scan for Hardcoded Plaintext Secrets
353
421
  secret_patterns = [
354
422
  (re.compile(r'\bghp_[0-9a-zA-Z]{36}\b'), "GitHub Personal Access Token"),
423
+ (re.compile(r'\bgithub_pat_[0-9a-zA-Z_]{82}\b'), "GitHub Fine-Grained Personal Access Token"),
355
424
  (re.compile(r'\bAKIA[0-9A-Z]{16}\b'), "AWS Access Key ID"),
425
+ (re.compile(r'\bsk_(?:live|test)_[0-9a-zA-Z]{24,}\b'), "Stripe Secret Key"),
426
+ (re.compile(r'\bxox[baprs]-[0-9a-zA-Z-]{10,}\b'), "Slack API Token"),
427
+ (re.compile(r'\bAIza[0-9A-Za-z-_]{35}\b'), "Google API Key"),
356
428
  (re.compile(r'-----BEGIN (?:RSA )?PRIVATE KEY-----'), "Private Key Header"),
357
429
  (re.compile(r'(?i)\b(?:Pass(?:word)?@?\d+|Admin@?\d+|Root@?\d+|Default@?\d+)\b'), "Known Default Password"), # gate6-allowlist
358
430
  (re.compile(r'(?i)\b(?:password|passwd|pwd)\s*[:=]\s*[\x22\x27][^\x22\x27]{4,}[\x22\x27]'), "Hardcoded Plaintext Password Assignment"), # gate6-allowlist
@@ -362,14 +434,14 @@ def gate_6_credential_security(repo_root, mode="AUTO"):
362
434
  "System.getenv", "os.getenv", "os.environ", "process.env",
363
435
  "dummy", "placeholder", "***", "<password>", "your_password",
364
436
  "test_password", "mock_", "NEO4J_PASSWORD", "DynamicTestPwd",
365
- "secret_patterns", "PASSWORD", 'password = ""', "password = ''",
437
+ "secret_patterns", "<name>PASSWORD</name>", 'password = ""', "password = ''",
366
438
  "type=str", "help=", "# gate6-allowlist", "# nosec"
367
439
  ]
368
440
 
369
441
  ignored_dirs = {
370
442
  ".git", ".gradle", "node_modules", "allure-results", "Screenshots",
371
443
  "Reports", "reports", "TestReports", "testreports", "bin", "Libs", ".settings", "graphify-out",
372
- "__pycache__", "agent_qa.egg-info", "dist", "build"
444
+ "__pycache__", "agent_qa.egg-info", "dist", "build", "claude-ecosystem-repos"
373
445
  }
374
446
 
375
447
  for root, dirs, files in os.walk(repo_root):
@@ -388,7 +460,7 @@ def gate_6_credential_security(repo_root, mode="AUTO"):
388
460
  for pat, desc in secret_patterns:
389
461
  if pat.search(line):
390
462
  violations.append((rel_path, line_no, desc))
391
- except Exception:
463
+ except (OSError, PermissionError):
392
464
  pass
393
465
 
394
466
  if violations:
@@ -401,12 +473,231 @@ def gate_6_credential_security(repo_root, mode="AUTO"):
401
473
  return True
402
474
 
403
475
 
476
+ def check_python_ast_integrity(repo_root):
477
+ """
478
+ Performs static AST validation across all Python files in the repository.
479
+ Enforces F821 parity (zero undefined variable names) and zero syntax errors.
480
+ """
481
+ builtin_names = set(dir(builtins)) | {'__file__', '__name__', '__doc__', '__package__'}
482
+ py_files = []
483
+ ignored = {'.git', 'node_modules', '__pycache__', '.venv', 'venv', 'claude-ecosystem-repos', 'build', 'dist', '.gradle'}
484
+
485
+ for root, dirs, files in os.walk(repo_root):
486
+ dirs[:] = [d for d in dirs if d not in ignored]
487
+ for f in files:
488
+ if f.endswith('.py'):
489
+ py_files.append(os.path.join(root, f))
490
+
491
+ violations = []
492
+
493
+ for file_path in sorted(py_files):
494
+ rel_path = os.path.relpath(file_path, repo_root)
495
+ try:
496
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as fp:
497
+ content = fp.read()
498
+ tree = ast.parse(content, file_path)
499
+ except SyntaxError as e:
500
+ violations.append((rel_path, e.lineno or 1, f"SyntaxError: {e.msg}"))
501
+ continue
502
+
503
+ module_names = set(builtin_names)
504
+ for stmt in tree.body:
505
+ if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
506
+ module_names.add(stmt.name)
507
+ elif isinstance(stmt, ast.Import):
508
+ for a in stmt.names:
509
+ module_names.add(a.asname or a.name.split('.')[0])
510
+ elif isinstance(stmt, ast.ImportFrom):
511
+ for a in stmt.names:
512
+ module_names.add(a.asname or a.name)
513
+ elif isinstance(stmt, (ast.Assign, ast.AnnAssign)):
514
+ targets = [stmt.target] if isinstance(stmt, ast.AnnAssign) else stmt.targets
515
+ for t in targets:
516
+ for n in ast.walk(t):
517
+ if isinstance(n, ast.Name):
518
+ module_names.add(n.id)
519
+ for n in ast.walk(stmt):
520
+ if hasattr(ast, 'NamedExpr') and isinstance(n, ast.NamedExpr) and isinstance(getattr(n, 'target', None), ast.Name):
521
+ module_names.add(n.target.id)
522
+
523
+ class ScopeVisitor(ast.NodeVisitor):
524
+ def __init__(self):
525
+ self.scopes = [set(module_names)]
526
+ self.file_violations = []
527
+
528
+ def visit_FunctionDef(self, node):
529
+ self.scopes[-1].add(node.name)
530
+ fn_scope = set(self.scopes[-1])
531
+ for arg in node.args.posonlyargs + node.args.args + node.args.kwonlyargs:
532
+ fn_scope.add(arg.arg)
533
+ if node.args.vararg:
534
+ fn_scope.add(node.args.vararg.arg)
535
+ if node.args.kwarg:
536
+ fn_scope.add(node.args.kwarg.arg)
537
+ self.scopes.append(fn_scope)
538
+ self.generic_visit(node)
539
+ self.scopes.pop()
540
+
541
+ visit_AsyncFunctionDef = visit_FunctionDef
542
+
543
+ def visit_ClassDef(self, node):
544
+ self.scopes[-1].add(node.name)
545
+ cls_scope = set(self.scopes[-1])
546
+ self.scopes.append(cls_scope)
547
+ self.generic_visit(node)
548
+ self.scopes.pop()
549
+
550
+ def visit_Assign(self, node):
551
+ for t in node.targets:
552
+ for n in ast.walk(t):
553
+ if isinstance(n, ast.Name):
554
+ self.scopes[-1].add(n.id)
555
+ self.generic_visit(node)
556
+
557
+ def visit_AnnAssign(self, node):
558
+ if isinstance(node.target, ast.Name):
559
+ self.scopes[-1].add(node.target.id)
560
+ self.generic_visit(node)
561
+
562
+ def visit_For(self, node):
563
+ for n in ast.walk(node.target):
564
+ if isinstance(n, ast.Name):
565
+ self.scopes[-1].add(n.id)
566
+ self.generic_visit(node)
567
+
568
+ def visit_With(self, node):
569
+ for item in node.items:
570
+ if item.optional_vars:
571
+ for n in ast.walk(item.optional_vars):
572
+ if isinstance(n, ast.Name):
573
+ self.scopes[-1].add(n.id)
574
+ self.generic_visit(node)
575
+
576
+ def visit_ExceptHandler(self, node):
577
+ if node.name:
578
+ self.scopes[-1].add(node.name)
579
+ self.generic_visit(node)
580
+
581
+ def visit_NamedExpr(self, node):
582
+ if isinstance(node.target, ast.Name):
583
+ for s in self.scopes:
584
+ s.add(node.target.id)
585
+ self.generic_visit(node)
586
+
587
+ def visit_Match(self, node):
588
+ for case in getattr(node, 'cases', []):
589
+ pattern = getattr(case, 'pattern', None)
590
+ if pattern:
591
+ for n in ast.walk(pattern):
592
+ name = getattr(n, 'name', None)
593
+ if isinstance(name, str):
594
+ self.scopes[-1].add(name)
595
+ rest = getattr(n, 'rest', None)
596
+ if isinstance(rest, str):
597
+ self.scopes[-1].add(rest)
598
+ self.generic_visit(node)
599
+
600
+ def visit_Global(self, node):
601
+ for name in node.names:
602
+ for s in self.scopes:
603
+ s.add(name)
604
+ self.generic_visit(node)
605
+
606
+ def visit_Nonlocal(self, node):
607
+ for name in node.names:
608
+ self.scopes[-1].add(name)
609
+ self.generic_visit(node)
610
+
611
+ def visit_Import(self, node):
612
+ for a in node.names:
613
+ self.scopes[-1].add(a.asname or a.name.split('.')[0])
614
+
615
+ def visit_ImportFrom(self, node):
616
+ for a in node.names:
617
+ self.scopes[-1].add(a.asname or a.name)
618
+
619
+ def visit_ListComp(self, node):
620
+ comp_scope = set(self.scopes[-1])
621
+ for gen in node.generators:
622
+ for n in ast.walk(gen.target):
623
+ if isinstance(n, ast.Name):
624
+ comp_scope.add(n.id)
625
+ self.scopes.append(comp_scope)
626
+ self.generic_visit(node)
627
+ self.scopes.pop()
628
+
629
+ visit_SetComp = visit_ListComp
630
+ visit_GeneratorExp = visit_ListComp
631
+ visit_DictComp = visit_ListComp
632
+
633
+ def visit_Lambda(self, node):
634
+ lam_scope = set(self.scopes[-1])
635
+ for arg in node.args.posonlyargs + node.args.args + node.args.kwonlyargs:
636
+ lam_scope.add(arg.arg)
637
+ if node.args.vararg:
638
+ lam_scope.add(node.args.vararg.arg)
639
+ if node.args.kwarg:
640
+ lam_scope.add(node.args.kwarg.arg)
641
+ self.scopes.append(lam_scope)
642
+ self.generic_visit(node)
643
+ self.scopes.pop()
644
+
645
+ def visit_Name(self, node):
646
+ if isinstance(node.ctx, ast.Load):
647
+ if node.id.startswith('__') and node.id.endswith('__'):
648
+ return
649
+ if not any(node.id in s for s in reversed(self.scopes)):
650
+ self.file_violations.append((rel_path, node.lineno, f"Undefined name '{node.id}' (F821)"))
651
+
652
+ visitor = ScopeVisitor()
653
+ visitor.visit(tree)
654
+ violations.extend(visitor.file_violations)
655
+
656
+ return violations
657
+
658
+
404
659
  def gate_7_infer_linter(repo_root, mode="AUTO"):
405
660
  print("\n[GATE 7] Running Meta Infer-Style Static Defect & Fake-Pass Sentinel...")
406
661
  effective_mode = detect_repo_mode(repo_root) if mode == "AUTO" else mode
662
+
663
+ # Static Python AST & Undefined Name (F821) Sentinel
664
+ ast_violations = check_python_ast_integrity(repo_root)
665
+ if ast_violations:
666
+ print(f"[FAIL] Static Python AST Sentinel detected {len(ast_violations)} syntax errors or undefined names:")
667
+ for rel_file, line_no, desc in ast_violations:
668
+ print(f" - {rel_file}:{line_no} -> {desc}")
669
+ return False
670
+ print("[PASS] 100% Python AST integrity verified (zero syntax errors, zero undefined variables).")
671
+
672
+ # In HUB mode: dogfood tools/ with baseline to prevent new unbaselined defects
407
673
  if effective_mode == "HUB":
408
- print("[PASS] Hub Mode: Static Defect Sentinel active for target projects.")
409
- return True
674
+ baseline_file = os.path.join(repo_root, "tools", ".infer_baseline.json")
675
+ infer_script = os.path.join(repo_root, "tools", "infer_linter.py")
676
+ if os.path.exists(baseline_file) and os.path.exists(infer_script):
677
+ tools_dir = os.path.join(repo_root, "tools")
678
+ try:
679
+ res = subprocess.run([
680
+ sys.executable, infer_script, "--strict", "--baseline", baseline_file, "--dir", tools_dir
681
+ ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=60.0)
682
+ if res.returncode != 0:
683
+ print("[FAIL] Hub internal tools contain unbaselined critical defects:")
684
+ print(res.stdout)
685
+ return False
686
+ except subprocess.TimeoutExpired:
687
+ print("[FAIL] infer_linter.py timed out after 60s!")
688
+ return False
689
+ except (OSError, subprocess.SubprocessError):
690
+ pass
691
+
692
+ # Hub Mode Version Synchronization Verification
693
+ try:
694
+ import check_version_sync
695
+ synced, versions, canonical = check_version_sync.check_sync(repo_root)
696
+ if not synced:
697
+ print(f"[FAIL] Gate 7 Version Synchronization: Inconsistent versions detected across manifests: {versions}")
698
+ return False
699
+ except (ImportError, OSError):
700
+ pass
410
701
 
411
702
  test_dirs = ["Scripts", "tests", "test", "e2e", "specs", "cypress"]
412
703
  has_tests = False
@@ -423,7 +714,7 @@ def gate_7_infer_linter(repo_root, mode="AUTO"):
423
714
  break
424
715
 
425
716
  if not has_tests:
426
- print("[SKIP] No test scripts (Groovy/TS/JS/Py) present in candidate test directories.")
717
+ print("[N/A] No test scripts (Groovy/TS/JS/Py) present in candidate test directories.")
427
718
  return True
428
719
 
429
720
  infer_script = os.path.join(SCRIPT_DIR, "infer_linter.py")
@@ -431,17 +722,22 @@ def gate_7_infer_linter(repo_root, mode="AUTO"):
431
722
  infer_script = os.path.join(repo_root, "tools", "infer_linter.py")
432
723
 
433
724
  if os.path.exists(infer_script):
434
- res = subprocess.run([sys.executable, infer_script, "--dir", target_scan_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
435
- if res.returncode != 0:
436
- print("[FAIL] Static Defect Sentinel detected critical issues in test scripts:")
437
- print(res.stdout)
725
+ try:
726
+ res = subprocess.run([sys.executable, infer_script, "--strict", "--dir", target_scan_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=60.0)
727
+ if res.returncode != 0:
728
+ print("[FAIL] Static Defect Sentinel detected critical issues in test scripts:")
729
+ print(res.stdout)
730
+ return False
731
+ except subprocess.TimeoutExpired:
732
+ print("[FAIL] infer_linter.py timed out after 60s!")
438
733
  return False
439
- print("[PASS] Zero critical defects / zero swallowed exceptions across test scripts.")
734
+ print("[PASS] Zero critical defects / zero swallowed exceptions across active test suites.")
440
735
  return True
441
736
 
442
737
 
443
- def gate_8_micro_mutator(repo_root, mode="AUTO"):
444
- print("\n[GATE 8] Running AST Micro-Mutation Testing Sentinel (Min Mutation Score >= 80%)...")
738
+ def gate_8_micro_mutator(repo_root, mode="AUTO", test_cmd=""):
739
+ eval_mode_desc = f"DYNAMIC EXECUTION MODE ({test_cmd})" if test_cmd else "HEURISTIC STATIC MODE"
740
+ print(f"\n[GATE 8] Running Assertion Sensitivity & Syntactic Mutation Sentinel [{eval_mode_desc}] (Min Score >= 80%)...")
445
741
  scripts_dir = os.path.join(repo_root, "Scripts")
446
742
  tests_dir = os.path.join(repo_root, "tests")
447
743
 
@@ -450,17 +746,17 @@ def gate_8_micro_mutator(repo_root, mode="AUTO"):
450
746
  groovy_files = [f for f in glob.glob(os.path.join(scripts_dir, "**", "*.groovy"), recursive=True)]
451
747
  if groovy_files:
452
748
  target_dir = scripts_dir
453
- elif os.path.isdir(tests_dir):
749
+ if not target_dir and os.path.isdir(tests_dir):
454
750
  test_files = [f for f in glob.glob(os.path.join(tests_dir, "**", "*.*"), recursive=True) if f.endswith((".py", ".ts", ".js", ".groovy"))]
455
751
  if test_files:
456
752
  target_dir = tests_dir
457
753
 
458
754
  if not target_dir:
459
755
  if mode == "HUB":
460
- print("[PASS] Hub Mode: AST Micro-Mutation engine verified and operational.")
756
+ print(f"[N/A] Hub Mode: Not Applicable (Assertion Sensitivity & Syntactic Mutation applies to target test suites in Spokes) [{eval_mode_desc}].")
461
757
  return True
462
758
  else:
463
- print("[SKIP] No automated test scripts found in Scripts/ or tests/ to mutate.")
759
+ print("[N/A] No automated test scripts found in Scripts/ or tests/ to mutate.")
464
760
  return True
465
761
 
466
762
  mutator_script = os.path.join(SCRIPT_DIR, "micro_mutator.py")
@@ -468,68 +764,142 @@ def gate_8_micro_mutator(repo_root, mode="AUTO"):
468
764
  mutator_script = os.path.join(repo_root, "tools", "micro_mutator.py")
469
765
 
470
766
  if os.path.exists(mutator_script):
471
- res = subprocess.run([sys.executable, mutator_script, target_dir, "--min-score", "80.0", "--json"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
767
+ cmd = [sys.executable, mutator_script, target_dir, "--min-score", "80.0", "--json"]
768
+ if test_cmd:
769
+ cmd.extend(["--test-cmd", test_cmd])
770
+ try:
771
+ res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=120.0)
772
+ except subprocess.TimeoutExpired:
773
+ print("[FAIL] Assertion Sensitivity Sentinel timed out after 120s!")
774
+ return False
472
775
  if res.returncode != 0:
473
- print("[FAIL] AST Micro-Mutation Sentinel REJECTED: Mutation score < 80% or unasserted logic found.")
776
+ print("[FAIL] Assertion Sensitivity Sentinel REJECTED: Score < 80% or unasserted logic found.")
474
777
  try:
475
778
  data = json.loads(res.stdout)
476
- print(f" Mutation Score: {data.get('mutation_score', 0)}% (Required: >= 80%)")
477
- print(f" Mutants Killed: {data.get('killed', 0)} / {data.get('total_mutants', 0)}")
478
- except Exception:
779
+ print(f" Assertion Sensitivity: {data.get('mutation_score', 0)}% (Required: >= 80%)")
780
+ print(f" Mutants Caught: {data.get('killed', 0)} / {data.get('total_mutants', 0)}")
781
+ except (json.JSONDecodeError, ValueError, KeyError):
479
782
  if res.stdout:
480
783
  print(res.stdout)
481
784
  return False
482
785
  else:
483
786
  try:
484
787
  data = json.loads(res.stdout)
485
- print(f"[PASS] Mutation Score: {data.get('mutation_score', 100)}% >= 80% (Killed {data.get('killed', 0)}/{data.get('total_mutants', 0)} mutants). Zero Tautological Tests.")
486
- except Exception:
487
- print("[PASS] AST Micro-Mutation Sentinel passed: Mutation score >= 80%.")
788
+ mode_label = "DYNAMIC" if test_cmd else "HEURISTIC STATIC"
789
+ print(f"[PASS] Assertion Sensitivity [{mode_label}]: {data.get('mutation_score', 100)}% >= 80% (Caught {data.get('killed', 0)}/{data.get('total_mutants', 0)} syntactic mutants). Zero unasserted logic branches.")
790
+ except (json.JSONDecodeError, ValueError, KeyError):
791
+ print(f"[PASS] Assertion Sensitivity Sentinel passed: Score >= 80% [{eval_mode_desc}].")
488
792
  return True
489
793
 
490
- print("[PASS] AST Micro-Mutation verified.")
794
+ print("[PASS] Assertion Sensitivity verified.")
491
795
  return True
492
796
 
493
797
 
494
- def main():
495
- parser = argparse.ArgumentParser(description="Enterprise DoD Verification Sentinel")
496
- parser.add_argument("target", nargs="?", default=None,
497
- help="Target repository path to verify (defaults to auto-detection from CWD or Hub)")
498
- parser.add_argument("--repo", "--path", dest="repo_path", default=None,
499
- help="Target repository path to verify")
500
- parser.add_argument("--mode", choices=["auto", "hub", "target"], default="auto",
501
- help="Execution mode (auto-detect, hub, or target test project)")
502
- args = parser.parse_args()
798
+ def load_synapse_config(repo_root=None, config_path=None):
799
+ """Loads configuration from synapse.config.json if present."""
800
+ if config_path and os.path.exists(config_path):
801
+ try:
802
+ with open(config_path, "r", encoding="utf-8") as f:
803
+ return json.load(f)
804
+ except (OSError, json.JSONDecodeError):
805
+ pass
806
+ root = repo_root or REPO_ROOT
807
+ cand = os.path.join(root, "synapse.config.json")
808
+ if os.path.exists(cand):
809
+ try:
810
+ with open(cand, "r", encoding="utf-8") as f:
811
+ return json.load(f)
812
+ except (OSError, json.JSONDecodeError):
813
+ pass
814
+ return {}
815
+
816
+
817
+ def format_gates_junit(gate_results, repo_root):
818
+ """Formats 9 DoD Quality Gates result into JUnit XML testsuite."""
819
+ total = len(gate_results)
820
+ failures = len([g for g in gate_results if not g[1]])
821
+ lines = [
822
+ '<?xml version="1.0" encoding="utf-8"?>',
823
+ f'<testsuite name="synapse-quality-gates" tests="{total}" failures="{failures}" errors="0">'
824
+ ]
825
+ for name, success, reason in gate_results:
826
+ cname = "synapse.quality_gates"
827
+ tname = name.split(":")[0].strip().replace(" ", "_")
828
+ lines.append(f' <testcase classname="{cname}" name="{tname}">')
829
+ if not success:
830
+ lines.append(f' <failure message="Quality Gate Failed: {name}">{name} failed in repository {repo_root} ({reason})</failure>')
831
+ lines.append(' </testcase>')
832
+ lines.append('</testsuite>')
833
+ return "\n".join(lines)
503
834
 
504
- try:
505
- repo_root = resolve_target_repo(cli_target=args.target, cli_repo=args.repo_path)
506
- except FileNotFoundError as e:
507
- print(f"[ERROR] {e}")
508
- return 1
509
835
 
510
- effective_mode = detect_repo_mode(repo_root) if args.mode == "auto" else args.mode.upper()
836
+ def run_all_gates(repo_root, mode="AUTO", test_cmd="", output_format="text", config_path=None):
837
+ """Programmatic entrypoint to execute all 9 DoD Quality Gates with config and format support."""
838
+ effective_mode = detect_repo_mode(repo_root) if mode.upper() == "AUTO" else mode.upper()
839
+ cfg = load_synapse_config(repo_root, config_path=config_path)
840
+ qg_cfg = cfg.get("quality_gates", {})
841
+ gate_toggles = qg_cfg.get("gates", {})
511
842
 
512
- print_header(f"ENTERPRISE AUTOMATION - ONE-COMMAND DoD VERIFICATION [{effective_mode} MODE]")
513
- print(f"Target Repository: {repo_root}")
514
- print(f"Operational Mode : {effective_mode}")
843
+ if output_format == "text":
844
+ print_header(f"ENTERPRISE AUTOMATION - ONE-COMMAND DoD VERIFICATION [{effective_mode} MODE]")
845
+ print(f"Target Repository: {repo_root}")
846
+ print(f"Operational Mode : {effective_mode}")
515
847
 
516
848
  gates = [
517
- ("Gate 0: Author Attribution & Integrity", gate_0_author_integrity),
518
- ("Gate 1: UTF-8 No BOM", gate_1_bom_check),
519
- ("Gate 2: Path Portability", gate_2_path_portability),
520
- ("Gate 3: Clean POM & Flat Architecture", gate_3_clean_pom),
521
- ("Gate 4: AI Documentation Linter", gate_4_ai_docs_lint),
522
- ("Gate 5: Temporary Files Blacklist", gate_5_cleanup_blacklist),
523
- ("Gate 6: Credential Security", gate_6_credential_security),
524
- ("Gate 7: Static Defect Sentinel", gate_7_infer_linter),
525
- ("Gate 8: AST Micro-Mutation Sentinel", gate_8_micro_mutator),
849
+ ("gate_0_author_integrity", "Gate 0: Author Attribution & Integrity", gate_0_author_integrity),
850
+ ("gate_1_bom_check", "Gate 1: UTF-8 No BOM", gate_1_bom_check),
851
+ ("gate_2_path_portability", "Gate 2: Path Portability", gate_2_path_portability),
852
+ ("gate_3_clean_pom", "Gate 3: Clean POM & Flat Architecture", gate_3_clean_pom),
853
+ ("gate_4_ai_docs_lint", "Gate 4: AI Documentation Linter", gate_4_ai_docs_lint),
854
+ ("gate_5_cleanup_blacklist", "Gate 5: Temporary Files Blacklist", gate_5_cleanup_blacklist),
855
+ ("gate_6_credential_security", "Gate 6: Credential Security", gate_6_credential_security),
856
+ ("gate_7_infer_linter", "Gate 7: Static Defect Sentinel", gate_7_infer_linter),
857
+ ("gate_8_micro_mutator", "Gate 8: Assertion Sensitivity & Syntactic Mutation Sentinel", lambda r, mode: gate_8_micro_mutator(r, mode=mode, test_cmd=test_cmd)),
526
858
  ]
527
859
 
528
860
  all_passed = True
529
- for name, gate_fn in gates:
530
- success = gate_fn(repo_root, mode=effective_mode)
531
- if not success:
532
- all_passed = False
861
+ gate_results = []
862
+
863
+ if output_format in ("json", "junit"):
864
+ old_stdout = sys.stdout
865
+ sys.stdout = io.StringIO()
866
+ try:
867
+ for g_key, name, gate_fn in gates:
868
+ if gate_toggles.get(g_key) is False:
869
+ gate_results.append((name, True, "SKIPPED_BY_CONFIG"))
870
+ continue
871
+ success = gate_fn(repo_root, mode=effective_mode)
872
+ gate_results.append((name, success, "PASS" if success else "FAIL"))
873
+ if not success:
874
+ all_passed = False
875
+ finally:
876
+ sys.stdout = old_stdout
877
+ else:
878
+ for g_key, name, gate_fn in gates:
879
+ if gate_toggles.get(g_key) is False:
880
+ print(f"\n[{name.split(':')[0].upper()}] Disabled via synapse.config.json (SKIPPED)")
881
+ gate_results.append((name, True, "SKIPPED_BY_CONFIG"))
882
+ continue
883
+
884
+ success = gate_fn(repo_root, mode=effective_mode)
885
+ gate_results.append((name, success, "PASS" if success else "FAIL"))
886
+ if not success:
887
+ all_passed = False
888
+
889
+ if output_format == "json":
890
+ res_doc = {
891
+ "timestamp": datetime.now().isoformat(),
892
+ "target": repo_root,
893
+ "mode": effective_mode,
894
+ "all_passed": all_passed,
895
+ "gates": [{"name": g[0], "passed": g[1], "status": g[2]} for g in gate_results]
896
+ }
897
+ print(json.dumps(res_doc, indent=2))
898
+ return 0 if all_passed else 1
899
+
900
+ if output_format == "junit":
901
+ print(format_gates_junit(gate_results, repo_root))
902
+ return 0 if all_passed else 1
533
903
 
534
904
  print("\n" + "=" * 70)
535
905
  if all_passed:
@@ -544,5 +914,29 @@ def main():
544
914
  return 1
545
915
 
546
916
 
917
+ def main():
918
+ parser = argparse.ArgumentParser(description="Enterprise DoD Verification Sentinel")
919
+ parser.add_argument("target", nargs="?", default=None,
920
+ help="Target repository path to verify (defaults to auto-detection from CWD or Hub)")
921
+ parser.add_argument("--repo", "--path", dest="repo_path", default=None,
922
+ help="Target repository path to verify")
923
+ parser.add_argument("--mode", choices=["auto", "hub", "target"], default="auto",
924
+ help="Execution mode (auto-detect, hub, or target test project)")
925
+ parser.add_argument("--format", choices=["text", "json", "junit"], default="text",
926
+ help="Output report format (default: text)")
927
+ parser.add_argument("--config", help="Path to synapse.config.json")
928
+ parser.add_argument("--test-cmd", default="",
929
+ help="Optional test command for Gate 8 dynamic mutation testing (e.g. 'pytest' or 'npx playwright test')")
930
+ args = parser.parse_args()
931
+
932
+ try:
933
+ repo_root = resolve_target_repo(cli_target=args.target, cli_repo=args.repo_path)
934
+ except FileNotFoundError as e:
935
+ print(f"[ERROR] {e}")
936
+ return 1
937
+
938
+ return run_all_gates(repo_root, mode=args.mode, test_cmd=args.test_cmd, output_format=args.format, config_path=args.config)
939
+
940
+
547
941
  if __name__ == "__main__":
548
942
  sys.exit(main())