Skip to content

Latest commit

 

History

History
825 lines (663 loc) · 38.4 KB

development.md

File metadata and controls

825 lines (663 loc) · 38.4 KB

Development

Overview

This document defines how work on hum is done: the Rust toolchain policy, workspace conventions, dependency discipline, local validation, debugging and logging workflow, the relationship between documentation, issues, and code, the architecture-change process, the unsafe-code policy, Git conventions, and the Definition of Done for both contributors and coding agents.

hum is a native Rust modular monolith with a Cargo workspace. The internal crates are hum-core, hum-audio, hum-library, hum-config, hum-ui, and hum-platform, plus a root executable / composition root. The workspace, Cargo.lock, and a pinned toolchain now exist (Milestone 0); the subsystems themselves are still mostly unimplemented, and there is no CI yet. This document describes the intended workflow and uses "will", "must", and "once code exists" deliberately for the parts not yet built.

It owns engineering workflow. Subsystem specifications live in their own documents (architecture.md, audio-engine.md, testing.md, distribution.md, roadmap.md, and the rest) and are not duplicated here.

Core invariant

Planning documents describe the intended system. They do not grant permission to implement work outside the current issue.

A contributor or coding agent may read the roadmap, the architecture, and future subsystem documents without treating every planned capability as current implementation scope. Documentation answers how should this system work? The active issue answers what should be implemented now? These are never confused.

Specification, issues, and implementation

docs/            project specification — the intended system
   ▼
GitHub issues    scoped implementation work
   ▼
source code      implementation

Documentation describes the intended system. Issues describe the work required to make that system real. Code must conform to accepted documentation unless the task explicitly changes the architecture or specification.

Documentation as specification

Accepted documentation is authoritative. Code must not silently contradict it. When an implementation would change documented behavior, the owning document is updated in the same change (see Documentation changes).

Issues as implementation scope

The active GitHub issue or task defines what is implemented now. Adjacent roadmap items are not implemented merely because they look easy. For example, if the issue is "add the Symphonia decoder", that issue does not authorize also adding CPAL output, the queue, the TUI, or a config redesign. This rule is restated operationally in a future AGENTS.md.

Read order

A contributor working an issue inspects, roughly in order: the issue itself; the directly relevant subsystem document(s); architecture.md; related cross-subsystem documents; existing implementation and tests; and recent relevant Git history where useful. The rule is read enough accepted specification to understand the boundary being changed — not every file for every trivial change.

Conflict resolution

If an issue appears to contradict accepted documentation, do not silently pick one. Determine whether the issue intentionally changes the design, the issue is stale, the documentation is stale, or the scope is ambiguous. For architectural contradictions, stop and surface the discrepancy before inventing a new direction. Documented invariants are not silently overridden.

Rust toolchain

Language policy

Rust is the canonical implementation language. Another application language is not introduced because one subsystem seems easier in JavaScript, TypeScript, Python, Go, C++, or Java. Native platform bindings and dependencies may internally contain other languages, but hum's application implementation stays Rust. A language change would be a major architecture decision.

Stable Rust

Stable Rust is the default. Nightly Rust is not required for ordinary development unless a specific, documented technical requirement justifies it — in which case the reason is documented, the dependency is isolated, and the portability/tooling cost is weighed against the feature. Nightly is not used because it is interesting.

Edition

The workspace uses Rust edition 2024, selected at workspace initialization as the current stable edition with verified toolchain support. This is workspace policy; a change of edition is a deliberate decision.

Toolchain pinning

The project uses one explicit Rust toolchain policy, pinned in the root rust-toolchain.toml to a specific stable channel with the rustfmt and clippy components. The frozen principle: developer, CI, and release validation agree on the intended Rust toolchain, and local environments do not silently use incompatible compiler generations. Raising the pinned version is a deliberate change, kept consistent across rust-toolchain.toml, any CI, and this document.

Cargo workspace

The canonical implementation structure is a Cargo workspace:

hum/
├── Cargo.toml
├── Cargo.lock
├── src/
│   └── main.rs
└── crates/
    ├── hum-core/
    ├── hum-audio/
    ├── hum-library/
    ├── hum-config/
    ├── hum-ui/
    └── hum-platform/

