Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 33 additions & 40 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,27 @@ 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]'

- 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: |
Expand Down Expand Up @@ -73,15 +64,15 @@ 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

- 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

Expand All @@ -99,24 +90,27 @@ 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]'

- 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

Expand All @@ -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: >
Expand All @@ -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]'
Expand Down Expand Up @@ -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"]
Expand All @@ -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/
Expand Down
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Single maintainer at v1.0 OSS launch.
# All PRs route to @DGPisces for review.
* @DGPisces
26 changes: 26 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
125 changes: 125 additions & 0 deletions docs/release/RELEASE-FLOW.md
Original file line number Diff line number Diff line change
@@ -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 |
2 changes: 2 additions & 0 deletions install/public-allowlist.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/vocalize/server/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading