Skip to content

Commit b4b70db

Browse files
bitwize-musicgithub-actions[bot]claudezeel2104alijahak
authored
chore: release 0.91.0 (#358)
* chore: bump develop to 0.90.0-dev * chore: add .worktrees/ to gitignore * feat: add atomic_write_text() utility for crash-safe file writes Refs #140 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use atomic writes for all handler file operations Replaces direct path.write_text() with atomic_write_text() at 5 sites to prevent file corruption from interrupted writes. Updates two tests that mocked Path.write_text to instead mock atomic_write_text at the correct module scope. Refs #140 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove TOCTOU race in state lock acquisition Replace mtime-based stale lock detection with exponential backoff. fcntl.flock locks auto-release on process death, so stale detection was unnecessary and introduced a race condition. Refs #140 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace silent except-pass with specific types and logging All 5 bare 'except Exception: pass' handlers now catch specific exception types and log warnings so failures are diagnosable. Refs #140 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: backup corrupted state.json and degrade to empty state read_state() now creates a timestamped .corrupt backup when JSON parsing fails, logs at ERROR level, and returns an empty dict instead of None. Prevents silent cache poisoning. Refs #140 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use staging directory for mastering to prevent orphaned files master_album now writes to .mastering_staging/ and moves files to mastered/ only after all tracks succeed. Staging is cleaned up on failure so mastered/ never contains partial results. Refs #140 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: detect stale plugin cache with missing/ghost skill registration (#234) Add health_check MCP tool that combines venv and skill registration checks into a single session-start call. Compares on-disk skills against the Claude Code plugin cache to detect missing skills (lyric-refiner, voice-checker, genre-creator) and ghost registrations (ship). Integrates into session-start, diagnose, and test suite. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add missing mypy type arguments for dict returns Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add health-check skill for on-demand plugin health checks Lets users run /bitwize-music:health-check anytime to check venv packages and skill registration without a full session-start. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: prevent health-check skill from using Bash instead of MCP tool Haiku model was calling python3 -m mcp.cli via Bash instead of using the MCP tool interface directly. Added explicit instruction to use the tool interface only. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add ToolSearch to health-check allowed-tools, spell out exact MCP call Haiku needs ToolSearch to load deferred MCP tool schemas before calling them. Also made the workflow steps explicit with the full tool name to prevent the model from improvising with Bash. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add Discord server link to about skill Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add true peak limiting and TPDF dithering to mastering pipeline Replace sample-peak limiter with ITU-R BS.1770-4 true peak detection using 4x oversampling via scipy.signal.resample_poly. Add TPDF dither as the final step before 16-bit quantization to eliminate correlated truncation distortion. Add truepeak QC check to qc_tracks. Closes #245 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: clarify stem import wording in README example Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add input length bounds to text analysis MCP tools Add 50,000-character max-length validation to all text-input handlers: check_homographs, check_explicit_content, scan_artist_names, count_syllables, analyze_readability, analyze_rhyme_scheme, and extract_distinctive_phrases. Returns a clear JSON error when exceeded. Closes #242 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add negative prompting guidance to suno-engineer skill Add per-genre exclude styles tables to genre-practices.md for Hip-Hop, Alternative Rock, Electronic, Folk/Indie, and Country with concrete instrument/element exclusions per arrangement type. Add output-focused quality checklist item to verify exclusions were effective. Closes #232 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: bump pypdf 6.9.2 → 6.10.0 (GHSA-3crg-w4f6-42mx) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add code of conduct Be excellent to each other. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: wire saturation, lowpass, and stereo width into mix pipeline (#246) (#256) Implement Phase 1 of audio enhancement processing: wire existing dead preset fields (saturation_drive, lowpass_cutoff, stereo_width) that were tuned across 120+ genre entries but never had implementation code. - Add apply_saturation() — tanh soft saturation with level normalization - Add apply_lowpass() — 2nd-order Butterworth lowpass (mirrors apply_highpass) - Add _apply_character_effects() helper for consistent chain ordering - Update enhance_stereo() to support negative amounts (narrowing) - Wire effects into all 12 stem processors + full_mix per YAML chain spec - All defaults are off (drive=0, cutoff=20000, width=1.0) — no behavior change unless a genre preset overrides Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract hardcoded mastering params into configurable presets (#255) * refactor: convert genre presets from tuples to dicts load_genre_presets() now returns dict[str, dict[str, float]] instead of dict[str, tuple[float, ...]]. Adds _PRESET_DEFAULTS with all 12 preset keys including 7 newly exposed parameters. Updates all test assertions from tuple destructuring to dict key access. Refs: #251 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add compression, EQ, and dither defaults to genre-presets.yaml Adds compress_ratio, compress_threshold, compress_attack, compress_release, eq_highmid_freq, eq_highmid_q, eq_highs_freq, eq_highs_q, and dither_bits to the defaults block. Individual genres inherit these automatically. Closes the compress_ratio gap (was handled in code but missing from YAML). Refs: #251 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: thread preset dict through master_track and _process_one_track master_track() and _process_one_track() now accept an optional preset dict. When provided, compression params (threshold, attack, release), EQ params (freq, Q for both bands), and dither_bits are read from the preset. Legacy positional args still work for backward compatibility. Refs: #251 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: wire preset dict through main() with CLI overrides main() now builds a full preset dict from defaults -> genre preset -> CLI overrides, then passes it through the call chain. Adds 8 new CLI args: --compress-threshold, --compress-attack, --compress-release, --eq-highmid-freq, --eq-highmid-q, --eq-highs-freq, --eq-highs-q, --dither-bits. Session banner shows all active parameters. Refs: #251 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update override example and skill docs with new preset fields Documents all 12 configurable mastering parameters in the override example file and updates mastering-engineer skill with new override format examples. Removes stale boost_sub references. Refs: #251 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update MCP handlers to use dict-based genre presets The mastering preset refactor changed load_genre_presets() from returning tuples to dicts, but the MCP audio handlers still used tuple unpacking. Update both master_audio and master_album handlers to use dict key access. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: update test mocks to use dict-based genre presets Test mocks were still returning tuple presets after the tuple→dict refactor, causing 4 test failures in CI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add low-end EQ, stereo width, and 24-bit output to mastering (#252) (#257) Add three new mastering capabilities: - Low shelf EQ (eq_low_freq/eq_low_gain/eq_low_q) for bass shaping, plus sub-bass HPF (eq_sub_cut_freq) for rumble removal - Stereo width control (stereo_width) with optional bass mono fold (stereo_bass_mono_freq) for club/PA coherence - Selectable output bit depth (output_bits: 16 or 24) for platforms that accept 24-bit (Tidal HiFi, Apple Lossless, Bandcamp) All defaults are passthrough — no behavior change without explicit preset or CLI override. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add look-ahead limiting, parallel compression, oversampling, and LRA (#253) (#258) Upgrade the mastering dynamics chain with five new capabilities: - Look-ahead limiter with smooth release envelope — pre-applies gain reduction before transients hit, replacing reactive limiting - Parallel compression (wet/dry blend via compress_mix) — preserves dynamics while adding density - Oversampled nonlinear processing (processing_oversample: 1/2/4) — reduces aliasing from compression and limiting - LRA measurement and targeting (target_lra) — measures loudness range per EBU R128 short-term method - Compressor makeup gain (compress_makeup) — compensates for compression gain reduction All defaults preserve existing behavior exactly. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add DC offset removal, de-essing, sample rate conversion, and track gaps (#254) (#259) Add four new mastering pipeline stages: - DC offset removal (dc_filter_freq) — subsonic HPF as first processing stage, removes DC bias that wastes headroom - De-esser (deess_enabled/freq/bandwidth/threshold/ratio) — frequency- selective compression for sibilance reduction, applied after EQ - Sample rate conversion (output_sample_rate) — rational resampling via polyphase FIR, applied after processing before dither - Inter-track gap insertion (track_gap) — prepends silence for album sequencing, applied after dither before write All defaults preserve existing behavior. DC filter defaults to 5Hz (always-on subsonic cleanup); all others default to off/bypass. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add sub-bass exciter and transient shaping to mix pipeline (#246 Phase 2) (#260) Add two new per-stem effects: - Sub-bass harmonic exciter (bass stem) — isolates sub-bass via lowpass, generates upper harmonics via tanh + squaring waveshaping, highpasses harmonics to keep only generated content, blends back. Makes bass audible on small speakers. - Transient shaper (drums/percussion stems) — dual-envelope detection using the existing numba-accelerated envelope follower. Compares fast envelope (transients) against slow envelope (sustain) to independently boost/cut attack and sustain. New preset fields: sub_bass_exciter, sub_bass_freq (bass), transient_attack_db, transient_sustain_db (drums/percussion). All defaults are off — no behavior change without explicit override. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * Log failed optional module imports in processing helpers * feat: add multiband compression and mid/side EQ to mastering (#246 Phase 3) (#261) Add two mastering enhancements: - 3-band multiband compression with Linkwitz-Riley crossovers — splits signal into low/mid/high bands, compresses each independently with per-band ratio and threshold, recombines. Replaces single-band compression when enabled. Prevents pumping artifacts from bass triggering full-band gain reduction. - Mid/side EQ for frequency-selective stereo management — applies low shelf and high shelf EQ to the side channel only. Negative low gain narrows bass (mono bass coherence), positive high gain widens treble. New preset fields: multiband_enabled, multiband_{low,high}_crossover, multiband_{low,mid,high}_{ratio,threshold}, midside_{low,high}_{gain,freq}. All defaults are off — no behavior change without explicit override. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add album loudness consistency and extended loudness metering (#254) (#263) Complete remaining #254 items: - Album-level loudness consistency (--album-consistency): two-pass mastering that analyzes source LUFS across all tracks, then adjusts per-track targets to keep the spread within tolerance. Quieter sources get slightly higher targets, louder sources get slightly lower. - Short-term and momentary loudness in analysis: sliding-window LUFS measurements (3s short-term, 400ms momentary) per EBU R128. Reports max short-term, max momentary, and short-term range in analysis output. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add linear-phase FIR EQ option to mastering pipeline (#254) (#264) Add eq_linear_phase preset flag that switches EQ stages from minimum-phase IIR biquads to linear-phase FIR filters via frequency-sampled design. Zero phase distortion at the cost of higher latency (irrelevant for offline mastering). Completes the final item from #254. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add iterative LRA targeting and missing --deess-bandwidth CLI arg (#265) * fix: add iterative LRA targeting and missing --deess-bandwidth CLI arg LRA targeting (#253): previously only measured and reported LRA without acting on it. Now iteratively increases compression ratio (up to 5 iterations, capped at 8.0) when measured LRA exceeds target_lra, re-running compression from a pre-compression checkpoint each iteration. De-esser bandwidth (#254): adds missing --deess-bandwidth CLI arg to match the other four de-esser CLI parameters. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve mypy union-attr errors on pre_compress_data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve audit gaps across mastering issues #251-#254 (#266) Bugs fixed: - LRA targeting: parallel blend now downsamples before blending (was shape mismatch when oversample > 1) - LRA targeting: recalculate current_lufs after iterative re-compression (was using stale pre-loop value for final normalization) - LRA targeting: warn when audio is already over-compressed (LRA below target) per #253 spec — skip expansion to avoid artifacts - LRA targeting: handle compress_ratio=1.0 gracefully (start from 1.5) Code gaps fixed: - Wire eq_low_q through apply_low_shelf() (was defined but unused) - Add --eq-low-q CLI arg - Add 6 missing multiband CLI args (--multiband-{low,mid,high}-{ratio,threshold}) - Add 2 missing midside CLI args (--midside-{low,high}-freq) - Wire all new CLI args into cli_overrides dict Documentation gaps fixed: - SKILL.md: expand "Available preset fields" from 12 to all 47 fields - Override example: add all #253/#254 fields to header comment - CHANGELOG: document mastering overhaul in [Unreleased] Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use eq_low_q in linear-phase and mid/side low shelf paths (#267) The linear-phase low_shelf case in _design_linear_phase_eq() used hardcoded alpha = sin(w0)/2*sqrt(2) instead of sin(w0)/(2*q), causing a tonal mismatch with the IIR path when eq_low_q != 0.707. Also passes q explicitly in apply_midside_eq() low shelf calls and in the linear-phase low shelf call in master_track(). Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: log failed optional module imports in processing helpers * fix: add path traversal guards to MCP handler parameters (#274) Add _is_path_confined() helper to _shared.py and apply it to all user-supplied path parameters across MCP handlers: subfolder, source_subfolder, track_filename, reference_filename, target_filename, file_name, and songbook title. Validate color_hex/text_color against hex regex before ffmpeg filter interpolation. Closes #273 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: harden _normalize_slug against path traversal and fix CI injection (#276) _normalize_slug now rejects inputs containing path separators (/,\), null bytes, or traversal sequences (..) with a ValueError. This single-point fix closes path traversal vectors in rename_album, rename_track, resolve_path, and create_album_structure. Defense-in-depth: added _is_path_confined checks in rename and create_album_structure handlers before any shutil.move or mkdir. Also moved github.head_ref in pr-target-check.yml from inline interpolation to an env: block to prevent shell injection via malicious fork branch names. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: harden optional module import helpers and callers (#277) (#278) Narrow except clauses from Exception to (ImportError, OSError), add None guards at all call sites, and restructure _check_anthemscore to avoid double-logging by checking the helper return value before falling back. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add @zeel2104 to contributors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: establish external contributor attribution workflow Add maintainer checklist item to PR template and CLAUDE.md rule so external PR authors get added to README Contributors section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add discord link to readme Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: look-ahead limiter hits ceiling exactly on transients (#284) The release-smoothed envelope was shifted backward by lookahead_samples and applied directly, so at peak sample K the gain used was smoothed[K+lookahead] — already partially released from the attack. Output overshot the ceiling by roughly exp(-lookahead_ms/release_ms) in dB (~0.9 dB at the defaults). Replace the shift with a rolling minimum over [i, i+lookahead_samples] so the applied gain at each sample is bounded by the smallest gain required anywhere in the lookahead window. Peaks now receive their full pre-computed attenuation and the ceiling is hit within 0.01 dB. Regression test covers transient + ceiling compliance, which the existing steady-sine tests couldn't trigger. Fixes #283 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: default mastering routing to polish-then-master (#281) When the user asks to master an album, polish first via mix-engineer then hand off to mastering-engineer. Opt out with phrase ("master only", "skip polish", "already polished") or by having polished audio already staged at {audio_root}/.../polished/. Closes #280 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: make QC click thresholds genre-aware (#287) _check_clicks used a single hardcoded peak-to-RMS ratio (6.0) and fail count (3) for every genre. Electronic, IDM, breakcore, trap, metal, and glitch work FAILed click QC on intentional musical transients. Promote click_peak_ratio and click_fail_count into genre-presets.yaml defaults and add overrides for dense-transient genres: tier 1 (8.0, 15) covers electronic/metal/trap; tier 2 (10.0, 30) covers IDM/breakcore/ glitch/footwork/speedcore/gabber. User mastering-presets.yaml overrides still apply per the existing load_genre_presets merge. qc_tracks.py accepts --genre, and the qc_audio MCP tool accepts an optional genre argument that routes through to _check_clicks. Closes #285 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: post-downsample true-peak guard closes SRC ripple overshoot (#288) The look-ahead limiter (#284) hits the target ceiling exactly at the oversampled rate, but the subsequent `scipy.signal.resample_poly` downsample (and optional output SRC) has passband ripple that re-introduces 0.1–0.9 dB of inter-sample overshoot above the ceiling at the output rate. Add one reactive `limit_peaks()` pass after SRC and before dither. A single pass converges because `gain = ceiling / true_peak` is exact for the measured peak and soft-clip absorbs any residual. `final_lufs` is still measured at the processing rate (authoritative and pyln- supported); `final_peak` is now measured at the output rate after the guard so the returned value reflects what was written to disk. Closes #286 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: analyze every stem per track in analyze_mix_issues stems mode (#272) (#293) Previously sampled only the alphabetically first stem per track, so issues in specific stems (muddy bass, harsh vocals, etc.) were missed. Now iterates every stem and reports per-stem diagnostics under tracks[].stems[stem_name], with track-level issues aggregated as the union across stems. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: genre-aware declick with stem-tier cubic repair (#289) (#291) * refactor: remove_clicks returns (data, n_clicks) and accepts peak_ratio/repair Extend remove_clicks() to support two detection modes (std-diff default, windowed peak/rms matching qc_tracks._check_clicks) and two repair modes (linear default, cubic spline for stem isolation). All three in-file call sites updated to unpack the new tuple return. Backward-compatible: callers that pass no new kwargs see identical audio output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: peak_ratio detection and cubic repair paths in remove_clicks Adds TestRemoveClicksPeakRatio (4 tests) and TestRemoveClicksCubicRepair (5 tests) covering the #289 windowed peak/rms detection path and the cubic-spline stem-tier repair mode. Also corrects _generate_click's background sine scale from 0.3× to 0.1× amplitude: the original scale made the window RMS too high for the peak/rms ratio to exceed 6.0 with any window-based approach, so the new detection path could never fire. At 0.1× the window peak/rms ratio is ~10.2 (detected at 6.0, not at 50.0) while all existing std-path tests continue to pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: tighten cubic repair amplitude bound and fix peak_ratio test naming * feat: polish pipeline reads master click thresholds per genre Add _resolve_master_click_thresholds() helper that looks up click_peak_ratio / click_fail_count from mastering GENRE_PRESETS so _get_stem_settings and _get_full_mix_settings overlay the same detection semantics as the QC click detector (#289). Mix-preset overrides win when already present; helper fails soft for unknown genres. 4 new tests in TestMasterClickThresholdsThreaded. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: tighten click-threshold helper docstring, test, and exception scope Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: drums/percussion processors use peak_ratio+cubic repair and report clicks Wire click_peak_ratio into process_drums and process_percussion: when the setting is present the processors call remove_clicks with peak_ratio+cubic repair (stem-tier quality); fall back to the legacy std+linear path when only click_threshold is supplied. Both processors now accept an optional report dict and accumulate clicks_removed into it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: mix_track_stems reports clicks_removed per stem Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: mix_track_full reports clicks_removed and uses peak_ratio when genre is set Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: include clicks_removed in mix_track_full skip-path result Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: stem cubic repair differs from full-mix linear repair Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: hoist CubicSpline import and remove unreachable _repair_cubic fallback Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: changelog entry for genre-aware declick and stem-tier repair (#289) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve mypy errors in declick machinery - Narrow `peak_ratio` in `_detect_peak_ratio` via assert so mypy sees the non-None closure value (caller `_process_channel` already guards). - Dispatch `process_drums` / `process_percussion` directly from `mix_track_stems` so mypy sees the `report=` kwarg on the matching signature; other stems continue to route through `STEM_PROCESSORS`. - Annotate `STEM_PROCESSORS` as `dict[str, Callable[..., Any]]` to cover the drums/percussion signature asymmetry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: research Suno V5.5 and update reference guide (#279) (#294) V5.5 (March 26, 2026) is backward-compatible with V5 — same boxes, metatags, and sliders. Engine is more expressive; new features are Voices (cloning), Custom Models (fine-tuning), and My Taste. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: polish pipeline enhancements (#269, #270, #271) (#295) * feat: add per-track polish support to polish_audio (#271) polish_audio now accepts an optional track_filename to process a single track instead of the whole album, mirroring master_audio's granularity. Stems mode matches the stem directory by filename stem; full-mix mode matches the WAV filename directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: run full qc_track suite in polish_album verify stage (#270) Replaces the lightweight peak/RMS/finite/clipping checks with the full 8-check qc_track suite (format, mono, phase, clipping, truepeak, clicks, silence, spectral). Catches polish-introduced issues at the point they're introduced rather than surfacing later in master_album pre-QC. Passes genre through so click thresholds stay genre-aware. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add polish_and_master_album orchestrator tool (#269) Combines polish_album() and master_album(source_subfolder="polished") into a single MCP call, removing the manual handoff between the two pipelines. Stops on failure at either phase. The individual tools remain available for granular control (re-polish or re-master alone). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: accept FAIL verdict in polish_album verify assertion The verify stage now runs the full qc_track suite, which can legitimately FAIL on synthetic fixture audio (1.5s degenerate signal triggers spectral or silence checks). The test's intent is to confirm the pipeline ran all stages to completion, not that synthetic audio passes production QC. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: pre-master QC no longer gates on truepeak/clicks/tinniness (#297) (#298) The Stage 3 pre-master QC gate in polish_and_master_album was blocking all tracks by running checks that mastering is designed to fix: - truepeak: polished audio is pre-limiter; post-master verification (Stage 5) is the real ceiling gate. - clicks: polish already ran stem-tier declick (#289); residual transients here false-positive on legitimate percussive content. - spectral tinniness: mastering's cut_highmid EQ exists to tame high-mid buildup; a hard FAIL blocks work the master can fix. Pre-QC now passes an explicit checks list and spectral tinniness returns WARN instead of FAIL. The six checks mastering cannot fix (format, mono, phase, clipping, silence, spectral) remain active, so the gate still catches real issues. Post-QC is unchanged. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: codec preview and mono fold-down QC artifacts (#296) (#299) * feat: codec preview and mono fold-down QC artifacts (#296) Adds a sibling `mastering_samples/` directory for operator-listening QC artifacts so `mastered/` stays byte-identical to DistroKid uploads. - Codec preview: renders 128 kbps AAC (.aac.m4a) per mastered track for Bluetooth-path audition. - Mono fold-down QC: sums stereo to mono, measures per-band / LUFS / vocal-RMS / correlation deltas; a >6 dB band drop hard-fails the pipeline with the offending frequency surfaced (phase cancellation guard). Writes a `.MONO_FOLD.md` sidecar plus a listenable `.mono.wav` sample to `mastering_samples/`. - Wired into `master_album` as stage 5.5 (between verification and post-QC); codec preview never blocks, mono fold short-circuits on FAIL. - New standalone MCP tools `render_codec_preview` and `mono_fold_check` run the same checks independently. - Configurable via new `genre-presets.yaml` defaults (`codec_preview_*`, `mono_fold_*`); user overrides supported. - `reset_mastering` now accepts `mastering_samples` alongside `mastered` / `polished`. Tests: 30 new unit tests covering the analysis, renderer, report writer, MCP wrappers, and pipeline stage (in-phase PASS + phase-inversion short-circuit). All 3116 unit + 245 plugin tests pass. Closes #296 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: CI lint and merged-in test regression - mypy: annotate `worst` as `dict[str, Any]` so its entries can accept `None | float` without inference narrowing; coerce `fold_to_mono` mean return to `np.ndarray`; replace conditional expression in stage 5.5 config lookup with explicit if/else so mypy infers `dict[str, Any]`. - test: `test_pre_qc_skips_truepeak_and_clicks` (landed on develop in #297/#298 after I branched) still wrote empty bytes for the mastered output; swap to `_write_tiny_stereo_wav` so stage 5.5 can read it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add setup skill to SKILL_INDEX.md alphabetical table (#292) Co-authored-by: bitwize-music <bitwize@bitwizemusic.com> * docs: add @alijahak to README contributors (#301) Recognizes @alijahak's contribution via PR #292 (docs: add /setup skill to index tables). Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: realistic audio fixtures and integration tests (#300) (#302) Add make_clicks_and_pops, make_silent_gaps, and make_phase_partial generators for QC paths a sine wave can't trigger (injected DC pops, internal silent gaps, partial phase cancellation). Move audio fixtures from the unscanned tests/fixtures/audio/conftest.py into tests/conftest.py so all tests auto-discover them. Add tests/unit/mastering/test_integration_realistic_audio.py covering sibilance/tinniness, partial-phase mono fold FAIL, click detection, internal-gap silence FAIL, and LUFS spread across dynamic content. Document the generator catalog and authoring conventions in tests/fixtures/README.md. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: mastering foundation — 24/96 delivery + archival + config block (#290 phase 1a) (#304) * docs: spec pointer for album-coherence mastering pipeline Durable design reference pointing to issue #290 (canonical spec) and companion issue #303 (full-fidelity metadata embedding). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: plan 1a — mastering foundation implementation plan Adds docs/superpowers/plans/2026-04-13-mastering-foundation-plan-1a.md: 9-task TDD plan for the mastering foundation (24/96 delivery, archival, config block, prune_archival) per issue #290 phase 1a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(config): add mastering: config block with 24/96 defaults Documents streaming-grade delivery defaults for the mastering pipeline (24-bit WAV at 96 kHz, -14 LUFS, -1 dBTP) and the 96 kHz upsampling caveat. Adds optional artist.copyright_holder and artist.label keys for downstream metadata embedding. All keys are optional; existing configs continue to work unchanged. Part of #290 phase 1a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): add load_mastering_config + resolve_mastering_targets Dedicated config loader for the mastering: YAML block plus a resolver that combines config values, genre-preset values, and explicit handler arguments into the effective per-run targets. Precedence: explicit arg > genre preset > config > default. Used by master_audio / master_album to honor streaming-grade delivery defaults (24/96 WAV, -14 LUFS, -1 dBTP) without requiring per-call args. 15 unit tests covering: default behavior, override precedence, type coercion, malformed input, and source-rate comparison. Part of #290 phase 1a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): wire master_audio to mastering config for delivery targets master_audio now consumes load_mastering_config() and passes the resolved output bit depth + sample rate through to master_track via preset. Mastered output defaults to 24-bit / 96 kHz (streaming-grade), overridable via the mastering: config block or legacy genre presets. Response settings dict now reports output_bits and output_sample_rate alongside target_lufs and ceiling_db. Existing tests pass unchanged; 3 new integration tests cover the 24/96 default, the 16-bit legacy override path, and the 44.1 kHz match-source path. Part of #290 phase 1a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): wire master_album + upsampling notice + 96 kHz QC support Tasks 4 + 5 of Plan 1a: master_album now consumes load_mastering_config() + resolve_mastering_targets() identically to master_audio. Effective delivery targets (output_bits, output_sample_rate) flow through the per-track _master_track call via the preset dict. Auto-recovery path honors effective_ceiling and output_bits for consistency. Response settings block now reports: target_lufs, ceiling_db, output_bits, output_sample_rate, source_sample_rate, upsampled_from_source, archival_enabled, adm_aac_encoder, plus legacy cut_highmid/cut_highs/genre. New 'notices' array in the response: emits a one-line caveat when the effective delivery_sample_rate exceeds the measured source rate, surfacing the 96 kHz honesty note at runtime. Side effects: - qc_tracks.py format check now accepts 44.1/48/88.2/96/176.4/192 kHz (was limited to 44.1/48 — would FAIL on the new 96 kHz default). - master_album initializes album_status before the state-update branch (fixes latent UnboundLocalError when album not in state cache). - Two existing tests updated to accept EQ propagation via preset dict instead of only the legacy eq_settings tuple list. Added: - tests/unit/mastering/test_master_album_config_wiring.py — 2 integration tests covering 24/96 default output, upsampling notice emission, and matched-rate no-notice behavior. Part of #290 phase 1a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): opt-in archival output + prune_archival MCP tool Tasks 6 + 7 of Plan 1a: Archival output (opt-in via mastering.archival_enabled = true): - master_album writes 32-bit float copies of each mastered track to {audio_dir}/archival/ as a new stage between post_qc and status_update. - Default OFF; archival is intended for re-mastering without re-polishing stems (e.g. spec changes, re-release with different targets). - Cost: ~1-2 GB per 12-track album. - 3 tests covering disabled-by-default, enabled-writes-float, stage-recorded. prune_archival MCP tool: - Explicit user-action cleanup. keep=N retains the N newest files by mtime and removes the rest; keep=0 removes everything; negative keep treated as 0; missing directory is a no-op with a note field. - 5 tests covering all edge cases. - Registered alongside existing master/analyze/qc tools. Part of #290 phase 1a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): re-export prune_archival from handlers.processing + server.py Completes the wiring so server.py's re-export completeness check passes and the prune_archival tool is actually discoverable by MCP clients. Part of #290 phase 1a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(mastering): genre presets must not force 16-bit output Bug: all shipped genre presets inherited output_bits=16 and output_sample_rate=0 from tools/mastering/genre-presets.yaml defaults. resolve_mastering_targets() then saw preset_bits=16 and took the "legacy 16-bit" branch, silently overriding the mastering.delivery_bit_depth=24 config default whenever a genre argument was passed. Any call to master_album(genre=...) or master_audio(genre=...) would downgrade to 16-bit output. Only no-genre calls honored the 24/96 streaming delivery target. Fix: promote genre-presets.yaml defaults to output_bits=24 and output_sample_rate=96000 so the built-in genres align with the new streaming-grade foundation. User-supplied overrides in {overrides}/mastering-presets.yaml can still force 16-bit legacy output explicitly. Tests: - New regression unit test verifying all core genres (electronic/hip-hop/metal/folk/pop) resolve to 24/96 with default config. - New integration test exercising master_album(genre="electronic") end to end — verifies mastered output is actually 24-bit at 96 kHz. Part of #290 phase 1a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(mastering): address code review on PR #304 Applies 6 should-fix items from the code-reviewer pass: 1. tools/mastering/config.py — collapse dead-code branches in output_bits resolution. The elif preset_bits == 16 branch was tautological after commit 338b24c promoted YAML defaults to 24; both branches produced output_bits = preset_bits. Replace with a single non-zero check. 2. config/config.example.yaml — remove misleading "use -1.5 for opus-safe genres" comment on true_peak_ceiling. Per-genre opus-safe overrides are reserved for a later phase; the field is global for now. 3. CHANGELOG.md — add [Unreleased] entry for the 24/96 foundation in Added and document the default-format change in Changed so users upgrading see the format shift explicitly. 4. migrations/0.90.0.md — add migration note so the plugin upgrade flow surfaces the format change, disk-usage impact, and legacy-config snippet at session start after upgrade. 5. audio.py::master_audio — remove dead eq_settings tuple list. EQ now flows entirely through preset.cut_highmid / preset.cut_highs, which master_track rebuilds internally. 6. audio.py::master_album — add logger.debug breadcrumb when the source-sample-rate probe fails so silent fallback leaves a trail. Plus two test hygiene improvements: - test_eq_settings_propagated + test_genre_preset_sets_effective_settings: drop the legacy-path else branch. Now asserts strictly on the preset path, since that's the only path the production code takes after this PR. Stronger coverage. Part of #290 phase 1a. Review findings in PR #304 discussion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: multi-metric signature foundation (#290 phase 1b) (#305) * docs: phase 1b multi-metric signature design Specifies the analyze_track() extension that feeds Phase 2 anchor selection and coherence checking: STL-95, low-RMS (STL-95-windowed, 20-200 Hz), vocal-RMS (polished stem when present, 1-4 kHz band fallback), plus a signature_meta provenance dict. Additive return schema; stem path auto-resolve covers album-root, polished/, and mastered/ input layouts. Part of #290 phase 1b. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: phase 1b implementation plan + spec clarifications Plan breaks Phase 1b into 8 TDD-structured tasks: STL-95, low-RMS, vocal-RMS stem branch, vocal-RMS band fallback, signature_meta provenance, stem auto-resolve, E2 disk-usage note, and full regression run. Each task is self-contained with failing-test-first cycles, exact code, and individual commit messages. Spec clarifications: corrected handler-kwarg scoping (analyze_audio batches per-album, so no handler change needed; kwarg lives only on analyze_track for Phase 2 callers), tightened top-K and tie-break semantics for STL-95. Part of #290 phase 1b. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): add STL-95 signature metric to analyze_track Collects short-term LUFS values from the existing 3s/1s loop, computes the 95th percentile when at least 20 windows exist, and retains the top-5% window indices for downstream low-RMS windowing. Returns None for tracks too short or silent to produce a meaningful percentile. Part of #290 phase 1b. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): add low-RMS signature metric (20-200 Hz, windowed) Bandpasses the mono mixdown at 20-200 Hz (4th-order Butterworth SOS, zero-phase via sosfiltfilt) and takes the median RMS across the top-5% STL windows identified by STL-95. Whole-track low-RMS would false-alarm on arrangements with sparse verses and wall-of-bass choruses; windowing keeps the metric faithful to what listeners perceive as the track's low-end signature. Returns None when STL-95 is None (track too short or silent). Part of #290 phase 1b. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): add vocal-RMS stem branch with explicit stem kwarg Adds optional vocal_stem_path kwarg to analyze_track(). When the stem resolves and reads, measures whole-stem RMS in dB on a mono mixdown, resampling to the mix rate when needed. Unreadable stem files log a warning and fall through (band fallback lands in Task 4). Part of #290 phase 1b. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): add 1-4 kHz band fallback for vocal-RMS When no vocal stem resolves (or the stem is unreadable), falls back to measuring whole-track RMS on the 1-4 kHz bandpassed mono mixdown of the full mix. Covers the today-default path (polish does not yet preserve per-stem WAVs); stem measurement activates automatically once the per-stem artifact lands. Part of #290 phase 1b. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): expose signature_meta provenance dict Records stl_window_count, stl_top_5pct_count, and vocal_rms_source alongside the scalar signature fields. Downstream consumers (anchor selector, coherence check) branch on vocal_rms_source to explain their decisions ('stem' / 'band_fallback' / 'unavailable') without re-parsing paths. Part of #290 phase 1b. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(mastering): auto-resolve vocal stem path for analyze_track When the caller omits vocal_stem_path, analyze_track walks two layouts looking for <input_stem>/vocals.wav under a sibling polished/ directory — first the input's directory, then one level up. Covers album-root, polished/, and mastered/ input callsites without touching the wider filesystem. Part of #290 phase 1b. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(config): add disk-usage note to delivery_sample_rate comment Expands the 24/96 default documentation with a concrete disk-usage estimate (~33 MB/min at 24/96 vs ~10 MB/min at 44.1; ~1.5 GB per 12-track album before archival). Surfaces the tradeoff users should weigh when enabling archival output. Carried forward from #304 review item E2. Part of #290 phase 1b. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(mastering): extend test_returns_expected_keys for phase 1b fields The schema assertion used strict set equality on analyze_track's return keys. Adds stl_95, low_rms, vocal_rms, and signature_meta to the expected set so the phase 1b additions pass schema validation alongside the existing fields. Part of #290 phase 1b. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: anchor selector for album mastering (#290 phase 2) (#306) * docs: phase 2 implementation plan — anchor selector (#290) Covers composite scoring + README frontmatter override + deterministic tie-breaker, plus the D1 carried-forward refactor to consolidate build_effective_preset between master_audio and master_album. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: extract build_effective_preset helper (#290 phase 2) Consolidates the ~30 lines of preset construction duplicated between master_audio and master_album handlers (D1 review item from #304). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: clarify local-import rationale + cover source_sample_rate plumbing (#290 phase 2) Addresses review nits from commit 153616b: - The local `load_genre_presets` import isn't about a circular dependency (there isn't one); it's about keeping config.py's startup import path free of master_tracks.py's YAML-loading side effect. - Adds three assertions to test_pop_genre_happy_path verifying source_sample_rate and upsampled_from_source thread through to both `targets` and `settings` — this plumbing is load-bearing for the master_album upsampling-notice emitted in Task 2. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: route master_audio+master_album through build_effective_preset (#290 phase 2) Eliminates the last copy of the preset construction block. Both MCP handlers now call the shared helper from tools.mastering.config, completing the D1 refactor from the #304 review. The consolidation removes the top-level load_mastering_config / resolve_mastering_targets / load_genre_presets imports from the handler — those names now live only inside build_effective_preset. Three wiring tests that used to patch audio_mod.load_mastering_config are re-pointed at tools.mastering.config.load_mastering_config, where the helper resolves the symbol, so behavior coverage is preserved. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: drop dead assignments + tidy bundle unpacking (#290 phase 2) Addresses review nits M1–M3 from commit e22fde0: - Drop `preset_dict = bundle["preset_dict"]` at both sites (never read after the refactor — the helper threads it into resolve_mastering_targets internally now). - Drop `genre_applied = bundle["genre_applied"]` in master_album (dead; master_album reads the value via settings["genre"]). - Pull `settings` and `effective_preset` once at the top of each handler so downstream reads use simple indexing instead of bundle[..][..]. - Drop the redundant loop-local `effective_preset = bundle["effective_preset"]` in both track loops — same binding is already in scope. Behavior unchanged; test patch targets unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: surface anchor_track frontmatter field (#290 phase 2) parse_album_readme now extracts the optional anchor_track override so it flows through the state cache to the mastering pipeline without mastering code ever reading README files directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add genre_ideal_lra + spectral_reference_energy defaults (#290 phase 2) New preset fields consumed by the album-mastering anchor selector. Pop-balanced defaults; per-genre overrides can land later as reference albums are measured with measure_album_signature. load_genre_presets() does not inherit nested-dict defaults into per-genre presets; the anchor selector (Task 8) falls back to hard-coded defaults at runtime when the per-genre preset omits spectral_reference_energy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: anchor selector — spectral match score (#290 phase 2) First scoring component for the album-mastering anchor selector: Euclidean distance between track band_energy and genre reference curve, mapped to (0, 1]. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: anchor selector — mix_quality, representativeness, ceiling_penalty (#290 phase 2) Implements the three per-track component scores from the issue #290 composite formula. Next task wires them together + override path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: select_anchor composite scoring + override + tie-breaker (#290 phase 2) Wires mix_quality, representativeness, and ceiling_penalty into the composite formula from issue #290. Supports README frontmatter override and deterministic tie-breaker on ties within 0.05. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: harden anchor_selector inputs before handler integration (#290 phase 2) Addresses review findings from the Tasks 5–7 combined pass: - I1: reject non-int override_index (float, bool, str) at module boundary with a clear override_reason; previously floats smuggled through as selected_index, strings raised TypeError on `> 0`. - I2: _is_eligible now validates band_energy has all 7 bands (not just that it's truthy); select_anchor raises ValueError when the genre preset's spectral_reference_energy is missing bands — that's a config bug, not a per-track eligibility issue. - I4: _album_medians uses t.get(key) everywhere (was t[key] on the value-extraction line), eliminating a latent KeyError when a track dict lacks a signature key entirely. Adds 7 tests covering malformed override, malformed band_energy, malformed preset, empty track list, and missing-key-entirely edge case. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: integrate anchor selector into master_album pipeline (#290 phase 2) After Stage 2 (Analysis), master_album now runs the anchor selector and records its choice in stages["anchor_selection"]. Honors the optional anchor_track README frontmatter override (surfaced via the state cache by parse_album_readme). The mastering loop itself is unchanged — this phase ships the selector as metadata. Coherence correction (which will use the anchor) lands in the next phase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: propagate anchor_track through indexer + drop dead fallback (#290 phase 2) Addresses review findings C1 and I1 from the Task 8 integration: - C1: tools/state/parsers.py surfaces anchor_track in its output dict, but tools/state/indexer.py never copied the field into the state cache's albums[slug] entry. The README override chain was broken end-to-end. Adds the propagation at both indexer write sites (full scan + incremental re-parse) plus a state-schema test. - I1: The handler's spectral-defaults fallback used load_genre_presets().get("defaults", {}), which always returns {} because load_genre_presets returns {genre: preset}, not a dict with a top-level "defaults" key. The fallback did nothing — select_anchor's internal pop-balanced defaults are what kept things working. Drop the dead code and document where the real defaults live. Adds an end-to-end override integration test covering the full chain: frontmatter → parser → indexer → state cache → handler → select_anchor. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: extend _mock_analyze with Phase 1b signature fields (#290 phase 2) After Task 8 landed the anchor_selection stage, the test_server_qc mock for analyze_track no longer produced enough signal for the selector to mark tracks eligible — every mocked track surfaced as no_eligible_tracks / warn. test_full_pipeline_success asserts every stage is "pass", so it regressed. Add stl_95, short_term_range, low_rms, vocal_rms, and signature_meta to _mock_analyze so the selector has something to score. All existing tests in the file share this helper; none of them assert on the anchor_selection stage specifically, so extending the mock is the minimal fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: measure_album_signature MCP tool (#290 phase 3a) (#307) * feat: add build_signature pure-Python aggregator (#290 phase 3a) Introduces tools/mastering/album_signature.py with build_signature(), which aggregates a list of analyze_track results into per-track + album-level signature summaries (median/p95/min/max/range). Pure Python — no I/O, no MCP coupling. Happy-path unit test covers a three-track album. compute_anchor_deltas() is also in the module but not yet covered; subsequent commits add tests for None handling, aggregate edge cases, and delta computation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: cover build_signature edge cases (#290 phase 3a) Adds coverage for: - None stl_95 values excluded from medians - all-None metric returns None across aggregates - non-finite lufs (inf, nan) excluded - empty album → empty tracks + None aggregates - single-track album → zero range - 5-value p95 uses numpy's linear interpolation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: cover compute_anchor_deltas (#290 phase 3a) Tests the pure-Python delta computation: - deltas follow track - anchor convention - None in track or anchor yields None delta - None anchor propagates to every row's metric - empty list + out-of-range index raise ValueError - every AGGREGATE_KEYS metric has a delta_ column Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add measure_album_signature handler (#290 phase 3a) New MCP handler in handlers/processing/audio.py. Resolves the album's subfolder (default 'mastered/'), runs analyze_track on every WAV, calls build_signature to produce per-track + album-level aggregates, and optionally runs the anchor selector + compute_anchor_deltas when genre or an explicit anchor_track is supplied. Read-only — no files are written. Used for tuning genre tolerances from reference albums and for feeding phase 3b coherence tools. Guards against _shared.cache being None (test harness / early-init callers) — state-cache anchor_track lookup is only attempted when the cache is actually wired up. Integration tests cover: - no-anchor happy path (3 sine-wave tracks) - missing subfolder → error - subfolder escape blocked - empty subfolder → error Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: cover anchor paths for measure_album_signature (#290 phase 3a) - genre argument → anchor block populated via select_anchor - explicit anchor_track=2 → method=override, deltas[1].is_anchor=True - unknown genre → error JSON with available_genres catalogue - out-of-range anchor_track → override_reason set, falls through to composite scoring Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: register measure_album_signature with MCP server (#290 phase 3a) Adds mcp.tool()(measure_album_signature) to the audio-handler register() so the tool is exposed via the bitwize-music MCP server alongside analyze_audio / master_album / prune_archival. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: re-export measure_album_signature + CHANGELOG entry (#290 phase 3a) - Add measure_album_signature to handlers.processing.__init__ and server.py re-exports so the integrity test TestReExportCompleteness::test_all_registered_tools_are_reexported passes. - Add an [Unreleased] CHANGELOG entry describing the new tool, matching the style of the phase 1a entry (#304). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add phase 3a implementation plan (#290) Plan document guiding the implementation of the measure_album_signature MCP tool — phase 3a of issue #290. Captures the file structure, design decisions (signature shape, aggregation rules, delta convention, anchor precedence), and the TDD task breakdown used to land this PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: patch _shared.cache to None for no-anchor test (#290 phase 3a) CI exposed a flake: the no-anchor test asserted "anchor" not in result, but the handler was reading anchor_track from the persistent state cache (populated by another test in the suite with anchor_track: 2 on a different test-album). Locally the cache started empty so the test passed; in CI it didn't. Patch _shared.cache to None for that specific test so the handler treats the cache as unavailable — matching the test's intent of "no anchor + no override". The other tests already pass anchor_track explicitly so they're insulated from this leak. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: album coherence check + correct (#290 phase 3b) (#309) * feat: add coherence tolerance fields to genre-presets defaults (#290 phase 3b) Four new fields in the defaults block: - coherence_stl_95_lu: 0.5 (±LU around anchor's STL-95) - coherence_lra_floor_lu: 1.0 (minimum short_term_range allowed) - coherence_low_rms_db: 2.0 (±dB around anchor's low_rms) - coherence_vocal_rms_db: 2.0 (±dB around anchor's vocal_rms) Also extends _PRESET_DEFAULTS in tools/mastering/master_tracks.py so the fields flow through load_genre_presets() into per-genre merged presets — load_genre_presets filters YAML keys through _PRESET_DEFAULTS, so YAML-only additions are silently dropped otherwise. (Phase 2's genre_ideal_lra_lu / spectral_reference_energy work around this by falling back to hardcoded defaults in anchor_selector — phase 3b prefers the cleaner flow-through path so load_tolerances sees real preset values.) Consumed by the upcoming album_coherence_check / album_coherence_correct MCP tools to classify outlier tracks relative to the anchor. Default pop-balanced values ship in defaults; per-genre overrides can be added incrementally as reference-album tuning reveals needs. Header-comment block extended with per-field docstrings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add tools/mastering/coherence.py with load_tolerances (#290 phase 3b) Introduces a new pure-Python module that will own tolerance-band merging, outlier classification, and correction planning for the upcoming album_coherence_check / album_coherence_correct handlers. This commit ships the DEFAULTS constant + load_tolerances(): - DEFAULTS maps the four coherence_* preset fields to their documented defaults plus lufs_tolerance_lu (hardcoded 0.5 LU matching master_album Stage 5). - load_tolerances(preset) merges a partial preset on top of defaults key-by-key; lufs_tolerance_lu is non-overridable. classify_outliers + build_correction_plan are stubbed with NotImplementedError — next commits fill them in with TDD. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: classify_outliers tolerance classifier (#290 phase 3b) classify_outliers consumes the phase-3a compute_anchor_deltas output plus per-genre tolerance bands and produces a per-track violation list. Five metrics are checked per track: - lufs: ±0.5 LU delta (correctable in MVP) - stl_95: ±0.5 LU delta (reported, not correctable) - lra_floor: absolute floor of 1.0 LU on short_term_range - low_rms: ±2.0 dB delta (reported, not correctable) - vocal_rms: ±2.0 dB delta (reported, not correctable) Missing metrics (e.g., None vocal_rms) produce severity="missing" which does NOT flag the track as an outlier — can't classify what the analyzer couldn't compute. 10 new unit tests covering every metric, severity state, and the anchor / no-anchor paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: build_correction_plan for LUFS-only correction (#290 phase 3b) build_correction_plan consumes classify_outliers output and produces a per-track plan: - Anchor track → skipped with reason="is_anchor" - Clean tracks → skipped with reason="no_violations" - LUFS-outlier tracks → correctable, corrected_target_lufs = anchor's measured LUFS (ground truth, not the preset target — guarantees convergence because we chase real output) - Non-LUFS-only outliers → non-correctable with a clear reason string explaining the MVP scope limit The anchor's measured LUFS becomes the correction target so we match the actual mastered output rather than an idealized preset target that the first mastering pass may have missed by a few tenths of a dB. 4 new unit tests covering every branch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add album_coherence_check handler (#290 phase 3b) Read-only MCP tool that classifies each mastered track against the selected anchor using five per-genre tolerance bands: - lufs (±0.5 LU, hardcoded, correctable) - stl_95 (±coherence_stl_95_lu, reported) - lra_floor (absolute floor, reported) - low_rms (±coherence_low_rms_db, reported) - vocal_rms (±coherence_vocal_rms_db, reported) Requires either genre= (preferred) or anchor_track= (falls back to default tolerances with a warning in the response). Response includes a summary block with outlier counts broken down by metric so callers can triage at a glance. Three integration tests: - LUFS outlier detected (amplitude-varied sine tracks) - Errors without genre + anchor - Falls back to default tolerances when anchor_track is given without genre Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add album_coherence_correct handler (#290 phase 3b) Re-masters LUFS-outlier tracks from polished/ into mastered/ using the anchor's measured LUFS as the per-track target. Staging pattern mirrors master_album: - .coherence_staging/ receives master_track output - atomic replace into mastered/ on full success - staging cleaned up on any failure (no partial writes) Pre-flight validates: - polished/ and mastered/ both exist - every track in mastered/ has a matching file in polished/ - anchor is selectable (otherwise no correction reference) - genre is set (tolerances + preset base require it) Scope limits (MVP — documented in docstring): - Corrects LUFS outliers only - Non-LUFS outliers reported but not auto-corrected - Single-pass (no iteration budget) dry_run=True returns the plan without writing anything. 4 new integration tests covering dry-run, missing-polished, polished/mastered file-set mismatch, and end-to-end correction convergence (amplitude-varied sine outlier pulled within 1 dB of anchor). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: register album_coherence_{check,correct} with MCP server (#290 phase 3b) Adds the two new handlers to: - handlers.processing.audio.register() (MCP tool registration) - handlers.processing.__init__ (package re-export) - server.py (top-level re-export) Required for the integrity test TestReExportCompleteness to pass and for the tools to be callable from the Claude Code client. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: CHANGELOG entry + plan doc for album coherence (#290 phase 3b) - CHANGELOG entry under [Unreleased] documenting both new MCP tools, the four new coherence_* preset fields, and the MVP scope limit (LUFS-only correction). - Plan document captures the architecture, design decisions (tolerance semantics, correction policy, why re-master from polished/ not mastered/, scope limits), and the 8-task TDD breakdown used to land this PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace lambda with functools.partial for mypy (#290 phase 3b) mypy 'Cannot infer type of lambda' on the executor invocation in album_coherence_correct. functools.partial preserves master_track's typing while binding the per-iteration src/staged/modified_preset values — same default-arg-binding behavior as the lambda but mypy can resolve the call type cleanly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: album signature persistence + frozen mode (#290 phase 4) (#310…
1 parent 7306bca commit b4b70db

175 files changed

Lines changed: 31052 additions & 1441 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{
88
"name": "bitwize-music",
99
"description": "AI music generation workflow for Suno - album concepts, lyrics, prompts, mastering, release",
10-
"version": "0.89.0",
10+
"version": "0.91.0",
1111
"source": "./"
1212
}
1313
]

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "bitwize-music",
33
"description": "AI music generation workflow for Suno - album concepts, lyrics, prompts, mastering, release",
4-
"version": "0.89.0",
4+
"version": "0.91.0",
55
"author": {
66
"name": "bitwize-music"
77
},

