ci: go native — 13 GitHub workflows out, publish moves to git.hanzo.ai

git.hanzo.ai is the canonical forge and GitHub is a mirror, so this repo's CI
had no business living in .github/workflows. It also did not work: Actions is
disabled at the ACCOUNT level for the identity that pushes here —

  gh workflow run publish-pypi.yml
  HTTP 422: Actions has been disabled for this user

— and a tag push produced no run for the same reason. Thirteen workflows (ci,
test, test-windows, test-hanzo-mcp, test-hanzo-tools, quality-gate, docs,
generate, generate-api-providers, hanzo-packages-ci, publish-external-dists,
publish-pypi, test-auto-publish) looked like CI and could not run. That is worse
than having none, and it is how `hanzo` 0.4.4 sat tagged-but-unpublished while
PyPI kept serving 0.4.3 — a build whose `hanzo auth login` cannot complete,
because Cloudflare rejects urllib's default User-Agent with `error code: 1010`
and the token exchange 403s after the user has already signed in.

Depending on GitHub to ship a fix for our own CLI was the actual defect.

Now:
  .hanzo/workflows/publish-pypi.yml     — native, runs on hanzo-build-linux-amd64
  .hanzo/workflows/sync-from-github.yml — the proven fast-forward-only pull
  .github/workflows/sync.yml            — a mirror nudge, nothing else

The tag → package mapping is carried over intact and checked against real tag
shapes: hanzo-v0.4.4 → hanzo, hanzo-dev-0.1.0 → hanzo-dev, hanzoai-3.1.1 →
hanzoai, hanzo-tools-vcs-v0.1.2 → the tools set, v1.0.0 → all. The package list
is auto-discovered from pkg/ rather than hardcoded, because the old hardcoded
lists silently dropped newly added packages.

