feat: SDD workflow — test sync, song generation + validation, ReaScript hybrid pipeline
- compose-test-sync: fix 3 failing tests (NOTE_TO_MIDI, DrumLoopAnalyzer mock, section name) - generate-song: CLI wrapper + RPP validator (6 structural checks) + 4 e2e tests - reascript-hybrid: ReaScriptGenerator + command protocol + CLI + 16 unit tests - 110/110 tests passing - Full SDD cycle (propose→spec→design→tasks→apply→verify) for all 3 changes
This commit is contained in:
65
.sdd/changes/archive/2026-05-03-compose-test-sync/ARCHIVE.md
Normal file
65
.sdd/changes/archive/2026-05-03-compose-test-sync/ARCHIVE.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Archive: compose-test-sync
|
||||
|
||||
**Archived**: 2026-05-03
|
||||
**Status**: Complete — all 90 tests pass
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Test-only sync change. Fixed 3 failing tests to match the new compose.py API after the great rewrite. No compose.py files were modified.
|
||||
|
||||
---
|
||||
|
||||
## Specs Synced
|
||||
|
||||
| Domain | Action | Details |
|
||||
|--------|--------|---------|
|
||||
| None | N/A | Test-only change — no spec-level capabilities modified |
|
||||
|
||||
No main specs updated (test-only change, no capability modification per proposal).
|
||||
|
||||
---
|
||||
|
||||
## Files Changed
|
||||
|
||||
| File | Change | Description |
|
||||
|------|--------|-------------|
|
||||
| `tests/test_section_builder.py` | Modified | Replaced `root_to_midi` with `NOTE_TO_MIDI` dict lookup |
|
||||
| `tests/test_render_cli.py` | Modified | Removed obsolete `DrumLoopAnalyzer` mock |
|
||||
| `tests/test_compose_integration.py` | Modified | Fixed section name from `"verse"` to `"chorus"`, removed `analysis` arg |
|
||||
|
||||
---
|
||||
|
||||
## Tasks Completed
|
||||
|
||||
All 4 phases completed per `tasks.md`:
|
||||
|
||||
- ✅ 1.1–1.3: Replaced `root_to_midi` with `NOTE_TO_MIDI["A"]` + octave offset formula
|
||||
- ✅ 2.1–2.5: Removed `DrumLoopAnalyzer` patch and related dead code
|
||||
- ✅ 3.1–3.2: Changed section name to `"chorus"`, removed unused `analysis` line
|
||||
- ✅ 4.1–4.2: Confirmed 90/90 tests pass, no scripts/ files modified
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
- **Test result**: 90/90 tests passing (`pytest tests/ -q`)
|
||||
- **Scripts modified**: None confirmed via `git diff --name-only scripts/`
|
||||
- **Change scope**: Test-only, no compose.py changes
|
||||
|
||||
---
|
||||
|
||||
## Archive Contents
|
||||
|
||||
- `proposal.md` ✅
|
||||
- `design.md` ✅
|
||||
- `tasks.md` ✅
|
||||
|
||||
No `spec.md` existed for this change (test-only, no spec-level capabilities).
|
||||
|
||||
---
|
||||
|
||||
## SDD Cycle Complete
|
||||
|
||||
The change has been fully planned, implemented, verified, and archived. Ready for the next change.
|
||||
91
.sdd/changes/archive/2026-05-03-compose-test-sync/design.md
Normal file
91
.sdd/changes/archive/2026-05-03-compose-test-sync/design.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Design: compose-test-sync
|
||||
|
||||
## Technical Approach
|
||||
|
||||
Fix 3 failing tests by replacing calls to removed/changed compose.py APIs with their current equivalents. Each fix is surgical — only the broken import or call signature changes, no test logic rewrites.
|
||||
|
||||
## File Changes
|
||||
|
||||
| File | Action | Description |
|
||||
|------|--------|-------------|
|
||||
| `tests/test_section_builder.py` | Modify | Replace `root_to_midi` with `NOTE_TO_MIDI` dict lookup |
|
||||
| `tests/test_render_cli.py` | Modify | Remove obsolete `DrumLoopAnalyzer` patch, retain `SampleSelector` if still needed |
|
||||
| `tests/test_compose_integration.py` | Modify | Remove `analysis` arg, use section name `"chorus"` instead of `"verse"` |
|
||||
|
||||
## Fix Details
|
||||
|
||||
### Fix 1: `test_root_to_midi` (test_section_builder.py:121)
|
||||
|
||||
**Problem**: Test imports `root_to_midi` which no longer exists in compose.py.
|
||||
**Fix**: Replace the import and the two assertions.
|
||||
|
||||
```python
|
||||
# Before
|
||||
from scripts.compose import root_to_midi
|
||||
assert root_to_midi("A", 4) == 69
|
||||
assert root_to_midi("C", 4) == 60
|
||||
|
||||
# After
|
||||
from scripts.compose import NOTE_TO_MIDI
|
||||
assert NOTE_TO_MIDI["A"] + (4 + 1) * 12 == 69
|
||||
assert NOTE_TO_MIDI["C"] + (4 + 1) * 12 == 60
|
||||
```
|
||||
|
||||
`NOTE_TO_MIDI` is at compose.py:37 and maps `{"A": 69, "C": 60, ...}`. The formula `NOTE_TO_MIDI[root] + (octave + 1) * 12` reconstructs the old `root_to_midi` behavior.
|
||||
|
||||
---
|
||||
|
||||
### Fix 2: `test_melody_uses_pentatonic` (test_compose_integration.py:200)
|
||||
|
||||
**Problem 1**: `build_melody_track` no longer accepts `analysis` as 5th arg.
|
||||
**Problem 2**: `build_lead_track` (called inside `build_melody_track`) skips any section not named `"chorus"`, `"chorus2"`, or `"final"`. Test passes `"verse"` → zero clips generated.
|
||||
|
||||
**Fix**: Remove `analysis` arg from call, change section name to `"chorus"`.
|
||||
|
||||
```python
|
||||
# Before
|
||||
sections = [SectionDef(name="verse", bars=4, energy=1.0)]
|
||||
offsets = [0.0]
|
||||
track = build_melody_track(sections, offsets, "A", True, seed=42)
|
||||
|
||||
# After
|
||||
sections = [SectionDef(name="chorus", bars=4, energy=1.0)]
|
||||
offsets = [0.0]
|
||||
track = build_melody_track(sections, offsets, "A", True, seed=42)
|
||||
```
|
||||
|
||||
Note: `analysis = _fake_analysis()` at line 204 becomes unused — leave it in place (no harm) or remove if preferred.
|
||||
|
||||
---
|
||||
|
||||
### Fix 3: `test_main_without_render_produces_rpp` (test_render_cli.py:44)
|
||||
|
||||
**Problem**: Test patches `scripts.compose.DrumLoopAnalyzer` which no longer exists in compose.py.
|
||||
**Fix**: Remove `patch("scripts.compose.DrumLoopAnalyzer")` from both the mock setup and the with statement.
|
||||
|
||||
```python
|
||||
# Before
|
||||
with patch("scripts.compose.SampleSelector") as mock_cls, \
|
||||
patch("scripts.compose.DrumLoopAnalyzer") as mock_a_cls:
|
||||
|
||||
# After
|
||||
with patch("scripts.compose.SampleSelector") as mock_cls:
|
||||
```
|
||||
|
||||
Lines 48-55 (fake `DrumLoopAnalysis` object) and lines 68-70 (mock analyzer setup) should be removed since `fake_analysis` and `mock_a` are no longer used.
|
||||
|
||||
Verify `SampleSelector` patch is still functional — if `SampleSelector` was also removed, remove that patch too and simplify the mock block.
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
1. Run `pytest tests/ -q` — expect 90/90 pass
|
||||
2. Run each fixed test individually to confirm:
|
||||
- `pytest tests/test_section_builder.py::TestHelpers::test_root_to_midi -v`
|
||||
- `pytest tests/test_compose_integration.py::TestComposeIntegration::test_melody_uses_pentatonic -v`
|
||||
- `pytest tests/test_render_cli.py::TestComposeNoRender::test_main_without_render_produces_rpp -v`
|
||||
3. Confirm no compose.py files modified: `git diff --name-only scripts/`
|
||||
|
||||
## Open Questions
|
||||
|
||||
- **SampleSelector still used?**: Verify `SampleSelector` is still imported/patched in compose.py before leaving that patch in place. If also removed, strip it.
|
||||
- **None**: All three fixes are clear from the proposal and code inspection.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Proposal: compose-test-sync
|
||||
|
||||
## Intent
|
||||
|
||||
Sync 3 failing tests to the new compose.py API. The compose.py rewrite removed `root_to_midi` and `DrumLoopAnalyzer`, and changed `build_melody_track` signature. Tests are calling old APIs that no longer exist.
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
- Fix `test_melody_uses_pentatonic` — remove stale `analysis` arg and ensure single-section test works
|
||||
- Fix `test_main_without_render_produces_rpp` — remove obsolete `DrumLoopAnalyzer` mock
|
||||
- Fix `test_root_to_midi` — replace removed `root_to_midi` with `NOTE_TO_MIDI` dict lookup
|
||||
- Run full suite to confirm all 90 pass
|
||||
|
||||
### Out of Scope
|
||||
- No new features
|
||||
- No compose.py changes — only test fixes
|
||||
|
||||
## Capabilities
|
||||
|
||||
> No spec-level capabilities change. Tests are sync fixes only.
|
||||
|
||||
- None — test-only change, no capability modification
|
||||
|
||||
## Approach
|
||||
|
||||
1. **`test_root_to_midi`**: Replace `from scripts.compose import root_to_midi` with direct `NOTE_TO_MIDI` dict access. `root_to_midi` was removed; the constant `NOTE_TO_MIDI` ({"A": 69, "C": 60, ...}) is the correct replacement.
|
||||
|
||||
2. **`test_main_without_render_produces_rpp`**: Remove both `patch("scripts.compose.SampleSelector")` and `patch("scripts.compose.DrumLoopAnalyzer")`. The rewrite moved drumloop logic to `build_drumloop_track` which uses `Path(ABLETON_DRUMLOOP_DIR)` directly — no class-based analyzer. Keep only `SampleSelector` patch if still needed for other path.
|
||||
|
||||
3. **`test_melody_uses_pentatonic`**: The call signature is now `build_melody_track(sections, offsets, "A", True, seed=42)` — `analysis` arg removed. However `build_lead_track` (called by `build_melody_track`) only generates clips for sections named `chorus`, `chorus2`, or `final`. The test uses `"verse"` which produces zero clips. Fix: change section name to `"chorus"` OR add a second section named `"final"`.
|
||||
|
||||
## Affected Areas
|
||||
|
||||
| Area | Impact | Description |
|
||||
|------|--------|-------------|
|
||||
| `tests/test_section_builder.py` | Modified | Replace `root_to_midi` with `NOTE_TO_MIDI` |
|
||||
| `tests/test_render_cli.py` | Modified | Remove `DrumLoopAnalyzer` mock, adjust patches |
|
||||
| `tests/test_compose_integration.py` | Modified | Fix call signature, use chorus section |
|
||||
|
||||
## Risks
|
||||
|
||||
| Risk | Likelihood | Mitigation |
|
||||
|------|------------|------------|
|
||||
| Test fix breaks another test | Low | Run full suite after each change |
|
||||
| compose.py behavior changed unexpectedly | Low | 87/90 tests already pass |
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
Revert test files to prior commit. `git checkout HEAD~1 -- tests/` restores all three to original state.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- None — test-only fix, no external deps
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] All 90 tests pass (`pytest tests/ -q`)
|
||||
- [ ] No compose.py files modified (only test files changed)
|
||||
25
.sdd/changes/archive/2026-05-03-compose-test-sync/tasks.md
Normal file
25
.sdd/changes/archive/2026-05-03-compose-test-sync/tasks.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Tasks: compose-test-sync
|
||||
|
||||
## Phase 1: Fix test_root_to_midi (test_section_builder.py)
|
||||
|
||||
- [ ] 1.1 In `tests/test_section_builder.py` line 121–124, replace `from scripts.compose import root_to_midi` with `from scripts.compose import NOTE_TO_MIDI`
|
||||
- [ ] 1.2 Change `root_to_midi("A", 4)` to `NOTE_TO_MIDI["A"] + 12 * (4 - 3)` (transposes A4 up one octave)
|
||||
- [ ] 1.3 Change `root_to_midi("C", 4)` to `NOTE_TO_MIDI["C"] + 12 * (4 - 3)` (transposes C4 up one octave)
|
||||
|
||||
## Phase 2: Fix test_main_without_render_produces_rpp (test_render_cli.py)
|
||||
|
||||
- [ ] 2.1 In `tests/test_render_cli.py` lines 44–80, remove `patch("scripts.compose.DrumLoopAnalyzer")` from the `with` statement (line 58)
|
||||
- [ ] 2.2 Remove the `DrumLoopAnalysis` import from `src.composer.drum_analyzer` (line 46) — no longer needed
|
||||
- [ ] 2.3 Remove `fake_analysis` fixture creation (lines 49–55)
|
||||
- [ ] 2.4 Remove `mock_a = MagicMock()` and its `.analyze.return_value = fake_analysis` assignment (lines 68–69)
|
||||
- [ ] 2.5 Remove `mock_a_cls.return_value = mock_a` assignment (line 70)
|
||||
|
||||
## Phase 3: Fix test_melody_uses_pentatonic (test_compose_integration.py)
|
||||
|
||||
- [ ] 3.1 In `tests/test_compose_integration.py` line 204, remove the `analysis = _fake_analysis()` line (dead code — function no longer takes analysis arg)
|
||||
- [ ] 3.2 Line 205: change `SectionDef(name="verse", ...)` to `SectionDef(name="chorus", ...)` (build_lead_track only generates for chorus/chorus2/final)
|
||||
|
||||
## Phase 4: Verification
|
||||
|
||||
- [ ] 4.1 Run `python -m pytest tests/ -q` — confirm 90/90 tests pass
|
||||
- [ ] 4.2 Run `git diff --name-only scripts/` — confirm no files in scripts/ were modified
|
||||
Reference in New Issue
Block a user