From 693be6e830925306b7edc6db8005c8af0a027333 Mon Sep 17 00:00:00 2001 From: DGPisces Date: Mon, 18 May 2026 22:49:32 -0700 Subject: [PATCH 1/2] chore(09): public-repo workflow setup (FLOW-01..05) - Switch CI to ubuntu-latest GitHub-hosted runners (FLOW-01) - Add CODEOWNERS with @DGPisces global rule (FLOW-03) - Add CHANGELOG.md (Keep a Changelog 1.1.0; v1.0.0 entry) (FLOW-05) - Add docs/release/RELEASE-FLOW.md: 8-step manual release recipe (FLOW-05) - Update CONTRIBUTING.md: CI contract for external fork PRs (FLOW-04) - Update install/public-allowlist.md: add CHANGELOG.md + CODEOWNERS Internal source: 4518793 (Phase 9 final commit on internal main) --- .github/workflows/ci.yml | 73 +++++++++----------- CHANGELOG.md | 38 +++++++++++ CODEOWNERS | 3 + CONTRIBUTING.md | 26 ++++++++ docs/release/RELEASE-FLOW.md | 125 +++++++++++++++++++++++++++++++++++ install/public-allowlist.md | 2 + 6 files changed, 227 insertions(+), 40 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 CODEOWNERS create mode 100644 docs/release/RELEASE-FLOW.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 325897a..1f505d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,19 +14,19 @@ permissions: jobs: backend: name: Backend lint and scoped tests - runs-on: [self-hosted, macOS] + runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install backend dependencies - # Use system python3.11 (homebrew on the self-hosted Mac runner) - # instead of actions/setup-python@v5, which hard-codes the - # GitHub-hosted /Users/runner/hostedtoolcache path and fails on a - # self-hosted Mac with `mkdir: /Users/runner: Permission denied` - # regardless of AGENT_TOOLSDIRECTORY / RUNNER_TOOL_CACHE / TOOLCACHE_ROOT. run: | - python3.11 -m venv .venv + python -m venv .venv . .venv/bin/activate python -m pip install --upgrade pip python -m pip install -e '.[dev]' @@ -34,16 +34,7 @@ jobs: - name: Ruff run: | . .venv/bin/activate - ruff check \ - src/vocalize/server/frames.py \ - src/vocalize/server/runner.py \ - tests/test_server_frames.py \ - tests/test_server_ws_integration.py \ - tests/test_runner_phase_transitions.py \ - tests/integration/ai_merchant.py \ - tests/integration/test_ai_merchant.py \ - tests/integration/test_judge.py \ - tests/integration/conftest.py + ruff check src/ - name: Mypy run: | @@ -73,7 +64,7 @@ jobs: frontend: name: Frontend type and unit tests - runs-on: [self-hosted, macOS] + runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 @@ -81,7 +72,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: "22" + node-version: '20' cache: npm cache-dependency-path: frontend/package-lock.json @@ -99,16 +90,19 @@ jobs: playwright-loopback: name: Playwright loopback - runs-on: [self-hosted, macOS] + runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install backend dependencies - # See backend job for why we skip actions/setup-python@v5 on the - # self-hosted Mac runner. run: | - python3.11 -m venv .venv + python -m venv .venv . .venv/bin/activate python -m pip install --upgrade pip python -m pip install -e '.[dev]' @@ -116,7 +110,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: "22" + node-version: '20' cache: npm cache-dependency-path: frontend/package-lock.json @@ -128,14 +122,6 @@ jobs: working-directory: frontend run: npm exec -- playwright install --with-deps chromium - - name: Free up port 8000 for loopback server - # Self-hosted Mac may have a dev uvicorn running on 8000. Playwright - # webServer.reuseExistingServer is !CI so it won't reuse it; if the - # port is busy, Playwright errors out. Kill anything bound to 8000 - # before starting. The developer can restart their uvicorn after CI. - run: | - lsof -ti :8000 | xargs kill -9 2>/dev/null || true - - name: Playwright integration tests working-directory: frontend run: > @@ -145,16 +131,25 @@ jobs: ai-merchant: name: AI merchant scenarios - runs-on: [self-hosted, macOS] + runs-on: ubuntu-latest + # Skip on fork PRs — secrets are not available and this job would fail + # rather than produce a meaningful result. External contributors get full + # coverage from backend + frontend + playwright-loopback jobs above. + if: > + github.event.pull_request.head.repo.full_name == github.repository || + github.event_name == 'push' steps: - name: Check out repository uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install backend dependencies - # See backend job for why we skip actions/setup-python@v5 on the - # self-hosted Mac runner. run: | - python3.11 -m venv .venv + python -m venv .venv . .venv/bin/activate python -m pip install --upgrade pip python -m pip install -e '.[dev]' @@ -182,9 +177,7 @@ jobs: VOCALIZE_ENABLE_TEST_FRAMES: "1" run: | export AI_MERCHANT_PR_COMMAND="pytest tests/integration/test_ai_merchant.py -q" - # python3.11 (not `python`) because the self-hosted Mac runner - # has homebrew python3.11 on PATH but no `python` alias. - python3.11 - <<'PY' + python - <<'PY' import os command = os.environ["AI_MERCHANT_PR_COMMAND"] @@ -201,7 +194,7 @@ jobs: - name: Upload AI merchant failure evidence if: failure() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v4 with: name: ai-merchant-evidence path: tests/integration/evidence/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..802634d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +All notable changes to VocalizeAI are documented in this file. + +Format follows [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/). +Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +_No unreleased changes yet._ + +--- + +## [1.0.0] — 2026-05-18 + +**First public release.** Apache 2.0 open-source milestone. + +Internal source: `d9bd923` · Public repo first commit: `591aa9e` + +Release notes: https://github.com/DGPisces/VocalizeAI/releases/tag/v1.0.0 + +### Added + +- **Universal phone-task engine** — 5-layer dialogue pipeline (preflight, question, + translate, summarize, merchant) handles arbitrary phone-call scenarios without + per-task configuration. +- **Bilingual zh/en UI** — React frontend with full Chinese and English locale + support from day 1. +- **Raspberry Pi orchestrator** — production-grade deploy target; install script + + systemd unit + cloudflared tunnel for zero-port-forward remote access. +- **Audio loopback testing** — Playwright + pytest harness drives + laptop-loopback calls and post-call callback flows; 8 text-bypass AI merchant + scenarios with a deterministic judge. +- **Apache 2.0 license** — OSS launch with SECURITY.md, CONTRIBUTING.md, + issue templates, PR template, and CODEOWNERS. + +[Unreleased]: https://github.com/DGPisces/VocalizeAI/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/DGPisces/VocalizeAI/releases/tag/v1.0.0 diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..7200ff2 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +# Single maintainer at v1.0 OSS launch. +# All PRs route to @DGPisces for review. +* @DGPisces diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39a1be1..c5037c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,6 +80,32 @@ directly to `main`. - Security vulnerabilities: follow the process in [SECURITY.md](SECURITY.md). Do NOT file public GitHub issues for security topics. +## CI behavior for external PRs + +VocalizeAI's CI pipeline has two tiers depending on where your PR originates: + +**External fork PRs** (contributors opening a PR from their own fork): +- Run: ruff lint (`src/`), mypy type check, pytest unit tests, TypeScript type check, + Vitest unit tests, and the Playwright loopback smoke tests. +- Skip: the `ai-merchant` job. This job requires repository secrets (LLM API keys) that + GitHub does not expose to fork PRs for security reasons. The job is skipped with a + neutral status — it is **not counted as a required check** for fork PRs. +- All skipped jobs show a "skipped" badge, not a failure. Your PR is considered + CI-green when all non-skipped jobs pass. + +**Internal PRs** (PRs opened from a branch within `DGPisces/VocalizeAI`): +- Run all jobs including `ai-merchant` (deterministic judge; no live LLM key required + in CI — real-LLM coverage is gated behind `--release-audio` and runs pre-release). + +**What this means for contributors:** +- You don't need to supply any API keys. Lint, type checks, and unit tests are fully + self-contained and run on GitHub-hosted ubuntu runners. +- If the maintainer needs to validate AI-merchant behavior on your PR, they will apply + the change to the internal repo (per the out-of-band contribution model above) and + run the full pipeline there. +- The `good first issue` label on GitHub Issues marks well-scoped bugs and features + suitable for first-time contributors. + ## Code of Conduct VocalizeAI does not adopt a formal Code of Conduct at this stage. Standard diff --git a/docs/release/RELEASE-FLOW.md b/docs/release/RELEASE-FLOW.md new file mode 100644 index 0000000..22d0591 --- /dev/null +++ b/docs/release/RELEASE-FLOW.md @@ -0,0 +1,125 @@ +# VocalizeAI Release Flow + +Manual release recipe for VocalizeAI. No automated tooling (Release Please, semantic-release) +is adopted at v1 scale — the solo-maintainer overhead exceeds the benefit. + +v1.0.0 was the first exercise of this flow (Phase 8), establishing the baseline. +Each subsequent release follows the same steps. + +--- + +## Prerequisites + +- `gh` CLI authenticated (`gh auth status`) +- Working directory clean on `main` (`git status`) +- All CI checks green on `main` + +--- + +## Step 1 — Create a release branch + +```bash +git checkout main && git pull +git checkout -b release/vX.Y.Z +``` + +--- + +## Step 2 — Bump version in pyproject.toml + +```bash +# Edit pyproject.toml: update [project] version = "X.Y.Z" +$EDITOR pyproject.toml +``` + +Commit: + +```bash +git add pyproject.toml +git commit -m "chore(release): bump version to X.Y.Z" +``` + +--- + +## Step 3 — Write CHANGELOG entry + +Edit `CHANGELOG.md` at repo root: + +1. Move items from `## [Unreleased]` into a new `## [X.Y.Z] — YYYY-MM-DD` section. +2. Leave `## [Unreleased]` empty with the stub line. +3. Add the comparison link at the bottom: + `[X.Y.Z]: https://github.com/DGPisces/VocalizeAI/compare/vA.B.C...vX.Y.Z` + +```bash +git add CHANGELOG.md +git commit -m "docs(release): CHANGELOG entry for vX.Y.Z" +``` + +--- + +## Step 4 — Open and merge the release PR + +```bash +git push -u origin release/vX.Y.Z +gh pr create \ + --base main \ + --title "Release vX.Y.Z" \ + --body "Bump version to X.Y.Z. See CHANGELOG.md for details." +``` + +Wait for CI to pass, self-approve (solo maintainer), then merge to `main`. + +```bash +# After merge: +git checkout main && git pull +``` + +--- + +## Step 5 — Tag the release + +```bash +git tag -a vX.Y.Z -m "Release vX.Y.Z" +git push origin vX.Y.Z +``` + +--- + +## Step 6 — Create GitHub Release + +Prepare a release notes file (can reuse the CHANGELOG section): + +```bash +# Extract the relevant CHANGELOG section into a temp file, or write inline: +gh release create vX.Y.Z \ + --title "vX.Y.Z" \ + --notes-file path/to/release-notes.md \ + --verify-tag +``` + +Alternatively, draft the release in the GitHub UI after pushing the tag. + +--- + +## Step 7 — Sync to public repo + +Run the sync-private-to-public flow (documented in `.planning/skills/`) to push +sanitized source to `github.com/DGPisces/VocalizeAI`. The tag and release travel +with the sync. + +--- + +## Step 8 — Post-release verification + +- Confirm the GitHub Release page shows the correct tag and notes. +- Confirm the Discussions > Announcements tab auto-created a release announcement (optional). +- Run layer4 smoke checklist against the production Pi deployment: + `docs/release/layer4-smoke-checklist.md`. + +--- + +## History + +| Version | Date | Internal SHA | Public SHA | +|---------|------------|--------------|------------| +| 1.0.0 | 2026-05-18 | d9bd923 | 591aa9e | diff --git a/install/public-allowlist.md b/install/public-allowlist.md index 2d844b4..8664e33 100644 --- a/install/public-allowlist.md +++ b/install/public-allowlist.md @@ -12,6 +12,8 @@ Paths are anchored at repo root. Used by `scripts/build-public-filelist.py`. - README.md - README.zh-CN.md +- CHANGELOG.md +- CODEOWNERS - LICENSE - SECURITY.md - CONTRIBUTING.md From 508bd9a80798d17e7ce970069143c73cc758cc1a Mon Sep 17 00:00:00 2001 From: DGPisces Date: Mon, 18 May 2026 22:51:52 -0700 Subject: [PATCH 2/2] fix(09): reorder imports in sessions.py to fix ruff E402 Logger assignment placed between stdlib and third-party imports was causing 4 E402 violations when ruff scope expanded to full src/ tree. Move log = logging.getLogger(__name__) after all imports. --- src/vocalize/server/sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vocalize/server/sessions.py b/src/vocalize/server/sessions.py index 4e1059d..28f6704 100644 --- a/src/vocalize/server/sessions.py +++ b/src/vocalize/server/sessions.py @@ -10,14 +10,14 @@ import secrets from typing import Literal -log = logging.getLogger(__name__) - from fastapi import Depends, FastAPI, Header, HTTPException, Request, status from pydantic import BaseModel, Field from vocalize.server.review import register_review_routes from vocalize.server.state import SessionRegistry +log = logging.getLogger(__name__) + class CreateSessionRequest(BaseModel): preferred_voice_id: str | None = None