Skip to content

feat(zenable): migrate from zenable-mcp to compiled CLI#90

Open
JonZeolla wants to merge 20 commits intomainfrom
update
Open

feat(zenable): migrate from zenable-mcp to compiled CLI#90
JonZeolla wants to merge 20 commits intomainfrom
update

Conversation

@JonZeolla
Copy link
Copy Markdown
Member

Summary

  • Replace uvx zenable-mcp@latest with the compiled Zenable CLI binary from cli.zenable.app
  • Auto-install the CLI during cookiecutter generation (non-interactive, curl/wget fallback)
  • Fix trufflehog pre-commit hook to work in git worktrees by resolving repo root via git-common-dir

Test plan

  • Generate a new project with cookiecutter and verify zenable CLI is auto-installed
  • Verify zenable install runs and configures IDE integrations
  • Run pre-commit run --all-files to verify all hooks pass
  • Test on a machine without zenable pre-installed to verify the install script flow

🤖 Generated with Claude Code

Replace the Python-based zenable-mcp package (installed via uvx) with
the compiled Zenable CLI binary from cli.zenable.app. The CLI is now
auto-installed during cookiecutter generation using the official
install script, with curl/wget fallback and non-interactive mode.

Also fix trufflehog pre-commit hook to work in git worktrees by
resolving the repo root via git-common-dir.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@ai-coding-guardrails ai-coding-guardrails bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got 2 comments for you to consider

Reviewed with 🤟 by Zenable

# 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)
language: system
entry: bash -c 'trufflehog git "file://$(cd "$(git rev-parse --git-common-dir)/.." && pwd)" --branch "$(git rev-parse --abbrev-ref HEAD)" --since-commit main~1 --no-verification --fail'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --branch flag passed to trufflehog may not correctly scope the scan. When HEAD is detached (e.g., in CI), git rev-parse --abbrev-ref HEAD returns HEAD literally, which trufflehog may interpret incorrectly. Also, the --since-commit main~1 combined with --branch could cause issues if main doesn't exist in the current context (e.g., a fresh worktree or shallow clone).

Consider using --since-commit HEAD~1 instead of main~1 to avoid dependency on the main branch existing locally, or guard against detached HEAD:

Suggested change
entry: bash -c 'trufflehog git "file://$(cd "$(git rev-parse --git-common-dir)/.." && pwd)" --branch "$(git rev-parse --abbrev-ref HEAD)" --since-commit main~1 --no-verification --fail'
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'
Why did I show this?

Category: bug
Comment Quality: high

Based on general best practices

Tools used:

  1. get_file_lines, {'file_path': '.github/linters/lychee.toml', 'start_line': '1', 'end_line': '200'}
  2. get_file_lines, {'file_path': '.github/linters/cspell.config.js', 'start_line': '1', 'end_line': '200'}
  3. list_changed_files, {'pattern': '**/cookiecutter*'}
  4. list_changed_files, {'pattern': ''}
  5. get_file_lines, {'file_path': 'hooks/post_gen_project.py', 'start_line': '1', 'end_line': '200'}
  6. get_file_lines, {'file_path': 'hooks/post_gen_project.py', 'start_line': '201', 'end_line': '380'}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Message

Addressed in b9974e1

JonZeolla and others added 19 commits April 3, 2026 13:14
Guard against detached HEAD by falling back to commit SHA. Use HEAD~1
instead of main~1 to avoid dependency on main branch existing locally.
Install trufflehog binary in CI bootstrap since language: system
requires it in PATH.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add docstring note that cli.zenable.app/install.sh performs cosign
signature verification and checksum validation of the downloaded binary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add conftest, docstrings, and taskfile to both the root and template
dictionaries to fix cspell lint failures in CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a windows-latest job that generates a project from the template
and verifies it renders correctly with all defaults. The bootstrap
action is updated to handle Windows by skipping Unix-specific steps
(Homebrew, trufflehog, sha256sum) and using PowerShell alternatives.

The smoke test verifies:
- Project directory is created
- Key files exist (pyproject.toml, Taskfile.yml, Dockerfile, etc.)
- No unrendered cookiecutter variables remain
- Git repo is initialized with at least one commit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The template directory name contains pipe and quote characters
({{cookiecutter.project_name|replace(" ", "")}}) which are invalid
on NTFS. Set core.protectNTFS=false before checkout to allow these
paths in the Windows smoke test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
actions/checkout overrides HOME with a temp directory, so global git
config set in a prior step is lost. Use system-level config instead
which persists across HOME changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
actions/checkout overrides both HOME and system config. Use the
GIT_CONFIG_GLOBAL environment variable to point at a custom gitconfig
file that disables core.protectNTFS, which persists through all of
checkout's internal git operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Actionlint flags github.head_ref as potentially untrusted in inline
scripts. Pass it through an environment variable instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cookiecutter internally runs git clone, which also fails on NTFS due
to the template directory name containing quote characters. Use
GIT_CONFIG_COUNT/KEY/VALUE environment variables to inject
core.protectNTFS=false into all git invocations without needing a
config file or HOME directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Double-quote characters in the template directory name are
fundamentally illegal on NTFS — no git config can work around this.
Use cookiecutter's zip URL support instead, which extracts via
Python's zipfile module and avoids NTFS filesystem restrictions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cookiecutter's zip URL support still fails because Python's zipfile
extraction on Windows cannot create paths with double-quote characters.
Download the zip manually, extract with Python (which can handle the
entries internally), then point cookiecutter at the extracted directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Python's zipfile.extractall() strips double-quote characters from
paths on Windows since NTFS rejects them. Use the \\?\ extended-length
path prefix when extracting, which bypasses NTFS filename validation
and allows the template directory with quotes to be created.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The \\?\ extended-length path prefix requires backslashes and no
trailing slashes. Zip entries use forward slashes which must be
converted to OS-native separators before joining with the prefix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Double quotes are fundamentally illegal in NTFS paths — even the \\?\
extended-length prefix cannot work around this. Replace " with ' during
zip extraction, which is safe because Jinja2 treats both quote types
identically: replace(" ", "") and replace(' ', '') produce the same
output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use Git Bash (available on all Windows runners) for the zip download
and extraction. Git Bash's unzip may handle NTFS-illegal characters
differently than Python's os.makedirs through MSYS2's path translation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tter

The template directory contains | and " which are illegal on NTFS.
Extract the zip with unzip (which handles these in MSYS2), then rename
the directory to {{cookiecutter.project_name}} which is NTFS-safe and
produces identical output for the default project name (no spaces).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
unzip silently skips entries with NTFS-illegal chars. Use Python's
zipfile to extract all entries, renaming any directory containing
{{cookiecutter. to {{cookiecutter.project_name}} which is NTFS-safe
and renders identically for the default project name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fetch release metadata from cli.zenable.app/zenable/latest and verify
the install.sh SHA-256 checksum before piping it to bash. Uses Python
stdlib (urllib.request, hashlib) instead of curl/wget for downloading.
The install script itself also performs cosign signature verification
of the binary it downloads, providing defense in depth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant