synapse-hub 2.2.4 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/failure-analyst.md +11 -0
- package/.agents/agents/live-explorer.md +28 -0
- package/.agents/agents/orchestrator.md +20 -4
- package/.agents/agents/pom-crafter.md +12 -0
- package/.agents/agents/quality-gatekeeper.md +19 -4
- package/.agents/agents/test-runner.md +11 -0
- package/.agents/rules/communication-style.md +40 -0
- package/.agents/rules/context-engineering.md +23 -0
- package/.agents/rules/git-push-protocol.md +10 -16
- package/.agents/rules/graphify.md +14 -14
- package/.agents/rules/mandatory-playwright-live.md +29 -29
- package/.agents/rules/mobile_appium_rules.md +132 -0
- package/.agents/rules/performance_testing_rules.md +74 -0
- package/.agents/rules/systematic-debugging.md +6 -6
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +64 -215
- package/.agents/skills/agent-orchestration-multi-agent-optimize/examples/swarm_workload_config.json +12 -0
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +3 -3
- package/.agents/skills/antigravity-test-sentinel/examples/dod_gate_policy.json +11 -0
- package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
- package/.agents/skills/api-network-mocking-interception/examples/network_route_stub.json +9 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +9 -9
- package/.agents/skills/api-spec-to-e2e-matrix/examples/sample_openapi.json +19 -0
- package/.agents/skills/architecture-decision-records/SKILL.md +35 -412
- package/.agents/skills/architecture-decision-records/examples/0001-record-architecture-decisions.md +12 -0
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +1 -1
- package/.agents/skills/auth-session-and-cookie-manager/examples/storage_state_sample.json +11 -0
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +80 -186
- package/.agents/skills/backend-load-and-stress-patterns/examples/k6_load_test_sample.js +13 -0
- package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/bats-testing-patterns/examples/cli_runner_test.bats +6 -0
- package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -614
- package/.agents/skills/clean-pom-crafter/SKILL.md +21 -3
- package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -28
- package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -138
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +2 -2
- package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_page_sample.groovy +9 -0
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +5 -5
- package/.agents/skills/db-state-verifier-and-seeder/examples/db_fixture_seed.json +12 -0
- package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/e2e-testing-patterns/examples/playwright_sharded_config.ts +12 -0
- package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -522
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +17 -3
- package/.agents/skills/failure-autopsy-and-healing/examples/sample_junit_failure.xml +8 -0
- package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -64
- package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -56
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -98
- package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
- package/.agents/skills/file-and-report-validator/examples/sample_export_schema.json +14 -0
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +3 -3
- package/.agents/skills/flaky-test-sentinel-and-quarantine/examples/quarantine_registry.json +11 -0
- package/.agents/skills/gherkin-to-suite/SKILL.md +103 -0
- package/.agents/skills/gherkin-to-suite/examples/checkout.feature +39 -0
- package/.agents/skills/gherkin-to-suite/examples/suite_report_sample.json +334 -0
- package/.agents/skills/github-actions-templates/SKILL.md +47 -304
- package/.agents/skills/github-actions-templates/examples/matrix_test_pipeline.yml +8 -0
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +6 -6
- package/.agents/skills/graphify-impact-analyzer/examples/dependency_blast_radius.json +8 -0
- package/.agents/skills/katalon-cli-runner/SKILL.md +2 -2
- package/.agents/skills/katalon-cli-runner/examples/headless_kre_exec.sh +3 -0
- package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -209
- package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -155
- package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -67
- package/.agents/skills/lam-script-generator/SKILL.md +42 -1
- package/.agents/skills/lam-script-generator/examples/user_story_sample.md +8 -0
- package/.agents/skills/lean-code-ladder/SKILL.md +83 -0
- package/.agents/skills/lean-code-ladder/examples/ladder_walkthrough.md +37 -0
- package/.agents/skills/meta-prompt-crafter/SKILL.md +107 -0
- package/.agents/skills/meta-prompt-crafter/examples/qa_prompt_template.md +35 -0
- package/.agents/skills/persistent-planning-files/SKILL.md +108 -0
- package/.agents/skills/persistent-planning-files/examples/task_plan_example.md +56 -0
- package/.agents/skills/playwright-live-interaction/SKILL.md +49 -1
- package/.agents/skills/playwright-live-interaction/examples/live_probe_script.js +5 -0
- package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -160
- package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -75
- package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -388
- package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -134
- package/.agents/skills/playwright-webapp-testing/SKILL.md +93 -0
- package/.agents/skills/playwright-webapp-testing/examples/server_lifecycle_playwright.py +63 -0
- package/.agents/skills/rbt-manual-testing/SKILL.md +150 -0
- package/.agents/skills/rbt-manual-testing/examples/checkout_rbt_manual_suite.md +25 -0
- package/.agents/skills/security-dast-sentinel/SKILL.md +54 -135
- package/.agents/skills/security-dast-sentinel/examples/dast_scan_config.json +10 -0
- package/.agents/skills/self-healing-locator/SKILL.md +24 -10
- package/.agents/skills/self-healing-locator/examples/aom_diff_sample.json +16 -0
- package/.agents/skills/skillspector-security-guard/SKILL.md +32 -90
- package/.agents/skills/skillspector-security-guard/examples/safe_mcp_config.json +12 -0
- package/.agents/skills/surgical-coding-principles/SKILL.md +89 -0
- package/.agents/skills/surgical-coding-principles/examples/surgical_vs_scope_creep.md +46 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +3 -3
- package/.agents/skills/synthetic-test-data-generator/examples/idempotent_data_recipe.json +9 -0
- package/.agents/skills/systematic-debugging/SKILL.md +100 -0
- package/.agents/skills/systematic-debugging/examples/four_phase_case.md +44 -0
- package/.agents/skills/test-driven-development/SKILL.md +114 -0
- package/.agents/skills/test-driven-development/examples/red_green_refactor_qa.md +78 -0
- package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
- package/.agents/skills/token-and-context-optimizer/examples/distilled_log_sample.txt +3 -0
- package/.agents/skills/verification-before-completion/SKILL.md +100 -0
- package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +46 -0
- package/.agents/skills/visual-regression-sentinel/SKILL.md +21 -1
- package/.agents/skills/visual-regression-sentinel/examples/visual_mask_rules.json +9 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +3 -3
- package/.agents/skills/wcag-audit-patterns/examples/wcag_compliance_checklist.json +10 -0
- package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -541
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
- package/.agents/skills/web-vitals-and-performance-profiler/examples/cwv_budget_policy.json +6 -0
- package/.agents/templates/completion_report.md +57 -57
- package/.agents/workflows/analyze_requirement_document.md +18 -0
- package/.agents/workflows/export_testcases_excel.md +32 -0
- package/.agents/workflows/generate_combinatorial_test_data.md +28 -0
- package/.agents/workflows/generate_cross_module_test_plan.md +37 -0
- package/.agents/workflows/generate_manual_testcases_rbt.md +56 -0
- package/.agents/workflows/generate_requirements_from_website.md +25 -0
- package/.agents/workflows/generate_testcases_from_requirements.md +21 -0
- package/.agents/workflows/graphify.md +10 -10
- package/.agents/workflows/verify.md +2 -2
- package/.cursor/rules/language.mdc +7 -7
- package/.cursor/rules/synapse-qa.mdc +23 -0
- package/.cursorrules +7 -0
- package/.pre-commit-hooks.yaml +14 -0
- package/.windsurfrules +7 -0
- package/AGENTS.md +54 -15
- package/CLAUDE.md +35 -12
- package/LICENSE +30 -0
- package/NOTICE +180 -0
- package/README.en.md +325 -0
- package/README.md +247 -516
- package/action.yml +51 -0
- package/benchmarks/qa_eval/README.md +71 -0
- package/benchmarks/qa_eval/build_dataset.py +677 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +23 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/eval_runner.py +418 -0
- package/benchmarks/qa_eval/reports/AB_METHODOLOGY_REPORT.md +83 -0
- package/benchmarks/qa_eval/reports/BENCHMARK_REPORT.md +69 -0
- package/benchmarks/qa_eval/reports/eval_results.json +105 -0
- package/bin/agent-qa.js +10 -3
- package/docs/ARCHITECTURE.md +107 -0
- package/docs/CI_CD_INTEGRATION.md +94 -0
- package/docs/CLI_REFERENCE.md +157 -0
- package/docs/CONTRIBUTING.md +91 -0
- package/docs/PLUGIN_DEVELOPMENT_GUIDE.md +126 -0
- package/docs/TOKEN_QUOTA_PLAYBOOK.md +41 -0
- package/docs/adopted-ecosystem-tools.md +87 -0
- package/docs/schemas/synapse.config.schema.json +64 -0
- package/examples/cypress-ecommerce-suite/README.md +45 -0
- package/examples/cypress-ecommerce-suite/cypress/e2e/checkout.cy.js +17 -0
- package/examples/cypress-ecommerce-suite/cypress/pages/BasePage.js +29 -0
- package/examples/cypress-ecommerce-suite/cypress/pages/CartPage.js +51 -0
- package/examples/cypress-ecommerce-suite/cypress.config.js +13 -0
- package/examples/cypress-ecommerce-suite/package.json +13 -0
- package/examples/katalon-suite/Keywords/pages/BasePage.groovy +19 -0
- package/examples/katalon-suite/Keywords/pages/DashboardPage.groovy +29 -0
- package/examples/katalon-suite/Keywords/pages/LoginPage.groovy +45 -0
- package/examples/katalon-suite/Profiles/default.glbl +12 -0
- package/examples/katalon-suite/README.md +18 -0
- package/examples/katalon-suite/Scripts/Auth/TC_LoginSuccess/Script1234567890.groovy +12 -0
- package/examples/katalon-suite/Scripts/Dashboard/TC_ViewSummary/Script1234567891.groovy +15 -0
- package/examples/katalon-suite/katalon_suite.prj +21 -0
- package/examples/playwright-crm-suite/README.md +40 -0
- package/examples/playwright-crm-suite/package.json +14 -0
- package/examples/playwright-crm-suite/pages/BasePage.ts +37 -0
- package/examples/playwright-crm-suite/pages/CustomerPage.ts +57 -0
- package/examples/playwright-crm-suite/pages/LoginPage.ts +32 -0
- package/examples/playwright-crm-suite/playwright.config.ts +27 -0
- package/examples/playwright-crm-suite/tests/auth.spec.ts +22 -0
- package/examples/playwright-crm-suite/tests/customers.spec.ts +33 -0
- package/examples/plugins/sample-custom-plugin/README.md +14 -0
- package/examples/plugins/sample-custom-plugin/plugin.json +16 -0
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/SKILL.md +29 -0
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/examples/audit_fixture.json +8 -0
- package/examples/plugins/sample-custom-plugin/tools/custom_security_headers.py +125 -0
- package/examples/practices/README.md +22 -0
- package/examples/practices/requirements/crm/requirements_contracts.md +292 -0
- package/examples/practices/requirements/crm/requirements_customers.md +462 -0
- package/examples/practices/requirements/crm/requirements_login.md +202 -0
- package/examples/practices/requirements/crm/requirements_projects.md +396 -0
- package/examples/practices/requirements/crm/requirements_tasks.md +343 -0
- package/examples/practices/requirements/ecommerce/requirements_category_brand.txt +402 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/2026-04-03_04-28-27.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/KAN-1_requirement.md +52 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-19.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-57.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/KAN-4_requirement.md +119 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-5_overview.md +9 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-08.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-23.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/KAN-6_requirement.md +32 -0
- package/examples/practices/testcases/api/book_management_api_test_cases.md +144 -0
- package/examples/practices/testcases/api/user_management_api_test_cases.md +139 -0
- package/examples/practices/testcases/crm/test_cases_crm_login.md +148 -0
- package/examples/practices/testcases/crm/test_cases_customers.md +156 -0
- package/examples/pytest-api-suite/README.md +45 -0
- package/examples/pytest-api-suite/conftest.py +31 -0
- package/examples/pytest-api-suite/pages/user_api_client.py +36 -0
- package/examples/pytest-api-suite/requirements.txt +2 -0
- package/examples/pytest-api-suite/tests/test_user_api.py +44 -0
- package/examples/selenium-suite/README.md +18 -0
- package/examples/selenium-suite/pages/base_page.py +27 -0
- package/examples/selenium-suite/pages/login_page.py +29 -0
- package/examples/selenium-suite/pages/order_page.py +25 -0
- package/examples/selenium-suite/pytest.ini +6 -0
- package/examples/selenium-suite/requirements.txt +2 -0
- package/examples/selenium-suite/tests/test_login.py +10 -0
- package/examples/selenium-suite/tests/test_order.py +10 -0
- package/package.json +22 -7
- package/scripts/generate_skill_examples.py +244 -0
- package/scripts/install.ps1 +112 -0
- package/scripts/install_global_plugin.ps1 +94 -0
- package/scripts/install_global_plugin.sh +49 -0
- package/scripts/lint_skills.ps1 +57 -0
- package/scripts/package_dist.py +323 -0
- package/scripts/setup.sh +59 -0
- package/scripts/update_hub.ps1 +19 -0
- package/scripts/update_hub.sh +15 -0
- package/skills_manifest.json +209 -6
- package/synapse.config.json +30 -0
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +7 -7
- package/templates/PROJECT_STATE_TEMPLATE.md +1 -1
- package/templates/ai_bridges/CLAUDE.md +33 -16
- package/templates/ai_bridges/copilot-instructions.md +14 -21
- package/templates/ai_bridges/cursor_synapse_qa.mdc +17 -20
- package/templates/ai_bridges/windsurfrules +21 -21
- package/templates/ci/github-actions-synapse.yml +71 -4
- package/templates/welcome.html.tmpl +1674 -0
- package/tools/.infer_baseline.json +1102 -0
- package/tools/TOOLS_RUNBOOK.md +92 -130
- package/tools/__init__.py +26 -0
- package/tools/agent_canary.py +501 -501
- package/tools/analyze_project.py +31 -17
- package/tools/api_fuzzer.py +278 -6
- package/tools/author_integrity.py +6 -6
- package/tools/auto_heal.py +1 -1
- package/tools/browser_helpers.js +105 -7
- package/tools/bug_reporter.py +31 -1
- package/tools/bump_version.py +23 -3
- package/tools/cache_resolver.py +248 -14
- package/tools/check_env.py +55 -14
- package/tools/check_version_sync.py +153 -0
- package/tools/ci_healing_hook.py +1 -1
- package/tools/cli.py +263 -13
- package/tools/count_tokens.py +1257 -0
- package/tools/dashboard_generator.py +2638 -0
- package/tools/db_sandbox.py +198 -20
- package/tools/e2e_flow_prober.ps1 +12 -64
- package/tools/e2e_flow_prober.py +99 -0
- package/tools/e2e_flow_prober.sh +4 -0
- package/tools/flaky_detector.py +4 -3
- package/tools/generate_allure_report.ps1 +12 -29
- package/tools/generate_allure_report.py +77 -0
- package/tools/generate_allure_report.sh +4 -0
- package/tools/generate_jmx_plan.py +257 -58
- package/tools/generate_test_reports.ps1 +12 -134
- package/tools/generate_test_reports.py +133 -0
- package/tools/generate_test_reports.sh +4 -0
- package/tools/gherkin_suite.py +758 -0
- package/tools/git_pre_commit_guard.py +12 -6
- package/tools/goldfish_review.py +366 -0
- package/tools/heal_pr_automator.py +1 -1
- package/tools/heuristic_locator_matcher.py +170 -21
- package/tools/infer_linter.py +612 -70
- package/tools/lint_ai_docs.py +64 -0
- package/tools/matrix_sharder.py +1 -1
- package/tools/mcp_excel_server.ps1 +12 -250
- package/tools/mcp_excel_server.py +249 -0
- package/tools/mcp_excel_server.sh +4 -0
- package/tools/mcp_server.py +368 -144
- package/tools/md_to_xlsx.py +307 -0
- package/tools/micro_mutator.py +300 -69
- package/tools/omni_transpiler.py +2 -2
- package/tools/output_formatter.py +128 -0
- package/tools/pinpoint_failure.py +88 -88
- package/tools/project_state.py +2 -2
- package/tools/repo_map.py +1 -1
- package/tools/safety_firewall.py +196 -45
- package/tools/setup_project.py +294 -30
- package/tools/spec_drift_sentinel.py +439 -0
- package/tools/sync_agent_rules.py +248 -0
- package/tools/telemetry_correlator.py +45 -0
- package/tools/test_impact.py +51 -12
- package/tools/tms_sync_bridge.py +21 -5
- package/tools/universal_ir.py +3 -0
- package/tools/update_sentinel.py +87 -15
- package/tools/verify_all.ps1 +13 -0
- package/tools/verify_all.py +463 -79
- package/tools/visual_diff_canvas.py +1 -0
- package/tools/visual_som_helper.py +1 -1
- package/tools/watch_mode.py +305 -0
- package/tools/welcome_page.py +291 -0
package/tools/verify_all.py
CHANGED
|
@@ -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:
|
|
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]
|
|
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.
|
|
156
|
+
# 1. Author metadata hash verification
|
|
152
157
|
if not verify_author_fingerprint():
|
|
153
|
-
print("[FAIL]
|
|
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
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
170
|
+
print(" Under MIT License, original author copyright notice to 'Thế Vị (@TeeVee998)' must be preserved in LICENSE.")
|
|
165
171
|
return False
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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)")
|
|
183
220
|
except Exception:
|
|
184
221
|
pass
|
|
185
222
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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):
|
|
@@ -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("[
|
|
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("[
|
|
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,10 +317,21 @@ 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
|
-
|
|
270
|
-
|
|
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"))
|
|
271
335
|
except Exception:
|
|
272
336
|
pass
|
|
273
337
|
|
|
@@ -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
|
-
|
|
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):
|
|
@@ -401,12 +473,221 @@ 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
|
-
|
|
409
|
-
|
|
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
|
|
410
691
|
|
|
411
692
|
test_dirs = ["Scripts", "tests", "test", "e2e", "specs", "cypress"]
|
|
412
693
|
has_tests = False
|
|
@@ -423,7 +704,7 @@ def gate_7_infer_linter(repo_root, mode="AUTO"):
|
|
|
423
704
|
break
|
|
424
705
|
|
|
425
706
|
if not has_tests:
|
|
426
|
-
print("[
|
|
707
|
+
print("[N/A] No test scripts (Groovy/TS/JS/Py) present in candidate test directories.")
|
|
427
708
|
return True
|
|
428
709
|
|
|
429
710
|
infer_script = os.path.join(SCRIPT_DIR, "infer_linter.py")
|
|
@@ -431,17 +712,22 @@ def gate_7_infer_linter(repo_root, mode="AUTO"):
|
|
|
431
712
|
infer_script = os.path.join(repo_root, "tools", "infer_linter.py")
|
|
432
713
|
|
|
433
714
|
if os.path.exists(infer_script):
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
715
|
+
try:
|
|
716
|
+
res = subprocess.run([sys.executable, infer_script, "--strict", "--dir", target_scan_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=60.0)
|
|
717
|
+
if res.returncode != 0:
|
|
718
|
+
print("[FAIL] Static Defect Sentinel detected critical issues in test scripts:")
|
|
719
|
+
print(res.stdout)
|
|
720
|
+
return False
|
|
721
|
+
except subprocess.TimeoutExpired:
|
|
722
|
+
print("[FAIL] infer_linter.py timed out after 60s!")
|
|
438
723
|
return False
|
|
439
|
-
print("[PASS] Zero critical defects / zero swallowed exceptions across test
|
|
724
|
+
print("[PASS] Zero critical defects / zero swallowed exceptions across active test suites.")
|
|
440
725
|
return True
|
|
441
726
|
|
|
442
727
|
|
|
443
|
-
def gate_8_micro_mutator(repo_root, mode="AUTO"):
|
|
444
|
-
|
|
728
|
+
def gate_8_micro_mutator(repo_root, mode="AUTO", test_cmd=""):
|
|
729
|
+
eval_mode_desc = f"DYNAMIC EXECUTION MODE ({test_cmd})" if test_cmd else "HEURISTIC STATIC MODE"
|
|
730
|
+
print(f"\n[GATE 8] Running Assertion Sensitivity & Syntactic Mutation Sentinel [{eval_mode_desc}] (Min Score >= 80%)...")
|
|
445
731
|
scripts_dir = os.path.join(repo_root, "Scripts")
|
|
446
732
|
tests_dir = os.path.join(repo_root, "tests")
|
|
447
733
|
|
|
@@ -450,17 +736,17 @@ def gate_8_micro_mutator(repo_root, mode="AUTO"):
|
|
|
450
736
|
groovy_files = [f for f in glob.glob(os.path.join(scripts_dir, "**", "*.groovy"), recursive=True)]
|
|
451
737
|
if groovy_files:
|
|
452
738
|
target_dir = scripts_dir
|
|
453
|
-
|
|
739
|
+
if not target_dir and os.path.isdir(tests_dir):
|
|
454
740
|
test_files = [f for f in glob.glob(os.path.join(tests_dir, "**", "*.*"), recursive=True) if f.endswith((".py", ".ts", ".js", ".groovy"))]
|
|
455
741
|
if test_files:
|
|
456
742
|
target_dir = tests_dir
|
|
457
743
|
|
|
458
744
|
if not target_dir:
|
|
459
745
|
if mode == "HUB":
|
|
460
|
-
print("[
|
|
746
|
+
print(f"[N/A] Hub Mode: Not Applicable (Assertion Sensitivity & Syntactic Mutation applies to target test suites in Spokes) [{eval_mode_desc}].")
|
|
461
747
|
return True
|
|
462
748
|
else:
|
|
463
|
-
print("[
|
|
749
|
+
print("[N/A] No automated test scripts found in Scripts/ or tests/ to mutate.")
|
|
464
750
|
return True
|
|
465
751
|
|
|
466
752
|
mutator_script = os.path.join(SCRIPT_DIR, "micro_mutator.py")
|
|
@@ -468,13 +754,20 @@ def gate_8_micro_mutator(repo_root, mode="AUTO"):
|
|
|
468
754
|
mutator_script = os.path.join(repo_root, "tools", "micro_mutator.py")
|
|
469
755
|
|
|
470
756
|
if os.path.exists(mutator_script):
|
|
471
|
-
|
|
757
|
+
cmd = [sys.executable, mutator_script, target_dir, "--min-score", "80.0", "--json"]
|
|
758
|
+
if test_cmd:
|
|
759
|
+
cmd.extend(["--test-cmd", test_cmd])
|
|
760
|
+
try:
|
|
761
|
+
res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=120.0)
|
|
762
|
+
except subprocess.TimeoutExpired:
|
|
763
|
+
print("[FAIL] Assertion Sensitivity Sentinel timed out after 120s!")
|
|
764
|
+
return False
|
|
472
765
|
if res.returncode != 0:
|
|
473
|
-
print("[FAIL]
|
|
766
|
+
print("[FAIL] Assertion Sensitivity Sentinel REJECTED: Score < 80% or unasserted logic found.")
|
|
474
767
|
try:
|
|
475
768
|
data = json.loads(res.stdout)
|
|
476
|
-
print(f"
|
|
477
|
-
print(f" Mutants
|
|
769
|
+
print(f" Assertion Sensitivity: {data.get('mutation_score', 0)}% (Required: >= 80%)")
|
|
770
|
+
print(f" Mutants Caught: {data.get('killed', 0)} / {data.get('total_mutants', 0)}")
|
|
478
771
|
except Exception:
|
|
479
772
|
if res.stdout:
|
|
480
773
|
print(res.stdout)
|
|
@@ -482,54 +775,121 @@ def gate_8_micro_mutator(repo_root, mode="AUTO"):
|
|
|
482
775
|
else:
|
|
483
776
|
try:
|
|
484
777
|
data = json.loads(res.stdout)
|
|
485
|
-
|
|
778
|
+
mode_label = "DYNAMIC" if test_cmd else "HEURISTIC STATIC"
|
|
779
|
+
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.")
|
|
486
780
|
except Exception:
|
|
487
|
-
print("[PASS]
|
|
781
|
+
print(f"[PASS] Assertion Sensitivity Sentinel passed: Score >= 80% [{eval_mode_desc}].")
|
|
488
782
|
return True
|
|
489
783
|
|
|
490
|
-
print("[PASS]
|
|
784
|
+
print("[PASS] Assertion Sensitivity verified.")
|
|
491
785
|
return True
|
|
492
786
|
|
|
493
787
|
|
|
494
|
-
def
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
788
|
+
def load_synapse_config(repo_root=None, config_path=None):
|
|
789
|
+
"""Loads configuration from synapse.config.json if present."""
|
|
790
|
+
if config_path and os.path.exists(config_path):
|
|
791
|
+
try:
|
|
792
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
793
|
+
return json.load(f)
|
|
794
|
+
except (OSError, json.JSONDecodeError):
|
|
795
|
+
pass
|
|
796
|
+
root = repo_root or REPO_ROOT
|
|
797
|
+
cand = os.path.join(root, "synapse.config.json")
|
|
798
|
+
if os.path.exists(cand):
|
|
799
|
+
try:
|
|
800
|
+
with open(cand, "r", encoding="utf-8") as f:
|
|
801
|
+
return json.load(f)
|
|
802
|
+
except (OSError, json.JSONDecodeError):
|
|
803
|
+
pass
|
|
804
|
+
return {}
|
|
503
805
|
|
|
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
806
|
|
|
510
|
-
|
|
807
|
+
def format_gates_junit(gate_results, repo_root):
|
|
808
|
+
"""Formats 9 DoD Quality Gates result into JUnit XML testsuite."""
|
|
809
|
+
total = len(gate_results)
|
|
810
|
+
failures = len([g for g in gate_results if not g[1]])
|
|
811
|
+
lines = [
|
|
812
|
+
'<?xml version="1.0" encoding="utf-8"?>',
|
|
813
|
+
f'<testsuite name="synapse-quality-gates" tests="{total}" failures="{failures}" errors="0">'
|
|
814
|
+
]
|
|
815
|
+
for name, success, reason in gate_results:
|
|
816
|
+
cname = "synapse.quality_gates"
|
|
817
|
+
tname = name.split(":")[0].strip().replace(" ", "_")
|
|
818
|
+
lines.append(f' <testcase classname="{cname}" name="{tname}">')
|
|
819
|
+
if not success:
|
|
820
|
+
lines.append(f' <failure message="Quality Gate Failed: {name}">{name} failed in repository {repo_root} ({reason})</failure>')
|
|
821
|
+
lines.append(' </testcase>')
|
|
822
|
+
lines.append('</testsuite>')
|
|
823
|
+
return "\n".join(lines)
|
|
824
|
+
|
|
511
825
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
826
|
+
def run_all_gates(repo_root, mode="AUTO", test_cmd="", output_format="text", config_path=None):
|
|
827
|
+
"""Programmatic entrypoint to execute all 9 DoD Quality Gates with config and format support."""
|
|
828
|
+
effective_mode = detect_repo_mode(repo_root) if mode.upper() == "AUTO" else mode.upper()
|
|
829
|
+
cfg = load_synapse_config(repo_root, config_path=config_path)
|
|
830
|
+
qg_cfg = cfg.get("quality_gates", {})
|
|
831
|
+
gate_toggles = qg_cfg.get("gates", {})
|
|
832
|
+
|
|
833
|
+
if output_format == "text":
|
|
834
|
+
print_header(f"ENTERPRISE AUTOMATION - ONE-COMMAND DoD VERIFICATION [{effective_mode} MODE]")
|
|
835
|
+
print(f"Target Repository: {repo_root}")
|
|
836
|
+
print(f"Operational Mode : {effective_mode}")
|
|
515
837
|
|
|
516
838
|
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:
|
|
839
|
+
("gate_0_author_integrity", "Gate 0: Author Attribution & Integrity", gate_0_author_integrity),
|
|
840
|
+
("gate_1_bom_check", "Gate 1: UTF-8 No BOM", gate_1_bom_check),
|
|
841
|
+
("gate_2_path_portability", "Gate 2: Path Portability", gate_2_path_portability),
|
|
842
|
+
("gate_3_clean_pom", "Gate 3: Clean POM & Flat Architecture", gate_3_clean_pom),
|
|
843
|
+
("gate_4_ai_docs_lint", "Gate 4: AI Documentation Linter", gate_4_ai_docs_lint),
|
|
844
|
+
("gate_5_cleanup_blacklist", "Gate 5: Temporary Files Blacklist", gate_5_cleanup_blacklist),
|
|
845
|
+
("gate_6_credential_security", "Gate 6: Credential Security", gate_6_credential_security),
|
|
846
|
+
("gate_7_infer_linter", "Gate 7: Static Defect Sentinel", gate_7_infer_linter),
|
|
847
|
+
("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
848
|
]
|
|
527
849
|
|
|
528
850
|
all_passed = True
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
851
|
+
gate_results = []
|
|
852
|
+
|
|
853
|
+
if output_format in ("json", "junit"):
|
|
854
|
+
old_stdout = sys.stdout
|
|
855
|
+
sys.stdout = io.StringIO()
|
|
856
|
+
try:
|
|
857
|
+
for g_key, name, gate_fn in gates:
|
|
858
|
+
if gate_toggles.get(g_key) is False:
|
|
859
|
+
gate_results.append((name, True, "SKIPPED_BY_CONFIG"))
|
|
860
|
+
continue
|
|
861
|
+
success = gate_fn(repo_root, mode=effective_mode)
|
|
862
|
+
gate_results.append((name, success, "PASS" if success else "FAIL"))
|
|
863
|
+
if not success:
|
|
864
|
+
all_passed = False
|
|
865
|
+
finally:
|
|
866
|
+
sys.stdout = old_stdout
|
|
867
|
+
else:
|
|
868
|
+
for g_key, name, gate_fn in gates:
|
|
869
|
+
if gate_toggles.get(g_key) is False:
|
|
870
|
+
print(f"\n[{name.split(':')[0].upper()}] Disabled via synapse.config.json (SKIPPED)")
|
|
871
|
+
gate_results.append((name, True, "SKIPPED_BY_CONFIG"))
|
|
872
|
+
continue
|
|
873
|
+
|
|
874
|
+
success = gate_fn(repo_root, mode=effective_mode)
|
|
875
|
+
gate_results.append((name, success, "PASS" if success else "FAIL"))
|
|
876
|
+
if not success:
|
|
877
|
+
all_passed = False
|
|
878
|
+
|
|
879
|
+
if output_format == "json":
|
|
880
|
+
res_doc = {
|
|
881
|
+
"timestamp": datetime.now().isoformat(),
|
|
882
|
+
"target": repo_root,
|
|
883
|
+
"mode": effective_mode,
|
|
884
|
+
"all_passed": all_passed,
|
|
885
|
+
"gates": [{"name": g[0], "passed": g[1], "status": g[2]} for g in gate_results]
|
|
886
|
+
}
|
|
887
|
+
print(json.dumps(res_doc, indent=2))
|
|
888
|
+
return 0 if all_passed else 1
|
|
889
|
+
|
|
890
|
+
if output_format == "junit":
|
|
891
|
+
print(format_gates_junit(gate_results, repo_root))
|
|
892
|
+
return 0 if all_passed else 1
|
|
533
893
|
|
|
534
894
|
print("\n" + "=" * 70)
|
|
535
895
|
if all_passed:
|
|
@@ -544,5 +904,29 @@ def main():
|
|
|
544
904
|
return 1
|
|
545
905
|
|
|
546
906
|
|
|
907
|
+
def main():
|
|
908
|
+
parser = argparse.ArgumentParser(description="Enterprise DoD Verification Sentinel")
|
|
909
|
+
parser.add_argument("target", nargs="?", default=None,
|
|
910
|
+
help="Target repository path to verify (defaults to auto-detection from CWD or Hub)")
|
|
911
|
+
parser.add_argument("--repo", "--path", dest="repo_path", default=None,
|
|
912
|
+
help="Target repository path to verify")
|
|
913
|
+
parser.add_argument("--mode", choices=["auto", "hub", "target"], default="auto",
|
|
914
|
+
help="Execution mode (auto-detect, hub, or target test project)")
|
|
915
|
+
parser.add_argument("--format", choices=["text", "json", "junit"], default="text",
|
|
916
|
+
help="Output report format (default: text)")
|
|
917
|
+
parser.add_argument("--config", help="Path to synapse.config.json")
|
|
918
|
+
parser.add_argument("--test-cmd", default="",
|
|
919
|
+
help="Optional test command for Gate 8 dynamic mutation testing (e.g. 'pytest' or 'npx playwright test')")
|
|
920
|
+
args = parser.parse_args()
|
|
921
|
+
|
|
922
|
+
try:
|
|
923
|
+
repo_root = resolve_target_repo(cli_target=args.target, cli_repo=args.repo_path)
|
|
924
|
+
except FileNotFoundError as e:
|
|
925
|
+
print(f"[ERROR] {e}")
|
|
926
|
+
return 1
|
|
927
|
+
|
|
928
|
+
return run_all_gates(repo_root, mode=args.mode, test_cmd=args.test_cmd, output_format=args.format, config_path=args.config)
|
|
929
|
+
|
|
930
|
+
|
|
547
931
|
if __name__ == "__main__":
|
|
548
932
|
sys.exit(main())
|