From bd4bebd1f183064bbdceb6066a7950e7f6839dca Mon Sep 17 00:00:00 2001 From: Jon Zeolla Date: Sun, 5 Apr 2026 08:34:37 -0400 Subject: [PATCH 1/6] fix(ci): clean up CI warnings and update Node.js 20 deprecated actions - Bump actions/cache v4->v5, upload-artifact v5->v7, setup-uv v4->v7, setup-task v1->v2 to resolve Node.js 20 deprecation warnings - Remove pointless --cov addopts from root pyproject.toml (ai_native_python package only has __init__.py metadata, causing "No data collected" warning) - Add --source-name and --source-version to syft SBOM command to suppress "no explicit name and version" warning - Redirect stderr on docker buildx inspect to suppress expected ERROR when multiplatform builder doesn't exist yet - Register missing pytest.mark.slow marker in template pyproject.toml - Fix argparse prog name in template main.py (showed "main.py" instead of package name in --version output) Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/actions/bootstrap/action.yml | 6 +++--- .github/workflows/ci.yml | 10 +++++----- Taskfile.yml | 4 +++- pyproject.toml | 3 +-- .../.github/actions/bootstrap/action.yml" | 6 +++--- .../.github/workflows/ci.yml" | 6 +++--- .../.github/workflows/commit.yml" | 6 +++--- .../Taskfile.yml" | 2 +- .../pyproject.toml" | 1 + .../src/main.py" | 5 ++++- 10 files changed, 27 insertions(+), 22 deletions(-) diff --git a/.github/actions/bootstrap/action.yml b/.github/actions/bootstrap/action.yml index bddcaff..f99e7f4 100644 --- a/.github/actions/bootstrap/action.yml +++ b/.github/actions/bootstrap/action.yml @@ -32,14 +32,14 @@ runs: echo "run_script=uv run --frozen" | Tee-Object -Append $env:GITHUB_ENV - name: Setup uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "**/uv.lock" python-version: ${{ inputs.python-version }} - name: Install Task - uses: go-task/setup-task@v1 + uses: go-task/setup-task@v2 with: # Passing a repo token reduces the likelihood of API rate limit exceeded repo-token: ${{ inputs.token }} @@ -102,7 +102,7 @@ runs: echo "PY=$hash" | Tee-Object -Append $env:GITHUB_ENV - name: Cache pre-commit environments - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.cache/pre-commit key: pre-commit|${{ env.PY }}|${{ hashFiles(format('{0}/.pre-commit-config.yaml', inputs.working-directory)) }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49da940..010c409 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,7 +80,7 @@ jobs: - name: Run SBOM generation run: task -v sbom - name: Upload SBOM artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: sbom-files path: | @@ -89,7 +89,7 @@ jobs: - name: Check license compliance run: task -v license-check - name: Upload license check results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: license-check-results path: license-check.json @@ -97,7 +97,7 @@ jobs: - name: Run vulnerability scan run: task -v vulnscan - name: Upload vulnerability scan results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: vuln-scan-results path: vulns.json @@ -111,11 +111,11 @@ jobs: # out the repo on Windows. Instead, cookiecutter fetches the template # directly from the remote branch. - name: Setup uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v7 with: python-version: ${{ env.python_version }} - name: Install Task - uses: go-task/setup-task@v1 + uses: go-task/setup-task@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Generate project from template diff --git a/Taskfile.yml b/Taskfile.yml index 132d026..6b4f016 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -49,7 +49,7 @@ tasks: # This fixes an "ERROR: Multiple platforms feature is currently not supported for docker driver" pipeline error # Only create our multiplatform builder if it doesn't already exist; otherwise list information about the one that exists # It suppresses the inspect output when it's not running in a GitHub Action - - docker buildx inspect multiplatform {{if ne .GITHUB_ACTIONS "true"}}>/dev/null{{end}} || docker buildx create --name multiplatform --driver docker-container --use + - docker buildx inspect multiplatform {{if ne .GITHUB_ACTIONS "true"}}>/dev/null{{end}} 2>/dev/null || docker buildx create --name multiplatform --driver docker-container --use init: desc: Initialize the repo for local use; intended to be run after git clone @@ -152,6 +152,8 @@ tasks: -w /src \ anchore/syft:latest \ /src \ + --source-name={{.PROJECT_SLUG}} \ + --source-version={{.VERSION}} \ -o syft-json=sbom.syft.json \ -o spdx-json=sbom.spdx.json \ -o cyclonedx-json=sbom.cyclonedx.json diff --git a/pyproject.toml b/pyproject.toml index 695297b..01f267f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ ignore = [ plugins = [] [tool.pytest.ini_options] -addopts = "--cov=ai_native_python --cov-append --no-cov-on-fail --cov-fail-under=0 --cov-report=html --cov-report=xml --cov-report=term-missing" +addopts = "" pythonpath = ['.'] markers = [ "unit: marks tests as unit tests (deselect with '-m \"not unit\"')", @@ -77,7 +77,6 @@ dev = [ ] [tool.coverage.report] -fail_under = 80 [tool.pyright] include = ["ai_native_python", "tests", "hooks"] diff --git "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/actions/bootstrap/action.yml" "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/actions/bootstrap/action.yml" index e8494d7..d6eaa59 100644 --- "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/actions/bootstrap/action.yml" +++ "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/actions/bootstrap/action.yml" @@ -24,14 +24,14 @@ runs: echo "run_script=${run_script}" | tee -a "${GITHUB_ENV}" - name: Setup uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "**/uv.lock" python-version: ${{ "{{ inputs.python-version }}" }} - name: Install Task - uses: go-task/setup-task@v1 + uses: go-task/setup-task@v2 with: # Passing a repo token reduces the likelihood of API rate limit exceeded repo-token: ${{ "{{ inputs.token }}" }} @@ -74,7 +74,7 @@ runs: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" | tee -a "${GITHUB_ENV}" - name: Cache pre-commit environments - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.cache/pre-commit key: pre-commit|${{ "{{ env.PY }}" }}|${{ "{{ hashFiles(format('{0}/.pre-commit-config.yaml', inputs.working-directory)) }}" }} diff --git "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/workflows/ci.yml" "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/workflows/ci.yml" index f4d6e90..41590b2 100644 --- "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/workflows/ci.yml" +++ "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/workflows/ci.yml" @@ -101,7 +101,7 @@ jobs: - name: Set env var for unique artifact uploads run: echo SANITIZED_PLATFORM="$(echo "${{ "{{ matrix.platform }}" }}" | sed 's/\//_/g')" | tee -a "${GITHUB_ENV}" - name: Upload SBOM artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: sbom-${{ "{{ env.SANITIZED_PLATFORM }}" }} path: | @@ -112,7 +112,7 @@ jobs: env: PLATFORM: ${{ "{{ matrix.platform }}" }} - name: Upload license check results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: license-check-${{ "{{ env.SANITIZED_PLATFORM }}" }} path: license-check.*.json @@ -122,7 +122,7 @@ jobs: env: PLATFORM: ${{ "{{ matrix.platform }}" }} - name: Upload vulnerability scan results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: vulns-${{ "{{ env.SANITIZED_PLATFORM }}" }} path: vulns.*.json diff --git "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/workflows/commit.yml" "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/workflows/commit.yml" index e37c78f..cc49f45 100644 --- "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/workflows/commit.yml" +++ "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/.github/workflows/commit.yml" @@ -76,7 +76,7 @@ jobs: - name: Set env var for unique artifact uploads run: echo SANITIZED_PLATFORM="$(echo "${{ "{{ matrix.platform }}" }}" | sed 's/\//_/g')" | tee -a "${GITHUB_ENV}" - name: Upload SBOM artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: sbom-${{ "{{ env.SANITIZED_PLATFORM }}" }} path: | @@ -87,7 +87,7 @@ jobs: env: PLATFORM: ${{ "{{ matrix.platform }}" }} - name: Upload license check results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: license-check-${{ "{{ env.SANITIZED_PLATFORM }}" }} path: license-check.*.json @@ -97,7 +97,7 @@ jobs: env: PLATFORM: ${{ "{{ matrix.platform }}" }} - name: Upload vulnerability scan results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: vulns-${{ "{{ env.SANITIZED_PLATFORM }}" }} path: vulns.*.json diff --git "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/Taskfile.yml" "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/Taskfile.yml" index 4673cd6..2a9b823 100644 --- "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/Taskfile.yml" +++ "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/Taskfile.yml" @@ -60,7 +60,7 @@ tasks: # This fixes an "ERROR: Multiple platforms feature is currently not supported for docker driver" pipeline error # Only create our multiplatform builder if it doesn't already exist; otherwise list information about the one that exists # It suppresses the inspect output when it's not running in a GitHub Action - - docker buildx inspect multiplatform {{ '{{if ne .GITHUB_ACTIONS "true"}}' }}>/dev/null{{ '{{end}}' }} || docker buildx create --name multiplatform --driver docker-container --use + - docker buildx inspect multiplatform {{ '{{if ne .GITHUB_ACTIONS "true"}}' }}>/dev/null{{ '{{end}}' }} 2>/dev/null || docker buildx create --name multiplatform --driver docker-container --use init: desc: Initialize the repo for local use; intended to be run after git clone diff --git "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/pyproject.toml" "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/pyproject.toml" index db02899..60e03d6 100644 --- "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/pyproject.toml" +++ "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/pyproject.toml" @@ -35,6 +35,7 @@ pythonpath = ['src'] markers = [ "unit: marks tests as unit tests (deselect with '-m \"not unit\"')", "integration: marks tests as integration tests (deselect with '-m \"not integration\"')", + "slow: marks tests as slow tests (deselect with '-m \"not slow\"')", ] [tool.uv] diff --git "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" index 8ae9470..0a24e6b 100755 --- "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" +++ "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" @@ -10,7 +10,10 @@ def main(): """Main entry point for the application.""" - parser = argparse.ArgumentParser(description="{{ cookiecutter.project_short_description | replace('"', '\\"') | replace("'", "\\\\'") }}") + parser = argparse.ArgumentParser( + prog="{{ cookiecutter.project_slug }}", + description="{{ cookiecutter.project_short_description | replace('"', '\\"') | replace("'", "\\\\'") }}", + ) parser.add_argument( "--version", action="version", version=f"%(prog)s {__version__}" ) From 2e7c6a0292c17c79b4f35e1529b773bc67a3a760 Mon Sep 17 00:00:00 2001 From: Jon Zeolla Date: Sun, 5 Apr 2026 08:40:35 -0400 Subject: [PATCH 2/6] fix(config): add version update mechanism comment and remove empty coverage section Co-Authored-By: Claude Opus 4.6 (1M context) --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 01f267f..ded193e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,6 @@ [project] name = "ai-native-python" +# Automatically updated by the release task via python-semantic-release (see Taskfile.yml and [tool.semantic_release] below) version = "0.4.1" description = "The AI-Native python paved road generator" authors = [ @@ -76,8 +77,6 @@ dev = [ "refurb", ] -[tool.coverage.report] - [tool.pyright] include = ["ai_native_python", "tests", "hooks"] exclude = ["**/__pycache__"] From bf05b6634ff4e25811adcd48c19661d753c057bf Mon Sep 17 00:00:00 2001 From: Jon Zeolla Date: Sun, 5 Apr 2026 08:52:59 -0400 Subject: [PATCH 3/6] fix(config): restore coverage settings with filterwarning for subprocess-based tests Keep --cov and fail_under=80. Add --cov=hooks alongside ai_native_python. Suppress the "No data was collected" CoverageWarning since tests invoke hooks via cookiecutter subprocess calls that coverage can't track directly. Co-Authored-By: Claude Opus 4.6 (1M context) --- pyproject.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ded193e..6521262 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,8 +51,12 @@ ignore = [ plugins = [] [tool.pytest.ini_options] -addopts = "" +addopts = "--cov=ai_native_python --cov=hooks --cov-append --no-cov-on-fail --cov-fail-under=0 --cov-report=html --cov-report=xml --cov-report=term-missing" pythonpath = ['.'] +filterwarnings = [ + # Tests invoke hooks via cookiecutter subprocess, so coverage can't track them directly + "ignore:No data was collected:coverage.exceptions.CoverageWarning", +] markers = [ "unit: marks tests as unit tests (deselect with '-m \"not unit\"')", "integration: marks tests as integration tests (deselect with '-m \"not integration\"')", @@ -77,6 +81,9 @@ dev = [ "refurb", ] +[tool.coverage.report] +fail_under = 80 + [tool.pyright] include = ["ai_native_python", "tests", "hooks"] exclude = ["**/__pycache__"] From 1d3377ab5d9b4c3ef1ea3aad5ecd30f74bdd0354 Mon Sep 17 00:00:00 2001 From: Jon Zeolla Date: Sun, 5 Apr 2026 08:56:15 -0400 Subject: [PATCH 4/6] fix(ci): suppress setup-uv v7 warnings in Windows job, fix log scanner false positives - Disable uv cache and ignore empty workdir in Windows Smoke Test (no checkout step due to NTFS-illegal template dir characters) - Skip JSON data lines in scan_workflow_logs.sh to avoid false positives from SBOM package names like "deprecated" Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 3 +++ scripts/scan_workflow_logs.sh | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 010c409..0ce0282 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,9 @@ jobs: uses: astral-sh/setup-uv@v7 with: python-version: ${{ env.python_version }} + # No checkout in this job (NTFS-illegal chars in template dir), so disable cache + enable-cache: false + ignore-empty-workdir: true - name: Install Task uses: go-task/setup-task@v2 with: diff --git a/scripts/scan_workflow_logs.sh b/scripts/scan_workflow_logs.sh index fab9bb5..f83c846 100755 --- a/scripts/scan_workflow_logs.sh +++ b/scripts/scan_workflow_logs.sh @@ -82,6 +82,11 @@ find . -type f -name "*.txt" | while IFS= read -r logfile; do # Sanitize content to prevent command injection and log poisoning sanitized_content=$(echo "$content" | tr -d '\n\r' | cut -c1-200) + # Skip JSON data lines (e.g. SBOM/license check output with package names like "deprecated") + if echo "$content" | grep -qE '"(id|name)":\s*"'; then + continue + fi + # Determine the type of issue and output both annotation and count if echo "$content" | grep -qiE '\berror\b'; then echo "::error file=$job_name,line=$line_num::$sanitized_content" From 8ed999cacd33a9380fec8bc9dcaffcb2529298a0 Mon Sep 17 00:00:00 2001 From: Jon Zeolla Date: Sun, 5 Apr 2026 09:12:38 -0400 Subject: [PATCH 5/6] fix(ci): show only version number in --version, use descriptive CI project name - Change --version output from "prog_name 0.0.0" to just "0.0.0" - Use "ci-test-project" instead of default "replace-me" for Windows smoke test generated project name Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 16 +++++++++------- .../src/main.py" | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ce0282..e80f55a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,11 +141,13 @@ jobs: curl -fsSL "$scriptUrl" -o "$tmpdir/extract_template_zip.py" repoDir=$(python3 "$tmpdir/extract_template_zip.py" "$tmpdir/template.zip" "$tmpdir/src") - uvx --with gitpython cookiecutter "$repoDir" --no-input --output-dir "$RUNNER_TEMP" + uvx --with gitpython cookiecutter "$repoDir" --no-input \ + project_name="ci-test-project" \ + --output-dir "$RUNNER_TEMP" - name: Verify generated project shell: pwsh run: | - $project = Join-Path $env:RUNNER_TEMP "replace-me" + $project = Join-Path $env:RUNNER_TEMP "ci-test-project" # Verify the project directory was created if (-not (Test-Path $project)) { @@ -222,25 +224,25 @@ jobs: - name: Initialize generated project shell: bash run: | - cd "$RUNNER_TEMP/replace-me" + cd "$RUNNER_TEMP/ci-test-project" task -v init - name: Run unit tests shell: bash # Integration tests require Docker (Linux images) which is not # available on Windows runners; those are covered by the Linux CI job. run: | - cd "$RUNNER_TEMP/replace-me" + cd "$RUNNER_TEMP/ci-test-project" task -v unit-test - name: Build Docker image shell: bash run: | - cd "$RUNNER_TEMP/replace-me" + cd "$RUNNER_TEMP/ci-test-project" task -v build - name: Verify Docker image shell: bash run: | - docker run --rm zenable-io/replace-me:latest --version - docker run --rm zenable-io/replace-me:latest --help + docker run --rm zenable-io/ci-test-project:latest --version + docker run --rm zenable-io/ci-test-project:latest --help - name: Verify zenable CLI shell: bash run: | diff --git "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" index 0a24e6b..b13afb4 100755 --- "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" +++ "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" @@ -15,7 +15,7 @@ def main(): description="{{ cookiecutter.project_short_description | replace('"', '\\"') | replace("'", "\\\\'") }}", ) parser.add_argument( - "--version", action="version", version=f"%(prog)s {__version__}" + "--version", action="version", version=__version__ ) parser.parse_args() From 038638ac41befd7cf1ef10e5c12fa42002e6b3ae Mon Sep 17 00:00:00 2001 From: Jon Zeolla Date: Sun, 5 Apr 2026 09:23:52 -0400 Subject: [PATCH 6/6] fix(template): fix ruff format issue in main.py Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/main.py" | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" index b13afb4..f2bbc0c 100755 --- "a/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" +++ "b/{{cookiecutter.project_name|replace(\" \", \"\")}}/src/main.py" @@ -14,9 +14,7 @@ def main(): prog="{{ cookiecutter.project_slug }}", description="{{ cookiecutter.project_short_description | replace('"', '\\"') | replace("'", "\\\\'") }}", ) - parser.add_argument( - "--version", action="version", version=__version__ - ) + parser.add_argument("--version", action="version", version=__version__) parser.parse_args() log = config.setup_logging()