This is planned. It is not initialized in this task. The physical structure may be refined if implementation evidence requires, but the architectural crate boundaries from architecture.md remain authoritative.

Cargo.lock

Per distribution.md, hum is an application. Once Cargo exists, Cargo.lock is committed and is not .gitignored. Dependency updates are deliberate and reviewed.

Dependencies

Adding dependencies

A dependency is added intentionally. Before adding a crate: What capability does it provide? Is it already provided by an accepted dependency? Does it belong in this crate? Does it leak infrastructure into hum-core? What runtime, build, and platform implications does it introduce? Is it maintained enough for the responsibility? What license does it use? Is it necessary now for the current issue? Dependencies are not added for hypothetical future features.

Canonical planned dependencies

The accepted stack (versions not claimed):

Concern Crate
TUI rendering Ratatui
Terminal I/O Crossterm
Audio output CPAL
Audio decoding Symphonia
Resampling Rubato
Real-time PCM SPSC buffer rtrb
Ordinary control channels crossbeam-channel
Metadata Lofty
Local persistence SQLite + rusqlite
Image decode/transform image
Terminal image integration ratatui-image
Filesystem watching notify
Serialization Serde
CLI parsing clap
Structured logging tracing
Library/domain errors thiserror
Composition root / CLI errors anyhow (where appropriate)

Alternative libraries duplicating these responsibilities are not added unless an accepted architecture change replaces them.

Dependency boundaries

Dependencies live only where their responsibility exists, following the dependency direction in architecture.md:

  • hum-core must not depend on CPAL, Ratatui, rusqlite, Lofty, image, notify, Tokio, or any other infrastructure crate. Before importing an external crate into core, confirm it is a domain/value-type utility, not infrastructure disguised as convenience. Core abstractions stay small and stable.
  • hum-ui may depend on TUI-specific libraries; hum-audio on audio processing/output libraries; hum-library on rusqlite/Lofty/notify; hum-config on parsing and file-watching libraries; hum-platform on path-resolution and platform libraries.
  • Internal crate dependencies stay acyclic. If two crates appear to need each other, reconsider ownership, a shared abstraction, or a command/event boundary — not merging responsibilities into core.

Cross-crate types live where their semantics naturally belong (core domain IDs in hum-core, audio-device types in hum-audio, terminal widget state in hum-ui). Boundary conversion is preferable to moving infrastructure-specific types into core.

Workspace dependency management

Once Cargo exists, common dependency versions may be managed through workspace dependency declarations where that reduces drift. A dependency is not centralized merely because Cargo permits it when only one crate uses it. The goal is coherent management, not configuration aesthetics.

Updating dependencies

Unrelated dependencies are not updated during a scoped feature or fix unless required:

dependency update ─▶ review diff / release notes ─▶ run relevant validation ─▶ commit intentionally

A broad cargo update is not run as incidental cleanup, and lockfile churn is kept out of unrelated issues.

Dependency licensing

Per distribution.md, dependency-license compatibility is verified before a public release. During development, a dependency whose license would conflict with the eventual project license/distribution model is not knowingly introduced. Because the project license is currently unresolved (see License status), licensing relevance is recorded without inventing compatibility conclusions.

Code quality

rustfmt

rustfmt is the canonical formatter. Once code exists, formatting validation is conceptually cargo fmt --all -- --check (or the workspace equivalent). Code is not hand-formatted around rustfmt for preference. This command does not run yet because Cargo does not exist.

Clippy

Clippy is the canonical Rust lint tool. A future baseline is conceptually cargo clippy --workspace --all-targets --all-features -- -D warnings (or an appropriate equivalent; --all-features is not frozen if future mutually-exclusive features make it invalid). Frozen: Clippy is required, warnings are treated seriously, and knowingly introduced warnings are not accepted casually. A large deny-list of stylistic lints is not created before code exists.

Compiler warnings

New code does not knowingly introduce compiler warnings. "Warning ignored because tests pass" is not normal practice. If a warning must be allowed, the allow is scoped narrowly and documented when non-obvious; broad warning classes are not disabled globally for convenience.

Error handling

