Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
f2b3654
feat(zenable): migrate from zenable-mcp to compiled zenable CLI
JonZeolla Apr 3, 2026
b9974e1
fix: handle detached HEAD and install trufflehog in CI
JonZeolla Apr 3, 2026
030d318
fix: document cosign signature verification in install script
JonZeolla Apr 3, 2026
dab3cd9
fix: add missing words to cspell dictionaries
JonZeolla Apr 3, 2026
1c341f1
feat: add Windows smoke test to CI pipeline
JonZeolla Apr 3, 2026
640f424
fix: allow cookiecutter template paths on Windows checkout
JonZeolla Apr 3, 2026
9de528c
fix: use system git config for core.protectNTFS on Windows
JonZeolla Apr 3, 2026
d514c52
fix: use GIT_CONFIG_GLOBAL env var for NTFS protection override
JonZeolla Apr 3, 2026
05bd404
fix: pass branch ref through env var to avoid script injection
JonZeolla Apr 3, 2026
955fd5f
fix: inject core.protectNTFS via GIT_CONFIG env vars
JonZeolla Apr 3, 2026
e070fb2
fix: use cookiecutter zip download to bypass NTFS path restrictions
JonZeolla Apr 3, 2026
f0fb118
fix: manually extract zip with Python zipfile to handle NTFS chars
JonZeolla Apr 3, 2026
c985fce
fix: use extended-length path prefix to bypass NTFS char restrictions
JonZeolla Apr 3, 2026
c75e86f
fix: normalize zip paths to backslashes for \\?\ prefix on Windows
JonZeolla Apr 3, 2026
f78df89
fix: replace double quotes with single quotes in NTFS paths
JonZeolla Apr 3, 2026
d8c66ff
fix: use bash shell for Windows template extraction
JonZeolla Apr 3, 2026
d2d3bd3
fix: rename template dir to remove NTFS-illegal chars before cookiecu…
JonZeolla Apr 3, 2026
297cf01
debug: add logging to Windows template dir rename
JonZeolla Apr 3, 2026
64f0b8d
fix: extract zip with Python, renaming template dir for NTFS safety
JonZeolla Apr 3, 2026
9b05e30
feat: verify install script checksum before execution
JonZeolla Apr 4, 2026
b208037
fix: validate URL scheme instead of blanket noqa for S310
JonZeolla Apr 4, 2026
d3c4b21
fix: add Windows support to zenable CLI install and discovery
JonZeolla Apr 4, 2026
49745f6
fix: add zenable install dir to PATH after install
JonZeolla Apr 4, 2026
dc2abc2
fix: use HTTPS-only opener instead of urlopen with noqa
JonZeolla Apr 4, 2026
ead2dd9
feat: expand Windows smoke test with init, test, build, and docker run
JonZeolla Apr 4, 2026
57c59f3
fix: bootstrap venv before task commands in Windows smoke test
JonZeolla Apr 4, 2026
c21c994
fix: override VERSION and LOCAL_PLATFORM env vars for Windows task co…
JonZeolla Apr 4, 2026
dba422f
fix: bypass Task for Windows CI steps to avoid VERSION var evaluation
JonZeolla Apr 4, 2026
85fc50d
fix: add Windows support to Taskfile VERSION and LOCAL_PLATFORM vars
JonZeolla Apr 4, 2026
8c3ec2c
fix: extract VERSION via file parsing instead of Python import
JonZeolla Apr 4, 2026
d25b55e
fix: move VERSION var below RUN_SCRIPT and SCRIPTS_DIR in Taskfile
JonZeolla Apr 4, 2026
d95362b
fix: use inline Python for VERSION var and disable Linux CI temporarily
JonZeolla Apr 4, 2026
206f9b4
fix: add debug output to VERSION var and use absolute path
JonZeolla Apr 4, 2026
c3c3bfb
fix: add src contents and syspath to VERSION debug output
JonZeolla Apr 5, 2026
ba9e2fe
fix: only rename top-level template dir in Windows zip extraction
JonZeolla Apr 5, 2026
04b3437
fix: add Docker buildx setup and handle missing buildx gracefully
JonZeolla Apr 5, 2026
fb2f5e9
fix: remove Docker build/run from Windows CI smoke test
JonZeolla Apr 5, 2026
21da236
fix: run only unit tests on Windows (integration tests need Docker)
JonZeolla Apr 5, 2026
98f821c
fix: write PowerShell installer to temp file instead of piping stdin
JonZeolla Apr 5, 2026
d7e9a87
feat: re-enable Linux CI jobs and clean up debug output
JonZeolla Apr 5, 2026
de8b5fe
feat: add WSL 2 + Docker for Linux container builds on Windows CI
JonZeolla Apr 5, 2026
afd5b02
fix: use PowerShell for WSL Docker steps to handle Windows paths
JonZeolla Apr 5, 2026
2f9b748
fix: fail on missing buildx, use docker wrapper, and fix get_epoch.sh
JonZeolla Apr 5, 2026
93ef040
fix: use .bat wrapper for docker so Task's mvdan/sh finds it on Windows
JonZeolla Apr 5, 2026
562be60
fix: add bash docker wrapper alongside .bat for Git Bash steps
JonZeolla Apr 5, 2026
196a9b7
refactor: extract zip template extraction to scripts/extract_template…
JonZeolla Apr 5, 2026
6cde0d9
feat: add --version and --help to template entrypoint
JonZeolla Apr 5, 2026
0375dba
fix: update tests for argparse in main.py
JonZeolla Apr 5, 2026
4569df1
fix: wrap add_argument call to satisfy ruff format line length
JonZeolla Apr 5, 2026
dee1672
docs: add Windows install command for Zenable CLI
JonZeolla Apr 5, 2026
2cbb1f9
fix: use dynamic version in test_main_as_script_version
JonZeolla Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions .github/actions/bootstrap/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,21 @@ inputs:
runs:
using: 'composite'
steps:
- name: Create run_script for running scripts downstream
- name: Create run_script for running scripts downstream (Unix)
if: runner.os != 'Windows'
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
working-directory: ${{ inputs.working-directory }}
run: |
run_script="uv run --frozen"
echo "run_script=${run_script}" | tee -a "${GITHUB_ENV}"
- name: Create run_script for running scripts downstream (Windows)
if: runner.os == 'Windows'
shell: pwsh
working-directory: ${{ inputs.working-directory }}
run: |
echo "run_script=uv run --frozen" | Tee-Object -Append $env:GITHUB_ENV
- name: Setup uv
uses: astral-sh/setup-uv@v4
with:
Expand All @@ -37,6 +45,7 @@ runs:
repo-token: ${{ inputs.token }}

