mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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`.
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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/
|
||||
@@ -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')
|
||||
"
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user