Per architecture.md: thiserror for library/domain-specific error types; anyhow only at the composition root / CLI where broad contextual errors are appropriate. anyhow::Error is not propagated through core subsystem APIs by default. Errors preserve useful semantic categories. Expected filesystem, config, decode, device, database, and malformed-input failures are not handled through panic.

Panics

Panics represent programming defects and invariant violations, not ordinary runtime errors. unwrap() and expect() are not used on user- or environment-controlled operations for convenience. expect may be appropriate when a genuine internal invariant has already been proven and the message explains it. Tests may use unwrap/expect where failure should abort the test and the context is obvious; production code deserves stricter scrutiny, but tests are not contorted into verbose error plumbing to eliminate every unwrap.

Unsafe Rust

unsafe is not prohibited, but it is exceptional. Before adding it: show why safe Rust is insufficient; keep the block/module narrow; document the safety invariants; test the surrounding behavior; and do not expose unsafe requirements to unrelated code. Audio real-time performance is not automatically sufficient justification — safe libraries are used first. Every unsafe block is reviewable in isolation; giant unsafe regions are avoided; SAFETY: comments explain non-obvious invariants without merely restating the code. If unsafe is introduced, tools such as Miri may be used where applicable (see testing.md).

Architecture discipline

Crate boundaries and visibility

Internal crate boundaries have intentional APIs. A type or function is not made pub merely because another crate might someday need it; the narrowest practical visibility is used. Large shared utility crates are not allowed to become dependency magnets.

Commands and events

Per architecture.md, commands mean something should happen and events mean something has happened. A generic enterprise event bus is not created. Direct calls are allowed inside clean boundaries; channels are used where concurrency requires them; not every function call becomes asynchronous messaging.

Concurrency

  • Bounded crossbeam-channel channels for ordinary control where appropriate; rtrb only for the real-time PCM SPSC boundary. Unbounded queues are not used for high-frequency data; crossbeam is not the PCM callback buffer; rtrb is not a generic app event bus.
  • Tokio is not introduced as the default runtime. The architecture prefers OS threads, normal channels, and real-time-safe buffers. Tokio may fit later inside a genuinely network-heavy adapter if a future feature explicitly requires it — not for local SQLite, filesystem scans, config reload, or ordinary TUI work.
  • One OS thread is not spawned per music file, artwork request, or watcher event. Concurrency stays bounded with workload-aware pools/workers consistent with subsystem docs; exact worker counts are implementation decisions.
  • Blocking work (SQLite, metadata parsing, image decode, filesystem scanning) belongs off the TUI/render loop and off the real-time callback. The UI receives prepared state and results. Blocking work is not hidden behind an innocent-looking function called during draw.

Real-time audio boundary

The CPAL callback is a protected boundary. Any change touching it is reviewed against audio-engine.md. Prohibited steady-state callback behavior: filesystem I/O, SQLite, metadata parsing, image work, terminal work, decoding, resampling, logging, blocking, contended locks, unbounded allocation. These rules are not "temporarily" violated for convenience.

The real-time callback is not debugged with ordinary logging inside it. Use real-time-safe counters, atomics, and bounded telemetry, observed and logged from non-real-time code.

Platform-specific code

Conditional compilation stays near platform/integration boundaries. Before adding #[cfg(target_os = "...")] inside a non-platform crate, ask whether the behavior belongs in hum-platform, a dependency adapter, or a platform-specific module. Domain semantics do not differ by OS without explicit architecture approval. Platform-specific behavior is validated on the actual OS before it is declared supported; cross-compilation is not native validation (see platform-support.md).

Feature flags, build scripts, code generation

  • Cargo feature flags stay sparse and justified — genuinely optional integration or a build-time capability with clear product meaning. They are not used to hide broken code, to select OS behavior cfg already handles, or to maintain many untested combinations. Every supported combination is test surface.
  • build.rs is avoided unless there is a concrete need (build metadata, native compilation). It is not added just to generate constants ordinary Cargo metadata can provide.
  • Code generation is not introduced without clear value. Generated files have explicit ownership (generated at build time, or generated and committed, never hand-edited). Opaque generation pipelines are not created for simple domain types.

No speculative infrastructure