Both native workflows parse as valid YAML. The PyPI token must exist as a secret
on git.hanzo.ai for the publish to run; the job fails loudly with that message
rather than half-publishing.
This commit is contained in:
2026-07-26 13:27:33 -07:00
parent a74176fa6c
commit 32af11bf85
14 changed files with 29 additions and 1983 deletions
-57
View File
@@ -1,57 +0,0 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
- next
jobs:
lint:
name: lint
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq libatomic1 2>/dev/null || true
- name: Install Rye
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Install dependencies
run: rye sync --all-features
- name: Run lints
run: ./scripts/lint
test:
name: test
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- name: Install Rye
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Bootstrap
run: ./scripts/bootstrap
- name: Run tests
run: ./scripts/test
-64
View File
@@ -1,64 +0,0 @@
name: Deploy Docs
on:
push:
branches: [main]
paths:
- 'docs/**'
- 'mkdocs.yml'
- 'pkg/hanzo-agent/docs/**'
- 'pkg/hanzo-mcp/docs/**'
- '.github/workflows/docs.yml'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Install dependencies
run: |
pip install mkdocs-material "mkdocstrings[python]<0.27" mkdocs-minify-plugin pymdown-extensions
# Install packages for API reference generation
cd pkg/hanzo-agent && pip install -e . && cd ../..
cd pkg/hanzo-mcp && pip install -e . && cd ../..
- name: Build docs
run: |
mkdocs build 2>&1 || echo "Build completed with warnings"
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: site
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: hanzo-build-linux-amd64
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
@@ -1,91 +0,0 @@
name: Generate API Providers
on:
schedule:
# Run weekly on Sunday at 00:00 UTC
- cron: '0 0 * * 0'
workflow_dispatch:
inputs:
create_pr:
description: 'Create a PR with the changes'
type: boolean
default: true
preload_specs:
description: 'Pre-download popular OpenAPI specs'
type: boolean
default: false
jobs:
generate:
runs-on: hanzo-build-linux-amd64
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install httpx aiofiles pyyaml
- name: Generate providers from APIs.guru + oapis.org
run: |
cd pkg/hanzo-tools-api
python scripts/generate_providers.py > hanzo_tools/api/apis_guru_providers.py
- name: Pre-download popular specs
if: github.event.inputs.preload_specs == 'true'
run: |
cd pkg/hanzo-tools-api
pip install -e .
python scripts/preload_specs.py
echo "Specs cached at ~/.hanzo/api/specs/"
ls -la ~/.hanzo/api/specs/ | head -20
- name: Check for changes
id: changes
run: |
if git diff --quiet pkg/hanzo-tools-api/hanzo_tools/api/apis_guru_providers.py; then
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
echo "Provider configs changed"
git diff --stat pkg/hanzo-tools-api/hanzo_tools/api/apis_guru_providers.py
fi
- name: Create Pull Request
if: steps.changes.outputs.changed == 'true' && (github.event.inputs.create_pr == 'true' || github.event_name == 'schedule')
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore(api): regenerate provider configs from APIs.guru + oapis.org'
title: 'chore(api): Update API provider configurations'
body: |
## Summary
Auto-generated provider configurations from:
- [APIs.guru](https://apis.guru/) - OpenAPI specification directory (2500+ APIs)
- [oapis.org](https://oapis.org/) - LLM-optimized API descriptions
This PR updates the `apis_guru_providers.py` file with the latest API configurations.
## Stats
- Total providers: ~1100+
- With OpenAPI specs: ~1100+
- LLM-optimized descriptions for popular APIs
## Changes
- Updated provider configurations
- Refreshed spec URLs
- Updated LLM-friendly descriptions for popular APIs
branch: chore/update-api-providers
delete-branch: true
labels: |
automated
dependencies
-40
View File
@@ -1,40 +0,0 @@
name: Regenerate from OpenAPI spec
# One spec, one way. hanzoai/openapi fires `spec-update` on every hanzo.yaml
# change; this regenerates the `hanzoai` client and opens a PR. Never hand-edit
# pkg/hanzoai — edit the per-service spec in hanzoai/openapi.
on:
repository_dispatch:
types: [spec-update]
workflow_dispatch:
jobs:
generate:
runs-on: hanzo-build-linux-amd64
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Regenerate
run: ./scripts/generate.sh
env:
# Read token for the private hanzoai/openapi (repo/contents:read).
SPEC_TOKEN: ${{ secrets.SPEC_TOKEN }}
- name: Verify it imports
run: |
python -m pip install -q urllib3 python-dateutil 'pydantic>=2' typing-extensions
PYTHONPATH=pkg python -c "import hanzoai; hanzoai.ApiClient(hanzoai.Configuration())"
- name: Open PR
uses: peter-evans/create-pull-request@v6
with:
branch: codegen/spec-update
title: 'chore: regenerate hanzoai from openapi hanzo.yaml'
commit-message: 'chore: regenerate hanzoai from openapi hanzo.yaml'
body: Automated regeneration of pkg/hanzoai from hanzoai/openapi `hanzo.yaml`.
-444
View File
@@ -1,444 +0,0 @@
name: Hanzo Packages CI
on:
# Manual runs: push-event dispatch in this repo is intermittent, and the
# publish jobs below are gated on `github.event_name == 'push'` / a tag ref,
# so a manual run tests without any chance of publishing.
workflow_dispatch:
push:
branches:
- main
paths:
- 'pkg/hanzo/**'
- 'pkg/hanzo-network/**'
- 'pkg/hanzo-mcp/**'
# hanzo-agent is now a git submodule - not tested here
- 'pkg/hanzo-memory/**'
- 'pkg/hanzo-aci/**'
- 'pkg/hanzo-dev/**'
- 'pkg/hanzoai/**'
- 'pkg/hanzo-kms/**'
- '.github/workflows/hanzo-packages-ci.yml'
tags:
- 'v*'
- 'hanzo-*'
- 'hanzo-network-*'
- 'hanzo-mcp-*'
# hanzo-agents is now a git submodule
- 'hanzo-memory-*'
- 'hanzo-aci-*'
- 'hanzo-dev-*'
pull_request:
branches:
- main
paths:
- 'pkg/hanzo/**'
- 'pkg/hanzo-network/**'
- 'pkg/hanzo-mcp/**'
# hanzo-agent is now a git submodule - not tested here
- 'pkg/hanzo-memory/**'
- 'pkg/hanzo-aci/**'
- 'pkg/hanzo-dev/**'
- 'pkg/hanzoai/**'
- 'pkg/hanzo-kms/**'
- '.github/workflows/hanzo-packages-ci.yml'
jobs:
test-hanzo-network:
name: Test hanzo-network
runs-on: hanzo-build-linux-amd64
timeout-minutes: 5
defaults:
run:
working-directory: pkg/hanzo-network
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-asyncio pytest-xdist numpy
pip install -e .
- name: Run ALL tests
run: |
timeout 120 python -m pytest tests/ -v --tb=short -n 4 --maxfail=5 || true # Parallel with 2min timeout
test-hanzo-mcp:
name: Test hanzo-mcp
runs-on: hanzo-build-linux-amd64
timeout-minutes: 5
defaults:
run:
working-directory: pkg/hanzo-mcp
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-asyncio pytest-cov pytest-xdist numpy
pip install -e .
# Install hanzo dependencies from the monorepo
pip install -e ../hanzo-network
# hanzo-agents is now a PyPI package (git submodule)
pip install hanzo-agents || true
pip install -e ../hanzo-memory
- name: Run ALL tests
run: |
timeout 120 python -m pytest tests/ -v --tb=short --cov=hanzo_mcp --cov-report=term-missing -n 4 --dist loadgroup --maxfail=5 -m "not slow" || echo "Tests completed or timed out"
test-hanzo-aci:
name: Test hanzo-aci
runs-on: hanzo-build-linux-amd64
timeout-minutes: 5
defaults:
run:
working-directory: pkg/hanzo-aci
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-asyncio pytest-cov pytest-xdist
pip install -e .
- name: Run ALL tests
run: |
timeout 120 python -m pytest tests/ -v --tb=short --cov=dev_aci --cov-report=term-missing -n 4 --maxfail=5 || true # Parallel with 2min timeout
# test-hanzo-agents: REMOVED - hanzo-agent is now a git submodule at pkg/hanzo-agent
# Tests run in the original repository: https://github.com/hanzoai/agent
test-hanzo-memory:
name: Test hanzo-memory
runs-on: hanzo-build-linux-amd64
timeout-minutes: 5
defaults:
run:
working-directory: pkg/hanzo-memory
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-asyncio pytest-cov pytest-xdist polars httpx
pip install -e .[test]
- name: Run ALL tests
run: |
timeout 120 python -m pytest tests/ -v --tb=short --cov=hanzo_memory --cov-report=term-missing -n 4 --maxfail=5 -k "not TestInfinityClient" || true # Parallel with 2min timeout
test-hanzo-kms:
name: Test hanzo-kms
runs-on: hanzo-build-linux-amd64
timeout-minutes: 5
defaults:
run:
working-directory: pkg/hanzo-kms
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[dev]
# No `|| true`: these tests pin the canonical luxfi/kms path shape. A
# regression back onto Infisical's /api/* surface has to turn CI red —
# in production it was invisible, because the console SPA behind the
# server's old root catch-all answered every wrong path with 200 HTML.
- name: Run tests
run: |
timeout 120 python -m pytest tests/ -v --tb=short
integration-test:
name: Integration Test
needs: [test-hanzo-network, test-hanzo-mcp, test-hanzo-aci, test-hanzo-memory, test-hanzo, test-hanzo-dev]
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install all packages
run: |
python -m pip install --upgrade pip
pip install pytest pytest-asyncio pytest-xdist numpy
cd pkg/hanzo && pip install -e . && cd ../..
cd pkg/hanzo-network && pip install -e . && cd ../..
cd pkg/hanzo-mcp && pip install -e . && cd ../..
# hanzo-agents is now a PyPI package (git submodule)
pip install hanzo-agents || true
cd pkg/hanzo-memory && pip install -e . && cd ../..
cd pkg/hanzo-aci && pip install -e . && cd ../..
cd pkg/hanzo-dev && pip install -e . && cd ../..
- name: Run integration tests
run: |
timeout 60 python -m pytest pkg/hanzo-mcp/tests/test_hanzo_mcp_integration.py -v -n 4 --maxfail=5 || true
lint:
name: Lint Hanzo Packages
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install linting tools
run: |
python -m pip install --upgrade pip
pip install ruff mypy
- name: Lint hanzo-network
run: |
cd pkg/hanzo-network
ruff check . || true
mypy . --ignore-missing-imports || true
- name: Lint hanzo-mcp
run: |
cd pkg/hanzo-mcp
ruff check . || true
mypy . --ignore-missing-imports || true
# Lint hanzo-agents: SKIPPED - hanzo-agent is now a git submodule
- name: Lint hanzo-aci
run: |
cd pkg/hanzo-aci
ruff check . || true
mypy . --ignore-missing-imports || true
- name: Lint hanzo-memory
run: |
cd pkg/hanzo-memory
ruff check . || true
mypy . --ignore-missing-imports || true
- name: Lint hanzo
run: |
cd pkg/hanzo
ruff check . || true
mypy . --ignore-missing-imports || true
- name: Lint hanzo-dev
run: |
cd pkg/hanzo-dev
ruff check . || true
mypy . --ignore-missing-imports || true
test-hanzo:
name: Test hanzo (main package)
runs-on: hanzo-build-linux-amd64
timeout-minutes: 5
defaults:
run:
working-directory: pkg/hanzo
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-asyncio pytest-cov pytest-xdist
pip install -e .
- name: Run tests if available
run: |
if [ -d "tests" ]; then
timeout 120 python -m pytest tests/ -v --tb=short -n 4 --maxfail=5 || true
else
echo "No tests directory found, skipping tests"
fi
test-hanzo-dev:
name: Test hanzo-dev
runs-on: hanzo-build-linux-amd64
timeout-minutes: 5
defaults:
run:
working-directory: pkg/hanzo-dev
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-asyncio pytest-cov pytest-xdist
pip install -e .
# Install hanzo dependencies
pip install -e ../hanzo-mcp
- name: Run tests if available
run: |
if [ -d "tests" ]; then
timeout 120 python -m pytest tests/ -v --tb=short -n 4 --maxfail=5 || true
else
echo "No tests directory found, checking for test module"
python -c "from hanzo_dev import tests; print('Test module found')" || echo "No test module"
fi
# Auto-publish packages with new versions on push to main
auto-publish-new-versions:
name: Auto-Publish New Versions
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: [test-hanzo-network, test-hanzo-mcp, test-hanzo-aci, test-hanzo-memory, test-hanzo, test-hanzo-dev, integration-test]
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Check and publish new versions
env:
PYPI_TOKEN: ${{ secrets.HANZO_PYPI_TOKEN || secrets.PYPI_TOKEN }}
run: |
python ./bin/check-and-publish.py
# Publish packages to PyPI when a tag is pushed (for manual releases)
publish-to-pypi:
name: Publish to PyPI (Tag)
if: startsWith(github.ref, 'refs/tags/')
needs: [test-hanzo-network, test-hanzo-mcp, test-hanzo-aci, test-hanzo-memory, test-hanzo, test-hanzo-dev, integration-test]
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Get tag name
id: get_tag
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Determine package to publish
id: determine_package
run: |
TAG="${{ steps.get_tag.outputs.TAG }}"
PACKAGES=""
# Determine which package(s) to publish based on tag
# Note: hanzo-agents is now a separate repo (git submodule), not published from here
if [[ $TAG == hanzo-network-* ]]; then
PACKAGES="hanzo-network"
elif [[ $TAG == hanzo-mcp-* ]]; then
PACKAGES="hanzo-mcp"
elif [[ $TAG == hanzo-memory-* ]]; then
PACKAGES="hanzo-memory"
elif [[ $TAG == hanzo-aci-* ]]; then
PACKAGES="hanzo-aci"
elif [[ $TAG == hanzo-dev-* ]]; then
PACKAGES="hanzo-dev"
elif [[ $TAG == hanzo-* ]]; then
PACKAGES="hanzo"
elif [[ $TAG == v* ]]; then
# For general version tags, publish all packages (except hanzo-agents which is a submodule)
PACKAGES="hanzo hanzo-network hanzo-mcp hanzo-memory hanzo-aci hanzo-dev"
fi
echo "PACKAGES=$PACKAGES" >> $GITHUB_OUTPUT
echo "Publishing packages: $PACKAGES"
- name: Publish packages
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.HANZO_PYPI_TOKEN || secrets.PYPI_TOKEN }}
run: |
PACKAGES="${{ steps.determine_package.outputs.PACKAGES }}"
if [ -z "$PACKAGES" ]; then
echo "No packages to publish for tag ${{ steps.get_tag.outputs.TAG }}"
exit 0
fi
for package in $PACKAGES; do
echo "Publishing $package..."
cd "pkg/$package"
# Clean and build
rm -rf dist/ build/ *.egg-info
python -m build
# Upload to PyPI
python -m twine upload dist/* --skip-existing
cd ../..
done
- name: Create release summary
run: |
echo "## 🚀 Published to PyPI" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Tag: ${{ steps.get_tag.outputs.TAG }}" >> $GITHUB_STEP_SUMMARY
echo "Packages: ${{ steps.determine_package.outputs.PACKAGES }}" >> $GITHUB_STEP_SUMMARY
@@ -1,34 +0,0 @@
name: Publish External Dists
# One-shot workflow: uploads any pre-built wheel/sdist staged under
# dist-external/ to PyPI using the existing HANZO_PYPI_TOKEN / PYPI_TOKEN
# repo secret. Used to bootstrap external deps (zap-mdns, zap-protocol)
# whose source lives outside this repo.
on:
workflow_dispatch:
jobs:
upload:
name: Upload dist-external/* to PyPI
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install twine
run: pip install --upgrade twine
- name: List dists
run: ls -la dist-external/
- name: Upload to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.HANZO_PYPI_TOKEN || secrets.PYPI_TOKEN }}
run: |
python -m twine upload --skip-existing dist-external/*.whl dist-external/*.tar.gz
-252
View File
@@ -1,252 +0,0 @@
# This workflow is triggered when a tag is pushed or a GitHub release is created.
# It can also be run manually to re-publish to PyPI in case it failed for some reason.
# You can run this workflow by navigating to https://www.github.com/hanzoai/python-sdk/actions/workflows/publish-pypi.yml
name: Publish PyPI
on:
workflow_dispatch:
inputs:
packages:
description: 'Packages to publish (space-separated, or "all" for everything)'
required: false
default: 'all'
push:
tags:
- 'v*'
- 'hanzo-*'
- 'hanzoai-*'
- 'hanzo-tools-*'
release:
types: [published]
jobs:
# Run tests first to ensure code quality
test:
name: Run Tests
uses: ./.github/workflows/test.yml
publish-all-packages:
name: Publish All Python Packages
runs-on: hanzo-build-linux-amd64 # live in-cluster ARC fleet (evo/spark external fleet is offline)
if: always() && !cancelled()
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all tags
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install build tools
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Get tag name
id: get_tag
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Determine packages to publish
id: determine_packages
run: |
TAG="${{ steps.get_tag.outputs.TAG }}"
MANUAL_INPUT="${{ github.event.inputs.packages }}"
PACKAGES=""
# Auto-discover every publishable package so these lists can never go
# stale. The prior hardcoded lists silently dropped hanzo-zap and the new
# packages, so tag-triggered "publish all" never shipped them. hanzoai is
# the root package; all others live under pkg/. sort -u folds the
# pkg/hanzoai mirror into the single root entry.
ALL_PACKAGES="$( { echo hanzoai; ls -d pkg/*/ 2>/dev/null | xargs -n1 basename; } | sort -u | xargs)"
TOOLS_PACKAGES="$(ls -d pkg/hanzo-tools-*/ 2>/dev/null | xargs -n1 basename | sort | xargs)"
MAIN_PACKAGES="$ALL_PACKAGES"
# Handle manual workflow dispatch
if [ -n "$MANUAL_INPUT" ]; then
if [ "$MANUAL_INPUT" = "all" ]; then
PACKAGES="$ALL_PACKAGES"
else
PACKAGES="$MANUAL_INPUT"
fi
# Handle specific package tags
elif [[ $TAG == hanzo-tools-core-* ]]; then
PACKAGES="hanzo-tools-core"
elif [[ $TAG == hanzo-tools-fs-* ]]; then
PACKAGES="hanzo-tools-fs"
elif [[ $TAG == hanzo-tools-shell-* ]]; then
PACKAGES="hanzo-tools-shell"
elif [[ $TAG == hanzo-tools-browser-* ]]; then
PACKAGES="hanzo-tools-browser"
elif [[ $TAG == hanzo-tools-memory-* ]]; then
PACKAGES="hanzo-tools-memory"
elif [[ $TAG == hanzo-tools-todo-* ]]; then
PACKAGES="hanzo-tools-todo"
elif [[ $TAG == hanzo-tools-reasoning-* ]]; then
PACKAGES="hanzo-tools-reasoning"
elif [[ $TAG == hanzo-tools-lsp-* ]]; then
PACKAGES="hanzo-tools-lsp"
elif [[ $TAG == hanzo-tools-refactor-* ]]; then
PACKAGES="hanzo-tools-refactor"
elif [[ $TAG == hanzo-tools-database-* ]]; then
PACKAGES="hanzo-tools-database"
elif [[ $TAG == hanzo-tools-agent-* ]]; then
PACKAGES="hanzo-tools-agent"
elif [[ $TAG == hanzo-tools-jupyter-* ]]; then
PACKAGES="hanzo-tools-jupyter"
elif [[ $TAG == hanzo-tools-editor-* ]]; then
PACKAGES="hanzo-tools-editor"
elif [[ $TAG == hanzo-tools-llm-* ]]; then
PACKAGES="hanzo-tools-llm"
elif [[ $TAG == hanzo-tools-vector-* ]]; then
PACKAGES="hanzo-tools-vector"
elif [[ $TAG == hanzo-tools-config-* ]]; then
PACKAGES="hanzo-tools-config"
elif [[ $TAG == hanzo-tools-mcp-* ]]; then
PACKAGES="hanzo-tools-mcp"
elif [[ $TAG == hanzo-tools-computer-* ]]; then
PACKAGES="hanzo-tools-computer"
elif [[ $TAG == hanzo-tools-* ]]; then
# All tools packages
PACKAGES="$TOOLS_PACKAGES"
elif [[ $TAG == hanzo-cli-* ]]; then
PACKAGES="hanzo-cli"
elif [[ $TAG == hanzo-kms-* ]]; then
PACKAGES="hanzo-kms"
elif [[ $TAG == hanzo-iam-* ]]; then
PACKAGES="hanzo-iam"
elif [[ $TAG == hanzo-consensus-* ]]; then
PACKAGES="hanzo-consensus"
elif [[ $TAG == hanzo-network-* ]]; then
PACKAGES="hanzo-network"
elif [[ $TAG == hanzo-mcp-* ]]; then
PACKAGES="hanzo-mcp"
elif [[ $TAG == hanzo-memory-* ]]; then
PACKAGES="hanzo-memory"
elif [[ $TAG == hanzo-aci-* ]]; then
PACKAGES="hanzo-aci"
elif [[ $TAG == hanzo-dev-* ]]; then
PACKAGES="hanzo-dev"
elif [[ $TAG == hanzo-train-* ]]; then
PACKAGES="hanzo-train"
elif [[ $TAG == hanzoai-* ]]; then
PACKAGES="hanzoai"
elif [[ $TAG == hanzo-* ]]; then
PACKAGES="hanzo"
elif [[ $TAG == v* ]]; then
# For general version tags, publish all packages
PACKAGES="$ALL_PACKAGES"
fi
echo "PACKAGES=$PACKAGES" >> $GITHUB_OUTPUT
echo "Publishing packages: $PACKAGES"
- name: Build and publish packages
env:
TWINE_USERNAME: __token__
# Two tokens cover per-project scope: PYPI_TOKEN owns most hanzo-*
# packages; HANZO_AI_PYPI_TOKEN owns the rest (e.g. hanzo-iam, published
# before consolidation under a different owner). Each upload tries the
# primary then falls back, so a 403 scope-miss on one token is covered by
# the other — no package is unpublishable for lack of the matching token.
PYPI_TOKEN_PRIMARY: ${{ secrets.PYPI_TOKEN }}
PYPI_TOKEN_FALLBACK: ${{ secrets.HANZO_AI_PYPI_TOKEN }}
run: |
PACKAGES="${{ steps.determine_packages.outputs.PACKAGES }}"
if [ -z "$PACKAGES" ]; then
echo "No packages to publish for tag ${{ steps.get_tag.outputs.TAG }}"
exit 0
fi
for package in $PACKAGES; do
echo "=========================================="
echo "Building and publishing $package..."
echo "=========================================="
# hanzoai is the root package, all others live under pkg/
if [ "$package" = "hanzoai" ]; then
PKG_DIR="."
elif [ -d "pkg/$package" ]; then
PKG_DIR="pkg/$package"
else
echo "Warning: Package directory pkg/$package does not exist, skipping"
continue
fi
# Clean any previous builds
rm -rf "$PKG_DIR/dist/" "$PKG_DIR/build/"
# Build from repo root to avoid stdlib shadowing (hanzoai/types vs types)
python -m build "$PKG_DIR" --outdir "$PKG_DIR/dist"
# Upload with token-fallback + 429 backoff. Try the primary token; on
# any failure try the fallback token once (covers per-project 403 scope,
# e.g. hanzo-iam owned by HANZO_AI_PYPI_TOKEN). --skip-existing keeps
# retries idempotent; backoff rides out PyPI's burst 429s. PyPI also
# rate-limits NEW-project creation per window — a brand-new name may need
# a later re-run once that window resets.
attempt=0
until TWINE_PASSWORD="$PYPI_TOKEN_PRIMARY" python -m twine upload "$PKG_DIR/dist/"* --skip-existing; do
if [ -n "$PYPI_TOKEN_FALLBACK" ] && TWINE_PASSWORD="$PYPI_TOKEN_FALLBACK" python -m twine upload "$PKG_DIR/dist/"* --skip-existing; then
break
fi
attempt=$((attempt + 1))
if [ "$attempt" -ge 5 ]; then
echo "Warning: giving up on $package after $attempt attempts (403 on both tokens, or persistent error)"
break
fi
echo "Upload of $package failed (attempt $attempt) — backing off $((attempt * 10))s (PyPI 429?)"
sleep "$((attempt * 10))"
done
done
- name: Create GitHub Release Notes
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/github-script@v7
with:
script: |
const tag = '${{ steps.get_tag.outputs.TAG }}';
const packages = '${{ steps.determine_packages.outputs.PACKAGES }}'.split(' ').filter(p => p);
let body = `## 🚀 Published Python Packages\n\n`;
body += `The following packages have been published to PyPI:\n\n`;
for (const pkg of packages) {
body += `- ✅ **${pkg}** - [View on PyPI](https://pypi.org/project/${pkg}/)\n`;
}
body += `\n### Installation\n\n`;
body += `\`\`\`bash\n`;
body += `# Install main SDK\n`;
body += `pip install hanzo\n\n`;
body += `# Install MCP tools\n`;
body += `pip install hanzo-mcp\n\n`;
body += `# Install individual tool packages\n`;
body += `pip install hanzo-tools # All tools\n`;
body += `pip install hanzo-tools-core # Core only\n`;
body += `\`\`\`\n`;
// Update release if it exists
try {
const releases = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
});
const release = releases.data.find(r => r.tag_name === tag);
if (release) {
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.id,
body: release.body + '\n\n' + body,
});
}
} catch (error) {
console.log('Could not update release notes:', error);
}
-268
View File
@@ -1,268 +0,0 @@
name: Quality Gate
on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master, develop]
release:
types: [created]
# Cancel any in-progress runs when a new run starts
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
block-todo-stub-code:
name: "🚫 Block TODO/STUB/FAKE Code"
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- name: "🔍 Search for forbidden patterns in hanzo-mcp"
run: |
echo "Searching for problematic TODO/STUB patterns in hanzo-mcp..."
# Focus on hanzo-mcp package only (other packages may have legitimate TODOs)
# We look for specific problematic patterns, not all TODOs
FOUND_ISSUES=0
# Check for stub functions that return "TODO" or "STUB" strings
echo "Checking for stub return values..."
if grep -rn --include="*.py" -E "return\s+['\"]TODO['\"]|return\s+['\"]STUB['\"]" pkg/hanzo-mcp/hanzo_mcp/ 2>/dev/null; then
echo "❌ Found stub return values"
FOUND_ISSUES=1
fi
# Check for empty pass-only functions (excluding fallback stubs in except blocks)
echo "Checking for empty functions..."
# This is better handled by the pytest test_no_stubs.py
# Check for explicit "STUB:" or "FAKE:" comments indicating unfinished code
echo "Checking for explicit stub markers..."
if grep -rn --include="*.py" -E "#\s*(STUB|FAKE|UNFINISHED):" pkg/hanzo-mcp/hanzo_mcp/ 2>/dev/null; then
echo "❌ Found explicit stub markers"
FOUND_ISSUES=1
fi
if [ $FOUND_ISSUES -eq 1 ]; then
echo "🚫 DEPLOYMENT BLOCKED: Remove stub/fake code before deploying!"
exit 1
fi
echo "✅ No forbidden stub patterns found in hanzo-mcp"
echo "Note: Other packages may contain legitimate TODO comments for documentation"
test-no-stubs:
name: "🧪 Anti-Stub Tests"
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install uv
uv venv
source .venv/bin/activate
uv pip install -e ./pkg/hanzo-mcp[test]
- name: "Run anti-stub tests"
run: |
source .venv/bin/activate
cd pkg/hanzo-mcp
python -m pytest tests/test_no_stubs.py -v --tb=short
- name: "Verify no incomplete implementations"
run: |
source .venv/bin/activate
cd pkg/hanzo-mcp
# Run the test file directly for extra validation
python tests/test_no_stubs.py
all-tests-must-pass:
name: "✅ ALL Tests Must Pass"
runs-on: hanzo-build-linux-amd64
strategy:
fail-fast: true # Stop immediately if any test fails
matrix:
python-version: ['3.12']
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install test dependencies
run: |
pip install uv
uv venv
source .venv/bin/activate
# Install hanzo-mcp with test deps and optional memory/agents packages
uv pip install -e ./pkg/hanzo-mcp[test,memory,agents]
# Install sibling packages for integration tests
uv pip install -e ./pkg/hanzo-memory || true
uv pip install -e ./pkg/hanzo-network || true
# Override PyPI versions with local tool packages (monorepo)
uv pip install -e ./pkg/hanzo-tools -e ./pkg/hanzo-tools-core \
-e ./pkg/hanzo-tools-agent -e ./pkg/hanzo-tools-shell -e ./pkg/hanzo-tools-fs \
-e ./pkg/hanzo-tools-memory -e ./pkg/hanzo-tools-todo -e ./pkg/hanzo-tools-reasoning \
-e ./pkg/hanzo-tools-browser -e ./pkg/hanzo-tools-lsp -e ./pkg/hanzo-tools-refactor \
-e ./pkg/hanzo-tools-computer -e ./pkg/hanzo-tools-config -e ./pkg/hanzo-tools-api \
-e ./pkg/hanzo-tools-vcs -e ./pkg/hanzo-tools-net -e ./pkg/hanzo-tools-plan \
-e ./pkg/hanzo-tools-jupyter -e ./pkg/hanzo-tools-llm 2>/dev/null || true
- name: "🧪 Run ALL tests"
run: |
source .venv/bin/activate
cd pkg/hanzo-mcp
# Run all working tests with strict mode
# Note: Some async tests require specific pytest-asyncio configuration
# The core test suite validates CI requirements
python -m pytest tests/test_agent_tools_ci.py tests/test_llm_warnings.py \
-v \
--strict-markers \
--tb=short \
2>&1 | tee test-output.log
# Run additional simple tests
python -m pytest tests/test_hanzo_mcp_simple.py::test_cli_help \
tests/test_hanzo_mcp_simple.py::test_cli_version \
tests/test_hanzo_mcp_simple.py::test_import_tools \
-v --tb=short 2>&1 | tee -a test-output.log
# Check if any tests failed
if grep -q "FAILED" test-output.log; then
echo "❌ TESTS FAILED! All tests must pass!"
exit 1
fi
echo "✅ All tests passed!"
code-quality:
name: "🎯 Code Quality Check"
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install quality tools
run: |
pip install ruff mypy pyright bandit
- name: "🔍 Lint with ruff"
run: |
cd pkg/hanzo-mcp
ruff check hanzo_mcp tests --fix --exit-non-zero-on-fix
- name: "🔍 Type check with mypy"
run: |
cd pkg/hanzo-mcp
mypy hanzo_mcp --ignore-missing-imports --strict || true
- name: "🔍 Security scan with bandit"
run: |
cd pkg/hanzo-mcp
bandit -r hanzo_mcp -f json -o bandit-report.json || true
if [ -f bandit-report.json ]; then
python -m json.tool bandit-report.json
fi
function-implementation-check:
name: "🔨 Verify Functions Are Implemented"
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- name: "Check for empty functions"
run: |
echo "Checking for empty functions with only 'pass'..."
# Find functions that only contain pass
FOUND_EMPTY=0
for file in $(find pkg/hanzo-mcp -name "*.py" -not -path "*/test*"); do
# Look for functions with only pass
if grep -Pzo "def\s+\w+\([^)]*\):\s*\n\s*pass\s*$" "$file" 2>/dev/null; then
echo "❌ Empty function found in: $file"
FOUND_EMPTY=1
fi
# Look for functions with only ellipsis
if grep -Pzo "def\s+\w+\([^)]*\):\s*\n\s*\.\.\.\s*$" "$file" 2>/dev/null; then
echo "❌ Ellipsis-only function found in: $file"
FOUND_EMPTY=1
fi
done
if [ $FOUND_EMPTY -eq 1 ]; then
echo "🚫 BLOCKED: Empty functions detected! Implement them properly!"
exit 1
fi
echo "✅ All functions have implementations"
block-deployment:
name: "🚀 Deployment Gate"
needs:
- block-todo-stub-code
- test-no-stubs
- all-tests-must-pass
- code-quality
- function-implementation-check
runs-on: hanzo-build-linux-amd64
if: github.event_name == 'release' || github.ref == 'refs/heads/main'
steps:
- name: "✅ Quality Gate PASSED"
run: |
echo "✅ All quality checks passed!"
echo "✅ No TODOs, STUBs, or FAKE code found"
echo "✅ All tests are passing"
echo "✅ All functions are implemented"
echo "🚀 Ready for deployment!"
- name: "📦 Prepare for PyPI deployment"
if: github.event_name == 'release'
run: |
echo "Ready to deploy to PyPI"
echo "Version: ${{ github.event.release.tag_name }}"
publish-to-pypi:
name: "📦 Publish to PyPI"
needs: [block-deployment]
if: github.event_name == 'release'
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Build package
run: |
pip install uv
cd pkg/hanzo-mcp
uv build
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
pip install twine
cd pkg/hanzo-mcp
twine upload dist/* --non-interactive --skip-existing
+29
View File
@@ -0,0 +1,29 @@
name: Sync to git.hanzo.ai
# git.hanzo.ai is the CANONICAL forge. This mirror exists so links and clones
# keep working; it does not build, test, or publish anything.
#
# All CI/CD lives in .hanzo/workflows and runs on the Hanzo git-runner fleet.
# Thirteen workflows used to live here (ci, test, test-windows, quality-gate,
# publish-pypi, …) and none of them could run: Actions is disabled at the
# account level for the identity that pushes this repo, so a tag push produced
# no run and `gh workflow run` returned HTTP 422. They looked like CI and were
# not — which is how `hanzo` 0.4.4 stayed tagged-but-unpublished while PyPI kept
# serving a build whose `auth login` cannot complete.
#
# This job only nudges the canonical forge to pull. If it fails, nothing is
# broken — the forge also polls on a schedule.
on:
push:
branches: [main]
workflow_dispatch: {}
jobs:
nudge:
runs-on: ubuntu-latest
steps:
- name: Ask git.hanzo.ai to sync
continue-on-error: true
run: |
curl -fsS -X POST \
-H "Authorization: token ${{ secrets.HANZO_GIT_TOKEN }}" \
"https://git.hanzo.ai/api/v1/repos/hanzoai/python-sdk/mirror-sync" \
|| echo "nudge failed — the scheduled pull will pick it up"
-90
View File
@@ -1,90 +0,0 @@
name: Test Auto-Publish
# This workflow can be manually triggered to test the auto-publish mechanism
on:
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (check versions without publishing)'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'
jobs:
test-version-check:
name: Test Version Check
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Create version check script
run: |
cat > check_versions.py << 'EOF'
import json
import re
import urllib.request
from pathlib import Path
def get_local_version(package_dir):
pyproject = package_dir / 'pyproject.toml'
if pyproject.exists():
content = pyproject.read_text()
match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
return match.group(1) if match else 'unknown'
return 'not found'
def get_pypi_version(package_name):
try:
with urllib.request.urlopen(f'https://pypi.org/pypi/{package_name}/json') as r:
return json.loads(r.read()).get('info', {}).get('version', 'error')
except:
return 'not published'
packages = [
'hanzo', 'hanzo-network', 'hanzo-mcp',
# hanzo-agents is now a git submodule
'hanzo-memory', 'hanzo-aci', 'hanzo-dev'
]
print('📦 Package Version Status:')
print('=' * 60)
for pkg in packages:
pkg_dir = Path('pkg') / pkg
local = get_local_version(pkg_dir)
pypi = get_pypi_version(pkg)
status = '🆕 NEW' if local != pypi and pypi != 'not published' else '✅ OK'
print(f'{pkg:20} Local: {local:10} PyPI: {pypi:10} {status}')
EOF
- name: Check package versions
run: python check_versions.py
- name: Install dependencies for dry run
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Run auto-publish check (dry run)
if: inputs.dry_run == 'true'
run: |
echo "🔍 DRY RUN - Checking what would be published..."
# Modify script to not actually publish
sed 's/python -m twine upload/echo "Would upload:"/' bin/check-and-publish.py > check-dry.py
PYPI_TOKEN="dry-run-token" python check-dry.py || echo "Dry run complete"
- name: Run actual auto-publish
if: inputs.dry_run == 'false'
env:
PYPI_TOKEN: ${{ secrets.HANZO_PYPI_TOKEN || secrets.PYPI_TOKEN }}
run: |
echo "🚀 ACTUAL RUN - Publishing new versions..."
python ./bin/check-and-publish.py
-198
View File
@@ -1,198 +0,0 @@
name: Test Hanzo MCP
on:
push:
branches: [main]
paths:
- 'pkg/hanzo-mcp/**'
- 'pkg/hanzo-tools-*/**'
- '.github/workflows/test-hanzo-mcp.yml'
pull_request:
branches: [main]
paths:
- 'pkg/hanzo-mcp/**'
- 'pkg/hanzo-tools-*/**'
- '.github/workflows/test-hanzo-mcp.yml'
workflow_dispatch:
inputs:
run_integration_tests:
description: 'Run integration tests requiring API keys'
required: false
type: boolean
default: false
jobs:
test:
runs-on: hanzo-build-linux-amd64
strategy:
matrix:
python-version: ['3.12']
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
cache-dependency-glob: "pkg/hanzo-mcp/pyproject.toml"
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install dependencies
run: |
cd pkg/hanzo-mcp
uv venv
source .venv/bin/activate
uv pip install -e ".[test,performance,analytics,dev]"
uv pip install pytest-cov
# Override PyPI versions with local tool packages (monorepo)
uv pip install -e ../hanzo-tools -e ../hanzo-tools-core \
-e ../hanzo-tools-agent -e ../hanzo-tools-shell -e ../hanzo-tools-fs \
-e ../hanzo-tools-memory -e ../hanzo-tools-todo -e ../hanzo-tools-reasoning \
-e ../hanzo-tools-browser -e ../hanzo-tools-lsp -e ../hanzo-tools-refactor \
-e ../hanzo-tools-computer -e ../hanzo-tools-config -e ../hanzo-tools-api \
-e ../hanzo-tools-vcs -e ../hanzo-tools-net -e ../hanzo-tools-plan \
-e ../hanzo-tools-jupyter -e ../hanzo-tools-llm 2>/dev/null || true
- name: Run type checking with mypy
run: |
cd pkg/hanzo-mcp
source .venv/bin/activate
mypy hanzo_mcp --ignore-missing-imports --strict || true
- name: Run linting with ruff
run: |
cd pkg/hanzo-mcp
source .venv/bin/activate
ruff check hanzo_mcp tests
- name: Run format check with ruff
run: |
cd pkg/hanzo-mcp
source .venv/bin/activate
ruff format --check hanzo_mcp tests
- name: Run unit tests
run: |
cd pkg/hanzo-mcp
source .venv/bin/activate
# Run specific working tests, excluding the failing stdio_server test
python -m pytest tests/test_agent_tools_ci.py tests/test_llm_warnings.py -v -o "addopts="
python -m pytest tests/test_hanzo_mcp_simple.py::test_cli_help tests/test_hanzo_mcp_simple.py::test_cli_version tests/test_hanzo_mcp_simple.py::test_import_tools -v -o "addopts="
echo "✓ Test Summary: 11 tests passed, 100% success rate"
integration-tests:
runs-on: hanzo-build-linux-amd64
if: |
github.event_name == 'workflow_dispatch' &&
github.event.inputs.run_integration_tests == 'true'
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- name: Set up Python
run: uv python install 3.12
- name: Install dependencies
run: |
cd pkg/hanzo-mcp
uv venv
source .venv/bin/activate
uv pip install -e ".[test,performance,analytics,dev]"
uv pip install pytest-cov
- name: Run integration tests with API keys
env:
HANZO_API_KEY: ${{ secrets.HANZO_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
run: |
cd pkg/hanzo-mcp
uv run pytest tests -v -m integration --timeout=300
- name: Test agent tools (minimal)
run: |
cd pkg/hanzo-mcp
# Test agent tool import and basic functionality
echo "Testing Agent tools..."
uv run python -c "
from hanzo_tools.agent import AgentTool, ZenTool, ReviewTool, TOOLS
# Verify all tools are available
tool_names = [t.__name__ for t in TOOLS]
assert 'AgentTool' in tool_names
assert 'ZenTool' in tool_names
assert 'ReviewTool' in tool_names
# Create tool instances
agent = AgentTool()
zen = ZenTool()
review = ReviewTool()
print(f'Agent tool name: {agent.name}')
print(f'Zen tool name: {zen.name}')
print(f'Review tool name: {review.name}')
print('All agent tools created successfully!')
"
release-check:
runs-on: hanzo-build-linux-amd64
needs: [test]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Set up Python
run: uv python install 3.12
- name: Build package
run: |
cd pkg/hanzo-mcp
uv build
- name: Check package with twine
run: |
cd pkg/hanzo-mcp
uv venv
source .venv/bin/activate
uv pip install twine
twine check dist/*
- name: Test installation
run: |
# Create a fresh venv and test installation
cd /tmp
uv venv test-env
source test-env/bin/activate
uv pip install $GITHUB_WORKSPACE/pkg/hanzo-mcp/dist/*.whl
# Override with local tool packages (not yet published to PyPI)
uv pip install -e $GITHUB_WORKSPACE/pkg/hanzo-tools -e $GITHUB_WORKSPACE/pkg/hanzo-tools-core \
-e $GITHUB_WORKSPACE/pkg/hanzo-tools-agent 2>/dev/null || true
python -c "import hanzo_mcp; print(f'Hanzo MCP version: {hanzo_mcp.__version__}')"
# Test that agent tools can be imported from hanzo_tools.agent
python -c "
from hanzo_tools.agent import AgentTool, ZenTool, ReviewTool, TOOLS
print(f'Agent tools available: {[t.__name__ for t in TOOLS]}')
print('All agent tools importable!')
"
- name: Archive built packages
uses: actions/upload-artifact@v4
with:
name: hanzo-mcp-dist
path: pkg/hanzo-mcp/dist/
-114
View File
@@ -1,114 +0,0 @@
name: Test Hanzo Tools
on:
push:
branches: [main]
paths:
- 'pkg/hanzo-tools-*/**'
- '.github/workflows/test-hanzo-tools.yml'
pull_request:
branches: [main]
paths:
- 'pkg/hanzo-tools-*/**'
- '.github/workflows/test-hanzo-tools.yml'
workflow_dispatch:
jobs:
test:
runs-on: hanzo-build-linux-amd64
timeout-minutes: 10
strategy:
matrix:
python-version: ['3.12']
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install hanzo-tools packages
run: |
uv venv
source .venv/bin/activate
# Install base shim first (provides hanzo_tools.core.unified)
uv pip install -e pkg/hanzo-tools
uv pip install -e pkg/hanzo-tools-core
# Install all tool packages
uv pip install -e pkg/hanzo-tools-fs
uv pip install -e pkg/hanzo-tools-shell
uv pip install -e pkg/hanzo-tools-browser
uv pip install -e pkg/hanzo-tools-memory
uv pip install -e pkg/hanzo-tools-todo
uv pip install -e pkg/hanzo-tools-reasoning
uv pip install -e pkg/hanzo-tools-lsp
uv pip install -e pkg/hanzo-tools-refactor
uv pip install -e pkg/hanzo-tools-database
uv pip install -e pkg/hanzo-tools-agent
uv pip install -e pkg/hanzo-tools-jupyter
uv pip install -e pkg/hanzo-tools-editor
uv pip install -e pkg/hanzo-tools-config
uv pip install -e pkg/hanzo-tools-computer
uv pip install pytest pytest-asyncio
- name: Run tests
run: |
source .venv/bin/activate
python -m pytest pkg/hanzo-tools-core/tests/test_all_tools.py -v --tb=short
- name: Verify tool counts
run: |
source .venv/bin/activate
python -c "
# Packages with exact expected counts
exact_packages = [
('hanzo_tools.fs', 1), # unified fs tool
('hanzo_tools.browser', 1),
('hanzo_tools.memory', 1), # unified memory tool with actions
('hanzo_tools.todo', 1),
('hanzo_tools.reasoning', 2), # think, critic
('hanzo_tools.lsp', 1),
('hanzo_tools.refactor', 1),
('hanzo_tools.database', 9),
('hanzo_tools.jupyter', 1),
('hanzo_tools.editor', 3),
('hanzo_tools.agent', 3), # agent, zen, review
]
# Shell tool count depends on detected shell, but core tools are fixed.
shell_required = {'ps', 'npx', 'uvx', 'open', 'curl', 'jq', 'wget'}
total = 0
import importlib
# Check exact packages
for pkg_name, expected in exact_packages:
pkg = importlib.import_module(pkg_name)
tools = getattr(pkg, 'TOOLS', [])
actual = len(tools)
status = '✓' if actual == expected else '✗'
print(f'{status} {pkg_name}: {actual}/{expected} tools')
assert actual == expected, f'{pkg_name}: expected {expected}, got {actual}'
total += actual
# Check shell package (detected-shell mode)
shell_pkg = importlib.import_module('hanzo_tools.shell')
shell_tools = getattr(shell_pkg, 'TOOLS', [])
shell_names = {t.name for t in shell_tools}
print(f'✓ hanzo_tools.shell: {len(shell_tools)} tools (detected-shell mode)')
assert len(shell_tools) >= 8, f'hanzo_tools.shell: expected >=8, got {len(shell_tools)}'
assert shell_required.issubset(shell_names), (
f'hanzo_tools.shell: missing required tools: {shell_required - shell_names}'
)
total += len(shell_tools)
print(f'\\nTotal: {total} tools')
# Minimum total: exact packages (23) + shell minimum (8) = 31
assert total >= 31, f'Expected at least 31 tools, got {total}'
print('✓ All tool packages verified')
"
-259
View File
@@ -1,259 +0,0 @@
name: Test Windows
on:
push:
branches: [main]
paths:
- 'pkg/hanzo-mcp/**'
- 'pkg/hanzo-tools-*/**'
- 'pkg/hanzo-async/**'
- 'hanzoai/**'
- '.github/workflows/test-windows.yml'
pull_request:
branches: [main]
paths:
- 'pkg/hanzo-mcp/**'
- 'pkg/hanzo-tools-*/**'
- 'pkg/hanzo-async/**'
- 'hanzoai/**'
- '.github/workflows/test-windows.yml'
workflow_dispatch:
jobs:
# Native Windows (no WSL)
test-windows-native:
name: Windows Native (Python ${{ matrix.python-version }})
runs-on: windows-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
python-version: ['3.12', '3.13']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- name: Install core packages
shell: pwsh
run: |
uv venv
.\.venv\Scripts\Activate.ps1
# hanzoai is the root workspace package
uv pip install -e .
# Async layer (uvloop skipped automatically on Windows)
uv pip install -e ./pkg/hanzo-async
# Test deps
uv pip install pytest pytest-asyncio pytest-cov
- name: Test hanzoai SDK imports
shell: pwsh
run: |
.\.venv\Scripts\Activate.ps1
python -c "from hanzoai import __version__; print(f'hanzoai {__version__}')"
python -c "import hanzo_async; print(f'uvloop active: {hanzo_async.using_uvloop()}')"
- name: Test hanzo-async (no uvloop on Windows)
shell: pwsh
run: |
.\.venv\Scripts\Activate.ps1
python -c "
import sys, asyncio
from hanzo_async import using_uvloop, read_file, write_file, path_exists, mkdir
assert sys.platform == 'win32'
assert not using_uvloop(), 'uvloop should not be active on Windows'
print('hanzo-async: OK (asyncio backend)')
"
- name: Install tool packages
shell: pwsh
run: |
.\.venv\Scripts\Activate.ps1
uv pip install -e ./pkg/hanzo-tools
uv pip install -e ./pkg/hanzo-tools-core 2>$null; $true
uv pip install -e ./pkg/hanzo-tools-fs
uv pip install -e ./pkg/hanzo-tools-shell
uv pip install -e ./pkg/hanzo-tools-reasoning
uv pip install -e ./pkg/hanzo-tools-memory
uv pip install -e ./pkg/hanzo-tools-todo
uv pip install -e ./pkg/hanzo-tools-config
- name: Test tool imports on Windows
shell: pwsh
run: |
.\.venv\Scripts\Activate.ps1
python -c "
import sys
assert sys.platform == 'win32'
# Shell tools should resolve to pwsh/cmd on Windows
from hanzo_tools.shell.cmd_tool import CmdTool
tool = CmdTool()
shell = tool.default_shell
print(f'Resolved shell: {shell}')
assert 'pwsh' in shell.lower() or 'powershell' in shell.lower() or 'cmd' in shell.lower(), (
f'Expected Windows shell, got: {shell}'
)
# FS tools
from hanzo_tools.fs import TOOLS as fs_tools
print(f'FS tools: {len(fs_tools)}')
# Reasoning tools
from hanzo_tools.reasoning import TOOLS as reason_tools
print(f'Reasoning tools: {len(reason_tools)}')
print('All tool imports OK on Windows')
"
- name: Install hanzo-mcp
shell: pwsh
run: |
.\.venv\Scripts\Activate.ps1
uv pip install -e ./pkg/hanzo-mcp 2>$null; $true
# Override with local tool packages
uv pip install -e ./pkg/hanzo-tools -e ./pkg/hanzo-tools-fs `
-e ./pkg/hanzo-tools-shell -e ./pkg/hanzo-tools-memory `
-e ./pkg/hanzo-tools-todo -e ./pkg/hanzo-tools-reasoning `
-e ./pkg/hanzo-tools-config 2>$null; $true
- name: Test hanzo-mcp CLI
shell: pwsh
run: |
.\.venv\Scripts\Activate.ps1
python -m hanzo_mcp.cli --version
python -m hanzo_mcp.cli --help
- name: Run unit tests (Windows-compatible subset)
shell: pwsh
run: |
.\.venv\Scripts\Activate.ps1
cd pkg/hanzo-mcp
python -m pytest tests/test_llm_warnings.py -v -o "addopts=" 2>$null; $true
python -m pytest tests/test_hanzo_mcp_simple.py -v -o "addopts=" -k "test_cli_help or test_cli_version or test_import_tools" 2>$null; $true
# WSL2 (Ubuntu on Windows)
test-wsl:
name: WSL2 Ubuntu
runs-on: windows-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: Vampire/setup-wsl@v5
with:
distribution: Ubuntu-24.04
additional-packages: python3 python3-pip python3-venv curl
- name: Install uv in WSL
shell: wsl-bash {0}
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
- name: Install and test in WSL
shell: wsl-bash {0}
run: |
export PATH="$HOME/.local/bin:$PATH"
WORKSPACE=$(wslpath "$(powershell.exe -Command 'Write-Host -NoNewline $env:GITHUB_WORKSPACE')")
cd "$WORKSPACE"
uv venv
source .venv/bin/activate
# hanzoai is the root workspace package
uv pip install -e .
uv pip install -e ./pkg/hanzo-async
uv pip install pytest pytest-asyncio
# Test imports
python -c "from hanzoai import __version__; print(f'hanzoai {__version__}')"
python -c "
import hanzo_async
print(f'uvloop active: {hanzo_async.using_uvloop()}')
import sys
print(f'platform: {sys.platform}')
"
- name: Install tool packages in WSL
shell: wsl-bash {0}
run: |
export PATH="$HOME/.local/bin:$PATH"
WORKSPACE=$(wslpath "$(powershell.exe -Command 'Write-Host -NoNewline $env:GITHUB_WORKSPACE')")
cd "$WORKSPACE"
source .venv/bin/activate
uv pip install -e ./pkg/hanzo-tools
uv pip install -e ./pkg/hanzo-tools-core 2>/dev/null || true
uv pip install -e ./pkg/hanzo-tools-fs
uv pip install -e ./pkg/hanzo-tools-shell
uv pip install -e ./pkg/hanzo-tools-reasoning
uv pip install -e ./pkg/hanzo-tools-memory
uv pip install -e ./pkg/hanzo-tools-todo
uv pip install -e ./pkg/hanzo-tools-config
- name: Test tools in WSL
shell: wsl-bash {0}
run: |
export PATH="$HOME/.local/bin:$PATH"
WORKSPACE=$(wslpath "$(powershell.exe -Command 'Write-Host -NoNewline $env:GITHUB_WORKSPACE')")
cd "$WORKSPACE"
source .venv/bin/activate
python -c "
import sys
print(f'Platform: {sys.platform}')
assert sys.platform == 'linux', f'WSL should report linux, got {sys.platform}'
from hanzo_tools.shell.cmd_tool import CmdTool
tool = CmdTool()
print(f'Shell: {tool.default_shell}')
# WSL has bash/zsh
assert '/bin/' in tool.default_shell or 'zsh' in tool.default_shell or 'bash' in tool.default_shell
from hanzo_tools.fs import TOOLS as fs_tools
print(f'FS tools: {len(fs_tools)}')
from hanzo_tools.reasoning import TOOLS as reason_tools
print(f'Reasoning tools: {len(reason_tools)}')
print('All WSL tool imports OK')
"
- name: Install and test hanzo-mcp in WSL
shell: wsl-bash {0}
run: |
export PATH="$HOME/.local/bin:$PATH"
WORKSPACE=$(wslpath "$(powershell.exe -Command 'Write-Host -NoNewline $env:GITHUB_WORKSPACE')")
cd "$WORKSPACE"
source .venv/bin/activate
uv pip install -e ./pkg/hanzo-mcp 2>/dev/null || true
uv pip install -e ./pkg/hanzo-tools -e ./pkg/hanzo-tools-fs \
-e ./pkg/hanzo-tools-shell -e ./pkg/hanzo-tools-memory \
-e ./pkg/hanzo-tools-todo -e ./pkg/hanzo-tools-reasoning \
-e ./pkg/hanzo-tools-config 2>/dev/null || true
python -m hanzo_mcp.cli --version
python -m hanzo_mcp.cli --help
- name: Run tests in WSL
shell: wsl-bash {0}
run: |
export PATH="$HOME/.local/bin:$PATH"
WORKSPACE=$(wslpath "$(powershell.exe -Command 'Write-Host -NoNewline $env:GITHUB_WORKSPACE')")
cd "$WORKSPACE"
source .venv/bin/activate
cd pkg/hanzo-mcp
python -m pytest tests/test_llm_warnings.py -v -o "addopts=" 2>/dev/null || true
python -m pytest tests/test_hanzo_mcp_simple.py -v -o "addopts=" \
-k "test_cli_help or test_cli_version or test_import_tools" 2>/dev/null || true
-72
View File
@@ -1,72 +0,0 @@
name: Test Hanzo Python SDK
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
workflow_call: # Allow this workflow to be called by other workflows
jobs:
test:
runs-on: hanzo-build-linux-amd64
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-asyncio pytest-cov
cd pkg/hanzo && pip install -e .
- name: Run tests
run: |
cd pkg/hanzo
python -m pytest tests/ -v --cov=hanzo --cov-report=term-missing
- name: Test hanzo CLI
run: |
hanzo --version
hanzo --help
hanzo node --help
lint:
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff "black<26.1" mypy
- name: Lint with ruff
run: |
cd pkg/hanzo
ruff check src/
- name: Format check with black
run: |
cd pkg/hanzo
black --check src/
- name: Type check with mypy
run: |
cd pkg/hanzo
pip install -e .
mypy src/ --ignore-missing-imports || true