# Contributing to Podspine Thanks for your interest in improving Podspine! This is a small, deliberately narrow tool — files in, per-chapter podcast feed out — so the best contributions keep that scope sharp. Bug fixes, format/edge-case handling, and docs are all very welcome. ## Before you start - For anything non-trivial, please **open an issue first** so we can agree on the approach before you write code. - Check existing [issues](https://github.com/schubydoo/podspine/issues) and the [CHANGELOG](CHANGELOG.md) so you're not duplicating work. - Out of scope by design: user accounts, a built-in player, ebooks, a full media library, and **any form of DRM circumvention** — please don't propose these. ## Development setup Podspine is a Rust workspace (one crate per pipeline stage). You need: - A recent stable **Rust** toolchain (see `rust-toolchain.toml` / `Cargo.toml` for the minimum version). - **`ffmpeg` and `ffprobe` on your `PATH`** — the prober/splitter shell out to them, and several tests synthesize fixtures with ffmpeg. ```bash cargo build cargo run -- --library ./sample-books # → http://localhost:8080 ``` ## Checks (all must pass — CI enforces them) ```bash cargo fmt --all --check cargo clippy --workspace --all-targets --all-features -- -D warnings cargo test --workspace --all-features ``` Please run these locally before opening a PR. Tests that need ffmpeg are gated on its availability, so run them with ffmpeg installed to actually exercise them. ## Branching & commits - Branch off `main`; PRs target `main`. - Use [Conventional Commits](https://www.conventionalcommits.org/) for messages, e.g. `feat(scanner): …`, `fix(feed): …`, `docs: …`. - Keep changes focused and the diff minimal — small, reviewable PRs merge faster. ## Pull requests - Fill in the PR template and link the issue it addresses. - Include tests for new behavior or bug fixes where practical. - Update the docs (`README.md`, `docs/`) when your change is user-visible. - **Add a changeset** for any user-visible change — create `.changeset/.md` (or run `knope document-change`) with YAML front matter and a **single-line** body: ```markdown --- default: minor --- Ingest Opus files with embedded chapters ``` - `default:` is one of `major` (breaking), `minor` (feature → Features), `patch` (fix → Fixes), `security`, or `perf`. - The body must be **exactly one line** — knope renders any second line/paragraph as a `#### heading` mid-list instead of a bullet, which corrupts the release notes. Fold all detail into that one sentence. (`scripts/lint_changesets.sh`, run by pre-commit, enforces this.) - **Never put a `README.md` or other doc in `.changeset/`** — knope parses every `.md` there and a non-fragment fails the release with "missing front matter". Releases and `CHANGELOG.md` are generated from these fragments — don't hand-edit the changelog. Internal-only PRs (CI, refactor, tests) skip it with the `no-changelog` label. ## A few project-specific rules worth knowing These are the invariants that keep feeds correct and the server safe — reviewers will look for them: - **Feed ordering is sacred.** `pubDate`s are sequential with oldest = chapter 1; always emit `itunes:episode`, `itunes:duration`, and an `enclosure length` read from the real output file (never prorated from bitrate). - **ffmpeg is invoked with an argv vector, never a shell string** — chapter titles and filenames are untrusted. - **Book/episode ids are opaque keys.** Never build a path from user input; canonicalize and assert it stays under the data directory. - Keep `ffmpeg`/`ffprobe` out-of-process (the GPL boundary) and DRM-free-only. ## License Podspine is licensed under **AGPL-3.0-only**. By contributing, you agree that your contributions are licensed under the same terms.