The simplest architecture consistent with the documented future boundaries is implemented. A plugin system, networking, daemon mode, a generic event bus, an enormous abstraction layer, unused traits, or a generic persistence framework are not added "for later" without a current requirement. Both short-term hacks that violate architecture and speculative enterprise architecture are avoided.

An abstraction is introduced when it protects a real boundary — the physical audio sink seam, platform paths, application command interfaces, the terminal backend/test seam. It is not introduced around every struct, as a repository trait over logic that never needs substitution, or as a wrapper over standard library APIs with no platform/test reason. Ports and adapters are tools, not ceremony.

Testing and validation

Testing policy is owned by testing.md; this section covers the developer workflow.

Local baseline

For ordinary Rust changes, the eventual baseline is at least format, lint, and tests:

cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace

These do not run yet — Cargo does not exist. The normal test suite does not require real audio hardware. cargo test is the valid baseline runner; cargo nextest is not mandated.

Scope-specific validation

Validation is proportional to the affected boundary, not "run everything for every change":

Change Validation
hum-core core tests, workspace tests, lint/format
layout layout tests / property tests, TUI tests, workspace tests
audio pipeline audio fixture/state/buffering tests, workspace tests, relevant manual/device validation when behavior requires it
docs-only link checks, configured Markdown checks, cross-document consistency review

The issue/task states any additional validation. Expensive hardware/manual tests are not run for every tiny change.

Regression tests and fixtures

Per testing.md: a confirmed bug receives a regression test where practical, reproduced before the fix when practical, at the smallest meaningful boundary. Speculative patches whose effect cannot be demonstrated are avoided.

New binary fixtures require known expected properties, legal redistribution rights, small size, and a clear purpose. Commercial music and artwork are not used casually. A large fixture is not added just because a user's local file reproduced a bug — a safe fixture is reduced or generated where practical. Repository test fixtures are kept separate from developer local music and from hum runtime database/cache; runtime-generated state is never committed as a fixture.

Debugging and logging

tracing is used for structured diagnostics. Logs carry useful context — fields such as track_id, root_id, path, device, operation, error_category where appropriate. Logs do not contain PCM buffers, artwork bytes, whole-library dumps, or secrets. Missing artwork and expected empty states do not flood logs.

Log levels are used deliberately: trace for very detailed development diagnostics, debug for useful subsystem diagnostics, info for meaningful lifecycle state, warn for a recoverable unexpected condition, error for an operation/subsystem failure. error is not used for expected normal states such as no artwork, an empty queue, or a missing optional config file. Exact messages are not frozen.

Developers primarily use normal debug builds for iteration, but performance and real-time behavior are also validated in release-like builds where optimization materially changes behavior — audio performance is not concluded from debug mode alone. Release builds are not required for every unit test.

Temporary development instrumentation (verbose per-frame logs, PCM dumps, giant trace files, debug overlays) must not remain enabled in normal release behavior. If a permanent diagnostic is useful, it is designed intentionally with bounded output.

println! / eprintln! debugging is not left in the active full-screen TUI path — it corrupts terminal rendering. Use tracing / log files or an explicit debug view. CLI and pre-TUI paths may legitimately use stdout/stderr.

Filesystem and local development safety

Per filesystem.md:

  • Automated tests and scripts never use the developer's real music library, real HOME, real XDG directories, or an existing hum database as a target. They use isolated temporary roots.
  • Development experiments may explicitly point hum at local music manually, but temp artifacts are never created inside library roots.
  • Parser and reload changes are tested against fixture config, an alternate config path once supported, or a temporary config directory — never by editing the developer's canonical real config. A parser bug is reproducible without damaging dotfiles.
  • Running hum locally may create normal app data under platform paths. Developers should have a way to use isolated development/test paths where needed; the exact mechanism (a CLI flag, an env override, a test mode) is not frozen here. Wiping real user state is not required for development.
  • The developer's machine configuration is not the product specification. /home/<name>, /Users/<name>, personal Music directories, a specific audio device, a terminal name, a distro, or a username are never hardcoded — use platform/config/test abstractions.

