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.
- package/.agents/agents/failure-analyst.md +11 -0
- package/.agents/agents/live-explorer.md +28 -0
- package/.agents/agents/orchestrator.md +23 -4
- package/.agents/agents/pom-crafter.md +15 -0
- package/.agents/agents/quality-gatekeeper.md +20 -4
- package/.agents/agents/test-runner.md +12 -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 +331 -0
- package/README.md +252 -515
- 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 +16 -4
- 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 +27 -12
- 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 +98 -131
- 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 +30 -3
- package/tools/cache_resolver.py +248 -14
- package/tools/check_env.py +55 -14
- package/tools/check_version_sync.py +171 -0
- package/tools/ci_healing_hook.py +1 -1
- package/tools/cli.py +454 -42
- 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 +302 -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 +295 -31
- package/tools/skillspector.py +31 -0
- 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 +479 -85
- 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
|
-
|
|
183
|
-
|
|
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
|
-
|
|
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):
|
|
@@ -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
|
|
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("[
|
|
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,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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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("[
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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("[
|
|
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("[
|
|
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
|
-
|
|
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]
|
|
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"
|
|
477
|
-
print(f" Mutants
|
|
478
|
-
except
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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]
|
|
794
|
+
print("[PASS] Assertion Sensitivity verified.")
|
|
491
795
|
return True
|
|
492
796
|
|
|
493
797
|
|
|
494
|
-
def
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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:
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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())
|