- name: Add Homebrew to the path
if: runner.os != 'Windows'
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
# This ensures compatibility with macOS runners and Linux runners with Homebrew
run: |
Expand Down Expand Up @@ -67,19 +76,45 @@ runs:
fi
working-directory: ${{ inputs.working-directory }}

- name: Set Python hash for caching
- name: Install trufflehog
if: runner.os != 'Windows'
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
run: |
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
- name: Set Python hash for caching (Unix)
if: runner.os != 'Windows'
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
run: |
# Create a hash of the Python version for better cache keys
echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" | tee -a "${GITHUB_ENV}"
- name: Set Python hash for caching (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$pyVersion = python -VV 2>&1
$hash = [System.BitConverter]::ToString(
[System.Security.Cryptography.SHA256]::Create().ComputeHash(
[System.Text.Encoding]::UTF8.GetBytes($pyVersion)
)
).Replace("-", "").ToLower()
echo "PY=$hash" | Tee-Object -Append $env:GITHUB_ENV
- name: Cache pre-commit environments
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit|${{ env.PY }}|${{ hashFiles(format('{0}/.pre-commit-config.yaml', inputs.working-directory)) }}

- name: Initialize the repository
- name: Initialize the repository (Unix)
if: runner.os != 'Windows'
working-directory: ${{ inputs.working-directory }}
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
run: task -v init

- name: Initialize the repository (Windows)
if: runner.os == 'Windows'
working-directory: ${{ inputs.working-directory }}
shell: pwsh
run: task -v init
3 changes: 3 additions & 0 deletions .github/etc/dictionary.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
allstar
anchore
buildx
conftest
cookiecutter
dependabot
digestabot
docstrings
dockerhub
htmlcov
pylance
pythonpath
refurb
skopeo
syft
taskfile
zenable
zizmor
143 changes: 142 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,147 @@ jobs:
name: vuln-scan-results
path: vulns.json
if-no-files-found: error
windows-smoke-test:
name: Windows Smoke Test
runs-on: windows-latest
steps:
# Note: no checkout step. The cookiecutter template directory contains
# characters (pipe, quotes) that are illegal on NTFS, so we cannot check
# out the repo on Windows. Instead, cookiecutter fetches the template
# directly from the remote branch.
- name: Setup uv
uses: astral-sh/setup-uv@v4
with:
python-version: ${{ env.python_version }}
- name: Install Task
uses: go-task/setup-task@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Generate project from template
shell: bash
env:
RUN_POST_HOOK: 'true'
SKIP_GIT_PUSH: 'true'
TEMPLATE_REF: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
git config --global user.name "CI Automation"
git config --global user.email "ci@zenable.io"

# The template directory name contains NTFS-illegal characters
# (double quotes, pipe). Use extract_template_zip.py to safely
# extract and rename only the top-level template dir.
zipUrl="https://github.com/${{ github.repository }}/archive/${TEMPLATE_REF}.zip"
scriptUrl="https://raw.githubusercontent.com/${{ github.repository }}/${TEMPLATE_REF}/scripts/extract_template_zip.py"
tmpdir=$(mktemp -d)
curl -fsSL "$zipUrl" -o "$tmpdir/template.zip"
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"
- name: Verify generated project
shell: pwsh
run: |
$project = Join-Path $env:RUNNER_TEMP "replace-me"

# Verify the project directory was created
if (-not (Test-Path $project)) {
Write-Error "Project directory not found at $project"
exit 1
}

# Verify key files exist
$requiredFiles = @(
"pyproject.toml",
"Taskfile.yml",
"Dockerfile",
"CLAUDE.md",
".github/project.yml",
".github/workflows/ci.yml"
)
foreach ($file in $requiredFiles) {
$filePath = Join-Path $project $file
if (-not (Test-Path $filePath)) {
Write-Error "Required file missing: $file"
exit 1
}
}

# Verify no unrendered cookiecutter variables remain
$pattern = '\{\{\s*cookiecutter\.'
$matches = Get-ChildItem -Path $project -Recurse -File -Exclude '.git' |
Where-Object { $_.FullName -notmatch '[\\/]\.git[\\/]' } |
Select-String -Pattern $pattern
if ($matches) {
Write-Error "Unrendered cookiecutter variables found:"
$matches | ForEach-Object { Write-Error $_.ToString() }
exit 1
}

# Verify git repo was initialized and has a commit
$gitDir = Join-Path $project ".git"
if (-not (Test-Path $gitDir)) {
Write-Error "Git repository not initialized"
exit 1
}

Push-Location $project
$commitCount = git rev-list --count HEAD 2>$null
Pop-Location
if ($commitCount -lt 1) {
Write-Error "No commits found in generated project"
exit 1
}

Write-Host "Windows smoke test passed: project generated and verified successfully"
- name: Setup WSL with Docker
shell: bash
run: |
wsl --install -d Ubuntu-24.04 --no-launch
wsl -d Ubuntu-24.04 -u root -- bash -ec "
apt-get update -qq
apt-get install -y -qq curl ca-certificates >/dev/null
curl -fsSL https://get.docker.com | sh -s -- --quiet
service docker start
"

# Create docker wrappers that route to WSL's Docker.
# - .bat for Task's mvdan/sh (Go's exec.LookPath needs a Windows extension)
# - bash script for Git Bash steps
mkdir -p "$HOME/bin"
printf '@wsl -d Ubuntu-24.04 -u root -- docker %%*\r\n' > "$HOME/bin/docker.bat"
cat > "$HOME/bin/docker" << 'WRAPPER'
#!/bin/bash
exec wsl -d Ubuntu-24.04 -u root -- docker "$@"
WRAPPER
chmod +x "$HOME/bin/docker"
echo "$HOME/bin" >> "$GITHUB_PATH"
- name: Initialize generated project
shell: bash
run: |
cd "$RUNNER_TEMP/replace-me"
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"
task -v unit-test
- name: Build Docker image
shell: bash
run: |
cd "$RUNNER_TEMP/replace-me"
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
- name: Verify zenable CLI
shell: bash
run: |
export PATH="$HOME/.zenable/bin:$PATH"
zenable version
finalizer:
# This gives us something to set as required in the repo settings. Some projects use dynamic fan-outs using matrix strategies and the fromJSON function, so
# you can't hard-code what _should_ run vs not. Having a finalizer simplifies that so you can just check that the finalizer succeeded, and if so, your
Expand All @@ -110,7 +251,7 @@ jobs:
name: Finalize the pipeline
runs-on: ubuntu-24.04
# Keep this aligned with the above jobs
needs: [lint, test]
needs: [lint, test, windows-smoke-test]
if: always() # Ensure it runs even if "needs" fails or is cancelled
steps:
- name: Check for failed or cancelled jobs
Expand Down
7 changes: 5 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ repos:
rev: ad6fc8fb446b8fafbf7ea8193d2d6bfd42f45690 # frozen: v3.90.11
hooks:
- id: trufflehog
# Check the past 2 commits; it's useful to make this go further back than main when running this where main and HEAD are equal
entry: trufflehog git file://. --since-commit main~1 --no-verification --fail
# Resolve the repo root via git-common-dir so this works in both normal repos and worktrees
# (trufflehog doesn't support .git files used by worktrees).
# Guard against detached HEAD (e.g. CI) by falling back to the commit SHA.
language: system
entry: bash -c 'BRANCH=$(git rev-parse --abbrev-ref HEAD); [ "$BRANCH" = "HEAD" ] && BRANCH=$(git rev-parse HEAD); trufflehog git "file://$(cd "$(git rev-parse --git-common-dir)/.." && pwd)" --branch "$BRANCH" --since-commit HEAD~1 --no-verification --fail'
- repo: https://github.com/python-openapi/openapi-spec-validator
rev: a76da2ffdaf698a7fdbd755f89b051fef4c790fd # frozen: 0.8.0b1
hooks:
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
- id: zenable-check
name: Run a Zenable check on all changed files
language: system
entry: uvx zenable-mcp@latest check
entry: zenable check
pass_filenames: true
20 changes: 16 additions & 4 deletions docs/ai-ide-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@ The AI-Native Python template automatically configures AI-powered development to

## Automatic Configuration

When you generate a new project, the post-generation hook automatically detects which IDEs and AI assistants you have installed and creates appropriate configuration files:
When you generate a new project, the post-generation hook automatically installs the [Zenable CLI](https://cli.zenable.app) and configures your IDE integrations:

- Model Context Protocol (MCP) configuration for [Zenable](https://zenable.io) and other MCP servers (if supported tools are detected)
- IDE-specific configuration files based on what's installed (Claude, GitHub Copilot, Cursor, etc.)
- Project-specific context and guidelines tailored to your project
**Installation (if the Zenable CLI is not already installed):**

macOS/Linux:
```bash
curl -fsSL https://cli.zenable.app/install.sh | bash
```

Windows:
```powershell
powershell -ExecutionPolicy Bypass -Command "irm https://cli.zenable.app/install.ps1 | iex"
```

**IDE Configuration:**

Once installed, `zenable install` detects which IDEs and AI assistants you have installed and creates appropriate configuration files for 15+ supported IDEs including Claude Code, Cursor, Windsurf, VS Code, GitHub Copilot, and more.

These configurations are dynamically generated based on your installed IDEs and project settings, and include:

Expand Down
Loading
Loading