Database and configuration evolution

  • Migrations (once persistence exists, per library.md): every schema change uses a migration; migration files are immutable after a public release except to correct a never-released state; upgrade paths receive tests; non-rebuildable user data is preserved. The user's real database is not manually edited for development tests, and DROP DATABASE-style reset workflows are not run against user data — temporary DBs and controlled migration fixtures are used.
  • Configuration (once public syntax exists, per configuration.md): breaking changes are deliberate, docs are updated, release notes include migration guidance, deprecated fields may bridge transitions, and config is never silently rewritten. Before a public release, syntax may evolve faster, but accepted docs stay aligned.

Performance and profiling

Measure before optimizing. Complex caches, unsafe code, lock-free structures, parallelism, and micro-optimizations are not added without a measured reason (the real-time PCM path is an already-justified exception). Performance goals are tied to actual user behavior — uninterrupted playback, a responsive TUI, bounded memory, reasonable scans — with no fabricated benchmark numbers.

When a performance problem appears, profile the correct build and workload. A performance change identifies the measured problem, the changed behavior, and the validation. Code unrelated to a real bottleneck is not micro-optimized. Tools are not frozen. Benchmarks may be added after meaningful implementation exists (never against placeholder code), use representative workloads, report context, and do not treat one machine's result as universal. Large generated profiling artifacts (flamegraphs, perf recordings, heap dumps) are not committed by default.

Git workflow

Working tree

Before modifying code: inspect git status, understand existing changes, and avoid overwriting unrelated work. If the working tree already contains unrelated modifications, leave them alone, stage only issue files, and mention interference if it prevents safe completion. Destructive cleanup — git reset --hard, git clean -fd — is never run without explicit permission. git add . is not used blindly when unrelated working-tree changes may exist.

Commits

  • All commit messages are in English and follow Conventional Commits.
  • Types: feat, fix, test, docs, refactor, perf, build, ci, chore. Scopes are used where they improve clarity (feat(audio):, fix(library):), not forced for every repository-wide change.
  • Subjects are concise, imperative, lowercase where natural, no trailing period, preferably under 72 characters. "Updated stuff", "final fixes", and "misc changes" are not acceptable.
  • Commit messages contain no mention of AI, Claude, Codex, prompts, or generated code, and no AI co-author attribution.
  • A commit represents one coherent purpose. Feature implementation, dependency upgrades, architecture refactors, repository-wide formatting, and unrelated cleanup are not combined in one commit. Multiple coherent commits for one issue are fine; meaningful history is not artificially squashed.
  • A commit is never known to fail required tests, fail compilation, fail formatting, violate documented architecture, or leave an issue intentionally half-implemented without explicit task design. This matters especially for agent-created commits.

Example subjects:

chore: initialize cargo workspace
feat(audio): add symphonia decoder
feat(library): add sqlite track index
test(audio): add decoder fixtures
fix(artwork): handle missing embedded covers
docs: define project roadmap

Amend, rebase, push

Existing shared history is not rewritten casually. A coding agent does not amend, squash, or rebase unless the task explicitly requests it; new work creates new commits, which matters when an issue is implemented incrementally. Work is not pushed unless explicitly requested — local completion and commit are distinct from publication. A future issue workflow may explicitly request pushes/PRs.

Branches and pull requests

A complex branching model is not invented before it is needed. Normal development can use short-lived task branches; Git Flow is not required. Branch naming is decided by the workflow system later and is not frozen here.

When pull requests are used, they explain the problem, the approach, important architecture decisions, validation, and known limitations. Massive raw logs are not pasted. A PR is not required for every local exploratory commit before the collaboration workflow is established.

Documentation changes

If an implementation changes documented behavior, the owning document is updated in the same issue/change — code says A, docs say B is never knowingly merged. If behavior is purely an implementation detail that stays within the spec, a docs change may not be needed.

The document that owns the concept is updated (audio callback semantics → audio-engine.md; filesystem path policy → filesystem.md; keybinding context → keybindings.md). The same full specification is not duplicated across documents — links are used.

All project technical documentation is in English: README, docs, issue technical descriptions where practical, commit messages, code comments, and user-facing config/reference names. Portuguese is not mixed into canonical technical docs unless an explicit localization feature is being implemented.

