| name | symusic-dependency-taste |
| description | Decision framework for selecting and evolving third-party dependencies in symusic across C++, Python packaging, bindings, testing, and docs. |
| metadata | {"domain":"symusic","role":"dependency-architecture-advisor","scope":"dependency-selection-and-governance","output-format":"decision-record-and-checklist"} |
Symusic Dependency Taste and Selection Framework
Use this skill when adding, replacing, or auditing dependencies.
High-Level Dependency Philosophy
-
Prefer focused libraries with clear performance or ergonomics wins.
- Symusic favors small, purpose-built components (parser, serializer, binding layer) over heavy frameworks.
-
Keep the dependency surface narrow and intentional.
- Every added dependency increases wheel complexity, ABI risk, cross-platform CI cost, and maintenance burden.
-
Keep core competency in-repo; outsource commodity building blocks.
- MIDI-domain semantics and transformation logic should stay first-party.
- Generic concerns (formatting, UTF-8 helpers, serialization primitives) can be external.
-
Choose libraries that align with cross-platform wheels and modern toolchains.
- Compatibility with C++20, Python 3.9+ wheels, and reproducible CI builds is non-negotiable.
Existing Stack Taste Analysis
C++/Core Dependencies
minimidi: chosen for fast and lightweight MIDI parsing; tightly aligned with symusic's core mission.
prestosynth: dedicated synthesis backend for SoundFont rendering, consistent with a focused audio-rendering layer.
pyvec: optimized container bridge that complements shared-pointer semantics and Python interop patterns.
nanobind: modern, efficient C++/Python binding layer; matches performance-first and typed API goals.
fmt: pragmatic formatting utility that improves clarity and maintainability over manual string assembly.
zpp_bits (header-only include): lightweight serialization building block for pickle support.
utfcpp + small utility headers (unordered_dense, macros): low-level helpers kept lean and local where practical.
Taste signal: this is a deliberately modular high-performance stack with minimal overlap between libraries.
Python Runtime Dependencies
numpy: foundational numerical ecosystem choice; necessary for array-oriented conversions and interoperability.
pySmartDL: targeted utility for managed downloads in soundfont workflows.
platformdirs: minimal, standard choice for per-platform cache/config path correctness.
Taste signal: runtime Python dependencies are intentionally few, stable, and functionally justified.
Test/Dev Dependencies
pytest, pytest-cov, pytest-xdist: standard, scalable Python testing stack.
mypy, ruff configuration in pyproject.toml: static and style guardrails with pragmatic exceptions.
pretty_midi (optional test extra): used as ecosystem interop/reference check rather than core runtime requirement.
Taste signal: strong quality automation, but optional extras remain clearly separated from runtime essentials.
Dependency Decision Rubric (Use Before Adding Anything New)
A candidate dependency should pass all gates:
- Problem clarity: Is there a repeated, non-trivial problem not already solved in-repo?
- Scope fit: Does the library solve exactly that problem without pulling broad framework baggage?
- Performance profile: Any predictable hot-path cost, memory overhead, or startup regression?
- Build/distribution impact: Does it complicate wheels, ABI compatibility, or CI matrix reliability?
- Maintenance health: Active releases, clear license, issue responsiveness, and transparent governance?
- Fallback plan: Can we pin, patch, or replace it safely if upstream breaks?
If any gate fails, prefer a smaller alternative or an internal implementation.
Preferred Choices by Category
- Bindings: stay with nanobind unless there is a compelling ABI/performance blocker.
- Numerics interop: keep NumPy as the canonical Python array surface.
- Paths/platform behavior: prefer tiny standard helpers like
platformdirs over custom OS branching.
- Formatting/logging in C++: keep
fmt style consistency.
- Serialization: keep lightweight binary serialization approaches; avoid monolithic runtime frameworks.
Red Flags (Usually Reject)
- Dependencies that duplicate existing stack capabilities with no measurable win.
- Libraries with unstable release cadence or unclear licensing.
- New runtime dependencies that are only needed for niche workflows (move those to optional extras).
- Python-only workarounds for problems best solved once in C++ core.
- Heavy frameworks that increase import/build time for marginal convenience.
How to Document a Dependency Change
For every addition/removal, include a short dependency decision record:
- Context: what limitation or pain point triggered the change?
- Decision: chosen library/version and integration boundary.
- Alternatives considered: at least one rejected option and why.
- Consequences: performance, binary size, CI, maintenance, and user-facing impact.
- Rollback strategy: how to safely revert or swap in future.
Minimal PR Checklist for Dependency Changes
- Runtime vs optional extra placement is correct.
- Lock/build metadata updated consistently.
- Cross-platform build path is validated.
- Tests cover new integration boundary.
- Docs mention user-visible installation/runtime implications.