diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abb5b50a5ce..7c78ec52a8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -317,6 +317,32 @@ jobs: - name: Check docs run: pnpm check:docs + skills-python: + needs: [docs-scope, changed-scope] + if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true') + runs-on: blacksmith-16vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: false + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Python tooling + run: | + python -m pip install --upgrade pip + python -m pip install pytest ruff + + - name: Lint Python skill scripts + run: python -m ruff check skills + + - name: Test skill Python scripts + run: python -m pytest -q skills + secrets: runs-on: blacksmith-16vcpu-ubuntu-2404 steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e946d18c112..e6f6faca112 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -69,6 +69,24 @@ repos: args: [--persona=regular, --min-severity=medium, --min-confidence=medium] exclude: "^(vendor/|Swabble/)" + # Python checks for skills scripts + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.1 + hooks: + - id: ruff + files: "^skills/.*\\.py$" + args: [--config, pyproject.toml] + + - repo: local + hooks: + - id: skills-python-tests + name: skills python tests + entry: pytest -q skills + language: python + additional_dependencies: [pytest>=8,<9] + pass_filenames: false + files: "^skills/.*\\.py$" + # Project checks (same commands as CI) - repo: local hooks: diff --git a/CHANGELOG.md b/CHANGELOG.md index ad66f56bb6d..fb3a6d647b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,11 @@ Docs: https://docs.openclaw.ai ### Breaking ### Fixes + - Security/Skills: escape user-controlled prompt, filename, and output-path values in `openai-image-gen` HTML gallery generation to prevent stored XSS in generated `index.html` output. (#12538) Thanks @CornBrother0x. - Security/OTEL: redact sensitive values (API keys, tokens, credential fields) from diagnostics-otel log bodies, log attributes, and error/reason span fields before OTLP export. (#12542) Thanks @brandonwise. - Providers/OpenRouter: remove conflicting top-level `reasoning_effort` when injecting nested `reasoning.effort`, preventing OpenRouter 400 payload-validation failures for reasoning models. (#24120) thanks @tenequm. +- Skills/Python: add CI + pre-commit linting (`ruff`) and pytest discovery coverage for Python scripts/tests under `skills/`, including package test execution from repo root. Thanks @vincentkoc. ## 2026.2.23 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..b69da03be53 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[tool.ruff] +target-version = "py310" +line-length = 100 + +[tool.ruff.lint] +select = ["E9", "F63", "F7", "F82", "I"] + +[tool.pytest.ini_options] +testpaths = ["skills"] +python_files = ["test_*.py"] diff --git a/skills/skill-creator/scripts/test_package_skill.py b/skills/skill-creator/scripts/test_package_skill.py index 59407b6cb15..e8567beaaf6 100644 --- a/skills/skill-creator/scripts/test_package_skill.py +++ b/skills/skill-creator/scripts/test_package_skill.py @@ -10,6 +10,10 @@ import zipfile from pathlib import Path from unittest import TestCase, main +SCRIPT_DIR = Path(__file__).resolve().parent +if str(SCRIPT_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPT_DIR)) + fake_quick_validate = types.ModuleType("quick_validate") fake_quick_validate.validate_skill = lambda _path: (True, "Skill is valid!")