Comments explain why, invariants, non-obvious constraints, unsafe justification, real-time requirements, and external quirks — not obvious syntax. Rustdoc documents important contracts (invariants, ownership, threading, callback restrictions, error semantics) where useful, with effort following architectural significance rather than covering every private helper. A TODO references an issue, an explicit deferred decision, or a short explanation; unrelated roadmap work is not implemented just to remove a TODO, and large planned work is tracked as GitHub issues.

Architecture changes

An implementation task may reveal that accepted architecture is inadequate. It is not worked around silently:

implementation evidence
      ▼
identify the documented assumption
      ▼
propose an architecture / documentation change
      ▼
review / accept
      ▼
implement

Documentation is updated when a change affects subsystem responsibility, an invariant, a technology choice, persistence semantics, config semantics, a concurrency boundary, support policy, or user-visible stable behavior. A local helper refactor does not require documenting.

Definition of Done

Implementation tasks

A normal implementation issue is complete when the applicable items are true:

  • the issue scope is implemented;
  • no unrelated roadmap work was added;
  • architecture boundaries are preserved;
  • relevant errors, empty states, and invalid input are handled;
  • tests are added/updated at the smallest appropriate boundary;
  • a confirmed bug has regression coverage where practical;
  • relevant existing tests pass;
  • formatting passes;
  • Clippy / static validation passes as applicable;
  • documentation is updated if documented behavior changed;
  • no secrets or user-local artifacts are included;
  • the complete diff has been reviewed;
  • only intended files are staged;
  • commit(s) follow repository conventions;
  • unresolved scope questions are reported rather than invented;
  • nothing is pushed unless requested.

A physical hardware test is not required when the issue does not affect hardware behavior.

Documentation tasks

A documentation-only task is complete when:

  • the target document is correctly updated;
  • canonical names and links are valid;
  • no unrelated docs were expanded;
  • cross-document contradictions were checked;
  • configured Markdown checks were run if available;
  • the diff was inspected;
  • the correct Conventional Commit was created when requested.

Irrelevant Rust validation is not run before the workspace exists.

Review triggers

A reviewer asks: is the scope correct? Is the architecture preserved? Do tests prove the behavior? Are errors handled? Is the real-time boundary respected? Are filesystem and user data safe? Are platform assumptions justified? Are docs aligned? Is the diff focused? Review is about architecture and behavior, not only style.

  • Security scrutiny for changes touching config parsing, filesystem path construction, image/media decoding boundaries, unsafe Rust, release credentials, process execution, or future network code. hum's local-first nature reduces network attack surface but does not make local files trusted.
  • Performance scrutiny for changes touching the CPAL callback, the decoder/render pipeline, library scanning, artwork decoding/cache, the TUI redraw loop, large-library search, or layout hot paths. Not every string helper is benchmarked.
  • Platform validation for changes affecting application paths, signals, the terminal lifecycle, terminal protocol detection, keyboard normalization, CPAL integration, or notify backend assumptions. A Linux-local fix is not assumed to work identically on macOS.

Local developer environment

Developer setup is kept small. Once code exists, the expected baseline is Git, a stable Rust toolchain, the platform build dependencies required by the chosen Rust crates, a usable terminal, and an optional audio device for manual audio testing. Docker, Node.js, PostgreSQL, Redis, and a JVM are not required for ordinary hum development unless a future explicit tool requires one.

Docker is not part of the canonical runtime or ordinary development requirement; it may later help reproduce a build environment or CI but is not inherently useful for audio-device or terminal-protocol testing.

The project requires no specific editor or IDE; instructions work from the shell. VS Code, IntelliJ, and Neovim are not required. Editor configuration files may be added later only if they encode project-wide formatting/tooling rather than personal preference.

Local Git hooks are not required initially — they are local, bypassable, and another setup step. Canonical correctness comes from documented commands and CI, not from assuming a hook ran. A future optional hook may be provided.

CI will automate portions of the same documented local validation; a CI-only model where contributors cannot run checks locally is not created. The CI provider and matrix are intentionally deferred (see platform-support.md and testing.md). Development work prepares a repository state that distribution can release; contributors do not manually create release artifacts during normal feature work unless the issue is specifically about distribution (see distribution.md).

License status

