chore: update development documentation and dependencies
- Added important notes regarding Docker operations to prevent disruption of the hot-reload server. - Introduced a section outlining forbidden Docker commands without explicit user permission. - Refactored `pyproject.toml` to categorize audio-related dependencies under an optional section. - Updated dependencies for `weasyprint` and `spacy` to their latest versions. - Adjusted the server Dockerfile to ensure the hot-reload server starts correctly. All quality checks pass.
This commit is contained in:
10
CLAUDE.md
10
CLAUDE.md
@@ -61,6 +61,8 @@ python scripts/dev_watch_server.py # Auto-reload server (watches src/)
|
||||
|
||||
## Docker Development
|
||||
|
||||
**IMPORTANT: The server runs with hot-reload enabled.** Assume Docker services are always running. Never restart, rebuild, or stop containers without explicit user permission—doing so disrupts the development workflow.
|
||||
|
||||
```bash
|
||||
# Start PostgreSQL (with pgvector)
|
||||
docker compose up -d postgres
|
||||
@@ -75,6 +77,14 @@ python scripts/dev_watch_server.py # Uses watchfiles, monitors src/ and alembic
|
||||
|
||||
Dev container features: dbus-x11, GTK-3, libgl1 for system tray and hotkey support.
|
||||
|
||||
### Forbidden Docker Operations (without explicit permission)
|
||||
- `docker compose build` — rebuilds images, disrupts running containers
|
||||
- `docker compose up` / `down` / `restart` — starts/stops services
|
||||
- `docker stop` / `docker kill` — kills running containers
|
||||
- Any command that would interrupt the hot-reload server
|
||||
|
||||
Code changes are automatically picked up by the watchfiles-based hot-reload. If you need to suggest a Docker operation, ask the user first.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
|
||||
@@ -61,6 +61,8 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
|
||||
EXPOSE 50051
|
||||
|
||||
CMD ["uv", "run", "python", "scripts/dev_watch_server.py"]
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# NER stage: Add spaCy model for named entity recognition
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -7,9 +7,6 @@ requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
# Core
|
||||
"pydantic>=2.0",
|
||||
# Audio
|
||||
"sounddevice>=0.4.6",
|
||||
"numpy>=1.26",
|
||||
# Spike 3: ASR
|
||||
"faster-whisper>=1.0",
|
||||
# Spike 4: Encryption
|
||||
@@ -27,17 +24,18 @@ dependencies = [
|
||||
# Settings
|
||||
"pydantic-settings>=2.0",
|
||||
"psutil>=7.1.3",
|
||||
"diart>=0.9.2",
|
||||
# HTTP client for webhooks and integrations
|
||||
"httpx>=0.27",
|
||||
"weasyprint>=67.0",
|
||||
"authlib>=1.6.6",
|
||||
"spacy>=3.8.11",
|
||||
"rich>=14.2.0",
|
||||
"types-psutil>=7.2.0.20251228",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
audio = [
|
||||
"sounddevice>=0.4.6",
|
||||
"numpy>=1.26",
|
||||
]
|
||||
dev = [
|
||||
"pytest>=8.0",
|
||||
"pytest-cov>=4.0",
|
||||
@@ -61,10 +59,10 @@ diarization = [
|
||||
"torch>=2.0",
|
||||
]
|
||||
pdf = [
|
||||
"weasyprint>=62.0",
|
||||
"weasyprint>=67.0",
|
||||
]
|
||||
ner = [
|
||||
"spacy>=3.7",
|
||||
"spacy>=3.8.11",
|
||||
]
|
||||
calendar = [
|
||||
"google-api-python-client>=2.100",
|
||||
@@ -77,8 +75,36 @@ observability = [
|
||||
"opentelemetry-instrumentation-grpc>=0.49b",
|
||||
"opentelemetry-exporter-otlp>=1.28",
|
||||
]
|
||||
optional = [
|
||||
# Audio
|
||||
"sounddevice>=0.4.6",
|
||||
"numpy>=1.26",
|
||||
# Triggers
|
||||
"pywinctl>=0.3",
|
||||
# Summarization
|
||||
"ollama>=0.6.1",
|
||||
"openai>=2.13.0",
|
||||
"anthropic>=0.75.0",
|
||||
# Diarization
|
||||
"pyannote.audio>=3.3",
|
||||
"diart>=0.9.2",
|
||||
"torch>=2.0",
|
||||
# PDF export
|
||||
"weasyprint>=67.0",
|
||||
# NER
|
||||
"spacy>=3.8.11",
|
||||
# Calendar
|
||||
"google-api-python-client>=2.100",
|
||||
"google-auth>=2.23",
|
||||
"google-auth-oauthlib>=1.1",
|
||||
# Observability
|
||||
"opentelemetry-api>=1.28",
|
||||
"opentelemetry-sdk>=1.28",
|
||||
"opentelemetry-instrumentation-grpc>=0.49b",
|
||||
"opentelemetry-exporter-otlp>=1.28",
|
||||
]
|
||||
all = [
|
||||
"noteflow[dev,triggers,summarization,diarization,pdf,ner,calendar,observability]",
|
||||
"noteflow[audio,dev,triggers,summarization,diarization,pdf,ner,calendar,observability]",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
@@ -184,7 +210,15 @@ reportUnknownVariableType = false
|
||||
reportArgumentType = false # proto enums accept ints at runtime
|
||||
reportIncompatibleVariableOverride = false # SQLAlchemy __table_args__
|
||||
reportAttributeAccessIssue = false # SQLAlchemy mapped column assignments
|
||||
reportMissingImports = "warning" # Optional deps like opentelemetry may not be installed
|
||||
reportMissingImports = "warning" # Optional deps (audio, summarization, triggers, etc.) may not be installed
|
||||
exclude = ["**/proto/*_pb2*.py", "**/proto/*_pb2*.pyi", ".venv"]
|
||||
venvPath = "."
|
||||
venv = ".venv"
|
||||
|
||||
[tool.pyrefly]
|
||||
pythonVersion = "3.12"
|
||||
python-interpreter-path = "/home/vasceannie/repos/noteflow/.venv/bin/python"
|
||||
site-package-path = ["/home/vasceannie/repos/noteflow/.venv/lib/python3.12/site-packages"]
|
||||
exclude = ["**/proto/*_pb2*.py", "**/proto/*_pb2*.pyi", ".venv"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
|
||||
@@ -128,7 +128,7 @@ class SoundDeviceCapture:
|
||||
self._callback(audio_data, timestamp)
|
||||
|
||||
try:
|
||||
self._stream = sd.InputStream(
|
||||
stream = sd.InputStream(
|
||||
device=device_id,
|
||||
channels=channels,
|
||||
samplerate=sample_rate,
|
||||
@@ -136,7 +136,8 @@ class SoundDeviceCapture:
|
||||
dtype=np.float32,
|
||||
callback=_stream_callback,
|
||||
)
|
||||
self._stream.start()
|
||||
stream.start()
|
||||
self._stream = stream
|
||||
logger.info(
|
||||
"Started audio capture: device=%s, rate=%d, channels=%d, blocksize=%d",
|
||||
device_id,
|
||||
@@ -154,10 +155,11 @@ class SoundDeviceCapture:
|
||||
|
||||
Safe to call even if not capturing.
|
||||
"""
|
||||
if self._stream is not None:
|
||||
stream = self._stream
|
||||
if stream is not None:
|
||||
try:
|
||||
self._stream.stop()
|
||||
self._stream.close()
|
||||
stream.stop()
|
||||
stream.close()
|
||||
except sd.PortAudioError as e:
|
||||
logger.warning("Error stopping audio stream: %s", e)
|
||||
finally:
|
||||
@@ -171,7 +173,8 @@ class SoundDeviceCapture:
|
||||
Returns:
|
||||
True if capture is active.
|
||||
"""
|
||||
return self._stream is not None and self._stream.active
|
||||
stream = self._stream
|
||||
return stream is not None and stream.active
|
||||
|
||||
@property
|
||||
def current_device_id(self) -> int | None:
|
||||
|
||||
Reference in New Issue
Block a user