.github/SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ If you're using this plugin:
8989

9090
This project uses AI assistance (Claude Code) for development. Commits include:
9191
```
92-
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
92+
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9393
```
9494

9595
This is for transparency. All AI-generated code is reviewed by humans before merging.

.github/pull_request_template.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
- [ ] I have read the [CONTRIBUTING.md](../CONTRIBUTING.md) guidelines
1818
- [ ] My commit message follows [Conventional Commits](https://conventionalcommits.org/)
19-
- [ ] I included the co-author line: `Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>`
19+
- [ ] I included the co-author line: `Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>`
2020
- [ ] I have run `/bitwize-music:test` and all relevant tests pass
2121
- [ ] I have updated CHANGELOG.md under "Unreleased" section
2222

@@ -32,6 +32,10 @@
3232
- [ ] I have updated README.md if user-facing changes
3333
- [ ] I have added/updated skill documentation if applicable
3434

35+
### Maintainer Checklist (for reviewer)
36+
37+
- [ ] If PR author is an external contributor, add them to the Contributors section of README.md
38+
3539
### Testing
3640

3741
**Required local testing (run before submitting):**

.github/workflows/pr-target-check.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ jobs:
2020
runs-on: ubuntu-latest
2121
steps:
2222
- name: Verify PR source branch
23+
env:
24+
HEAD_REF: ${{ github.head_ref }}
2325
run: |
24-
HEAD_REF="${{ github.head_ref }}"
2526
echo "PR source branch: $HEAD_REF"
2627
echo "PR target branch: main"
2728

.github/workflows/test.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ jobs:
195195
python-version: '3.11'
196196
cache: 'pip'
197197

198+
- name: Install system dependencies
199+
run: |
200+
sudo apt-get update -qq
201+
sudo apt-get install -y --no-install-recommends ffmpeg
202+
198203
- name: Install test dependencies
199204
run: |
200205
python -m pip install --upgrade pip
@@ -206,7 +211,7 @@ jobs:
206211
python -m pytest tests/ -v --tb=short --cov=tools --cov=servers --cov-report=term-missing --cov-report=html:coverage-html --cov-fail-under=75
207212
208213
- name: Upload coverage report
209-
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
214+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
210215
with:
211216
name: coverage-report
212217
path: coverage-html/
@@ -238,7 +243,7 @@ jobs:
238243
239244
- name: Run bandit security linter
240245
run: |
241-
bandit -r tools/ servers/ -ll -q -s B108
246+
bandit -r tools/ servers/ -ll -q -s B108,B608
242247
243248
- name: Run mypy type checker
244249
run: |

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,7 @@ TESTING.md
3838
.coverage
3939
TODO.md
4040
.claude/worktrees/
41+
.worktrees/
42+
43+
# Superpowers planning/spec docs - kept local, not committed
44+
docs/superpowers/

CHANGELOG.md

Lines changed: 208 additions & 1 deletion
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,14 @@ At the beginning of a fresh session:
7474
- If MCP missing → **Stop immediately** and suggest: `/bitwize-music:setup mcp`
7575
- If config missing → suggest: `/bitwize-music:configure`
7676
- Don't proceed with session start until setup is complete
77-
1.5. **Check venv health** — Use `check_venv_health` MCP tool:
78-
- `status: "ok"` → continue silently
79-
- `status: "stale"` → warn with mismatches and fix command, continue session
80-
- `status: "no_venv"`**stop** and suggest `/bitwize-music:setup`
81-
- `status: "error"` → warn and continue
77+
1.5. **Health check** — Use `health_check` MCP tool (checks venv + skill registration):
78+
- Venv `status: "ok"` → continue silently
79+
- Venv `status: "stale"` → warn with mismatches and fix command, continue session
80+
- Venv `status: "no_venv"`**stop** and suggest `/bitwize-music:setup`
81+
- Venv `status: "error"` → warn and continue
82+
- Skills `status: "ok"` → continue silently
83+
- Skills `status: "stale"` → warn with missing/ghost skill names and fix message, continue session
84+
- Skills `status: "no_cache"` → warn (plugin may not be installed via marketplace), continue
8285
2. **Load config** — Read `~/.bitwize-music/config.yaml`. If missing, tell user to run `/bitwize-music:configure`.
8386
3. **Load overrides** — Check `paths.overrides` (default: `{content_root}/overrides`):
8487
- `{overrides}/CLAUDE.md` → incorporate instructions
@@ -97,7 +100,9 @@ At the beginning of a fresh session:
97100
- If versions match → no action
98101
5. _(Removed — run `/bitwize-music:skill-model-updater check` manually when new models are released)_
99102
6. **Report from MCP state**:
100-
- Venv health warnings (from step 1.5 — omit if ok, warn if stale: "⚠️ Venv has N outdated package(s): pkg1 (1.0.0 → 1.1.0), ... Run: `~/.bitwize-music/venv/bin/pip install -r .../requirements.txt`")
103+
- Health warnings (from step 1.5 — omit if ok):
104+
- Venv stale: "⚠️ Venv has N outdated package(s): pkg1 (1.0.0 → 1.1.0), ... Run: `~/.bitwize-music/venv/bin/pip install -r .../requirements.txt`"
105+
- Skills stale: "⚠️ N skill(s) missing from Claude Code, N ghost — run: `claude plugin update bitwize-music`"
101106
- Album ideas (from `get_ideas`)
102107
- In-progress albums (status: "In Progress", "Research Complete", "Complete")
103108
- Pending source verifications (from `get_pending_verifications(summary_only=True)`)
@@ -139,13 +144,14 @@ Concept → Research → Write (+Suno Prompt) → [Refine] → QC/Verify → Gen
139144

140145
- **Album mentioned**`/bitwize-music:resume`
141146
- **"Make a new album"** → IMMEDIATELY use `/bitwize-music:new-album` BEFORE any discussion
147+
- **"Turn idea into album" / "promote [idea]"**`/bitwize-music:promote-idea "<idea title>"` (one-shot: creates album from a Pending idea, injects concept, updates status)
142148
- **Writing lyrics** → apply `/bitwize-music:lyric-writer` expertise (auto-invokes suno-engineer)
143149
- **Refining/polishing lyrics**`/bitwize-music:lyric-refiner` (post-writing multi-pass refinement)
144150
- **Planning album** → apply `/bitwize-music:album-conceptualizer` (7 planning phases required)
145151
- **Suno prompts** → apply `/bitwize-music:suno-engineer` expertise (usually auto-invoked by lyric-writer; use directly only for re-prompting)
146152
- **Research needed** → apply `/bitwize-music:researcher` standards
147153
- **Polishing audio / fixing Suno artifacts** → apply `/bitwize-music:mix-engineer` expertise
148-
- **Mastering audio** → apply `/bitwize-music:mastering-engineer` standards
154+
- **Mastering audio**polish first via `/bitwize-music:mix-engineer`, then apply `/bitwize-music:mastering-engineer` standards. Skip polish only if: (a) user says "master only", "skip polish", or "already polished"; or (b) polished audio already exists at `{audio_root}/artists/[artist]/albums/[genre]/[album]/polished/`. Applies equally to single-track and whole-album mastering.
149155
- **Album art** → apply `/bitwize-music:album-art-director`
150156
- **Writing promo copy** → apply `/bitwize-music:promo-writer` expertise
151157
- **Releasing** → apply `/bitwize-music:release-director`
@@ -269,14 +275,18 @@ Currently supports **Suno** (default). Service-specific template sections marked
269275
| `feat!:` | MAJOR |
270276
| `docs:`, `chore:` | None |
271277

272-
**Co-author line**: `Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>`
278+
**Co-author line**: `Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>`
273279

274280
**Version files (must stay in sync)**: `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`
275281

276282
**Release process**: Update CHANGELOG.md `[Unreleased]``[0.x.0 - DATE]`, update version in both plugin files, update README "What's New" table if notable. Commit: `chore: release 0.x.0`
277283

278284
**Development workflow**: Feature branch off `develop` → Conventional Commits → `/bitwize-music:test all` → PR into `develop` → Release: merge `develop``main`. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
279285

286+
**Pre-push gate**: **ALWAYS run `make check` before `git push`.** This runs the same `ruff` + `bandit` + `mypy` + `pytest` suite that CI runs in the Lint and Tests jobs (see `Makefile` + `.github/workflows/test.yml`). `make lint` alone is fine for a quick type-check. Running targeted `pytest tests/unit/…` and file-scoped `ruff check` is NOT equivalent — `make` spins up `.venv` from `requirements.txt + requirements-test.txt` so mypy sees real (not stubbed) third-party types, which is what CI sees. If `make check` fails, fix the root cause; do not push and hope CI catches a different picture.
287+
288+
**External contributor PRs**: When the user mentions merging, reviewing, or having merged a PR from a non-maintainer (anyone other than @bitwize-music), check the Contributors section of README.md. If the PR author is not listed, proactively offer to add them using the same `<a href>` avatar block format as existing entries. Do this without being asked.
289+
280290
---
281291

282292
## Mid-Session Rules

CODE_OF_CONDUCT.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Code of Conduct
2+
3+
![Be Excellent To Each Other](docs/images/be-excellent-to-each-other.jpg)

0 commit comments

Comments
 (0)