The repository's LICENSE file is empty and no software license has been chosen. Per distribution.md, this is not changed here, and no public release occurs while it is unresolved. Development may proceed before license selection, but contribution and public-release policy may need revisiting once outside contributors are invited. Contributor licensing terms are not invented.

A detailed external-contributor governance model is not built yet; there is no CONTRIBUTING.md. If external contribution volume grows, contribution guidance can be introduced explicitly later. For now, this document provides the engineering workflow.

Relationship to AGENTS.md and CLAUDE.md

AGENTS.md will later turn this policy into concise operational rules for coding agents, referencing the docs and extracting hard rules. CLAUDE.md will later hold Claude-specific working instructions. Neither is populated now. This document is the human-readable engineering specification and stays tool/agent-neutral; the entire future AGENTS.md is not duplicated here.

Development invariants

These hold unless changed through the architecture-change process.

  1. Rust is the canonical application implementation language.
  2. Stable Rust is the default toolchain direction.
  3. The project uses a Cargo workspace once implementation begins.
  4. Cargo.lock is committed for the application.
  5. rustfmt is the canonical formatter.
  6. Clippy is the canonical Rust lint tool.
  7. New code does not knowingly introduce compiler warnings.
  8. Tests exercise the smallest meaningful boundary.
  9. Normal automated tests do not require real hardware.
  10. Documentation is the project specification.
  11. GitHub issues/tasks define implementation scope.
  12. Planning documents do not authorize implementation outside the active issue.
  13. Code must not silently contradict accepted documentation.
  14. Architecture changes are proposed explicitly rather than hidden in implementation.
  15. hum-core remains infrastructure-independent.
  16. Dependency direction remains acyclic and follows architecture.
  17. Dependencies are added only for current, justified capabilities.
  18. Dependency updates are deliberate, not incidental lockfile churn.
  19. Tokio is not introduced as the default application runtime.
  20. rtrb remains the real-time PCM boundary, not a generic event bus.
  21. Bounded crossbeam channels remain the ordinary control direction where appropriate.
  22. The CPAL callback never gains blocking or infrastructure work.
  23. unsafe Rust is exceptional, narrow, justified, and documented.
  24. Expected runtime errors are not handled through panic.
  25. anyhow does not become the default error type across library boundaries.
  26. Structured logging uses tracing.
  27. TUI code does not leave stdout/stderr debug prints in active rendering paths.
  28. Automated tests never destructively use real user music/config/data.
  29. Database schema changes use migrations once persistence exists.
  30. User configuration is not silently rewritten.
  31. Platform conditionals stay near platform/integration boundaries.
  32. Cross-compilation is not treated as native platform validation.
  33. Feature flags remain sparse and justified.
  34. Build scripts and code generation require a concrete need.
  35. Public visibility is kept as narrow as practical.
  36. Current issue scope is not expanded with unrelated roadmap work.
  37. Working-tree changes unrelated to the issue are not discarded or staged.
  38. Destructive Git cleanup is never used without explicit permission.
  39. Commits use English Conventional Commits.
  40. Commit messages contain no AI attribution.
  41. Existing commits are not amended, rebased, or squashed unless explicitly requested.
  42. Work is not pushed unless explicitly requested.
  43. Relevant validation runs before a commit.
  44. Known failing required validation is not committed as finished work.
  45. Bug fixes receive regression tests where practical.
  46. Documentation changes accompany changes to documented behavior.
  47. User-local paths, devices, and platform assumptions are never hardcoded.
  48. Secrets and release credentials are never committed.
  49. Generated build/runtime artifacts are not source.
  50. A normal implementation is complete only when its applicable Definition of Done is satisfied.

Decisions left for implementation

Deliberately deferred, pending implementation evidence:

  • the exact workspace physical organization if implementation reveals a better canonical shape;
  • the exact Clippy command and any allow policy once features exist;
  • the exact CI validation command set;
  • the developer setup package names per platform;
  • the branch naming convention;
  • the PR template/process;
  • optional pre-commit hooks;
  • benchmark and profiling tooling;
  • the isolated dev-data/config override mechanism;
  • the external contribution workflow;
  • whether CONTRIBUTING.md is introduced later.

These are deferred decisions, not gaps in the workflow.

Related documentation