Every folder has a single job. Every file at the root earns its place.
VoiceStudio/
│
├── README.md ⟵ user-facing overview (translations: docs/readme/README_*.md)
├── CHANGELOG.md ⟵ release history; Electron releases use the tagged section
├── CLAUDE.md / AGENTS.md ⟵ matching copies of the agent operating rules
├── LICENSE / LICENSE-NOTICE.md / SPONSORS.md ⟵ legal terms, required notices, supporters
│
├── pyproject.toml ⟵ Python metadata plus the pytest and lint settings
├── uv.lock ⟵ Python lockfile
├── package.json ⟵ monorepo manifest (Bun workspaces) and THE app
│ version (pyproject.toml + backend/core/version.py mirror it)
├── bun.lock ⟵ JS lockfile — repo-root, covers the electron/ workspace
├── flake.nix, flake.lock ⟵ community Nix dev shells + a Nix-built Linux app (docs/install/nix.md)
│
├── .coderabbit.yaml / greptile.json ⟵ bot-review setup; CLAUDE.md supplies both rule sets
├── skills-lock.json ⟵ records the origin and digest of each .agents/skills/ copy
├── .gitleaks.toml ⟵ repository secret-detection policy
├── .gitmodules ⟵ registers omnivoice-gallery
├── .python-version ⟵ interpreter selection for Python tooling
├── .dockerignore ⟵ Docker build context filter
├── alembic.ini ⟵ DB migration config (stays at root by alembic convention)
│
├── .gitignore ⟵ excludes a checkout-local .env; Settings persists user choices in
│ ~/.config/omnivoice/env through backend/core/user_env.py
│
├── backend/ ⟵ FastAPI server
│ ├── main.py sole process entry; its annotated startup sequence must retain its order
│ ├── api/routers/ 45 routers, auto-included; HTTP/WS adaptation stays thin here
│ │ └── setup/ setup-wizard endpoints and model acquisition
│ ├── core/ shared infrastructure: configuration, persistence, job/event plumbing,
│ │ auth and CSRF, safe paths, model artifact fingerprints, analytics consent,
│ │ version and diagnostics
│ ├── services/ 134 modules of business logic spanning speech, dubbing, DSP, GPU dispatch,
│ │ engine selection and loaded-model ownership
│ ├── engines/ per-engine adapters: audiocpp, confucius4, cosyvoice_gguf,
│ │ cosyvoice_subprocess, dots_tts, indextts, ming_tts, moss_sfx,
│ │ moss_tts_nano_subprocess, moss_tts_v15, omnivoice_gguf, omnivoice_subprocess, pockettts,
│ │ supertonic3, voxcpm2_subprocess, whisper_cpp, _asr_sidecar, _echo
│ ├── schemas/ Pydantic types at the request/response boundary
│ ├── config/models.yaml catalogue of selectable models
│ ├── migrations/versions/ mandatory Alembic home for database evolution
│ ├── plugins/ extension landing area; contract lives in services/plugin_sdk.py
│ ├── mcp_shim/ MCP launch surface documented by docs/mcp.md
│ ├── speech_client/ client entry for the speech sidecar
│ └── tests/ separately collected backend test session (details below)
│
├── electron/ ⟵ the only desktop app and web UI (Electron + React 19)
│ ├── src/main/ lifecycle, backend supervisor, runtime setup, updater, native IPC
│ ├── src/preload/ typed contextBridge → window.voicestudio
│ ├── src/renderer/ desktop React app: routes/, features/, components/, i18n/locales/
│ ├── src/shared/ modules shared by the desktop and browser builds (api/, store/,
│ │ utils/) — not a runnable app
│ ├── tests/ packaged-app, installer and workflow smoke tests
│ ├── build/ packaging inputs: icons, entitlements, Info.plist
│ ├── scripts/ dev launcher, AppImage update-info embedding
│ ├── vite.web.config.ts browser build (`bun run build:web`) → frontend/dist/
│ └── electron-builder.config.mjs installers: DMG/zip, NSIS, AppImage + deb
│
├── native/desktop-bridge/ ⟵ Rust helper used by Electron
│
├── frontend/dist/ ⟵ build output of `bun run build:web` (gitignored): the web UI
│ the backend serves in Docker and, from Electron packages, to LAN devices
│
├── omnivoice/ ⟵ the underlying TTS model package
│ ├── models/
│ ├── cli/ CLI entry points (omnivoice-infer, etc.)
│ ├── data/ data utilities used by the model
│ ├── eval/ evaluation scripts
│ ├── scripts/ one-off utilities that ship with the package
│ ├── training/
│ └── utils/
│
├── tests/ ⟵ default pytest collection selected by pyproject.toml `testpaths`
│ ├── conftest.py redirects OMNIVOICE_DATA_DIR to disposable state for the whole session
│ ├── backend/ / scripts/ path-shaped coverage for those source areas
│ ├── smoke/ quick end-to-end gates; isolated CI job runs with HF_HUB_OFFLINE=1
│ ├── probe/ / fixtures/ diagnostic probes and reusable inputs
│ └── frontend/ retained node:test suite for utilities and developer tooling
│
├── scripts/ ⟵ development, packaging and release commands (sh, Python, mjs)
│ ├── install.sh / install.ps1 installers delivered from voicestudio.sh/install
│ ├── dev-backend.mjs supervisor behind `bun run dev:api`
│ ├── setup.py platform finishing step for `bun run setup:api`, including ROCm opt-in
│ ├── prune-runtime-node-modules.mjs Nix build: keeps only the main process's runtime deps
│ ├── electron-smoke-test.mjs packaged-launch probe invoked by `bun run smoke-test`
│ ├── prepare_electron_release.py, check_electron_release_assets.py artifact verification
│ ├── check-docs-drift.py checks docs-drift.yml against authoritative docs/features.yaml
│ └── build-omnivoice-tts.sh produces the sidecars stored under bin/
│
├── bin/ ⟵ one precompiled omnivoice-tts sidecar for each supported platform
├── .agents/skills/ ⟵ real-file canonical skills whose revisions are locked in skills-lock.json
├── skills/ ⟵ distributable voicestudio and voicestudio-maintainer skill packages
│
├── deploy/ ⟵ container delivery plus the hosted installer worker
│ ├── Dockerfile CUDA baseline; BASE_IMAGE and GPU_FLAVOR let CI build ROCm too
│ ├── docker-compose.yml local one-command stack
│ ├── torch-constraints.txt image-specific torch-family lock
│ ├── security-constraints.txt security floors for the Docker and Colab installs
│ ├── security_floors.py raises and checks installed packages against those floors
│ ├── dockerhub-overview.md release-synchronised Docker Hub description
│ └── install-worker/ Cloudflare worker serving voicestudio.sh/install; embeds both installers
│ from scripts/ (see docs/install/script.md)
│
├── docs/ ⟵ engineering references, product imagery and brand material
│ ├── README.md current documentation index and maintenance policy
│ ├── STRUCTURE.md this map
│ ├── ROADMAP.md links to the live roadmap and issue trackers
│ ├── RELEASING.md complete multi-channel shipping checklist
│ ├── features.yaml source inventory consumed by docs-drift.yml
│ ├── adr/ durable architectural decisions
│ ├── agents/ tracker, triage-role and domain instructions for agents
│ ├── engines/ dubbing/ install/ setup/ migration/ features/ playbooks/ specs/
│ ├── media/ screenshots/ supporting media and captured UI
│ ├── preview.png / logo.* repository preview and logo assets
│ └── languages.md training.md data_preparation.md evaluation.md voice-design.md
│
├── examples/ ⟵ executable samples and inputs, including agentic/ and config/
├── notebooks/ ⟵ contains OmniVoice_Studio_Colab.ipynb
├── omnivoice-gallery/ ⟵ submodule that publishes the voice gallery
│
├── omnivoice_data/ ⟵ Docker bind-mount target (gitignored)
│ DB + HF cache live here when running via compose
│
├── .github/workflows/ ⟵ ci, electron-build, electron-release, docker, security,
│ docs-drift, install-smoke, build-omnivoice-tts,
│ cla, commit-identity, cosyvoice-dependencies
└── .git/
-
Nothing at the root is a runtime artifact. Outputs, temp files, local DBs, crash logs — all go to
~/Library/Application Support/OmniVoice/(or the OS equivalent), never into the repo. The one exception isomnivoice_data/, which exists as a bind-mount anchor for Docker. -
No ad-hoc scripts or tests at the root. Put one-off debugging tools in
scripts/; tests belong in one of the three homes below. -
Each subdirectory owns one concern. If you can't describe what goes in a directory in one sentence, it's wrong.
-
Packages declare dependencies independently.
backend/,electron/, andomnivoice/each have their ownpyproject.tomlorpackage.json, allowing them to be tested separately. The Bun workspace shares one rootbun.lock, and Docker uses a frozen install that fails rather than modifying it.
The single test job in ci.yml invokes three independent runners. Keeping their collection boundaries separate is intentional.
| Home | Runner | Why it's separate |
|---|---|---|
tests/ |
pytest tests/ (also the testpaths default) |
Primary Python collection. Its fixture redirects OMNIVOICE_DATA_DIR to temporary storage, preventing contact with developer data (#878). |
backend/tests/ |
pytest backend/tests/; CI step: “Run pytest (backend/tests, isolated)” |
Validates the backend's bare-import mode in a fresh session and uses its own disposable data root. Collection-time sys.modules fakes are forbidden because they escape their file and contaminate combined runs. |
electron/src/**/*.test.{ts,tsx} |
bun run test using Vitest/jsdom; CI enters through bun run check:electron |
UI tests sit with implementation. Packaged and workflow smoke coverage belongs to electron/tests/; older helper/dev-script node:test cases live at tests/frontend/ and use bun run test:frontend. |
| Kind of thing | Goes in |
|---|---|
| User-facing product code | backend/, electron/ |
| The TTS model (independent of the studio) | omnivoice/ |
| New TTS/ASR engine adapter | backend/engines/<engine>/ |
| Everything executable but not user-facing | scripts/ |
| Prebuilt platform sidecars | bin/ |
| Python tests | tests/ (or backend/tests/ when isolation is required) |
| Electron / UI unit tests | next to the module, as *.test.ts(x) |
| Developer + user docs (Markdown) | docs/ |
| Architecture decision records (ADRs) | docs/adr/ |
| Agent-facing documentation | docs/agents/ |
| Runnable demos and sample data | examples/ |
| Runtime data (never committed) | ~/Library/Application Support/OmniVoice/ on Mac |
Removed in the cleanup pass:
| File | Why it was there | Where it went |
|---|---|---|
test_crash.py, test_server.py, test_whisper.py, test_mock.py, test_pyannote.py |
One-off debug scripts from an April 14 crash investigation. Imported symbols that no longer exist after the router refactor. | Deleted (already gitignored, referenced dead code). |
benchmark.py |
Another stale debug script; imported backend.main._get_db which no longer exists. |
Deleted. |
output.wav, test.wav |
Runtime artifacts. | Deleted / moved out. |
crash_log.txt |
Runtime log. Now written to $DATA_DIR/crash_log.txt. |
Deleted. |
omnivoice.zip (148 MB) |
Offline reference archive of the project itself. | Moved out of the repo to ../omnivoice.zip.bak. |
data/ |
Only contained .DS_Store. |
Deleted. |
legacy_gradio/ |
The pre-React Gradio UI. Kept for historical reference. | Archived to research/legacy_gradio/, then removed in the 2026-07-12 cleanup (git history). |
Scattered .DS_Store files |
macOS Finder droppings. | Deleted from every non-ignored directory. |
Removed in the 2026-07-12 cleanup pass (all preserved in git history):
| Dir | Why it was there | Where it went |
|---|---|---|
.planning/ (74 files) |
GSD-era planning archive: phases, quick plans, issue clusters. The GSD workflow was retired 2026-07-08. | Deleted; the four load-bearing decision docs moved to docs/adr/. |
design/ |
ASCII mockups of the pre-React target UX, superseded by the shipped app. | Deleted. |
research/ |
Archived legacy Gradio UI + April-2026 competitor notes. | Deleted. |
.agents/ |
Previously belonged to a retired third-party agent tool. | Removed, then recreated for canonical skill copies pinned by skills-lock.json. |
The current flat layout works fine for the current size. If the project grows to include additional apps (a mobile companion, a plugin SDK, multiple backends), migrate to a Turborepo-style monorepo:
VoiceStudio/
├── apps/
│ ├── api/ ← was backend/
│ └── desktop/ ← was electron/ (desktop + browser UI)
├── packages/
│ ├── omnivoice-model/ ← was omnivoice/
│ └── tts-adapters/ ← new; the pluggable TTS interface from ROADMAP phase 3
├── config/
│ └── docker/
├── tests/
└── docs/
Do not execute this migration without a dedicated PR. It breaks:
pyproject.toml[tool.hatch.build.targets.{sdist,wheel}]pathspackage.jsonworkspaces and scriptsDockerfile,docker-compose.ymlpaths- Electron Builder, native-helper, updater and packaged-smoke paths
- every import that reads
from backend.main import …(tests, scripts) - the root
package.jsonas the version source of truth and its mirrors
Migrate when adding the second apps/* or the second packages/*. Not before.
- Filenames: snake_case for Python, kebab-case or PascalCase for JS/TS components, lowercase for Markdown.
- One-off scripts go into
scripts/with a descriptive name, nottest_*.pyat the root. - New top-level directories require a PR that updates this file.
- Test placement: mirrored suites follow source paths (
tests/backend/mirrorsapi/,core/,engines/andservices/; for example,backend/services/ffmpeg_utils.pymaps totests/backend/services/test_ffmpeg_utils.py). Otherwise backend-wide tests are flat intests/backend/, cross-cutting tests are flat intests/, and React component tests are colocated with their component.