Unified guidance for AI coding assistants (Copilot, Claude, others) collaborating on the VideoAnnotator project.
- Name: VideoAnnotator
- Current Release: v1.5.0
- In Progress: v1.6.0, the public release (
docs/development/roadmap_v1.6.0.md). Sections below written for v1.2.x describe the registry's origins;CLAUDE.mdand the roadmap are current. - Viewer: the Video Annotation Viewer lives in
viewer/(merged 2026-09-30); see §23. - Primary Users: Individual researchers (90%) needing simple local installs.
- Secondary Users: Lab / enterprise research groups (10%).
- Core Pillars: Modular pipelines, standardized outputs, reproducibility, minimal friction.
AI agents MUST optimize for:
- Low-friction local usability (no mandatory external services).
- Predictable, non-breaking incremental improvements (especially in v1.2.x series).
- Standard formats first (COCO, WebVTT, RTTM) – no bespoke schema proliferation.
- Future extensibility (registry metadata now → adaptive routing/plugins later).
- Clear, ASCII-safe console / log output (Windows encoding constraint: NO emoji, no fancy unicode blocks that may fail in PowerShell).
| Concern | Rule | Rationale |
|---|---|---|
| Emoji / Unicode | Prohibited in console/log prints | Windows charmap crashes |
| Log Directory | Use setup_videoannotator_logging(); do not reinvent handlers |
Central consistency |
| Structured Logs | Prefer logger over print; CLI may still use typer.echo for UX |
Unified diagnostics |
| Error Prefix | Use [ERROR], [WARNING], [OK], [START] |
Consistency + plain ASCII |
| JSON Output | Provide optional --json for machine parsing (v1.2.1 introduction) |
Scripting support |
Minimal YAML-driven registry located at src/registry/metadata/ (to be added). Each file MUST define:
name: string (slug)
display_name: string
category: tracking|detection|analysis|segmentation|preprocessing
description: short sentence
outputs:
- format: COCO|RTTM|WebVTT|JSON
types: [list of semantic output types]
config_schema: { field_name: { type, default, description } }
examples: optional list (cli / api usage)
version: integer schema version
Assistant tasks involving pipelines should:
- Load via registry once implemented rather than hard-coding.
- Offer graceful degradation if metadata missing (warn, skip).
- Avoid adding advanced fields early (resources, capabilities) unless explicitly approved (those are v1.3.0 targets).
| Aspect | Convention |
|---|---|
| CLI Base Command | videoannotator (Typer app) |
| Job Submission | videoannotator job submit |
| Pipelines Listing | videoannotator pipelines --detailed (backed by registry) |
| Config Validation | videoannotator config --validate path/to.yaml |
| Health Check | /health (enriched incrementally) |
| Pipeline Endpoint | /api/v1/pipelines dynamic (replace mock) |
When adding new CLI commands:
- Provide
--jsonif the output lists entities. - Fail fast with exit codes; include a suggestion line for common errors.
- Single version source in
src/version.py(no duplicated literals). - Registry metadata includes its own
version(schema evolution). - Generated docs (pipeline specs) MUST be reproducible – diffs trigger CI failure.
- For v1.2.x the public API reports the package version; plan to introduce a dedicated API semantic version constant in v1.3.0.
| Tier | Path | Goal | Runtime |
|---|---|---|---|
| Unit | tests/unit/ |
Fast logic validation | < 1 min total |
| Integration | tests/integration/ |
Cross-component behaviors | ~5 min |
| Pipelines | tests/pipelines/ |
Real or simulated model runs | Longer |
Assistant-created code MUST include at least minimal tests for:
- Registry loader integrity.
- API pipeline endpoint parity with registry.
- Markdown generation (smoke: file contains each pipeline name).
Workspace Configuration:
- All terminals open in
/workspaces/VideoAnnotatorby default (dev container). - No need to prefix commands with
cd /workspaces/VideoAnnotator &&. - Use
uvfor all Python operations (see section 21 for details).
Command Patterns:
# Correct (terminal is already in workspace)
uv run pytest tests/unit/
# Incorrect (unnecessary)
cd /workspaces/VideoAnnotator && uv run pytest tests/unit/File Paths:
- Use absolute paths when required by tools:
/workspaces/VideoAnnotator/src/... - Relative paths work naturally since terminal CWD is the workspace root.
- Plugin execution sandbox
- Multi-modal correlation graph engine
- Active learning feedback store
- Real-time streaming/WebRTC stack
- RBAC / SSO / multi-tenancy / audit log frameworks
- Complex resource scheduling or autoscaling logic
- Rich TUI or progress bar framework
- Favor lazy initialization; avoid loading heavy models outside
initialize(). - Avoid hidden network calls during import time.
- Annotate any model download operations with explicit logging.
- Provide timing logs (execution time context manager available in
logging_config).
- Never log raw PII (e.g., full file system paths of user home beyond what’s already necessary).
- Validate file existence before processing.
- Reject unsupported video formats with clear error messages.
- Avoid embedding secrets or tokens in code or docs.
| Action | Required Practice |
|---|---|
| File Edits | Small, atomic patches with rationale |
| New Features | Add tests + docs stub in same PR scope |
| Refactors | Preserve public CLI/API unless release notes updated |
| Docs Generation | Provide script, never hand-edit generated file |
| Logging Additions | Use existing logger categories (api, pipelines, requests, database) |
Standard error envelope (foundation for future):
{
"error": {
"code": "PIPELINE_NOT_FOUND",
"message": "Pipeline 'xyz' not registered",
"hint": "Run 'videoannotator pipelines --detailed' to list available pipelines"
}
}
Assistant changes touching API endpoints should migrate ad hoc responses toward this structure (without breaking existing clients unless major release).
Before approving output that touches user-visible text:
- No emojis or fancy unicode
- Paths use
Pathabstractions not hard-coded separators - Avoid color control chars (future optional feature)
| Category | v1.2.1 Expectation | v1.3.0 Expansion |
|---|---|---|
| Registry | Minimal YAML + loader | Capability & resource aware, plugin aware |
| Config Validation | Presence & primitive types | Full schema + templates + precedence engine |
| Health | Basic enrichment (registry, GPU flag) | Deep performance & scaling metrics |
| Errors | Simple envelope | Taxonomy + GraphQL alignment |
| Docs | Generated pipeline specs | Marketplace + plugin publishing |
| CLI | JSON option & consistent formatting | Color/TUI, adaptive suggestions |
- Is the change needed for v1.2.1 scope? If not, defer.
- Can it be undone easily if incorrect? If not, request human confirmation.
- Does it introduce silent behavior change? If yes, add explicit note to changelog.
- Is there a testable seam? Add or extend a test BEFORE large refactor.
- Implement class in
src/pipelines/<name>/ - Provide metadata YAML
- Update tests (registry presence)
- Regenerate docs (script) → commit
- Propose field (resources/capabilities) → doc it in metadata README
- Add parsing with backward-compatible default
- Update one metadata file as exemplar
- Add validation + tests
- Adding model downloads in global import scope
- Introducing new colorized/emoji output prematurely
- Creating parallel config systems instead of extending registry
- Hard-coding pipeline lists in multiple places
- Adding advanced multi-modal logic in v1.2.1 timeframe
- No emoji / unicode hazards
- Registry unaffected OR metadata updated
- Tests added/updated (if feature code)
- Docs regenerated (if pipeline changes)
- Logging consistent & non-duplicative
- Version not duplicated outside
version.py
Use labels/phrases to keep scope controlled:
scope:v1.2.1-foundationdefer:v1.3.0needs:registry-extensiontype:docs-generationtype:cli-ux
If generating Azure deployment or infra code:
The project STANDARD is to use uv (https://github.com/astral-sh/uv) for all Python dependency resolution, execution, and testing. Assistants MUST NOT rely on a pre-activated conda / venv environment when giving commands or running tests.
Required practices:
- Installation / Sync
- To sync the environment exactly to
pyproject.toml+uv.lock:uv sync(never manuallypip installinside the venv for project deps)
- Adding / Removing Dependencies
- Add runtime dependency:
uv add <package> - Add dev/test dependency:
uv add --dev <package> - Remove:
uv remove <package> - After changes: commit updated
uv.lock.
- Running Code
- Module / script:
uv run python -m videoannotator ... - CLI entry (if defined):
uv run videoannotator ...
- Tests & Quality
- Full test suite:
uv run pytest -q(or with markers/coverage flags) - Ruff lint (if configured):
uv run ruff check . - Mypy (if configured):
uv run mypy src/
- REPL / One-off
uv run python(never rely on external shell’s site-packages)
- Caching & Performance
- Let
uvmanage wheels; do not manually clear caches unless diagnosing a build issue.
- Consistency Enforcement
- If guidance or scripts previously referenced bare
pytest/python, update them touv runform. - Any test failure reproduction steps in issues/PR descriptions MUST use
uv run.
- CI Alignment
- CI should mirror:
uv syncthenuv run pytest .... Do not introduce alternative installers.
Common Anti-Patterns to Avoid (uv):
- Using
pip install -e .directly (useuv syncinstead). - Activating a conda environment and running
pytestwithoutuv run. - Editing
requirements.txt(the project is pyproject+lock driven; no requirements.txt unless generated artifact).
Assistant Heuristic Update:
- Before running tests, always ensure commands are prefixed with
uv rununless explicitly operating inside a scriptedmaketarget that already does so. - If a user asks for install instructions, default to
uvcommands.
Quick Reference:
uv sync # create/update environment
uv run pytest -q # run tests
uv add requests # add runtime dep
uv add --dev pytest-cov # add dev dep
uv run videoannotator --help
If any discrepancy between an active shell environment and uv run behavior is suspected, treat the uv run result as canonical.
The project uses pre-commit hooks (ruff, ruff-format, mypy, trailing-whitespace) that run automatically on git commit. Understanding their behavior reduces iteration cycles.
The project has DIFFERENT mypy strictness for different modules (see pyproject.toml):
Strict modules (src/api/*, tests/api/*, tests/integration/test_api_integration):
check_untyped_defs = truedisallow_incomplete_defs = trueno_implicit_optional = true
Relaxed modules (pipelines, storage, utils, exporters, schemas):
- Excluded from strict checking (reduces noise from ML/storage deps)
-
Pydantic Model Config Classes (RUF012)
- Use
json_schema_extra: ClassVar[dict[str, Any]] = {...} - Import:
from typing import ClassVar, Any
- Use
-
Exception Subclass Attributes
- Use inline annotations in
__init__:self.code: str = code - Avoid class-level annotations on Exception subclasses
- Use inline annotations in
-
Mixed-Type Dictionaries
- Declare explicit type upfront:
detail: dict[str, str | int] = {} - Mypy infers dict value type from first assignment
- Declare explicit type upfront:
-
Mypy Cache Staleness
- Clear cache when errors persist:
rm -rf .mypy_cache ~/.cache/pre-commit/mypy*
- Clear cache when errors persist:
Before committing:
uv run ruff format src/api/v1/ # Format first
uv run mypy src/api/ # Check types
uv run ruff check src/api/ # LintIf pre-commit fails:
- Auto-formatted files → re-stage:
git add <files> - Mypy cache stale → clear:
rm -rf .mypy_cache - After verifying fixes → last resort:
git commit --no-verify
Hook order: trailing-whitespace → end-of-file-fixer → ruff → ruff-format → mypy
Files modified by first 4 hooks require re-staging.
For new src/api/ modules:
- All parameters and returns typed
- Pydantic Config: use
ClassVarfor class attributes - Exceptions: inline type annotations in
__init__ - Mixed dicts: explicit
dict[str, str | int]hints - Import
ClassVar,Anyfromtyping
| Error | Fix |
|---|---|
| RUF012 mutable class attribute | ClassVar[dict[str, Any]] |
| dict type mismatch | Declare dict[str, str | int] upfront |
| Exception init type error | Inline: self.attr: type = value |
| Stale mypy errors | Clear .mypy_cache |
| Pre-commit formatting | Re-stage files |
The Video Annotation Viewer: React 18 + TypeScript (strict), Vite, Tailwind, shadcn/ui, Zod. It
renders VideoAnnotator's outputs (COCO person/face/OpenFace/VLM JSON, WebVTT, RTTM, scenes) over the
video, and drives the API (job submission, SSE progress, results). It was a separate repository
(InfantLab/video-annotation-viewer) until v1.5.0; it now shares VideoAnnotator's version number
(viewer/package.json follows pyproject.toml; tests/unit/test_versions_match.py checks) and its
changelog (the root CHANGELOG.md; viewer/CHANGELOG.md is frozen history). Standalone mode
(drop in files, no server) is still supported.
bash scripts/dev.sh # API server + Vite dev server (port 19011, proxies to 18011)
cd viewer && bun run lint && bun run test:run
cd viewer && bun run typecheck # zero errors, checked in CI (plain `tsc` checks nothing)
bash scripts/gen_viewer_api_types.sh # after an API change: regenerate src/api/schema.d.ts
cd viewer && bun run e2e # Playwright smoke (bun run e2e:install first)
bash scripts/build_viewer.sh # rebuild into src/videoannotator/viewer_static/Any change under viewer/src/ needs scripts/build_viewer.sh and the rebuilt viewer_static/
committed in the same commit; CI's viewer job runs --check and fails on a stale bundle.
- API client:
viewer/src/api/client.ts; hooks (SSE, health, job actions):viewer/src/hooks/; pages:viewer/src/pages/; shared types:viewer/src/types/annotations.ts. - A new output format: type in
types/annotations.ts, parser inlib/parsers/<format>.ts, detection and merge inlib/parsers/merger.ts, drawing incomponents/VideoPlayer.tsx(and the timeline), validation inlib/validation.ts, tests inviewer/src/test/. - Changing what a pipeline writes, or how the viewer reads it: refresh the real-output fixtures in
tests/fixtures/viewer_contract/(its README says how) soviewer/src/test/contract/videoannotator-outputs.test.tstests the new shape. - Job results load from the artifacts zip (
hooks/useZipDownloader.ts), through the same detection and merge as dropped-in files.
- Reuse shadcn/ui components (
viewer/src/components/ui/); validate external data with Zod. - ESLint's
react-refresh/only-export-components: a module that exports a component exports only components; move constants, helper hooks and variants to their own modules. - Keep
react-hooks/exhaustive-depsclean (don't disable it); stabilise derived deps withuseMemo. - Use
127.0.0.1, notlocalhost, for the API: the browser keeps separate storage for the two, so a token saved under one is missing under the other. - Tests mock the network; no real API calls in CI. jsdom's
Filedrops NodeBuffers (size 0): build test files from strings. - Large files: load progressively; don't block the UI.
- Viewer specs before the merge are in
viewer/specs/(history); new viewer work gets a spec inspecs/like any other feature, under the one constitution (.specify/memory/constitution.md), which covers the viewer since v1.1.0: no telemetry (I), the formats it reads (II), layers (IV), UI stability (V), and faithful display (VI): never create or hide values; holding the latest sample until the next is allowed; overlays attributable to their pipeline.